From 4f47327287c00836a9826805a8799678d2c18516 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 15 Jun 2025 18:55:57 +0900 Subject: [PATCH 0001/2435] Update current namespace management by using control frames and lexical contexts to fix inconsistent and wrong current namespace detections. This includes: * Moving load_path and related things from rb_vm_t to rb_namespace_t to simplify accessing those values via namespace (instead of accessing either vm or ns) * Initializing root_namespace earlier and consolidate builtin_namespace into root_namespace * Adding VM_FRAME_FLAG_NS_REQUIRE for checkpoints to detect a namespace to load/require files * Removing implicit refinements in the root namespace which was used to determine the namespace to be loaded (replaced by VM_FRAME_FLAG_NS_REQUIRE) * Removing namespaces from rb_proc_t because its namespace can be identified by lexical context * Starting to use ep[VM_ENV_DATA_INDEX_SPECVAL] to store the current namespace when the frame type is MAGIC_TOP or MAGIC_CLASS (block handlers don't exist in this case) --- builtin.c | 11 +- class.c | 2 +- eval.c | 1 + eval_intern.h | 5 +- inits.c | 2 +- insns.def | 3 +- internal/class.h | 16 +- internal/inits.h | 3 + internal/namespace.h | 21 +-- iseq.c | 2 +- load.c | 372 ++++++++++++------------------------ mini_builtin.c | 19 +- namespace.c | 437 +++++++++++++++---------------------------- proc.c | 2 - ruby.c | 16 +- variable.c | 66 +------ vm.c | 225 +++++++++++++--------- vm_core.h | 57 +++--- vm_dump.c | 16 +- vm_insnhelper.c | 21 --- vm_insnhelper.h | 2 +- 21 files changed, 487 insertions(+), 812 deletions(-) diff --git a/builtin.c b/builtin.c index 3400c4976ef526..158b98568530cb 100644 --- a/builtin.c +++ b/builtin.c @@ -50,17 +50,8 @@ load_with_builtin_functions(const char *feature_name, const struct rb_builtin_fu ASSUME(iseq); // otherwise an exception should have raised vm->builtin_function_table = NULL; - rb_namespace_enable_builtin(); - // exec - if (rb_namespace_available() && rb_mNamespaceRefiner) { - rb_iseq_eval_with_refinement(rb_iseq_check(iseq), rb_mNamespaceRefiner); - } - else { - rb_iseq_eval(rb_iseq_check(iseq)); - } - - rb_namespace_disable_builtin(); + rb_iseq_eval(rb_iseq_check(iseq), rb_root_namespace()); // builtin functions are loaded in the root namespace } void diff --git a/class.c b/class.c index b15f9f5ae7258d..e133d1579b3dd3 100644 --- a/class.c +++ b/class.c @@ -650,7 +650,7 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) { rb_ns_subclasses_t *ns_subclasses; rb_subclass_anchor_t *anchor; - const rb_namespace_t *ns = rb_definition_namespace(); + const rb_namespace_t *ns = rb_current_namespace(); if (!ruby_namespace_init_done) { namespaceable = true; diff --git a/eval.c b/eval.c index 019a2d19a25f3b..0ff59efd988975 100644 --- a/eval.c +++ b/eval.c @@ -80,6 +80,7 @@ ruby_setup(void) rb_vm_encoded_insn_data_table_init(); Init_enable_namespace(); Init_vm_objects(); + Init_root_namespace(); Init_fstring_table(); EC_PUSH_TAG(GET_EC()); diff --git a/eval_intern.h b/eval_intern.h index 2c244aa5e09b40..6353319c6ffe83 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -296,7 +296,10 @@ VALUE rb_vm_make_jump_tag_but_local_jump(enum ruby_tag_type state, VALUE val); rb_cref_t *rb_vm_cref(void); rb_cref_t *rb_vm_cref_replace_with_duplicated_cref(void); VALUE rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, VALUE block_handler, VALUE filename); -VALUE rb_vm_call_cfunc2(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, VALUE block_handler, VALUE filename); +VALUE rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, VALUE filename, const rb_namespace_t *ns); +void rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec); +const rb_namespace_t *rb_vm_current_namespace(const rb_execution_context_t *ec); +const rb_namespace_t *rb_vm_loading_namespace(const rb_execution_context_t *ec); void rb_vm_set_progname(VALUE filename); VALUE rb_vm_cbase(void); diff --git a/inits.c b/inits.c index e0dab9e890fbd8..5209e429f996e9 100644 --- a/inits.c +++ b/inits.c @@ -52,6 +52,7 @@ rb_call_inits(void) CALL(Time); CALL(Random); CALL(load); + CALL(Namespace); CALL(Proc); CALL(Binding); CALL(Math); @@ -78,7 +79,6 @@ rb_call_inits(void) CALL(Prism); CALL(unicode_version); CALL(Set); - CALL(Namespace); // enable builtin loading CALL(builtin); diff --git a/insns.def b/insns.def index 35afe28a1a5905..239fe85aa51439 100644 --- a/insns.def +++ b/insns.def @@ -803,12 +803,13 @@ defineclass (VALUE val) { VALUE klass = vm_find_or_create_class_by_id(id, flags, cbase, super); + const rb_namespace_t *ns = rb_current_namespace(); rb_iseq_check(class_iseq); /* enter scope */ vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass, - GET_BLOCK_HANDLER(), + GC_GUARDED_PTR(ns), (VALUE)vm_cref_push(ec, klass, NULL, FALSE, FALSE), ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(), ISEQ_BODY(class_iseq)->local_table_size, diff --git a/internal/class.h b/internal/class.h index bed69adef7cd1a..29b3526cf55cfd 100644 --- a/internal/class.h +++ b/internal/class.h @@ -390,8 +390,7 @@ RCLASS_EXT_READABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) static inline rb_classext_t * RCLASS_EXT_READABLE_IN_NS(VALUE obj, const rb_namespace_t *ns) { - if (!ns - || NAMESPACE_BUILTIN_P(ns) + if (NAMESPACE_ROOT_P(ns) || RCLASS_PRIME_CLASSEXT_READABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } @@ -405,9 +404,9 @@ RCLASS_EXT_READABLE(VALUE obj) if (RCLASS_PRIME_CLASSEXT_READABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } - // delay namespace loading to optimize for unmodified classes + // delay determining the current namespace to optimize for unmodified classes ns = rb_current_namespace(); - if (!ns || NAMESPACE_BUILTIN_P(ns)) { + if (NAMESPACE_ROOT_P(ns)) { return RCLASS_EXT_PRIME(obj); } return RCLASS_EXT_READABLE_LOOKUP(obj, ns); @@ -440,8 +439,7 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) static inline rb_classext_t * RCLASS_EXT_WRITABLE_IN_NS(VALUE obj, const rb_namespace_t *ns) { - if (!ns - || NAMESPACE_BUILTIN_P(ns) + if (NAMESPACE_ROOT_P(ns) || RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } @@ -455,11 +453,9 @@ RCLASS_EXT_WRITABLE(VALUE obj) if (LIKELY(RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj))) { return RCLASS_EXT_PRIME(obj); } - // delay namespace loading to optimize for unmodified classes + // delay determining the current namespace to optimize for unmodified classes ns = rb_current_namespace(); - if (!ns || NAMESPACE_BUILTIN_P(ns)) { - // If no namespace is specified, Ruby VM is in bootstrap - // and the clean class definition is under construction. + if (NAMESPACE_ROOT_P(ns)) { return RCLASS_EXT_PRIME(obj); } return RCLASS_EXT_WRITABLE_LOOKUP(obj, ns); diff --git a/internal/inits.h b/internal/inits.h index e618d87cc335d2..c1cf3db94d664c 100644 --- a/internal/inits.h +++ b/internal/inits.h @@ -32,6 +32,9 @@ void Init_enable_namespace(void); void Init_BareVM(void); void Init_vm_objects(void); +/* namespace.c */ +void Init_root_namespace(void); + /* vm_backtrace.c */ void Init_vm_backtrace(void); diff --git a/internal/namespace.h b/internal/namespace.h index 4cdfbc305fe1da..c68f0987aa814f 100644 --- a/internal/namespace.h +++ b/internal/namespace.h @@ -35,13 +35,14 @@ struct rb_namespace_struct { VALUE gvar_tbl; - bool is_builtin; bool is_user; bool is_optional; }; typedef struct rb_namespace_struct rb_namespace_t; -#define NAMESPACE_BUILTIN_P(ns) (ns && ns->is_builtin) +#define NAMESPACE_OBJ_P(obj) (CLASS_OF(obj) == rb_cNamespace) + +#define NAMESPACE_ROOT_P(ns) (ns && !ns->is_user) #define NAMESPACE_USER_P(ns) (ns && ns->is_user) #define NAMESPACE_OPTIONAL_P(ns) (ns && ns->is_optional) #define NAMESPACE_MAIN_P(ns) (ns && ns->is_user && !ns->is_optional) @@ -60,24 +61,16 @@ rb_namespace_available(void) return ruby_namespace_enabled; } -void rb_namespace_enable_builtin(void); -void rb_namespace_disable_builtin(void); -void rb_namespace_push_loading_namespace(const rb_namespace_t *); -void rb_namespace_pop_loading_namespace(const rb_namespace_t *); -rb_namespace_t * rb_root_namespace(void); -const rb_namespace_t *rb_builtin_namespace(void); -rb_namespace_t * rb_main_namespace(void); -const rb_namespace_t * rb_definition_namespace(void); -const rb_namespace_t * rb_loading_namespace(void); +const rb_namespace_t * rb_root_namespace(void); +const rb_namespace_t * rb_main_namespace(void); const rb_namespace_t * rb_current_namespace(void); -VALUE rb_current_namespace_details(VALUE); +const rb_namespace_t * rb_loading_namespace(void); void rb_namespace_entry_mark(void *); +void rb_namespace_gc_update_references(void *ptr); rb_namespace_t * rb_get_namespace_t(VALUE ns); VALUE rb_get_namespace_object(rb_namespace_t *ns); -typedef VALUE namespace_exec_func(VALUE arg); -VALUE rb_namespace_exec(const rb_namespace_t *ns, namespace_exec_func *func, VALUE arg); VALUE rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path); diff --git a/iseq.c b/iseq.c index 082ea28c4ad199..ad9149ef98e815 100644 --- a/iseq.c +++ b/iseq.c @@ -1966,7 +1966,7 @@ iseqw_eval(VALUE self) if (0 == ISEQ_BODY(iseq)->iseq_size) { rb_raise(rb_eTypeError, "attempt to evaluate dummy InstructionSequence"); } - return rb_iseq_eval(iseq); + return rb_iseq_eval(iseq, rb_current_namespace()); } /* diff --git a/load.c b/load.c index 217d7bad847fa5..6f3da288aaafcc 100644 --- a/load.c +++ b/load.c @@ -23,8 +23,6 @@ #include "ractor_core.h" #include "vm_core.h" -static VALUE ruby_dln_libmap; - #define IS_RBEXT(e) (strcmp((e), ".rb") == 0) #define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0) #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) @@ -39,38 +37,6 @@ static VALUE ruby_dln_libmap; # error Need integer for VALUE #endif -#define IS_NAMESPACE(obj) (CLASS_OF(obj) == rb_cNamespace) - -struct vm_and_namespace_struct { - rb_vm_t *vm; - rb_namespace_t *ns; -}; -typedef struct vm_and_namespace_struct vm_ns_t; -#define GET_vm_ns() vm_ns_t vm_ns_v = { .vm = GET_VM(), .ns = (rb_namespace_t *)rb_current_namespace(), }; vm_ns_t *vm_ns = &vm_ns_v; -#define GET_loading_vm_ns() vm_ns_t vm_ns_v = { .vm = GET_VM(), .ns = (rb_namespace_t *)rb_loading_namespace(), }; vm_ns_t *vm_ns = &vm_ns_v; - -#define CURRENT_NS_attr(vm_ns, attr) (NAMESPACE_USER_P(vm_ns->ns) ? vm_ns->ns->attr : vm_ns->vm->attr) -#define SET_NS_attr(vm_ns, attr, value) do { \ - if (NAMESPACE_USER_P(vm_ns->ns)) { vm_ns->ns->attr = value; } \ - else { vm_ns->vm->attr = value; } \ -} while (0) - -#define SET_NS_LOAD_PATH_CHECK_CACHE(vm_ns, value) SET_NS_attr(vm_ns, load_path_check_cache, value) -#define SET_NS_EXPANDED_LOAD_PATH(vm_ns, value) SET_NS_attr(vm_ns, expanded_load_path, value) - -#define CURRENT_NS_LOAD_PATH(vm_ns) CURRENT_NS_attr(vm_ns, load_path) -#define CURRENT_NS_LOAD_PATH_SNAPSHOT(vm_ns) CURRENT_NS_attr(vm_ns, load_path_snapshot) -#define CURRENT_NS_LOAD_PATH_CHECK_CACHE(vm_ns) CURRENT_NS_attr(vm_ns, load_path_check_cache) -#define CURRENT_NS_EXPANDED_LOAD_PATH(vm_ns) CURRENT_NS_attr(vm_ns, expanded_load_path) -#define CURRENT_NS_LOADING_TABLE(vm_ns) CURRENT_NS_attr(vm_ns, loading_table) -#define CURRENT_NS_LOADED_FEATURES(vm_ns) CURRENT_NS_attr(vm_ns, loaded_features) -#define CURRENT_NS_LOADED_FEATURES_SNAPSHOT(vm_ns) CURRENT_NS_attr(vm_ns, loaded_features_snapshot) -#define CURRENT_NS_LOADED_FEATURES_REALPATHS(vm_ns) CURRENT_NS_attr(vm_ns, loaded_features_realpaths) -#define CURRENT_NS_LOADED_FEATURES_REALPATH_MAP(vm_ns) CURRENT_NS_attr(vm_ns, loaded_features_realpath_map) -#define CURRENT_NS_LOADED_FEATURES_INDEX(vm_ns) CURRENT_NS_attr(vm_ns, loaded_features_index) - -#define CURRENT_NS_RUBY_DLN_LIBMAP(vm_ns, map) (NAMESPACE_USER_P(vm_ns->ns) ? vm_ns->ns->ruby_dln_libmap : map) - enum { loadable_ext_rb = (0+ /* .rb extension is the first in both tables */ 1) /* offset by rb_find_file_ext() */ @@ -99,10 +65,10 @@ enum expand_type { string objects in $LOAD_PATH are frozen. */ static void -rb_construct_expanded_load_path(vm_ns_t *vm_ns, enum expand_type type, int *has_relative, int *has_non_cache) +rb_construct_expanded_load_path(rb_namespace_t *ns, enum expand_type type, int *has_relative, int *has_non_cache) { - VALUE load_path = CURRENT_NS_LOAD_PATH(vm_ns); - VALUE expanded_load_path = CURRENT_NS_EXPANDED_LOAD_PATH(vm_ns); + VALUE load_path = ns->load_path; + VALUE expanded_load_path = ns->expanded_load_path; VALUE snapshot; VALUE ary; long i; @@ -142,39 +108,39 @@ rb_construct_expanded_load_path(vm_ns_t *vm_ns, enum expand_type type, int *has_ rb_ary_push(ary, rb_fstring(expanded_path)); } rb_ary_freeze(ary); - SET_NS_EXPANDED_LOAD_PATH(vm_ns, ary); - snapshot = CURRENT_NS_LOAD_PATH_SNAPSHOT(vm_ns); - load_path = CURRENT_NS_LOAD_PATH(vm_ns); + ns->expanded_load_path = ary; + snapshot = ns->load_path_snapshot; + load_path = ns->load_path; rb_ary_replace(snapshot, load_path); } static VALUE -get_expanded_load_path(vm_ns_t *vm_ns) +get_expanded_load_path(rb_namespace_t *ns) { VALUE check_cache; const VALUE non_cache = Qtrue; - const VALUE load_path_snapshot = CURRENT_NS_LOAD_PATH_SNAPSHOT(vm_ns); - const VALUE load_path = CURRENT_NS_LOAD_PATH(vm_ns); + const VALUE load_path_snapshot = ns->load_path_snapshot; + const VALUE load_path = ns->load_path; if (!rb_ary_shared_with_p(load_path_snapshot, load_path)) { /* The load path was modified. Rebuild the expanded load path. */ int has_relative = 0, has_non_cache = 0; - rb_construct_expanded_load_path(vm_ns, EXPAND_ALL, &has_relative, &has_non_cache); + rb_construct_expanded_load_path(ns, EXPAND_ALL, &has_relative, &has_non_cache); if (has_relative) { - SET_NS_LOAD_PATH_CHECK_CACHE(vm_ns, rb_dir_getwd_ospath()); + ns->load_path_check_cache = rb_dir_getwd_ospath(); } else if (has_non_cache) { /* Non string object. */ - SET_NS_LOAD_PATH_CHECK_CACHE(vm_ns, non_cache); + ns->load_path_check_cache = non_cache; } else { - SET_NS_LOAD_PATH_CHECK_CACHE(vm_ns, 0); + ns->load_path_check_cache = 0; } } - else if ((check_cache = CURRENT_NS_LOAD_PATH_CHECK_CACHE(vm_ns)) == non_cache) { + else if ((check_cache = ns->load_path_check_cache) == non_cache) { int has_relative = 1, has_non_cache = 1; /* Expand only non-cacheable objects. */ - rb_construct_expanded_load_path(vm_ns, EXPAND_NON_CACHE, + rb_construct_expanded_load_path(ns, EXPAND_NON_CACHE, &has_relative, &has_non_cache); } else if (check_cache) { @@ -183,76 +149,49 @@ get_expanded_load_path(vm_ns_t *vm_ns) if (!rb_str_equal(check_cache, cwd)) { /* Current working directory or filesystem encoding was changed. Expand relative load path and non-cacheable objects again. */ - SET_NS_LOAD_PATH_CHECK_CACHE(vm_ns, cwd); - rb_construct_expanded_load_path(vm_ns, EXPAND_RELATIVE, + ns->load_path_check_cache = cwd; + rb_construct_expanded_load_path(ns, EXPAND_RELATIVE, &has_relative, &has_non_cache); } else { /* Expand only tilde (User HOME) and non-cacheable objects. */ - rb_construct_expanded_load_path(vm_ns, EXPAND_HOME, + rb_construct_expanded_load_path(ns, EXPAND_HOME, &has_relative, &has_non_cache); } } - return CURRENT_NS_EXPANDED_LOAD_PATH(vm_ns); + return ns->expanded_load_path; } VALUE rb_get_expanded_load_path(void) { - GET_loading_vm_ns(); - return get_expanded_load_path(vm_ns); -} - -static VALUE -load_path_getter(ID id, VALUE * p) -{ - GET_loading_vm_ns(); - return CURRENT_NS_LOAD_PATH(vm_ns); -} - -static VALUE -get_loaded_features(vm_ns_t *vm_ns) -{ - return CURRENT_NS_LOADED_FEATURES(vm_ns); -} - -static VALUE -get_loaded_features_realpaths(vm_ns_t *vm_ns) -{ - return CURRENT_NS_LOADED_FEATURES_REALPATHS(vm_ns); + return get_expanded_load_path((rb_namespace_t *)rb_loading_namespace()); } static VALUE -get_loaded_features_realpath_map(vm_ns_t *vm_ns) +load_path_getter(ID _x, VALUE * _y) { - return CURRENT_NS_LOADED_FEATURES_REALPATH_MAP(vm_ns); + return rb_loading_namespace()->load_path; } static VALUE get_LOADED_FEATURES(ID _x, VALUE *_y) { - GET_loading_vm_ns(); - return get_loaded_features(vm_ns); + return rb_loading_namespace()->loaded_features; } static void -reset_loaded_features_snapshot(vm_ns_t *vm_ns) +reset_loaded_features_snapshot(const rb_namespace_t *ns) { - VALUE snapshot = CURRENT_NS_LOADED_FEATURES_SNAPSHOT(vm_ns); - VALUE loaded_features = CURRENT_NS_LOADED_FEATURES(vm_ns); + VALUE snapshot = ns->loaded_features_snapshot; + VALUE loaded_features = ns->loaded_features; rb_ary_replace(snapshot, loaded_features); } static struct st_table * -get_loaded_features_index_raw(vm_ns_t *vm_ns) -{ - return CURRENT_NS_LOADED_FEATURES_INDEX(vm_ns); -} - -static st_table * -get_loading_table(vm_ns_t *vm_ns) +get_loaded_features_index_raw(const rb_namespace_t *ns) { - return CURRENT_NS_LOADING_TABLE(vm_ns); + return ns->loaded_features_index; } static st_data_t @@ -273,7 +212,7 @@ is_rbext_path(VALUE feature_path) typedef rb_darray(long) feature_indexes_t; struct features_index_add_single_args { - vm_ns_t *vm_ns; + const rb_namespace_t *ns; VALUE offset; bool rb; }; @@ -282,7 +221,7 @@ static int features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t raw_args, int existing) { struct features_index_add_single_args *args = (struct features_index_add_single_args *)raw_args; - vm_ns_t *vm_ns = args->vm_ns; + const rb_namespace_t *ns = args->ns; VALUE offset = args->offset; bool rb = args->rb; @@ -290,7 +229,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r VALUE this_feature_index = *value; if (FIXNUM_P(this_feature_index)) { - VALUE loaded_features = get_loaded_features(vm_ns); + VALUE loaded_features = ns->loaded_features; VALUE this_feature_path = RARRAY_AREF(loaded_features, FIX2LONG(this_feature_index)); feature_indexes_t feature_indexes; @@ -310,7 +249,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r long pos = -1; if (rb) { - VALUE loaded_features = get_loaded_features(vm_ns); + VALUE loaded_features = ns->loaded_features; for (size_t i = 0; i < rb_darray_size(feature_indexes); ++i) { long idx = rb_darray_get(feature_indexes, i); VALUE this_feature_path = RARRAY_AREF(loaded_features, idx); @@ -342,7 +281,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r } static void -features_index_add_single(vm_ns_t *vm_ns, const char* str, size_t len, VALUE offset, bool rb) +features_index_add_single(const rb_namespace_t *ns, const char* str, size_t len, VALUE offset, bool rb) { struct st_table *features_index; st_data_t short_feature_key; @@ -350,10 +289,10 @@ features_index_add_single(vm_ns_t *vm_ns, const char* str, size_t len, VALUE off Check_Type(offset, T_FIXNUM); short_feature_key = feature_key(str, len); - features_index = get_loaded_features_index_raw(vm_ns); + features_index = get_loaded_features_index_raw(ns); struct features_index_add_single_args args = { - .vm_ns = vm_ns, + .ns = ns, .offset = offset, .rb = rb, }; @@ -370,7 +309,7 @@ features_index_add_single(vm_ns_t *vm_ns, const char* str, size_t len, VALUE off relies on for its fast lookup. */ static void -features_index_add(vm_ns_t *vm_ns, VALUE feature, VALUE offset) +features_index_add(const rb_namespace_t *ns, VALUE feature, VALUE offset) { RUBY_ASSERT(rb_ractor_main_p()); @@ -398,14 +337,14 @@ features_index_add(vm_ns_t *vm_ns, VALUE feature, VALUE offset) if (p < feature_str) break; /* Now *p == '/'. We reach this point for every '/' in `feature`. */ - features_index_add_single(vm_ns, p + 1, feature_end - p - 1, offset, false); + features_index_add_single(ns, p + 1, feature_end - p - 1, offset, false); if (ext) { - features_index_add_single(vm_ns, p + 1, ext - p - 1, offset, rb); + features_index_add_single(ns, p + 1, ext - p - 1, offset, rb); } } - features_index_add_single(vm_ns, feature_str, feature_end - feature_str, offset, false); + features_index_add_single(ns, feature_str, feature_end - feature_str, offset, false); if (ext) { - features_index_add_single(vm_ns, feature_str, ext - feature_str, offset, rb); + features_index_add_single(ns, feature_str, ext - feature_str, offset, rb); } } @@ -419,31 +358,20 @@ loaded_features_index_clear_i(st_data_t key, st_data_t val, st_data_t arg) return ST_DELETE; } -void -rb_free_loaded_features_index(rb_vm_t *vm) -{ - /* Destructs vm->loaded_features_index directly because this is only for - the VM destruction */ - st_foreach(vm->loaded_features_index, loaded_features_index_clear_i, 0); - st_free_table(vm->loaded_features_index); -} - - - static st_table * -get_loaded_features_index(vm_ns_t *vm_ns) +get_loaded_features_index(const rb_namespace_t *ns) { int i; - VALUE features = CURRENT_NS_LOADED_FEATURES(vm_ns); - const VALUE snapshot = CURRENT_NS_LOADED_FEATURES_SNAPSHOT(vm_ns); + VALUE features = ns->loaded_features; + const VALUE snapshot = ns->loaded_features_snapshot; if (!rb_ary_shared_with_p(snapshot, features)) { /* The sharing was broken; something (other than us in rb_provide_feature()) modified loaded_features. Rebuild the index. */ - st_foreach(CURRENT_NS_LOADED_FEATURES_INDEX(vm_ns), loaded_features_index_clear_i, 0); + st_foreach(ns->loaded_features_index, loaded_features_index_clear_i, 0); - VALUE realpaths = CURRENT_NS_LOADED_FEATURES_REALPATHS(vm_ns); - VALUE realpath_map = CURRENT_NS_LOADED_FEATURES_REALPATH_MAP(vm_ns); + VALUE realpaths = ns->loaded_features_realpaths; + VALUE realpath_map = ns->loaded_features_realpath_map; VALUE previous_realpath_map = rb_hash_dup(realpath_map); rb_hash_clear(realpaths); rb_hash_clear(realpath_map); @@ -459,15 +387,15 @@ get_loaded_features_index(vm_ns_t *vm_ns) as_str = rb_fstring(as_str); if (as_str != entry) rb_ary_store(features, i, as_str); - features_index_add(vm_ns, as_str, INT2FIX(i)); + features_index_add(ns, as_str, INT2FIX(i)); } /* The user modified $LOADED_FEATURES, so we should restore the changes. */ if (!rb_ary_shared_with_p(features, CURRENT_NS_LOADED_FEATURES(vm_ns))) { - rb_ary_replace(CURRENT_NS_LOADED_FEATURES(vm_ns), features); + rb_ary_replace(ns->loaded_features, features); } reset_loaded_features_snapshot(vm_ns); - features = CURRENT_NS_LOADED_FEATURES_SNAPSHOT(vm_ns); + features = ns->loaded_features_snapshot; long j = RARRAY_LEN(features); for (i = 0; i < j; i++) { VALUE as_str = rb_ary_entry(features, i); @@ -481,7 +409,7 @@ get_loaded_features_index(vm_ns_t *vm_ns) rb_hash_aset(realpath_map, as_str, realpath); } } - return CURRENT_NS_LOADED_FEATURES_INDEX(vm_ns); + return ns->loaded_features_index; } /* This searches `load_path` for a value such that @@ -566,7 +494,7 @@ loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f) * 'u': unsuffixed */ static int -rb_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) +rb_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) { VALUE features, this_feature_index = Qnil, v, p, load_path = 0; const char *f, *e; @@ -587,8 +515,8 @@ rb_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int e elen = 0; type = 0; } - features = get_loaded_features(vm_ns); - features_index = get_loaded_features_index(vm_ns); + features = ns->loaded_features; + features_index = get_loaded_features_index(ns); key = feature_key(feature, strlen(feature)); /* We search `features` for an entry such that either @@ -636,7 +564,7 @@ rb_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int e if ((n = RSTRING_LEN(v)) < len) continue; if (strncmp(f, feature, len) != 0) { if (expanded) continue; - if (!load_path) load_path = get_expanded_load_path(vm_ns); + if (!load_path) load_path = get_expanded_load_path((rb_namespace_t *)ns); if (!(p = loaded_feature_path(f, n, feature, len, type, load_path))) continue; expanded = 1; @@ -656,14 +584,14 @@ rb_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int e } } - loading_tbl = get_loading_table(vm_ns); + loading_tbl = ns->loading_table; f = 0; if (!expanded && !rb_is_absolute_path(feature)) { struct loaded_feature_searching fs; fs.name = feature; fs.len = len; fs.type = type; - fs.load_path = load_path ? load_path : get_expanded_load_path(vm_ns); + fs.load_path = load_path ? load_path : get_expanded_load_path((rb_namespace_t *)ns); fs.result = 0; st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs); if ((f = fs.result) != 0) { @@ -718,7 +646,7 @@ rb_provided(const char *feature) } static int -feature_provided(vm_ns_t *vm_ns, const char *feature, const char **loading) +feature_provided(rb_namespace_t *ns, const char *feature, const char **loading) { const char *ext = strrchr(feature, '.'); VALUE fullpath = 0; @@ -730,15 +658,15 @@ feature_provided(vm_ns_t *vm_ns, const char *feature, const char **loading) } if (ext && !strchr(ext, '/')) { if (IS_RBEXT(ext)) { - if (rb_feature_p(vm_ns, feature, ext, TRUE, FALSE, loading)) return TRUE; + if (rb_feature_p(ns, feature, ext, TRUE, FALSE, loading)) return TRUE; return FALSE; } else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { - if (rb_feature_p(vm_ns, feature, ext, FALSE, FALSE, loading)) return TRUE; + if (rb_feature_p(ns, feature, ext, FALSE, FALSE, loading)) return TRUE; return FALSE; } } - if (rb_feature_p(vm_ns, feature, 0, TRUE, FALSE, loading)) + if (rb_feature_p(ns, feature, 0, TRUE, FALSE, loading)) return TRUE; RB_GC_GUARD(fullpath); return FALSE; @@ -747,30 +675,30 @@ feature_provided(vm_ns_t *vm_ns, const char *feature, const char **loading) int rb_feature_provided(const char *feature, const char **loading) { - GET_vm_ns(); - return feature_provided(vm_ns, feature, loading); + rb_namespace_t *ns = (rb_namespace_t *)rb_current_namespace(); + return feature_provided(ns, feature, loading); } static void -rb_provide_feature(vm_ns_t *vm_ns, VALUE feature) +rb_provide_feature(const rb_namespace_t *ns, VALUE feature) { VALUE features; - features = get_loaded_features(vm_ns); + features = ns->loaded_features; if (OBJ_FROZEN(features)) { rb_raise(rb_eRuntimeError, "$LOADED_FEATURES is frozen; cannot append feature"); } feature = rb_fstring(feature); - get_loaded_features_index(vm_ns); + get_loaded_features_index(ns); // If loaded_features and loaded_features_snapshot share the same backing // array, pushing into it would cause the whole array to be copied. // To avoid this we first clear loaded_features_snapshot. - rb_ary_clear(CURRENT_NS_LOADED_FEATURES_SNAPSHOT(vm_ns)); + rb_ary_clear(ns->loaded_features_snapshot); rb_ary_push(features, feature); - features_index_add(vm_ns, feature, INT2FIX(RARRAY_LEN(features)-1)); - reset_loaded_features_snapshot(vm_ns); + features_index_add(ns, feature, INT2FIX(RARRAY_LEN(features)-1)); + reset_loaded_features_snapshot(ns); } void @@ -780,8 +708,7 @@ rb_provide(const char *feature) * rb_provide() must use rb_current_namespace to store provided features * in the current namespace's loaded_features, etc. */ - GET_vm_ns(); - rb_provide_feature(vm_ns, rb_fstring_cstr(feature)); + rb_provide_feature(rb_current_namespace(), rb_fstring_cstr(feature)); } NORETURN(static void load_failed(VALUE)); @@ -799,35 +726,17 @@ realpath_internal_cached(VALUE hash, VALUE path) return realpath; } -struct iseq_eval_in_namespace_data { - const rb_iseq_t *iseq; - bool in_builtin; -}; - -static VALUE -iseq_eval_in_namespace(VALUE arg) -{ - struct iseq_eval_in_namespace_data *data = (struct iseq_eval_in_namespace_data *)arg; - if (rb_namespace_available() && data->in_builtin) { - return rb_iseq_eval_with_refinement(data->iseq, rb_mNamespaceRefiner); - } - else { - return rb_iseq_eval(data->iseq); - } -} - static inline void load_iseq_eval(rb_execution_context_t *ec, VALUE fname) { - GET_loading_vm_ns(); - const rb_namespace_t *loading_ns = rb_loading_namespace(); + const rb_namespace_t *ns = rb_loading_namespace(); const rb_iseq_t *iseq = rb_iseq_load_iseq(fname); if (!iseq) { rb_execution_context_t *ec = GET_EC(); VALUE v = rb_vm_push_frame_fname(ec, fname); - VALUE realpath_map = get_loaded_features_realpath_map(vm_ns); + VALUE realpath_map = ns->loaded_features_realpath_map; if (rb_ruby_prism_p()) { pm_parse_result_t result = { 0 }; @@ -872,16 +781,7 @@ load_iseq_eval(rb_execution_context_t *ec, VALUE fname) } rb_exec_event_hook_script_compiled(ec, iseq, Qnil); - if (loading_ns) { - struct iseq_eval_in_namespace_data arg = { - .iseq = iseq, - .in_builtin = NAMESPACE_BUILTIN_P(loading_ns), - }; - rb_namespace_exec(loading_ns, iseq_eval_in_namespace, (VALUE)&arg); - } - else { - rb_iseq_eval(iseq); - } + rb_iseq_eval(iseq, ns); } static inline enum ruby_tag_type @@ -899,7 +799,7 @@ load_wrapping(rb_execution_context_t *ec, VALUE fname, VALUE load_wrapper) ec->errinfo = Qnil; /* ensure */ /* load in module as toplevel */ - if (IS_NAMESPACE(load_wrapper)) { + if (NAMESPACE_OBJ_P(load_wrapper)) { ns = rb_get_namespace_t(load_wrapper); if (!ns->top_self) { ns->top_self = rb_obj_clone(rb_vm_top_self()); @@ -1058,10 +958,10 @@ rb_f_load(int argc, VALUE *argv, VALUE _) } static char * -load_lock(vm_ns_t *vm_ns, const char *ftptr, bool warn) +load_lock(const rb_namespace_t *ns, const char *ftptr, bool warn) { st_data_t data; - st_table *loading_tbl = get_loading_table(vm_ns); + st_table *loading_tbl = ns->loading_table; if (!st_lookup(loading_tbl, (st_data_t)ftptr, &data)) { /* partial state */ @@ -1103,11 +1003,11 @@ release_thread_shield(st_data_t *key, st_data_t *value, st_data_t done, int exis } static void -load_unlock(vm_ns_t *vm_ns, const char *ftptr, int done) +load_unlock(const rb_namespace_t *ns, const char *ftptr, int done) { if (ftptr) { st_data_t key = (st_data_t)ftptr; - st_table *loading_tbl = get_loading_table(vm_ns); + st_table *loading_tbl = ns->loading_table; st_update(loading_tbl, key, release_thread_shield, done); } @@ -1186,10 +1086,10 @@ rb_f_require_relative(VALUE obj, VALUE fname) return rb_require_relative_entrypoint(fname); } -typedef int (*feature_func)(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int expanded, const char **fn); +typedef int (*feature_func)(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn); static int -search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func rb_feature_p) +search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, feature_func rb_feature_p) { VALUE tmp; char *ext, *ftptr; @@ -1200,20 +1100,20 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func ext = strrchr(ftptr = RSTRING_PTR(fname), '.'); if (ext && !strchr(ext, '/')) { if (IS_RBEXT(ext)) { - if (rb_feature_p(vm_ns, ftptr, ext, TRUE, FALSE, &loading)) { + if (rb_feature_p(ns, ftptr, ext, TRUE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 'r'; } if ((tmp = rb_find_file(fname)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(vm_ns, ftptr, ext, TRUE, TRUE, &loading) || loading) + if (!rb_feature_p(ns, ftptr, ext, TRUE, TRUE, &loading) || loading) *path = tmp; return 'r'; } return 0; } else if (IS_SOEXT(ext)) { - if (rb_feature_p(vm_ns, ftptr, ext, FALSE, FALSE, &loading)) { + if (rb_feature_p(ns, ftptr, ext, FALSE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 's'; } @@ -1222,25 +1122,25 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func OBJ_FREEZE(tmp); if ((tmp = rb_find_file(tmp)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(vm_ns, ftptr, ext, FALSE, TRUE, &loading) || loading) + if (!rb_feature_p(ns, ftptr, ext, FALSE, TRUE, &loading) || loading) *path = tmp; return 's'; } } else if (IS_DLEXT(ext)) { - if (rb_feature_p(vm_ns, ftptr, ext, FALSE, FALSE, &loading)) { + if (rb_feature_p(ns, ftptr, ext, FALSE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 's'; } if ((tmp = rb_find_file(fname)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(vm_ns, ftptr, ext, FALSE, TRUE, &loading) || loading) + if (!rb_feature_p(ns, ftptr, ext, FALSE, TRUE, &loading) || loading) *path = tmp; return 's'; } } } - else if ((ft = rb_feature_p(vm_ns, ftptr, 0, FALSE, FALSE, &loading)) == 'r') { + else if ((ft = rb_feature_p(ns, ftptr, 0, FALSE, FALSE, &loading)) == 'r') { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 'r'; } @@ -1249,7 +1149,8 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func // Check if it's a statically linked extension when // not already a feature and not found as a dynamic library. - if (!ft && type != loadable_ext_rb && vm_ns->vm->static_ext_inits) { + rb_vm_t *vm = GET_VM(); + if (!ft && type != loadable_ext_rb && vm->static_ext_inits) { VALUE lookup_name = tmp; // Append ".so" if not already present so for example "etc" can find "etc.so". // We always register statically linked extensions with a ".so" extension. @@ -1259,7 +1160,7 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func rb_str_cat_cstr(lookup_name, ".so"); } ftptr = RSTRING_PTR(lookup_name); - if (st_lookup(vm_ns->vm->static_ext_inits, (st_data_t)ftptr, NULL)) { + if (st_lookup(vm->static_ext_inits, (st_data_t)ftptr, NULL)) { *path = rb_filesystem_str_new_cstr(ftptr); RB_GC_GUARD(lookup_name); return 's'; @@ -1271,7 +1172,7 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func if (ft) goto feature_present; ftptr = RSTRING_PTR(tmp); - return rb_feature_p(vm_ns, ftptr, 0, FALSE, TRUE, 0); + return rb_feature_p(ns, ftptr, 0, FALSE, TRUE, 0); default: if (ft) { @@ -1280,7 +1181,7 @@ search_required(vm_ns_t *vm_ns, VALUE fname, volatile VALUE *path, feature_func /* fall through */ case loadable_ext_rb: ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (rb_feature_p(vm_ns, ftptr, ext, type == loadable_ext_rb, TRUE, &loading) && !loading) + if (rb_feature_p(ns, ftptr, ext, type == loadable_ext_rb, TRUE, &loading) && !loading) break; *path = tmp; } @@ -1301,9 +1202,9 @@ static VALUE load_ext(VALUE path, VALUE fname) { VALUE loaded = path; - GET_loading_vm_ns(); - if (NAMESPACE_USER_P(vm_ns->ns)) { - loaded = rb_namespace_local_extension(vm_ns->ns->ns_object, fname, path); + const rb_namespace_t *ns = rb_loading_namespace(); + if (NAMESPACE_USER_P(ns)) { + loaded = rb_namespace_local_extension(ns->ns_object, fname, path); } rb_scope_visibility_set(METHOD_VISI_PUBLIC); return (VALUE)dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); @@ -1323,7 +1224,7 @@ run_static_ext_init(rb_vm_t *vm, const char *feature) } static int -no_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) +no_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) { return 0; } @@ -1335,11 +1236,11 @@ rb_resolve_feature_path(VALUE klass, VALUE fname) VALUE path; int found; VALUE sym; - GET_loading_vm_ns(); + const rb_namespace_t *ns = rb_loading_namespace(); fname = rb_get_path(fname); path = rb_str_encode_ospath(fname); - found = search_required(vm_ns, path, &path, no_feature_p); + found = search_required(ns, path, &path, no_feature_p); switch (found) { case 'r': @@ -1374,21 +1275,6 @@ rb_ext_ractor_safe(bool flag) GET_THREAD()->ext_config.ractor_safe = flag; } -struct rb_vm_call_cfunc2_data { - VALUE recv; - VALUE arg1; - VALUE arg2; - VALUE block_handler; - VALUE filename; -}; - -static VALUE -call_load_ext_in_ns(VALUE data) -{ - struct rb_vm_call_cfunc2_data *arg = (struct rb_vm_call_cfunc2_data *)data; - return rb_vm_call_cfunc2(arg->recv, load_ext, arg->arg1, arg->arg2, arg->block_handler, arg->filename); -} - /* * returns * 0: if already loaded (false) @@ -1408,14 +1294,14 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa th->top_wrapper, th->top_self, ec->errinfo, ec, }; - GET_loading_vm_ns(); + const rb_namespace_t *ns = rb_loading_namespace(); enum ruby_tag_type state; char *volatile ftptr = 0; VALUE path; volatile VALUE saved_path; volatile VALUE realpath = 0; - VALUE realpaths = get_loaded_features_realpaths(vm_ns); - VALUE realpath_map = get_loaded_features_realpath_map(vm_ns); + VALUE realpaths = ns->loaded_features_realpaths; + VALUE realpath_map = ns->loaded_features_realpath_map; volatile bool reset_ext_config = false; volatile struct rb_ext_config prev_ext_config; @@ -1431,12 +1317,12 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa int found; RUBY_DTRACE_HOOK(FIND_REQUIRE_ENTRY, RSTRING_PTR(fname)); - found = search_required(vm_ns, path, &saved_path, rb_feature_p); + found = search_required(ns, path, &saved_path, rb_feature_p); RUBY_DTRACE_HOOK(FIND_REQUIRE_RETURN, RSTRING_PTR(fname)); path = saved_path; if (found) { - if (!path || !(ftptr = load_lock(vm_ns, RSTRING_PTR(path), warn))) { + if (!path || !(ftptr = load_lock(ns, RSTRING_PTR(path), warn))) { result = 0; } else if (!*ftptr) { @@ -1453,10 +1339,10 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa switch (found) { case 'r': // iseq_eval_in_namespace will be called with the loading namespace eventually - if (NAMESPACE_OPTIONAL_P(vm_ns->ns)) { + if (NAMESPACE_OPTIONAL_P(ns)) { // check with NAMESPACE_OPTIONAL_P (not NAMESPACE_USER_P) for NS1::xxx naming // it is not expected for the main namespace - load_wrapping(saved.ec, path, vm_ns->ns->ns_object); + load_wrapping(saved.ec, path, ns->ns_object); } else { load_iseq_eval(saved.ec, path); @@ -1464,19 +1350,10 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa break; case 's': - // the loading namespace must be set to the current namespace before calling load_ext reset_ext_config = true; ext_config_push(th, &prev_ext_config); - struct rb_vm_call_cfunc2_data arg = { - .recv = rb_vm_top_self(), - .arg1 = path, - .arg2 = fname, - .block_handler = VM_BLOCK_HANDLER_NONE, - .filename = path, - }; - handle = rb_namespace_exec(vm_ns->ns, call_load_ext_in_ns, (VALUE)&arg); - rb_hash_aset(CURRENT_NS_RUBY_DLN_LIBMAP(vm_ns, ruby_dln_libmap), path, - SVALUE2NUM((SIGNED_VALUE)handle)); + handle = rb_vm_call_cfunc_in_namespace(ns->top_self, load_ext, path, fname, path, ns); + rb_hash_aset(ns->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle)); break; } result = TAG_RETURN; @@ -1492,7 +1369,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa if (reset_ext_config) ext_config_pop(th2, &prev_ext_config); path = saved_path; - if (ftptr) load_unlock(vm_ns, RSTRING_PTR(path), !state); + if (ftptr) load_unlock(ns, RSTRING_PTR(path), !state); if (state) { if (state == TAG_FATAL || state == TAG_THROW) { @@ -1518,7 +1395,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa } if (result == TAG_RETURN) { - rb_provide_feature(vm_ns, path); + rb_provide_feature(ns, path); VALUE real = realpath; if (real) { real = rb_fstring(real); @@ -1621,15 +1498,16 @@ void ruby_init_ext(const char *name, void (*init)(void)) { st_table *inits_table; - GET_loading_vm_ns(); + rb_vm_t *vm = GET_VM(); + const rb_namespace_t *ns = rb_loading_namespace(); - if (feature_provided(vm_ns, name, 0)) + if (feature_provided((rb_namespace_t *)ns, name, 0)) return; - inits_table = vm_ns->vm->static_ext_inits; + inits_table = vm->static_ext_inits; if (!inits_table) { inits_table = st_init_strtable(); - vm_ns->vm->static_ext_inits = inits_table; + vm->static_ext_inits = inits_table; } st_update(inits_table, (st_data_t)name, register_init_ext, (st_data_t)init); } @@ -1774,7 +1652,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) VALUE path; char *ext; VALUE fname_str = rb_str_new_cstr(fname); - GET_loading_vm_ns(); + const rb_namespace_t *ns = rb_loading_namespace(); resolved = rb_resolve_feature_path((VALUE)NULL, fname_str); if (NIL_P(resolved)) { @@ -1782,7 +1660,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) if (!ext || !IS_SOEXT(ext)) { rb_str_cat_cstr(fname_str, ".so"); } - if (rb_feature_p(vm_ns, fname, 0, FALSE, FALSE, 0)) { + if (rb_feature_p(ns, fname, 0, FALSE, FALSE, 0)) { return dln_symbol(NULL, symbol); } return NULL; @@ -1791,7 +1669,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) return NULL; } path = rb_ary_entry(resolved, 1); - handle = rb_hash_lookup(CURRENT_NS_RUBY_DLN_LIBMAP(vm_ns, ruby_dln_libmap), path); + handle = rb_hash_lookup(ns->ruby_dln_libmap, path); if (NIL_P(handle)) { return NULL; } @@ -1801,31 +1679,18 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) void Init_load(void) { - rb_vm_t *vm = GET_VM(); static const char var_load_path[] = "$:"; ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1); - rb_define_hooked_variable(var_load_path, (VALUE*)vm, load_path_getter, rb_gvar_readonly_setter); + rb_define_hooked_variable(var_load_path, 0, load_path_getter, rb_gvar_readonly_setter); rb_gvar_namespace_ready(var_load_path); rb_alias_variable(rb_intern_const("$-I"), id_load_path); rb_alias_variable(rb_intern_const("$LOAD_PATH"), id_load_path); - vm->load_path = rb_ary_new(); - vm->expanded_load_path = rb_ary_hidden_new(0); - vm->load_path_snapshot = rb_ary_hidden_new(0); - vm->load_path_check_cache = 0; - rb_define_singleton_method(vm->load_path, "resolve_feature_path", rb_resolve_feature_path, 1); rb_define_virtual_variable("$\"", get_LOADED_FEATURES, 0); rb_gvar_namespace_ready("$\""); rb_define_virtual_variable("$LOADED_FEATURES", get_LOADED_FEATURES, 0); // TODO: rb_alias_variable ? rb_gvar_namespace_ready("$LOADED_FEATURES"); - vm->loaded_features = rb_ary_new(); - vm->loaded_features_snapshot = rb_ary_hidden_new(0); - vm->loaded_features_index = st_init_numtable(); - vm->loaded_features_realpaths = rb_hash_new(); - rb_obj_hide(vm->loaded_features_realpaths); - vm->loaded_features_realpath_map = rb_hash_new(); - rb_obj_hide(vm->loaded_features_realpath_map); rb_define_global_function("load", rb_f_load, -1); rb_define_global_function("require", rb_f_require, 1); @@ -1834,7 +1699,4 @@ Init_load(void) rb_define_method(rb_cModule, "autoload?", rb_mod_autoload_p, -1); rb_define_global_function("autoload", rb_f_autoload, 2); rb_define_global_function("autoload?", rb_f_autoload_p, -1); - - ruby_dln_libmap = rb_hash_new_with_size(0); - rb_vm_register_global_object(ruby_dln_libmap); } diff --git a/mini_builtin.c b/mini_builtin.c index d9827e70e472b8..3ff3dc5c1d2a08 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -96,24 +96,9 @@ builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *ta return iseq; } -static void -load_with_builtin_functions(const char *feature_name, const struct rb_builtin_function *table) -{ - const rb_iseq_t *iseq = builtin_iseq_load(feature_name, table); - rb_namespace_enable_builtin(); - rb_iseq_eval_with_refinement(iseq, rb_mNamespaceRefiner); - rb_namespace_disable_builtin(); -} - void rb_load_with_builtin_functions(const char *feature_name, const struct rb_builtin_function *table) { - const rb_iseq_t *iseq; - if (rb_namespace_available() && rb_mNamespaceRefiner) { - load_with_builtin_functions(feature_name, table); - } - else { - iseq = builtin_iseq_load(feature_name, table); - rb_iseq_eval(iseq); - } + const rb_iseq_t *iseq = builtin_iseq_load(feature_name, table); + rb_iseq_eval(iseq, rb_root_namespace()); } diff --git a/namespace.c b/namespace.c index 28e2c63a81940e..d3ac255363822f 100644 --- a/namespace.c +++ b/namespace.c @@ -1,5 +1,6 @@ /* indent-tabs-mode: nil */ +#include "eval_intern.h" #include "internal.h" #include "internal/class.h" #include "internal/eval.h" @@ -19,18 +20,13 @@ VALUE rb_cNamespace = 0; VALUE rb_cNamespaceEntry = 0; -VALUE rb_mNamespaceRefiner = 0; VALUE rb_mNamespaceLoader = 0; -static rb_namespace_t builtin_namespace_data = { - .ns_object = Qnil, - .ns_id = 0, - .is_builtin = true, - .is_user = false, - .is_optional = false +static rb_namespace_t root_namespace_data = { + /* Initialize values lazily in Init_namespace() */ }; -static rb_namespace_t * const root_namespace = 0; -static rb_namespace_t * const builtin_namespace = &builtin_namespace_data; + +static rb_namespace_t * root_namespace = &root_namespace_data; static rb_namespace_t * main_namespace = 0; static char *tmp_dir; static bool tmp_dir_has_dirsep; @@ -52,8 +48,6 @@ bool ruby_namespace_init_done = false; // extern VALUE rb_resolve_feature_path(VALUE klass, VALUE fname); static VALUE rb_namespace_inspect(VALUE obj); -static void namespace_push(rb_thread_t *th, VALUE namespace); -static VALUE namespace_pop(VALUE th_value); void rb_namespace_init_done(void) @@ -61,72 +55,29 @@ rb_namespace_init_done(void) ruby_namespace_init_done = true; } -void -rb_namespace_enable_builtin(void) -{ - VALUE require_stack = GET_VM()->require_stack; - if (require_stack) { - rb_ary_push(require_stack, Qnil); - } -} - -void -rb_namespace_disable_builtin(void) -{ - VALUE require_stack = GET_VM()->require_stack; - if (require_stack) { - rb_ary_pop(require_stack); - } -} - -void -rb_namespace_push_loading_namespace(const rb_namespace_t *ns) -{ - VALUE require_stack = GET_VM()->require_stack; - rb_ary_push(require_stack, ns->ns_object); -} - -void -rb_namespace_pop_loading_namespace(const rb_namespace_t *ns) -{ - VALUE require_stack = GET_VM()->require_stack; - long size = RARRAY_LEN(require_stack); - if (size == 0) - rb_bug("popping on the empty require_stack"); - VALUE latest = RARRAY_AREF(require_stack, size-1); - if (latest != ns->ns_object) - rb_bug("Inconsistent loading namespace"); - rb_ary_pop(require_stack); -} - -rb_namespace_t * +const rb_namespace_t * rb_root_namespace(void) { return root_namespace; } const rb_namespace_t * -rb_builtin_namespace(void) -{ - return (const rb_namespace_t *)builtin_namespace; -} - -rb_namespace_t * rb_main_namespace(void) { return main_namespace; } +/* static bool namespace_ignore_builtin_primitive_methods_p(const rb_namespace_t *ns, rb_method_definition_t *def) { if (!NAMESPACE_BUILTIN_P(ns)) { return false; } - /* Primitive methods (just to call C methods) covers/hides the effective + / Primitive methods (just to call C methods) covers/hides the effective namespaces, so ignore the methods' namespaces to expose user code's namespace to the implementation. - */ + / if (def->type == VM_METHOD_TYPE_ISEQ) { ID mid = def->original_id; const char *path = RSTRING_PTR(pathobj_path(def->body.iseq.iseqptr->body->location.pathobj)); @@ -169,10 +120,10 @@ block_proc_namespace(const VALUE procval) static const rb_namespace_t * current_namespace(bool permit_calling_builtin) { - /* + / * TODO: move this code to vm.c or somewhere else * when it's fully updated with VM_FRAME_FLAG_* - */ + / const rb_callable_method_entry_t *cme; const rb_namespace_t *ns; rb_execution_context_t *ec = GET_EC(); @@ -227,16 +178,28 @@ current_namespace(bool permit_calling_builtin) } return main_namespace; } +*/ const rb_namespace_t * rb_current_namespace(void) { - return current_namespace(true); + /* + * If RUBY_NAMESPACE is not set, the root namespace is the only available one. + * + * Until the main_namespace is not initialized, the root namespace is + * the only valid namespace. + * This early return is to avoid accessing EC before its setup. + */ + if (!main_namespace) + return root_namespace; + + return rb_vm_current_namespace(GET_EC()); } const rb_namespace_t * rb_loading_namespace(void) { + /* VALUE namespace; long len; VALUE require_stack = GET_VM()->require_stack; @@ -256,16 +219,11 @@ rb_loading_namespace(void) namespace = RARRAY_AREF(require_stack, len-1); return rb_get_namespace_t(namespace); -} - -const rb_namespace_t * -rb_definition_namespace(void) -{ - const rb_namespace_t *ns = current_namespace(true); - if (NAMESPACE_BUILTIN_P(ns)) { + */ + if (!main_namespace) return root_namespace; - } - return ns; + + return rb_vm_loading_namespace(GET_EC()); } static long namespace_id_counter = 0; @@ -283,37 +241,43 @@ namespace_generate_id(void) static void namespace_entry_initialize(rb_namespace_t *ns) { - rb_vm_t *vm = GET_VM(); + const rb_namespace_t *root = rb_root_namespace(); // These will be updated immediately ns->ns_object = 0; ns->ns_id = 0; - ns->top_self = 0; - ns->load_path = rb_ary_dup(vm->load_path); - ns->expanded_load_path = rb_ary_dup(vm->expanded_load_path); + ns->top_self = rb_obj_alloc(rb_cObject); + // TODO: + // rb_define_singleton_method(rb_vm_top_self(), "to_s", main_to_s, 0); + // rb_define_alias(rb_singleton_class(rb_vm_top_self()), "inspect", "to_s"); + ns->load_path = rb_ary_dup(root->load_path); + ns->expanded_load_path = rb_ary_dup(root->expanded_load_path); ns->load_path_snapshot = rb_ary_new(); ns->load_path_check_cache = 0; - ns->loaded_features = rb_ary_dup(vm->loaded_features); + ns->loaded_features = rb_ary_dup(root->loaded_features); ns->loaded_features_snapshot = rb_ary_new(); ns->loaded_features_index = st_init_numtable(); - ns->loaded_features_realpaths = rb_hash_dup(vm->loaded_features_realpaths); - ns->loaded_features_realpath_map = rb_hash_dup(vm->loaded_features_realpath_map); + ns->loaded_features_realpaths = rb_hash_dup(root->loaded_features_realpaths); + ns->loaded_features_realpath_map = rb_hash_dup(root->loaded_features_realpath_map); ns->loading_table = st_init_strtable(); ns->ruby_dln_libmap = rb_hash_new_with_size(0); ns->gvar_tbl = rb_hash_new_with_size(0); - ns->is_builtin = false; ns->is_user = true; ns->is_optional = true; } -void rb_namespace_gc_update_references(void *ptr) +void +rb_namespace_gc_update_references(void *ptr) { rb_namespace_t *ns = (rb_namespace_t *)ptr; - if (!NIL_P(ns->ns_object)) + if (!ns) return; + + if (ns->ns_object) ns->ns_object = rb_gc_location(ns->ns_object); - ns->top_self = rb_gc_location(ns->top_self); + if (ns->top_self) + ns->top_self = rb_gc_location(ns->top_self); ns->load_path = rb_gc_location(ns->load_path); ns->expanded_load_path = rb_gc_location(ns->expanded_load_path); ns->load_path_snapshot = rb_gc_location(ns->load_path_snapshot); @@ -332,6 +296,8 @@ void rb_namespace_entry_mark(void *ptr) { const rb_namespace_t *ns = (rb_namespace_t *)ptr; + if (!ns) return; + rb_gc_mark(ns->ns_object); rb_gc_mark(ns->top_self); rb_gc_mark(ns->load_path); @@ -349,11 +315,26 @@ rb_namespace_entry_mark(void *ptr) rb_gc_mark(ns->gvar_tbl); } +// TODO: implemente namespace_entry_free to free loading_table etc +/* +static int +free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg) +{ + xfree((char *)key); + return ST_DELETE; +} + if (vm->loading_table) { + st_foreach(vm->loading_table, free_loading_table_entry, 0); + st_free_table(vm->loading_table); + vm->loading_table = 0; + } +*/ #define namespace_entry_free RUBY_TYPED_DEFAULT_FREE static size_t namespace_entry_memsize(const void *ptr) { + // TODO: rb_st_memsize(loaded_features_index) + rb_st_memsize(vm->loading_table) return sizeof(rb_namespace_t); } @@ -391,10 +372,12 @@ rb_get_namespace_t(VALUE namespace) VALUE entry; ID id_namespace_entry; - if (!namespace) - return root_namespace; + VM_ASSERT(namespace); + if (NIL_P(namespace)) - return builtin_namespace; + return root_namespace; + + VM_ASSERT(NAMESPACE_OBJ_P(namespace)); CONST_ID(id_namespace_entry, "__namespace_entry__"); entry = rb_attr_get(namespace, id_namespace_entry); @@ -404,13 +387,10 @@ rb_get_namespace_t(VALUE namespace) VALUE rb_get_namespace_object(rb_namespace_t *ns) { - if (!ns) // root namespace - return Qfalse; + VM_ASSERT(ns && ns->ns_object); return ns->ns_object; } -static void setup_pushing_loading_namespace(rb_namespace_t *ns); - /* * call-seq: * Namespace.new -> new_namespace @@ -435,8 +415,6 @@ namespace_initialize(VALUE namespace) ns->ns_object = namespace; ns->ns_id = namespace_generate_id(); - ns->load_path = rb_ary_dup(GET_VM()->load_path); - ns->is_user = true; rb_define_singleton_method(ns->load_path, "resolve_feature_path", rb_resolve_feature_path, 1); // Set the Namespace object unique/consistent from any namespaces to have just single @@ -451,8 +429,6 @@ namespace_initialize(VALUE namespace) rb_ivar_set(namespace, id_namespace_entry, entry); - setup_pushing_loading_namespace(ns); - return namespace; } @@ -480,13 +456,12 @@ static VALUE rb_namespace_current(VALUE klass) { const rb_namespace_t *ns = rb_current_namespace(); - if (NAMESPACE_USER_P(ns)) { - return ns->ns_object; - } - if (NAMESPACE_BUILTIN_P(ns)) { + + if (!rb_namespace_available()) return Qnil; - } - return Qfalse; + + VM_ASSERT(ns && ns->ns_object); + return ns->ns_object; } /* @@ -512,6 +487,7 @@ rb_namespace_s_is_builtin_p(VALUE namespace, VALUE klass) static VALUE rb_namespace_load_path(VALUE namespace) { + VM_ASSERT(NAMESPACE_OBJ_P(namespace)); return rb_get_namespace_t(namespace)->load_path; } @@ -791,108 +767,79 @@ rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) // At least for _WIN32, deleting extension files should be delayed until the namespace's destructor. // And it requires calling dlclose before deleting it. -static void -namespace_push(rb_thread_t *th, VALUE namespace) -{ - if (RTEST(th->namespaces)) { - rb_ary_push(th->namespaces, namespace); - } - else { - th->namespaces = rb_ary_new_from_args(1, namespace); - } - th->ns = rb_get_namespace_t(namespace); -} - -static VALUE -namespace_pop(VALUE th_value) -{ - VALUE upper_ns; - long stack_len; - rb_thread_t *th = (rb_thread_t *)th_value; - VALUE namespaces = th->namespaces; - if (!namespaces) { - rb_bug("Too many namespace pops"); - } - rb_ary_pop(namespaces); - stack_len = RARRAY_LEN(namespaces); - if (stack_len == 0) { - th->namespaces = 0; - th->ns = main_namespace; - } - else { - upper_ns = RARRAY_AREF(namespaces, stack_len-1); - th->ns = rb_get_namespace_t(upper_ns); - } - return Qnil; -} - -VALUE -rb_namespace_exec(const rb_namespace_t *ns, namespace_exec_func *func, VALUE arg) -{ - rb_thread_t *th = GET_THREAD(); - namespace_push(th, ns ? ns->ns_object : Qnil); - return rb_ensure(func, arg, namespace_pop, (VALUE)th); -} - -struct namespace_pop2_arg { - rb_thread_t *th; - rb_namespace_t *ns; -}; - -static VALUE -namespace_both_pop(VALUE arg) -{ - struct namespace_pop2_arg *data = (struct namespace_pop2_arg *)arg; - namespace_pop((VALUE) data->th); - rb_namespace_pop_loading_namespace(data->ns); - return Qnil; -} - static VALUE rb_namespace_load(int argc, VALUE *argv, VALUE namespace) { VALUE fname, wrap; - rb_thread_t *th = GET_THREAD(); - rb_namespace_t *ns = rb_get_namespace_t(namespace); - rb_scan_args(argc, argv, "11", &fname, &wrap); + rb_vm_frame_flag_set_ns_require(GET_EC()); + VALUE args = rb_ary_new_from_args(2, fname, wrap); - namespace_push(th, namespace); - rb_namespace_push_loading_namespace(ns); - struct namespace_pop2_arg arg = { - .th = th, - .ns = ns - }; - return rb_ensure(rb_load_entrypoint, args, namespace_both_pop, (VALUE)&arg); + return rb_load_entrypoint(args); } static VALUE rb_namespace_require(VALUE namespace, VALUE fname) { - rb_thread_t *th = GET_THREAD(); - rb_namespace_t *ns = rb_get_namespace_t(namespace); - namespace_push(th, namespace); - rb_namespace_push_loading_namespace(ns); - struct namespace_pop2_arg arg = { - .th = th, - .ns = ns - }; - return rb_ensure(rb_require_string, fname, namespace_both_pop, (VALUE)&arg); + rb_vm_frame_flag_set_ns_require(GET_EC()); + + return rb_require_string(fname); } static VALUE rb_namespace_require_relative(VALUE namespace, VALUE fname) { - rb_thread_t *th = GET_THREAD(); - rb_namespace_t *ns = rb_get_namespace_t(namespace); - namespace_push(th, namespace); - rb_namespace_push_loading_namespace(ns); - struct namespace_pop2_arg arg = { - .th = th, - .ns = ns - }; - return rb_ensure(rb_require_relative_entrypoint, fname, namespace_both_pop, (VALUE)&arg); + rb_vm_frame_flag_set_ns_require(GET_EC()); + + return rb_require_relative_entrypoint(fname); +} + +static void +initialize_root_namespace(void) +{ + VALUE root_namespace, entry; + ID id_namespace_entry; + rb_vm_t *vm = GET_VM(); + rb_namespace_t *root = (rb_namespace_t *)rb_root_namespace(); + + root->load_path = rb_ary_new(); + root->expanded_load_path = rb_ary_hidden_new(0); + root->load_path_snapshot = rb_ary_hidden_new(0); + root->load_path_check_cache = 0; + rb_define_singleton_method(root->load_path, "resolve_feature_path", rb_resolve_feature_path, 1); + + root->loaded_features = rb_ary_new(); + root->loaded_features_snapshot = rb_ary_hidden_new(0); + root->loaded_features_index = st_init_numtable(); + root->loaded_features_realpaths = rb_hash_new(); + rb_obj_hide(root->loaded_features_realpaths); + root->loaded_features_realpath_map = rb_hash_new(); + rb_obj_hide(root->loaded_features_realpath_map); + + root->ruby_dln_libmap = rb_hash_new_with_size(0); + root->gvar_tbl = rb_hash_new_with_size(0); + + vm->root_namespace = root; + + if (rb_namespace_available()) { + CONST_ID(id_namespace_entry, "__namespace_entry__"); + + root_namespace = rb_obj_alloc(rb_cNamespace); + rb_evict_ivars_to_hash(root_namespace); + RCLASS_SET_PRIME_CLASSEXT_WRITABLE(root_namespace, true); + RCLASS_SET_CONST_TBL(root_namespace, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true); + + root->ns_id = namespace_generate_id(); + root->ns_object = root_namespace; + + entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_namespace_data_type, root); + rb_ivar_set(root_namespace, id_namespace_entry, entry); + } + else { + root->ns_id = 1; + root->ns_object = Qnil; + } } static VALUE @@ -918,9 +865,10 @@ void rb_initialize_main_namespace(void) { rb_namespace_t *ns; - rb_vm_t *vm = GET_VM(); - rb_thread_t *th = GET_THREAD(); VALUE main_ns; + rb_vm_t *vm = GET_VM(); + + VM_ASSERT(rb_namespace_available()); if (!namespace_experimental_warned) { rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, @@ -933,13 +881,12 @@ rb_initialize_main_namespace(void) ns = rb_get_namespace_t(main_ns); ns->ns_object = main_ns; ns->ns_id = namespace_generate_id(); - ns->is_builtin = false; ns->is_user = true; ns->is_optional = false; rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_ns); - vm->main_namespace = th->ns = main_namespace = ns; + vm->main_namespace = main_namespace = ns; } static VALUE @@ -954,8 +901,8 @@ rb_namespace_inspect(VALUE obj) ns = rb_get_namespace_t(obj); r = rb_str_new_cstr("#ns_id), rb_intern("to_s"), 0)); - if (NAMESPACE_BUILTIN_P(ns)) { - rb_str_cat_cstr(r, ",builtin"); + if (NAMESPACE_ROOT_P(ns)) { + rb_str_cat_cstr(r, ",root"); } if (NAMESPACE_USER_P(ns)) { rb_str_cat_cstr(r, ",user"); @@ -970,106 +917,24 @@ rb_namespace_inspect(VALUE obj) return r; } -struct refiner_calling_super_data { - int argc; - VALUE *argv; -}; - static VALUE -namespace_builtin_refiner_calling_super(VALUE arg) +rb_namespace_loading_func(int argc, VALUE *argv, VALUE _self) { - struct refiner_calling_super_data *data = (struct refiner_calling_super_data *)arg; - return rb_call_super(data->argc, data->argv); -} - -static VALUE -namespace_builtin_refiner_loading_func_ensure(VALUE _) -{ - rb_vm_t *vm = GET_VM(); - if (!vm->require_stack) - rb_bug("require_stack is not ready but the namespace refiner is called"); - rb_namespace_disable_builtin(); - return Qnil; -} - -static VALUE -rb_namespace_builtin_refiner_loading_func(int argc, VALUE *argv, VALUE _self) -{ - rb_vm_t *vm = GET_VM(); - if (!vm->require_stack) - rb_bug("require_stack is not ready but the namespace refiner is called"); - rb_namespace_enable_builtin(); - // const rb_namespace_t *ns = rb_loading_namespace(); - // printf("N:current loading ns: %ld\n", ns->ns_id); - struct refiner_calling_super_data data = { - .argc = argc, - .argv = argv - }; - return rb_ensure(namespace_builtin_refiner_calling_super, (VALUE)&data, - namespace_builtin_refiner_loading_func_ensure, Qnil); + rb_vm_frame_flag_set_ns_require(GET_EC()); + return rb_call_super(argc, argv); } static void -setup_builtin_refinement(VALUE mod) -{ - struct rb_refinements_data data; - rb_refinement_setup(&data, mod, rb_mKernel); - rb_define_method(data.refinement, "require", rb_namespace_builtin_refiner_loading_func, -1); - rb_define_method(data.refinement, "require_relative", rb_namespace_builtin_refiner_loading_func, -1); - rb_define_method(data.refinement, "load", rb_namespace_builtin_refiner_loading_func, -1); -} - -static VALUE -namespace_user_loading_func_calling_super(VALUE arg) -{ - struct refiner_calling_super_data *data = (struct refiner_calling_super_data *)arg; - return rb_call_super(data->argc, data->argv); -} - -static VALUE -namespace_user_loading_func_ensure(VALUE arg) -{ - rb_namespace_t *ns = (rb_namespace_t *)arg; - rb_namespace_pop_loading_namespace(ns); - return Qnil; -} - -static VALUE -rb_namespace_user_loading_func(int argc, VALUE *argv, VALUE _self) -{ - const rb_namespace_t *ns; - rb_vm_t *vm = GET_VM(); - if (!vm->require_stack) - rb_bug("require_stack is not ready but require/load is called in user namespaces"); - ns = rb_current_namespace(); - VM_ASSERT(rb_namespace_available() || !ns); - rb_namespace_push_loading_namespace(ns); - struct refiner_calling_super_data data = { - .argc = argc, - .argv = argv - }; - return rb_ensure(namespace_user_loading_func_calling_super, (VALUE)&data, - namespace_user_loading_func_ensure, (VALUE)ns); -} - -static VALUE -setup_pushing_loading_namespace_include(VALUE mod) -{ - rb_include_module(rb_cObject, mod); - return Qnil; -} - -static void -setup_pushing_loading_namespace(rb_namespace_t *ns) +namespace_define_loader_method(const char *name) { - rb_namespace_exec(ns, setup_pushing_loading_namespace_include, rb_mNamespaceLoader); + rb_define_private_method(rb_mNamespaceLoader, name, rb_namespace_loading_func, -1); + rb_define_singleton_method(rb_mNamespaceLoader, name, rb_namespace_loading_func, -1); } -static void -namespace_define_loader_method(const char *name) +void +Init_root_namespace(void) { - rb_define_private_method(rb_mNamespaceLoader, name, rb_namespace_user_loading_func, -1); - rb_define_singleton_method(rb_mNamespaceLoader, name, rb_namespace_user_loading_func, -1); + root_namespace->loading_table = st_init_strtable(); } void @@ -1104,17 +969,16 @@ Init_Namespace(void) rb_cNamespaceEntry = rb_define_class_under(rb_cNamespace, "Entry", rb_cObject); rb_define_alloc_func(rb_cNamespaceEntry, rb_namespace_entry_alloc); - /* :nodoc: */ - rb_mNamespaceRefiner = rb_define_module_under(rb_cNamespace, "Refiner"); - if (rb_namespace_available()) { - setup_builtin_refinement(rb_mNamespaceRefiner); - } + initialize_root_namespace(); /* :nodoc: */ rb_mNamespaceLoader = rb_define_module_under(rb_cNamespace, "Loader"); namespace_define_loader_method("require"); namespace_define_loader_method("require_relative"); namespace_define_loader_method("load"); + if (rb_namespace_available()) { + rb_include_module(rb_cObject, rb_mNamespaceLoader); + } rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_current, 0); @@ -1127,7 +991,4 @@ Init_Namespace(void) rb_define_method(rb_cNamespace, "eval", rb_namespace_eval, 1); rb_define_method(rb_cNamespace, "inspect", rb_namespace_inspect, 0); - - rb_vm_t *vm = GET_VM(); - vm->require_stack = rb_ary_new(); } diff --git a/proc.c b/proc.c index 8f0eb0a898b711..9c1a7a7fff2916 100644 --- a/proc.c +++ b/proc.c @@ -683,7 +683,6 @@ cfunc_proc_new(VALUE klass, VALUE ifunc) { rb_proc_t *proc; cfunc_proc_t *sproc; - const rb_namespace_t *ns = rb_current_namespace(); VALUE procval = TypedData_Make_Struct(klass, cfunc_proc_t, &proc_data_type, sproc); VALUE *ep; @@ -698,7 +697,6 @@ cfunc_proc_new(VALUE klass, VALUE ifunc) /* self? */ RB_OBJ_WRITE(procval, &proc->block.as.captured.code.ifunc, ifunc); - proc->ns = ns; proc->is_lambda = TRUE; return procval; } diff --git a/ruby.c b/ruby.c index f64412a9cf0fd2..8c1fb5719bc02e 100644 --- a/ruby.c +++ b/ruby.c @@ -449,7 +449,7 @@ ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { const char sep = PATH_SEP_CHAR; const char *p, *s; - VALUE load_path = GET_VM()->load_path; + VALUE load_path = rb_root_namespace()->load_path; #ifdef __CYGWIN__ char rubylib[FILENAME_MAX]; VALUE buf = 0; @@ -754,7 +754,7 @@ ruby_init_loadpath(void) rb_gc_register_address(&ruby_archlibdir_path); ruby_archlibdir_path = archlibdir; - load_path = GET_VM()->load_path; + load_path = rb_root_namespace()->load_path; ruby_push_include(getenv("RUBYLIB"), identical_path); @@ -2328,8 +2328,8 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) char fbuf[MAXPATHLEN]; int i = (int)proc_options(argc, argv, opt, 0); unsigned int dump = opt->dump & dump_exit_bits; - rb_vm_t *vm = GET_VM(); - const long loaded_before_enc = RARRAY_LEN(vm->loaded_features); + const rb_namespace_t *ns = rb_root_namespace(); + const long loaded_before_enc = RARRAY_LEN(ns->loaded_features); if (opt->dump & (DUMP_BIT(usage)|DUMP_BIT(help))) { const char *const progname = @@ -2477,7 +2477,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) rb_obj_freeze(opt->script_name); if (IF_UTF8_PATH(uenc != lenc, 1)) { long i; - VALUE load_path = vm->load_path; + VALUE load_path = ns->load_path; const ID id_initial_load_path_mark = INITIAL_LOAD_PATH_MARK; int modifiable = FALSE; @@ -2500,11 +2500,11 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) RARRAY_ASET(load_path, i, path); } if (modifiable) { - rb_ary_replace(vm->load_path_snapshot, load_path); + rb_ary_replace(ns->load_path_snapshot, load_path); } } { - VALUE loaded_features = vm->loaded_features; + VALUE loaded_features = ns->loaded_features; bool modified = false; for (long i = loaded_before_enc; i < RARRAY_LEN(loaded_features); ++i) { VALUE path = RARRAY_AREF(loaded_features, i); @@ -2516,7 +2516,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) RARRAY_ASET(loaded_features, i, path); } if (modified) { - rb_ary_replace(vm->loaded_features_snapshot, loaded_features); + rb_ary_replace(ns->loaded_features_snapshot, loaded_features); } } diff --git a/variable.c b/variable.c index b1782a01c85c93..bb3811a81c5425 100644 --- a/variable.c +++ b/variable.c @@ -1012,6 +1012,7 @@ rb_gvar_set(ID id, VALUE val) RB_VM_LOCKING() { entry = rb_global_entry(id); + // TODO: consider root/main namespaces if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); retval = val; @@ -3170,43 +3171,6 @@ autoload_apply_constants(VALUE _arguments) return Qtrue; } -struct autoload_feature_require_data { - struct autoload_load_arguments *arguments; - VALUE receiver; - VALUE feature; -}; - -static VALUE -autoload_feature_require_in_builtin(VALUE arg) -{ - struct autoload_feature_require_data *data = (struct autoload_feature_require_data *)arg; - - VALUE result = rb_funcall(data->receiver, rb_intern("require"), 1, data->feature); - if (RTEST(result)) { - return rb_mutex_synchronize(autoload_mutex, autoload_apply_constants, (VALUE)data->arguments); - } - return Qnil; -} - -static VALUE -autoload_feature_require_ensure_in_builtin(VALUE _arg) -{ - /* - * The gccct should be cleared again after the rb_funcall() to remove - * the inconsistent cache entry against the current namespace. - */ - rb_gccct_clear_table(Qnil); - rb_namespace_disable_builtin(); - return Qnil; -} - -static VALUE -autoload_feature_require_in_builtin_wrap(VALUE arg) -{ - return rb_ensure(autoload_feature_require_in_builtin, arg, - autoload_feature_require_ensure_in_builtin, Qnil); -} - static VALUE autoload_feature_require(VALUE _arguments) { @@ -3220,26 +3184,16 @@ autoload_feature_require(VALUE _arguments) // We save this for later use in autoload_apply_constants: arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type); - if (NIL_P(autoload_namespace)) { - rb_namespace_enable_builtin(); - /* - * Clear the global cc cache table because the require method can be different from the current - * namespace's one and it may cause inconsistent cc-cme states. - * For example, the assertion below may fail in gccct_method_search(); - * VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))) - */ - rb_gccct_clear_table(Qnil); - struct autoload_feature_require_data data = { - .arguments = arguments, - .receiver = receiver, - .feature = arguments->autoload_data->feature, - }; - return rb_namespace_exec(rb_builtin_namespace(), autoload_feature_require_in_builtin_wrap, (VALUE)&data); - } - - if (RTEST(autoload_namespace) && NAMESPACE_OPTIONAL_P(rb_get_namespace_t(autoload_namespace))) { + if (rb_namespace_available() && NAMESPACE_OBJ_P(autoload_namespace)) receiver = autoload_namespace; - } + + /* + * Clear the global cc cache table because the require method can be different from the current + * namespace's one and it may cause inconsistent cc-cme states. + * For example, the assertion below may fail in gccct_method_search(); + * VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))) + */ + rb_gccct_clear_table(Qnil); VALUE result = rb_funcall(receiver, rb_intern("require"), 1, arguments->autoload_data->feature); diff --git a/vm.c b/vm.c index 154a0ba9d16d04..3eb5d74e8390e1 100644 --- a/vm.c +++ b/vm.c @@ -118,7 +118,15 @@ PUREFUNC(static inline VALUE VM_CF_BLOCK_HANDLER(const rb_control_frame_t * cons static inline VALUE VM_CF_BLOCK_HANDLER(const rb_control_frame_t * const cfp) { - const VALUE *ep = VM_CF_LEP(cfp); + const VALUE *ep; + if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_TOP) { + VM_ASSERT(VM_ENV_LOCAL_P(cfp->ep)); + /* Never set black_handler for VM_FRAME_MAGIC_TOP + * and the specval is used for namespace (rb_namespace_t) in the case + */ + return VM_BLOCK_HANDLER_NONE; + } + ep = VM_CF_LEP(cfp); return VM_ENV_BLOCK_HANDLER(ep); } @@ -778,15 +786,16 @@ vm_stat(int argc, VALUE *argv, VALUE self) /* control stack frame */ static void -vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq) +vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_namespace_t *ns) { if (ISEQ_BODY(iseq)->type != ISEQ_TYPE_TOP) { rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence"); } /* for return */ - vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, rb_ec_thread_ptr(ec)->top_self, - VM_BLOCK_HANDLER_NONE, + vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, + ns ? ns->top_self : rb_ec_thread_ptr(ec)->top_self, + GC_GUARDED_PTR(ns), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp, ISEQ_BODY(iseq)->local_table_size, ISEQ_BODY(iseq)->stack_max); @@ -992,7 +1001,11 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co VM_FORCE_WRITE_SPECIAL_CONST(&ep[VM_ENV_DATA_INDEX_SPECVAL], VM_GUARDED_PREV_EP(prev_cfp->ep)); } } + else if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)) { + // block_handler is always VM_BLOCK_HANDLER_NONE in this case + } else { + VM_ASSERT(VM_ENV_LOCAL_P(ep) && !VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)); VALUE block_handler = VM_ENV_BLOCK_HANDLER(ep); if (block_handler != VM_BLOCK_HANDLER_NONE) { @@ -1180,7 +1193,6 @@ vm_proc_create_from_captured(VALUE klass, { VALUE procval = rb_proc_alloc(klass); rb_proc_t *proc = RTYPEDDATA_DATA(procval); - const rb_namespace_t *ns = rb_current_namespace(); VM_ASSERT(VM_EP_IN_HEAP_P(GET_EC(), captured->ep)); @@ -1190,7 +1202,6 @@ vm_proc_create_from_captured(VALUE klass, rb_vm_block_ep_update(procval, &proc->block, captured->ep); vm_block_type_set(&proc->block, block_type); - proc->ns = ns; proc->is_from_method = is_from_method; proc->is_lambda = is_lambda; @@ -1222,12 +1233,10 @@ proc_create(VALUE klass, const struct rb_block *block, int8_t is_from_method, in { VALUE procval = rb_proc_alloc(klass); rb_proc_t *proc = RTYPEDDATA_DATA(procval); - const rb_namespace_t *ns = rb_current_namespace(); VM_ASSERT(VM_EP_IN_HEAP_P(GET_EC(), vm_block_ep(block))); rb_vm_block_copy(procval, &proc->block, block); vm_block_type_set(&proc->block, block->type); - proc->ns = ns; proc->is_from_method = is_from_method; proc->is_lambda = is_lambda; @@ -2910,24 +2919,11 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V /* misc */ VALUE -rb_iseq_eval(const rb_iseq_t *iseq) -{ - rb_execution_context_t *ec = GET_EC(); - VALUE val; - vm_set_top_stack(ec, iseq); - // TODO: set the namespace frame like require/load - val = vm_exec(ec); - return val; -} - -VALUE -rb_iseq_eval_with_refinement(const rb_iseq_t *iseq, VALUE mod) +rb_iseq_eval(const rb_iseq_t *iseq, const rb_namespace_t *ns) { rb_execution_context_t *ec = GET_EC(); VALUE val; - vm_set_top_stack(ec, iseq); - rb_vm_using_module(mod); - // TODO: set the namespace frame like require/load + vm_set_top_stack(ec, iseq, ns); val = vm_exec(ec); return val; } @@ -2937,8 +2933,7 @@ rb_iseq_eval_main(const rb_iseq_t *iseq) { rb_execution_context_t *ec = GET_EC(); VALUE val; - vm_set_main_stack(ec, iseq); - // TODO: set the namespace frame like require/load + vm_set_main_stack(ec, iseq); // TODO: not need to set the namespace? val = vm_exec(ec); return val; } @@ -2978,10 +2973,11 @@ rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, rb_execution_context_t *ec = GET_EC(); const rb_control_frame_t *reg_cfp = ec->cfp; const rb_iseq_t *iseq = rb_iseq_new(Qnil, filename, filename, Qnil, 0, ISEQ_TYPE_TOP); + const rb_namespace_t *ns = rb_current_namespace(); VALUE val; vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, - recv, block_handler, + recv, GC_GUARDED_PTR(ns), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ 0, reg_cfp->sp, 0, 0); @@ -2991,9 +2987,11 @@ rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, return val; } +/* namespace */ + VALUE -rb_vm_call_cfunc2(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, - VALUE block_handler, VALUE filename) +rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, + VALUE filename, const rb_namespace_t *ns) { rb_execution_context_t *ec = GET_EC(); const rb_control_frame_t *reg_cfp = ec->cfp; @@ -3001,7 +2999,7 @@ rb_vm_call_cfunc2(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg VALUE val; vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, - recv, block_handler, + recv, GC_GUARDED_PTR(ns), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ 0, reg_cfp->sp, 0, 0); @@ -3011,6 +3009,86 @@ rb_vm_call_cfunc2(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg return val; } +void +rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec) +{ + VM_ASSERT(rb_namespace_available()); + VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_NS_REQUIRE); +} + +static const rb_namespace_t * +current_namespace_on_env(const VALUE *ep) +{ + rb_callable_method_entry_t *cme; + const rb_namespace_t *ns; + const VALUE *lep = VM_EP_LEP(ep); + VM_ASSERT(lep); + VM_ASSERT(rb_namespace_available()); + + if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CFUNC)) { + cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); + VM_ASSERT(cme); + VM_ASSERT(cme->def); + return cme->def->ns; + } + else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_TOP) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CLASS)) { + VM_ASSERT(VM_ENV_LOCAL_P(lep)); + return VM_ENV_NAMESPACE(lep); + } + else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_DUMMY)) { + // No valid local ep found (just after process boot?) + // return the root namespace (the only valid namespace) until the main is initialized + ns = rb_main_namespace(); + if (ns) + return ns; + return rb_root_namespace(); + } + else { + rb_bug("BUG: Local ep without cme/namespace, flags: %08lX", (unsigned long)lep[VM_ENV_DATA_INDEX_FLAGS]); + } + UNREACHABLE_RETURN(0); +} + +const rb_namespace_t * +rb_vm_current_namespace(const rb_execution_context_t *ec) +{ + const rb_control_frame_t *cfp; + + if (!rb_namespace_available() || !ec) + return rb_root_namespace(); + + cfp = ec->cfp; + return current_namespace_on_env(cfp->ep); +} + +const rb_namespace_t * +rb_vm_loading_namespace(const rb_execution_context_t *ec) +{ + const rb_control_frame_t *cfp, *current_cfp, *end_cfp; + + if (!rb_namespace_available() || !ec) + return rb_root_namespace(); + + cfp = ec->cfp; + current_cfp = cfp; + end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); + + while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { + if (VM_FRAME_RUBYFRAME_P(cfp)) { + if (VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE)) { + if (RTEST(cfp->self) && NAMESPACE_OBJ_P(cfp->self)) { + // Namespace#require (, require_relative, load) + return rb_get_namespace_t(cfp->self); + } + return current_namespace_on_env(cfp->ep); + } + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + // no require/load with explicit namespaces. + return current_namespace_on_env(current_cfp->ep); +} + /* vm */ void @@ -3021,22 +3099,13 @@ rb_vm_update_references(void *ptr) vm->self = rb_gc_location(vm->self); vm->mark_object_ary = rb_gc_location(vm->mark_object_ary); - vm->load_path = rb_gc_location(vm->load_path); - vm->load_path_snapshot = rb_gc_location(vm->load_path_snapshot); - - if (vm->load_path_check_cache) { - vm->load_path_check_cache = rb_gc_location(vm->load_path_check_cache); - } - - vm->expanded_load_path = rb_gc_location(vm->expanded_load_path); - vm->loaded_features = rb_gc_location(vm->loaded_features); - vm->loaded_features_snapshot = rb_gc_location(vm->loaded_features_snapshot); - vm->loaded_features_realpaths = rb_gc_location(vm->loaded_features_realpaths); - vm->loaded_features_realpath_map = rb_gc_location(vm->loaded_features_realpath_map); - vm->top_self = rb_gc_location(vm->top_self); - vm->require_stack = rb_gc_location(vm->require_stack); vm->orig_progname = rb_gc_location(vm->orig_progname); + if (vm->root_namespace) + rb_namespace_gc_update_references(vm->root_namespace); + if (vm->main_namespace) + rb_namespace_gc_update_references(vm->main_namespace); + rb_gc_update_values(RUBY_NSIG, vm->trap_list.cmd); if (vm->coverages) { @@ -3108,29 +3177,18 @@ rb_vm_mark(void *ptr) rb_gc_mark_movable(vm->self); + if (vm->root_namespace) { + rb_namespace_entry_mark(vm->root_namespace); + } if (vm->main_namespace) { - rb_namespace_entry_mark((void *)vm->main_namespace); + rb_namespace_entry_mark(vm->main_namespace); } rb_gc_mark_movable(vm->mark_object_ary); - rb_gc_mark_movable(vm->load_path); - rb_gc_mark_movable(vm->load_path_snapshot); - rb_gc_mark_movable(vm->load_path_check_cache); - rb_gc_mark_movable(vm->expanded_load_path); - rb_gc_mark_movable(vm->loaded_features); - rb_gc_mark_movable(vm->loaded_features_snapshot); - rb_gc_mark_movable(vm->loaded_features_realpaths); - rb_gc_mark_movable(vm->loaded_features_realpath_map); - rb_gc_mark_movable(vm->require_stack); - rb_gc_mark_movable(vm->top_self); rb_gc_mark_movable(vm->orig_progname); rb_gc_mark_movable(vm->coverages); rb_gc_mark_movable(vm->me2counter); - if (vm->loading_table) { - rb_mark_tbl(vm->loading_table); - } - rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); rb_id_table_foreach_values(vm->negative_cme_table, vm_mark_negative_cme, NULL); @@ -3165,14 +3223,6 @@ rb_vm_register_special_exception_str(enum ruby_special_exceptions sp, VALUE cls, rb_vm_register_global_object(exc); } -static int -free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg) -{ - xfree((char *)key); - return ST_DELETE; -} - -void rb_free_loaded_features_index(rb_vm_t *vm); void rb_objspace_free_objects(void *objspace); int @@ -3194,7 +3244,6 @@ ruby_vm_destruct(rb_vm_t *vm) rb_free_vm_opt_tables(); rb_free_warning(); rb_free_rb_global_tbl(); - rb_free_loaded_features_index(vm); rb_id_table_free(vm->negative_cme_table); st_free_table(vm->overloaded_cme_table); @@ -3225,11 +3274,6 @@ ruby_vm_destruct(rb_vm_t *vm) rb_vm_living_threads_init(vm); ruby_vm_run_at_exit_hooks(vm); - if (vm->loading_table) { - st_foreach(vm->loading_table, free_loading_table_entry, 0); - st_free_table(vm->loading_table); - vm->loading_table = 0; - } if (vm->ci_table) { st_free_table(vm->ci_table); vm->ci_table = NULL; @@ -3329,8 +3373,6 @@ vm_memsize(const void *ptr) return ( sizeof(rb_vm_t) + - rb_st_memsize(vm->loaded_features_index) + - rb_st_memsize(vm->loading_table) + rb_vm_memsize_postponed_job_queue() + rb_vm_memsize_workqueue(&vm->workqueue) + vm_memsize_at_exit_list(vm->at_exit) + @@ -3599,8 +3641,6 @@ thread_mark(void *ptr) rb_gc_mark(th->pending_interrupt_mask_stack); rb_gc_mark(th->top_self); rb_gc_mark(th->top_wrapper); - rb_gc_mark(th->namespaces); - if (NAMESPACE_USER_P(th->ns)) rb_namespace_entry_mark(th->ns); if (th->root_fiber) rb_fiber_mark_self(th->root_fiber); RUBY_ASSERT(th->ec == rb_fiberptr_get_ec(th->ec->fiber_ptr)); @@ -3727,6 +3767,8 @@ rb_ec_clear_vm_stack(rb_execution_context_t *ec) static void th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) { + const rb_namespace_t *ns = rb_current_namespace(); + th->self = self; rb_threadptr_root_fiber_setup(th); @@ -3748,9 +3790,12 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->status = THREAD_RUNNABLE; th->last_status = Qnil; th->top_wrapper = 0; - th->top_self = vm->top_self; // 0 while self == 0 - th->namespaces = 0; - th->ns = 0; + if (ns->top_self) { + th->top_self = ns->top_self; + } + else { + th->top_self = 0; + } th->value = Qundef; th->ec->errinfo = Qnil; @@ -3780,16 +3825,10 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) VALUE rb_thread_alloc(VALUE klass) { - rb_namespace_t *ns; - rb_execution_context_t *ec = GET_EC(); VALUE self = thread_alloc(klass); rb_thread_t *target_th = rb_thread_ptr(self); target_th->ractor = GET_RACTOR(); th_init(target_th, self, target_th->vm = GET_VM()); - if ((ns = rb_ec_thread_ptr(ec)->ns) == 0) { - ns = rb_main_namespace(); - } - target_th->ns = ns; return self; } @@ -4336,8 +4375,6 @@ Init_VM(void) th->vm = vm; th->top_wrapper = 0; th->top_self = rb_vm_top_self(); - th->namespaces = 0; - th->ns = 0; rb_vm_register_global_object((VALUE)iseq); th->ec->cfp->iseq = iseq; @@ -4552,7 +4589,6 @@ Init_vm_objects(void) /* initialize mark object array, hash */ vm->mark_object_ary = pin_array_list_new(Qnil); - vm->loading_table = st_init_strtable(); vm->ci_table = st_init_table(&vm_ci_hashtype); vm->cc_refinement_table = rb_set_init_numtable(); } @@ -4588,17 +4624,20 @@ main_to_s(VALUE obj) VALUE rb_vm_top_self(void) { - return GET_VM()->top_self; + const rb_namespace_t *ns = rb_current_namespace(); + VM_ASSERT(ns); + VM_ASSERT(ns->top_self); + return ns->top_self; } void Init_top_self(void) { rb_vm_t *vm = GET_VM(); - - vm->top_self = rb_obj_alloc(rb_cObject); - rb_define_singleton_method(rb_vm_top_self(), "to_s", main_to_s, 0); - rb_define_alias(rb_singleton_class(rb_vm_top_self()), "inspect", "to_s"); + vm->root_namespace = (rb_namespace_t *)rb_root_namespace(); + vm->root_namespace->top_self = rb_obj_alloc(rb_cObject); + rb_define_singleton_method(vm->root_namespace->top_self, "to_s", main_to_s, 0); + rb_define_alias(rb_singleton_class(vm->root_namespace->top_self), "inspect", "to_s"); } VALUE * diff --git a/vm_core.h b/vm_core.h index 487a4020eec5cd..fab440f1fd5175 100644 --- a/vm_core.h +++ b/vm_core.h @@ -311,7 +311,6 @@ struct rb_calling_info { int argc; bool kw_splat; VALUE heap_argv; - const rb_namespace_t *proc_ns; }; #ifndef VM_ARGC_STACK_MAX @@ -755,20 +754,10 @@ typedef struct rb_vm_struct { const VALUE special_exceptions[ruby_special_error_count]; /* namespace */ + rb_namespace_t *root_namespace; rb_namespace_t *main_namespace; /* load */ - VALUE top_self; - VALUE load_path; - VALUE load_path_snapshot; - VALUE load_path_check_cache; - VALUE expanded_load_path; - VALUE loaded_features; - VALUE loaded_features_snapshot; - VALUE loaded_features_realpaths; - VALUE loaded_features_realpath_map; - struct st_table *loaded_features_index; - struct st_table *loading_table; // For running the init function of statically linked // extensions when they are loaded struct st_table *static_ext_inits; @@ -832,9 +821,6 @@ typedef struct rb_vm_struct { size_t fiber_vm_stack_size; size_t fiber_machine_stack_size; } default_params; - - // TODO: a single require_stack can't support multi-threaded require trees - VALUE require_stack; } rb_vm_t; /* default values */ @@ -1141,9 +1127,6 @@ typedef struct rb_thread_struct { /* for load(true) */ VALUE top_self; VALUE top_wrapper; - /* for namespace */ - VALUE namespaces; // Stack of namespaces - rb_namespace_t *ns; // The current one /* thread control */ @@ -1283,7 +1266,6 @@ RUBY_SYMBOL_EXPORT_END typedef struct { const struct rb_block block; - const rb_namespace_t *ns; unsigned int is_from_method: 1; /* bool */ unsigned int is_lambda: 1; /* bool */ unsigned int is_isolated: 1; /* bool */ @@ -1375,11 +1357,11 @@ typedef rb_control_frame_t * enum vm_frame_env_flags { /* Frame/Environment flag bits: - * MMMM MMMM MMMM MMMM __FF FFFF FFFE EEEX (LSB) + * MMMM MMMM MMMM MMMM ___F FFFF FFFE EEEX (LSB) * * X : tag for GC marking (It seems as Fixnum) * EEE : 4 bits Env flags - * FF..: 9 bits Frame flags + * FF..: 8 bits Frame flags * MM..: 15 bits frame magic (to check frame corruption) */ @@ -1402,10 +1384,9 @@ enum vm_frame_env_flags { VM_FRAME_FLAG_CFRAME = 0x0080, VM_FRAME_FLAG_LAMBDA = 0x0100, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200, - VM_FRAME_FLAG_CFRAME_KW = 0x0400, - VM_FRAME_FLAG_PASSED = 0x0800, - VM_FRAME_FLAG_NS_SWITCH = 0x1000, - VM_FRAME_FLAG_LOAD_ISEQ = 0x2000, + VM_FRAME_FLAG_CFRAME_KW = 0x0400, + VM_FRAME_FLAG_PASSED = 0x0800, + VM_FRAME_FLAG_NS_REQUIRE = 0x1000, /* env flag */ VM_ENV_FLAG_LOCAL = 0x0002, @@ -1456,6 +1437,12 @@ VM_ENV_FLAGS_UNCHECKED(const VALUE *ep, long flag) return flags & flag; } +static inline unsigned long +VM_ENV_FRAME_TYPE_P(const VALUE *ep, unsigned long frame_type) +{ + return VM_ENV_FLAGS(ep, VM_FRAME_MAGIC_MASK) == frame_type; +} + static inline unsigned long VM_FRAME_TYPE(const rb_control_frame_t *cfp) { @@ -1536,9 +1523,9 @@ VM_FRAME_RUBYFRAME_P_UNCHECKED(const rb_control_frame_t *cfp) } static inline int -VM_FRAME_NS_SWITCH_P(const rb_control_frame_t *cfp) +VM_FRAME_NS_REQUIRE_P(const rb_control_frame_t *cfp) { - return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_SWITCH) != 0; + return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE) != 0; } #define RUBYVM_CFUNC_FRAME_P(cfp) \ @@ -1575,10 +1562,23 @@ VM_ENV_PREV_EP(const VALUE *ep) static inline VALUE VM_ENV_BLOCK_HANDLER(const VALUE *ep) { + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)) { + VM_ASSERT(VM_ENV_LOCAL_P(ep)); + return VM_BLOCK_HANDLER_NONE; + } + VM_ASSERT(VM_ENV_LOCAL_P(ep)); return ep[VM_ENV_DATA_INDEX_SPECVAL]; } +static inline const rb_namespace_t * +VM_ENV_NAMESPACE(const VALUE *ep) +{ + VM_ASSERT(VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)); + VM_ASSERT(VM_ENV_LOCAL_P(ep)); + return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); +} + #if VM_CHECK_MODE > 0 int rb_vm_ep_in_heap_p(const VALUE *ep); #endif @@ -1899,8 +1899,7 @@ NORETURN(void rb_bug_for_fatal_signal(ruby_sighandler_t default_sighandler, int /* functions about thread/vm execution */ RUBY_SYMBOL_EXPORT_BEGIN -VALUE rb_iseq_eval(const rb_iseq_t *iseq); -VALUE rb_iseq_eval_with_refinement(const rb_iseq_t *iseq, VALUE mod); +VALUE rb_iseq_eval(const rb_iseq_t *iseq, const rb_namespace_t *ns); VALUE rb_iseq_eval_main(const rb_iseq_t *iseq); VALUE rb_iseq_path(const rb_iseq_t *iseq); VALUE rb_iseq_realpath(const rb_iseq_t *iseq); diff --git a/vm_dump.c b/vm_dump.c index 131844b4cc1766..03f573a516fe61 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1150,6 +1150,7 @@ rb_vm_bugreport(const void *ctx, FILE *errout) enum {other_runtime_info = 0}; #endif const rb_vm_t *const vm = GET_VM(); + const rb_namespace_t *ns = rb_current_namespace(); const rb_execution_context_t *ec = rb_current_execution_context(false); if (vm && ec) { @@ -1198,10 +1199,19 @@ rb_vm_bugreport(const void *ctx, FILE *errout) LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); kprintf("\n"); } - if (vm->loaded_features) { + if (rb_namespace_available()) { + kprintf("* Namespace: enabled\n"); + kprintf("* Current namespace id: %ld, type: %s\n", + ns->ns_id, + NAMESPACE_USER_P(ns) ? (NAMESPACE_MAIN_P(ns) ? "main" : "user") : "root"); + } + else { + kprintf("* Namespace: disabled\n"); + } + if (ns->loaded_features) { kprintf("* Loaded features:\n\n"); - for (i=0; iloaded_features); i++) { - name = RARRAY_AREF(vm->loaded_features, i); + for (i=0; iloaded_features); i++) { + name = RARRAY_AREF(ns->loaded_features, i); if (RB_TYPE_P(name, T_STRING)) { kprintf(" %4d %.*s\n", i, LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 75339aaa5c5abd..d50b11e377874b 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5238,20 +5238,6 @@ block_proc_is_lambda(const VALUE procval) } } -static inline const rb_namespace_t * -block_proc_namespace(const VALUE procval) -{ - rb_proc_t *proc; - - if (procval) { - GetProcPtr(procval, proc); - return proc->ns; - } - else { - return NULL; - } -} - static VALUE vm_yield_with_cfunc(rb_execution_context_t *ec, const struct rb_captured_block *captured, @@ -5407,10 +5393,6 @@ vm_invoke_iseq_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, SET_SP(rsp); - if (calling->proc_ns) { - frame_flag |= VM_FRAME_FLAG_NS_SWITCH; - } - vm_push_frame(ec, iseq, frame_flag, captured->self, @@ -5511,9 +5493,6 @@ vm_invoke_proc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, { while (vm_block_handler_type(block_handler) == block_handler_type_proc) { VALUE proc = VM_BH_TO_PROC(block_handler); - if (!calling->proc_ns) { - calling->proc_ns = block_proc_namespace(proc); - } is_lambda = block_proc_is_lambda(proc); block_handler = vm_proc_to_block_handler(proc); } diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 015edaed9d12ee..8e7b7c743e7ebb 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -144,7 +144,7 @@ CC_SET_FASTPATH(const struct rb_callcache *cc, vm_call_handler func, bool enable } } -#define GET_BLOCK_HANDLER() (GET_LEP()[VM_ENV_DATA_INDEX_SPECVAL]) +#define GET_BLOCK_HANDLER() VM_ENV_BLOCK_HANDLER(VM_EP_LEP(GET_EP())) /**********************************************************/ /* deal with control flow 3: exception */ From 76c4663a77796fdcba539250dca3e6786ca0fd32 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 22 Jun 2025 17:38:24 +0900 Subject: [PATCH 0002/2435] Fix Namespace.current to show its caller's namespace Calling rb_current_namespace() in rb_namespace_current() means to show the definition namespace of Namespace.current itself (it's the root always) but the users' expectation is to show the namespace of the place where the Namespace.current is called. --- eval_intern.h | 1 + namespace.c | 3 ++- vm.c | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/eval_intern.h b/eval_intern.h index 6353319c6ffe83..4ac950e23813cb 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -299,6 +299,7 @@ VALUE rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, VALUE block_ VALUE rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, VALUE filename, const rb_namespace_t *ns); void rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec); const rb_namespace_t *rb_vm_current_namespace(const rb_execution_context_t *ec); +const rb_namespace_t *rb_vm_caller_namespace(const rb_execution_context_t *ec); const rb_namespace_t *rb_vm_loading_namespace(const rb_execution_context_t *ec); void rb_vm_set_progname(VALUE filename); VALUE rb_vm_cbase(void); diff --git a/namespace.c b/namespace.c index d3ac255363822f..65a90cb0da405c 100644 --- a/namespace.c +++ b/namespace.c @@ -455,11 +455,12 @@ rb_namespace_s_getenabled(VALUE namespace) static VALUE rb_namespace_current(VALUE klass) { - const rb_namespace_t *ns = rb_current_namespace(); + const rb_namespace_t *ns; if (!rb_namespace_available()) return Qnil; + ns = rb_vm_caller_namespace(GET_EC()); VM_ASSERT(ns && ns->ns_object); return ns->ns_object; } diff --git a/vm.c b/vm.c index 3eb5d74e8390e1..83119c32d2fc6c 100644 --- a/vm.c +++ b/vm.c @@ -3052,13 +3052,21 @@ current_namespace_on_env(const VALUE *ep) const rb_namespace_t * rb_vm_current_namespace(const rb_execution_context_t *ec) { - const rb_control_frame_t *cfp; + VM_ASSERT(rb_namespace_available()); + return current_namespace_on_env(ec->cfp->ep); +} - if (!rb_namespace_available() || !ec) - return rb_root_namespace(); +const rb_namespace_t * +rb_vm_caller_namespace(const rb_execution_context_t *ec) +{ + const rb_control_frame_t *caller_cfp; - cfp = ec->cfp; - return current_namespace_on_env(cfp->ep); + VM_ASSERT(rb_namespace_available()); + + // The current control frame is MAGIC_CFUNC to call Namespace.current, but + // we want to get the current namespace of its caller. + caller_cfp = vm_get_ruby_level_caller_cfp(ec, RUBY_VM_PREVIOUS_CONTROL_FRAME(ec->cfp)); + return current_namespace_on_env(caller_cfp->ep); } const rb_namespace_t * From 545cee083b9096366884bf0b092f064db6682d75 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 22 Jun 2025 18:02:01 +0900 Subject: [PATCH 0003/2435] There is no longer needs to evict ivars thanks to fields See 8b5ac5abf258270b32ef63a6acb4eb0d191f79d9 --- namespace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/namespace.c b/namespace.c index 65a90cb0da405c..81017f902bfde5 100644 --- a/namespace.c +++ b/namespace.c @@ -827,7 +827,6 @@ initialize_root_namespace(void) CONST_ID(id_namespace_entry, "__namespace_entry__"); root_namespace = rb_obj_alloc(rb_cNamespace); - rb_evict_ivars_to_hash(root_namespace); RCLASS_SET_PRIME_CLASSEXT_WRITABLE(root_namespace, true); RCLASS_SET_CONST_TBL(root_namespace, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true); From 2100826243ae23e159ccdf9c9805a84074261808 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 22 Jun 2025 18:03:57 +0900 Subject: [PATCH 0004/2435] Fix wrong way to check an object is an instance of rb_cNamespace --- internal/namespace.h | 2 +- namespace.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/namespace.h b/internal/namespace.h index c68f0987aa814f..9ffc9a5c8be8bb 100644 --- a/internal/namespace.h +++ b/internal/namespace.h @@ -40,7 +40,7 @@ struct rb_namespace_struct { }; typedef struct rb_namespace_struct rb_namespace_t; -#define NAMESPACE_OBJ_P(obj) (CLASS_OF(obj) == rb_cNamespace) +#define NAMESPACE_OBJ_P(obj) (rb_obj_class(obj) == rb_cNamespace) #define NAMESPACE_ROOT_P(ns) (ns && !ns->is_user) #define NAMESPACE_USER_P(ns) (ns && ns->is_user) diff --git a/namespace.c b/namespace.c index 81017f902bfde5..8d50ba441566aa 100644 --- a/namespace.c +++ b/namespace.c @@ -877,7 +877,8 @@ rb_initialize_main_namespace(void) namespace_experimental_warned = 1; } - main_ns = rb_class_new_instance_pass_kw(0, NULL, rb_cNamespace); + main_ns = rb_class_new_instance(0, NULL, rb_cNamespace); + VM_ASSERT(NAMESPACE_OBJ_P(main_ns)); ns = rb_get_namespace_t(main_ns); ns->ns_object = main_ns; ns->ns_id = namespace_generate_id(); From c755f35f0ef755274ba409e3c6e21b759f248b29 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 24 Jun 2025 23:11:01 +0900 Subject: [PATCH 0005/2435] Stop using ns->top_self here because it's set to th->top_self beforehand if needed --- vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm.c b/vm.c index 83119c32d2fc6c..76f356342b867e 100644 --- a/vm.c +++ b/vm.c @@ -794,7 +794,7 @@ vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_nam /* for return */ vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, - ns ? ns->top_self : rb_ec_thread_ptr(ec)->top_self, + rb_ec_thread_ptr(ec)->top_self, GC_GUARDED_PTR(ns), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp, From 48523daef68a9ad2361bb2ee281c90e940330ed2 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 29 Jun 2025 09:42:22 +0900 Subject: [PATCH 0006/2435] Follow the usual naming rule for singleton methods --- namespace.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/namespace.c b/namespace.c index 8d50ba441566aa..7fc45f168851c3 100644 --- a/namespace.c +++ b/namespace.c @@ -439,7 +439,7 @@ namespace_initialize(VALUE namespace) * Returns +true+ if namespace is enabled. */ static VALUE -rb_namespace_s_getenabled(VALUE namespace) +rb_namespace_s_getenabled(VALUE recv) { return RBOOL(rb_namespace_available()); } @@ -453,7 +453,7 @@ rb_namespace_s_getenabled(VALUE namespace) * Returns +false+ if namespace is not enabled. */ static VALUE -rb_namespace_current(VALUE klass) +rb_namespace_s_current(VALUE recv) { const rb_namespace_t *ns; @@ -472,7 +472,7 @@ rb_namespace_current(VALUE klass) * Returns +true+ if +klass+ is only in a user namespace. */ static VALUE -rb_namespace_s_is_builtin_p(VALUE namespace, VALUE klass) +rb_namespace_s_is_builtin_p(VALUE recv, VALUE klass) { if (RCLASS_PRIME_CLASSEXT_READABLE_P(klass) && !RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)) return Qtrue; @@ -982,7 +982,7 @@ Init_Namespace(void) } rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); - rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_current, 0); + rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_s_current, 0); rb_define_singleton_method(rb_cNamespace, "is_builtin?", rb_namespace_s_is_builtin_p, 1); rb_define_method(rb_cNamespace, "load_path", rb_namespace_load_path, 0); From bb21b619f01926fa99f6cca5ec8ac89389207d10 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 29 Jun 2025 09:44:31 +0900 Subject: [PATCH 0007/2435] Detect the correct loading namespace from control frames * checking all control frames (instead of filtering by VM_FRAME_RUBYFRAME_P) because VM_FRAME_FLAG_NS_REQUIRE is set on non-rubyframe * skip frames of CFUNC in the root namespace for Kernel#require (etc) to avoid detecting the root namespace of those frames wrongly --- vm.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/vm.c b/vm.c index 76f356342b867e..790a5af4f0cf7d 100644 --- a/vm.c +++ b/vm.c @@ -3065,10 +3065,24 @@ rb_vm_caller_namespace(const rb_execution_context_t *ec) // The current control frame is MAGIC_CFUNC to call Namespace.current, but // we want to get the current namespace of its caller. - caller_cfp = vm_get_ruby_level_caller_cfp(ec, RUBY_VM_PREVIOUS_CONTROL_FRAME(ec->cfp)); + caller_cfp = vm_get_ruby_level_caller_cfp(ec, ec->cfp); return current_namespace_on_env(caller_cfp->ep); } +static const rb_control_frame_t * +find_loader_control_frame(const rb_control_frame_t *cfp, const rb_control_frame_t *end_cfp) +{ + while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { + if (!VM_ENV_FRAME_TYPE_P(cfp->ep, VM_FRAME_MAGIC_CFUNC)) + break; + if (!NAMESPACE_ROOT_P(current_namespace_on_env(cfp->ep))) + break; + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + VM_ASSERT(RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)); + return cfp; +} + const rb_namespace_t * rb_vm_loading_namespace(const rb_execution_context_t *ec) { @@ -3082,14 +3096,14 @@ rb_vm_loading_namespace(const rb_execution_context_t *ec) end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { - if (VM_FRAME_RUBYFRAME_P(cfp)) { - if (VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE)) { - if (RTEST(cfp->self) && NAMESPACE_OBJ_P(cfp->self)) { - // Namespace#require (, require_relative, load) - return rb_get_namespace_t(cfp->self); - } - return current_namespace_on_env(cfp->ep); + if (VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE)) { + if (RTEST(cfp->self) && NAMESPACE_OBJ_P(cfp->self)) { + // Namespace#require, #require_relative, #load + return rb_get_namespace_t(cfp->self); } + // Kernel#require, #require_relative, #load + cfp = find_loader_control_frame(cfp, end_cfp); + return current_namespace_on_env(cfp->ep); } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } From a5df24fe010d3631b324a6aadcb2db5b32c270e5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 27 Jul 2025 11:02:52 +0900 Subject: [PATCH 0008/2435] Define a debug method Kernel#dump_classext only when RUBY_DEBUG is set --- namespace.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/namespace.c b/namespace.c index 7fc45f168851c3..e30d426d68b237 100644 --- a/namespace.c +++ b/namespace.c @@ -950,6 +950,140 @@ Init_enable_namespace(void) } } +#ifdef RUBY_DEBUG + +static const char * +classname(VALUE klass) +{ + VALUE p = RCLASS_CLASSPATH(klass); + if (RTEST(p)) + return RSTRING_PTR(p); + if (RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)) + return RSTRING_PTR(rb_inspect(klass)); + return "NonClassValue"; +} + +static enum rb_id_table_iterator_result +dump_classext_methods_i(ID mid, VALUE _val, void *data) +{ + VALUE ary = (VALUE)data; + rb_ary_push(ary, rb_id2str(mid)); + return ID_TABLE_CONTINUE; +} + +static enum rb_id_table_iterator_result +dump_classext_constants_i(ID mid, VALUE _val, void *data) +{ + VALUE ary = (VALUE)data; + rb_ary_push(ary, rb_id2str(mid)); + return ID_TABLE_CONTINUE; +} + +static void +dump_classext_i(rb_classext_t *ext, bool is_prime, VALUE _ns, void *data) +{ + char buf[4096]; + struct rb_id_table *tbl; + VALUE ary, res = (VALUE)data; + + snprintf(buf, 4096, "Namespace %ld:%s classext %p\n", + RCLASSEXT_NS(ext)->ns_id, is_prime ? " prime" : "", (void *)ext); + rb_str_cat_cstr(res, buf); + + snprintf(buf, 2048, " Super: %s\n", classname(RCLASSEXT_SUPER(ext))); + rb_str_cat_cstr(res, buf); + + tbl = RCLASSEXT_M_TBL(ext); + if (tbl) { + ary = rb_ary_new_capa((long)rb_id_table_size(tbl)); + rb_id_table_foreach(RCLASSEXT_M_TBL(ext), dump_classext_methods_i, (void *)ary); + rb_ary_sort_bang(ary); + snprintf(buf, 4096, " Methods(%ld): ", RARRAY_LEN(ary)); + rb_str_cat_cstr(res, buf); + rb_str_concat(res, rb_ary_join(ary, rb_str_new_cstr(","))); + rb_str_cat_cstr(res, "\n"); + } + else { + rb_str_cat_cstr(res, " Methods(0): .\n"); + } + + tbl = RCLASSEXT_CONST_TBL(ext); + if (tbl) { + ary = rb_ary_new_capa((long)rb_id_table_size(tbl)); + rb_id_table_foreach(tbl, dump_classext_constants_i, (void *)ary); + rb_ary_sort_bang(ary); + snprintf(buf, 4096, " Constants(%ld): ", RARRAY_LEN(ary)); + rb_str_cat_cstr(res, buf); + rb_str_concat(res, rb_ary_join(ary, rb_str_new_cstr(","))); + rb_str_cat_cstr(res, "\n"); + } + else { + rb_str_cat_cstr(res, " Constants(0): .\n"); + } +} + +static VALUE +rb_f_dump_classext(VALUE recv, VALUE klass) +{ + /* + * The desired output String value is: + * Class: 0x88800932 (String) [singleton] + * Prime classext namespace(2,main), readable(t), writable(f) + * Non-prime classexts: 3 + * Namespace 2: prime classext 0x88800933 + * Super: Object + * Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ... + * Constants(12): FOO, Bar, ... + * Namespace 5: classext 0x88800934 + * Super: Object + * Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ... + * Constants(12): FOO, Bar, ... + */ + char buf[2048]; + VALUE res; + const rb_classext_t *ext; + const rb_namespace_t *ns; + st_table *classext_tbl; + + if (!(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE))) { + snprintf(buf, 2048, "Non-class/module value: %p (%s)\n", (void *)klass, rb_type_str(BUILTIN_TYPE(klass))); + return rb_str_new_cstr(buf); + } + + if (RB_TYPE_P(klass, T_CLASS)) { + snprintf(buf, 2048, "Class: %p (%s)%s\n", + (void *)klass, classname(klass), RCLASS_SINGLETON_P(klass) ? " [singleton]" : ""); + } + else { + snprintf(buf, 2048, "Module: %p (%s)\n", (void *)klass, classname(klass)); + } + res = rb_str_new_cstr(buf); + + ext = RCLASS_EXT_PRIME(klass); + ns = RCLASSEXT_NS(ext); + snprintf(buf, 2048, "Prime classext namespace(%ld,%s), readable(%s), writable(%s)\n", + ns->ns_id, + NAMESPACE_ROOT_P(ns) ? "root" : (NAMESPACE_MAIN_P(ns) ? "main" : "optional"), + RCLASS_PRIME_CLASSEXT_READABLE_P(klass) ? "t" : "f", + RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass) ? "t" : "f"); + rb_str_cat_cstr(res, buf); + + classext_tbl = RCLASS_CLASSEXT_TBL(klass); + if (!classext_tbl) { + rb_str_cat_cstr(res, "Non-prime classexts: 0\n"); + } + else { + snprintf(buf, 2048, "Non-prime classexts: %zu\n", st_table_size(classext_tbl)); + rb_str_cat_cstr(res, buf); + } + + rb_class_classext_foreach(klass, dump_classext_i, (void *)res); + + return res; +} + +#endif /* RUBY_DEBUG */ + /* * Document-class: Namespace * @@ -977,8 +1111,13 @@ Init_Namespace(void) namespace_define_loader_method("require"); namespace_define_loader_method("require_relative"); namespace_define_loader_method("load"); + if (rb_namespace_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); + +#ifdef RUBY_DEBUG + rb_define_global_function("dump_classext", rb_f_dump_classext, 1); +#endif } rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); From 20c73b17232cc5bd0f8a3c13507d56b5f11ab2ed Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 27 Jul 2025 11:04:15 +0900 Subject: [PATCH 0009/2435] Skip CFUNC frames in the current namespace detection * The current namespace should be based on the Ruby-level location (file, line no in .rb) and we can get it by LEP(ep) basically (VM_ENV_FLAG_LOCAL flag is set) * But the control frame with VM_FRAME_MAGIC_CFUNC is also a LOCAL frame because it's a visible Ruby-level frame without block handlers * So, for the namespace detection, LEP(ep) is not enough and we need to skip CFUNC frames to fetch the caller of such frames --- namespace.c | 2 +- vm.c | 59 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/namespace.c b/namespace.c index e30d426d68b237..82bacd41081208 100644 --- a/namespace.c +++ b/namespace.c @@ -460,7 +460,7 @@ rb_namespace_s_current(VALUE recv) if (!rb_namespace_available()) return Qnil; - ns = rb_vm_caller_namespace(GET_EC()); + ns = rb_vm_current_namespace(GET_EC()); VM_ASSERT(ns && ns->ns_object); return ns->ns_object; } diff --git a/vm.c b/vm.c index 790a5af4f0cf7d..1a328fb63c6c3d 100644 --- a/vm.c +++ b/vm.c @@ -95,6 +95,29 @@ rb_vm_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame } } +static const VALUE * +VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp) +{ + const VALUE *ep = current_cfp->ep; + const rb_control_frame_t *cfp, *checkpoint_cfp = current_cfp; + + while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + } + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + if (!cfp) { + cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + ep = cfp->ep; + } + checkpoint_cfp = cfp; + cfp = NULL; + } + return ep; +} + const VALUE * rb_vm_ep_local_ep(const VALUE *ep) { @@ -1001,11 +1024,8 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co VM_FORCE_WRITE_SPECIAL_CONST(&ep[VM_ENV_DATA_INDEX_SPECVAL], VM_GUARDED_PREV_EP(prev_cfp->ep)); } } - else if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)) { - // block_handler is always VM_BLOCK_HANDLER_NONE in this case - } else { - VM_ASSERT(VM_ENV_LOCAL_P(ep) && !VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)); + VM_ASSERT(VM_ENV_LOCAL_P(ep)); VALUE block_handler = VM_ENV_BLOCK_HANDLER(ep); if (block_handler != VM_BLOCK_HANDLER_NONE) { @@ -3017,15 +3037,15 @@ rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec) } static const rb_namespace_t * -current_namespace_on_env(const VALUE *ep) +current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) { rb_callable_method_entry_t *cme; const rb_namespace_t *ns; - const VALUE *lep = VM_EP_LEP(ep); + const VALUE *lep = VM_EP_RUBY_LEP(ec, cfp); VM_ASSERT(lep); VM_ASSERT(rb_namespace_available()); - if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CFUNC)) { + if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD)) { cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); VM_ASSERT(cme); VM_ASSERT(cme->def); @@ -3053,29 +3073,16 @@ const rb_namespace_t * rb_vm_current_namespace(const rb_execution_context_t *ec) { VM_ASSERT(rb_namespace_available()); - return current_namespace_on_env(ec->cfp->ep); -} - -const rb_namespace_t * -rb_vm_caller_namespace(const rb_execution_context_t *ec) -{ - const rb_control_frame_t *caller_cfp; - - VM_ASSERT(rb_namespace_available()); - - // The current control frame is MAGIC_CFUNC to call Namespace.current, but - // we want to get the current namespace of its caller. - caller_cfp = vm_get_ruby_level_caller_cfp(ec, ec->cfp); - return current_namespace_on_env(caller_cfp->ep); + return current_namespace_on_cfp(ec, ec->cfp); } static const rb_control_frame_t * -find_loader_control_frame(const rb_control_frame_t *cfp, const rb_control_frame_t *end_cfp) +find_loader_control_frame(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, const rb_control_frame_t *end_cfp) { while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { if (!VM_ENV_FRAME_TYPE_P(cfp->ep, VM_FRAME_MAGIC_CFUNC)) break; - if (!NAMESPACE_ROOT_P(current_namespace_on_env(cfp->ep))) + if (!NAMESPACE_ROOT_P(current_namespace_on_cfp(ec, cfp))) break; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } @@ -3102,13 +3109,13 @@ rb_vm_loading_namespace(const rb_execution_context_t *ec) return rb_get_namespace_t(cfp->self); } // Kernel#require, #require_relative, #load - cfp = find_loader_control_frame(cfp, end_cfp); - return current_namespace_on_env(cfp->ep); + cfp = find_loader_control_frame(ec, cfp, end_cfp); + return current_namespace_on_cfp(ec, cfp); } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } // no require/load with explicit namespaces. - return current_namespace_on_env(current_cfp->ep); + return current_namespace_on_cfp(ec, current_cfp); } /* vm */ From 32f58628e900894b48b9e8630c250dedbbb1c126 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 10 Aug 2025 18:45:44 +0900 Subject: [PATCH 0010/2435] Update Namespace#eval to use control frames instead of namespace_push/pop With this change, the argument code of Namespace#eval cannot refer local variables around the calling line, but it should not be able to refer these values. The code is evaluated in the receiver namespace, independently from the local context. --- iseq.c | 15 +++++++++++++++ iseq.h | 1 + namespace.c | 18 +++++++++--------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/iseq.c b/iseq.c index ad9149ef98e815..ae30d60ced9e3f 100644 --- a/iseq.c +++ b/iseq.c @@ -1161,6 +1161,21 @@ rb_iseq_load_iseq(VALUE fname) return NULL; } +const rb_iseq_t * +rb_iseq_compile_iseq(VALUE str, VALUE fname) +{ + VALUE args[] = { + str, fname + }; + VALUE iseqv = rb_check_funcall(rb_cISeq, rb_intern("compile"), 2, args); + + if (!SPECIAL_CONST_P(iseqv) && RBASIC_CLASS(iseqv) == rb_cISeq) { + return iseqw_check(iseqv); + } + + return NULL; +} + #define CHECK_ARRAY(v) rb_to_array_type(v) #define CHECK_HASH(v) rb_to_hash_type(v) #define CHECK_STRING(v) rb_str_to_str(v) diff --git a/iseq.h b/iseq.h index c7f091a0b4c2df..a8ad8ef9b064b8 100644 --- a/iseq.h +++ b/iseq.h @@ -192,6 +192,7 @@ void rb_iseq_init_trace(rb_iseq_t *iseq); int rb_iseq_add_local_tracepoint_recursively(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line, bool target_bmethod); int rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval); const rb_iseq_t *rb_iseq_load_iseq(VALUE fname); +const rb_iseq_t *rb_iseq_compile_iseq(VALUE str, VALUE fname); int rb_iseq_opt_frozen_string_literal(void); #if VM_INSN_INFO_TABLE_IMPL == 2 diff --git a/namespace.c b/namespace.c index 82bacd41081208..3b7185e9aada1d 100644 --- a/namespace.c +++ b/namespace.c @@ -12,6 +12,7 @@ #include "internal/namespace.h" #include "internal/st.h" #include "internal/variable.h" +#include "iseq.h" #include "ruby/internal/globals.h" #include "ruby/util.h" #include "vm_core.h" @@ -842,21 +843,20 @@ initialize_root_namespace(void) } } -static VALUE -rb_namespace_eval_string(VALUE str) -{ - return rb_eval_string(RSTRING_PTR(str)); -} - static VALUE rb_namespace_eval(VALUE namespace, VALUE str) { - rb_thread_t *th = GET_THREAD(); + const rb_iseq_t *iseq; + const rb_namespace_t *ns; StringValue(str); - namespace_push(th, namespace); - return rb_ensure(rb_namespace_eval_string, str, namespace_pop, (VALUE)th); + iseq = rb_iseq_compile_iseq(str, rb_str_new_cstr("eval")); + VM_ASSERT(iseq); + + ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + + return rb_iseq_eval(iseq, ns); } static int namespace_experimental_warned = 0; From 53a1ff71e7dd898408b68c5c5c4429d0c93ba25f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 11 Aug 2025 15:50:28 +0900 Subject: [PATCH 0011/2435] Add and fix dependencies --- depend | 26 +++++++++++++++++ yjit/src/cruby_bindings.inc.rs | 53 +--------------------------------- zjit/src/cruby_bindings.inc.rs | 3 +- 3 files changed, 28 insertions(+), 54 deletions(-) diff --git a/depend b/depend index fa17e2ea6a43f6..0f1d0d3af8c308 100644 --- a/depend +++ b/depend @@ -9263,6 +9263,26 @@ namespace.$(OBJEXT): $(top_srcdir)/internal/string.h namespace.$(OBJEXT): $(top_srcdir)/internal/variable.h namespace.$(OBJEXT): $(top_srcdir)/internal/vm.h namespace.$(OBJEXT): $(top_srcdir)/internal/warnings.h +namespace.$(OBJEXT): $(top_srcdir)/prism/defines.h +namespace.$(OBJEXT): $(top_srcdir)/prism/encoding.h +namespace.$(OBJEXT): $(top_srcdir)/prism/node.h +namespace.$(OBJEXT): $(top_srcdir)/prism/options.h +namespace.$(OBJEXT): $(top_srcdir)/prism/pack.h +namespace.$(OBJEXT): $(top_srcdir)/prism/parser.h +namespace.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h +namespace.$(OBJEXT): $(top_srcdir)/prism/prism.h +namespace.$(OBJEXT): $(top_srcdir)/prism/regexp.h +namespace.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h +namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h namespace.$(OBJEXT): {$(VPATH)}assert.h namespace.$(OBJEXT): {$(VPATH)}atomic.h namespace.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -9279,6 +9299,7 @@ namespace.$(OBJEXT): {$(VPATH)}constant.h namespace.$(OBJEXT): {$(VPATH)}debug_counter.h namespace.$(OBJEXT): {$(VPATH)}defines.h namespace.$(OBJEXT): {$(VPATH)}encoding.h +namespace.$(OBJEXT): {$(VPATH)}eval_intern.h namespace.$(OBJEXT): {$(VPATH)}id.h namespace.$(OBJEXT): {$(VPATH)}id_table.h namespace.$(OBJEXT): {$(VPATH)}intern.h @@ -9433,12 +9454,17 @@ namespace.$(OBJEXT): {$(VPATH)}internal/value_type.h namespace.$(OBJEXT): {$(VPATH)}internal/variable.h namespace.$(OBJEXT): {$(VPATH)}internal/warning_push.h namespace.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +namespace.$(OBJEXT): {$(VPATH)}iseq.h namespace.$(OBJEXT): {$(VPATH)}method.h namespace.$(OBJEXT): {$(VPATH)}missing.h namespace.$(OBJEXT): {$(VPATH)}namespace.c namespace.$(OBJEXT): {$(VPATH)}node.h namespace.$(OBJEXT): {$(VPATH)}onigmo.h namespace.$(OBJEXT): {$(VPATH)}oniguruma.h +namespace.$(OBJEXT): {$(VPATH)}prism/ast.h +namespace.$(OBJEXT): {$(VPATH)}prism/diagnostic.h +namespace.$(OBJEXT): {$(VPATH)}prism/version.h +namespace.$(OBJEXT): {$(VPATH)}prism_compile.h namespace.$(OBJEXT): {$(VPATH)}ruby_assert.h namespace.$(OBJEXT): {$(VPATH)}ruby_atomic.h namespace.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index d9ef2d494f6d55..1e34440460edd1 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -259,33 +259,6 @@ pub const RSTRING_FSTR: ruby_rstring_flags = 536870912; pub type ruby_rstring_flags = u32; pub type st_data_t = ::std::os::raw::c_ulong; pub type st_index_t = st_data_t; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct st_hash_type { - pub compare: ::std::option::Option< - unsafe extern "C" fn(arg1: st_data_t, arg2: st_data_t) -> ::std::os::raw::c_int, - >, - pub hash: ::std::option::Option st_index_t>, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct st_table_entry { - _unused: [u8; 0], -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct st_table { - pub entry_power: ::std::os::raw::c_uchar, - pub bin_power: ::std::os::raw::c_uchar, - pub size_ind: ::std::os::raw::c_uchar, - pub rebuilds_num: ::std::os::raw::c_uint, - pub type_: *const st_hash_type, - pub num_entries: st_index_t, - pub bins: *mut st_index_t, - pub entries_start: st_index_t, - pub entries_bound: st_index_t, - pub entries: *mut st_table_entry, -} pub const ST_CONTINUE: st_retval = 0; pub const ST_STOP: st_retval = 1; pub const ST_DELETE: st_retval = 2; @@ -373,28 +346,6 @@ pub const BOP_PACK: ruby_basic_operators = 32; pub const BOP_INCLUDE_P: ruby_basic_operators = 33; pub const BOP_LAST_: ruby_basic_operators = 34; pub type ruby_basic_operators = u32; -#[repr(C)] -pub struct rb_namespace_struct { - pub ns_object: VALUE, - pub ns_id: ::std::os::raw::c_long, - pub top_self: VALUE, - pub load_path: VALUE, - pub load_path_snapshot: VALUE, - pub load_path_check_cache: VALUE, - pub expanded_load_path: VALUE, - pub loaded_features: VALUE, - pub loaded_features_snapshot: VALUE, - pub loaded_features_realpaths: VALUE, - pub loaded_features_realpath_map: VALUE, - pub loaded_features_index: *mut st_table, - pub loading_table: *mut st_table, - pub ruby_dln_libmap: VALUE, - pub gvar_tbl: VALUE, - pub is_builtin: bool, - pub is_user: bool, - pub is_optional: bool, -} -pub type rb_namespace_t = rb_namespace_struct; pub type rb_serial_t = ::std::os::raw::c_ulonglong; pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1; @@ -580,7 +531,6 @@ pub type rb_control_frame_t = rb_control_frame_struct; #[repr(C)] pub struct rb_proc_t { pub block: rb_block, - pub ns: *const rb_namespace_t, pub _bitfield_align_1: [u8; 0], pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, pub __bindgen_padding_0: [u8; 7usize], @@ -676,8 +626,7 @@ pub const VM_FRAME_FLAG_LAMBDA: vm_frame_env_flags = 256; pub const VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM: vm_frame_env_flags = 512; pub const VM_FRAME_FLAG_CFRAME_KW: vm_frame_env_flags = 1024; pub const VM_FRAME_FLAG_PASSED: vm_frame_env_flags = 2048; -pub const VM_FRAME_FLAG_NS_SWITCH: vm_frame_env_flags = 4096; -pub const VM_FRAME_FLAG_LOAD_ISEQ: vm_frame_env_flags = 8192; +pub const VM_FRAME_FLAG_NS_REQUIRE: vm_frame_env_flags = 4096; pub const VM_ENV_FLAG_LOCAL: vm_frame_env_flags = 2; pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5a06d99f9ee661..17a2d5a63d6e6a 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -390,8 +390,7 @@ pub const VM_FRAME_FLAG_LAMBDA: vm_frame_env_flags = 256; pub const VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM: vm_frame_env_flags = 512; pub const VM_FRAME_FLAG_CFRAME_KW: vm_frame_env_flags = 1024; pub const VM_FRAME_FLAG_PASSED: vm_frame_env_flags = 2048; -pub const VM_FRAME_FLAG_NS_SWITCH: vm_frame_env_flags = 4096; -pub const VM_FRAME_FLAG_LOAD_ISEQ: vm_frame_env_flags = 8192; +pub const VM_FRAME_FLAG_NS_REQUIRE: vm_frame_env_flags = 4096; pub const VM_ENV_FLAG_LOCAL: vm_frame_env_flags = 2; pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; From f3f70323bb2d66b823f81c286463f91dbfe853fe Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 11 Aug 2025 16:14:30 +0900 Subject: [PATCH 0012/2435] Skip loading gem_prelude in wasm environment --- builtin.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin.c b/builtin.c index 158b98568530cb..d91ab2157c0e71 100644 --- a/builtin.c +++ b/builtin.c @@ -77,5 +77,11 @@ Init_builtin(void) void Init_builtin_features(void) { + +#ifndef BUILTIN_BINARY_SIZE + load_with_builtin_functions("gem_prelude", NULL); + +#endif + } From 228d2c39f05fff9c056a02647a764e164cbd729f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 11 Aug 2025 16:17:15 +0900 Subject: [PATCH 0013/2435] Stop using C23 spec: initialization with an empty struct --- namespace.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/namespace.c b/namespace.c index 3b7185e9aada1d..c655a5e947f0ea 100644 --- a/namespace.c +++ b/namespace.c @@ -25,6 +25,10 @@ VALUE rb_mNamespaceLoader = 0; static rb_namespace_t root_namespace_data = { /* Initialize values lazily in Init_namespace() */ + (VALUE)NULL, 0, + (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, + (struct st_table *)NULL, (struct st_table *)NULL, (VALUE)NULL, (VALUE)NULL, + false, false }; static rb_namespace_t * root_namespace = &root_namespace_data; From bff625d2a673ea73c720cac50a9d596ab02432a8 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 12 Aug 2025 10:08:20 +0900 Subject: [PATCH 0014/2435] add VM_ENV_NAMESPACED_P to unify/simplify/correct when SPECVAL has a namespace --- vm.c | 6 +++--- vm_core.h | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/vm.c b/vm.c index 1a328fb63c6c3d..6d63a5eeaff30f 100644 --- a/vm.c +++ b/vm.c @@ -142,10 +142,10 @@ static inline VALUE VM_CF_BLOCK_HANDLER(const rb_control_frame_t * const cfp) { const VALUE *ep; - if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_TOP) { + if (VM_ENV_NAMESPACED_P(cfp->ep)) { VM_ASSERT(VM_ENV_LOCAL_P(cfp->ep)); - /* Never set black_handler for VM_FRAME_MAGIC_TOP - * and the specval is used for namespace (rb_namespace_t) in the case + /* Never set black_handler for VM_FRAME_MAGIC_TOP or VM_FRAME_MAGIC_CLASS + * and the specval is used for namespace (rb_namespace_t) in these case */ return VM_BLOCK_HANDLER_NONE; } diff --git a/vm_core.h b/vm_core.h index fab440f1fd5175..51898f56f9c559 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1559,10 +1559,16 @@ VM_ENV_PREV_EP(const VALUE *ep) return VM_ENV_PREV_EP_UNCHECKED(ep); } +static inline bool +VM_ENV_NAMESPACED_P(const VALUE *ep) +{ + return VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP); +} + static inline VALUE VM_ENV_BLOCK_HANDLER(const VALUE *ep) { - if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)) { + if (VM_ENV_NAMESPACED_P(ep)) { VM_ASSERT(VM_ENV_LOCAL_P(ep)); return VM_BLOCK_HANDLER_NONE; } @@ -1574,7 +1580,7 @@ VM_ENV_BLOCK_HANDLER(const VALUE *ep) static inline const rb_namespace_t * VM_ENV_NAMESPACE(const VALUE *ep) { - VM_ASSERT(VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP)); + VM_ASSERT(VM_ENV_NAMESPACED_P(ep)); VM_ASSERT(VM_ENV_LOCAL_P(ep)); return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); } From 2622d792969ab53275b84b6df6094902c6309c80 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 12 Aug 2025 10:09:28 +0900 Subject: [PATCH 0015/2435] fix the wrong patch: 6cea12a4de44e0c072e33eca51b57965068b474a --- builtin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin.c b/builtin.c index d91ab2157c0e71..657143a739f649 100644 --- a/builtin.c +++ b/builtin.c @@ -78,7 +78,7 @@ void Init_builtin_features(void) { -#ifndef BUILTIN_BINARY_SIZE +#ifdef BUILTIN_BINARY_SIZE load_with_builtin_functions("gem_prelude", NULL); From 140bf4d8a035f669b24541ff71144d086588f7b5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 12 Aug 2025 11:34:30 +0900 Subject: [PATCH 0016/2435] localize rb_vm_t and minimize times of GET_VM() calls --- load.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/load.c b/load.c index 6f3da288aaafcc..cab3cdf3b20767 100644 --- a/load.c +++ b/load.c @@ -1149,21 +1149,23 @@ search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, fea // Check if it's a statically linked extension when // not already a feature and not found as a dynamic library. - rb_vm_t *vm = GET_VM(); - if (!ft && type != loadable_ext_rb && vm->static_ext_inits) { - VALUE lookup_name = tmp; - // Append ".so" if not already present so for example "etc" can find "etc.so". - // We always register statically linked extensions with a ".so" extension. - // See encinit.c and extinit.c (generated at build-time). - if (!ext) { - lookup_name = rb_str_dup(lookup_name); - rb_str_cat_cstr(lookup_name, ".so"); - } - ftptr = RSTRING_PTR(lookup_name); - if (st_lookup(vm->static_ext_inits, (st_data_t)ftptr, NULL)) { - *path = rb_filesystem_str_new_cstr(ftptr); - RB_GC_GUARD(lookup_name); - return 's'; + if (!ft && type != loadable_ext_rb) { + rb_vm_t *vm = GET_VM(); + if (vm->static_ext_inits) { + VALUE lookup_name = tmp; + // Append ".so" if not already present so for example "etc" can find "etc.so". + // We always register statically linked extensions with a ".so" extension. + // See encinit.c and extinit.c (generated at build-time). + if (!ext) { + lookup_name = rb_str_dup(lookup_name); + rb_str_cat_cstr(lookup_name, ".so"); + } + ftptr = RSTRING_PTR(lookup_name); + if (st_lookup(vm->static_ext_inits, (st_data_t)ftptr, NULL)) { + *path = rb_filesystem_str_new_cstr(ftptr); + RB_GC_GUARD(lookup_name); + return 's'; + } } } From f9ea85dd41f66f50447e55123e826726574cb418 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 12 Aug 2025 11:36:04 +0900 Subject: [PATCH 0017/2435] delete unused code --- namespace.c | 134 ---------------------------------------------------- 1 file changed, 134 deletions(-) diff --git a/namespace.c b/namespace.c index c655a5e947f0ea..3ace63ffc4ab46 100644 --- a/namespace.c +++ b/namespace.c @@ -72,119 +72,6 @@ rb_main_namespace(void) return main_namespace; } -/* -static bool -namespace_ignore_builtin_primitive_methods_p(const rb_namespace_t *ns, rb_method_definition_t *def) -{ - if (!NAMESPACE_BUILTIN_P(ns)) { - return false; - } - / Primitive methods (just to call C methods) covers/hides the effective - namespaces, so ignore the methods' namespaces to expose user code's - namespace to the implementation. - / - if (def->type == VM_METHOD_TYPE_ISEQ) { - ID mid = def->original_id; - const char *path = RSTRING_PTR(pathobj_path(def->body.iseq.iseqptr->body->location.pathobj)); - if (strcmp(path, "") == 0) { - if (mid == rb_intern("class") || mid == rb_intern("clone") || - mid == rb_intern("tag") || mid == rb_intern("then") || - mid == rb_intern("yield_self") || mid == rb_intern("loop") || - mid == rb_intern("Float") || mid == rb_intern("Integer") - ) { - return true; - } - } - else if (strcmp(path, "") == 0) { - if (mid == rb_intern("warn")) { - return true; - } - } - else if (strcmp(path, "") == 0) { - if (mid == rb_intern("load")) - return true; - } - } - return false; -} - -static inline const rb_namespace_t * -block_proc_namespace(const VALUE procval) -{ - rb_proc_t *proc; - - if (procval) { - GetProcPtr(procval, proc); - return proc->ns; - } - else { - return NULL; - } -} - -static const rb_namespace_t * -current_namespace(bool permit_calling_builtin) -{ - / - * TODO: move this code to vm.c or somewhere else - * when it's fully updated with VM_FRAME_FLAG_* - / - const rb_callable_method_entry_t *cme; - const rb_namespace_t *ns; - rb_execution_context_t *ec = GET_EC(); - rb_control_frame_t *cfp = ec->cfp; - rb_thread_t *th = rb_ec_thread_ptr(ec); - int calling = 1; - - if (!rb_namespace_available()) - return 0; - - if (th->namespaces && RARRAY_LEN(th->namespaces) > 0) { - // temp code to detect the context is in require/load - // TODO: this doesn't work well in optional namespaces - // calling = 0; - } - while (calling) { - const rb_namespace_t *proc_ns = NULL; - VALUE bh; - if (VM_FRAME_NS_SWITCH_P(cfp)) { - bh = rb_vm_frame_block_handler(cfp); - if (bh && vm_block_handler_type(bh) == block_handler_type_proc) { - proc_ns = block_proc_namespace(VM_BH_TO_PROC(bh)); - if (permit_calling_builtin || NAMESPACE_USER_P(proc_ns)) - return proc_ns; - } - } - cme = rb_vm_frame_method_entry(cfp); - if (cme && cme->def) { - ns = cme->def->ns; - if (ns) { - // this method is not a built-in class/module's method - // or a built-in primitive (Ruby) method - if (!namespace_ignore_builtin_primitive_methods_p(ns, cme->def)) { - if (permit_calling_builtin || (proc_ns && NAMESPACE_USER_P(proc_ns))) - return ns; - } - } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - } - else { - calling = 0; - } - } - // not in namespace-marked method calls - ns = th->ns; - if (ns) { - return ns; - } - if (!main_namespace) { - // Namespaces are not ready to be created - return root_namespace; - } - return main_namespace; -} -*/ - const rb_namespace_t * rb_current_namespace(void) { @@ -204,27 +91,6 @@ rb_current_namespace(void) const rb_namespace_t * rb_loading_namespace(void) { - /* - VALUE namespace; - long len; - VALUE require_stack = GET_VM()->require_stack; - - if (!rb_namespace_available()) - return 0; - - if (!require_stack) { - return current_namespace(false); - } - if ((len = RARRAY_LEN(require_stack)) == 0) { - return current_namespace(false); - } - - if (!RB_TYPE_P(require_stack, T_ARRAY)) - rb_bug("require_stack is not an array: %s", rb_type_str(BUILTIN_TYPE(require_stack))); - - namespace = RARRAY_AREF(require_stack, len-1); - return rb_get_namespace_t(namespace); - */ if (!main_namespace) return root_namespace; From 58030884d80cb6f24f887c5914ceb572e996df4f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 12 Aug 2025 15:04:31 -0700 Subject: [PATCH 0018/2435] YJIT: respect the code in master branch * Originally, k0kubun added a change to respect namespace in gen_block_given https://github.com/ruby/ruby/pull/13454/commits/d129669b1729b9570da7958394ea594031e79f59 * Just after the change, XrXr proposes a change on master and he says it can fix the problem around block_given?, and it has been merged into the master https://github.com/ruby/ruby/pull/14208 * tagomoris respect the commit on master then will see if it works --- yjit/src/codegen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 67841d2fdc721e..25a8545e859172 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6540,6 +6540,7 @@ fn jit_rb_f_block_given_p( true } +/// Codegen for `block_given?` and `defined?(yield)` fn gen_block_given( jit: &mut JITState, asm: &mut Assembler, From 81f3591b17bbcbc6081d9f80e2855fd109ec3830 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 14:15:42 +0900 Subject: [PATCH 0019/2435] Unify all block_handler getter to check namespace consistently --- vm_insnhelper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 8e7b7c743e7ebb..6d32a7535b3308 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -144,7 +144,7 @@ CC_SET_FASTPATH(const struct rb_callcache *cc, vm_call_handler func, bool enable } } -#define GET_BLOCK_HANDLER() VM_ENV_BLOCK_HANDLER(VM_EP_LEP(GET_EP())) +#define GET_BLOCK_HANDLER() VM_CF_BLOCK_HANDLER(GET_CFP()) /**********************************************************/ /* deal with control flow 3: exception */ From f58f7f25a38a452ac7b288dab2e463e2d3076db5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 14:17:14 +0900 Subject: [PATCH 0020/2435] Fix bug of uninitialized variable, missed EoCFP, return values --- vm.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vm.c b/vm.c index 6d63a5eeaff30f..1867f95fd9fc6a 100644 --- a/vm.c +++ b/vm.c @@ -99,18 +99,37 @@ static const VALUE * VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp) { const VALUE *ep = current_cfp->ep; - const rb_control_frame_t *cfp, *checkpoint_cfp = current_cfp; + const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ + const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { while (!VM_ENV_LOCAL_P(ep)) { ep = VM_ENV_PREV_EP(ep); } - while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { if (!cfp) { cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); + VM_ASSERT(cfp, "rb_vm_search_cf_from_ep should return a valid cfp for the ep"); + VM_ASSERT(cfp->ep == ep); + } + if (!cfp) { + return NULL; + } + VM_ASSERT(cfp->ep); + VM_ASSERT(cfp->ep == ep); + + if (VM_FRAME_FINISHED_P(cfp)) { + rb_bug("CFUNC frame should not FINISHED"); } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + if (cfp >= eocfp) { + return NULL; + } + VM_ASSERT(cfp, "CFUNC should have a valid previous control frame"); ep = cfp->ep; + if (!ep) { + return NULL; + } } checkpoint_cfp = cfp; cfp = NULL; From 4644d14990df61b1fcc22cd9016a9f877ea29d32 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 14:44:35 +0900 Subject: [PATCH 0021/2435] Fix the missed vm_ns during rebase to follow the change b227a942b205c89fdb5adc85acdf029b9b83faf1 --- load.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/load.c b/load.c index cab3cdf3b20767..a0c45561d7eca4 100644 --- a/load.c +++ b/load.c @@ -390,10 +390,10 @@ get_loaded_features_index(const rb_namespace_t *ns) features_index_add(ns, as_str, INT2FIX(i)); } /* The user modified $LOADED_FEATURES, so we should restore the changes. */ - if (!rb_ary_shared_with_p(features, CURRENT_NS_LOADED_FEATURES(vm_ns))) { + if (!rb_ary_shared_with_p(features, ns->loaded_features)) { rb_ary_replace(ns->loaded_features, features); } - reset_loaded_features_snapshot(vm_ns); + reset_loaded_features_snapshot(ns); features = ns->loaded_features_snapshot; long j = RARRAY_LEN(features); From 9361af6ee7019958ea5d011d9e4428bf71f5e080 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 16:09:11 +0900 Subject: [PATCH 0022/2435] Update dependency after rebase --- depend | 3 +++ 1 file changed, 3 insertions(+) diff --git a/depend b/depend index 0f1d0d3af8c308..d5ac468b44209d 100644 --- a/depend +++ b/depend @@ -9263,7 +9263,9 @@ namespace.$(OBJEXT): $(top_srcdir)/internal/string.h namespace.$(OBJEXT): $(top_srcdir)/internal/variable.h namespace.$(OBJEXT): $(top_srcdir)/internal/vm.h namespace.$(OBJEXT): $(top_srcdir)/internal/warnings.h +namespace.$(OBJEXT): $(top_srcdir)/prism/ast.h namespace.$(OBJEXT): $(top_srcdir)/prism/defines.h +namespace.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h namespace.$(OBJEXT): $(top_srcdir)/prism/encoding.h namespace.$(OBJEXT): $(top_srcdir)/prism/node.h namespace.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -9283,6 +9285,7 @@ namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h +namespace.$(OBJEXT): $(top_srcdir)/prism/version.h namespace.$(OBJEXT): {$(VPATH)}assert.h namespace.$(OBJEXT): {$(VPATH)}atomic.h namespace.$(OBJEXT): {$(VPATH)}backward/2/assume.h From 88b5287d29b381126a4a5566005033acb269d874 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 16:33:27 +0900 Subject: [PATCH 0023/2435] re-implement free/memsize for rb_namespace_t correctly --- namespace.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/namespace.c b/namespace.c index 3ace63ffc4ab46..2aadd37208386b 100644 --- a/namespace.c +++ b/namespace.c @@ -186,27 +186,32 @@ rb_namespace_entry_mark(void *ptr) rb_gc_mark(ns->gvar_tbl); } -// TODO: implemente namespace_entry_free to free loading_table etc -/* static int free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg) { xfree((char *)key); return ST_DELETE; } - if (vm->loading_table) { - st_foreach(vm->loading_table, free_loading_table_entry, 0); - st_free_table(vm->loading_table); - vm->loading_table = 0; - } -*/ -#define namespace_entry_free RUBY_TYPED_DEFAULT_FREE + +static void +namespace_entry_free(void *ptr) +{ + rb_namespace_t *ns = (rb_namespace_t *)ptr; + if (ns->loading_table) { + st_foreach(ns->loading_table, free_loading_table_entry, 0); + st_free_table(ns->loading_table); + ns->loading_table = 0; + } + xfree(ptr); +} static size_t namespace_entry_memsize(const void *ptr) { - // TODO: rb_st_memsize(loaded_features_index) + rb_st_memsize(vm->loading_table) - return sizeof(rb_namespace_t); + const rb_namespace_t *ns = (const rb_namespace_t *)ptr; + return sizeof(rb_namespace_t) + \ + rb_st_memsize(ns->loaded_features_index) + \ + rb_st_memsize(ns->loading_table); } const rb_data_type_t rb_namespace_data_type = { From ccbf0662f826c38231e1c14b64695130e09dc582 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Sep 2025 16:46:30 +0900 Subject: [PATCH 0024/2435] No need to set namespace to the frame start evaluating main * rb_vm_current_namespace() returns main_namespace if it's ready, root_namespace otherwise on the top of main_stack. --- vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm.c b/vm.c index 1867f95fd9fc6a..524bde55e6447f 100644 --- a/vm.c +++ b/vm.c @@ -2972,7 +2972,7 @@ rb_iseq_eval_main(const rb_iseq_t *iseq) { rb_execution_context_t *ec = GET_EC(); VALUE val; - vm_set_main_stack(ec, iseq); // TODO: not need to set the namespace? + vm_set_main_stack(ec, iseq); val = vm_exec(ec); return val; } From 88d7ef4c2d3b0009cd38c1cc8d9382a237ae84a4 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 28 Sep 2025 17:39:06 +0900 Subject: [PATCH 0025/2435] calling free() here causes free for un-malloced memory --- namespace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/namespace.c b/namespace.c index 2aadd37208386b..c61b879a88bd4b 100644 --- a/namespace.c +++ b/namespace.c @@ -202,7 +202,6 @@ namespace_entry_free(void *ptr) st_free_table(ns->loading_table); ns->loading_table = 0; } - xfree(ptr); } static size_t From 6e9a3412793a93e437aaed536f9e47a2f4ab9d80 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 28 Sep 2025 17:40:44 +0900 Subject: [PATCH 0026/2435] zeroing on the table to suppress unintentional call of classext_foreach --- class.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/class.c b/class.c index e133d1579b3dd3..68a56a129db72e 100644 --- a/class.c +++ b/class.c @@ -693,6 +693,10 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) RCLASS_SET_SUPER((VALUE)obj, 0); */ + if (namespaceable) { + ((struct RClass_namespaceable *)obj)->ns_classext_tbl = NULL; + } + RCLASS_PRIME_NS((VALUE)obj) = ns; // Classes/Modules defined in user namespaces are // writable directly because it exists only in a namespace. From 9d9390a33c2b9f4836bc7dfe6de1b497663f8b45 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 28 Sep 2025 21:32:57 +0900 Subject: [PATCH 0027/2435] Add methods for debugging only when RUBY_DEBUG --- namespace.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/namespace.c b/namespace.c index c61b879a88bd4b..f6f373094f1b5e 100644 --- a/namespace.c +++ b/namespace.c @@ -826,6 +826,18 @@ Init_enable_namespace(void) #ifdef RUBY_DEBUG +static VALUE +rb_namespace_s_root(VALUE recv) +{ + return root_namespace->ns_object; +} + +static VALUE +rb_namespace_s_main(VALUE recv) +{ + return main_namespace->ns_object; +} + static const char * classname(VALUE klass) { @@ -990,6 +1002,8 @@ Init_Namespace(void) rb_include_module(rb_cObject, rb_mNamespaceLoader); #ifdef RUBY_DEBUG + rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); + rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); rb_define_global_function("dump_classext", rb_f_dump_classext, 1); #endif } From 14c234ae7345c8c325f0d360548c0c08d2838a4a Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 27 Sep 2025 19:59:33 +0100 Subject: [PATCH 0028/2435] [DOC] Tweaks for String#prepend --- doc/string.rb | 2 +- string.c | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/string.rb b/doc/string.rb index c75d171876a1ea..43386c644c80cb 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -388,6 +388,7 @@ # - #<<: Returns +self+ concatenated with a given string or integer. # - #append_as_bytes: Returns +self+ concatenated with strings without performing any # encoding validation or conversion. +# - #prepend: Prefixes to +self+ the concatenation of given other strings. # # _Substitution_ # @@ -448,7 +449,6 @@ # - #+: Returns the concatenation of +self+ and a given other string. # - #center: Returns a copy of +self+, centered by specified padding. # - #concat: Returns the concatenation of +self+ with given other strings. -# - #prepend: Returns the concatenation of a given other string with +self+. # - #ljust: Returns a copy of +self+ of a given length, right-padded with a given other string. # - #rjust: Returns a copy of +self+ of a given length, left-padded with a given other string. # diff --git a/string.c b/string.c index a5743ceda596ce..81353556c956c0 100644 --- a/string.c +++ b/string.c @@ -4086,15 +4086,14 @@ rb_ascii8bit_appendable_encoding_index(rb_encoding *enc, unsigned int code) /* * call-seq: - * prepend(*other_strings) -> string + * prepend(*other_strings) -> new_string * - * Prepends each string in +other_strings+ to +self+ and returns +self+: + * Prefixes to +self+ the concatenation of the given +other_strings+; returns +self+: * - * s = 'foo' - * s.prepend('bar', 'baz') # => "barbazfoo" - * s # => "barbazfoo" + * 'baz'.prepend('foo', 'bar') # => "foobarbaz" + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * - * Related: String#concat. */ static VALUE From 6a66254d0c2d96269afd12ad17498ddac2911cf5 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Mon, 29 Sep 2025 10:18:42 +0900 Subject: [PATCH 0029/2435] [DOC] Mark `Namespace` debug methods to be "nodoc" These methods are debug methods and no RDoc is provided. Mark these methods as "nodoc" to fix "Miscellaneous checks" CI job. Failed CI job: https://github.com/ruby/ruby/actions/runs/18081591948/job/51445635741 --- namespace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/namespace.c b/namespace.c index f6f373094f1b5e..b48914ac0ab004 100644 --- a/namespace.c +++ b/namespace.c @@ -826,12 +826,14 @@ Init_enable_namespace(void) #ifdef RUBY_DEBUG +/* :nodoc: */ static VALUE rb_namespace_s_root(VALUE recv) { return root_namespace->ns_object; } +/* :nodoc: */ static VALUE rb_namespace_s_main(VALUE recv) { @@ -908,6 +910,7 @@ dump_classext_i(rb_classext_t *ext, bool is_prime, VALUE _ns, void *data) } } +/* :nodoc: */ static VALUE rb_f_dump_classext(VALUE recv, VALUE klass) { From 0a0571319e9f10953fc483322a362e2d5cf00e19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 02:03:37 +0000 Subject: [PATCH 0030/2435] Bump actions/cache from 4.2.4 to 4.3.0 Bumps [actions/cache](https://github.com/actions/cache) from 4.2.4 to 4.3.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0400d5f644dc74513175e3cd8d07132dd4860809...0057852bfaa89a56745cba8c7296529d2fc39830) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1083de63b33567..af3d48de72d2d2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -86,7 +86,7 @@ jobs: - name: Restore vcpkg artifact id: restore-vcpkg - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -98,7 +98,7 @@ jobs: if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - name: Save vcpkg artifact - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} From 05471d7f511b7f6fdc758655fda4d13720d8abb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 02:08:47 +0000 Subject: [PATCH 0031/2435] Bump actions/cache in /.github/actions/setup/directories Bumps [actions/cache](https://github.com/actions/cache) from 4.2.4 to 4.3.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0400d5f644dc74513175e3cd8d07132dd4860809...0057852bfaa89a56745cba8c7296529d2fc39830) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 5c2028b98e5d08..0120f1213082c3 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -93,7 +93,7 @@ runs: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache From baec95c85aa7e1ae80180eaf392277e6ecf1a1f3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 29 Sep 2025 08:55:19 -0700 Subject: [PATCH 0032/2435] ZJIT: Incorporate parameter loads into HIR (#14659) --- zjit/src/codegen.rs | 122 +---- zjit/src/cruby.rs | 16 +- zjit/src/hir.rs | 1096 +++++++++++++++++++++++++++++++------------ 3 files changed, 826 insertions(+), 408 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8230d39522504f..2845aaf719963c 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -16,7 +16,7 @@ use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by use crate::stats::{counter_ptr, with_time_stat, Counter, send_fallback_counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, MethodType, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType, SELF_PARAM_IDX}; +use crate::hir::{iseq_to_hir, BlockId, BranchEdge, Invariant, MethodType, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types, Type}; use crate::options::get_option; @@ -126,7 +126,7 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool) })?; // Compile an entry point to the JIT code - gen_entry(cb, iseq, &function, start_ptr).inspect_err(|err| { + gen_entry(cb, iseq, start_ptr).inspect_err(|err| { debug!("{err:?}: gen_entry failed: {}", iseq_get_location(iseq, 0)); }) } @@ -164,11 +164,10 @@ fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) { } /// Compile a JIT entry -fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr) -> Result { +fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result { // Set up registers for CFP, EC, SP, and basic block arguments let mut asm = Assembler::new(); gen_entry_prologue(&mut asm, iseq); - gen_entry_params(&mut asm, iseq, function.entry_block()); // Jump to the first block using a call instruction asm.ccall(function_ptr.raw_ptr(cb), vec![]); @@ -409,8 +408,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)), - &Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level), - &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal_with_ep(asm, opnd!(val), function.type_of(val), ep_offset, level)), + &Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp), + &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), Insn::SetIvar { self_val, id, val, state: _ } => no_output!(gen_setivar(asm, opnd!(self_val), *id, opnd!(val))), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), @@ -430,6 +429,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) }, &Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)), Insn::LoadPC => gen_load_pc(asm), + Insn::LoadSelf => gen_load_self(), &Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index), &Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index), &Insn::ArrayMax { state, .. } @@ -537,19 +537,28 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, /// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs. /// We generate this instruction with level=0 only when the local variable is on the heap, so we /// can't optimize the level=0 case using the SP register. -fn gen_getlocal_with_ep(asm: &mut Assembler, local_ep_offset: u32, level: u32) -> lir::Opnd { +fn gen_getlocal(asm: &mut Assembler, local_ep_offset: u32, level: u32, use_sp: bool) -> lir::Opnd { + let local_ep_offset = i32::try_from(local_ep_offset).unwrap_or_else(|_| panic!("Could not convert local_ep_offset {local_ep_offset} to i32")); if level > 0 { gen_incr_counter(asm, Counter::vm_read_from_parent_iseq_local_count); } - let ep = gen_get_ep(asm, level); - let offset = -(SIZEOF_VALUE_I32 * i32::try_from(local_ep_offset).unwrap_or_else(|_| panic!("Could not convert local_ep_offset {local_ep_offset} to i32"))); - asm.load(Opnd::mem(64, ep, offset)) + let local = if use_sp { + assert_eq!(level, 0, "use_sp optimization should be used only for level=0 locals"); + let offset = -(SIZEOF_VALUE_I32 * (local_ep_offset + 1)); + Opnd::mem(64, SP, offset) + } else { + let ep = gen_get_ep(asm, level); + let offset = -(SIZEOF_VALUE_I32 * local_ep_offset); + Opnd::mem(64, ep, offset) + }; + asm.load(local) } /// Set a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs. /// We generate this instruction with level=0 only when the local variable is on the heap, so we /// can't optimize the level=0 case using the SP register. -fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep_offset: u32, level: u32) { +fn gen_setlocal(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep_offset: u32, level: u32) { + let local_ep_offset = c_int::try_from(local_ep_offset).unwrap_or_else(|_| panic!("Could not convert local_ep_offset {local_ep_offset} to i32")); if level > 0 { gen_incr_counter(asm, Counter::vm_write_to_parent_iseq_local_count); } @@ -558,12 +567,12 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep // When we've proved that we're writing an immediate, // we can skip the write barrier. if val_type.is_immediate() { - let offset = -(SIZEOF_VALUE_I32 * i32::try_from(local_ep_offset).unwrap_or_else(|_| panic!("Could not convert local_ep_offset {local_ep_offset} to i32"))); + let offset = -(SIZEOF_VALUE_I32 * local_ep_offset); asm.mov(Opnd::mem(64, ep, offset), val); } else { // We're potentially writing a reference to an IMEMO/env object, // so take care of the write barrier with a function. - let local_index = c_int::try_from(local_ep_offset).ok().and_then(|idx| idx.checked_mul(-1)).unwrap_or_else(|| panic!("Could not turn {local_ep_offset} into a negative c_int")); + let local_index = local_ep_offset * -1; asm_ccall!(asm, rb_vm_env_write, ep, local_index.into(), val); } } @@ -834,6 +843,10 @@ fn gen_load_pc(asm: &mut Assembler) -> Opnd { asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC)) } +fn gen_load_self() -> Opnd { + Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF) +} + fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd { // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h @@ -871,53 +884,6 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm.mov(SP, Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP)); } -/// Assign method arguments to basic block arguments at JIT entry -fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { - let num_params = entry_block.params().len() - 1; // -1 to exclude self - if num_params > 0 { - asm_comment!(asm, "set method params: {num_params}"); - - // Fill basic block parameters. - // Doing it in reverse is load-bearing. High index params have memory slots that might - // require using a register to fill. Filling them first avoids clobbering. - for idx in (0..num_params).rev() { - let param = param_opnd(idx + 1); // +1 for self - let local = gen_entry_param(asm, iseq, idx); - - // Funky offset adjustment to write into the native stack frame of the - // HIR function we'll be calling into. This only makes sense in context - // of the schedule of instructions in gen_entry() for the JIT entry point. - // - // The entry point needs to load VALUEs into native stack slots _before_ the - // frame containing the slots exists. So, we anticipate the stack frame size - // of the Function and subtract offsets based on that. - // - // native SP at entry point ─────►┌────────────┐ Native SP grows downwards - // │ │ ↓ on all arches we support. - // SP-0x8 ├────────────┤ - // │ │ - // where native SP SP-0x10├────────────┤ - // would be while │ │ - // the HIR function ────────────► └────────────┘ - // is running - match param { - Opnd::Mem(lir::Mem { base: _, disp, num_bits }) => { - let param_slot = Opnd::mem(num_bits, NATIVE_STACK_PTR, disp - Assembler::frame_size()); - asm.mov(param_slot, local); - } - // Prepare for parallel move for locals in registers - reg @ Opnd::Reg(_) => { - asm.load_into(reg, local); - } - _ => unreachable!("on entry, params are either in memory or in reg. Got {param:?}") - } - - // Assign local variables to the basic block arguments - } - } - asm.load_into(param_opnd(SELF_PARAM_IDX), Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF)); -} - /// Set branch params to basic block arguments fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdge) { if branch.args.is_empty() { @@ -941,28 +907,6 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg asm.parallel_mov(moves); } -/// Get a method parameter on JIT entry. As of entry, whether EP is escaped or not solely -/// depends on the ISEQ type. -fn gen_entry_param(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir::Opnd { - let ep_offset = local_idx_to_ep_offset(iseq, local_idx); - - // If the ISEQ does not escape EP, we can optimize the local variable access using the SP register. - if !iseq_entry_escapes_ep(iseq) { - // Create a reference to the local variable using the SP register. We assume EP == BP. - // TODO: Implement the invalidation in rb_zjit_invalidate_no_ep_escape() - let offs = -(SIZEOF_VALUE_I32 * (ep_offset + 1)); - Opnd::mem(64, SP, offs) - } else { - // Get the EP of the current CFP - let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP); - let ep_reg = asm.load(ep_opnd); - - // Create a reference to the local variable using cfp->ep - let offs = -(SIZEOF_VALUE_I32 * ep_offset); - Opnd::mem(64, ep_reg, offs) - } -} - /// Compile a constant fn gen_const_value(val: VALUE) -> lir::Opnd { // Just propagate the constant value and generate nothing @@ -1817,20 +1761,6 @@ fn build_side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason, l } } -/// Return true if a given ISEQ is known to escape EP to the heap on entry. -/// -/// As of vm_push_frame(), EP is always equal to BP. However, after pushing -/// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP. -fn iseq_entry_escapes_ep(iseq: IseqPtr) -> bool { - match unsafe { get_iseq_body_type(iseq) } { - //
frame is always associated to TOPLEVEL_BINDING. - ISEQ_TYPE_MAIN | - // Kernel#eval uses a heap EP when a Binding argument is not nil. - ISEQ_TYPE_EVAL => true, - _ => false, - } -} - /// Returne the maximum number of arguments for a block in a given function fn max_num_params(function: &Function) -> usize { let reverse_post_order = function.rpo(); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 9575c3d1993393..baba0992ccaef9 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -294,7 +294,10 @@ pub fn iseq_opcode_at_idx(iseq: IseqPtr, insn_idx: u32) -> u32 { unsafe { rb_iseq_opcode_at_pc(iseq, pc) as u32 } } -/// Return true if the ISEQ always uses a frame with escaped EP. +/// Return true if a given ISEQ is known to escape EP to the heap on entry. +/// +/// As of vm_push_frame(), EP is always equal to BP. However, after pushing +/// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP. pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { match unsafe { get_iseq_body_type(iseq) } { // The EP of the
frame points to TOPLEVEL_BINDING @@ -305,6 +308,17 @@ pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { } } +/// Index of the local variable that has a rest parameter if any +pub fn iseq_rest_param_idx(iseq: IseqPtr) -> Option { + if !iseq.is_null() && unsafe { get_iseq_flags_has_rest(iseq) } { + let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; + let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; + Some(opt_num + lead_num) + } else { + None + } +} + /// Iterate over all existing ISEQs pub fn for_each_iseq(mut callback: F) { unsafe extern "C" fn callback_wrapper(iseq: IseqPtr, data: *mut c_void) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 598bcb00c4e082..6805854802a30a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -589,13 +589,17 @@ pub enum Insn { /// Load cfp->pc LoadPC, + /// Load cfp->self + LoadSelf, /// Read an instance variable at the given index, embedded in the object LoadIvarEmbedded { self_val: InsnId, id: ID, index: u16 }, /// Read an instance variable at the given index, from the extended table LoadIvarExtended { self_val: InsnId, id: ID, index: u16 }, - /// Get a local variable from a higher scope or the heap - GetLocal { level: u32, ep_offset: u32 }, + /// Get a local variable from a higher scope or the heap. + /// If `use_sp` is true, it uses the SP register to optimize the read. + /// `rest_param` is used by infer_types to infer the ArrayExact type. + GetLocal { level: u32, ep_offset: u32, use_sp: bool, rest_param: bool }, /// Set a local variable in a higher scope or the heap SetLocal { level: u32, ep_offset: u32, val: InsnId }, GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId }, @@ -769,6 +773,8 @@ impl Insn { Insn::FixnumOr { .. } => false, Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, + Insn::LoadPC => false, + Insn::LoadSelf => false, Insn::LoadIvarEmbedded { .. } => false, Insn::LoadIvarExtended { .. } => false, Insn::CCall { elidable, .. } => !elidable, @@ -992,12 +998,14 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::DefinedIvar { self_val, id, .. } => write!(f, "DefinedIvar {self_val}, :{}", id.contents_lossy()), Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy()), Insn::LoadPC => write!(f, "LoadPC"), + Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadIvarEmbedded { self_val, id, index } => write!(f, "LoadIvarEmbedded {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), &Insn::LoadIvarExtended { self_val, id, index } => write!(f, "LoadIvarExtended {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()), - Insn::GetLocal { level, ep_offset } => write!(f, "GetLocal l{level}, EP@{ep_offset}"), + &Insn::GetLocal { level, ep_offset, use_sp: true, rest_param } => write!(f, "GetLocal l{level}, SP@{}{}", ep_offset + 1, if rest_param { ", *" } else { "" }), + &Insn::GetLocal { level, ep_offset, use_sp: false, rest_param } => write!(f, "GetLocal l{level}, EP@{ep_offset}{}", if rest_param { ", *" } else { "" }), Insn::SetLocal { val, level, ep_offset } => write!(f, "SetLocal l{level}, EP@{ep_offset}, {val}"), Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"), Insn::GetSpecialNumber { nth, .. } => write!(f, "GetSpecialNumber {nth}"), @@ -1376,6 +1384,7 @@ impl Function { | SideExit {..} | EntryPoint {..} | LoadPC + | LoadSelf | IncrCounter(_)) => result.clone(), &Snapshot { state: FrameState { iseq, insn_idx, pc, ref stack, ref locals } } => Snapshot { @@ -1593,6 +1602,7 @@ impl Function { Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, + Insn::LoadSelf => types::BasicObject, Insn::LoadIvarEmbedded { .. } => types::BasicObject, Insn::LoadIvarExtended { .. } => types::BasicObject, Insn::GetSpecialSymbol { .. } => types::BasicObject, @@ -1601,6 +1611,7 @@ impl Function { Insn::ToArray { .. } => types::ArrayExact, Insn::ObjToString { .. } => types::BasicObject, Insn::AnyToString { .. } => types::String, + Insn::GetLocal { rest_param: true, .. } => types::ArrayExact, Insn::GetLocal { .. } => types::BasicObject, // The type of Snapshot doesn't really matter; it's never materialized. It's used only // as a reference for FrameState, which we use to generate side-exit code. @@ -1608,17 +1619,11 @@ impl Function { } } - /// Set self.param_types. They are copied to the param types of entry blocks. + /// Set self.param_types. They are copied to the param types of jit_entry_blocks. fn set_param_types(&mut self) { let iseq = self.iseq; let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); - let rest_param_idx = if !iseq.is_null() && unsafe { get_iseq_flags_has_rest(iseq) } { - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - Some(opt_num + lead_num) - } else { - None - }; + let rest_param_idx = iseq_rest_param_idx(iseq); self.param_types.push(types::BasicObject); // self for local_idx in 0..param_size { @@ -1631,10 +1636,10 @@ impl Function { } } - /// Copy self.param_types to the param types of entry blocks. + /// Copy self.param_types to the param types of jit_entry_blocks. fn copy_param_types(&mut self) { - for entry_block in self.entry_blocks() { - let entry_params = self.blocks[entry_block.0].params.iter(); + for jit_entry_block in self.jit_entry_blocks.iter() { + let entry_params = self.blocks[jit_entry_block.0].params.iter(); let param_types = self.param_types.iter(); assert_eq!( entry_params.len(), @@ -2414,7 +2419,8 @@ impl Function { &Insn::Const { .. } | &Insn::Param { .. } | &Insn::EntryPoint { .. } - | &Insn::LoadPC { .. } + | &Insn::LoadPC + | &Insn::LoadSelf | &Insn::GetLocal { .. } | &Insn::PutSpecialObject { .. } | &Insn::IncrCounter(_) => @@ -3653,7 +3659,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let ep_offset = get_arg(pc, 0).as_u32(); if ep_escaped || has_blockiseq { // TODO: figure out how to drop has_blockiseq here // Read the local using EP - let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 }); + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); state.setlocal(ep_offset, val); // remember the result to spill on side-exits state.stack_push(val); } else { @@ -3687,7 +3693,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_getlocal_WC_1 => { let ep_offset = get_arg(pc, 0).as_u32(); - state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level: 1 })); + state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level: 1, use_sp: false, rest_param: false })); } YARVINSN_setlocal_WC_1 => { let ep_offset = get_arg(pc, 0).as_u32(); @@ -3696,7 +3702,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_getlocal => { let ep_offset = get_arg(pc, 0).as_u32(); let level = get_arg(pc, 1).as_u32(); - state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level })); + state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level, use_sp: false, rest_param: false })); } YARVINSN_setlocal => { let ep_offset = get_arg(pc, 0).as_u32(); @@ -3896,7 +3902,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // or not used after this. Max thinks we could eventually DCE them. for local_idx in 0..state.locals.len() { let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32; - let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 }); + // TODO: We could use `use_sp: true` with PatchPoint + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); state.setlocal(ep_offset, val); } } @@ -3925,7 +3932,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // Reload locals that may have been modified by the blockiseq. for local_idx in 0..state.locals.len() { let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32; - let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 }); + // TODO: We could use `use_sp: true` with PatchPoint + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); state.setlocal(ep_offset, val); } } @@ -3955,7 +3963,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // or not used after this. Max thinks we could eventually DCE them. for local_idx in 0..state.locals.len() { let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32; - let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 }); + // TODO: We could use `use_sp: true` with PatchPoint + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); state.setlocal(ep_offset, val); } } @@ -4199,30 +4208,51 @@ fn compile_entry_block(fun: &mut Function, jit_entry_insns: &Vec) { } } +/// Compile initial locals for an entry_block for the interpreter +fn compile_entry_state(fun: &mut Function, entry_block: BlockId) -> (InsnId, FrameState) { + let iseq = fun.iseq; + let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let rest_param_idx = iseq_rest_param_idx(iseq); + + let self_param = fun.push_insn(entry_block, Insn::LoadSelf); + let mut entry_state = FrameState::new(iseq); + for local_idx in 0..num_locals(iseq) { + if local_idx < param_size { + let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32; + let use_sp = !iseq_escapes_ep(iseq); // If the ISEQ does not escape EP, we can assume EP + 1 == SP + let rest_param = Some(local_idx as i32) == rest_param_idx; + entry_state.locals.push(fun.push_insn(entry_block, Insn::GetLocal { level: 0, ep_offset, use_sp, rest_param })); + } else { + entry_state.locals.push(fun.push_insn(entry_block, Insn::Const { val: Const::Value(Qnil) })); + } + } + (self_param, entry_state) +} + /// Compile a jit_entry_block fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_block: BlockId) { let jit_entry_block = fun.jit_entry_blocks[jit_entry_idx]; fun.push_insn(jit_entry_block, Insn::EntryPoint { jit_entry_idx: Some(jit_entry_idx) }); // Prepare entry_state with basic block params - let (self_param, entry_state) = compile_entry_state(fun, jit_entry_block); + let (self_param, entry_state) = compile_jit_entry_state(fun, jit_entry_block); // Jump to target_block fun.push_insn(jit_entry_block, Insn::Jump(BranchEdge { target: target_block, args: entry_state.as_args(self_param) })); } -/// Compile params and initial locals for an entry block -fn compile_entry_state(fun: &mut Function, entry_block: BlockId) -> (InsnId, FrameState) { +/// Compile params and initial locals for a jit_entry_block +fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId) -> (InsnId, FrameState) { let iseq = fun.iseq; let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); - let self_param = fun.push_insn(entry_block, Insn::Param { idx: SELF_PARAM_IDX }); + let self_param = fun.push_insn(jit_entry_block, Insn::Param { idx: SELF_PARAM_IDX }); let mut entry_state = FrameState::new(iseq); for local_idx in 0..num_locals(iseq) { if local_idx < param_size { - entry_state.locals.push(fun.push_insn(entry_block, Insn::Param { idx: local_idx + 1 })); // +1 for self + entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Param { idx: local_idx + 1 })); // +1 for self } else { - entry_state.locals.push(fun.push_insn(entry_block, Insn::Const { val: Const::Value(Qnil) })); + entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Const { val: Const::Value(Qnil) })); } } (self_param, entry_state) @@ -4541,18 +4571,6 @@ mod infer_tests { }); } - #[test] - fn test_unknown() { - crate::cruby::with_rubyvm(|| { - let mut function = Function::new(std::ptr::null()); - let param = function.push_insn(function.entry_block, Insn::Param { idx: SELF_PARAM_IDX }); - function.param_types.push(types::BasicObject); // self - let val = function.push_insn(function.entry_block, Insn::Test { val: param }); - function.infer_types(); - assert_bit_equal(function.type_of(val), types::CBool); - }); - } - #[test] fn newarray() { let mut function = Function::new(std::ptr::null()); @@ -4627,8 +4645,11 @@ mod snapshot_tests { eval("def test(a, b) = [a, b]"); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -4729,8 +4750,10 @@ mod tests { eval("def test(x=1) = 123"); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 v3:CPtr = LoadPC v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) v5:CBool = IsBitEqual v3, v4 @@ -4758,8 +4781,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putobject); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -4777,8 +4801,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_newarray); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -4796,8 +4821,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_newarray); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4815,8 +4842,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_newarray); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -4834,8 +4864,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_newrange); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4854,8 +4886,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_newrange); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -4873,8 +4908,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_newrange); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4893,8 +4930,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_newrange); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -4912,8 +4952,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_duparray); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -4932,8 +4973,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_duphash); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -4952,8 +4994,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_newhash); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -4971,8 +5014,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_newhash); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -4992,8 +5038,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putchilledstring); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5012,8 +5059,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putobject); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5031,8 +5079,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putobject); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5050,8 +5099,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putobject); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5069,8 +5119,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putobject); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5088,8 +5139,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_plus); assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5111,8 +5163,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_hash_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5136,8 +5189,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_hash_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5155,8 +5209,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_ary_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5180,8 +5235,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_ary_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5199,8 +5255,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_str_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5224,8 +5281,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_str_freeze); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5243,8 +5301,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_str_uminus); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5268,8 +5327,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_str_uminus); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5290,8 +5350,9 @@ mod tests { assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -5329,8 +5390,9 @@ mod tests { YARVINSN_getlocal, YARVINSN_setlocal]); assert_snapshot!(hir_string("test"), @r" fn block (3 levels) in @:10: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5360,8 +5422,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_setlocal_WC_0); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 v3:NilClass = Const Value(nil) v4:CPtr = LoadPC v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) @@ -5411,8 +5475,10 @@ mod tests { "); assert_snapshot!(hir_string_proc("test"), @r" fn block in test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5431,8 +5497,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_definedivar); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5458,8 +5525,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_definedivar); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5487,8 +5555,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_defined); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5519,8 +5588,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_leave); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5553,8 +5624,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -5589,8 +5662,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_plus); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5611,8 +5687,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_minus); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5633,8 +5712,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_mult); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5655,8 +5737,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_div); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5677,8 +5762,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_mod); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5699,8 +5787,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_eq); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5721,8 +5812,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_neq); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5743,8 +5837,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_lt); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5765,8 +5862,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_le); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5787,8 +5887,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_gt); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5816,8 +5919,9 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) @@ -5861,8 +5965,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_ge); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5888,8 +5995,9 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -5924,8 +6032,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_send_without_block); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -5952,8 +6061,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_send); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5977,8 +6088,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_intern); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6002,8 +6114,9 @@ mod tests { // The 2 string literals have the same address because they're deduped. assert_snapshot!(hir_string("test"), @r" fn test@:1: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6030,8 +6143,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6049,8 +6164,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6069,8 +6186,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6088,8 +6207,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6110,8 +6231,9 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6130,8 +6252,9 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6150,8 +6273,9 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6171,8 +6295,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6187,8 +6313,10 @@ mod tests { eval("def forwardable(...) = nil"); assert_snapshot!(hir_string("forwardable"), @r" fn forwardable@:1: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6209,8 +6337,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6237,8 +6367,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:ArrayExact): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * Jump bb2(v1, v2) bb1(v5:BasicObject, v6:ArrayExact): EntryPoint JIT(0) @@ -6258,8 +6390,10 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6278,8 +6412,13 @@ mod tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:ArrayExact, v4:BasicObject, v5:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:ArrayExact = GetLocal l0, SP@7, * + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 v6:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5, v6) bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): @@ -6304,8 +6443,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_new); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6337,8 +6477,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6359,8 +6500,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6386,8 +6530,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -6415,8 +6562,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -6444,8 +6594,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -6477,8 +6630,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_newarray_send); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -6501,8 +6657,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_length); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6523,8 +6682,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_size); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6546,8 +6708,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_getinstancevariable); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6569,8 +6732,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_setinstancevariable); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6593,8 +6757,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_setglobal); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6616,8 +6781,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_getglobal); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6637,8 +6803,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_splatarray); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6658,8 +6826,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_concattoarray); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6682,8 +6852,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_pushtoarray); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6705,8 +6877,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_pushtoarray); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6732,8 +6906,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_aset); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6755,8 +6932,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_aref); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6776,8 +6956,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_empty_p); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6797,8 +6979,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_succ); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6818,8 +7002,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_and); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6839,8 +7026,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_or); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6860,8 +7050,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_not); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6881,8 +7073,11 @@ mod tests { assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6906,8 +7101,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_putspecialobject); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -6940,8 +7136,9 @@ mod tests { assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" fn reverse_odd@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) v3:NilClass = Const Value(nil) v4:NilClass = Const Value(nil) @@ -6965,8 +7162,9 @@ mod tests { Return v33 fn reverse_even@:8: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) v3:NilClass = Const Value(nil) v4:NilClass = Const Value(nil) @@ -7003,8 +7201,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_branchnil); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7026,8 +7226,12 @@ mod tests { assert_contains_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave); assert_snapshot!(hir_string("Float"), @r" fn Float@: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): EntryPoint JIT(0) @@ -7046,8 +7250,9 @@ mod tests { assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); assert_snapshot!(hir_string("class"), @r" fn class@: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7069,8 +7274,13 @@ mod tests { let function = iseq_to_hir(iseq).unwrap(); assert_snapshot!(hir_string_function(&function), @r" fn open@: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject, v5:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:BasicObject = GetLocal l0, SP@7 + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 v6:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5, v6) bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): @@ -7104,8 +7314,9 @@ mod tests { let function = iseq_to_hir(iseq).unwrap(); assert_snapshot!(hir_string_function(&function), @r" fn enable@: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7126,8 +7337,13 @@ mod tests { let function = iseq_to_hir(iseq).unwrap(); assert_snapshot!(hir_string_function(&function), @r" fn start@: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject, v5:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:BasicObject = GetLocal l0, SP@5 + v5:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3, v4, v5) bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): EntryPoint JIT(0) @@ -7148,8 +7364,10 @@ mod tests { assert_contains_opcode("test", YARVINSN_dupn); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7180,8 +7398,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_objtostring); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7205,8 +7424,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_concatstrings); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7235,8 +7455,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_concatstrings); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7260,8 +7481,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_toregexp); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7290,8 +7512,9 @@ mod tests { assert_contains_opcode("test", YARVINSN_toregexp); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7319,8 +7542,9 @@ mod tests { assert_contains_opcode("throw_break", YARVINSN_throw); assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" fn block in @:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7330,8 +7554,9 @@ mod tests { Throw TAG_RETURN, v12 fn block in @:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7351,8 +7576,9 @@ mod tests { "#); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7373,8 +7599,11 @@ mod tests { "#); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7414,8 +7643,11 @@ mod graphviz_tests { node [shape=plaintext]; mode=hier; overlap=false; splines=true; bb0 [label=< - + + + +
bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject) 
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@5 
v3:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2, v3) 
>]; bb0:v4 -> bb2:params:n; @@ -7460,8 +7692,10 @@ mod graphviz_tests { node [shape=plaintext]; mode=hier; overlap=false; splines=true; bb0 [label=< - + + +
bb0(v1:BasicObject, v2:BasicObject) 
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2) 
>]; bb0:v3 -> bb2:params:n; @@ -7527,8 +7761,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -7558,8 +7793,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -7584,8 +7820,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7612,8 +7849,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7640,8 +7878,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7665,8 +7904,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7691,8 +7931,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7726,8 +7968,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7757,8 +8000,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7793,8 +8037,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7824,8 +8069,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7860,8 +8106,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7891,8 +8138,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7922,8 +8170,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7954,8 +8203,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -7983,8 +8233,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8010,8 +8262,10 @@ mod opt_tests { "); assert_snapshot!(hir_strings!("rest", "kw", "kw_rest", "block", "post"), @r" fn rest@:2: - bb0(v1:BasicObject, v2:ArrayExact): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * Jump bb2(v1, v2) bb1(v5:BasicObject, v6:ArrayExact): EntryPoint JIT(0) @@ -8021,8 +8275,11 @@ mod opt_tests { Return v9 fn kw@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8032,8 +8289,10 @@ mod opt_tests { Return v11 fn kw_rest@:4: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8043,8 +8302,10 @@ mod opt_tests { Return v9 fn block@:6: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8055,8 +8316,11 @@ mod opt_tests { Return v13 fn post@:5: - bb0(v1:BasicObject, v2:ArrayExact, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@5, * + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): EntryPoint JIT(0) @@ -8079,8 +8343,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8107,8 +8372,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8133,8 +8399,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8158,8 +8425,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8186,8 +8454,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8218,8 +8487,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:7: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8246,8 +8516,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -8276,8 +8547,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:7: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8297,8 +8571,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8321,8 +8598,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8345,8 +8624,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8369,8 +8650,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8393,8 +8677,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8417,8 +8703,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8444,8 +8732,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8473,8 +8762,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8500,8 +8790,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8525,8 +8817,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8550,8 +8844,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8575,8 +8871,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8601,8 +8899,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8628,8 +8927,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8655,8 +8955,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8687,8 +8988,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -8713,8 +9016,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8740,8 +9044,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 v4:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): @@ -8770,8 +9077,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8797,8 +9105,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8825,8 +9134,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8851,8 +9161,9 @@ mod opt_tests { "#); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -8879,8 +9190,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8906,8 +9220,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8933,8 +9250,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8960,8 +9280,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8988,8 +9311,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9016,8 +9342,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9043,8 +9372,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9070,8 +9402,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9097,8 +9432,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9124,8 +9462,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9151,8 +9492,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -9178,8 +9522,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9201,8 +9546,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9223,8 +9570,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9248,8 +9596,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -9279,8 +9628,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:4: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -9310,8 +9660,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -9337,8 +9688,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9360,8 +9712,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9393,8 +9746,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9422,8 +9776,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:4: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9447,8 +9802,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -9475,8 +9831,9 @@ mod opt_tests { // Not specialized assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9497,8 +9854,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9522,8 +9881,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -9550,8 +9911,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9584,8 +9946,10 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:8: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9609,8 +9973,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9633,8 +9998,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9660,8 +10026,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:4: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -9689,8 +10056,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9713,8 +10081,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9735,8 +10104,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9756,8 +10126,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9779,8 +10150,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9801,8 +10173,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9822,8 +10195,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9851,8 +10225,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:8: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9875,8 +10250,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9909,8 +10285,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:7: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9939,8 +10316,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9968,8 +10346,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -9997,8 +10376,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10026,8 +10406,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10054,8 +10435,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10084,8 +10466,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10111,8 +10494,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10141,8 +10525,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -10163,8 +10550,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -10185,8 +10575,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -10207,8 +10599,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10228,8 +10621,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10250,8 +10644,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10274,8 +10669,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10292,8 +10688,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10314,8 +10711,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10336,8 +10734,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10358,8 +10757,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10379,8 +10779,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10401,8 +10802,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10423,8 +10825,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10445,8 +10848,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10466,8 +10870,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10488,8 +10893,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10511,8 +10917,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10534,8 +10941,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10555,8 +10963,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10577,8 +10986,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10600,8 +11010,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10623,8 +11034,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10651,8 +11063,10 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -10680,8 +11094,10 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -10706,8 +11122,10 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -10734,8 +11152,9 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -10761,8 +11180,9 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf v2:NilClass = Const Value(nil) Jump bb2(v1, v2) bb1(v5:BasicObject): @@ -10786,8 +11206,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10810,8 +11231,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10834,8 +11256,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10858,8 +11281,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10885,8 +11309,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10911,8 +11336,9 @@ mod opt_tests { "##); assert_snapshot!(hir_string("test"), @r" fn test@:5: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10939,8 +11365,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:4: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10961,8 +11388,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -10981,8 +11409,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11006,8 +11435,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11028,8 +11458,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11053,8 +11484,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11077,8 +11509,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11101,8 +11535,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11125,8 +11561,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11149,8 +11587,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11173,8 +11613,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11197,8 +11639,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11221,8 +11665,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11245,8 +11691,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11269,8 +11717,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11293,8 +11743,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11318,8 +11770,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -11342,8 +11797,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -11367,8 +11825,11 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:2: - bb0(v1:BasicObject, v2:BasicObject, v3:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -11394,8 +11855,9 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@:3: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11426,8 +11888,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:10: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11459,8 +11923,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:10: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11503,8 +11969,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:20: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11530,8 +11998,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:7: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11562,8 +12031,9 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:7: - bb0(v1:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) @@ -11593,8 +12063,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -11622,8 +12094,10 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v1:BasicObject, v2:BasicObject): + bb0(): EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) From 7a1873857eff3125c8124416f2fb3e0b13f3f238 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 26 Sep 2025 15:48:41 -0400 Subject: [PATCH 0033/2435] ZJIT: Remove RefCell from IseqCall No point taking the panic risks with RefCell when most fields in it are already in a Cell. Put `iseq` in a Cell and we no longer need the wrapping. Saves memory, too. --- zjit/src/codegen.rs | 48 ++++++++++++++++++++++----------------------- zjit/src/gc.rs | 10 ++++------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2845aaf719963c..0ea675016134ee 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -37,7 +37,7 @@ struct JITState { jit_entries: Vec>>, /// ISEQ calls that need to be compiled later - iseq_calls: Vec>>, + iseq_calls: Vec, /// The number of bytes allocated for basic block arguments spilled onto the C stack c_stack_slots: usize, @@ -132,17 +132,17 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool) } /// Stub a branch for a JIT-to-JIT call -fn gen_iseq_call(cb: &mut CodeBlock, caller_iseq: IseqPtr, iseq_call: &Rc>) -> Result<(), CompileError> { +fn gen_iseq_call(cb: &mut CodeBlock, caller_iseq: IseqPtr, iseq_call: &IseqCallRef) -> Result<(), CompileError> { // Compile a function stub let stub_ptr = gen_function_stub(cb, iseq_call.clone()).inspect_err(|err| { debug!("{err:?}: gen_function_stub failed: {} -> {}", - iseq_get_location(caller_iseq, 0), iseq_get_location(iseq_call.borrow().iseq, 0)); + iseq_get_location(caller_iseq, 0), iseq_get_location(iseq_call.iseq.get(), 0)); })?; // Update the JIT-to-JIT call to call the stub let stub_addr = stub_ptr.raw_ptr(cb); - let iseq = iseq_call.borrow().iseq; - iseq_call.borrow_mut().regenerate(cb, |asm| { + let iseq = iseq_call.iseq.get(); + iseq_call.regenerate(cb, |asm| { asm_comment!(asm, "call function stub: {}", iseq_get_location(iseq, 0)); asm.ccall(stub_addr, vec![]); }); @@ -235,7 +235,7 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, } // Prepare for GC - payload.iseq_calls.extend(iseq_calls.clone()); + payload.iseq_calls.extend(iseq_calls); append_gc_offsets(iseq, &gc_offsets); Ok(iseq_code_ptrs) } @@ -1797,8 +1797,8 @@ c_callable! { with_vm_lock(src_loc!(), || { // gen_push_frame() doesn't set PC, so we need to set them before exit. // function_stub_hit_body() may allocate and call gc_validate_pc(), so we always set PC. - let iseq_call = unsafe { Rc::from_raw(iseq_call_ptr as *const RefCell) }; - let iseq = iseq_call.borrow().iseq; + let iseq_call = unsafe { Rc::from_raw(iseq_call_ptr as *const IseqCall) }; + let iseq = iseq_call.iseq.get(); let pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; // TODO: handle opt_pc once supported unsafe { rb_set_cfp_pc(cfp, pc) }; @@ -1834,7 +1834,7 @@ c_callable! { }; if let Some(compile_error) = compile_error { // We'll use this Rc again, so increment the ref count decremented by from_raw. - unsafe { Rc::increment_strong_count(iseq_call_ptr as *const RefCell); } + unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); } prepare_for_exit(iseq, cfp, sp, compile_error); return ZJITState::get_exit_trampoline_with_counter().raw_ptr(cb); @@ -1853,10 +1853,10 @@ c_callable! { } /// Compile an ISEQ for a function stub -fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc>) -> Result { +fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result { // Compile the stubbed ISEQ - let IseqCodePtrs { jit_entry_ptrs, .. } = gen_iseq(cb, iseq_call.borrow().iseq, None).inspect_err(|err| { - debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq_call.borrow().iseq, 0)); + let IseqCodePtrs { jit_entry_ptrs, .. } = gen_iseq(cb, iseq_call.iseq.get(), None).inspect_err(|err| { + debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq_call.iseq.get(), 0)); })?; // We currently don't support JIT-to-JIT calls for ISEQs with optional arguments. @@ -1866,8 +1866,8 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc>) // Update the stub to call the code pointer let code_addr = jit_entry_ptr.raw_ptr(cb); - let iseq = iseq_call.borrow().iseq; - iseq_call.borrow_mut().regenerate(cb, |asm| { + let iseq = iseq_call.iseq.get(); + iseq_call.regenerate(cb, |asm| { asm_comment!(asm, "call compiled function: {}", iseq_get_location(iseq, 0)); asm.ccall(code_addr, vec![]); }); @@ -1876,9 +1876,9 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc>) } /// Compile a stub for an ISEQ called by SendWithoutBlockDirect -fn gen_function_stub(cb: &mut CodeBlock, iseq_call: Rc>) -> Result { +fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result { let mut asm = Assembler::new(); - asm_comment!(asm, "Stub: {}", iseq_get_location(iseq_call.borrow().iseq, 0)); + asm_comment!(asm, "Stub: {}", iseq_get_location(iseq_call.iseq.get(), 0)); // Call function_stub_hit using the shared trampoline. See `gen_function_stub_hit_trampoline`. // Use load_into instead of mov, which is split on arm64, to avoid clobbering ALLOC_REGS. @@ -2043,7 +2043,7 @@ fn aligned_stack_bytes(num_slots: usize) -> usize { impl Assembler { /// Make a C call while marking the start and end positions for IseqCall - fn ccall_with_iseq_call(&mut self, fptr: *const u8, opnds: Vec, iseq_call: &Rc>) -> Opnd { + fn ccall_with_iseq_call(&mut self, fptr: *const u8, opnds: Vec, iseq_call: &IseqCallRef) -> Opnd { // We need to create our own branch rc objects so that we can move the closure below let start_iseq_call = iseq_call.clone(); let end_iseq_call = iseq_call.clone(); @@ -2052,10 +2052,10 @@ impl Assembler { fptr, opnds, move |code_ptr, _| { - start_iseq_call.borrow_mut().start_addr.set(Some(code_ptr)); + start_iseq_call.start_addr.set(Some(code_ptr)); }, move |code_ptr, _| { - end_iseq_call.borrow_mut().end_addr.set(Some(code_ptr)); + end_iseq_call.end_addr.set(Some(code_ptr)); }, ) } @@ -2084,7 +2084,7 @@ impl JITEntry { #[derive(Debug)] pub struct IseqCall { /// Callee ISEQ that start_addr jumps to - pub iseq: IseqPtr, + pub iseq: Cell, /// Position where the call instruction starts start_addr: Cell>, @@ -2093,17 +2093,17 @@ pub struct IseqCall { end_addr: Cell>, } -type IseqCallRef = Rc>; +pub type IseqCallRef = Rc; impl IseqCall { /// Allocate a new IseqCall - fn new(iseq: IseqPtr) -> Rc> { + fn new(iseq: IseqPtr) -> IseqCallRef { let iseq_call = IseqCall { - iseq, + iseq: Cell::new(iseq), start_addr: Cell::new(None), end_addr: Cell::new(None), }; - Rc::new(RefCell::new(iseq_call)) + Rc::new(iseq_call) } /// Regenerate a IseqCall with a given callback diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index eed202ac77a378..cc08b8fc9ebb07 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -1,9 +1,7 @@ //! This module is responsible for marking/moving objects on GC. -use std::cell::RefCell; -use std::rc::Rc; use std::{ffi::c_void, ops::Range}; -use crate::codegen::IseqCall; +use crate::codegen::IseqCallRef; use crate::stats::CompileError; use crate::{cruby::*, profile::IseqProfile, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; use crate::stats::Counter::gc_time_ns; @@ -21,7 +19,7 @@ pub struct IseqPayload { pub gc_offsets: Vec, /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. - pub iseq_calls: Vec>>, + pub iseq_calls: Vec, } impl IseqPayload { @@ -191,10 +189,10 @@ fn iseq_update_references(payload: &mut IseqPayload) { // Move ISEQ references in IseqCall for iseq_call in payload.iseq_calls.iter_mut() { - let old_iseq = iseq_call.borrow().iseq; + let old_iseq = iseq_call.iseq.get(); let new_iseq = unsafe { rb_gc_location(VALUE(old_iseq as usize)) }.0 as IseqPtr; if old_iseq != new_iseq { - iseq_call.borrow_mut().iseq = new_iseq; + iseq_call.iseq.set(new_iseq); } } From 1083c2c063e388003eeded1c2364f749bcd91b1a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 29 Sep 2025 18:48:44 +0100 Subject: [PATCH 0034/2435] ZJIT: Add stats for cfuncs that are not optimized (#14638) * ZJIT: Add stats for cfuncs that are not optimized * ZJIT: Add IncrCounterPtr HIR instead From `lobsters` ``` Top-20 Unoptimized C functions (73.0% of total 15,276,688): Kernel#is_a?: 2,052,363 (13.4%) Class#current: 1,892,623 (12.4%) String#to_s: 975,973 ( 6.4%) Hash#key?: 677,623 ( 4.4%) String#empty?: 636,468 ( 4.2%) TrueClass#===: 457,232 ( 3.0%) Hash#[]=: 455,908 ( 3.0%) FalseClass#===: 448,798 ( 2.9%) ActiveSupport::OrderedOptions#_get: 377,468 ( 2.5%) Kernel#kind_of?: 339,551 ( 2.2%) Kernel#dup: 329,371 ( 2.2%) String#==: 324,286 ( 2.1%) String#include?: 297,528 ( 1.9%) Hash#[]: 294,561 ( 1.9%) Array#include?: 287,145 ( 1.9%) Kernel#block_given?: 283,633 ( 1.9%) BasicObject#!=: 278,874 ( 1.8%) Hash#delete: 250,951 ( 1.6%) Set#include?: 246,447 ( 1.6%) NilClass#===: 242,776 ( 1.6%) ``` From `liquid-render` ``` Top-20 Unoptimized C functions (99.8% of total 5,195,549): Hash#key?: 2,459,048 (47.3%) String#to_s: 1,119,758 (21.6%) Set#include?: 799,469 (15.4%) Kernel#is_a?: 214,223 ( 4.1%) Integer#<<: 171,073 ( 3.3%) Integer#/: 127,622 ( 2.5%) CGI::EscapeExt#escapeHTML: 56,971 ( 1.1%) Regexp#===: 50,008 ( 1.0%) String#empty?: 43,990 ( 0.8%) String#===: 36,838 ( 0.7%) String#==: 21,309 ( 0.4%) Time#strftime: 21,251 ( 0.4%) String#strip: 15,271 ( 0.3%) String#scan: 13,753 ( 0.3%) String#+@: 12,603 ( 0.2%) Array#include?: 8,059 ( 0.2%) String#+: 5,295 ( 0.1%) String#dup: 4,606 ( 0.1%) String#-@: 3,213 ( 0.1%) Class#generate: 3,011 ( 0.1%) ``` --- zjit.rb | 1 + zjit/src/codegen.rs | 16 +++++++++---- zjit/src/hir.rs | 56 +++++++++++++++++++++++++++++++-------------- zjit/src/state.rs | 10 ++++++++ zjit/src/stats.rs | 7 ++++++ 5 files changed, 68 insertions(+), 22 deletions(-) diff --git a/zjit.rb b/zjit.rb index eb47a704708cae..b85b5629a72654 100644 --- a/zjit.rb +++ b/zjit.rb @@ -43,6 +43,7 @@ def stats_string print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'send fallback unspecialized def_types', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'dynamic send types', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'not_optimized_cfuncs_', prompt: 'unoptimized sends to C functions', buf:, stats:, limit: 20) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0ea675016134ee..e8f1d968298860 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -419,6 +419,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetSpecialSymbol { symbol_type, state: _ } => gen_getspecial_symbol(asm, *symbol_type), Insn::GetSpecialNumber { nth, state } => gen_getspecial_number(asm, *nth, &function.frame_state(*state)), &Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)), + Insn::IncrCounterPtr { counter_ptr } => no_output!(gen_incr_counter_ptr(asm, *counter_ptr)), Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)), &Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))), &Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) }, @@ -1528,15 +1529,20 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val } +/// Generate code that records unoptimized C functions if --zjit-stats is enabled +fn gen_incr_counter_ptr(asm: &mut Assembler, counter_ptr: *mut u64) { + if get_option!(stats) { + let ptr_reg = asm.load(Opnd::const_ptr(counter_ptr as *const u8)); + let counter_opnd = Opnd::mem(64, ptr_reg, 0); + asm.incr_counter(counter_opnd, Opnd::UImm(1)); + } +} + /// Generate code that increments a counter if --zjit-stats fn gen_incr_counter(asm: &mut Assembler, counter: Counter) { if get_option!(stats) { let ptr = counter_ptr(counter); - let ptr_reg = asm.load(Opnd::const_ptr(ptr as *const u8)); - let counter_opnd = Opnd::mem(64, ptr_reg, 0); - - // Increment and store the updated value - asm.incr_counter(counter_opnd, Opnd::UImm(1)); + gen_incr_counter_ptr(asm, ptr); } } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6805854802a30a..d81231e2820b88 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -711,6 +711,9 @@ pub enum Insn { /// Increment a counter in ZJIT stats IncrCounter(Counter), + /// Increment a counter in ZJIT stats for the given counter pointer + IncrCounterPtr { counter_ptr: *mut u64 }, + /// Equivalent of RUBY_VM_CHECK_INTS. Automatically inserted by the compiler before jumps and /// return instructions. CheckInterrupts { state: InsnId }, @@ -724,7 +727,7 @@ impl Insn { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } - | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) + | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false, _ => true, } @@ -978,6 +981,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) }, + Insn::IncrCounterPtr { .. } => write!(f, "IncrCounterPtr"), Insn::Snapshot { state } => write!(f, "Snapshot {}", state.print(self.ptr_map)), Insn::Defined { op_type, v, .. } => { // op_type (enum defined_type) printing logic from iseq.c. @@ -1385,6 +1389,7 @@ impl Function { | EntryPoint {..} | LoadPC | LoadSelf + | IncrCounterPtr {..} | IncrCounter(_)) => result.clone(), &Snapshot { state: FrameState { iseq, insn_idx, pc, ref stack, ref locals } } => Snapshot { @@ -1534,7 +1539,7 @@ impl Function { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } => panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -2154,9 +2159,9 @@ impl Function { self_type: Type, send: Insn, send_insn_id: InsnId, - ) -> Result<(), ()> { + ) -> Result<(), Option<*const rb_callable_method_entry_struct>> { let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else { - return Err(()); + return Err(None); }; let call_info = unsafe { (*cd).ci }; @@ -2168,20 +2173,20 @@ impl Function { (class, None) } else { let iseq_insn_idx = fun.frame_state(state).insn_idx; - let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) }; + let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(None) }; (recv_type.class(), Some(recv_type)) }; // Do method lookup - let method = unsafe { rb_callable_method_entry(recv_class, method_id) }; + let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; if method.is_null() { - return Err(()); + return Err(None); } // Filter for C methods let def_type = unsafe { get_cme_def_type(method) }; if def_type != VM_METHOD_TYPE_CFUNC { - return Err(()); + return Err(None); } // Find the `argc` (arity) of the C method, which describes the parameters it expects @@ -2193,7 +2198,7 @@ impl Function { // // Bail on argc mismatch if argc != cfunc_argc as u32 { - return Err(()); + return Err(Some(method)); } // Filter for a leaf and GC free function @@ -2201,7 +2206,7 @@ impl Function { let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = ZJITState::get_method_annotations().get_cfunc_properties(method) else { - return Err(()); + return Err(Some(method)); }; let ci_flags = unsafe { vm_ci_flag(call_info) }; @@ -2218,13 +2223,13 @@ impl Function { cfunc_args.append(&mut args); let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); - return Ok(()); + return Ok(()) } } // Variadic method -1 => { if unsafe { rb_zjit_method_tracing_currently_enabled() } { - return Err(()); + return Err(None); } // The method gets a pointer to the first argument // func(int argc, VALUE *argv, VALUE recv) @@ -2256,8 +2261,9 @@ impl Function { }); fun.make_equal_to(send_insn_id, ccall); - return Ok(()); + return Ok(()) } + // Fall through for complex cases (splat, kwargs, etc.) } -2 => { @@ -2267,7 +2273,7 @@ impl Function { _ => unreachable!("unknown cfunc kind: argc={argc}") } - Err(()) + Err(Some(method)) } for block in self.rpo() { @@ -2276,8 +2282,23 @@ impl Function { for insn_id in old_insns { if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) { let recv_type = self.type_of(recv); - if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() { - continue; + match reduce_to_ccall(self, block, recv_type, send, insn_id) { + Ok(()) => continue, + Err(Some(cme)) => { + if get_option!(stats) { + let owner = unsafe { (*cme).owner }; + let called_id = unsafe { (*cme).called_id }; + let class_name = get_class_name(owner); + let method_name = called_id.contents_lossy(); + let qualified_method_name = format!("{}#{}", class_name, method_name); + let unoptimized_cfunc_counter_pointers = ZJITState::get_unoptimized_cfunc_counter_pointers(); + let counter_ptr = unoptimized_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); + let counter_ptr = &mut **counter_ptr as *mut u64; + + self.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); + } + } + _ => {} } } self.push_insn_id(block, insn_id); @@ -2423,7 +2444,8 @@ impl Function { | &Insn::LoadSelf | &Insn::GetLocal { .. } | &Insn::PutSpecialObject { .. } - | &Insn::IncrCounter(_) => + | &Insn::IncrCounter(_) + | &Insn::IncrCounterPtr { .. } => {} &Insn::PatchPoint { state, .. } | &Insn::CheckInterrupts { state } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index c0f81e1e856dec..fa5d3bc83f506c 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -8,6 +8,7 @@ use crate::asm::CodeBlock; use crate::options::get_option; use crate::stats::{Counters, ExitCounters}; use crate::virtualmem::CodePtr; +use std::collections::HashMap; #[allow(non_upper_case_globals)] #[unsafe(no_mangle)] @@ -46,6 +47,9 @@ pub struct ZJITState { /// Trampoline to call function_stub_hit function_stub_hit_trampoline: CodePtr, + + /// Counter pointers for unoptimized C functions + unoptimized_cfunc_counter_pointers: HashMap>, } /// Private singleton instance of the codegen globals @@ -80,6 +84,7 @@ impl ZJITState { exit_trampoline, function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, + unoptimized_cfunc_counter_pointers: HashMap::new(), }; unsafe { ZJIT_STATE = Some(zjit_state); } @@ -138,6 +143,11 @@ impl ZJITState { &mut ZJITState::get_instance().exit_counters } + /// Get a mutable reference to unoptimized cfunc counter pointers + pub fn get_unoptimized_cfunc_counter_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().unoptimized_cfunc_counter_pointers + } + /// Was --zjit-save-compiled-iseqs specified? pub fn should_log_compiled_iseqs() -> bool { get_option!(log_compiled_iseqs).is_some() diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 6d4525084ef849..42dc44538ada0a 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -384,6 +384,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_f64!(hash, "ratio_in_zjit", 100.0 * zjit_insn_count as f64 / total_insn_count as f64); } + // Set unoptimized cfunc counters + let unoptimized_cfuncs = ZJITState::get_unoptimized_cfunc_counter_pointers(); + for (signature, counter) in unoptimized_cfuncs.iter() { + let key_string = format!("not_optimized_cfuncs_{}", signature); + set_stat_usize!(hash, &key_string, **counter); + } + hash } From 0e60426fe79b311738ce5107c829c0b3563485a5 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Sep 2025 23:08:09 +0200 Subject: [PATCH 0035/2435] ZJIT: Count dynamic instance variable lookups (#14615) --- zjit.rb | 2 ++ zjit/src/codegen.rs | 2 ++ zjit/src/stats.rs | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/zjit.rb b/zjit.rb index b85b5629a72654..a46802553c274a 100644 --- a/zjit.rb +++ b/zjit.rb @@ -53,6 +53,8 @@ def stats_string # Show the most important stats ratio_in_zjit at the end print_counters([ :dynamic_send_count, + :dynamic_getivar_count, + :dynamic_setivar_count, :compiled_iseq_count, :failed_iseq_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e8f1d968298860..7676d7eed46e70 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -715,11 +715,13 @@ fn gen_ccall_variadic( /// Emit an uncached instance variable lookup fn gen_getivar(asm: &mut Assembler, recv: Opnd, id: ID) -> Opnd { + gen_incr_counter(asm, Counter::dynamic_getivar_count); asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) } /// Emit an uncached instance variable store fn gen_setivar(asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd) { + gen_incr_counter(asm, Counter::dynamic_setivar_count); asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 42dc44538ada0a..7329b3442af0df 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -142,6 +142,10 @@ make_counters! { dynamic_send_type_invokeblock, dynamic_send_type_invokesuper, + // The number of times we do a dynamic ivar lookup from JIT code + dynamic_getivar_count, + dynamic_setivar_count, + // Method call def_type related to fallback to dynamic dispatch unspecialized_def_type_iseq, unspecialized_def_type_cfunc, From 40bb47665d3ff57e0f2eb5a9fd9e0109617015c9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Sep 2025 23:08:44 +0200 Subject: [PATCH 0036/2435] ZJIT: Inline attr_accessor/attr_writer to SetIvar (#14629) --- test/ruby/test_zjit.rb | 43 +++++++++++++++++++++++++++- zjit/src/hir.rs | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 683d53f339f8c2..937cf44e190a56 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1642,7 +1642,7 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end - def test_attr_accessor + def test_attr_accessor_getivar assert_compiles '[4, 4]', %q{ class C attr_accessor :foo @@ -1658,6 +1658,47 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end + def test_attr_accessor_setivar + assert_compiles '[5, 5]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) + c.foo = 5 + c.foo + end + + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_writer + assert_compiles '[5, 5]', %q{ + class C + attr_writer :foo + + def initialize + @foo = 4 + end + + def get_foo = @foo + end + + def test(c) + c.foo = 5 + c.get_foo + end + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + def test_uncached_getconstant_path assert_compiles RUBY_COPYRIGHT.dump, %q{ def test = RUBY_COPYRIGHT diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d81231e2820b88..e797379d8116c3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1986,6 +1986,24 @@ impl Function { } let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state }); self.make_equal_to(insn_id, getivar); + } else if def_type == VM_METHOD_TYPE_ATTRSET && args.len() == 1 { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + let id = unsafe { get_cme_def_body_attr_id(cme) }; + + // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. + // We omit gen_prepare_non_leaf_call on gen_setivar, so it's unsafe to raise for multi-ractor mode. + if unsafe { rb_zjit_singleton_class_p(klass) } { + let attached = unsafe { rb_class_attached_object(klass) }; + if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); + } + } + let val = args[0]; + let setivar = self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); + self.make_equal_to(insn_id, setivar); } else { if let Insn::SendWithoutBlock { def_type: insn_def_type, .. } = &mut self.insns[insn_id.0] { *insn_def_type = Some(MethodType::from(def_type)); @@ -12133,4 +12151,50 @@ mod opt_tests { Return v25 "); } + + #[test] + fn test_inline_attr_accessor_set() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(v0:BasicObject, v1:BasicObject): + v6:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v15:HeapObject[class_exact:C] = GuardType v1, HeapObject[class_exact:C] + SetIvar v15, :@foo, v6 + CheckInterrupts + Return v6 + "); + } + + #[test] + fn test_inline_attr_writer_set() { + eval(" + class C + attr_writer :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(v0:BasicObject, v1:BasicObject): + v6:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v15:HeapObject[class_exact:C] = GuardType v1, HeapObject[class_exact:C] + SetIvar v15, :@foo, v6 + CheckInterrupts + Return v6 + "); + } } From 37248c51233d827ca56471661175c56e31c3b14f Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 29 Sep 2025 23:09:58 +0200 Subject: [PATCH 0037/2435] ZJIT: Fix rebase issue with tests --- zjit/src/hir.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e797379d8116c3..824b35f7673dc3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12165,13 +12165,21 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v0:BasicObject, v1:BasicObject): - v6:Fixnum[5] = Const Value(5) + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v15:HeapObject[class_exact:C] = GuardType v1, HeapObject[class_exact:C] - SetIvar v15, :@foo, v6 + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 CheckInterrupts - Return v6 + Return v14 "); } @@ -12188,13 +12196,21 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" fn test@:6: - bb0(v0:BasicObject, v1:BasicObject): - v6:Fixnum[5] = Const Value(5) + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v15:HeapObject[class_exact:C] = GuardType v1, HeapObject[class_exact:C] - SetIvar v15, :@foo, v6 + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 CheckInterrupts - Return v6 + Return v14 "); } } From 46830474bdbdd32a62183e4c21e7eaeffd834168 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 16 Sep 2022 22:10:53 +0900 Subject: [PATCH 0038/2435] ifchange: Allow options with an argument without quoting A batch file splits the command line with equal signs (`=`) not only spaces. --- win32/ifchange.bat | 102 ++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/win32/ifchange.bat b/win32/ifchange.bat index 1de98f99900d70..cc0ef85cf3af32 100755 --- a/win32/ifchange.bat +++ b/win32/ifchange.bat @@ -1,54 +1,78 @@ @echo off :: usage: ifchange target temporary +:: @set PROMPT=$T:$S for %%I in (%0) do set progname=%%~nI set timestamp= set keepsuffix= set empty= set color=auto :optloop +set optarg= +:optnext for %%I in (%1) do set opt=%%~I -if "%opt%" == "--" ( - shift -) else if "%opt%" == "--timestamp" ( - set timestamp=. - shift - goto :optloop -) else if "%opt:~0,12%" == "--timestamp=" ( - set timestamp=%opt:~12% - shift - goto :optloop -) else if "%opt%" == "--keep" ( - set keepsuffix=.old - shift - goto :optloop -) else if "%opt:~0,7%" == "--keep=" ( - set keepsuffix=%opt:~7% - shift - goto :optloop -) else if "%opt%" == "--empty" ( - set empty=yes - shift - goto :optloop -) else if "%opt%" == "--color" ( - set color=always - shift - goto :optloop -) else if "%opt:~0,8%" == "--color=" ( - set color=%opt:~8% - shift - goto :optloop -) else if "%opt%" == "--debug" ( - shift - echo on - goto :optloop -) else if "%opt%" == "--help" ( - call :help - exit /b -) else if "%opt:~0,2%" == "--" ( + if not "%opt:~0,2%" == "--" ( + if not "%optarg%" == "" ( + call set %optarg%=%%opt%% + shift + goto :optloop + ) + goto :optend + ) + if "%opt%" == "--" ( + shift + goto :optend + ) + if "%opt%" == "--timestamp" ( + set timestamp=. + set optarg=timestamp + shift + goto :optnext + ) + if "%opt:~0,12%" == "--timestamp=" ( + set timestamp=%opt:~12% + shift + goto :optloop + ) + if "%opt%" == "--keep" ( + set keepsuffix=.old + set optarg=keep + shift + goto :optnext + ) + if "%opt:~0,7%" == "--keep=" ( + set keepsuffix=%opt:~7% + shift + goto :optloop + ) + if "%opt%" == "--empty" ( + set empty=yes + shift + goto :optloop + ) + if "%opt%" == "--color" ( + set color=always + set optarg=color + shift + goto :optnext + ) + if "%opt:~0,8%" == "--color=" ( + set color=%opt:~8% + shift + goto :optloop + ) + if "%opt%" == "--debug" ( + shift + echo on + goto :optloop + ) + if "%opt%" == "--help" ( + call :help + exit /b + ) echo %progname%: unknown option: %1 1>&2 exit /b 1 -) +:optend if "%2" == "" ( call :help 1>&2 From ed69b9ed12f374d2dd7b9185d83075c56d916173 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Sep 2025 15:06:36 +0900 Subject: [PATCH 0039/2435] ifchange: Allow input from stdin --- win32/ifchange.bat | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/win32/ifchange.bat b/win32/ifchange.bat index cc0ef85cf3af32..c7a57fad3583c4 100755 --- a/win32/ifchange.bat +++ b/win32/ifchange.bat @@ -84,6 +84,19 @@ set src=%2 set dest=%dest:/=\% set src=%src:/=\% +if not "%src%" == "-" goto :srcfile + if not "%TMPDIR%" == "" ( + set src=%TMPDIR%\ifchange%RANDOM%.tmp + ) else if not "%TEMP%" == "" ( + set src=%TEMP%\ifchange%RANDOM%.tmp + ) else if not "%TMP%" == "" ( + set src=%TMP%\ifchange%RANDOM%.tmp + ) else ( + set src=.\ifchange%RANDOM%.tmp + ) + findstr -r -c:"^" > "%src%" +:srcfile + if exist %dest% ( if not exist %src% goto :nt_unchanged1 if not "%empty%" == "" for %%I in (%src%) do if %%~zI == 0 goto :nt_unchanged From a35973366ac0a02c388440f3d2b76b3146a9fb8f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Sep 2025 15:19:32 +0900 Subject: [PATCH 0040/2435] lastrev.bat: Extract from windows.yml workflow --- .github/workflows/windows.yml | 25 ++----------------------- win32/lastrev.bat | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 23 deletions(-) create mode 100755 win32/lastrev.bat diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index af3d48de72d2d2..38f628243ccff4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -154,30 +154,9 @@ jobs: # windows-11-arm runner cannot run `ruby tool/file2lastrev.rb --revision.h --output=revision.h` - name: make revision.h run: | - if not exist revision.h ( - for /f "tokens=1-3" %%I in ('git log -1 "--date=format-local:%%F %%T" "--format=%%H %%cd" @') do ( - set rev=%%I - set dt=%%J - set tm=%%K - ) - call set yy=%%dt:~0,4%% - call set /a mm=100%%dt:~5,2%% %%%% 100 - call set /a dd=100%%dt:~8,2%% %%%% 100 - call set branch=%%GITHUB_REF:refs/heads/=%% - ( - call echo #define RUBY_REVISION "%%rev:~,10%%" - call echo #define RUBY_FULL_REVISION "%%rev%%" - call echo #define RUBY_BRANCH_NAME "%%branch%%" - call echo #define RUBY_RELEASE_DATETIME "%%dt%%T%%tm%%" - call echo #define RUBY_RELEASE_YEAR %%yy%% - call echo #define RUBY_RELEASE_MONTH %%mm%% - call echo #define RUBY_RELEASE_DAY %%dd%% - ) > revision.h - copy /y NUL .revision.time - ) + win32\lastrev.bat | win32\ifchange.bat --timestamp=.revision.time revision.h - type revision.h - env: - TZ: UTC + working-directory: src - run: nmake diff --git a/win32/lastrev.bat b/win32/lastrev.bat new file mode 100755 index 00000000000000..f1c799f8976f2c --- /dev/null +++ b/win32/lastrev.bat @@ -0,0 +1,29 @@ +@setlocal +@echo off +if "%1" == "" (set gitdir=.) else (set gitdir=%1) +set TZ=UTC +for /f "usebackq tokens=1-3" %%I in ( + `git -C "%gitdir%" log -1 --no-show-signature "--date=format-local:%%F %%T" "--format=%%H %%cd" HEAD` +) do ( + set rev=%%I + set dt=%%J + set tm=%%K +) +if not "%dt%" == "" ( + set /a yy=%dt:-=% / 10000 + set /a mm=%dt:-=% / 100 %% 100 + set /a dd=%dt:-=% %% 100 +) +for /f "usebackq tokens=1" %%I in ( + `git -C "%gitdir%" symbolic-ref --short HEAD` +) do set branch=%%I +if not "%rev%" == "" ( + echo #define RUBY_REVISION "%rev:~,10%" + echo #define RUBY_FULL_REVISION "%rev%" + echo #define RUBY_BRANCH_NAME "%branch%" + echo #define RUBY_RELEASE_DATETIME "%dt%T%tm%Z" + echo #define RUBY_RELEASE_YEAR %yy% + echo #define RUBY_RELEASE_MONTH %mm% + echo #define RUBY_RELEASE_DAY %dd% +) +@endlocal From f4480095ebd4b241b881d6639e83ef86832e3712 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:20:00 +0000 Subject: [PATCH 0041/2435] Bump github.com/microsoft/vcpkg from master to 2025.09.17 Bumps [github.com/microsoft/vcpkg](https://github.com/microsoft/vcpkg) from master to 2025.09.17. This release includes the previously tagged commit. - [Release notes](https://github.com/microsoft/vcpkg/releases) - [Commits](https://github.com/microsoft/vcpkg/compare/120deac3062162151622ca4860575a33844ba10b...4334d8b4c8916018600212ab4dd4bbdc343065d1) --- updated-dependencies: - dependency-name: github.com/microsoft/vcpkg dependency-version: 2025.09.17 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 7ea28de5a2da23..b8b0faf93edb1a 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,5 +7,5 @@ "openssl", "zlib" ], - "builtin-baseline": "120deac3062162151622ca4860575a33844ba10b" + "builtin-baseline": "4334d8b4c8916018600212ab4dd4bbdc343065d1" } \ No newline at end of file From d4393772b89dab4f33c118a284d92dc80cd63c39 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 30 Sep 2025 10:03:49 +0200 Subject: [PATCH 0042/2435] ZJIT: Revert SetIvar specialization (#14673) CI passed on SetIvar but broke some larger Ruby tests. Needs further investigation and testing. * Revert "ZJIT: Fix rebase issue with tests" This reverts commit 37248c51233d827ca56471661175c56e31c3b14f. * Revert "ZJIT: Inline attr_accessor/attr_writer to SetIvar (#14629)" This reverts commit 40bb47665d3ff57e0f2eb5a9fd9e0109617015c9. --- test/ruby/test_zjit.rb | 43 +---------------------- zjit/src/hir.rs | 80 ------------------------------------------ 2 files changed, 1 insertion(+), 122 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 937cf44e190a56..683d53f339f8c2 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1642,7 +1642,7 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end - def test_attr_accessor_getivar + def test_attr_accessor assert_compiles '[4, 4]', %q{ class C attr_accessor :foo @@ -1658,47 +1658,6 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end - def test_attr_accessor_setivar - assert_compiles '[5, 5]', %q{ - class C - attr_accessor :foo - - def initialize - @foo = 4 - end - end - - def test(c) - c.foo = 5 - c.foo - end - - c = C.new - [test(c), test(c)] - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_attr_writer - assert_compiles '[5, 5]', %q{ - class C - attr_writer :foo - - def initialize - @foo = 4 - end - - def get_foo = @foo - end - - def test(c) - c.foo = 5 - c.get_foo - end - c = C.new - [test(c), test(c)] - }, call_threshold: 2, insns: [:opt_send_without_block] - end - def test_uncached_getconstant_path assert_compiles RUBY_COPYRIGHT.dump, %q{ def test = RUBY_COPYRIGHT diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 824b35f7673dc3..d81231e2820b88 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1986,24 +1986,6 @@ impl Function { } let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state }); self.make_equal_to(insn_id, getivar); - } else if def_type == VM_METHOD_TYPE_ATTRSET && args.len() == 1 { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); - if let Some(profiled_type) = profiled_type { - recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); - } - let id = unsafe { get_cme_def_body_attr_id(cme) }; - - // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. - // We omit gen_prepare_non_leaf_call on gen_setivar, so it's unsafe to raise for multi-ractor mode. - if unsafe { rb_zjit_singleton_class_p(klass) } { - let attached = unsafe { rb_class_attached_object(klass) }; - if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); - } - } - let val = args[0]; - let setivar = self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); - self.make_equal_to(insn_id, setivar); } else { if let Insn::SendWithoutBlock { def_type: insn_def_type, .. } = &mut self.insns[insn_id.0] { *insn_def_type = Some(MethodType::from(def_type)); @@ -12151,66 +12133,4 @@ mod opt_tests { Return v25 "); } - - #[test] - fn test_inline_attr_accessor_set() { - eval(" - class C - attr_accessor :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_inline_attr_writer_set() { - eval(" - class C - attr_writer :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } } From 00fcef53785a36a8f787808bace09de42bd5f5d8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 30 Sep 2025 16:23:09 +0900 Subject: [PATCH 0043/2435] Update bundled_gems --- gems/bundled_gems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 8de29d9937c808..d37d90bbf14eb2 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -10,7 +10,7 @@ minitest 5.25.5 https://github.com/minitest/minitest power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a rake 13.3.0 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit -rexml 3.4.2 https://github.com/ruby/rexml +rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp net-imap 0.5.10 https://github.com/ruby/net-imap 71c0288b9a8f78a7125a4ce2980ab421acdf5836 @@ -18,7 +18,7 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.9.4 https://github.com/ruby/rbs aa22d7ea0c992de7557c3e346c9100b8aa36d945 +rbs 3.9.5 https://github.com/ruby/rbs typeprof 0.30.1 https://github.com/ruby/typeprof debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc From 986d9177dd63aaecbbb6e3a02fe20370cbd21bc5 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 25 Jan 2025 15:50:03 +0900 Subject: [PATCH 0044/2435] [ruby/openssl] pkey: define and use OSSL_HAVE_IMMUTABLE_PKEY macro Introduce a useful macro indicating that the low-level struct wrapped in an EVP_PKEY cannot be modified. Currently, the macro is defined for OpenSSL 3.0 or later only. LibreSSL and AWS-LC can follow suit in the future. https://github.com/ruby/openssl/commit/032ed63096 --- ext/openssl/ossl.h | 4 ++++ ext/openssl/ossl_pkey.c | 2 +- ext/openssl/ossl_pkey.h | 2 +- ext/openssl/ossl_pkey_ec.c | 10 +++++----- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 22471d2085fa69..d519c96cd6d313 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -74,6 +74,10 @@ # include #endif +#if OSSL_OPENSSL_PREREQ(3, 0, 0) +# define OSSL_HAVE_IMMUTABLE_PKEY +#endif + /* * Common Module */ diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 0fed03332fc31e..37c132ef2ea680 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -508,7 +508,7 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self) void ossl_pkey_check_public_key(const EVP_PKEY *pkey) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY if (EVP_PKEY_missing_parameters(pkey)) ossl_raise(ePKeyError, "parameters missing"); #else diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 6778381210e882..24823e0f3e721a 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -105,7 +105,7 @@ static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ _type##_get0_##_group(obj, NULL, &bn)) -#if !OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifndef OSSL_HAVE_IMMUTABLE_PKEY #define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ /* \ * call-seq: \ diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 1d20f63e0c4648..a2b68cc1d1f4e5 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -246,7 +246,7 @@ ossl_ec_key_get_group(VALUE self) static VALUE ossl_ec_key_set_group(VALUE self, VALUE group_v) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -288,7 +288,7 @@ static VALUE ossl_ec_key_get_private_key(VALUE self) */ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -339,7 +339,7 @@ static VALUE ossl_ec_key_get_public_key(VALUE self) */ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -511,7 +511,7 @@ ossl_ec_key_to_der(VALUE self) */ static VALUE ossl_ec_key_generate_key(VALUE self) { -#if OSSL_OPENSSL_PREREQ(3, 0, 0) +#ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; @@ -1368,7 +1368,7 @@ static VALUE ossl_ec_point_make_affine(VALUE self) GetECPointGroup(self, group); rb_warn("OpenSSL::PKey::EC::Point#make_affine! is deprecated"); -#if !OSSL_OPENSSL_PREREQ(3, 0, 0) && !defined(OPENSSL_IS_AWSLC) +#if !defined(OSSL_HAVE_IMMUTABLE_PKEY) && !defined(OPENSSL_IS_AWSLC) if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1) ossl_raise(eEC_POINT, "EC_POINT_make_affine"); #endif From ad35a4be82f9356045036875759874bfac6c483b Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Jan 2025 02:26:41 +0900 Subject: [PATCH 0045/2435] [ruby/openssl] pkey: disallow {DH,DSA,EC,RSA}.new without arguments with OpenSSL 3.0 Raise ArgumentError if this is attempted when the extension is compiled with OpenSSL 3.0 or later. The form will be fully removed when we drop support for OpenSSL 1.1.1. When OpenSSL::PKey::{DH,DSA,EC,RSA}.new is called without any arguments, it sets up an empty corresponding low-level struct and wraps it in an EVP_PKEY. This is useful when the user later fills the missing fields using low-level setter methods such as OpenSSL::PKey::RSA#set_key. Such setter methods are not compatible with OpenSSL 3.0 or later, where EVP_PKEY is immutable once created. This means that the ability to create an empty instance is useless. https://github.com/ruby/openssl/commit/affd569f78 --- ext/openssl/ossl_pkey_dh.c | 11 +++++++++-- ext/openssl/ossl_pkey_dsa.c | 6 ++++++ ext/openssl/ossl_pkey_ec.c | 5 +++++ ext/openssl/ossl_pkey_rsa.c | 6 ++++++ test/openssl/test_pkey_dh.rb | 11 ++++++++--- test/openssl/test_pkey_dsa.rb | 11 ++++++++--- test/openssl/test_pkey_ec.rb | 29 ++++++++++++++++++----------- test/openssl/test_pkey_rsa.rb | 12 +++++++++++- 8 files changed, 71 insertions(+), 20 deletions(-) diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 118d29f04fffed..77082d5c348acb 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -43,6 +43,7 @@ static VALUE eDHError; * If called without arguments, an empty instance without any parameter or key * components is created. Use #set_pqg to manually set the parameters afterwards * (and optionally #set_key to set private and public key components). + * This form is not compatible with OpenSSL 3.0 or later. * * If a String is given, tries to parse it as a DER- or PEM- encoded parameters. * See also OpenSSL::PKey.read which can parse keys of any kinds. @@ -58,14 +59,15 @@ static VALUE eDHError; * * Examples: * # Creating an instance from scratch - * # Note that this is deprecated and will not work on OpenSSL 3.0 or later. + * # Note that this is deprecated and will result in ArgumentError when + * # using OpenSSL 3.0 or later. * dh = OpenSSL::PKey::DH.new * dh.set_pqg(bn_p, nil, bn_g) * * # Generating a parameters and a key pair * dh = OpenSSL::PKey::DH.new(2048) # An alias of OpenSSL::PKey::DH.generate(2048) * - * # Reading DH parameters + * # Reading DH parameters from a PEM-encoded string * dh_params = OpenSSL::PKey::DH.new(File.read('parameters.pem')) # loads parameters only * dh = OpenSSL::PKey.generate_key(dh_params) # generates a key pair */ @@ -84,10 +86,15 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) /* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */ if (rb_scan_args(argc, argv, "01", &arg) == 0) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::DH.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else dh = DH_new(); if (!dh) ossl_raise(eDHError, "DH_new"); goto legacy; +#endif } arg = ossl_to_der_if_possible(arg); diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 9f4f012cfe1686..bf92e1ceac6412 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -56,6 +56,7 @@ static VALUE eDSAError; * * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_pqg and #set_key. + * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of a \DSA key. * See also OpenSSL::PKey.read which can parse keys of any kinds. @@ -96,10 +97,15 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) /* The DSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::DSA.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else dsa = DSA_new(); if (!dsa) ossl_raise(eDSAError, "DSA_new"); goto legacy; +#endif } pass = ossl_pem_passwd_value(pass); diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index a2b68cc1d1f4e5..e3553c44188012 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -147,9 +147,14 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "02", &arg, &pass); if (NIL_P(arg)) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::EC.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else if (!(ec = EC_KEY_new())) ossl_raise(eECError, "EC_KEY_new"); goto legacy; +#endif } else if (rb_obj_is_kind_of(arg, cEC_GROUP)) { ec = ec_key_new_from_group(arg); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 185b47cbfb6561..4f7862023a61c7 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -59,6 +59,7 @@ static VALUE eRSAError; * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_key, #set_factors, and * #set_crt_params. + * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of an \RSA key. * Note that if _password_ is not specified, but the key is encrypted with a @@ -89,10 +90,15 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) /* The RSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { +#ifdef OSSL_HAVE_IMMUTABLE_PKEY + rb_raise(rb_eArgError, "OpenSSL::PKey::RSA.new cannot be called " \ + "without arguments; pkeys are immutable with OpenSSL 3.0"); +#else rsa = RSA_new(); if (!rsa) ossl_raise(eRSAError, "RSA_new"); goto legacy; +#endif } pass = ossl_pem_passwd_value(pass); diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index f0c42866ea5df7..ace9f8c0e05482 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -7,9 +7,14 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase NEW_KEYLEN = 2048 def test_new_empty - dh = OpenSSL::PKey::DH.new - assert_equal nil, dh.p - assert_equal nil, dh.priv_key + # pkeys are immutable with OpenSSL >= 3.0 + if openssl?(3, 0, 0) + assert_raise(ArgumentError) { OpenSSL::PKey::DH.new } + else + dh = OpenSSL::PKey::DH.new + assert_nil(dh.p) + assert_nil(dh.priv_key) + end end def test_new_generate diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index f3324b04af258b..0779483bd482b1 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -34,9 +34,14 @@ def test_new_break end def test_new_empty - key = OpenSSL::PKey::DSA.new - assert_nil(key.p) - assert_raise(OpenSSL::PKey::PKeyError) { key.to_der } + # pkeys are immutable with OpenSSL >= 3.0 + if openssl?(3, 0, 0) + assert_raise(ArgumentError) { OpenSSL::PKey::DSA.new } + else + key = OpenSSL::PKey::DSA.new + assert_nil(key.p) + assert_raise(OpenSSL::PKey::PKeyError) { key.to_der } + end end def test_generate diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index e569397c0a874c..58857cb038b10b 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -4,19 +4,9 @@ if defined?(OpenSSL) class OpenSSL::TestEC < OpenSSL::PKeyTestCase - def test_ec_key + def test_ec_key_new key1 = OpenSSL::PKey::EC.generate("prime256v1") - # PKey is immutable in OpenSSL >= 3.0; constructing an empty EC object is - # deprecated - if !openssl?(3, 0, 0) - key2 = OpenSSL::PKey::EC.new - key2.group = key1.group - key2.private_key = key1.private_key - key2.public_key = key1.public_key - assert_equal key1.to_der, key2.to_der - end - key3 = OpenSSL::PKey::EC.new(key1) assert_equal key1.to_der, key3.to_der @@ -35,6 +25,23 @@ def test_ec_key end end + def test_ec_key_new_empty + # pkeys are immutable with OpenSSL >= 3.0; constructing an empty EC object is + # disallowed + if openssl?(3, 0, 0) + assert_raise(ArgumentError) { OpenSSL::PKey::EC.new } + else + key = OpenSSL::PKey::EC.new + assert_nil(key.group) + + p256 = Fixtures.pkey("p256") + key.group = p256.group + key.private_key = p256.private_key + key.public_key = p256.public_key + assert_equal(p256.to_der, key.to_der) + end + end + def test_builtin_curves builtin_curves = OpenSSL::PKey::EC.builtin_curves assert_not_empty builtin_curves diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 850c16a029e1d3..6a8768d1fffb3b 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -61,6 +61,16 @@ def test_new_public_exponent assert_equal 3, key.e end + def test_new_empty + # pkeys are immutable with OpenSSL >= 3.0 + if openssl?(3, 0, 0) + assert_raise(ArgumentError) { OpenSSL::PKey::RSA.new } + else + key = OpenSSL::PKey::RSA.new + assert_nil(key.n) + end + end + def test_s_generate key1 = OpenSSL::PKey::RSA.generate(2048) assert_equal 2048, key1.n.num_bits @@ -181,7 +191,7 @@ def test_verify_empty_rsa assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") { rsa.verify("SHA1", "a", "b") } - end + end unless openssl?(3, 0, 0) # Empty RSA is not possible with OpenSSL >= 3.0 def test_sign_verify_pss key = Fixtures.pkey("rsa2048") From 37d65e9252a4c3096325c63a876a289e03a7417c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 30 Sep 2025 18:27:35 +0900 Subject: [PATCH 0046/2435] [ruby/openssl] pkey/dh: refactor tests - Generate smaller parameters in test_new_generate. Generating 2048-bit parameters is slow and sometimes takes a few minutes on GitHub-hosted CI runners. Also test the DH.generate alias, not just DH.new. - Simplify test_new_break to just check exceptions raised in the block because it is redundant. - Remove unnecessary OpenSSL::PKey::DH#public_key calls. - Update bare "assert" with more appropriate methods. https://github.com/ruby/openssl/commit/8bc7442310 --- test/openssl/test_pkey_dh.rb | 68 +++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index ace9f8c0e05482..6ca5b1f5f8cd40 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -4,8 +4,6 @@ if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH) class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase - NEW_KEYLEN = 2048 - def test_new_empty # pkeys are immutable with OpenSSL >= 3.0 if openssl?(3, 0, 0) @@ -18,22 +16,30 @@ def test_new_empty end def test_new_generate - # This test is slow - dh = OpenSSL::PKey::DH.new(NEW_KEYLEN) - assert_key(dh) + begin + dh1 = OpenSSL::PKey::DH.new(512) + rescue OpenSSL::PKey::PKeyError + omit "generating 512-bit DH parameters failed; " \ + "likely not supported by this OpenSSL build" + end + assert_equal(512, dh1.p.num_bits) + assert_key(dh1) + + dh2 = OpenSSL::PKey::DH.generate(512) + assert_equal(512, dh2.p.num_bits) + assert_key(dh2) + assert_not_equal(dh1.p, dh2.p) end if ENV["OSSL_TEST_ALL"] == "1" def test_new_break unless openssl? && OpenSSL.fips_mode - assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break }) assert_raise(RuntimeError) do - OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise } + OpenSSL::PKey::DH.new(2048) { raise } end else # The block argument is not executed in FIPS case. # See https://github.com/ruby/openssl/issues/692 for details. - assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break }) - assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise }) + assert_kind_of(OpenSSL::PKey::DH, OpenSSL::PKey::DH.new(2048) { raise }) end end @@ -56,15 +62,15 @@ def test_derive_key end def test_DHparams - dh = Fixtures.pkey("dh2048_ffdhe2048") - dh_params = dh.public_key + dh_params = Fixtures.pkey("dh2048_ffdhe2048") asn1 = OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(dh.p), - OpenSSL::ASN1::Integer(dh.g) + OpenSSL::ASN1::Integer(dh_params.p), + OpenSSL::ASN1::Integer(dh_params.g) ]) + assert_equal(asn1.to_der, dh_params.to_der) key = OpenSSL::PKey::DH.new(asn1.to_der) - assert_same_dh dh_params, key + assert_same_dh_params(dh_params, key) pem = <<~EOF -----BEGIN DH PARAMETERS----- @@ -76,14 +82,20 @@ def test_DHparams ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- EOF + assert_equal(pem, dh_params.export) key = OpenSSL::PKey::DH.new(pem) - assert_same_dh dh_params, key + assert_same_dh_params(dh_params, key) + assert_no_key(key) key = OpenSSL::PKey.read(pem) - assert_same_dh dh_params, key - - assert_equal asn1.to_der, dh.to_der - assert_equal pem, dh.export + assert_same_dh_params(dh_params, key) + assert_no_key(key) + + key = OpenSSL::PKey.generate_key(dh_params) + assert_same_dh_params(dh_params, key) + assert_key(key) + assert_equal(dh_params.to_der, key.to_der) + assert_equal(dh_params.to_pem, key.to_pem) end def test_public_key @@ -96,14 +108,14 @@ def test_public_key def test_generate_key # Deprecated in v3.0.0; incompatible with OpenSSL 3.0 - # Creates a copy with params only - dh = Fixtures.pkey("dh2048_ffdhe2048").public_key + dh = Fixtures.pkey("dh2048_ffdhe2048") assert_no_key(dh) dh.generate_key! assert_key(dh) - dh2 = dh.public_key + dh2 = OpenSSL::PKey::DH.new(dh.to_der) dh2.generate_key! + assert_not_equal(dh.pub_key, dh2.pub_key) assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key)) end if !openssl?(3, 0, 0) @@ -209,14 +221,14 @@ def assert_no_key(dh) end def assert_key(dh) - assert(dh.public?) - assert(dh.private?) - assert(dh.pub_key) - assert(dh.priv_key) + assert_true(dh.public?) + assert_true(dh.private?) + assert_kind_of(OpenSSL::BN, dh.pub_key) + assert_kind_of(OpenSSL::BN, dh.priv_key) end - def assert_same_dh(expected, key) - check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) + def assert_same_dh_params(expected, key) + check_component(expected, key, [:p, :q, :g]) end end From d8c8623f50af8f5324e1679ff95b1a2071c0c61e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 29 Sep 2025 18:33:04 -0400 Subject: [PATCH 0047/2435] Set context_stack on main thread We allocate the stack of the main thread using malloc, but we never set malloc_stack to true and context_stack. If we fork, the main thread may no longer be the main thread anymore so it reports memory being leaked in RUBY_FREE_AT_EXIT. This commit allows the main thread to free its own VM stack at shutdown. --- thread_none.c | 6 ++++++ thread_pthread.c | 7 +++++++ thread_win32.c | 6 ++++++ vm.c | 7 ++++--- vm_core.h | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/thread_none.c b/thread_none.c index 38686e17c1c339..e6616c05856ff9 100644 --- a/thread_none.c +++ b/thread_none.c @@ -335,4 +335,10 @@ rb_thread_prevent_fork(void *(*func)(void *), void *data) return func(data); } +void +rb_thread_malloc_stack_set(rb_thread_t *th, void *stack) +{ + // no-op +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ diff --git a/thread_pthread.c b/thread_pthread.c index 730ecb54163f7d..5150a6173e6e6b 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -3492,4 +3492,11 @@ rb_thread_lock_native_thread(void) return is_snt; } +void +rb_thread_malloc_stack_set(rb_thread_t *th, void *stack) +{ + th->sched.malloc_stack = true; + th->sched.context_stack = stack; +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ diff --git a/thread_win32.c b/thread_win32.c index 576f617e8d5c2a..3fc763924846bd 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -1020,4 +1020,10 @@ rb_thread_prevent_fork(void *(*func)(void *), void *data) return func(data); } +void +rb_thread_malloc_stack_set(rb_thread_t *th, void *stack) +{ + // no-op +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ diff --git a/vm.c b/vm.c index 524bde55e6447f..431db0cb0bc88d 100644 --- a/vm.c +++ b/vm.c @@ -3280,7 +3280,7 @@ ruby_vm_destruct(rb_vm_t *vm) if (vm) { rb_thread_t *th = vm->ractor.main_thread; - VALUE *stack = th->ec->vm_stack; + if (rb_free_at_exit) { rb_free_encoded_insn_data(); rb_free_global_enc_table(); @@ -3345,7 +3345,6 @@ ruby_vm_destruct(rb_vm_t *vm) rb_free_default_rand_key(); if (th && vm->fork_gen == 0) { /* If we have forked, main_thread may not be the initial thread */ - xfree(stack); ruby_mimfree(th); } } @@ -3827,7 +3826,9 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) if (self == 0) { size_t size = vm->default_params.thread_vm_stack_size / sizeof(VALUE); - rb_ec_initialize_vm_stack(th->ec, ALLOC_N(VALUE, size), size); + VALUE *stack = ALLOC_N(VALUE, size); + rb_ec_initialize_vm_stack(th->ec, stack, size); + rb_thread_malloc_stack_set(th, stack); } else { VM_ASSERT(th->ec->cfp == NULL); diff --git a/vm_core.h b/vm_core.h index 51898f56f9c559..da0249e567977d 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1965,6 +1965,7 @@ VALUE *rb_vm_svar_lep(const rb_execution_context_t *ec, const rb_control_frame_t int rb_vm_get_sourceline(const rb_control_frame_t *); void rb_vm_stack_to_heap(rb_execution_context_t *ec); void ruby_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame); +void rb_thread_malloc_stack_set(rb_thread_t *th, void *stack); rb_thread_t * ruby_thread_from_native(void); int ruby_thread_set_native(rb_thread_t *th); int rb_vm_control_frame_id_and_class(const rb_control_frame_t *cfp, ID *idp, ID *called_idp, VALUE *klassp); From d016595387069677c6b992dffe9322f67dc9bc73 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 30 Sep 2025 08:15:06 -0700 Subject: [PATCH 0048/2435] ZJIT: Unify fallback counters for send-ish insns (#14676) --- zjit.rb | 10 ++-- zjit/src/codegen.rs | 62 +++++++++++++---------- zjit/src/hir.rs | 119 ++++++++++++++++++++++++++++++++------------ zjit/src/state.rs | 15 ++++-- zjit/src/stats.rs | 80 +++++++++++++++++++++++------ 5 files changed, 209 insertions(+), 77 deletions(-) diff --git a/zjit.rb b/zjit.rb index a46802553c274a..4438a10c754a43 100644 --- a/zjit.rb +++ b/zjit.rb @@ -39,12 +39,14 @@ def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats - # Show non-exit counters - print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'send fallback unspecialized def_types', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'dynamic send types', buf:, stats:, limit: 20) + # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_optimized_cfuncs_', prompt: 'unoptimized sends to C functions', buf:, stats:, limit: 20) + # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time + print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) + # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7676d7eed46e70..c62fef73dea6be 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -12,12 +12,12 @@ use crate::backend::current::{Reg, ALLOC_REGS}; use crate::invariants::{track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption}; use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; -use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by, CompileError}; -use crate::stats::{counter_ptr, with_time_stat, Counter, send_fallback_counter, Counter::{compile_time_ns, exit_compile_error}}; +use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; +use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP}; -use crate::hir::{iseq_to_hir, BlockId, BranchEdge, Invariant, MethodType, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; -use crate::hir::{Const, FrameState, Function, Insn, InsnId}; +use crate::hir::{iseq_to_hir, BlockId, BranchEdge, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; +use crate::hir::{Const, FrameState, Function, Insn, InsnId, SendFallbackReason}; use crate::hir_type::{types, Type}; use crate::options::get_option; use crate::cast::IntoUsize; @@ -366,15 +366,15 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Jump(branch) => no_output!(gen_jump(jit, asm, branch)), Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)), Insn::IfFalse { val, target } => no_output!(gen_if_false(jit, asm, opnd!(val), target)), - &Insn::Send { cd, blockiseq, state, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state)), - &Insn::SendForward { cd, blockiseq, state, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state)), - Insn::SendWithoutBlock { cd, state, def_type, .. } => gen_send_without_block(jit, asm, *cd, *def_type, &function.frame_state(*state)), + &Insn::Send { cd, blockiseq, state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), + &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), + &Insn::SendWithoutBlock { cd, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason), // Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it. Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self - gen_send_without_block(jit, asm, *cd, None, &function.frame_state(*state)), + gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::SendWithoutBlockDirectTooManyArgs), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), - &Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)), - Insn::InvokeBlock { cd, state, .. } => gen_invokeblock(jit, asm, *cd, &function.frame_state(*state)), + &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), + &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), @@ -981,9 +981,9 @@ fn gen_send( cd: *const rb_call_data, blockiseq: IseqPtr, state: &FrameState, + reason: SendFallbackReason, ) -> lir::Opnd { - gen_incr_counter(asm, Counter::dynamic_send_count); - gen_incr_counter(asm, Counter::dynamic_send_type_send); + gen_incr_send_fallback_counter(asm, reason); gen_prepare_non_leaf_call(jit, asm, state); asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd)); @@ -1003,9 +1003,9 @@ fn gen_send_forward( cd: *const rb_call_data, blockiseq: IseqPtr, state: &FrameState, + reason: SendFallbackReason, ) -> lir::Opnd { - gen_incr_counter(asm, Counter::dynamic_send_count); - gen_incr_counter(asm, Counter::dynamic_send_type_send_forward); + gen_incr_send_fallback_counter(asm, reason); gen_prepare_non_leaf_call(jit, asm, state); @@ -1024,15 +1024,10 @@ fn gen_send_without_block( jit: &mut JITState, asm: &mut Assembler, cd: *const rb_call_data, - def_type: Option, state: &FrameState, + reason: SendFallbackReason, ) -> lir::Opnd { - gen_incr_counter(asm, Counter::dynamic_send_count); - gen_incr_counter(asm, Counter::dynamic_send_type_send_without_block); - - if let Some(def_type) = def_type { - gen_incr_counter(asm, send_fallback_counter(def_type)); - } + gen_incr_send_fallback_counter(asm, reason); gen_prepare_non_leaf_call(jit, asm, state); asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd)); @@ -1118,9 +1113,9 @@ fn gen_invokeblock( asm: &mut Assembler, cd: *const rb_call_data, state: &FrameState, + reason: SendFallbackReason, ) -> lir::Opnd { - gen_incr_counter(asm, Counter::dynamic_send_count); - gen_incr_counter(asm, Counter::dynamic_send_type_invokeblock); + gen_incr_send_fallback_counter(asm, reason); gen_prepare_non_leaf_call(jit, asm, state); @@ -1141,9 +1136,9 @@ fn gen_invokesuper( cd: *const rb_call_data, blockiseq: IseqPtr, state: &FrameState, + reason: SendFallbackReason, ) -> lir::Opnd { - gen_incr_counter(asm, Counter::dynamic_send_count); - gen_incr_counter(asm, Counter::dynamic_send_type_invokesuper); + gen_incr_send_fallback_counter(asm, reason); gen_prepare_non_leaf_call(jit, asm, state); asm_comment!(asm, "call super with dynamic dispatch"); @@ -1548,6 +1543,23 @@ fn gen_incr_counter(asm: &mut Assembler, counter: Counter) { } } +/// Increment a counter for each DynamicSendReason. If the variant has +/// a counter prefix to break down the details, increment that as well. +fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReason) { + gen_incr_counter(asm, send_fallback_counter(reason)); + + use SendFallbackReason::*; + match reason { + NotOptimizedInstruction(opcode) => { + gen_incr_counter_ptr(asm, send_fallback_counter_ptr_for_opcode(opcode)); + } + SendWithoutBlockNotOptimizedMethodType(method_type) => { + gen_incr_counter(asm, send_fallback_counter_for_method_type(method_type)); + } + _ => {} + } +} + /// Save the current PC on the CFP as a preparation for calling a C function /// that may allocate objects and trigger GC. Use gen_prepare_non_leaf_call() /// if it may raise exceptions or call arbitrary methods. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d81231e2820b88..8f6e92d6539dd0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -15,6 +15,7 @@ use crate::hir_type::{Type, types}; use crate::bitset::BitSet; use crate::profile::{TypeDistributionSummary, ProfiledType}; use crate::stats::Counter; +use SendFallbackReason::*; /// An index of an [`Insn`] in a [`Function`]. This is a popular /// type since this effectively acts as a pointer to an [`Insn`]. @@ -514,6 +515,21 @@ impl std::fmt::Display for SideExitReason { } } +/// Reason why a send-ish instruction cannot be optimized from a fallback instruction +#[derive(Debug, Clone, Copy)] +pub enum SendFallbackReason { + SendWithoutBlockPolymorphic, + SendWithoutBlockNoProfiles, + SendWithoutBlockCfuncNotVariadic, + SendWithoutBlockCfuncArrayVariadic, + SendWithoutBlockNotOptimizedMethodType(MethodType), + SendWithoutBlockDirectTooManyArgs, + ObjToStringNotString, + /// Initial fallback reason for every instruction, which should be mutated to + /// a more actionable reason when an attempt to specialize the instruction fails. + NotOptimizedInstruction(ruby_vminsn_type), +} + /// An instruction in the SSA IR. The output of an instruction is referred to by the index of /// the instruction ([`InsnId`]). SSA form enables this, and [`UnionFind`] ([`Function::find`]) /// helps with editing. @@ -638,13 +654,39 @@ pub enum Insn { recv: InsnId, cd: *const rb_call_data, args: Vec, - def_type: Option, // Assigned in `optimize_direct_sends` if it's not optimized state: InsnId, + reason: SendFallbackReason, + }, + Send { + recv: InsnId, + cd: *const rb_call_data, + blockiseq: IseqPtr, + args: Vec, + state: InsnId, + reason: SendFallbackReason, + }, + SendForward { + recv: InsnId, + cd: *const rb_call_data, + blockiseq: IseqPtr, + args: Vec, + state: InsnId, + reason: SendFallbackReason, + }, + InvokeSuper { + recv: InsnId, + cd: *const rb_call_data, + blockiseq: IseqPtr, + args: Vec, + state: InsnId, + reason: SendFallbackReason, + }, + InvokeBlock { + cd: *const rb_call_data, + args: Vec, + state: InsnId, + reason: SendFallbackReason, }, - Send { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec, state: InsnId }, - SendForward { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec, state: InsnId }, - InvokeSuper { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec, state: InsnId }, - InvokeBlock { cd: *const rb_call_data, args: Vec, state: InsnId }, /// Optimized ISEQ call SendWithoutBlockDirect { @@ -1442,12 +1484,12 @@ impl Function { str: find!(str), state, }, - &SendWithoutBlock { recv, cd, ref args, def_type, state } => SendWithoutBlock { + &SendWithoutBlock { recv, cd, ref args, state, reason } => SendWithoutBlock { recv: find!(recv), cd, args: find_vec!(args), - def_type, state, + reason, }, &SendWithoutBlockDirect { recv, cd, cme, iseq, ref args, state } => SendWithoutBlockDirect { recv: find!(recv), @@ -1457,31 +1499,35 @@ impl Function { args: find_vec!(args), state, }, - &Send { recv, cd, blockiseq, ref args, state } => Send { + &Send { recv, cd, blockiseq, ref args, state, reason } => Send { recv: find!(recv), cd, blockiseq, args: find_vec!(args), state, + reason, }, - &SendForward { recv, cd, blockiseq, ref args, state } => SendForward { + &SendForward { recv, cd, blockiseq, ref args, state, reason } => SendForward { recv: find!(recv), cd, blockiseq, args: find_vec!(args), state, + reason, }, - &InvokeSuper { recv, cd, blockiseq, ref args, state } => InvokeSuper { + &InvokeSuper { recv, cd, blockiseq, ref args, state, reason } => InvokeSuper { recv: find!(recv), cd, blockiseq, args: find_vec!(args), state, + reason, }, - &InvokeBlock { cd, ref args, state } => InvokeBlock { + &InvokeBlock { cd, ref args, state, reason } => InvokeBlock { cd, args: find_vec!(args), state, + reason, }, &InvokeBuiltin { bf, ref args, state, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, return_type }, &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, @@ -1515,6 +1561,22 @@ impl Function { } } + /// Update DynamicSendReason for the instruction at insn_id + fn set_dynamic_send_reason(&mut self, insn_id: InsnId, dynamic_send_reason: SendFallbackReason) { + use Insn::*; + if get_option!(stats) { + match self.insns.get_mut(insn_id.0).unwrap() { + Send { reason, .. } + | SendForward { reason, .. } + | SendWithoutBlock { reason, .. } + | InvokeSuper { reason, .. } + | InvokeBlock { reason, .. } + => *reason = dynamic_send_reason, + _ => unreachable!("unexpected instruction {} at {insn_id}", self.find(insn_id)) + } + } + } + /// Replace `insn` with the new instruction `replacement`, which will get appended to `insns`. fn make_equal_to(&mut self, insn: InsnId, replacement: InsnId) { // Don't push it to the block @@ -1927,12 +1989,11 @@ impl Function { let Some(recv_type) = self.profiled_type_of_at(recv, frame_state.insn_idx) else { if get_option!(stats) { match self.is_polymorphic_at(recv, frame_state.insn_idx) { - Some(true) => self.push_insn(block, Insn::IncrCounter(Counter::send_fallback_polymorphic)), + Some(true) => self.set_dynamic_send_reason(insn_id, SendWithoutBlockPolymorphic), // If the class isn't known statically, then it should not also be monomorphic Some(false) => panic!("Should not have monomorphic profile at this point in this branch"), - None => self.push_insn(block, Insn::IncrCounter(Counter::send_fallback_no_profiles)), - - }; + None => self.set_dynamic_send_reason(insn_id, SendWithoutBlockNoProfiles), + } } self.push_insn_id(block, insn_id); continue; }; @@ -1943,9 +2004,7 @@ impl Function { // Do method lookup let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; if cme.is_null() { - if let Insn::SendWithoutBlock { def_type: insn_def_type, .. } = &mut self.insns[insn_id.0] { - *insn_def_type = Some(MethodType::Null); - } + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Null)); self.push_insn_id(block, insn_id); continue; } // Load an overloaded cme if applicable. See vm_search_cc(). @@ -1958,9 +2017,7 @@ impl Function { // TODO(max): Handle other kinds of parameter passing let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; if !can_direct_send(iseq) { - if let Insn::SendWithoutBlock { def_type: insn_def_type, .. } = &mut self.insns[insn_id.0] { - *insn_def_type = Some(MethodType::from(def_type)); - } + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Iseq)); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -1987,9 +2044,7 @@ impl Function { let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state }); self.make_equal_to(insn_id, getivar); } else { - if let Insn::SendWithoutBlock { def_type: insn_def_type, .. } = &mut self.insns[insn_id.0] { - *insn_def_type = Some(MethodType::from(def_type)); - } + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; } } @@ -2031,7 +2086,7 @@ impl Function { self.make_equal_to(insn_id, guard); } else { self.push_insn(block, Insn::GuardTypeNot { val, guard_type: types::String, state}); - let send_to_s = self.push_insn(block, Insn::SendWithoutBlock { recv: val, cd, args: vec![], def_type: None, state}); + let send_to_s = self.push_insn(block, Insn::SendWithoutBlock { recv: val, cd, args: vec![], state, reason: ObjToStringNotString }); self.make_equal_to(insn_id, send_to_s); } } @@ -2206,6 +2261,7 @@ impl Function { let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = ZJITState::get_method_annotations().get_cfunc_properties(method) else { + fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockCfuncNotVariadic); return Err(Some(method)); }; @@ -2269,6 +2325,7 @@ impl Function { -2 => { // (self, args_ruby_array) parameter form // Falling through for now + fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockCfuncArrayVariadic); } _ => unreachable!("unknown cfunc kind: argc={argc}") } @@ -3787,7 +3844,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let args = state.stack_pop_n(argc as usize)?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::SendWithoutBlock { recv, cd, args, def_type: None, state: exit_id }); + let send = fun.push_insn(block, Insn::SendWithoutBlock { recv, cd, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(send); } YARVINSN_opt_hash_freeze => { @@ -3895,7 +3952,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let args = state.stack_pop_n(argc as usize)?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::SendWithoutBlock { recv, cd, args, def_type: None, state: exit_id }); + let send = fun.push_insn(block, Insn::SendWithoutBlock { recv, cd, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(send); } YARVINSN_send => { @@ -3915,7 +3972,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id }); + let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(send); if !blockiseq.is_null() { @@ -3947,7 +4004,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let args = state.stack_pop_n(argc as usize + usize::from(forwarding))?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send_forward = fun.push_insn(block, Insn::SendForward { recv, cd, blockiseq, args, state: exit_id }); + let send_forward = fun.push_insn(block, Insn::SendForward { recv, cd, blockiseq, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(send_forward); if !blockiseq.is_null() { @@ -3976,7 +4033,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let recv = state.stack_pop()?; let blockiseq: IseqPtr = get_arg(pc, 1).as_ptr(); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let result = fun.push_insn(block, Insn::InvokeSuper { recv, cd, blockiseq, args, state: exit_id }); + let result = fun.push_insn(block, Insn::InvokeSuper { recv, cd, blockiseq, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(result); if !blockiseq.is_null() { @@ -4005,7 +4062,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id }); + let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id, reason: NotOptimizedInstruction(opcode) }); state.stack_push(result); } YARVINSN_getglobal => { diff --git a/zjit/src/state.rs b/zjit/src/state.rs index fa5d3bc83f506c..81c05f4986ac78 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -6,7 +6,7 @@ use crate::cruby_methods; use crate::invariants::Invariants; use crate::asm::CodeBlock; use crate::options::get_option; -use crate::stats::{Counters, ExitCounters}; +use crate::stats::{Counters, InsnCounters}; use crate::virtualmem::CodePtr; use std::collections::HashMap; @@ -28,7 +28,10 @@ pub struct ZJITState { counters: Counters, /// Side-exit counters - exit_counters: ExitCounters, + exit_counters: InsnCounters, + + /// Send fallback counters + send_fallback_counters: InsnCounters, /// Assumptions that require invalidation invariants: Invariants, @@ -78,6 +81,7 @@ impl ZJITState { code_block: cb, counters: Counters::default(), exit_counters: [0; VM_INSTRUCTION_SIZE as usize], + send_fallback_counters: [0; VM_INSTRUCTION_SIZE as usize], invariants: Invariants::default(), assert_compiles: false, method_annotations: cruby_methods::init(), @@ -139,10 +143,15 @@ impl ZJITState { } /// Get a mutable reference to side-exit counters - pub fn get_exit_counters() -> &'static mut ExitCounters { + pub fn get_exit_counters() -> &'static mut InsnCounters { &mut ZJITState::get_instance().exit_counters } + /// Get a mutable reference to fallback counters + pub fn get_send_fallback_counters() -> &'static mut InsnCounters { + &mut ZJITState::get_instance().send_fallback_counters + } + /// Get a mutable reference to unoptimized cfunc counter pointers pub fn get_unoptimized_cfunc_counter_pointers() -> &'static mut HashMap> { &mut ZJITState::get_instance().unoptimized_cfunc_counter_pointers diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7329b3442af0df..f9f9fb9e37bd5b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -17,6 +17,9 @@ macro_rules! make_counters { exit { $($exit_counter_name:ident,)+ } + dynamic_send { + $($dynamic_send_counter_name:ident,)+ + } $($counter_name:ident,)+ ) => { /// Struct containing the counter values @@ -24,6 +27,7 @@ macro_rules! make_counters { pub struct Counters { $(pub $default_counter_name: u64,)+ $(pub $exit_counter_name: u64,)+ + $(pub $dynamic_send_counter_name: u64,)+ $(pub $counter_name: u64,)+ } @@ -33,6 +37,7 @@ macro_rules! make_counters { pub enum Counter { $($default_counter_name,)+ $($exit_counter_name,)+ + $($dynamic_send_counter_name,)+ $($counter_name,)+ } @@ -41,6 +46,7 @@ macro_rules! make_counters { match self { $( Counter::$default_counter_name => stringify!($default_counter_name).to_string(), )+ $( Counter::$exit_counter_name => stringify!($exit_counter_name).to_string(), )+ + $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name).to_string(), )+ $( Counter::$counter_name => stringify!($counter_name).to_string(), )+ } } @@ -52,6 +58,7 @@ macro_rules! make_counters { match counter { $( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+ $( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+ + $( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+ $( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+ } } @@ -67,6 +74,11 @@ macro_rules! make_counters { $( Counter::$exit_counter_name, )+ ]; + /// List of other counters that are summed as dynamic_send_count. + pub const DYNAMIC_SEND_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_send_counter_name, )+ + ]; + /// List of other counters that are available only for --zjit-stats. pub const OTHER_COUNTERS: &'static [Counter] = &[ $( Counter::$counter_name, )+ @@ -114,6 +126,19 @@ make_counters! { exit_block_param_proxy_not_iseq_or_ifunc, } + // Send fallback counters that are summed as dynamic_send_count + dynamic_send { + // send_fallback_: Fallback reasons for send-ish instructions + send_fallback_send_without_block_polymorphic, + send_fallback_send_without_block_no_profiles, + send_fallback_send_without_block_cfunc_not_variadic, + send_fallback_send_without_block_cfunc_array_variadic, + send_fallback_send_without_block_not_optimized_method_type, + send_fallback_send_without_block_direct_too_many_args, + send_fallback_obj_to_string_not_string, + send_fallback_not_optimized_instruction, + } + // compile_error_: Compile error reasons compile_error_iseq_stack_too_large, compile_error_exception_handler, @@ -134,14 +159,6 @@ make_counters! { // The number of times YARV instructions are executed on JIT code zjit_insn_count, - // The number of times we do a dynamic dispatch from JIT code - dynamic_send_count, - dynamic_send_type_send_without_block, - dynamic_send_type_send, - dynamic_send_type_send_forward, - dynamic_send_type_invokeblock, - dynamic_send_type_invokesuper, - // The number of times we do a dynamic ivar lookup from JIT code dynamic_getivar_count, dynamic_setivar_count, @@ -161,9 +178,6 @@ make_counters! { unspecialized_def_type_refined, unspecialized_def_type_null, - send_fallback_polymorphic, - send_fallback_no_profiles, - // Writes to the VM frame vm_write_pc_count, vm_write_sp_count, @@ -190,7 +204,7 @@ macro_rules! incr_counter { pub(crate) use incr_counter; /// The number of side exits from each YARV instruction -pub type ExitCounters = [u64; VM_INSTRUCTION_SIZE as usize]; +pub type InsnCounters = [u64; VM_INSTRUCTION_SIZE as usize]; /// Return a raw pointer to the exit counter for a given YARV opcode pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { @@ -198,6 +212,12 @@ pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { unsafe { exit_counters.get_unchecked_mut(opcode as usize) } } +/// Return a raw pointer to the fallback counter for a given YARV opcode +pub fn send_fallback_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { + let fallback_counters = ZJITState::get_send_fallback_counters(); + unsafe { fallback_counters.get_unchecked_mut(opcode as usize) } +} + /// Reason why ZJIT failed to produce any JIT code #[derive(Clone, Debug, PartialEq)] pub enum CompileError { @@ -268,11 +288,26 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { counter_ptr(counter) } -pub fn send_fallback_counter(def_type: crate::hir::MethodType) -> Counter { +pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter { + use crate::hir::SendFallbackReason::*; + use crate::stats::Counter::*; + match reason { + SendWithoutBlockPolymorphic => send_fallback_send_without_block_polymorphic, + SendWithoutBlockNoProfiles => send_fallback_send_without_block_no_profiles, + SendWithoutBlockCfuncNotVariadic => send_fallback_send_without_block_cfunc_not_variadic, + SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, + SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, + SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, + ObjToStringNotString => send_fallback_obj_to_string_not_string, + NotOptimizedInstruction(_) => send_fallback_not_optimized_instruction, + } +} + +pub fn send_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter { use crate::hir::MethodType::*; use crate::stats::Counter::*; - match def_type { + match method_type { Iseq => unspecialized_def_type_iseq, Cfunc => unspecialized_def_type_cfunc, Attrset => unspecialized_def_type_attrset, @@ -376,6 +411,23 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, &key_string, *count); } + // Set send fallback counters for each DynamicSendReason + let mut dynamic_send_count = 0; + for &counter in DYNAMIC_SEND_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_send_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_send_count", dynamic_send_count); + + // Set send fallback counters for NotOptimizedInstruction + let send_fallback_counters = ZJITState::get_send_fallback_counters(); + for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) { + let op_name = insn_name(op_idx); + let key_string = "not_optimized_yarv_insn_".to_owned() + &op_name; + set_stat_usize!(hash, &key_string, *count); + } + // Only ZJIT_STATS builds support rb_vm_insn_count if unsafe { rb_vm_insn_count } > 0 { let vm_insn_count = unsafe { rb_vm_insn_count }; From 2f1c30cd50e464880e44da670d3ad8ebe00fc899 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 11:55:33 -0400 Subject: [PATCH 0049/2435] ZJIT: Add --zjit-trace-exits (#14640) Add side exit tracing functionality for ZJIT --- doc/zjit.md | 14 +++ gc.c | 8 ++ yjit/src/stats.rs | 4 +- zjit.c | 91 ++++++++++++++++ zjit.rb | 114 +++++++++++++++++++- zjit/bindgen/src/main.rs | 3 + zjit/src/backend/lir.rs | 12 ++- zjit/src/cruby_bindings.inc.rs | 5 + zjit/src/gc.rs | 7 ++ zjit/src/options.rs | 10 ++ zjit/src/state.rs | 185 ++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 49 +++++++++ 12 files changed, 496 insertions(+), 6 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index 4eedcca3ba7ac9..57a95457d304e3 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -153,6 +153,20 @@ To build with stats support: make -j ``` +### Tracing side exits + +Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. Note that the use of `--zjit-trace-exits` must be used alongside `--zjit-stats`. + +```bash +./miniruby --zjit-stats --zjit-trace-exits script.rb +``` + +A file called `zjit_exit_locations.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: + +```bash +stackprof path/to/zjit_exit_locations.dump +``` + ## ZJIT Glossary This glossary contains terms that are helpful for understanding ZJIT. diff --git a/gc.c b/gc.c index 8c8887c46b7d78..1961670c54d062 100644 --- a/gc.c +++ b/gc.c @@ -3070,6 +3070,14 @@ rb_gc_mark_roots(void *objspace, const char **categoryp) } #endif +#if USE_ZJIT + void rb_zjit_root_mark(void); + if (rb_zjit_enabled_p) { + MARK_CHECKPOINT("ZJIT"); + rb_zjit_root_mark(); + } +#endif + MARK_CHECKPOINT("machine_context"); mark_current_machine_context(ec); diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 09971c5b3afb48..b63e1c3272e356 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -893,7 +893,7 @@ fn rb_yjit_gen_stats_dict(key: VALUE) -> VALUE { /// and line samples. Their length should be the same, however the data stored in /// them is different. #[no_mangle] -pub extern "C" fn rb_yjit_record_exit_stack(_exit_pc: *const VALUE) +pub extern "C" fn rb_yjit_record_exit_stack(exit_pc: *const VALUE) { // Return if YJIT is not enabled if !yjit_enabled_p() { @@ -920,7 +920,7 @@ pub extern "C" fn rb_yjit_record_exit_stack(_exit_pc: *const VALUE) #[cfg(not(test))] { // Get the opcode from the encoded insn handler at this PC - let insn = unsafe { rb_vm_insn_addr2opcode((*_exit_pc).as_ptr()) }; + let insn = unsafe { rb_vm_insn_addr2opcode((*exit_pc).as_ptr()) }; // Use the same buffer size as Stackprof. const BUFF_LEN: usize = 2048; diff --git a/zjit.c b/zjit.c index 37619fd7296494..4bc27d9fe2dd15 100644 --- a/zjit.c +++ b/zjit.c @@ -31,6 +31,95 @@ #include +#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) + +// For a given raw_sample (frame), set the hash with the caller's +// name, file, and line number. Return the hash with collected frame_info. +static void +rb_zjit_add_frame(VALUE hash, VALUE frame) +{ + VALUE frame_id = PTR2NUM(frame); + + if (RTEST(rb_hash_aref(hash, frame_id))) { + return; + } + else { + VALUE frame_info = rb_hash_new(); + // Full label for the frame + VALUE name = rb_profile_frame_full_label(frame); + // Absolute path of the frame from rb_iseq_realpath + VALUE file = rb_profile_frame_absolute_path(frame); + // Line number of the frame + VALUE line = rb_profile_frame_first_lineno(frame); + + // If absolute path isn't available use the rb_iseq_path + if (NIL_P(file)) { + file = rb_profile_frame_path(frame); + } + + rb_hash_aset(frame_info, ID2SYM(rb_intern("name")), name); + rb_hash_aset(frame_info, ID2SYM(rb_intern("file")), file); + + if (line != INT2FIX(0)) { + rb_hash_aset(frame_info, ID2SYM(rb_intern("line")), line); + } + + rb_hash_aset(hash, frame_id, frame_info); + } +} + +// Parses the ZjitExitLocations raw_samples and line_samples collected by +// rb_zjit_record_exit_stack and turns them into 3 hashes (raw, lines, and frames) to +// be used by RubyVM::ZJIT.exit_locations. zjit_raw_samples represents the raw frames information +// (without name, file, and line), and zjit_line_samples represents the line information +// of the iseq caller. +VALUE +rb_zjit_exit_locations_dict(VALUE *zjit_raw_samples, int *zjit_line_samples, int samples_len) +{ + VALUE result = rb_hash_new(); + VALUE raw_samples = rb_ary_new_capa(samples_len); + VALUE line_samples = rb_ary_new_capa(samples_len); + VALUE frames = rb_hash_new(); + int idx = 0; + + // While the index is less than samples_len, parse zjit_raw_samples and + // zjit_line_samples, then add casted values to raw_samples and line_samples array. + while (idx < samples_len) { + int num = (int)zjit_raw_samples[idx]; + int line_num = (int)zjit_line_samples[idx]; + idx++; + + rb_ary_push(raw_samples, SIZET2NUM(num)); + rb_ary_push(line_samples, INT2NUM(line_num)); + + // Loop through the length of samples_len and add data to the + // frames hash. Also push the current value onto the raw_samples + // and line_samples arrary respectively. + for (int o = 0; o < num; o++) { + rb_zjit_add_frame(frames, zjit_raw_samples[idx]); + rb_ary_push(raw_samples, SIZET2NUM(zjit_raw_samples[idx])); + rb_ary_push(line_samples, INT2NUM(zjit_line_samples[idx])); + idx++; + } + + rb_ary_push(raw_samples, SIZET2NUM(zjit_raw_samples[idx])); + rb_ary_push(line_samples, INT2NUM(zjit_line_samples[idx])); + idx++; + + rb_ary_push(raw_samples, SIZET2NUM(zjit_raw_samples[idx])); + rb_ary_push(line_samples, INT2NUM(zjit_line_samples[idx])); + idx++; + } + + // Set add the raw_samples, line_samples, and frames to the results + // hash. + rb_hash_aset(result, ID2SYM(rb_intern("raw")), raw_samples); + rb_hash_aset(result, ID2SYM(rb_intern("lines")), line_samples); + rb_hash_aset(result, ID2SYM(rb_intern("frames")), frames); + + return result; +} + void rb_zjit_profile_disable(const rb_iseq_t *iseq); void @@ -217,6 +306,8 @@ VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key); VALUE rb_zjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_print_stats_p(rb_execution_context_t *ec, VALUE self); +VALUE rb_zjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self); +VALUE rb_zjit_get_exit_locations(rb_execution_context_t *ec, VALUE self); // Preprocessed zjit.rb generated during build #include "zjit.rbinc" diff --git a/zjit.rb b/zjit.rb index 4438a10c754a43..8289846c03b403 100644 --- a/zjit.rb +++ b/zjit.rb @@ -9,7 +9,10 @@ module RubyVM::ZJIT # Avoid calling a Ruby method here to avoid interfering with compilation tests if Primitive.rb_zjit_print_stats_p - at_exit { print_stats } + at_exit { + print_stats + dump_locations + } end end @@ -19,6 +22,106 @@ def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end + # Check if `--zjit-trace-exits` is used + def trace_exit_locations_enabled? + Primitive.rb_zjit_trace_exit_locations_enabled_p + end + + # If --zjit-trace-exits is enabled parse the hashes from + # Primitive.rb_zjit_get_exit_locations into a format readable + # by Stackprof. This will allow us to find the exact location of a + # side exit in ZJIT based on the instruction that is exiting. + def exit_locations + return unless trace_exit_locations_enabled? + + results = Primitive.rb_zjit_get_exit_locations + raw_samples = results[:raw].dup + line_samples = results[:lines].dup + frames = results[:frames].dup + samples_count = 0 + + frames.each do |frame_id, frame| + frame[:samples] = 0 + frame[:edges] = {} + end + + # Loop through the instructions and set the frame hash with the data. + # We use nonexistent.def for the file name, otherwise insns.def will be displayed + # and that information isn't useful in this context. + RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| + frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil } + results[:frames][frame_id] = frame_hash + frames[frame_id] = frame_hash + end + + # Loop through the raw_samples and build the hashes for StackProf. + # The loop is based off an example in the StackProf documentation and therefore + # this functionality can only work with that library. + while raw_samples.length > 0 + stack_trace = raw_samples.shift(raw_samples.shift + 1) + lines = line_samples.shift(line_samples.shift + 1) + prev_frame_id = nil + + stack_trace.each_with_index do |frame_id, idx| + if prev_frame_id + prev_frame = frames[prev_frame_id] + prev_frame[:edges][frame_id] ||= 0 + prev_frame[:edges][frame_id] += 1 + end + + frame_info = frames[frame_id] + frame_info[:total_samples] ||= 0 + frame_info[:total_samples] += 1 + + frame_info[:lines] ||= {} + frame_info[:lines][lines[idx]] ||= [0, 0] + frame_info[:lines][lines[idx]][0] += 1 + + prev_frame_id = frame_id + end + + top_frame_id = stack_trace.last + top_frame_line = 1 + + frames[top_frame_id][:samples] += 1 + frames[top_frame_id][:lines] ||= {} + frames[top_frame_id][:lines][top_frame_line] ||= [0, 0] + frames[top_frame_id][:lines][top_frame_line][1] += 1 + + samples_count += raw_samples.shift + line_samples.shift + end + + results[:samples] = samples_count + # Set missed_samples and gc_samples to 0 as their values + # don't matter to us in this context. + results[:missed_samples] = 0 + results[:gc_samples] = 0 + results + end + + # Marshal dumps exit locations to the given filename. + # + # Usage: + # + # In a script call: + # + # RubyVM::ZJIT.dump_exit_locations("my_file.dump") + # + # Then run the file with the following options: + # + # ruby --zjit --zjit-stats --zjit-trace-exits test.rb + # + # Once the code is done running, use Stackprof to read the dump file. + # See Stackprof documentation for options. + def dump_exit_locations(filename) + unless trace_exit_locations_enabled? + raise ArgumentError, "--zjit-trace-exits must be enabled to use dump_exit_locations." + end + + File.write(filename, Marshal.dump(RubyVM::ZJIT.exit_locations)) + end + # Check if `--zjit-stats` is used def stats_enabled? Primitive.rb_zjit_stats_enabled_p @@ -148,4 +251,13 @@ def number_with_delimiter(number) def print_stats $stderr.write stats_string end + + def dump_locations # :nodoc: + return unless trace_exit_locations_enabled? + + filename = "zjit_exit_locations.dump" + dump_exit_locations(filename) + + $stderr.puts("ZJIT exit locations dumped to `#{filename}`.") + end end diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index c4233521cce7fc..e1d19f9442c62b 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -281,6 +281,7 @@ fn main() { .allowlist_function("rb_RSTRING_PTR") .allowlist_function("rb_RSTRING_LEN") .allowlist_function("rb_ENCODING_GET") + .allowlist_function("rb_zjit_exit_locations_dict") .allowlist_function("rb_optimized_call") .allowlist_function("rb_jit_icache_invalidate") .allowlist_function("rb_zjit_print_exception") @@ -327,6 +328,8 @@ fn main() { .allowlist_function("rb_class_new_instance_pass_kw") .allowlist_function("rb_obj_alloc") .allowlist_function("rb_obj_info") + // From include/ruby/debug.h + .allowlist_function("rb_profile_frames") .allowlist_function("ruby_xfree") .allowlist_function("rb_profile_frames") diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 21adc42cd1c753..76a53c66d6b652 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -9,6 +9,7 @@ use crate::cruby::VALUE; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; +use crate::state::rb_zjit_record_exit_stack; pub use crate::backend::current::{ Reg, @@ -1629,6 +1630,16 @@ impl Assembler } } + if get_option!(trace_side_exits) { + // Use `load_into` with `C_ARG_OPNDS` instead of `opnds` argument for ccall, since `compile_side_exits` + // is after the split pass, which would allow use of `opnds`. + self.load_into(C_ARG_OPNDS[0], Opnd::const_ptr(pc as *const u8)); + self.ccall( + rb_zjit_record_exit_stack as *const u8, + vec![] + ); + } + asm_comment!(self, "exit to the interpreter"); self.frame_teardown(&[]); // matching the setup in :bb0-prologue: self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64())); @@ -2080,4 +2091,3 @@ mod tests { asm.load_into(mem, mem); } } - diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 17a2d5a63d6e6a..2d8a8eb11e7036 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -921,6 +921,11 @@ unsafe extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); + pub fn rb_zjit_exit_locations_dict( + zjit_raw_samples: *mut VALUE, + zjit_line_samples: *mut ::std::os::raw::c_int, + samples_len: ::std::os::raw::c_int, + ) -> VALUE; pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t); pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; pub fn rb_zjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool; diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index cc08b8fc9ebb07..0974c5bfce18f2 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -5,6 +5,7 @@ use crate::codegen::IseqCallRef; use crate::stats::CompileError; use crate::{cruby::*, profile::IseqProfile, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; use crate::stats::Counter::gc_time_ns; +use crate::state::gc_mark_raw_samples; /// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. #[derive(Debug)] @@ -250,3 +251,9 @@ pub fn remove_gc_offsets(payload_ptr: *mut IseqPayload, removed_range: &Range(left: &Range, right: &Range) -> bool where T: PartialOrd { left.start < right.end && right.start < left.end } + +/// Callback for marking GC objects inside [Invariants]. +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_root_mark() { + gc_mark_raw_samples(); +} diff --git a/zjit/src/options.rs b/zjit/src/options.rs index b33d18efffd4ab..ab9d1960ebaa20 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -69,6 +69,9 @@ pub struct Options { /// Dump all compiled machine code. pub dump_disasm: bool, + /// Trace and write side exit source maps to /tmp for stackprof. + pub trace_side_exits: bool, + /// Dump code map to /tmp for performance profilers. pub perf: bool, @@ -94,6 +97,7 @@ impl Default for Options { dump_hir_graphviz: None, dump_lir: false, dump_disasm: false, + trace_side_exits: false, perf: false, allowed_iseqs: None, log_compiled_iseqs: None, @@ -115,6 +119,8 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", "Log compiled ISEQs to the file. The file will be truncated."), + ("--zjit-trace-exits", + "Record Ruby source location when side-exiting.") ]; #[derive(Clone, Copy, Debug)] @@ -235,6 +241,10 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.print_stats = false; } + ("trace-exits", "") => { + options.trace_side_exits = true; + } + ("debug", "") => options.debug = true, ("disable-hir-opt", "") => options.disable_hir_opt = true, diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 81c05f4986ac78..50c3f4b1c18fa2 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -1,12 +1,12 @@ //! Runtime state of ZJIT. use crate::codegen::{gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline}; -use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, EcPtr, Qnil, VALUE, VM_INSTRUCTION_SIZE}; +use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, EcPtr, Qnil, rb_vm_insn_addr2opcode, rb_profile_frames, VALUE, VM_INSTRUCTION_SIZE, size_t, rb_gc_mark}; use crate::cruby_methods; use crate::invariants::Invariants; use crate::asm::CodeBlock; use crate::options::get_option; -use crate::stats::{Counters, InsnCounters}; +use crate::stats::{Counters, InsnCounters, SideExitLocations}; use crate::virtualmem::CodePtr; use std::collections::HashMap; @@ -53,6 +53,9 @@ pub struct ZJITState { /// Counter pointers for unoptimized C functions unoptimized_cfunc_counter_pointers: HashMap>, + + /// Locations of side exists within generated code + exit_locations: Option, } /// Private singleton instance of the codegen globals @@ -76,6 +79,12 @@ impl ZJITState { let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap(); let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap(); + let exit_locations = if get_option!(trace_side_exits) { + Some(SideExitLocations::default()) + } else { + None + }; + // Initialize the codegen globals instance let zjit_state = ZJITState { code_block: cb, @@ -89,6 +98,7 @@ impl ZJITState { function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, unoptimized_cfunc_counter_pointers: HashMap::new(), + exit_locations, }; unsafe { ZJIT_STATE = Some(zjit_state); } @@ -203,6 +213,16 @@ impl ZJITState { pub fn get_function_stub_hit_trampoline() -> CodePtr { ZJITState::get_instance().function_stub_hit_trampoline } + + /// Get a mutable reference to the ZJIT raw samples Vec + pub fn get_raw_samples() -> Option<&'static mut Vec> { + ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.raw_samples) + } + + /// Get a mutable reference to the ZJIT line samples Vec. + pub fn get_line_samples() -> Option<&'static mut Vec> { + ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.line_samples) + } } /// Initialize ZJIT @@ -238,3 +258,164 @@ pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE { ZJITState::enable_assert_compiles(); Qnil } + +/// Call `rb_profile_frames` and write the result into buffers to be consumed by `rb_zjit_record_exit_stack`. +fn record_profiling_frames() -> (i32, Vec, Vec) { + // Stackprof uses a buffer of length 2048 when collating the frames into statistics. + // Since eventually the collected information will be used by Stackprof, collect only + // 2048 frames at a time. + // https://github.com/tmm1/stackprof/blob/5d832832e4afcb88521292d6dfad4a9af760ef7c/ext/stackprof/stackprof.c#L21 + const BUFF_LEN: usize = 2048; + + let mut frames_buffer = vec![VALUE(0_usize); BUFF_LEN]; + let mut lines_buffer = vec![0; BUFF_LEN]; + + let stack_length = unsafe { + rb_profile_frames( + 0, + BUFF_LEN as i32, + frames_buffer.as_mut_ptr(), + lines_buffer.as_mut_ptr(), + ) + }; + + // Trim at `stack_length` since anything past it is redundant + frames_buffer.truncate(stack_length as usize); + lines_buffer.truncate(stack_length as usize); + + (stack_length, frames_buffer, lines_buffer) +} + +/// Write samples in `frames_buffer` and `lines_buffer` from profiling into +/// `raw_samples` and `line_samples`. Also write opcode, number of frames, +/// and stack size to be consumed by Stackprof. +fn write_exit_stack_samples( + raw_samples: &'static mut Vec, + line_samples: &'static mut Vec, + frames_buffer: &[VALUE], + lines_buffer: &[i32], + stack_length: i32, + exit_pc: *const VALUE, +) { + raw_samples.push(VALUE(stack_length as usize)); + line_samples.push(stack_length); + + // Push frames and their lines in reverse order. + for i in (0..stack_length as usize).rev() { + raw_samples.push(frames_buffer[i]); + line_samples.push(lines_buffer[i]); + } + + // Get the opcode from instruction handler at exit PC. + let exit_opcode = unsafe { rb_vm_insn_addr2opcode((*exit_pc).as_ptr()) }; + raw_samples.push(VALUE(exit_opcode as usize)); + // Push a dummy line number since we don't know where this insn is from. + line_samples.push(0); + + // Push number of times seen onto the stack. + raw_samples.push(VALUE(1usize)); + line_samples.push(1); +} + +fn try_increment_existing_stack( + raw_samples: &mut [VALUE], + line_samples: &mut [i32], + frames_buffer: &[VALUE], + stack_length: i32, + samples_length: usize, +) -> bool { + let prev_stack_len_index = raw_samples.len() - samples_length; + let prev_stack_len = i64::from(raw_samples[prev_stack_len_index]); + + if prev_stack_len == stack_length as i64 { + // Check if all stack lengths match and all frames are identical + let frames_match = (0..stack_length).all(|i| { + let current_frame = frames_buffer[stack_length as usize - 1 - i as usize]; + let prev_frame = raw_samples[prev_stack_len_index + i as usize + 1]; + current_frame == prev_frame + }); + + if frames_match { + let counter_idx = raw_samples.len() - 1; + let new_count = i64::from(raw_samples[counter_idx]) + 1; + + raw_samples[counter_idx] = VALUE(new_count as usize); + line_samples[counter_idx] = new_count as i32; + return true; + } + } + false +} + +/// Record a backtrace with ZJIT side exits +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { + if !zjit_enabled_p() || !get_option!(trace_side_exits) { + return; + } + + let (stack_length, frames_buffer, lines_buffer) = record_profiling_frames(); + + // Can safely unwrap since `trace_side_exits` must be true at this point + let zjit_raw_samples = ZJITState::get_raw_samples().unwrap(); + let zjit_line_samples = ZJITState::get_line_samples().unwrap(); + assert_eq!(zjit_raw_samples.len(), zjit_line_samples.len()); + + // Represents pushing the stack length, the instruction opcode, and the sample count. + const SAMPLE_METADATA_SIZE: usize = 3; + let samples_length = (stack_length as usize) + SAMPLE_METADATA_SIZE; + + // If zjit_raw_samples is greater than or equal to the current length of the samples + // we might have seen this stack trace previously. + if zjit_raw_samples.len() >= samples_length + && try_increment_existing_stack( + zjit_raw_samples, + zjit_line_samples, + &frames_buffer, + stack_length, + samples_length, + ) + { + return; + } + + write_exit_stack_samples( + zjit_raw_samples, + zjit_line_samples, + &frames_buffer, + &lines_buffer, + stack_length, + exit_pc, + ); +} + +/// Mark `raw_samples` so they can be used by rb_zjit_add_frame. +pub fn gc_mark_raw_samples() { + // Return if ZJIT is not enabled + if !zjit_enabled_p() || !get_option!(stats) || !get_option!(trace_side_exits) { + return; + } + + let mut idx: size_t = 0; + let zjit_raw_samples = ZJITState::get_raw_samples().unwrap(); + + while idx < zjit_raw_samples.len() as size_t { + let num = zjit_raw_samples[idx as usize]; + let mut i = 0; + idx += 1; + + // Mark the zjit_raw_samples at the given index. These represent + // the data that needs to be GC'd which are the current frames. + while i < i32::from(num) { + unsafe { rb_gc_mark(zjit_raw_samples[idx as usize]); } + i += 1; + idx += 1; + } + + // Increase index for exit instruction. + idx += 1; + // Increase index for bookeeping value (number of times we've seen this + // row in a stack). + idx += 1; + } +} diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index f9f9fb9e37bd5b..05ae231dad067a 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -463,3 +463,52 @@ pub fn with_time_stat(counter: Counter, func: F) -> R where F: FnOnce() -> pub fn zjit_alloc_size() -> usize { jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) } + +/// Struct of arrays for --zjit-trace-exits. +#[derive(Default)] +pub struct SideExitLocations { + /// Control frames of method entries. + pub raw_samples: Vec, + /// Line numbers of the iseq caller. + pub line_samples: Vec, +} + +/// Primitive called in zjit.rb +/// +/// Check if trace_exits generation is enabled. Requires the stats feature +/// to be enabled. +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { + if get_option!(stats) && get_option!(trace_side_exits) { + Qtrue + } else { + Qfalse + } +} + +/// Call the C function to parse the raw_samples and line_samples +/// into raw, lines, and frames hash for RubyVM::YJIT.exit_locations. +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { + if !zjit_enabled_p() || !get_option!(stats) || !get_option!(trace_side_exits) { + return Qnil; + } + + // Can safely unwrap since `trace_side_exits` must be true at this point + let zjit_raw_samples = ZJITState::get_raw_samples().unwrap(); + let zjit_line_samples = ZJITState::get_line_samples().unwrap(); + + assert_eq!(zjit_raw_samples.len(), zjit_line_samples.len()); + + // zjit_raw_samples and zjit_line_samples are the same length so + // pass only one of the lengths in the C function. + let samples_len = zjit_raw_samples.len() as i32; + + unsafe { + rb_zjit_exit_locations_dict( + zjit_raw_samples.as_mut_ptr(), + zjit_line_samples.as_mut_ptr(), + samples_len + ) + } +} From 8ce886b2d8bef0d0bc0edf8c1cd2c1348c1cbfef Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 30 Sep 2025 11:02:27 -0500 Subject: [PATCH 0050/2435] [DOC] Tweaks for String#partition --- doc/string/partition.rdoc | 52 +++++++++++++++++++++++++++------------ string.c | 2 +- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index ebe575e8eb3c51..ece034ee66225e 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -1,24 +1,44 @@ Returns a 3-element array of substrings of +self+. -Matches a pattern against +self+, scanning from the beginning. -The pattern is: +If +pattern+ is matched, returns the array: -- +string_or_regexp+ itself, if it is a Regexp. -- Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string. + [pre_match, first_match, post_match] -If the pattern is matched, returns pre-match, first-match, post-match: +where: - 'hello'.partition('l') # => ["he", "l", "lo"] - 'hello'.partition('ll') # => ["he", "ll", "o"] - 'hello'.partition('h') # => ["", "h", "ello"] - 'hello'.partition('o') # => ["hell", "o", ""] - 'hello'.partition(/l+/) #=> ["he", "ll", "o"] - 'hello'.partition('') # => ["", "", "hello"] - 'тест'.partition('т') # => ["", "т", "ест"] - 'こんにちは'.partition('に') # => ["こん", "に", "ちは"] +- +first_match+ is the first-found matching substring. +- +pre_match+ and +post_match+ are the preceding and following substrings. -If the pattern is not matched, returns a copy of +self+ and two empty strings: +If +pattern+ is not matched, returns the array: - 'hello'.partition('x') # => ["hello", "", ""] + [self.dup, "", ""] -Related: String#rpartition, String#split. +Note that in the examples below, a returned string 'hello' +is a copy of +self+, not +self+. + +If +pattern+ is a Regexp, performs the equivalent of self.match(pattern) +(also setting {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): + + 'hello'.partition(/h/) # => ["", "h", "ello"] + 'hello'.partition(/l/) # => ["he", "l", "lo"] + 'hello'.partition(/l+/) # => ["he", "ll", "o"] + 'hello'.partition(/o/) # => ["hell", "o", ""] + 'hello'.partition(/^/) # => ["", "", "hello"] + 'hello'.partition(//) # => ["", "", "hello"] + 'hello'.partition(/$/) # => ["hello", "", ""] + 'hello'.partition(/x/) # => ["hello", "", ""] + +If +pattern+ is not a Regexp, converts it to a string (if it is not already one), +then performs the equivalet of self.index(pattern) +(and does _not_ set {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): + + 'hello'.partition('h') # => ["", "h", "ello"] + 'hello'.partition('l') # => ["he", "l", "lo"] + 'hello'.partition('ll') # => ["he", "ll", "o"] + 'hello'.partition('o') # => ["hell", "o", ""] + 'hello'.partition('') # => ["", "", "hello"] + 'hello'.partition('x') # => ["hello", "", ""] + 'тест'.partition('т') # => ["", "т", "ест"] + 'こんにちは'.partition('に') # => ["こん", "に", "ちは"] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/string.c b/string.c index 81353556c956c0..9d11936de7b700 100644 --- a/string.c +++ b/string.c @@ -11217,7 +11217,7 @@ rb_str_center(int argc, VALUE *argv, VALUE str) /* * call-seq: - * partition(string_or_regexp) -> [head, match, tail] + * partition(pattern) -> [pre_match, first_match, post_match] * * :include: doc/string/partition.rdoc * From 4ae5d69d1fca828ec1a50fab0126d88fc0a3f361 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 30 Sep 2025 18:10:21 +0100 Subject: [PATCH 0051/2435] [DOC] Tweaks for String#reverse --- string.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index 9d11936de7b700..53bbeb419150fb 100644 --- a/string.c +++ b/string.c @@ -6983,12 +6983,16 @@ rb_str_bytesplice(int argc, VALUE *argv, VALUE str) /* * call-seq: - * reverse -> string + * reverse -> new_string * * Returns a new string with the characters from +self+ in reverse order. * - * 'stressed'.reverse # => "desserts" + * 'drawer'.reverse # => "reward" + * 'reviled'.reverse # => "deliver" + * 'stressed'.reverse # => "desserts" + * 'semordnilaps'.reverse # => "spalindromes" * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From 83f1082e638e6f9161c4ddd0acad7a658ce4648b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 30 Sep 2025 10:50:33 -0700 Subject: [PATCH 0052/2435] ZJIT: Fix "malformed format string" on stats (#14681) --- zjit.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zjit.rb b/zjit.rb index 8289846c03b403..83f8adb866c663 100644 --- a/zjit.rb +++ b/zjit.rb @@ -221,8 +221,8 @@ def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) return if stats.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } - left_pad = counters.keys.map(&:size).max - right_pad = counters.values.map { |value| number_with_delimiter(value).size }.max + key_pad = counters.keys.map(&:size).max + value_pad = counters.values.map { |value| number_with_delimiter(value).size }.max total = counters.values.sum counters = counters.to_a @@ -234,9 +234,7 @@ def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) buf << " (%.1f%% of total #{number_with_delimiter(total)})" % (100.0 * counters.map(&:last).sum / total) if limit buf << ":\n" counters.each do |key, value| - padded_key = key.rjust(left_pad, ' ') - padded_value = number_with_delimiter(value).rjust(right_pad, ' ') - buf << " #{padded_key}: #{padded_value} (%4.1f%%)\n" % (100.0 * value / total) + buf << " %*s: %*s (%4.1f%%)\n" % [key_pad, key, value_pad, number_with_delimiter(value), (100.0 * value / total)] end end From 14fce2746acc4ff5963d8c296af8b952746e1241 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 29 Sep 2025 21:23:10 -0400 Subject: [PATCH 0053/2435] CI: Fail the dump crash log step for visual reminder I forgot that this step existed and thought crash reporting wasn't working when they were simply moved to a different step. Failing these should give a nice visual hint. --- .github/workflows/yjit-macos.yml | 7 +++++-- .github/workflows/yjit-ubuntu.yml | 4 +++- .github/workflows/zjit-macos.yml | 4 +++- .github/workflows/zjit-ubuntu.yml | 4 +++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index fc538fe51be876..6299500ab34984 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -169,9 +169,12 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - if: ${{ failure() }} + - name: Dump crash logs + if: ${{ failure() }} continue-on-error: true - run: tail --verbose --lines=+1 rb_crash_*.txt + run: | + tail --verbose --lines=+1 rb_crash_*.txt + exit 1 - uses: ./.github/actions/slack with: diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index e086582e2430b3..bf12f80c0eae1a 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -214,7 +214,9 @@ jobs: - name: Dump crash logs if: ${{ failure() }} continue-on-error: true - run: tail --verbose --lines=+1 rb_crash_*.txt + run: | + tail --verbose --lines=+1 rb_crash_*.txt + exit 1 - uses: ./.github/actions/slack with: diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index bc4fef7ce2427f..a638a3b1b353dd 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -136,7 +136,9 @@ jobs: - name: Dump crash logs if: ${{ failure() }} continue-on-error: true - run: tail --verbose --lines=+1 rb_crash_*.txt + run: | + tail --verbose --lines=+1 rb_crash_*.txt + exit 1 - uses: ./.github/actions/slack with: diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 813f88c53b6713..99d5dbfdedc390 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -178,7 +178,9 @@ jobs: - name: Dump crash logs if: ${{ failure() }} continue-on-error: true - run: tail --verbose --lines=+1 rb_crash_*.txt + run: | + tail --verbose --lines=+1 rb_crash_*.txt + exit 1 - uses: ./.github/actions/slack with: From 0f3d3c78530260ef1b44f7b7808a3e0e009d54f0 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 12:42:53 -0400 Subject: [PATCH 0054/2435] ZJIT: Add extra info to rb_zjit_add_frame --- zjit.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zjit.c b/zjit.c index 4bc27d9fe2dd15..1affda0caf9bbe 100644 --- a/zjit.c +++ b/zjit.c @@ -59,6 +59,10 @@ rb_zjit_add_frame(VALUE hash, VALUE frame) rb_hash_aset(frame_info, ID2SYM(rb_intern("name")), name); rb_hash_aset(frame_info, ID2SYM(rb_intern("file")), file); + rb_hash_aset(frame_info, ID2SYM(rb_intern("samples")), INT2NUM(0)); + rb_hash_aset(frame_info, ID2SYM(rb_intern("total_samples")), INT2NUM(0)); + rb_hash_aset(frame_info, ID2SYM(rb_intern("edges")), rb_hash_new()); + rb_hash_aset(frame_info, ID2SYM(rb_intern("lines")), rb_hash_new()); if (line != INT2FIX(0)) { rb_hash_aset(frame_info, ID2SYM(rb_intern("line")), line); From 0a4bfb6499041535bebe7a6f5f27cc083716427e Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 12:43:22 -0400 Subject: [PATCH 0055/2435] ZJIT: Add correction rb_zjit_exit_locations_dict --- zjit.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zjit.c b/zjit.c index 1affda0caf9bbe..d877c0bacbd507 100644 --- a/zjit.c +++ b/zjit.c @@ -93,8 +93,9 @@ rb_zjit_exit_locations_dict(VALUE *zjit_raw_samples, int *zjit_line_samples, int int line_num = (int)zjit_line_samples[idx]; idx++; - rb_ary_push(raw_samples, SIZET2NUM(num)); - rb_ary_push(line_samples, INT2NUM(line_num)); + // + 1 as we append an additional sample for the insn + rb_ary_push(raw_samples, SIZET2NUM(num + 1)); + rb_ary_push(line_samples, INT2NUM(line_num + 1)); // Loop through the length of samples_len and add data to the // frames hash. Also push the current value onto the raw_samples From 55d363bd8dc8a5eddb63fee19af90edc98529a64 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 12:44:07 -0400 Subject: [PATCH 0056/2435] ZJIT: Use binwrite in zjit.rb --- zjit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index 83f8adb866c663..7ecf278de4d7ad 100644 --- a/zjit.rb +++ b/zjit.rb @@ -119,7 +119,7 @@ def dump_exit_locations(filename) raise ArgumentError, "--zjit-trace-exits must be enabled to use dump_exit_locations." end - File.write(filename, Marshal.dump(RubyVM::ZJIT.exit_locations)) + File.binwrite(filename, Marshal.dump(RubyVM::ZJIT.exit_locations)) end # Check if `--zjit-stats` is used From a0a4068e1be52d01533496237ced56fe2e8f743c Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 12:53:33 -0400 Subject: [PATCH 0057/2435] ZJIT: Use optimized exit_locations implementation --- zjit.rb | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/zjit.rb b/zjit.rb index 7ecf278de4d7ad..155f16a7131d9c 100644 --- a/zjit.rb +++ b/zjit.rb @@ -40,16 +40,11 @@ def exit_locations frames = results[:frames].dup samples_count = 0 - frames.each do |frame_id, frame| - frame[:samples] = 0 - frame[:edges] = {} - end - # Loop through the instructions and set the frame hash with the data. # We use nonexistent.def for the file name, otherwise insns.def will be displayed # and that information isn't useful in this context. RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| - frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil } + frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } results[:frames][frame_id] = frame_hash frames[frame_id] = frame_hash end @@ -57,39 +52,51 @@ def exit_locations # Loop through the raw_samples and build the hashes for StackProf. # The loop is based off an example in the StackProf documentation and therefore # this functionality can only work with that library. - while raw_samples.length > 0 - stack_trace = raw_samples.shift(raw_samples.shift + 1) - lines = line_samples.shift(line_samples.shift + 1) + # + # Raw Samples: + # [ length, frame1, frame2, frameN, ..., instruction, count + # + # Line Samples + # [ length, line_1, line_2, line_n, ..., dummy value, count + i = 0 + while i < raw_samples.length + stack_length = raw_samples[i] + i += 1 # consume the stack length + + sample_count = raw_samples[i + stack_length] + prev_frame_id = nil + stack_length.times do |idx| + idx += i + frame_id = raw_samples[idx] - stack_trace.each_with_index do |frame_id, idx| if prev_frame_id prev_frame = frames[prev_frame_id] prev_frame[:edges][frame_id] ||= 0 - prev_frame[:edges][frame_id] += 1 + prev_frame[:edges][frame_id] += sample_count end frame_info = frames[frame_id] - frame_info[:total_samples] ||= 0 - frame_info[:total_samples] += 1 + frame_info[:total_samples] += sample_count - frame_info[:lines] ||= {} - frame_info[:lines][lines[idx]] ||= [0, 0] - frame_info[:lines][lines[idx]][0] += 1 + frame_info[:lines][line_samples[idx]] ||= [0, 0] + frame_info[:lines][line_samples[idx]][0] += sample_count prev_frame_id = frame_id end - top_frame_id = stack_trace.last + i += stack_length # consume the stack + + top_frame_id = prev_frame_id top_frame_line = 1 - frames[top_frame_id][:samples] += 1 + frames[top_frame_id][:samples] += sample_count frames[top_frame_id][:lines] ||= {} frames[top_frame_id][:lines][top_frame_line] ||= [0, 0] - frames[top_frame_id][:lines][top_frame_line][1] += 1 + frames[top_frame_id][:lines][top_frame_line][1] += sample_count - samples_count += raw_samples.shift - line_samples.shift + samples_count += sample_count + i += 1 end results[:samples] = samples_count From e90729aa6c34d00743e2de9095293d3189587333 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 30 Sep 2025 20:20:55 +0100 Subject: [PATCH 0058/2435] ZJIT: Rust code refactors (#14687) --- zjit/src/codegen.rs | 2 +- zjit/src/hir.rs | 4 ++-- zjit/src/hir_type/mod.rs | 3 +-- zjit/src/options.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index c62fef73dea6be..b8d527fb8d6494 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -573,7 +573,7 @@ fn gen_setlocal(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep_offset: } else { // We're potentially writing a reference to an IMEMO/env object, // so take care of the write barrier with a function. - let local_index = local_ep_offset * -1; + let local_index = -local_ep_offset; asm_ccall!(asm, rb_vm_env_write, ep, local_index.into(), val); } } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8f6e92d6539dd0..7d6167d490bb74 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3263,7 +3263,7 @@ struct BytecodeInfo { has_blockiseq: bool, } -fn compute_bytecode_info(iseq: *const rb_iseq_t, opt_table: &Vec) -> BytecodeInfo { +fn compute_bytecode_info(iseq: *const rb_iseq_t, opt_table: &[u32]) -> BytecodeInfo { let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; let mut insn_idx = 0; let mut jump_targets: HashSet = opt_table.iter().copied().collect(); @@ -4251,7 +4251,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } /// Compile an entry_block for the interpreter -fn compile_entry_block(fun: &mut Function, jit_entry_insns: &Vec) { +fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32]) { let entry_block = fun.entry_block; fun.push_insn(entry_block, Insn::EntryPoint { jit_entry_idx: None }); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 3e690c79d0c04f..ffde7e458d3b28 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -355,8 +355,7 @@ impl Type { fn is_builtin(class: VALUE) -> bool { types::ExactBitsAndClass .iter() - .find(|&(_, class_object)| unsafe { **class_object } == class) - .is_some() + .any(|&(_, class_object)| unsafe { *class_object } == class) } /// Union both types together, preserving specialization if possible. diff --git a/zjit/src/options.rs b/zjit/src/options.rs index ab9d1960ebaa20..4040c85907de4c 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -314,7 +314,7 @@ fn update_profile_threshold() { /// Update --zjit-call-threshold for testing #[cfg(test)] pub fn set_call_threshold(call_threshold: u64) { - unsafe { rb_zjit_call_threshold = call_threshold as u64; } + unsafe { rb_zjit_call_threshold = call_threshold; } rb_zjit_prepare_options(); update_profile_threshold(); } From df2d1d5ad386c51ad9750282917ecacf2b343598 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 30 Sep 2025 16:51:56 -0400 Subject: [PATCH 0059/2435] ZJIT: Decouple stats and side exit tracing (#14688) --- doc/zjit.md | 4 ++-- zjit.rb | 8 ++++---- zjit/src/state.rs | 2 +- zjit/src/stats.rs | 9 +++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index 57a95457d304e3..ba65739e3ff697 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -155,10 +155,10 @@ make -j ### Tracing side exits -Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. Note that the use of `--zjit-trace-exits` must be used alongside `--zjit-stats`. +Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. ```bash -./miniruby --zjit-stats --zjit-trace-exits script.rb +./miniruby --zjit-trace-exits script.rb ``` A file called `zjit_exit_locations.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: diff --git a/zjit.rb b/zjit.rb index 155f16a7131d9c..2ff4cf2a5b2a6f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -9,10 +9,10 @@ module RubyVM::ZJIT # Avoid calling a Ruby method here to avoid interfering with compilation tests if Primitive.rb_zjit_print_stats_p - at_exit { - print_stats - dump_locations - } + at_exit { print_stats } + end + if Primitive.rb_zjit_trace_exit_locations_enabled_p + at_exit { dump_locations } end end diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 50c3f4b1c18fa2..206a7b3b618105 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -392,7 +392,7 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { /// Mark `raw_samples` so they can be used by rb_zjit_add_frame. pub fn gc_mark_raw_samples() { // Return if ZJIT is not enabled - if !zjit_enabled_p() || !get_option!(stats) || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || !get_option!(trace_side_exits) { return; } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 05ae231dad067a..a9cf1bde7cd131 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -2,6 +2,7 @@ use std::time::Instant; use std::sync::atomic::Ordering; +use crate::options::OPTIONS; #[cfg(feature = "stats_allocator")] #[path = "../../jit/src/lib.rs"] @@ -475,11 +476,11 @@ pub struct SideExitLocations { /// Primitive called in zjit.rb /// -/// Check if trace_exits generation is enabled. Requires the stats feature -/// to be enabled. +/// Check if trace_exits generation is enabled. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if get_option!(stats) && get_option!(trace_side_exits) { + // Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set. + if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits) { Qtrue } else { Qfalse @@ -490,7 +491,7 @@ pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: /// into raw, lines, and frames hash for RubyVM::YJIT.exit_locations. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if !zjit_enabled_p() || !get_option!(stats) || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || !get_option!(trace_side_exits) { return Qnil; } From 8cefb70e210348f509648df943eebe61ef708c3d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 30 Sep 2025 23:39:06 +0200 Subject: [PATCH 0060/2435] ZJIT: Re-apply attr_writer inlining (#14678) This re-applies https://github.com/ruby/ruby/pull/14629 / 40bb47665d3ff57e0f2eb5a9fd9e0109617015c9 by reverting https://github.com/ruby/ruby/pull/14673 / d4393772b89dab4f33c118a284d92dc80cd63c39. Co-authored-by: Alan Wu --- test/ruby/test_zjit.rb | 43 ++++++++++++++++++++++- zjit/src/hir.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 683d53f339f8c2..937cf44e190a56 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1642,7 +1642,7 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end - def test_attr_accessor + def test_attr_accessor_getivar assert_compiles '[4, 4]', %q{ class C attr_accessor :foo @@ -1658,6 +1658,47 @@ def test(c) = c.foo }, call_threshold: 2, insns: [:opt_send_without_block] end + def test_attr_accessor_setivar + assert_compiles '[5, 5]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) + c.foo = 5 + c.foo + end + + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_writer + assert_compiles '[5, 5]', %q{ + class C + attr_writer :foo + + def initialize + @foo = 4 + end + + def get_foo = @foo + end + + def test(c) + c.foo = 5 + c.get_foo + end + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + def test_uncached_getconstant_path assert_compiles RUBY_COPYRIGHT.dump, %q{ def test = RUBY_COPYRIGHT diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7d6167d490bb74..5588544b4f5a66 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2043,6 +2043,23 @@ impl Function { } let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state }); self.make_equal_to(insn_id, getivar); + } else if let (VM_METHOD_TYPE_ATTRSET, &[val]) = (def_type, args.as_slice()) { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + let id = unsafe { get_cme_def_body_attr_id(cme) }; + + // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. + // We omit gen_prepare_non_leaf_call on gen_setivar, so it's unsafe to raise for multi-ractor mode. + if unsafe { rb_zjit_singleton_class_p(klass) } { + let attached = unsafe { rb_class_attached_object(klass) }; + if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); + } + } + self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); + self.make_equal_to(insn_id, val); } else { self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; @@ -12190,4 +12207,66 @@ mod opt_tests { Return v25 "); } + + #[test] + fn test_inline_attr_accessor_set() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_inline_attr_writer_set() { + eval(" + class C + attr_writer :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } } From 17252958c9ce094c583616257452cfb1695dc7ef Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 29 Sep 2025 16:12:44 -0700 Subject: [PATCH 0061/2435] [ruby/prism] Add a "LAST" enum field to all flags enums This allows us to use the "last" of the enums in order to make masks, etc. This particular commit uses the call flag's last enum field as an offset so that we can define "private" flags but not accidentally clobber any newly added call node flags. https://github.com/ruby/prism/commit/e71aa980d8 --- prism/prism.c | 9 +++++---- prism/templates/include/prism/ast.h.erb | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 6dd0b2ae738588..875968f06be820 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2622,10 +2622,11 @@ pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument // There are certain flags that we want to use internally but don't want to // expose because they are not relevant beyond parsing. Therefore we'll define // them here and not define them in config.yml/a header file. -static const pm_node_flags_t PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY = 0x4; -static const pm_node_flags_t PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY = 0x40; -static const pm_node_flags_t PM_CALL_NODE_FLAGS_COMPARISON = 0x80; -static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = 0x100; +static const pm_node_flags_t PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY = (1 << 2); + +static const pm_node_flags_t PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY = ((PM_CALL_NODE_FLAGS_LAST - 1) << 1); +static const pm_node_flags_t PM_CALL_NODE_FLAGS_COMPARISON = ((PM_CALL_NODE_FLAGS_LAST - 1) << 2); +static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = ((PM_CALL_NODE_FLAGS_LAST - 1) << 3); /** * Allocate and initialize a new CallNode node. This sets everything to NULL or diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index 087eb81890eb45..e82cb78fe3106f 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -212,6 +212,8 @@ typedef enum pm_<%= flag.human %> { /** <%= value.comment %> */ PM_<%= flag.human.upcase %>_<%= value.name %> = <%= 1 << (index + Prism::Template::COMMON_FLAGS_COUNT) %>, <%- end -%> + + PM_<%= flag.human.upcase %>_LAST, } pm_<%= flag.human %>_t; <%- end -%> From 3eda2493ef00a684a1876309b1c1f95455e967fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 30 Sep 2025 17:35:32 +0200 Subject: [PATCH 0062/2435] [ruby/ipaddr] Remove warning by asserting its presence $ rake test >/dev/null /tmp/test/test_ipaddr.rb:202: warning: IPAddr#ipv4_compat is obsolete https://github.com/ruby/ipaddr/commit/31d62407c2 --- test/test_ipaddr.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index ac8921e75c8e47..64927a1444d7cb 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -199,7 +199,9 @@ def test_ipv4_compat assert_equal(128, b.prefix) a = IPAddr.new("192.168.0.0/16") - b = a.ipv4_compat + assert_warning(/obsolete/) { + b = a.ipv4_compat + } assert_equal("::192.168.0.0", b.to_s) assert_equal(Socket::AF_INET6, b.family) assert_equal(112, b.prefix) From 400e150f00ebbd71b14c9c4a885b1eb23be4b4b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:03:58 +0000 Subject: [PATCH 0063/2435] Bump ossf/scorecard-action from 2.4.2 to 2.4.3 Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.2 to 2.4.3. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/05b42c624433fc40578a4040d5cf5e36ddca8cde...4eaacf0543bb3f2c246792bd56e8cdeffafb205a) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index c997a6a453be76..99c563a25b3728 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif From 56f777cedf1b9acc2fe74bda3d5dde351ba31951 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 30 Sep 2025 20:50:08 -0700 Subject: [PATCH 0064/2435] ZJIT: Add more *_send_count stats (#14689) --- zjit.rb | 21 +++++++++++++++++---- zjit/src/codegen.rs | 5 +++++ zjit/src/stats.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/zjit.rb b/zjit.rb index 2ff4cf2a5b2a6f..ab5bae719a333e 100644 --- a/zjit.rb +++ b/zjit.rb @@ -162,9 +162,16 @@ def stats_string print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) - # Show the most important stats ratio_in_zjit at the end + # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end print_counters([ + :send_count, :dynamic_send_count, + :optimized_send_count, + :iseq_optimized_send_count, + :inline_cfunc_optimized_send_count, + :variadic_cfunc_optimized_send_count, + ], buf:, stats:, right_align: true, base: :send_count) + print_counters([ :dynamic_getivar_count, :dynamic_setivar_count, @@ -202,12 +209,18 @@ def assert_compiles # :nodoc: # :stopdoc: private - def print_counters(keys, buf:, stats:) - left_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 + def print_counters(keys, buf:, stats:, right_align: false, base: nil) + key_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 + key_align = '-' unless right_align + value_pad = keys.filter_map { |key| stats[key] }.map { |value| number_with_delimiter(value).size }.max + keys.each do |key| # Some stats like vm_insn_count and ratio_in_zjit are not supported on the release build next unless stats.key?(key) value = stats[key] + if base && key != base + ratio = " (%4.1f%%)" % (100.0 * value / stats[base]) + end case key when :ratio_in_zjit @@ -219,7 +232,7 @@ def print_counters(keys, buf:, stats:) value = number_with_delimiter(value) end - buf << "#{"%-#{left_pad}s" % "#{key}:"} #{value}\n" + buf << "%#{key_align}*s %*s%s\n" % [key_pad, "#{key}:", value_pad, value, ratio] end end diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b8d527fb8d6494..23631ae6baf70b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -661,6 +661,7 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. fn gen_ccall(asm: &mut Assembler, cfun: *const u8, args: Vec) -> lir::Opnd { + gen_incr_counter(asm, Counter::inline_cfunc_optimized_send_count); asm.ccall(cfun, args) } @@ -675,6 +676,8 @@ fn gen_ccall_variadic( cme: *const rb_callable_method_entry_t, state: &FrameState, ) -> lir::Opnd { + gen_incr_counter(asm, Counter::variadic_cfunc_optimized_send_count); + gen_prepare_non_leaf_call(jit, asm, state); let stack_growth = state.stack_size(); @@ -1051,6 +1054,8 @@ fn gen_send_without_block_direct( args: Vec, state: &FrameState, ) -> lir::Opnd { + gen_incr_counter(asm, Counter::iseq_optimized_send_count); + let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.as_usize(); let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.as_usize(); gen_stack_overflow_check(jit, asm, state, stack_growth); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index a9cf1bde7cd131..5c8333d01dc4b6 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -21,6 +21,9 @@ macro_rules! make_counters { dynamic_send { $($dynamic_send_counter_name:ident,)+ } + optimized_send { + $($optimized_send_counter_name:ident,)+ + } $($counter_name:ident,)+ ) => { /// Struct containing the counter values @@ -29,6 +32,7 @@ macro_rules! make_counters { $(pub $default_counter_name: u64,)+ $(pub $exit_counter_name: u64,)+ $(pub $dynamic_send_counter_name: u64,)+ + $(pub $optimized_send_counter_name: u64,)+ $(pub $counter_name: u64,)+ } @@ -39,6 +43,7 @@ macro_rules! make_counters { $($default_counter_name,)+ $($exit_counter_name,)+ $($dynamic_send_counter_name,)+ + $($optimized_send_counter_name,)+ $($counter_name,)+ } @@ -48,6 +53,7 @@ macro_rules! make_counters { $( Counter::$default_counter_name => stringify!($default_counter_name).to_string(), )+ $( Counter::$exit_counter_name => stringify!($exit_counter_name).to_string(), )+ $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name).to_string(), )+ + $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name).to_string(), )+ $( Counter::$counter_name => stringify!($counter_name).to_string(), )+ } } @@ -60,6 +66,7 @@ macro_rules! make_counters { $( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+ $( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+ $( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+ + $( Counter::$optimized_send_counter_name => std::ptr::addr_of_mut!(counters.$optimized_send_counter_name), )+ $( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+ } } @@ -80,6 +87,11 @@ macro_rules! make_counters { $( Counter::$dynamic_send_counter_name, )+ ]; + /// List of other counters that are summed as optimized_send_count. + pub const OPTIMIZED_SEND_COUNTERS: &'static [Counter] = &[ + $( Counter::$optimized_send_counter_name, )+ + ]; + /// List of other counters that are available only for --zjit-stats. pub const OTHER_COUNTERS: &'static [Counter] = &[ $( Counter::$counter_name, )+ @@ -140,6 +152,13 @@ make_counters! { send_fallback_not_optimized_instruction, } + // Optimized send counters that are summed as optimized_send_count + optimized_send { + iseq_optimized_send_count, + inline_cfunc_optimized_send_count, + variadic_cfunc_optimized_send_count, + } + // compile_error_: Compile error reasons compile_error_iseq_stack_too_large, compile_error_exception_handler, @@ -421,6 +440,16 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> } set_stat_usize!(hash, "dynamic_send_count", dynamic_send_count); + // Set optimized send counters + let mut optimized_send_count = 0; + for &counter in OPTIMIZED_SEND_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + optimized_send_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "optimized_send_count", optimized_send_count); + set_stat_usize!(hash, "send_count", dynamic_send_count + optimized_send_count); + // Set send fallback counters for NotOptimizedInstruction let send_fallback_counters = ZJITState::get_send_fallback_counters(); for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) { From df90a645d41d831431bff97ca53c8b61585c40c8 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 1 Oct 2025 00:20:26 -0400 Subject: [PATCH 0065/2435] ZJIT: Use Marshal.dump to handle large writes `File.binwrite` with a big string can exceed the `INT_MAX` limit of write(2) and fail with an exception. --- zjit.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index ab5bae719a333e..b84f2a4af63731 100644 --- a/zjit.rb +++ b/zjit.rb @@ -126,7 +126,9 @@ def dump_exit_locations(filename) raise ArgumentError, "--zjit-trace-exits must be enabled to use dump_exit_locations." end - File.binwrite(filename, Marshal.dump(RubyVM::ZJIT.exit_locations)) + File.open(filename, "wb") do |file| + Marshal.dump(RubyVM::ZJIT.exit_locations, file) + end end # Check if `--zjit-stats` is used From 3361aa5c7df35b1d1daea578fefec3addf29c9a6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 30 Sep 2025 23:16:15 +0900 Subject: [PATCH 0066/2435] win32: Setup prerelease version of Visual Studio --- win32/vssetup.cmd | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/win32/vssetup.cmd b/win32/vssetup.cmd index 1ff0a7d10ab6f4..c67bb0ad7c07de 100755 --- a/win32/vssetup.cmd +++ b/win32/vssetup.cmd @@ -10,7 +10,31 @@ ::- find the latest build tool and its setup batch file. @set VSDEVCMD= -@for /f "delims=" %%I in ('"%vswhere%" -products * -latest -property installationPath') do @( +@set VSDEV_ARGS= +@set where_opt= +@set arch= +:argloop +@(set arg=%1) & if defined arg (shift) else (goto :argend) + @if "%arg%" == "-prerelease" ( + set where_opt=-prerelease + goto :argloop + ) + @if /i "%arg%" == "-arch" ( + set arch=%1 + shift + goto :argloop + ) + @if /i "%arg:~0,6%" == "-arch=" ( + set arch=%arg:~6% + goto :argloop + ) + + @set VSDEV_ARGS=%VSDEV_ARGS% %arg% + @goto :argloop +:argend +@if defined VSDEV_ARGS set VSDEV_ARGS=%VSDEV_ARGS:~1% + +@for /f "delims=" %%I in ('"%vswhere%" -products * -latest -property installationPath %where_opt%') do @( set VSDEVCMD=%%I\Common7\Tools\VsDevCmd.bat ) @if not defined VSDEVCMD ( @@ -19,9 +43,14 @@ ) ::- default to the current processor. -@set arch=%PROCESSOR_ARCHITECTURE% +@set host_arch=%PROCESSOR_ARCHITECTURE% +@if not defined arch set arch=%PROCESSOR_ARCHITECTURE% ::- `vsdevcmd.bat` requires arch names to be lowercase @for %%i in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do @( call set arch=%%arch:%%i=%%i%% + call set host_arch=%%host_arch:%%i=%%i%% ) -@(endlocal && "%VSDEVCMD%" -arch=%arch% -host_arch=%arch% %*) +@if "%arch%" == "x86_64" set arch=amd64 + +::- chain to `vsdevcmd.bat` +@(endlocal && "%VSDEVCMD%" -arch=%arch% -host_arch=%host_arch% %VSDEV_ARGS%) From 77aaa6ab0a475b8a57b1de4aa1f1210e4f85a803 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:25:26 +0200 Subject: [PATCH 0067/2435] Interpolation with only string literals must not be frozen Basically a redo of https://github.com/ruby/ruby/commit/a1403fb7cbd1fe0df97c932be9814c86081783dc but respecting the frozen string literal magic comment Fixes [Bug #21187] --- prism/prism.c | 6 +++++ prism_compile.c | 23 ++++++++++++-------- spec/ruby/core/string/chilled_string_spec.rb | 8 +++++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 875968f06be820..22ac19b5e469fe 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5280,6 +5280,12 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ switch (PM_NODE_TYPE(part)) { case PM_STRING_NODE: + // If inner string is not frozen, it stops being a static literal. We should *not* clear other flags, + // because concatenating two frozen strings (`'foo' 'bar'`) is still frozen. This holds true for + // as long as this interpolation only consists of other string literals. + if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { + pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + } part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE); break; case PM_INTERPOLATED_STRING_NODE: diff --git a/prism_compile.c b/prism_compile.c index cd8287c7621d5b..66bee579c9b45a 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -558,7 +558,7 @@ parse_regexp_concat(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node); static int -pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result) +pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result, bool frozen_result) { int stack_size = 0; size_t parts_size = parts->size; @@ -668,10 +668,15 @@ pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const if (RTEST(current_string)) { current_string = rb_fstring(current_string); - if (stack_size == 0 && (interpolated || mutable_result)) { - PUSH_INSN1(ret, current_location, putstring, current_string); - } - else { + if (stack_size == 0) { + if (frozen_result) { + PUSH_INSN1(ret, current_location, putobject, current_string); + } else if (mutable_result || interpolated) { + PUSH_INSN1(ret, current_location, putstring, current_string); + } else { + PUSH_INSN1(ret, current_location, putchilledstring, current_string); + } + } else { PUSH_INSN1(ret, current_location, putobject, current_string); } @@ -692,7 +697,7 @@ pm_compile_regexp_dynamic(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_ rb_encoding *explicit_regexp_encoding = parse_regexp_encoding(scope_node, node); rb_encoding *implicit_regexp_encoding = explicit_regexp_encoding != NULL ? explicit_regexp_encoding : scope_node->encoding; - int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false); + int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false, false); PUSH_INSN2(ret, *node_location, toregexp, INT2FIX(parse_regexp_flags(node) & 0xFF), INT2FIX(length)); } @@ -9571,7 +9576,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } else { const pm_interpolated_string_node_t *cast = (const pm_interpolated_string_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, !PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE), PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); if (popped) PUSH_INSN(ret, location, pop); } @@ -9582,7 +9587,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // :"foo #{bar}" // ^^^^^^^^^^^^^ const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false, false); if (length > 1) { PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); @@ -9604,7 +9609,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN(ret, location, putself); - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false, false); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); PUSH_SEND_WITH_FLAG(ret, location, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); diff --git a/spec/ruby/core/string/chilled_string_spec.rb b/spec/ruby/core/string/chilled_string_spec.rb index 968e4ec1f0dfae..73d055cbdf2fd0 100644 --- a/spec/ruby/core/string/chilled_string_spec.rb +++ b/spec/ruby/core/string/chilled_string_spec.rb @@ -47,6 +47,14 @@ input.should == "chilled-mutated" end + it "emits a warning for concatenated strings" do + input = "still" "+chilled" + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "still+chilled-mutated" + end + it "emits a warning on singleton_class creation" do -> { "chilled".singleton_class From 1ef62d3fe7e2d0ef3db6a008afdd5f2ba68cdbd1 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 1 Oct 2025 22:02:48 +0100 Subject: [PATCH 0068/2435] ZJIT: Allow higher profile num (#14698) When we investigate guard failure issues, we sometimes need to use profile num around 100k (e.g. `lobsters` in ruby-bench). This change is to allow that. --- zjit/src/options.rs | 8 ++++---- zjit/src/profile.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 4040c85907de4c..1bfd0a91af1990 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -6,7 +6,7 @@ use crate::cruby::*; use std::collections::HashSet; /// Default --zjit-num-profiles -const DEFAULT_NUM_PROFILES: u8 = 5; +const DEFAULT_NUM_PROFILES: u32 = 5; /// Default --zjit-call-threshold. This should be large enough to avoid compiling /// warmup code, but small enough to perform well on micro-benchmarks. @@ -40,7 +40,7 @@ pub struct Options { pub mem_bytes: usize, /// Number of times YARV instructions should be profiled. - pub num_profiles: u8, + pub num_profiles: u32, /// Enable YJIT statsitics pub stats: bool, @@ -112,9 +112,9 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-mem-size=num", "Max amount of memory that ZJIT can use (in MiB)."), ("--zjit-call-threshold=num", - "Number of calls to trigger JIT (default: 2)."), + "Number of calls to trigger JIT (default: 30)."), ("--zjit-num-profiles=num", - "Number of profiled calls before JIT (default: 1, max: 255)."), + "Number of profiled calls before JIT (default: 5)."), ("--zjit-stats[=quiet]", "Enable collecting ZJIT statistics (=quiet to suppress output)."), ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index f73138d4bdcc41..471ff89e763148 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -278,7 +278,7 @@ pub struct IseqProfile { opnd_types: Vec>, /// Number of profiled executions for each YARV instruction, indexed by the instruction index - num_profiles: Vec, + num_profiles: Vec, } impl IseqProfile { From fb3b895f3f3c7bd1921c2db98e88a11dfcf60feb Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 1 Oct 2025 17:33:47 -0500 Subject: [PATCH 0069/2435] [DOC] Tweaks for String#reverse! --- string.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 53bbeb419150fb..7c2bc37a1dab7d 100644 --- a/string.c +++ b/string.c @@ -7052,10 +7052,12 @@ rb_str_reverse(VALUE str) * * Returns +self+ with its characters reversed: * - * s = 'stressed' - * s.reverse! # => "desserts" - * s # => "desserts" + * 'drawer'.reverse! # => "reward" + * 'reviled'.reverse! # => "deliver" + * 'stressed'.reverse! # => "desserts" + * 'semordnilaps'.reverse! # => "spalindromes" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From deba4d32332e41be674ec2d16f48c27cce1efe66 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 1 Oct 2025 17:47:51 -0500 Subject: [PATCH 0070/2435] Tweaks for String#replace --- doc/string.rb | 2 +- string.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/string.rb b/doc/string.rb index 43386c644c80cb..a5d143cf369b45 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -398,7 +398,7 @@ # - #gsub!: Replaces each substring that matches a given pattern with a given replacement string; # returns +self+ if any changes, +nil+ otherwise. # - #succ! (aliased as #next!): Returns +self+ modified to become its own successor. -# - #initialize_copy (aliased as #replace): Returns +self+ with its entire content replaced by a given string. +# - #replace: Returns +self+ with its entire content replaced by a given string. # - #reverse!: Returns +self+ with its characters in reverse order. # - #setbyte: Sets the byte at a given integer offset to a given value; returns the argument. # - #tr!: Replaces specified characters in +self+ with specified replacement characters; diff --git a/string.c b/string.c index 7c2bc37a1dab7d..a31aaece18f7a1 100644 --- a/string.c +++ b/string.c @@ -6648,11 +6648,13 @@ rb_str_gsub(int argc, VALUE *argv, VALUE str) * call-seq: * replace(other_string) -> self * - * Replaces the contents of +self+ with the contents of +other_string+: + * Replaces the contents of +self+ with the contents of +other_string+; + * returns +self+: * * s = 'foo' # => "foo" * s.replace('bar') # => "bar" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE @@ -12820,6 +12822,7 @@ Init_String(void) rb_define_singleton_method(rb_cString, "new", rb_str_s_new, -1); rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1); rb_define_method(rb_cString, "initialize", rb_str_init, -1); + rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1); rb_define_method(rb_cString, "<=>", rb_str_cmp_m, 1); rb_define_method(rb_cString, "==", rb_str_equal, 1); @@ -12850,7 +12853,6 @@ Init_String(void) rb_define_method(rb_cString, "byteindex", rb_str_byteindex_m, -1); rb_define_method(rb_cString, "rindex", rb_str_rindex_m, -1); rb_define_method(rb_cString, "byterindex", rb_str_byterindex_m, -1); - rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "clear", rb_str_clear, 0); rb_define_method(rb_cString, "chr", rb_str_chr, 0); rb_define_method(rb_cString, "getbyte", rb_str_getbyte, 1); From 67869b2e62e8adbfca6696c30ff56de654d72b77 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 22 Sep 2025 12:02:24 +0200 Subject: [PATCH 0071/2435] [ruby/json] Release 2.15.0 https://github.com/ruby/json/commit/4abfad090d --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 9c928e3940e493..e2db923e309cac 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.14.1' + VERSION = '2.15.0' end From 88222caaa9fd3772186f7ffcbe6691f39a4d7e09 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 2 Oct 2025 03:48:32 +0000 Subject: [PATCH 0072/2435] Update default gems list at 67869b2e62e8adbfca6696c30ff56d [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 3e153d8492423d..2f223e00921a56 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,7 +188,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 -* json 2.14.1 +* json 2.15.0 * optparse 0.7.0.dev.2 * prism 1.5.1 * psych 5.2.6 From 7ae67e8f6ad6e7fd0677b28a7a10961f79d55495 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 2 Oct 2025 19:19:51 +0900 Subject: [PATCH 0073/2435] load.c: Fix dest and src of MEMMOVE When multiple files with the same name are required, the features_index hash stores the indexes in `$LOADED_FEATURES` array into a darray. The dest and src arguments for `MEMMOVE` were wrongly reversed when inserting a new index in the darray. [Bug #21568] --- load.c | 2 +- test/ruby/test_require.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/load.c b/load.c index a0c45561d7eca4..c63aa2abbbf087 100644 --- a/load.c +++ b/load.c @@ -268,7 +268,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r if (pos >= 0) { long *ptr = rb_darray_data_ptr(feature_indexes); long len = rb_darray_size(feature_indexes); - MEMMOVE(ptr + pos, ptr + pos + 1, long, len - pos - 1); + MEMMOVE(ptr + pos + 1, ptr + pos, long, len - pos - 1); ptr[pos] = FIX2LONG(offset); } } diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 4f6c62dc35c174..49afcceb331e57 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -1035,4 +1035,18 @@ class Object end RUBY end + + def test_bug_21568 + load_path = $LOAD_PATH.dup + loaded_featrures = $LOADED_FEATURES.dup + + $LOAD_PATH.clear + $LOADED_FEATURES.replace(["foo.so", "a/foo.rb", "b/foo.rb"]) + + assert_nothing_raised(LoadError) { require "foo" } + + ensure + $LOAD_PATH.replace(load_path) if load_path + $LOADED_FEATURES.replace loaded_featrures + end end From 81f253577a77a934bfa02a33d80ca2a7c6af9a04 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 2 Oct 2025 11:57:43 -0400 Subject: [PATCH 0074/2435] ZJIT: Enable sample rate for side exit tracing (#14696) --- doc/zjit.md | 2 +- zjit.rb | 7 ++++--- zjit/src/options.rs | 15 ++++++++++++++- zjit/src/state.rs | 24 ++++++++++++++++++++++++ zjit/src/stats.rs | 2 ++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index ba65739e3ff697..65151051c8ea98 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -155,7 +155,7 @@ make -j ### Tracing side exits -Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. +Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. Optionally, you can use `--zjit-trace-exits-sample-rate=N` to sample every N-th occurrence. Enabling `--zjit-trace-exits-sample-rate=N` will automatically enable `--zjit-trace-exits`. ```bash ./miniruby --zjit-trace-exits script.rb diff --git a/zjit.rb b/zjit.rb index b84f2a4af63731..8a037e35a0a007 100644 --- a/zjit.rb +++ b/zjit.rb @@ -128,6 +128,7 @@ def dump_exit_locations(filename) File.open(filename, "wb") do |file| Marshal.dump(RubyVM::ZJIT.exit_locations, file) + file.size end end @@ -275,9 +276,9 @@ def print_stats def dump_locations # :nodoc: return unless trace_exit_locations_enabled? - filename = "zjit_exit_locations.dump" - dump_exit_locations(filename) + filename = "zjit_exits_#{Time.now.to_i}.dump" + n_bytes = dump_exit_locations(filename) - $stderr.puts("ZJIT exit locations dumped to `#{filename}`.") + $stderr.puts("#{n_bytes} bytes written to #{filename}.") end end diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 1bfd0a91af1990..44209b963f3bf9 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -72,6 +72,9 @@ pub struct Options { /// Trace and write side exit source maps to /tmp for stackprof. pub trace_side_exits: bool, + /// Frequency of tracing side exits. + pub trace_side_exits_sample_interval: usize, + /// Dump code map to /tmp for performance profilers. pub perf: bool, @@ -98,6 +101,7 @@ impl Default for Options { dump_lir: false, dump_disasm: false, trace_side_exits: false, + trace_side_exits_sample_interval: 0, perf: false, allowed_iseqs: None, log_compiled_iseqs: None, @@ -120,7 +124,9 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-log-compiled-iseqs=path", "Log compiled ISEQs to the file. The file will be truncated."), ("--zjit-trace-exits", - "Record Ruby source location when side-exiting.") + "Record Ruby source location when side-exiting."), + ("--zjit-trace-exits-sample-rate", + "Frequency at which to record side exits. Must be `usize`.") ]; #[derive(Clone, Copy, Debug)] @@ -245,6 +251,13 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.trace_side_exits = true; } + ("trace-exits-sample-rate", sample_interval) => { + // Even if `trace_side_exits` is already set, set it. + options.trace_side_exits = true; + // `sample_interval ` must provide a string that can be validly parsed to a `usize`. + options.trace_side_exits_sample_interval = sample_interval.parse::().ok()?; + } + ("debug", "") => options.debug = true, ("disable-hir-opt", "") => options.disable_hir_opt = true, diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 206a7b3b618105..8f88d2424436ab 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -223,6 +223,16 @@ impl ZJITState { pub fn get_line_samples() -> Option<&'static mut Vec> { ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.line_samples) } + + /// Get number of skipped samples. + pub fn get_skipped_samples() -> Option<&'static mut usize> { + ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.skipped_samples) + } + + /// Get number of skipped samples. + pub fn set_skipped_samples(n: usize) -> Option<()> { + ZJITState::get_instance().exit_locations.as_mut().map(|el| el.skipped_samples = n) + } } /// Initialize ZJIT @@ -354,6 +364,20 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { return; } + // When `trace_side_exits_sample_interval` is zero, then the feature is disabled. + if get_option!(trace_side_exits_sample_interval) != 0 { + // If `trace_side_exits_sample_interval` is set, then can safely unwrap + // both `get_skipped_samples` and `set_skipped_samples`. + let skipped_samples = *ZJITState::get_skipped_samples().unwrap(); + if skipped_samples < get_option!(trace_side_exits_sample_interval) { + // Skip sample and increment counter. + ZJITState::set_skipped_samples(skipped_samples + 1).unwrap(); + return; + } else { + ZJITState::set_skipped_samples(0).unwrap(); + } + } + let (stack_length, frames_buffer, lines_buffer) = record_profiling_frames(); // Can safely unwrap since `trace_side_exits` must be true at this point diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 5c8333d01dc4b6..d1c1aa7e032a3d 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -501,6 +501,8 @@ pub struct SideExitLocations { pub raw_samples: Vec, /// Line numbers of the iseq caller. pub line_samples: Vec, + /// Skipped samples + pub skipped_samples: usize } /// Primitive called in zjit.rb From 2ed5a02fcca4da4acf4c8c3d7ee4c392fc18d948 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 2 Oct 2025 17:03:25 +0100 Subject: [PATCH 0075/2435] ZJIT: Add `NoSingletonClass` patch point (#14680) * ZJIT: Add NoSingletonClass patch point This patch point makes sure that when the object has a singleton class, the JIT code is invalidated. As of now, this is only needed for C call optimization. In YJIT, the singleton class guard only applies to Array, Hash, and String. But in ZJIT, we may optimize C calls from gems (e.g. `sqlite3`). So the patch point needs to be applied to a broader range of classes. * ZJIT: Only generate NoSingletonClass guard when the type can have singleton class * ZJIT: Update or forget NoSingletonClass patch point when needed --- class.c | 2 + depend | 1 + gc.c | 3 + test/ruby/test_zjit.rb | 59 ++++++++ zjit.h | 3 + zjit/src/codegen.rs | 8 +- zjit/src/cruby.rs | 10 ++ zjit/src/gc.rs | 10 ++ zjit/src/hir.rs | 331 ++++++++++++++++++++++++----------------- zjit/src/invariants.rs | 55 ++++++- 10 files changed, 346 insertions(+), 136 deletions(-) diff --git a/class.c b/class.c index 68a56a129db72e..7baaa5e715f044 100644 --- a/class.c +++ b/class.c @@ -31,6 +31,7 @@ #include "ruby/st.h" #include "vm_core.h" #include "yjit.h" +#include "zjit.h" /* Flags of T_CLASS * @@ -1309,6 +1310,7 @@ make_singleton_class(VALUE obj) RBASIC_SET_CLASS(obj, klass); rb_singleton_class_attached(klass, obj); rb_yjit_invalidate_no_singleton_class(orig_class); + rb_zjit_invalidate_no_singleton_class(orig_class); SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class))); return klass; diff --git a/depend b/depend index d5ac468b44209d..63e73a56390056 100644 --- a/depend +++ b/depend @@ -1170,6 +1170,7 @@ class.$(OBJEXT): {$(VPATH)}vm_debug.h class.$(OBJEXT): {$(VPATH)}vm_opts.h class.$(OBJEXT): {$(VPATH)}vm_sync.h class.$(OBJEXT): {$(VPATH)}yjit.h +class.$(OBJEXT): {$(VPATH)}zjit.h compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h compar.$(OBJEXT): $(hdrdir)/ruby/version.h compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h diff --git a/gc.c b/gc.c index 1961670c54d062..42625e10046b59 100644 --- a/gc.c +++ b/gc.c @@ -1311,6 +1311,9 @@ rb_gc_obj_free(void *objspace, VALUE obj) break; case T_MODULE: case T_CLASS: +#if USE_ZJIT + rb_zjit_klass_free(obj); +#endif args.klass = obj; rb_class_classext_foreach(obj, classext_free, (void *)&args); if (RCLASS_CLASSEXT_TBL(obj)) { diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 937cf44e190a56..83af7347d61da9 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2912,6 +2912,65 @@ def self.new = :k }, call_threshold: 2, insns: [:opt_new] end + def test_singleton_class_invalidation_annotated_ccall + assert_compiles '[false, true]', %q{ + def define_singleton(obj, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + + def test(define) + obj = BasicObject.new + # This == call gets compiled to a CCall + obj == define_singleton(obj, define) + end + + result = [] + result << test(false) # Compiles BasicObject#== + result << test(true) # Should use singleton#== now + result + }, call_threshold: 2 + end + + def test_singleton_class_invalidation_optimized_variadic_ccall + assert_compiles '[1, 1000]', %q{ + def define_singleton(arr, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) # This CCall should be invalidated if singleton was defined + arr[0] + end + + result = [] + result << test(false) # Compiles Array#push as CCall + result << test(true) # Singleton defined, CCall should be invalidated + result + }, call_threshold: 2 + end + private # Assert that every method call in `test_script` can be compiled by ZJIT diff --git a/zjit.h b/zjit.h index 85f6e86d0350d4..7b3e410c91c4b0 100644 --- a/zjit.h +++ b/zjit.h @@ -19,6 +19,7 @@ void rb_zjit_profile_enable(const rb_iseq_t *iseq); void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme); void rb_zjit_cme_free(const rb_callable_method_entry_t *cme); +void rb_zjit_klass_free(VALUE klass); void rb_zjit_invalidate_no_ep_escape(const rb_iseq_t *iseq); void rb_zjit_constant_state_changed(ID id); void rb_zjit_iseq_mark(void *payload); @@ -26,6 +27,7 @@ void rb_zjit_iseq_update_references(void *payload); void rb_zjit_iseq_free(const rb_iseq_t *iseq); void rb_zjit_before_ractor_spawn(void); void rb_zjit_tracing_invalidate_all(void); +void rb_zjit_invalidate_no_singleton_class(VALUE klass); #else #define rb_zjit_enabled_p false static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception) {} @@ -37,6 +39,7 @@ static inline void rb_zjit_invalidate_no_ep_escape(const rb_iseq_t *iseq) {} static inline void rb_zjit_constant_state_changed(ID id) {} static inline void rb_zjit_before_ractor_spawn(void) {} static inline void rb_zjit_tracing_invalidate_all(void) {} +static inline void rb_zjit_invalidate_no_singleton_class(VALUE klass) {} #endif // #if USE_ZJIT #endif // #ifndef ZJIT_H diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 23631ae6baf70b..b7c3dc3532e903 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -9,7 +9,10 @@ use std::slice; use crate::asm::Label; use crate::backend::current::{Reg, ALLOC_REGS}; -use crate::invariants::{track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption}; +use crate::invariants::{ + track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, + track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption +}; use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; @@ -654,6 +657,9 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian Invariant::SingleRactorMode => { track_single_ractor_assumption(code_ptr, side_exit_ptr, payload_ptr); } + Invariant::NoSingletonClass { klass } => { + track_no_singleton_class_assumption(klass, code_ptr, side_exit_ptr, payload_ptr); + } } }); } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index baba0992ccaef9..1f514787f113d0 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -450,6 +450,16 @@ impl VALUE { self.static_sym_p() || self.dynamic_sym_p() } + pub fn instance_can_have_singleton_class(self) -> bool { + if self == unsafe { rb_cInteger } || self == unsafe { rb_cFloat } || + self == unsafe { rb_cSymbol } || self == unsafe { rb_cNilClass } || + self == unsafe { rb_cTrueClass } || self == unsafe { rb_cFalseClass } { + + return false + } + true + } + /// Return true for a static (non-heap) Ruby symbol (RB_STATIC_SYM_P) pub fn static_sym_p(self) -> bool { let VALUE(cval) = self; diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index 0974c5bfce18f2..934e1e8dcad165 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -148,6 +148,16 @@ pub extern "C" fn rb_zjit_cme_free(cme: *const rb_callable_method_entry_struct) invariants.forget_cme(cme); } +/// GC callback for finalizing a class +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_klass_free(klass: VALUE) { + if !ZJITState::has_instance() { + return; + } + let invariants = ZJITState::get_invariants(); + invariants.forget_klass(klass); +} + /// GC callback for updating object references after all object moves #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_root_update_references() { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5588544b4f5a66..4c123f5621fa00 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -141,6 +141,11 @@ pub enum Invariant { NoEPEscape(IseqPtr), /// There is one ractor running. If a non-root ractor gets spawned, this is invalidated. SingleRactorMode, + /// Objects of this class have no singleton class. + /// When a singleton class is created for an object of this class, this is invalidated. + NoSingletonClass { + klass: VALUE, + }, } impl Invariant { @@ -255,6 +260,12 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { Invariant::NoTracePoint => write!(f, "NoTracePoint"), Invariant::NoEPEscape(iseq) => write!(f, "NoEPEscape({})", &iseq_name(iseq)), Invariant::SingleRactorMode => write!(f, "SingleRactorMode"), + Invariant::NoSingletonClass { klass } => { + let class_name = get_class_name(klass); + write!(f, "NoSingletonClass({}@{:p})", + class_name, + self.ptr_map.map_ptr(klass.as_ptr::())) + } } } } @@ -2021,6 +2032,9 @@ impl Function { self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if klass.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); + } if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } @@ -2028,6 +2042,9 @@ impl Function { self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if klass.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); + } if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } @@ -2097,6 +2114,7 @@ impl Function { }; if recv_type.is_string() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_type.class() }, state }); let guard = self.push_insn(block, Insn::GuardType { val, guard_type: types::String, state }); // Infer type so AnyToString can fold off this self.insn_types[guard.0] = self.infer_type(guard); @@ -2224,6 +2242,11 @@ impl Function { /// Optimize SendWithoutBlock that land in a C method to a direct CCall without /// runtime lookup. fn optimize_c_calls(&mut self) { + fn gen_patch_points_for_optimized_ccall(fun: &mut Function, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); + } + // Try to reduce one SendWithoutBlock to a CCall fn reduce_to_ccall( fun: &mut Function, @@ -2285,12 +2308,16 @@ impl Function { let ci_flags = unsafe { vm_ci_flag(call_info) }; // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - // Commit to the replacement. Put PatchPoint. - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); + gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); + } if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } + let cfun = unsafe { get_mct_func(cfunc) }.cast(); let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); @@ -2308,16 +2335,11 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); - fun.push_insn(block, Insn::PatchPoint { - invariant: Invariant::MethodRedefined { - klass: recv_class, - method: method_id, - cme: method - }, - state - }); + gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); + } if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); @@ -8448,10 +8470,11 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) CheckInterrupts - Return v19 + Return v20 "); } @@ -8504,10 +8527,11 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) CheckInterrupts - Return v19 + Return v20 "); } @@ -8531,10 +8555,11 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[3] = Const Value(3) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :Integer (0x1038), v10 + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v10 CheckInterrupts - Return v20 + Return v21 "); } @@ -8561,10 +8586,11 @@ mod opt_tests { v10:Fixnum[1] = Const Value(1) v11:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v10, v11 + PatchPoint NoSingletonClass(Object@0x1000) + v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038), v10, v11 CheckInterrupts - Return v21 + Return v22 "); } @@ -8592,13 +8618,15 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) - v25:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v26:BasicObject = SendWithoutBlockDirect v25, :bar (0x1038) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) CheckInterrupts - Return v26 + Return v28 "); } @@ -8623,10 +8651,11 @@ mod opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) - v22:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] - v23:BasicObject = CCallVariadic puts@0x1040, v22, v12 + PatchPoint NoSingletonClass(Object@0x1008) + v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] + v24:BasicObject = CCallVariadic puts@0x1040, v23, v12 CheckInterrupts - Return v23 + Return v24 "); } @@ -9652,10 +9681,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v21:Fixnum = GuardType v9, Fixnum - v22:BasicObject = CCall itself@0x1038, v21 + v22:Fixnum = GuardType v9, Fixnum + v23:BasicObject = CCall itself@0x1038, v22 CheckInterrupts - Return v22 + Return v23 "); } @@ -9676,9 +9705,10 @@ mod opt_tests { bb2(v6:BasicObject): v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - v20:BasicObject = CCall itself@0x1038, v11 + PatchPoint NoSingletonClass(Array@0x1000) + v22:BasicObject = CCall itself@0x1038, v11 CheckInterrupts - Return v20 + Return v22 "); } @@ -9704,7 +9734,8 @@ mod opt_tests { bb2(v8:BasicObject, v9:NilClass): v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - v28:BasicObject = CCall itself@0x1038, v14 + PatchPoint NoSingletonClass(Array@0x1000) + v30:BasicObject = CCall itself@0x1038, v14 PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -9738,7 +9769,8 @@ mod opt_tests { PatchPoint StableConstantNames(0x1000, M) v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) - v31:StringExact|NilClass = CCall name@0x1048, v29 + PatchPoint NoSingletonClass(Module@0x1010) + v33:StringExact|NilClass = CCall name@0x1048, v29 PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -9768,7 +9800,8 @@ mod opt_tests { bb2(v8:BasicObject, v9:NilClass): v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - v28:Fixnum = CCall length@0x1038, v14 + PatchPoint NoSingletonClass(Array@0x1000) + v30:Fixnum = CCall length@0x1038, v14 v21:Fixnum[5] = Const Value(5) CheckInterrupts Return v21 @@ -9910,7 +9943,8 @@ mod opt_tests { bb2(v8:BasicObject, v9:NilClass): v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - v28:Fixnum = CCall size@0x1038, v14 + PatchPoint NoSingletonClass(Array@0x1000) + v30:Fixnum = CCall size@0x1038, v14 v21:Fixnum[5] = Const Value(5) CheckInterrupts Return v21 @@ -9991,9 +10025,10 @@ mod opt_tests { v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v18:ArrayExact = ArrayDup v16 PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) - v29:BasicObject = SendWithoutBlockDirect v18, :first (0x1040) + PatchPoint NoSingletonClass(Array@0x1008) + v30:BasicObject = SendWithoutBlockDirect v18, :first (0x1040) CheckInterrupts - Return v29 + Return v30 "); } @@ -10019,9 +10054,10 @@ mod opt_tests { PatchPoint StableConstantNames(0x1000, M) v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) - v23:BasicObject = SendWithoutBlockDirect v21, :class (0x1048) + PatchPoint NoSingletonClass(Module@0x1010) + v24:BasicObject = SendWithoutBlockDirect v21, :class (0x1048) CheckInterrupts - Return v23 + Return v24 "); } @@ -10052,10 +10088,11 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) CheckInterrupts - Return v22 + Return v23 "); } @@ -10233,9 +10270,10 @@ mod opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) - v21:Fixnum = CCall bytesize@0x1040, v12 + PatchPoint NoSingletonClass(String@0x1008) + v23:Fixnum = CCall bytesize@0x1040, v12 CheckInterrupts - Return v21 + Return v23 "); } @@ -10361,7 +10399,8 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - v45:NilClass = CCall initialize@0x1070, v43 + PatchPoint NoSingletonClass(C@0x1008) + v47:NilClass = CCall initialize@0x1070, v43 CheckInterrupts CheckInterrupts Return v43 @@ -10397,7 +10436,8 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) v45:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - v47:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 + PatchPoint NoSingletonClass(C@0x1008) + v48:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 CheckInterrupts CheckInterrupts Return v45 @@ -10427,7 +10467,8 @@ mod opt_tests { PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) - v45:NilClass = CCall initialize@0x1070, v43 + PatchPoint NoSingletonClass(Object@0x1008) + v47:NilClass = CCall initialize@0x1070, v43 CheckInterrupts CheckInterrupts Return v43 @@ -10457,7 +10498,8 @@ mod opt_tests { PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) - v45:NilClass = CCall initialize@0x1070, v43 + PatchPoint NoSingletonClass(BasicObject@0x1008) + v47:NilClass = CCall initialize@0x1070, v43 CheckInterrupts CheckInterrupts Return v43 @@ -10517,9 +10559,10 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - v51:BasicObject = CCallVariadic new@0x1048, v42, v13 + PatchPoint NoSingletonClass(Class@0x1040) + v53:BasicObject = CCallVariadic new@0x1048, v42, v13 CheckInterrupts - Return v51 + Return v53 "); } @@ -10546,8 +10589,9 @@ mod opt_tests { PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) v16:HeapObject = ObjectAlloc v40 PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) - v45:SetExact = GuardType v16, SetExact - v46:BasicObject = CCallVariadic initialize@0x1070, v45 + PatchPoint NoSingletonClass(Set@0x1008) + v46:SetExact = GuardType v16, SetExact + v47:BasicObject = CCallVariadic initialize@0x1070, v46 CheckInterrupts CheckInterrupts Return v16 @@ -10576,9 +10620,10 @@ mod opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - v49:BasicObject = CCallVariadic new@0x1048, v40 + PatchPoint NoSingletonClass(Class@0x1040) + v51:BasicObject = CCallVariadic new@0x1048, v40 CheckInterrupts - Return v49 + Return v51 "); } @@ -10607,7 +10652,8 @@ mod opt_tests { PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) - v50:BasicObject = CCallVariadic initialize@0x1078, v47, v15 + PatchPoint NoSingletonClass(Regexp@0x1008) + v51:BasicObject = CCallVariadic initialize@0x1078, v47, v15 CheckInterrupts CheckInterrupts Return v47 @@ -10633,9 +10679,10 @@ mod opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v17:ArrayExact = NewArray v11, v12 PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - v28:Fixnum = CCall length@0x1038, v17 + PatchPoint NoSingletonClass(Array@0x1000) + v30:Fixnum = CCall length@0x1038, v17 CheckInterrupts - Return v28 + Return v30 "); } @@ -10658,9 +10705,10 @@ mod opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v17:ArrayExact = NewArray v11, v12 PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - v28:Fixnum = CCall size@0x1038, v17 + PatchPoint NoSingletonClass(Array@0x1000) + v30:Fixnum = CCall size@0x1038, v17 CheckInterrupts - Return v28 + Return v30 "); } @@ -11169,8 +11217,9 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v25:String = GuardType v9, String - v19:StringExact = StringConcat v13, v25 + PatchPoint NoSingletonClass(String@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 CheckInterrupts Return v19 "); @@ -11200,8 +11249,9 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v25:String = GuardType v9, String - v19:StringExact = StringConcat v13, v25 + PatchPoint NoSingletonClass(MyString@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 CheckInterrupts Return v19 "); @@ -11289,9 +11339,9 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) CheckInterrupts PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v33:BasicObject = CCall itself@0x1038, v13 + v34:BasicObject = CCall itself@0x1038, v13 CheckInterrupts - Return v33 + Return v34 "); } @@ -11443,9 +11493,10 @@ mod opt_tests { v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:ArrayExact = ArrayDup v10 PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) - v21:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) + PatchPoint NoSingletonClass(Array@0x1008) + v22:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) CheckInterrupts - Return v21 + Return v22 "); } @@ -11515,9 +11566,9 @@ mod opt_tests { bb2(v6:BasicObject): v10:NilClass = Const Value(nil) PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v21:TrueClass = CCall nil?@0x1038, v10 + v22:TrueClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v21 + Return v22 "); } @@ -11564,9 +11615,9 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v21:FalseClass = CCall nil?@0x1038, v10 + v22:FalseClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v21 + Return v22 "); } @@ -11615,10 +11666,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v23:NilClass = GuardType v9, NilClass - v24:TrueClass = CCall nil?@0x1038, v23 + v24:NilClass = GuardType v9, NilClass + v25:TrueClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11641,10 +11692,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) - v23:FalseClass = GuardType v9, FalseClass - v24:FalseClass = CCall nil?@0x1038, v23 + v24:FalseClass = GuardType v9, FalseClass + v25:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11667,10 +11718,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) - v23:TrueClass = GuardType v9, TrueClass - v24:FalseClass = CCall nil?@0x1038, v23 + v24:TrueClass = GuardType v9, TrueClass + v25:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11693,10 +11744,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) - v23:StaticSymbol = GuardType v9, StaticSymbol - v24:FalseClass = CCall nil?@0x1038, v23 + v24:StaticSymbol = GuardType v9, StaticSymbol + v25:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11719,10 +11770,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v23:Fixnum = GuardType v9, Fixnum - v24:FalseClass = CCall nil?@0x1038, v23 + v24:Fixnum = GuardType v9, Fixnum + v25:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11745,10 +11796,10 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) - v23:Flonum = GuardType v9, Flonum - v24:FalseClass = CCall nil?@0x1038, v23 + v24:Flonum = GuardType v9, Flonum + v25:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v24 + Return v25 "); } @@ -11771,10 +11822,11 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) - v23:StringExact = GuardType v9, StringExact - v24:FalseClass = CCall nil?@0x1038, v23 + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + v26:FalseClass = CCall nil?@0x1038, v25 CheckInterrupts - Return v24 + Return v26 "); } @@ -11797,10 +11849,11 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) - v23:ArrayExact = GuardType v9, ArrayExact - v24:BoolExact = CCall !@0x1038, v23 + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + v26:BoolExact = CCall !@0x1038, v25 CheckInterrupts - Return v24 + Return v26 "); } @@ -11823,10 +11876,11 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) - v23:ArrayExact = GuardType v9, ArrayExact - v24:BoolExact = CCall empty?@0x1038, v23 + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + v26:BoolExact = CCall empty?@0x1038, v25 CheckInterrupts - Return v24 + Return v26 "); } @@ -11849,10 +11903,11 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) - v23:HashExact = GuardType v9, HashExact - v24:BoolExact = CCall empty?@0x1038, v23 + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + v26:BoolExact = CCall empty?@0x1038, v25 CheckInterrupts - Return v24 + Return v26 "); } @@ -11877,10 +11932,11 @@ mod opt_tests { Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) - v26:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v27:BoolExact = CCall ==@0x1038, v26, v12 + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:BoolExact = CCall ==@0x1038, v28, v12 CheckInterrupts - Return v27 + Return v29 "); } @@ -11960,10 +12016,11 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) CheckInterrupts - Return v19 + Return v20 "); } @@ -11994,11 +12051,12 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:BasicObject = LoadIvarEmbedded v24, :@foo@0x1039 + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 CheckInterrupts - Return v25 + Return v26 "); } @@ -12029,11 +12087,12 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:BasicObject = LoadIvarEmbedded v24, :@foo@0x1039 + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 CheckInterrupts - Return v25 + Return v26 "); } @@ -12106,10 +12165,11 @@ mod opt_tests { PatchPoint StableConstantNames(0x1000, O) v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v25:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v26:NilClass = Const Value(nil) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) CheckInterrupts - Return v26 + Return v27 "); } @@ -12139,10 +12199,11 @@ mod opt_tests { PatchPoint StableConstantNames(0x1000, O) v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v25:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v26:NilClass = Const Value(nil) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) CheckInterrupts - Return v26 + Return v27 "); } @@ -12169,11 +12230,12 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:NilClass = Const Value(nil) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) CheckInterrupts - Return v25 + Return v26 "); } @@ -12200,11 +12262,12 @@ mod opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:NilClass = Const Value(nil) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) CheckInterrupts - Return v25 + Return v26 "); } diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 75f900c38dffd8..119aa5ca52fb66 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, mem}; -use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID}, gc::IseqPayload, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; +use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID, VALUE}, gc::IseqPayload, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; use crate::stats::with_time_stat; use crate::stats::Counter::invalidation_time_ns; use crate::gc::remove_gc_offsets; @@ -59,6 +59,10 @@ pub struct Invariants { /// Set of patch points that assume that the interpreter is running with only one ractor single_ractor_patch_points: HashSet, + + /// Map from a class to a set of patch points that assume objects of the class + /// will have no singleton class. + no_singleton_class_patch_points: HashMap>, } impl Invariants { @@ -67,6 +71,7 @@ impl Invariants { self.update_ep_escape_iseqs(); self.update_no_ep_escape_iseq_patch_points(); self.update_cme_patch_points(); + self.update_no_singleton_class_patch_points(); } /// Forget an ISEQ when freeing it. We need to because a) if the address is reused, we'd be @@ -85,6 +90,11 @@ impl Invariants { self.cme_patch_points.remove(&cme); } + /// Forget a class when freeing it. See [Self::forget_iseq] for reasoning. + pub fn forget_klass(&mut self, klass: VALUE) { + self.no_singleton_class_patch_points.remove(&klass); + } + /// Update ISEQ references in Invariants::ep_escape_iseqs fn update_ep_escape_iseqs(&mut self) { let updated = std::mem::take(&mut self.ep_escape_iseqs) @@ -116,6 +126,17 @@ impl Invariants { .collect(); self.cme_patch_points = updated_cme_patch_points; } + + fn update_no_singleton_class_patch_points(&mut self) { + let updated_no_singleton_class_patch_points = std::mem::take(&mut self.no_singleton_class_patch_points) + .into_iter() + .map(|(klass, patch_points)| { + let new_klass = unsafe { rb_gc_location(klass) }; + (new_klass, patch_points) + }) + .collect(); + self.no_singleton_class_patch_points = updated_no_singleton_class_patch_points; + } } /// Called when a basic operator is redefined. Note that all the blocks assuming @@ -245,6 +266,21 @@ pub fn track_stable_constant_names_assumption( } } +/// Track a patch point for objects of a given class will have no singleton class. +pub fn track_no_singleton_class_assumption( + klass: VALUE, + patch_point_ptr: CodePtr, + side_exit_ptr: CodePtr, + payload_ptr: *mut IseqPayload, +) { + let invariants = ZJITState::get_invariants(); + invariants.no_singleton_class_patch_points.entry(klass).or_default().insert(PatchPoint { + patch_point_ptr, + side_exit_ptr, + payload_ptr, + }); +} + /// Called when a method is redefined. Invalidates all JIT code that depends on the CME. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_cme_invalidate(cme: *const rb_callable_method_entry_t) { @@ -357,3 +393,20 @@ pub extern "C" fn rb_zjit_tracing_invalidate_all() { cb.mark_all_executable(); }); } + +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_invalidate_no_singleton_class(klass: VALUE) { + if !zjit_enabled_p() { + return; + } + + with_vm_lock(src_loc!(), || { + let invariants = ZJITState::get_invariants(); + if let Some(patch_points) = invariants.no_singleton_class_patch_points.remove(&klass) { + let cb = ZJITState::get_code_block(); + debug!("Singleton class created for {:?}", klass); + compile_patch_points!(cb, patch_points, "Singleton class created for {:?}", klass); + cb.mark_all_executable(); + } + }); +} From 20fc91df395b834ecaee0bdb29df8da224fdf89a Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 1 Oct 2025 23:53:48 -0400 Subject: [PATCH 0076/2435] YJIT: Prevent making a branch from a dead block to a live block I'm seeing some memory corruption in the wild on blocks in `IseqPayload::dead_blocks`. While I unfortunately can't recreate the issue, (For all I know, it could be some external code corrupting YJIT's memory.) establishing a link between dead blocks and live blocks seems fishy enough that we ought to prevent it. When it did happen, it might've had bad interacts with Code GC and the optimization to immediately free empty blocks. --- yjit/src/core.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/yjit/src/core.rs b/yjit/src/core.rs index cfe55b8c767374..2999f151bfb73e 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -3591,6 +3591,13 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) - return CodegenGlobals::get_stub_exit_code().raw_ptr(cb); } + // Bail if this branch is housed in an invalidated (dead) block. + // This only happens in rare invalidation scenarios and we need + // to avoid linking a dead block to a live block with a branch. + if branch.block.get().as_ref().iseq.get().is_null() { + return CodegenGlobals::get_stub_exit_code().raw_ptr(cb); + } + (cfp, original_interp_sp) }; @@ -4297,11 +4304,9 @@ pub fn invalidate_block_version(blockref: &BlockRef) { incr_counter!(invalidation_count); } -// We cannot deallocate blocks immediately after invalidation since there -// could be stubs waiting to access branch pointers. Return stubs can do -// this since patching the code for setting up return addresses does not -// affect old return addresses that are already set up to use potentially -// invalidated branch pointers. Example: +// We cannot deallocate blocks immediately after invalidation since patching the code for setting +// up return addresses does not affect outstanding return addresses that are on stack and will use +// invalidated branch pointers when hit. Example: // def foo(n) // if n == 2 // # 1.times.each to create a cfunc frame to preserve the JIT frame @@ -4309,13 +4314,16 @@ pub fn invalidate_block_version(blockref: &BlockRef) { // return 1.times.each { Object.define_method(:foo) {} } // end // -// foo(n + 1) +// foo(n + 1) # The block for this call houses the return branch stub // end // p foo(1) pub fn delayed_deallocation(blockref: BlockRef) { block_assumptions_free(blockref); - let payload = get_iseq_payload(unsafe { blockref.as_ref() }.iseq.get()).unwrap(); + let block = unsafe { blockref.as_ref() }; + // Set null ISEQ on the block to signal that it's dead. + let iseq = block.iseq.replace(ptr::null()); + let payload = get_iseq_payload(iseq).unwrap(); payload.dead_blocks.push(blockref); } From 72055043fc2fb289b0dc3af01c276f06adfc4934 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 2 Oct 2025 10:04:38 -0700 Subject: [PATCH 0077/2435] macos.yml: macOS 13 hosted runner image is closing down https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/ --- .github/workflows/macos.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 70b2bc9d68d109..cab3334774f4e0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -46,8 +46,6 @@ jobs: - test_task: check os: macos-15 extra_checks: [capi] - - test_task: check - os: macos-13 fail-fast: false env: From 272e5b7cc582bb0cc81f29957a17011e1e7367bd Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 2 Oct 2025 10:19:17 -0700 Subject: [PATCH 0078/2435] [DOC] Remove now inaccurate comment about blocking Originally ractor_next_id used a VM_LOCK, but now it is an atomic and won't block. --- ractor.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ractor.c b/ractor.c index c439f25e859e39..43a7f6c140e11d 100644 --- a/ractor.c +++ b/ractor.c @@ -514,7 +514,6 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL rb_ractor_t *r = RACTOR_PTR(rv); ractor_init(r, name, loc); - // can block here r->pub.id = ractor_next_id(); RUBY_DEBUG_LOG("r:%u", r->pub.id); From 7a4f886cc5e7ebf6fece90491cf4a437576d26a6 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Wed, 1 Oct 2025 10:11:40 +0100 Subject: [PATCH 0079/2435] CI: ubuntu.yml Remove workarounds for ppc64le/s390x The 2 issues that we applied the workarounds for were fixed now. --- .github/workflows/ubuntu.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6174e991708704..af849720575652 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -77,16 +77,6 @@ jobs: ${{ !endsWith(matrix.os, 'arm') && !endsWith(matrix.os, 'ppc64le') && !endsWith(matrix.os, 's390x') }} - # A temporary workaround: Set HOME env to pass the step - # ./.github/actions/setup/directories. - # https://github.com/IBM/actionspz/issues/30 - - name: Set HOME env - run: | - echo "HOME: ${HOME}" - echo "HOME=$(ls -d ~)" >> $GITHUB_ENV - working-directory: - if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} - - uses: ./.github/actions/setup/directories with: srcdir: src @@ -142,14 +132,6 @@ jobs: run: echo "DFLTCC=0" >> $GITHUB_ENV if: ${{ endsWith(matrix.os, 's390x') }} - # A temporary workaround: Set the user's primary group to avoid a mismatch - # between the group IDs of "id -g" and C function getpwuid(uid_t uid) - # pw_gid. - # https://github.com/IBM/actionspz/issues/31 - - name: Set user's group id - run: sudo usermod -g "$(id -g)" runner - if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} - - name: make ${{ matrix.test_task }} run: | test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") From 3cd2407045a67838cf2ab949e5164676b6870958 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 2 Oct 2025 12:12:26 -0700 Subject: [PATCH 0080/2435] Don't call gc_mark from IO::buffer compact Previously on our mark_and_move we were calling rb_gc_mark, which isn't safe to call at compaction time. Co-authored-by: Luke Gruber --- gc/default/default.c | 1 + io_buffer.c | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index b5879b0ec25e7c..1288a44b8e1250 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4393,6 +4393,7 @@ static void gc_mark(rb_objspace_t *objspace, VALUE obj) { GC_ASSERT(during_gc); + GC_ASSERT(!objspace->flags.during_reference_updating); rgengc_check_relation(objspace, obj); if (!gc_mark_set(objspace, obj)) return; /* already marked */ diff --git a/io_buffer.c b/io_buffer.c index 96f13c364afc30..87e392b79181d6 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -273,7 +273,7 @@ io_buffer_free(struct rb_io_buffer *buffer) } static void -rb_io_buffer_type_mark_and_move(void *_buffer) +rb_io_buffer_type_mark(void *_buffer) { struct rb_io_buffer *buffer = _buffer; if (buffer->source != Qnil) { @@ -282,7 +282,21 @@ rb_io_buffer_type_mark_and_move(void *_buffer) // which can be otherwise moved by GC compaction. rb_gc_mark(buffer->source); } else { - rb_gc_mark_and_move(&buffer->source); + rb_gc_mark_movable(buffer->source); + } + } +} + +static void +rb_io_buffer_type_compact(void *_buffer) +{ + struct rb_io_buffer *buffer = _buffer; + if (buffer->source != Qnil) { + if (RB_TYPE_P(buffer->source, T_STRING)) { + // The `source` String has to be pinned, because the `base` may point to the embedded String content, + // which can be otherwise moved by GC compaction. + } else { + buffer->source = rb_gc_location(buffer->source); } } } @@ -311,10 +325,10 @@ rb_io_buffer_type_size(const void *_buffer) static const rb_data_type_t rb_io_buffer_type = { .wrap_struct_name = "IO::Buffer", .function = { - .dmark = rb_io_buffer_type_mark_and_move, + .dmark = rb_io_buffer_type_mark, .dfree = rb_io_buffer_type_free, .dsize = rb_io_buffer_type_size, - .dcompact = rb_io_buffer_type_mark_and_move, + .dcompact = rb_io_buffer_type_compact, }, .data = NULL, .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, From 1f0da240495f0626085fc32161d3f7bcabb409d5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 2 Oct 2025 12:15:19 -0700 Subject: [PATCH 0081/2435] ASAN poison parent_object after marking Previously we were tracking down a bug where this was used after being valid. Co-authored-by: Luke Gruber --- gc/default/default.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 1288a44b8e1250..9b40fed09768a6 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4544,6 +4544,28 @@ pin_value(st_data_t key, st_data_t value, st_data_t data) return ST_CONTINUE; } +static inline void +gc_mark_set_parent_raw(rb_objspace_t *objspace, VALUE obj, bool old_p) +{ + asan_unpoison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object), false); + asan_unpoison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p), false); + objspace->rgengc.parent_object = obj; + objspace->rgengc.parent_object_old_p = old_p; +} + +static inline void +gc_mark_set_parent(rb_objspace_t *objspace, VALUE obj) +{ + gc_mark_set_parent_raw(objspace, obj, RVALUE_OLD_P(objspace, obj)); +} + +static inline void +gc_mark_set_parent_invalid(rb_objspace_t *objspace) +{ + asan_poison_memory_region(&objspace->rgengc.parent_object, sizeof(objspace->rgengc.parent_object)); + asan_poison_memory_region(&objspace->rgengc.parent_object_old_p, sizeof(objspace->rgengc.parent_object_old_p)); +} + static void mark_roots(rb_objspace_t *objspace, const char **categoryp) { @@ -4552,8 +4574,7 @@ mark_roots(rb_objspace_t *objspace, const char **categoryp) } while (0) MARK_CHECKPOINT("objspace"); - objspace->rgengc.parent_object = Qundef; - objspace->rgengc.parent_object_old_p = false; + gc_mark_set_parent_raw(objspace, Qundef, false); if (finalizer_table != NULL) { st_foreach(finalizer_table, pin_value, (st_data_t)objspace); @@ -4563,13 +4584,7 @@ mark_roots(rb_objspace_t *objspace, const char **categoryp) rb_gc_save_machine_context(); rb_gc_mark_roots(objspace, categoryp); -} - -static inline void -gc_mark_set_parent(rb_objspace_t *objspace, VALUE obj) -{ - objspace->rgengc.parent_object = obj; - objspace->rgengc.parent_object_old_p = RVALUE_OLD_P(objspace, obj); + gc_mark_set_parent_invalid(objspace); } static void @@ -4577,6 +4592,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) { gc_mark_set_parent(objspace, obj); rb_gc_mark_children(objspace, obj); + gc_mark_set_parent_invalid(objspace); } /** @@ -5986,9 +6002,11 @@ gc_mark_from(rb_objspace_t *objspace, VALUE obj, VALUE parent) { gc_mark_set_parent(objspace, parent); rgengc_check_relation(objspace, obj); - if (gc_mark_set(objspace, obj) == FALSE) return; - gc_aging(objspace, obj); - gc_grey(objspace, obj); + if (gc_mark_set(objspace, obj) != FALSE) { + gc_aging(objspace, obj); + gc_grey(objspace, obj); + } + gc_mark_set_parent_invalid(objspace); } NOINLINE(static void gc_writebarrier_incremental(VALUE a, VALUE b, rb_objspace_t *objspace)); From c36c80bc25b5cbf8d09e5ed79c5261ad4933d653 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Sep 2025 13:30:24 -0400 Subject: [PATCH 0082/2435] Always free the main thread in RUBY_FREE_AT_EXIT --- vm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vm.c b/vm.c index 431db0cb0bc88d..8bdf9e4fc20ae2 100644 --- a/vm.c +++ b/vm.c @@ -3343,10 +3343,8 @@ ruby_vm_destruct(rb_vm_t *vm) rb_objspace_free_objects(objspace); rb_free_generic_fields_tbl_(); rb_free_default_rand_key(); - if (th && vm->fork_gen == 0) { - /* If we have forked, main_thread may not be the initial thread */ - ruby_mimfree(th); - } + + ruby_mimfree(th); } rb_objspace_free(objspace); } From ff8975dfc8732b3530dc04a22cd703abb5da7c37 Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:24:44 -0500 Subject: [PATCH 0083/2435] docs: Fix formatting in windows.md for icon file inclusion --- doc/windows.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/windows.md b/doc/windows.md index 4ea03d0507d2b7..dd2ed273ae1f30 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -285,6 +285,7 @@ Any icon files(`*.ico`) in the build directory, directories specified with _icondirs_ make variable and `win32` directory under the ruby source directory will be included in DLL or executable files, according to their base names. + $(RUBY_INSTALL_NAME).ico or ruby.ico --> $(RUBY_INSTALL_NAME).exe $(RUBYW_INSTALL_NAME).ico or rubyw.ico --> $(RUBYW_INSTALL_NAME).exe the others --> $(RUBY_SO_NAME).dll From b92db45b98f1deaa43efbbef11d5b6f213eeb65e Mon Sep 17 00:00:00 2001 From: MSP-Greg Date: Thu, 2 Oct 2025 18:38:22 -0500 Subject: [PATCH 0084/2435] [DOC] hash.c - fix 3 class doc typos --- hash.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hash.c b/hash.c index 8b1d5fde8b555a..7b523ba23561ef 100644 --- a/hash.c +++ b/hash.c @@ -6912,7 +6912,7 @@ static const rb_data_type_t env_data_type = { * A \Hash object maps each of its unique keys to a specific value. * * A hash has certain similarities to an Array, but: - + * * - An array index is always an integer. * - A hash key can be (almost) any object. * @@ -7313,7 +7313,7 @@ static const rb_data_type_t env_data_type = { * - #keys: Returns an array containing all keys in +self+. * - #rassoc: Returns a 2-element array consisting of the key and value * of the first-found entry having a given value. - * - #values: Returns an array containing all values in +self+/ + * - #values: Returns an array containing all values in +self+. * - #values_at: Returns an array containing values for given keys. * * ==== Methods for Assigning @@ -7362,7 +7362,6 @@ static const rb_data_type_t env_data_type = { * * ==== Methods for Transforming Keys and Values * - * - #flatten!: Returns +self+, flattened. * - #invert: Returns a hash with the each key-value pair inverted. * - #transform_keys: Returns a copy of +self+ with modified keys. * - #transform_keys!: Modifies keys in +self+ From c93b521fac3e92935b64ba8d802c87048a5769d6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 3 Oct 2025 11:46:00 +0900 Subject: [PATCH 0085/2435] Windows: Fallback to powershell if fiddle is not loadable --- tool/lib/memory_status.rb | 100 +++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 40 deletions(-) diff --git a/tool/lib/memory_status.rb b/tool/lib/memory_status.rb index 60632523a88a17..429e5f6a1d3fa9 100644 --- a/tool/lib/memory_status.rb +++ b/tool/lib/memory_status.rb @@ -20,48 +20,68 @@ def self.read_status data.scan(pat) {|k, v| keys << k.downcase.intern} when /mswin|mingw/ =~ RUBY_PLATFORM - require 'fiddle/import' - require 'fiddle/types' - - module Win32 - extend Fiddle::Importer - dlload "kernel32.dll", "psapi.dll" - include Fiddle::Win32Types - typealias "SIZE_T", "size_t" - - PROCESS_MEMORY_COUNTERS = struct [ - "DWORD cb", - "DWORD PageFaultCount", - "SIZE_T PeakWorkingSetSize", - "SIZE_T WorkingSetSize", - "SIZE_T QuotaPeakPagedPoolUsage", - "SIZE_T QuotaPagedPoolUsage", - "SIZE_T QuotaPeakNonPagedPoolUsage", - "SIZE_T QuotaNonPagedPoolUsage", - "SIZE_T PagefileUsage", - "SIZE_T PeakPagefileUsage", - ] - - typealias "PPROCESS_MEMORY_COUNTERS", "PROCESS_MEMORY_COUNTERS*" - - extern "HANDLE GetCurrentProcess()", :stdcall - extern "BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)", :stdcall - - module_function - def memory_info - size = PROCESS_MEMORY_COUNTERS.size - data = PROCESS_MEMORY_COUNTERS.malloc - data.cb = size - data if GetProcessMemoryInfo(GetCurrentProcess(), data, size) + keys.push(:size, :rss, :peak) + + begin + require 'fiddle/import' + require 'fiddle/types' + rescue LoadError + # Fallback to PowerShell command to get memory information for current process + def self.read_status + cmd = [ + "powershell.exe", "-NoProfile", "-Command", + "Get-Process -Id #{$$} | " \ + "% { Write-Output $_.PagedMemorySize64 $_.WorkingSet64 $_.PeakWorkingSet64 }" + ] + + IO.popen(cmd, "r", err: [:child, :out]) do |out| + if /^(\d+)\n(\d+)\n(\d+)$/ =~ out.read + yield :size, $1.to_i + yield :rss, $2.to_i + yield :peak, $3.to_i + end + end + end + else + module Win32 + extend Fiddle::Importer + dlload "kernel32.dll", "psapi.dll" + include Fiddle::Win32Types + typealias "SIZE_T", "size_t" + + PROCESS_MEMORY_COUNTERS = struct [ + "DWORD cb", + "DWORD PageFaultCount", + "SIZE_T PeakWorkingSetSize", + "SIZE_T WorkingSetSize", + "SIZE_T QuotaPeakPagedPoolUsage", + "SIZE_T QuotaPagedPoolUsage", + "SIZE_T QuotaPeakNonPagedPoolUsage", + "SIZE_T QuotaNonPagedPoolUsage", + "SIZE_T PagefileUsage", + "SIZE_T PeakPagefileUsage", + ] + + typealias "PPROCESS_MEMORY_COUNTERS", "PROCESS_MEMORY_COUNTERS*" + + extern "HANDLE GetCurrentProcess()", :stdcall + extern "BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)", :stdcall + + module_function + def memory_info + size = PROCESS_MEMORY_COUNTERS.size + data = PROCESS_MEMORY_COUNTERS.malloc + data.cb = size + data if GetProcessMemoryInfo(GetCurrentProcess(), data, size) + end end - end - keys.push(:size, :rss, :peak) - def self.read_status - if info = Win32.memory_info - yield :size, info.PagefileUsage - yield :rss, info.WorkingSetSize - yield :peak, info.PeakWorkingSetSize + def self.read_status + if info = Win32.memory_info + yield :size, info.PagefileUsage + yield :rss, info.WorkingSetSize + yield :peak, info.PeakWorkingSetSize + end end end when (require_relative 'find_executable' From 5b2ec0eb1be663ff6d3bc12660d48b1e25375353 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 3 Oct 2025 17:59:54 +0900 Subject: [PATCH 0086/2435] Save `ns` that may be clobbered by `setjmp`/`longjmp` --- load.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/load.c b/load.c index c63aa2abbbf087..1928721c9e9cb2 100644 --- a/load.c +++ b/load.c @@ -1289,14 +1289,15 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa { volatile int result = -1; rb_thread_t *th = rb_ec_thread_ptr(ec); + const rb_namespace_t *ns = rb_loading_namespace(); volatile const struct { VALUE wrapper, self, errinfo; rb_execution_context_t *ec; + const rb_namespace_t *ns; } saved = { th->top_wrapper, th->top_self, ec->errinfo, - ec, + ec, ns, }; - const rb_namespace_t *ns = rb_loading_namespace(); enum ruby_tag_type state; char *volatile ftptr = 0; VALUE path; @@ -1365,6 +1366,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa EC_POP_TAG(); ec = saved.ec; + ns = saved.ns; rb_thread_t *th2 = rb_ec_thread_ptr(ec); th2->top_self = saved.self; th2->top_wrapper = saved.wrapper; From 52287c68abd696ee7808fa231873e917d74dae7b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 3 Oct 2025 21:27:03 +0900 Subject: [PATCH 0087/2435] Set Ruby parser if the given ruby accepts `--parser` Now envutil.rb is a part of test-unit-ruby-core gem, which still supports old versions, 2.3 or later. --- tool/lib/envutil.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb index ab5e8d84e9c371..fef9a0c992b28c 100644 --- a/tool/lib/envutil.rb +++ b/tool/lib/envutil.rb @@ -225,7 +225,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = args = [args] if args.kind_of?(String) # use the same parser as current ruby - if args.none? { |arg| arg.start_with?("--parser=") } + if (args.none? { |arg| arg.start_with?("--parser=") } and + /^ +--parser=/ =~ IO.popen([rubybin, "--help"], &:read)) args = ["--parser=#{current_parser}"] + args end pid = spawn(child_env, *precommand, rubybin, *args, opt) From 14cdd88970a2f1ec07bf97a5de50a47f4f6e7e4f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 2 Oct 2025 18:39:24 -0400 Subject: [PATCH 0088/2435] [Bug #21620] Fix strict aliasing in rb_managed_id_table_lookup We cannot pass &ccs into rb_managed_id_table_lookup because rb_managed_id_table_lookup takes in a VALUE*, so it violates strict aliasing in C. This causes segfaults when compiling with LTO enabled. --- vm_insnhelper.c | 15 +++++++++++---- vm_method.c | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index d50b11e377874b..f4f0314ed90ece 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2120,8 +2120,9 @@ vm_evict_cc(VALUE klass, VALUE cc_tbl, ID mid) return; } - struct rb_class_cc_entries *ccs = NULL; - rb_managed_id_table_lookup(cc_tbl, mid, (VALUE *)&ccs); + VALUE ccs_obj = 0; + rb_managed_id_table_lookup(cc_tbl, mid, &ccs_obj); + struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_obj; if (!ccs || !METHOD_ENTRY_INVALIDATED(ccs->cme)) { // Another ractor replaced that entry while we were waiting on the VM lock. @@ -2182,7 +2183,11 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid) if (ccs == NULL) { VM_ASSERT(cc_tbl); - if (!LIKELY(rb_managed_id_table_lookup(cc_tbl, mid, (VALUE *)&ccs))) { + VALUE ccs_obj; + if (UNLIKELY(rb_managed_id_table_lookup(cc_tbl, mid, &ccs_obj))) { + ccs = (struct rb_class_cc_entries *)ccs_obj; + } + else { // TODO: required? ccs = vm_ccs_create(klass, cc_tbl, mid, cme); } @@ -2217,7 +2222,9 @@ vm_lookup_cc(const VALUE klass, const struct rb_callinfo * const ci, ID mid) // CCS data is keyed on method id, so we don't need the method id // for doing comparisons in the `for` loop below. - if (rb_managed_id_table_lookup(cc_tbl, mid, (VALUE *)&ccs)) { + VALUE ccs_obj; + if (rb_managed_id_table_lookup(cc_tbl, mid, &ccs_obj)) { + ccs = (struct rb_class_cc_entries *)ccs_obj; const int ccs_len = ccs->len; if (UNLIKELY(METHOD_ENTRY_INVALIDATED(ccs->cme))) { diff --git a/vm_method.c b/vm_method.c index 16f402e893c8ed..60c273ff2f34fb 100644 --- a/vm_method.c +++ b/vm_method.c @@ -195,8 +195,9 @@ rb_vm_ccs_invalidate_and_free(struct rb_class_cc_entries *ccs) void rb_vm_cc_table_delete(VALUE table, ID mid) { - struct rb_class_cc_entries *ccs; - if (rb_managed_id_table_lookup(table, mid, (VALUE *)&ccs)) { + VALUE ccs_obj; + if (rb_managed_id_table_lookup(table, mid, &ccs_obj)) { + struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_obj; rb_managed_id_table_delete(table, mid); rb_vm_ccs_invalidate_and_free(ccs); } From 8eaa9eb3eb287063f8e004ecf4d12225214cda42 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 2 Oct 2025 17:47:38 -0700 Subject: [PATCH 0089/2435] Clear fields on heap RStruct before allocating Now that we no longer explicitly set the first three elements, we need to ensure the object is in a state safe for GC before we call struct_heap_alloc, which may GC. --- struct.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/struct.c b/struct.c index 2076f5709e304d..9da9bbdfe369c8 100644 --- a/struct.c +++ b/struct.c @@ -834,10 +834,13 @@ struct_alloc(VALUE klass) else { NEWOBJ_OF(st, struct RStruct, klass, flags, sizeof(struct RStruct), 0); + st->as.heap.ptr = NULL; + st->as.heap.fields_obj = 0; + st->as.heap.len = 0; + st->as.heap.ptr = struct_heap_alloc((VALUE)st, n); rb_mem_clear((VALUE *)st->as.heap.ptr, n); st->as.heap.len = n; - st->as.heap.fields_obj = 0; return (VALUE)st; } From bd8e566b328b3308d638883a28f1a54f218e4fd7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 10:56:16 -0700 Subject: [PATCH 0090/2435] ZJIT: Make sure zjit-test-update works in asm tests (#14708) --- zjit/src/asm/arm64/mod.rs | 197 ++++---- zjit/src/asm/mod.rs | 53 +- zjit/src/asm/x86_64/tests.rs | 875 ++++++++++++++++++--------------- zjit/src/backend/arm64/mod.rs | 350 +++++++++---- zjit/src/backend/x86_64/mod.rs | 127 ++--- 5 files changed, 951 insertions(+), 651 deletions(-) diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index 176e51ee973e84..beb5a4b7bd61c7 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -1213,6 +1213,7 @@ fn cbz_cbnz(num_bits: u8, op: bool, offset: InstructionOffset, rt: u8) -> [u8; 4 mod tests { use super::*; use insta::assert_snapshot; + use crate::assert_disasm_snapshot; fn compile(run: R) -> CodeBlock where R: FnOnce(&mut super::CodeBlock) { let mut cb = super::CodeBlock::new_dummy(); @@ -1246,112 +1247,112 @@ mod tests { #[test] fn test_add_reg() { let cb = compile(|cb| add(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"2000028b"); } #[test] fn test_add_uimm() { let cb = compile(|cb| add(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c0091"); } #[test] fn test_add_imm_positive() { let cb = compile(|cb| add(cb, X0, X1, A64Opnd::new_imm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c0091"); } #[test] fn test_add_imm_negative() { let cb = compile(|cb| add(cb, X0, X1, A64Opnd::new_imm(-7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00d1"); } #[test] fn test_adds_reg() { let cb = compile(|cb| adds(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adds x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"200002ab"); } #[test] fn test_adds_uimm() { let cb = compile(|cb| adds(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adds x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00b1"); } #[test] fn test_adds_imm_positive() { let cb = compile(|cb| adds(cb, X0, X1, A64Opnd::new_imm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adds x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00b1"); } #[test] fn test_adds_imm_negative() { let cb = compile(|cb| adds(cb, X0, X1, A64Opnd::new_imm(-7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: subs x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: subs x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00f1"); } #[test] fn test_adr() { let cb = compile(|cb| adr(cb, X10, A64Opnd::new_imm(20))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adr x10, #0x14")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adr x10, #0x14"); assert_snapshot!(cb.hexdump(), @"aa000010"); } #[test] fn test_adrp() { let cb = compile(|cb| adrp(cb, X10, A64Opnd::new_imm(0x8000))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adrp x10, #0x8000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adrp x10, #0x8000"); assert_snapshot!(cb.hexdump(), @"4a000090"); } #[test] fn test_and_register() { let cb = compile(|cb| and(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: and x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"2000028a"); } #[test] fn test_and_immediate() { let cb = compile(|cb| and(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: and x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"20084092"); } #[test] fn test_and_32b_immediate() { let cb = compile(|cb| and(cb, W0, W2, A64Opnd::new_uimm(0xfffff))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and w0, w2, #0xfffff")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: and w0, w2, #0xfffff"); assert_snapshot!(cb.hexdump(), @"404c0012"); } #[test] fn test_ands_register() { let cb = compile(|cb| ands(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ands x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ands x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"200002ea"); } #[test] fn test_ands_immediate() { let cb = compile(|cb| ands(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ands x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ands x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"200840f2"); } #[test] fn test_asr() { let cb = compile(|cb| asr(cb, X20, X21, A64Opnd::new_uimm(10))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: asr x20, x21, #0xa")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: asr x20, x21, #0xa"); assert_snapshot!(cb.hexdump(), @"b4fe4a93"); } @@ -1359,7 +1360,7 @@ mod tests { fn test_bcond() { let offset = InstructionOffset::from_insns(0x100); let cb = compile(|cb| bcond(cb, Condition::NE, offset)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: b.ne #0x400")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: b.ne #0x400"); assert_snapshot!(cb.hexdump(), @"01200054"); } @@ -1367,7 +1368,7 @@ mod tests { fn test_b() { let offset = InstructionOffset::from_insns((1 << 25) - 1); let cb = compile(|cb| b(cb, offset)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: b #0x7fffffc")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: b #0x7fffffc"); assert_snapshot!(cb.hexdump(), @"ffffff15"); } @@ -1391,7 +1392,7 @@ mod tests { fn test_bl() { let offset = InstructionOffset::from_insns(-(1 << 25)); let cb = compile(|cb| bl(cb, offset)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: bl #0xfffffffff8000000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: bl #0xfffffffff8000000"); assert_snapshot!(cb.hexdump(), @"00000096"); } @@ -1414,14 +1415,14 @@ mod tests { #[test] fn test_blr() { let cb = compile(|cb| blr(cb, X20)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: blr x20")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: blr x20"); assert_snapshot!(cb.hexdump(), @"80023fd6"); } #[test] fn test_br() { let cb = compile(|cb| br(cb, X20)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: br x20")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: br x20"); assert_snapshot!(cb.hexdump(), @"80021fd6"); } @@ -1432,10 +1433,10 @@ mod tests { cbz(cb, X0, offset); cbz(cb, W0, offset); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: cbz x0, #0xfffffffffffffffc 0x4: cbz w0, #0 - ")); + "); assert_snapshot!(cb.hexdump(), @"e0ffffb4e0ffff34"); } @@ -1446,150 +1447,150 @@ mod tests { cbnz(cb, X20, offset); cbnz(cb, W20, offset); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: cbnz x20, #8 0x4: cbnz w20, #0xc - ")); + "); assert_snapshot!(cb.hexdump(), @"540000b554000035"); } #[test] fn test_brk_none() { let cb = compile(|cb| brk(cb, A64Opnd::None)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: brk #0xf000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: brk #0xf000"); assert_snapshot!(cb.hexdump(), @"00003ed4"); } #[test] fn test_brk_uimm() { let cb = compile(|cb| brk(cb, A64Opnd::new_uimm(14))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: brk #0xe")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: brk #0xe"); assert_snapshot!(cb.hexdump(), @"c00120d4"); } #[test] fn test_cmp_register() { let cb = compile(|cb| cmp(cb, X10, X11)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp x10, x11")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp x10, x11"); assert_snapshot!(cb.hexdump(), @"5f010beb"); } #[test] fn test_cmp_immediate() { let cb = compile(|cb| cmp(cb, X10, A64Opnd::new_uimm(14))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp x10, #0xe")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp x10, #0xe"); assert_snapshot!(cb.hexdump(), @"5f3900f1"); } #[test] fn test_csel() { let cb = compile(|cb| csel(cb, X10, X11, X12, Condition::EQ)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: csel x10, x11, x12, eq")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: csel x10, x11, x12, eq"); assert_snapshot!(cb.hexdump(), @"6a018c9a"); } #[test] fn test_eor_register() { let cb = compile(|cb| eor(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: eor x10, x11, x12")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: eor x10, x11, x12"); assert_snapshot!(cb.hexdump(), @"6a010cca"); } #[test] fn test_eor_immediate() { let cb = compile(|cb| eor(cb, X10, X11, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: eor x10, x11, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: eor x10, x11, #7"); assert_snapshot!(cb.hexdump(), @"6a0940d2"); } #[test] fn test_eor_32b_immediate() { let cb = compile(|cb| eor(cb, W9, W1, A64Opnd::new_uimm(0x80000001))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: eor w9, w1, #0x80000001")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: eor w9, w1, #0x80000001"); assert_snapshot!(cb.hexdump(), @"29040152"); } #[test] fn test_ldaddal() { let cb = compile(|cb| ldaddal(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldaddal x10, x11, [x12]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldaddal x10, x11, [x12]"); assert_snapshot!(cb.hexdump(), @"8b01eaf8"); } #[test] fn test_ldaxr() { let cb = compile(|cb| ldaxr(cb, X10, X11)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldaxr x10, [x11]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldaxr x10, [x11]"); assert_snapshot!(cb.hexdump(), @"6afd5fc8"); } #[test] fn test_ldp() { let cb = compile(|cb| ldp(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldp x10, x11, [x12, #0xd0]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldp x10, x11, [x12, #0xd0]"); assert_snapshot!(cb.hexdump(), @"8a2d4da9"); } #[test] fn test_ldp_pre() { let cb = compile(|cb| ldp_pre(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldp x10, x11, [x12, #0xd0]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldp x10, x11, [x12, #0xd0]!"); assert_snapshot!(cb.hexdump(), @"8a2dcda9"); } #[test] fn test_ldp_post() { let cb = compile(|cb| ldp_post(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldp x10, x11, [x12], #0xd0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldp x10, x11, [x12], #0xd0"); assert_snapshot!(cb.hexdump(), @"8a2dcda8"); } #[test] fn test_ldr() { let cb = compile(|cb| ldr(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldr x10, [x11, x12]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x10, [x11, x12]"); assert_snapshot!(cb.hexdump(), @"6a696cf8"); } #[test] fn test_ldr_literal() { let cb = compile(|cb| ldr_literal(cb, X0, 10.into())); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldr x0, #0x28")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x0, #0x28"); assert_snapshot!(cb.hexdump(), @"40010058"); } #[test] fn test_ldr_post() { let cb = compile(|cb| ldr_post(cb, X10, A64Opnd::new_mem(64, X11, 16))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldr x10, [x11], #0x10")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x10, [x11], #0x10"); assert_snapshot!(cb.hexdump(), @"6a0541f8"); } #[test] fn test_ldr_pre() { let cb = compile(|cb| ldr_pre(cb, X10, A64Opnd::new_mem(64, X11, 16))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldr x10, [x11, #0x10]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x10, [x11, #0x10]!"); assert_snapshot!(cb.hexdump(), @"6a0d41f8"); } #[test] fn test_ldrh() { let cb = compile(|cb| ldrh(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldrh w10, [x11, #0xc]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldrh w10, [x11, #0xc]"); assert_snapshot!(cb.hexdump(), @"6a194079"); } #[test] fn test_ldrh_pre() { let cb = compile(|cb| ldrh_pre(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldrh w10, [x11, #0xc]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldrh w10, [x11, #0xc]!"); assert_snapshot!(cb.hexdump(), @"6acd4078"); } #[test] fn test_ldrh_post() { let cb = compile(|cb| ldrh_post(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldrh w10, [x11], #0xc")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldrh w10, [x11], #0xc"); assert_snapshot!(cb.hexdump(), @"6ac54078"); } @@ -1599,352 +1600,352 @@ mod tests { ldurh(cb, W10, A64Opnd::new_mem(64, X1, 0)); ldurh(cb, W10, A64Opnd::new_mem(64, X1, 123)); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: ldurh w10, [x1] 0x4: ldurh w10, [x1, #0x7b] - ")); + "); assert_snapshot!(cb.hexdump(), @"2a0040782ab04778"); } #[test] fn test_ldur_memory() { let cb = compile(|cb| ldur(cb, X0, A64Opnd::new_mem(64, X1, 123))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldur x0, [x1, #0x7b]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [x1, #0x7b]"); assert_snapshot!(cb.hexdump(), @"20b047f8"); } #[test] fn test_ldur_register() { let cb = compile(|cb| ldur(cb, X0, X1)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldur x0, [x1]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [x1]"); assert_snapshot!(cb.hexdump(), @"200040f8"); } #[test] fn test_ldursw() { let cb = compile(|cb| ldursw(cb, X10, A64Opnd::new_mem(64, X11, 123))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldursw x10, [x11, #0x7b]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldursw x10, [x11, #0x7b]"); assert_snapshot!(cb.hexdump(), @"6ab187b8"); } #[test] fn test_lsl() { let cb = compile(|cb| lsl(cb, X10, X11, A64Opnd::new_uimm(14))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lsl x10, x11, #0xe")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: lsl x10, x11, #0xe"); assert_snapshot!(cb.hexdump(), @"6ac572d3"); } #[test] fn test_lsr() { let cb = compile(|cb| lsr(cb, X10, X11, A64Opnd::new_uimm(14))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lsr x10, x11, #0xe")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: lsr x10, x11, #0xe"); assert_snapshot!(cb.hexdump(), @"6afd4ed3"); } #[test] fn test_mov_registers() { let cb = compile(|cb| mov(cb, X10, X11)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov x10, x11")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x10, x11"); assert_snapshot!(cb.hexdump(), @"ea030baa"); } #[test] fn test_mov_immediate() { let cb = compile(|cb| mov(cb, X10, A64Opnd::new_uimm(0x5555555555555555))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: orr x10, xzr, #0x5555555555555555")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr x10, xzr, #0x5555555555555555"); assert_snapshot!(cb.hexdump(), @"eaf300b2"); } #[test] fn test_mov_32b_immediate() { let cb = compile(|cb| mov(cb, W10, A64Opnd::new_uimm(0x80000001))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov w10, #-0x7fffffff")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov w10, #-0x7fffffff"); assert_snapshot!(cb.hexdump(), @"ea070132"); } #[test] fn test_mov_into_sp() { let cb = compile(|cb| mov(cb, X31, X0)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov sp, x0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov sp, x0"); assert_snapshot!(cb.hexdump(), @"1f000091"); } #[test] fn test_mov_from_sp() { let cb = compile(|cb| mov(cb, X0, X31)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov x0, sp")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, sp"); assert_snapshot!(cb.hexdump(), @"e0030091"); } #[test] fn test_movk() { let cb = compile(|cb| movk(cb, X0, A64Opnd::new_uimm(123), 16)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movk x0, #0x7b, lsl #16")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movk x0, #0x7b, lsl #16"); assert_snapshot!(cb.hexdump(), @"600fa0f2"); } #[test] fn test_movn() { let cb = compile(|cb| movn(cb, X0, A64Opnd::new_uimm(123), 16)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov x0, #-0x7b0001")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #-0x7b0001"); assert_snapshot!(cb.hexdump(), @"600fa092"); } #[test] fn test_movz() { let cb = compile(|cb| movz(cb, X0, A64Opnd::new_uimm(123), 16)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov x0, #0x7b0000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #0x7b0000"); assert_snapshot!(cb.hexdump(), @"600fa0d2"); } #[test] fn test_mrs() { let cb = compile(|cb| mrs(cb, X10, SystemRegister::NZCV)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mrs x10, nzcv")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mrs x10, nzcv"); assert_snapshot!(cb.hexdump(), @"0a423bd5"); } #[test] fn test_msr() { let cb = compile(|cb| msr(cb, SystemRegister::NZCV, X10)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: msr nzcv, x10")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: msr nzcv, x10"); assert_snapshot!(cb.hexdump(), @"0a421bd5"); } #[test] fn test_mul() { let cb = compile(|cb| mul(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mul x10, x11, x12")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mul x10, x11, x12"); assert_snapshot!(cb.hexdump(), @"6a7d0c9b"); } #[test] fn test_mvn() { let cb = compile(|cb| mvn(cb, X10, X11)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mvn x10, x11")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mvn x10, x11"); assert_snapshot!(cb.hexdump(), @"ea032baa"); } #[test] fn test_nop() { let cb = compile(nop); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: nop"); assert_snapshot!(cb.hexdump(), @"1f2003d5"); } #[test] fn test_orn() { let cb = compile(|cb| orn(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: orn x10, x11, x12")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orn x10, x11, x12"); assert_snapshot!(cb.hexdump(), @"6a012caa"); } #[test] fn test_orr_register() { let cb = compile(|cb| orr(cb, X10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: orr x10, x11, x12")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr x10, x11, x12"); assert_snapshot!(cb.hexdump(), @"6a010caa"); } #[test] fn test_orr_immediate() { let cb = compile(|cb| orr(cb, X10, X11, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: orr x10, x11, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr x10, x11, #7"); assert_snapshot!(cb.hexdump(), @"6a0940b2"); } #[test] fn test_orr_32b_immediate() { let cb = compile(|cb| orr(cb, W10, W11, A64Opnd::new_uimm(1))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: orr w10, w11, #1")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr w10, w11, #1"); assert_snapshot!(cb.hexdump(), @"6a010032"); } #[test] fn test_ret_none() { let cb = compile(|cb| ret(cb, A64Opnd::None)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ret")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ret"); assert_snapshot!(cb.hexdump(), @"c0035fd6"); } #[test] fn test_ret_register() { let cb = compile(|cb| ret(cb, X20)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ret x20")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ret x20"); assert_snapshot!(cb.hexdump(), @"80025fd6"); } #[test] fn test_stlxr() { let cb = compile(|cb| stlxr(cb, W10, X11, X12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stlxr w10, x11, [x12]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stlxr w10, x11, [x12]"); assert_snapshot!(cb.hexdump(), @"8bfd0ac8"); } #[test] fn test_stp() { let cb = compile(|cb| stp(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stp x10, x11, [x12, #0xd0]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stp x10, x11, [x12, #0xd0]"); assert_snapshot!(cb.hexdump(), @"8a2d0da9"); } #[test] fn test_stp_pre() { let cb = compile(|cb| stp_pre(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stp x10, x11, [x12, #0xd0]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stp x10, x11, [x12, #0xd0]!"); assert_snapshot!(cb.hexdump(), @"8a2d8da9"); } #[test] fn test_stp_post() { let cb = compile(|cb| stp_post(cb, X10, X11, A64Opnd::new_mem(64, X12, 208))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stp x10, x11, [x12], #0xd0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stp x10, x11, [x12], #0xd0"); assert_snapshot!(cb.hexdump(), @"8a2d8da8"); } #[test] fn test_str_post() { let cb = compile(|cb| str_post(cb, X10, A64Opnd::new_mem(64, X11, -16))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: str x10, [x11], #0xfffffffffffffff0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: str x10, [x11], #0xfffffffffffffff0"); assert_snapshot!(cb.hexdump(), @"6a051ff8"); } #[test] fn test_str_pre() { let cb = compile(|cb| str_pre(cb, X10, A64Opnd::new_mem(64, X11, -16))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: str x10, [x11, #-0x10]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: str x10, [x11, #-0x10]!"); assert_snapshot!(cb.hexdump(), @"6a0d1ff8"); } #[test] fn test_strh() { let cb = compile(|cb| strh(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: strh w10, [x11, #0xc]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: strh w10, [x11, #0xc]"); assert_snapshot!(cb.hexdump(), @"6a190079"); } #[test] fn test_strh_pre() { let cb = compile(|cb| strh_pre(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: strh w10, [x11, #0xc]!")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: strh w10, [x11, #0xc]!"); assert_snapshot!(cb.hexdump(), @"6acd0078"); } #[test] fn test_strh_post() { let cb = compile(|cb| strh_post(cb, W10, A64Opnd::new_mem(64, X11, 12))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: strh w10, [x11], #0xc")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: strh w10, [x11], #0xc"); assert_snapshot!(cb.hexdump(), @"6ac50078"); } #[test] fn test_stur_64_bits() { let cb = compile(|cb| stur(cb, X10, A64Opnd::new_mem(64, X11, 128))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stur x10, [x11, #0x80]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stur x10, [x11, #0x80]"); assert_snapshot!(cb.hexdump(), @"6a0108f8"); } #[test] fn test_stur_32_bits() { let cb = compile(|cb| stur(cb, X10, A64Opnd::new_mem(32, X11, 128))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: stur w10, [x11, #0x80]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stur w10, [x11, #0x80]"); assert_snapshot!(cb.hexdump(), @"6a0108b8"); } #[test] fn test_sub_reg() { let cb = compile(|cb| sub(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"200002cb"); } #[test] fn test_sub_uimm() { let cb = compile(|cb| sub(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00d1"); } #[test] fn test_sub_imm_positive() { let cb = compile(|cb| sub(cb, X0, X1, A64Opnd::new_imm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00d1"); } #[test] fn test_sub_imm_negative() { let cb = compile(|cb| sub(cb, X0, X1, A64Opnd::new_imm(-7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c0091"); } #[test] fn test_subs_reg() { let cb = compile(|cb| subs(cb, X0, X1, X2)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: subs x0, x1, x2")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: subs x0, x1, x2"); assert_snapshot!(cb.hexdump(), @"200002eb"); } #[test] fn test_subs_imm_positive() { let cb = compile(|cb| subs(cb, X0, X1, A64Opnd::new_imm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: subs x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: subs x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00f1"); } #[test] fn test_subs_imm_negative() { let cb = compile(|cb| subs(cb, X0, X1, A64Opnd::new_imm(-7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: adds x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00b1"); } #[test] fn test_subs_uimm() { let cb = compile(|cb| subs(cb, X0, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: subs x0, x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: subs x0, x1, #7"); assert_snapshot!(cb.hexdump(), @"201c00f1"); } #[test] fn test_sxtw() { let cb = compile(|cb| sxtw(cb, X10, W11)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sxtw x10, w11")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sxtw x10, w11"); assert_snapshot!(cb.hexdump(), @"6a7d4093"); } #[test] fn test_tbnz() { let cb = compile(|cb| tbnz(cb, X10, A64Opnd::UImm(10), A64Opnd::Imm(2))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: tbnz w10, #0xa, #8")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tbnz w10, #0xa, #8"); assert_snapshot!(cb.hexdump(), @"4a005037"); } #[test] fn test_tbz() { let cb = compile(|cb| tbz(cb, X10, A64Opnd::UImm(10), A64Opnd::Imm(2))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: tbz w10, #0xa, #8")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tbz w10, #0xa, #8"); assert_snapshot!(cb.hexdump(), @"4a005036"); } #[test] fn test_tst_register() { let cb = compile(|cb| tst(cb, X0, X1)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: tst x0, x1")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x0, x1"); assert_snapshot!(cb.hexdump(), @"1f0001ea"); } #[test] fn test_tst_immediate() { let cb = compile(|cb| tst(cb, X1, A64Opnd::new_uimm(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: tst x1, #7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x1, #7"); assert_snapshot!(cb.hexdump(), @"3f0840f2"); } #[test] fn test_tst_32b_immediate() { let cb = compile(|cb| tst(cb, W0, A64Opnd::new_uimm(0xffff))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: tst w0, #0xffff")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst w0, #0xffff"); assert_snapshot!(cb.hexdump(), @"1f3c0072"); } @@ -1956,11 +1957,11 @@ mod tests { add_extended(&mut cb, X30, X30, X30); add_extended(&mut cb, X31, X31, X31); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x10, x11, x9, uxtx 0x4: add x30, x30, x30, uxtx 0x8: add sp, sp, xzr - ")); + "); assert_snapshot!(cb.hexdump(), @"6a61298bde633e8bff633f8b"); } } diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index 95202ad7292fa7..aeb429382d88c0 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -284,14 +284,11 @@ impl CodeBlock { /// Call a func with the disasm of generated code for testing #[allow(unused_variables)] - #[cfg(test)] - pub fn with_disasm(&self, func: T) where T: Fn(String) { - #[cfg(feature = "disasm")] - { - let start_addr = self.get_ptr(0).raw_addr(self); - let end_addr = self.get_write_ptr().raw_addr(self); - func(crate::disasm::disasm_addr_range(self, start_addr, end_addr)); - } + #[cfg(all(test, feature = "disasm"))] + pub fn disasm(&self) -> String { + let start_addr = self.get_ptr(0).raw_addr(self); + let end_addr = self.get_write_ptr().raw_addr(self); + crate::disasm::disasm_addr_range(self, start_addr, end_addr) } /// Return the hex dump of generated code for testing @@ -301,6 +298,46 @@ impl CodeBlock { } } +/// Run assert_snapshot! only if cfg!(feature = "disasm"). +/// $actual can be not only `cb.disasm()` but also `disasms!(cb1, cb2, ...)`. +#[cfg(test)] +#[macro_export] +macro_rules! assert_disasm_snapshot { + ($actual: expr, @$($tt: tt)*) => {{ + #[cfg(feature = "disasm")] + assert_snapshot!($actual, @$($tt)*) + }}; +} + +/// Combine multiple cb.disasm() results to match all of them at once, which allows +/// us to avoid running the set of zjit-test -> zjit-test-update multiple times. +#[cfg(all(test, feature = "disasm"))] +#[macro_export] +macro_rules! disasms { + ($( $cb:expr ),+ $(,)?) => {{ + crate::disasms_with!("", $( $cb ),+) + }}; +} + +/// Basically `disasms!` but allows a non-"" delimiter, such as "\n" +#[cfg(all(test, feature = "disasm"))] +#[macro_export] +macro_rules! disasms_with { + ($join:expr, $( $cb:expr ),+ $(,)?) => {{ + vec![$( $cb.disasm() ),+].join($join) + }}; +} + +/// Combine multiple cb.hexdump() results to match all of them at once, which allows +/// us to avoid running the set of zjit-test -> zjit-test-update multiple times. +#[cfg(test)] +#[macro_export] +macro_rules! hexdumps { + ($( $cb:expr ),+ $(,)?) => {{ + vec![$( $cb.hexdump() ),+].join("\n") + }}; +} + /// Produce hex string output from the bytes in a code block impl fmt::LowerHex for CodeBlock { fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs index a095d73f89faa1..0f867259466de9 100644 --- a/zjit/src/asm/x86_64/tests.rs +++ b/zjit/src/asm/x86_64/tests.rs @@ -2,7 +2,9 @@ use insta::assert_snapshot; -use crate::asm::x86_64::*; +#[cfg(feature = "disasm")] +use crate::disasms; +use crate::{asm::x86_64::*, hexdumps, assert_disasm_snapshot}; /// Check that the bytes for an instruction sequence match a hex string fn check_bytes(bytes: &str, run: R) where R: FnOnce(&mut super::CodeBlock) { @@ -36,39 +38,43 @@ fn test_add() { let cb15 = compile(|cb| add(cb, ECX, imm_opnd(8))); let cb16 = compile(|cb| add(cb, ECX, imm_opnd(255))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add cl, 3")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add cl, bl")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add cl, spl")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add cx, bx")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rax, rbx")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add ecx, edx")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rdx, r14")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add qword ptr [rax], rdx")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rdx, qword ptr [rax]")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rdx, qword ptr [rax + 8]")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rdx, qword ptr [rax + 0xff]")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add qword ptr [rax + 0x7f], 0xff")); - cb13.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add dword ptr [rax], edx")); - cb14.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add rsp, 8")); - cb15.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add ecx, 8")); - cb16.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add ecx, 0xff")); - - assert_snapshot!(cb01.hexdump(), @"80c103"); - assert_snapshot!(cb02.hexdump(), @"00d9"); - assert_snapshot!(cb03.hexdump(), @"4000e1"); - assert_snapshot!(cb04.hexdump(), @"6601d9"); - assert_snapshot!(cb05.hexdump(), @"4801d8"); - assert_snapshot!(cb06.hexdump(), @"01d1"); - assert_snapshot!(cb07.hexdump(), @"4c01f2"); - assert_snapshot!(cb08.hexdump(), @"480110"); - assert_snapshot!(cb09.hexdump(), @"480310"); - assert_snapshot!(cb10.hexdump(), @"48035008"); - assert_snapshot!(cb11.hexdump(), @"480390ff000000"); - assert_snapshot!(cb12.hexdump(), @"4881407fff000000"); - assert_snapshot!(cb13.hexdump(), @"0110"); - assert_snapshot!(cb14.hexdump(), @"4883c408"); - assert_snapshot!(cb15.hexdump(), @"83c108"); - assert_snapshot!(cb16.hexdump(), @"81c1ff000000"); + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @r" + 0x0: add cl, 3 + 0x0: add cl, bl + 0x0: add cl, spl + 0x0: add cx, bx + 0x0: add rax, rbx + 0x0: add ecx, edx + 0x0: add rdx, r14 + 0x0: add qword ptr [rax], rdx + 0x0: add rdx, qword ptr [rax] + 0x0: add rdx, qword ptr [rax + 8] + 0x0: add rdx, qword ptr [rax + 0xff] + 0x0: add qword ptr [rax + 0x7f], 0xff + 0x0: add dword ptr [rax], edx + 0x0: add rsp, 8 + 0x0: add ecx, 8 + 0x0: add ecx, 0xff + "); + + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @r" + 80c103 + 00d9 + 4000e1 + 6601d9 + 4801d8 + 01d1 + 4c01f2 + 480110 + 480310 + 48035008 + 480390ff000000 + 4881407fff000000 + 0110 + 4883c408 + 83c108 + 81c1ff000000 + "); } #[test] @@ -86,23 +92,27 @@ fn test_add_unsigned() { let cb7 = compile(|cb| add(cb, R8, uimm_opnd(1))); let cb8 = compile(|cb| add(cb, R8, uimm_opnd(i32::MAX.try_into().unwrap()))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8b, 1")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8b, 0x7f")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8w, 1")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8w, 0x7fff")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8d, 1")); - cb6.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8d, 0x7fffffff")); - cb7.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8, 1")); - cb8.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r8, 0x7fffffff")); - - assert_snapshot!(cb1.hexdump(), @"4180c001"); - assert_snapshot!(cb2.hexdump(), @"4180c07f"); - assert_snapshot!(cb3.hexdump(), @"664183c001"); - assert_snapshot!(cb4.hexdump(), @"664181c0ff7f"); - assert_snapshot!(cb5.hexdump(), @"4183c001"); - assert_snapshot!(cb6.hexdump(), @"4181c0ffffff7f"); - assert_snapshot!(cb7.hexdump(), @"4983c001"); - assert_snapshot!(cb8.hexdump(), @"4981c0ffffff7f"); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 0x0: add r8b, 1 + 0x0: add r8b, 0x7f + 0x0: add r8w, 1 + 0x0: add r8w, 0x7fff + 0x0: add r8d, 1 + 0x0: add r8d, 0x7fffffff + 0x0: add r8, 1 + 0x0: add r8, 0x7fffffff + "); + + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 4180c001 + 4180c07f + 664183c001 + 664181c0ff7f + 4183c001 + 4181c0ffffff7f + 4983c001 + 4981c0ffffff7f + "); } #[test] @@ -110,11 +120,15 @@ fn test_and() { let cb1 = compile(|cb| and(cb, EBP, R12D)); let cb2 = compile(|cb| and(cb, mem_opnd(64, RAX, 0), imm_opnd(0x08))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and ebp, r12d")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and qword ptr [rax], 8")); + assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + 0x0: and ebp, r12d + 0x0: and qword ptr [rax], 8 + "); - assert_snapshot!(cb1.hexdump(), @"4421e5"); - assert_snapshot!(cb2.hexdump(), @"48832008"); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + 4421e5 + 48832008 + "); } #[test] @@ -124,7 +138,7 @@ fn test_call_label() { call_label(cb, label_idx); cb.link_labels(); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: call 0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: call 0"); assert_snapshot!(cb.hexdump(), @"e8fbffffff"); } @@ -135,21 +149,21 @@ fn test_call_ptr() { let ptr = cb.get_write_ptr(); call_ptr(cb, RAX, ptr.raw_ptr(cb)); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: call 0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: call 0"); assert_snapshot!(cb.hexdump(), @"e8fbffffff"); } #[test] fn test_call_reg() { let cb = compile(|cb| call(cb, RAX)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: call rax")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: call rax"); assert_snapshot!(cb.hexdump(), @"ffd0"); } #[test] fn test_call_mem() { let cb = compile(|cb| call(cb, mem_opnd(64, RSP, 8))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: call qword ptr [rsp + 8]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: call qword ptr [rsp + 8]"); assert_snapshot!(cb.hexdump(), @"ff542408"); } @@ -161,17 +175,21 @@ fn test_cmovcc() { let cb4 = compile(|cb| cmovl(cb, RBX, RBP)); let cb5 = compile(|cb| cmovle(cb, ESI, mem_opnd(32, RSP, 4))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmovg esi, edi")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmovg esi, dword ptr [rbp + 0xc]")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmovl eax, ecx")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmovl rbx, rbp")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmovle esi, dword ptr [rsp + 4]")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + 0x0: cmovg esi, edi + 0x0: cmovg esi, dword ptr [rbp + 0xc] + 0x0: cmovl eax, ecx + 0x0: cmovl rbx, rbp + 0x0: cmovle esi, dword ptr [rsp + 4] + "); - assert_snapshot!(cb1.hexdump(), @"0f4ff7"); - assert_snapshot!(cb2.hexdump(), @"0f4f750c"); - assert_snapshot!(cb3.hexdump(), @"0f4cc1"); - assert_snapshot!(cb4.hexdump(), @"480f4cdd"); - assert_snapshot!(cb5.hexdump(), @"0f4e742404"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + 0f4ff7 + 0f4f750c + 0f4cc1 + 480f4cdd + 0f4e742404 + "); } #[test] @@ -182,23 +200,27 @@ fn test_cmp() { let cb4 = compile(|cb| cmp(cb, RAX, imm_opnd(2))); let cb5 = compile(|cb| cmp(cb, ECX, uimm_opnd(0x8000_0000))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp cl, dl")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp ecx, edi")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp rdx, qword ptr [r12]")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp rax, 2")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp ecx, 0x80000000")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + 0x0: cmp cl, dl + 0x0: cmp ecx, edi + 0x0: cmp rdx, qword ptr [r12] + 0x0: cmp rax, 2 + 0x0: cmp ecx, 0x80000000 + "); - assert_snapshot!(cb1.hexdump(), @"38d1"); - assert_snapshot!(cb2.hexdump(), @"39f9"); - assert_snapshot!(cb3.hexdump(), @"493b1424"); - assert_snapshot!(cb4.hexdump(), @"4883f802"); - assert_snapshot!(cb5.hexdump(), @"81f900000080"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + 38d1 + 39f9 + 493b1424 + 4883f802 + 81f900000080 + "); } #[test] fn test_cqo() { let cb = compile(cqo); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cqo")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cqo"); assert_snapshot!(cb.hexdump(), @"4899"); } @@ -209,13 +231,17 @@ fn test_imul() { // Operands flipped for encoding since multiplication is commutative let cb3 = compile(|cb| imul(cb, mem_opnd(64, RAX, 0), RDX)); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: imul rax, rbx")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: imul rdx, qword ptr [rax]")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: imul rdx, qword ptr [rax]")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3), @r" + 0x0: imul rax, rbx + 0x0: imul rdx, qword ptr [rax] + 0x0: imul rdx, qword ptr [rax] + "); - assert_snapshot!(cb1.hexdump(), @"480fafc3"); - assert_snapshot!(cb2.hexdump(), @"480faf10"); - assert_snapshot!(cb3.hexdump(), @"480faf10"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3), @r" + 480fafc3 + 480faf10 + 480faf10 + "); } #[test] @@ -225,7 +251,7 @@ fn test_jge_label() { jge_label(cb, label_idx); cb.link_labels(); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: jge 0")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: jge 0"); assert_snapshot!(cb.hexdump(), @"0f8dfaffffff"); } @@ -246,17 +272,21 @@ fn test_jmp_label() { cb.link_labels(); }); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: jmp 5")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: jmp 0")); + assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + 0x0: jmp 5 + 0x0: jmp 0 + "); - assert_snapshot!(cb1.hexdump(), @"e900000000"); - assert_snapshot!(cb2.hexdump(), @"e9fbffffff"); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + e900000000 + e9fbffffff + "); } #[test] fn test_jmp_rm() { let cb = compile(|cb| jmp_rm(cb, R12)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: jmp r12")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: jmp r12"); assert_snapshot!(cb.hexdump(), @"41ffe4"); } @@ -267,7 +297,8 @@ fn test_jo_label() { jo_label(cb, label_idx); cb.link_labels(); }); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: jo 0")); + + assert_disasm_snapshot!(cb.disasm(), @" 0x0: jo 0"); assert_snapshot!(cb.hexdump(), @"0f80faffffff"); } @@ -278,15 +309,19 @@ fn test_lea() { let cb3 = compile(|cb| lea(cb, RAX, mem_opnd(8, RIP, 5))); let cb4 = compile(|cb| lea(cb, RDI, mem_opnd(8, RIP, 5))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lea rdx, [rcx + 8]")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lea rax, [rip]")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lea rax, [rip + 5]")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lea rdi, [rip + 5]")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @r" + 0x0: lea rdx, [rcx + 8] + 0x0: lea rax, [rip] + 0x0: lea rax, [rip + 5] + 0x0: lea rdi, [rip + 5] + "); - assert_snapshot!(cb1.hexdump(), @"488d5108"); - assert_snapshot!(cb2.hexdump(), @"488d0500000000"); - assert_snapshot!(cb3.hexdump(), @"488d0505000000"); - assert_snapshot!(cb4.hexdump(), @"488d3d05000000"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @r" + 488d5108 + 488d0500000000 + 488d0505000000 + 488d3d05000000 + "); } #[test] @@ -320,59 +355,69 @@ fn test_mov() { let cb25 = compile(|cb| mov(cb, mem_opnd(64, R11, 0), R10)); let cb26 = compile(|cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 7")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 0xfffffffd")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r15d, 3")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, ebx")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, ecx")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov edx, dword ptr [rbx + 0x80]")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rax, qword ptr [rsp + 4]")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 0x34")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0x80000000")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0xffffffffffffffff")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 0x34")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0xffc0000000000002")); - cb13.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0x80000000")); - cb14.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0xffffffffffffffcc")); - cb15.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0xffffffffffffffff")); - cb16.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov cl, r9b")); - cb17.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rbx, rax")); - cb18.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rdi, rbx")); - cb19.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov sil, 0xb")); - cb20.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov byte ptr [rsp], 0xfd")); - cb21.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov qword ptr [rdi + 8], 1")); - cb22.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov dword ptr [rax + 4], 0x11")); - cb23.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov dword ptr [rax + 4], 0x80000001")); - cb24.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov dword ptr [r8 + 0x14], ebx")); - cb25.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov qword ptr [r11], r10")); - cb26.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov qword ptr [rdx - 8], 0xfffffffffffffff4")); - - assert_snapshot!(cb01.hexdump(), @"b807000000"); - assert_snapshot!(cb02.hexdump(), @"b8fdffffff"); - assert_snapshot!(cb03.hexdump(), @"41bf03000000"); - assert_snapshot!(cb04.hexdump(), @"89d8"); - assert_snapshot!(cb05.hexdump(), @"89c8"); - assert_snapshot!(cb06.hexdump(), @"8b9380000000"); - assert_snapshot!(cb07.hexdump(), @"488b442404"); - assert_snapshot!(cb08.hexdump(), @"41b834000000"); - assert_snapshot!(cb09.hexdump(), @"49b80000008000000000"); - assert_snapshot!(cb10.hexdump(), @"49b8ffffffffffffffff"); - assert_snapshot!(cb11.hexdump(), @"b834000000"); - assert_snapshot!(cb12.hexdump(), @"48b8020000000000c0ff"); - assert_snapshot!(cb13.hexdump(), @"48b80000008000000000"); - assert_snapshot!(cb14.hexdump(), @"48b8ccffffffffffffff"); - assert_snapshot!(cb15.hexdump(), @"48b8ffffffffffffffff"); - assert_snapshot!(cb16.hexdump(), @"4488c9"); - assert_snapshot!(cb17.hexdump(), @"4889c3"); - assert_snapshot!(cb18.hexdump(), @"4889df"); - assert_snapshot!(cb19.hexdump(), @"40b60b"); - assert_snapshot!(cb20.hexdump(), @"c60424fd"); - assert_snapshot!(cb21.hexdump(), @"48c7470801000000"); - assert_snapshot!(cb22.hexdump(), @"c7400411000000"); - assert_snapshot!(cb23.hexdump(), @"c7400401000080"); - assert_snapshot!(cb24.hexdump(), @"41895814"); - assert_snapshot!(cb25.hexdump(), @"4d8913"); - assert_snapshot!(cb26.hexdump(), @"48c742f8f4ffffff"); + assert_disasm_snapshot!(disasms!( + cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, + cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21, cb22, cb23, cb24, cb25, cb26, + ), @r" + 0x0: mov eax, 7 + 0x0: mov eax, 0xfffffffd + 0x0: mov r15d, 3 + 0x0: mov eax, ebx + 0x0: mov eax, ecx + 0x0: mov edx, dword ptr [rbx + 0x80] + 0x0: mov rax, qword ptr [rsp + 4] + 0x0: mov r8d, 0x34 + 0x0: movabs r8, 0x80000000 + 0x0: movabs r8, 0xffffffffffffffff + 0x0: mov eax, 0x34 + 0x0: movabs rax, 0xffc0000000000002 + 0x0: movabs rax, 0x80000000 + 0x0: movabs rax, 0xffffffffffffffcc + 0x0: movabs rax, 0xffffffffffffffff + 0x0: mov cl, r9b + 0x0: mov rbx, rax + 0x0: mov rdi, rbx + 0x0: mov sil, 0xb + 0x0: mov byte ptr [rsp], 0xfd + 0x0: mov qword ptr [rdi + 8], 1 + 0x0: mov dword ptr [rax + 4], 0x11 + 0x0: mov dword ptr [rax + 4], 0x80000001 + 0x0: mov dword ptr [r8 + 0x14], ebx + 0x0: mov qword ptr [r11], r10 + 0x0: mov qword ptr [rdx - 8], 0xfffffffffffffff4 + "); + + assert_snapshot!(hexdumps!( + cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, + cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21, cb22, cb23, cb24, cb25, cb26 + ), @r" + b807000000 + b8fdffffff + 41bf03000000 + 89d8 + 89c8 + 8b9380000000 + 488b442404 + 41b834000000 + 49b80000008000000000 + 49b8ffffffffffffffff + b834000000 + 48b8020000000000c0ff + 48b80000008000000000 + 48b8ccffffffffffffff + 48b8ffffffffffffffff + 4488c9 + 4889c3 + 4889df + 40b60b + c60424fd + 48c7470801000000 + c7400411000000 + c7400401000080 + 41895814 + 4d8913 + 48c742f8f4ffffff + "); } #[test] @@ -380,11 +425,15 @@ fn test_movabs() { let cb1 = compile(|cb| movabs(cb, R8, 0x34)); let cb2 = compile(|cb| movabs(cb, R8, 0x80000000)); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0x34")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0x80000000")); + assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + 0x0: movabs r8, 0x34 + 0x0: movabs r8, 0x80000000 + "); - assert_snapshot!(cb1.hexdump(), @"49b83400000000000000"); - assert_snapshot!(cb2.hexdump(), @"49b80000008000000000"); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + 49b83400000000000000 + 49b80000008000000000 + "); } #[test] @@ -421,49 +470,53 @@ fn test_mov_unsigned() { // MOV r64, imm64, will not move down into 32 bit since it does not fit into 32 bits let cb21 = compile(|cb| mov(cb, R8, uimm_opnd(u64::MAX))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov al, 1")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov al, 0xff")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov ax, 1")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov ax, 0xffff")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 1")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 0xffffffff")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 0")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 0xffffffff")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 1")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, 0xffffffff")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0x100000000")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs rax, 0xffffffffffffffff")); - cb13.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0xffffffffffffffff")); - cb14.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8b, 1")); - cb15.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8b, 0xff")); - cb16.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8w, 1")); - cb17.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8w, 0xffff")); - cb18.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 1")); - cb19.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 0xffffffff")); - cb20.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov r8d, 1")); - cb21.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movabs r8, 0xffffffffffffffff")); - - assert_snapshot!(cb01.hexdump(), @"b001"); - assert_snapshot!(cb02.hexdump(), @"b0ff"); - assert_snapshot!(cb03.hexdump(), @"66b80100"); - assert_snapshot!(cb04.hexdump(), @"66b8ffff"); - assert_snapshot!(cb05.hexdump(), @"b801000000"); - assert_snapshot!(cb06.hexdump(), @"b8ffffffff"); - assert_snapshot!(cb07.hexdump(), @"41b800000000"); - assert_snapshot!(cb08.hexdump(), @"41b8ffffffff"); - assert_snapshot!(cb09.hexdump(), @"b801000000"); - assert_snapshot!(cb10.hexdump(), @"b8ffffffff"); - assert_snapshot!(cb11.hexdump(), @"48b80000000001000000"); - assert_snapshot!(cb12.hexdump(), @"48b8ffffffffffffffff"); - assert_snapshot!(cb13.hexdump(), @"49b8ffffffffffffffff"); - assert_snapshot!(cb14.hexdump(), @"41b001"); - assert_snapshot!(cb15.hexdump(), @"41b0ff"); - assert_snapshot!(cb16.hexdump(), @"6641b80100"); - assert_snapshot!(cb17.hexdump(), @"6641b8ffff"); - assert_snapshot!(cb18.hexdump(), @"41b801000000"); - assert_snapshot!(cb19.hexdump(), @"41b8ffffffff"); - assert_snapshot!(cb20.hexdump(), @"41b801000000"); - assert_snapshot!(cb21.hexdump(), @"49b8ffffffffffffffff"); + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @r" + 0x0: mov al, 1 + 0x0: mov al, 0xff + 0x0: mov ax, 1 + 0x0: mov ax, 0xffff + 0x0: mov eax, 1 + 0x0: mov eax, 0xffffffff + 0x0: mov r8d, 0 + 0x0: mov r8d, 0xffffffff + 0x0: mov eax, 1 + 0x0: mov eax, 0xffffffff + 0x0: movabs rax, 0x100000000 + 0x0: movabs rax, 0xffffffffffffffff + 0x0: movabs r8, 0xffffffffffffffff + 0x0: mov r8b, 1 + 0x0: mov r8b, 0xff + 0x0: mov r8w, 1 + 0x0: mov r8w, 0xffff + 0x0: mov r8d, 1 + 0x0: mov r8d, 0xffffffff + 0x0: mov r8d, 1 + 0x0: movabs r8, 0xffffffffffffffff + "); + + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @r" + b001 + b0ff + 66b80100 + 66b8ffff + b801000000 + b8ffffffff + 41b800000000 + 41b8ffffffff + b801000000 + b8ffffffff + 48b80000000001000000 + 48b8ffffffffffffffff + 49b8ffffffffffffffff + 41b001 + 41b0ff + 6641b80100 + 6641b8ffff + 41b801000000 + 41b8ffffffff + 41b801000000 + 49b8ffffffffffffffff + "); } #[test] @@ -474,17 +527,21 @@ fn test_mov_iprel() { let cb4 = compile(|cb| mov(cb, RAX, mem_opnd(64, RIP, 5))); let cb5 = compile(|cb| mov(cb, RDI, mem_opnd(64, RIP, 5))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, dword ptr [rip]")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov eax, dword ptr [rip + 5]")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rax, qword ptr [rip]")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rax, qword ptr [rip + 5]")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: mov rdi, qword ptr [rip + 5]")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + 0x0: mov eax, dword ptr [rip] + 0x0: mov eax, dword ptr [rip + 5] + 0x0: mov rax, qword ptr [rip] + 0x0: mov rax, qword ptr [rip + 5] + 0x0: mov rdi, qword ptr [rip + 5] + "); - assert_snapshot!(cb1.hexdump(), @"8b0500000000"); - assert_snapshot!(cb2.hexdump(), @"8b0505000000"); - assert_snapshot!(cb3.hexdump(), @"488b0500000000"); - assert_snapshot!(cb4.hexdump(), @"488b0505000000"); - assert_snapshot!(cb5.hexdump(), @"488b3d05000000"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + 8b0500000000 + 8b0505000000 + 488b0500000000 + 488b0505000000 + 488b3d05000000 + "); } #[test] @@ -498,23 +555,27 @@ fn test_movsx() { let cb7 = compile(|cb| movsx(cb, RAX, mem_opnd(8, RSP, 0))); let cb8 = compile(|cb| movsx(cb, RDX, mem_opnd(16, R13, 4))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx ax, al")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx edx, al")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx rax, bl")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx ecx, ax")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx r11, cl")); - cb6.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsxd r10, dword ptr [rsp + 0xc]")); - cb7.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx rax, byte ptr [rsp]")); - cb8.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: movsx rdx, word ptr [r13 + 4]")); - - assert_snapshot!(cb1.hexdump(), @"660fbec0"); - assert_snapshot!(cb2.hexdump(), @"0fbed0"); - assert_snapshot!(cb3.hexdump(), @"480fbec3"); - assert_snapshot!(cb4.hexdump(), @"0fbfc8"); - assert_snapshot!(cb5.hexdump(), @"4c0fbed9"); - assert_snapshot!(cb6.hexdump(), @"4c6354240c"); - assert_snapshot!(cb7.hexdump(), @"480fbe0424"); - assert_snapshot!(cb8.hexdump(), @"490fbf5504"); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 0x0: movsx ax, al + 0x0: movsx edx, al + 0x0: movsx rax, bl + 0x0: movsx ecx, ax + 0x0: movsx r11, cl + 0x0: movsxd r10, dword ptr [rsp + 0xc] + 0x0: movsx rax, byte ptr [rsp] + 0x0: movsx rdx, word ptr [r13 + 4] + "); + + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 660fbec0 + 0fbed0 + 480fbec3 + 0fbfc8 + 4c0fbed9 + 4c6354240c + 480fbe0424 + 490fbf5504 + "); } #[test] @@ -532,40 +593,38 @@ fn test_nop() { let cb11 = compile(|cb| nop(cb, 11)); let cb12 = compile(|cb| nop(cb, 12)); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop dword ptr [rax]")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop dword ptr [rax]")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop dword ptr [rax + rax]")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop word ptr [rax + rax]")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop dword ptr [rax]")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop dword ptr [rax + rax]")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: nop word ptr [rax + rax]")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @r" + 0x0: nop + 0x0: nop + 0x0: nop dword ptr [rax] + 0x0: nop dword ptr [rax] + 0x0: nop dword ptr [rax + rax] + 0x0: nop word ptr [rax + rax] + 0x0: nop dword ptr [rax] + 0x0: nop dword ptr [rax + rax] + 0x0: nop word ptr [rax + rax] 0x0: nop word ptr [rax + rax] 0x9: nop - ")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @r" 0x0: nop word ptr [rax + rax] 0x9: nop - ")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @r" 0x0: nop word ptr [rax + rax] 0x9: nop dword ptr [rax] - ")); + "); - assert_snapshot!(cb01.hexdump(), @"90"); - assert_snapshot!(cb02.hexdump(), @"6690"); - assert_snapshot!(cb03.hexdump(), @"0f1f00"); - assert_snapshot!(cb04.hexdump(), @"0f1f4000"); - assert_snapshot!(cb05.hexdump(), @"0f1f440000"); - assert_snapshot!(cb06.hexdump(), @"660f1f440000"); - assert_snapshot!(cb07.hexdump(), @"0f1f8000000000"); - assert_snapshot!(cb08.hexdump(), @"0f1f840000000000"); - assert_snapshot!(cb09.hexdump(), @"660f1f840000000000"); - assert_snapshot!(cb10.hexdump(), @"660f1f84000000000090"); - assert_snapshot!(cb11.hexdump(), @"660f1f8400000000006690"); - assert_snapshot!(cb12.hexdump(), @"660f1f8400000000000f1f00"); + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @r" + 90 + 6690 + 0f1f00 + 0f1f4000 + 0f1f440000 + 660f1f440000 + 0f1f8000000000 + 0f1f840000000000 + 660f1f840000000000 + 660f1f84000000000090 + 660f1f8400000000006690 + 660f1f8400000000000f1f00 + "); } #[test] @@ -588,47 +647,51 @@ fn test_not() { let cb16 = compile(|cb| not(cb, mem_opnd(32, RDX, -55))); let cb17 = compile(|cb| not(cb, mem_opnd(32, RDX, -555))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not ax")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not eax")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not qword ptr [r12]")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rsp + 0x12d]")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rsp]")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rsp + 3]")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rbp]")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rbp + 0xd]")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not rax")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not r11")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rax]")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rsi]")); - cb13.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rdi]")); - cb14.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rdx + 0x37]")); - cb15.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rdx + 0x539]")); - cb16.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rdx - 0x37]")); - cb17.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: not dword ptr [rdx - 0x22b]")); - - assert_snapshot!(cb01.hexdump(), @"66f7d0"); - assert_snapshot!(cb02.hexdump(), @"f7d0"); - assert_snapshot!(cb03.hexdump(), @"49f71424"); - assert_snapshot!(cb04.hexdump(), @"f794242d010000"); - assert_snapshot!(cb05.hexdump(), @"f71424"); - assert_snapshot!(cb06.hexdump(), @"f7542403"); - assert_snapshot!(cb07.hexdump(), @"f75500"); - assert_snapshot!(cb08.hexdump(), @"f7550d"); - assert_snapshot!(cb09.hexdump(), @"48f7d0"); - assert_snapshot!(cb10.hexdump(), @"49f7d3"); - assert_snapshot!(cb11.hexdump(), @"f710"); - assert_snapshot!(cb12.hexdump(), @"f716"); - assert_snapshot!(cb13.hexdump(), @"f717"); - assert_snapshot!(cb14.hexdump(), @"f75237"); - assert_snapshot!(cb15.hexdump(), @"f79239050000"); - assert_snapshot!(cb16.hexdump(), @"f752c9"); - assert_snapshot!(cb17.hexdump(), @"f792d5fdffff"); + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @r" + 0x0: not ax + 0x0: not eax + 0x0: not qword ptr [r12] + 0x0: not dword ptr [rsp + 0x12d] + 0x0: not dword ptr [rsp] + 0x0: not dword ptr [rsp + 3] + 0x0: not dword ptr [rbp] + 0x0: not dword ptr [rbp + 0xd] + 0x0: not rax + 0x0: not r11 + 0x0: not dword ptr [rax] + 0x0: not dword ptr [rsi] + 0x0: not dword ptr [rdi] + 0x0: not dword ptr [rdx + 0x37] + 0x0: not dword ptr [rdx + 0x539] + 0x0: not dword ptr [rdx - 0x37] + 0x0: not dword ptr [rdx - 0x22b] + "); + + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @r" + 66f7d0 + f7d0 + 49f71424 + f794242d010000 + f71424 + f7542403 + f75500 + f7550d + 48f7d0 + 49f7d3 + f710 + f716 + f717 + f75237 + f79239050000 + f752c9 + f792d5fdffff + "); } #[test] fn test_or() { let cb = compile(|cb| or(cb, EDX, ESI)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: or edx, esi")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: or edx, esi"); assert_snapshot!(cb.hexdump(), @"09f2"); } @@ -645,27 +708,31 @@ fn test_pop() { let cb09 = compile(|cb| pop(cb, mem_opnd_sib(64, RAX, RCX, 8, 3))); let cb10 = compile(|cb| pop(cb, mem_opnd_sib(64, R8, RCX, 8, 3))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop rax")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop rbx")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop rsp")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop rbp")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop r12")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop qword ptr [rax]")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop qword ptr [r8]")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop qword ptr [r8 + 3]")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop qword ptr [rax + rcx*8 + 3]")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: pop qword ptr [r8 + rcx*8 + 3]")); - - assert_snapshot!(cb01.hexdump(), @"58"); - assert_snapshot!(cb02.hexdump(), @"5b"); - assert_snapshot!(cb03.hexdump(), @"5c"); - assert_snapshot!(cb04.hexdump(), @"5d"); - assert_snapshot!(cb05.hexdump(), @"415c"); - assert_snapshot!(cb06.hexdump(), @"8f00"); - assert_snapshot!(cb07.hexdump(), @"418f00"); - assert_snapshot!(cb08.hexdump(), @"418f4003"); - assert_snapshot!(cb09.hexdump(), @"8f44c803"); - assert_snapshot!(cb10.hexdump(), @"418f44c803"); + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @r" + 0x0: pop rax + 0x0: pop rbx + 0x0: pop rsp + 0x0: pop rbp + 0x0: pop r12 + 0x0: pop qword ptr [rax] + 0x0: pop qword ptr [r8] + 0x0: pop qword ptr [r8 + 3] + 0x0: pop qword ptr [rax + rcx*8 + 3] + 0x0: pop qword ptr [r8 + rcx*8 + 3] + "); + + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @r" + 58 + 5b + 5c + 5d + 415c + 8f00 + 418f00 + 418f4003 + 8f44c803 + 418f44c803 + "); } #[test] @@ -679,29 +746,33 @@ fn test_push() { let cb7 = compile(|cb| push(cb, mem_opnd_sib(64, RAX, RCX, 8, 3))); let cb8 = compile(|cb| push(cb, mem_opnd_sib(64, R8, RCX, 8, 3))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push rax")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push rbx")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push r12")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push qword ptr [rax]")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push qword ptr [r8]")); - cb6.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push qword ptr [r8 + 3]")); - cb7.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push qword ptr [rax + rcx*8 + 3]")); - cb8.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: push qword ptr [r8 + rcx*8 + 3]")); - - assert_snapshot!(cb1.hexdump(), @"50"); - assert_snapshot!(cb2.hexdump(), @"53"); - assert_snapshot!(cb3.hexdump(), @"4154"); - assert_snapshot!(cb4.hexdump(), @"ff30"); - assert_snapshot!(cb5.hexdump(), @"41ff30"); - assert_snapshot!(cb6.hexdump(), @"41ff7003"); - assert_snapshot!(cb7.hexdump(), @"ff74c803"); - assert_snapshot!(cb8.hexdump(), @"41ff74c803"); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 0x0: push rax + 0x0: push rbx + 0x0: push r12 + 0x0: push qword ptr [rax] + 0x0: push qword ptr [r8] + 0x0: push qword ptr [r8 + 3] + 0x0: push qword ptr [rax + rcx*8 + 3] + 0x0: push qword ptr [r8 + rcx*8 + 3] + "); + + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + 50 + 53 + 4154 + ff30 + 41ff30 + 41ff7003 + ff74c803 + 41ff74c803 + "); } #[test] fn test_ret() { let cb = compile(ret); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ret")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ret"); assert_snapshot!(cb.hexdump(), @"c3"); } @@ -713,30 +784,34 @@ fn test_sal() { let cb4 = compile(|cb| sal(cb, mem_opnd(32, RSP, 68), uimm_opnd(1))); let cb5 = compile(|cb| sal(cb, RCX, CL)); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shl cx, 1")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shl ecx, 1")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shl ebp, 5")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shl dword ptr [rsp + 0x44], 1")); - cb5.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shl rcx, cl")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + 0x0: shl cx, 1 + 0x0: shl ecx, 1 + 0x0: shl ebp, 5 + 0x0: shl dword ptr [rsp + 0x44], 1 + 0x0: shl rcx, cl + "); - assert_snapshot!(cb1.hexdump(), @"66d1e1"); - assert_snapshot!(cb2.hexdump(), @"d1e1"); - assert_snapshot!(cb3.hexdump(), @"c1e505"); - assert_snapshot!(cb4.hexdump(), @"d1642444"); - assert_snapshot!(cb5.hexdump(), @"48d3e1"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + 66d1e1 + d1e1 + c1e505 + d1642444 + 48d3e1 + "); } #[test] fn test_sar() { let cb = compile(|cb| sar(cb, EDX, uimm_opnd(1))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sar edx, 1")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sar edx, 1"); assert_snapshot!(cb.hexdump(), @"d1fa"); } #[test] fn test_shr() { let cb = compile(|cb| shr(cb, R14, uimm_opnd(7))); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: shr r14, 7")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: shr r14, 7"); assert_snapshot!(cb.hexdump(), @"49c1ee07"); } @@ -745,11 +820,15 @@ fn test_sub() { let cb1 = compile(|cb| sub(cb, EAX, imm_opnd(1))); let cb2 = compile(|cb| sub(cb, RAX, imm_opnd(2))); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub eax, 1")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub rax, 2")); + assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + 0x0: sub eax, 1 + 0x0: sub rax, 2 + "); - assert_snapshot!(cb1.hexdump(), @"83e801"); - assert_snapshot!(cb2.hexdump(), @"4883e802"); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + 83e801 + 4883e802 + "); } #[test] @@ -782,45 +861,49 @@ fn test_test() { let cb18 = compile(|cb| test(cb, mem_opnd(64, RSI, 64), imm_opnd(0x08))); let cb19 = compile(|cb| test(cb, RCX, imm_opnd(0x08))); - cb01.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test al, al")); - cb02.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test ax, ax")); - cb03.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test cl, 8")); - cb04.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test dl, 7")); - cb05.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test cl, 8")); - cb06.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test byte ptr [rdx + 8], 8")); - cb07.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test byte ptr [rdx + 8], 0xff")); - cb08.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test dx, 0xffff")); - cb09.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test word ptr [rdx + 8], 0xffff")); - cb10.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test byte ptr [rsi], 1")); - cb11.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test byte ptr [rsi + 0x10], 1")); - cb12.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test byte ptr [rsi - 0x10], 1")); - cb13.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test dword ptr [rsi + 0x40], eax")); - cb14.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test qword ptr [rdi + 0x2a], rax")); - cb15.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test rax, rax")); - cb16.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test rax, rsi")); - cb17.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test qword ptr [rsi + 0x40], -9")); - cb18.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test qword ptr [rsi + 0x40], 8")); - cb19.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test rcx, 8")); - - assert_snapshot!(cb01.hexdump(), @"84c0"); - assert_snapshot!(cb02.hexdump(), @"6685c0"); - assert_snapshot!(cb03.hexdump(), @"f6c108"); - assert_snapshot!(cb04.hexdump(), @"f6c207"); - assert_snapshot!(cb05.hexdump(), @"f6c108"); - assert_snapshot!(cb06.hexdump(), @"f6420808"); - assert_snapshot!(cb07.hexdump(), @"f64208ff"); - assert_snapshot!(cb08.hexdump(), @"66f7c2ffff"); - assert_snapshot!(cb09.hexdump(), @"66f74208ffff"); - assert_snapshot!(cb10.hexdump(), @"f60601"); - assert_snapshot!(cb11.hexdump(), @"f6461001"); - assert_snapshot!(cb12.hexdump(), @"f646f001"); - assert_snapshot!(cb13.hexdump(), @"854640"); - assert_snapshot!(cb14.hexdump(), @"4885472a"); - assert_snapshot!(cb15.hexdump(), @"4885c0"); - assert_snapshot!(cb16.hexdump(), @"4885f0"); - assert_snapshot!(cb17.hexdump(), @"48f74640f7ffffff"); - assert_snapshot!(cb18.hexdump(), @"48f7464008000000"); - assert_snapshot!(cb19.hexdump(), @"48f7c108000000"); + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @r" + 0x0: test al, al + 0x0: test ax, ax + 0x0: test cl, 8 + 0x0: test dl, 7 + 0x0: test cl, 8 + 0x0: test byte ptr [rdx + 8], 8 + 0x0: test byte ptr [rdx + 8], 0xff + 0x0: test dx, 0xffff + 0x0: test word ptr [rdx + 8], 0xffff + 0x0: test byte ptr [rsi], 1 + 0x0: test byte ptr [rsi + 0x10], 1 + 0x0: test byte ptr [rsi - 0x10], 1 + 0x0: test dword ptr [rsi + 0x40], eax + 0x0: test qword ptr [rdi + 0x2a], rax + 0x0: test rax, rax + 0x0: test rax, rsi + 0x0: test qword ptr [rsi + 0x40], -9 + 0x0: test qword ptr [rsi + 0x40], 8 + 0x0: test rcx, 8 + "); + + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @r" + 84c0 + 6685c0 + f6c108 + f6c207 + f6c108 + f6420808 + f64208ff + 66f7c2ffff + 66f74208ffff + f60601 + f6461001 + f646f001 + 854640 + 4885472a + 4885c0 + 4885f0 + 48f74640f7ffffff + 48f7464008000000 + 48f7c108000000 + "); } #[test] @@ -830,21 +913,25 @@ fn test_xchg() { let cb3 = compile(|cb| xchg(cb, RCX, RBX)); let cb4 = compile(|cb| xchg(cb, R9, R15)); - cb1.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xchg rcx, rax")); - cb2.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xchg r13, rax")); - cb3.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xchg rcx, rbx")); - cb4.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xchg r9, r15")); + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @r" + 0x0: xchg rcx, rax + 0x0: xchg r13, rax + 0x0: xchg rcx, rbx + 0x0: xchg r9, r15 + "); - assert_snapshot!(cb1.hexdump(), @"4891"); - assert_snapshot!(cb2.hexdump(), @"4995"); - assert_snapshot!(cb3.hexdump(), @"4887d9"); - assert_snapshot!(cb4.hexdump(), @"4d87f9"); + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @r" + 4891 + 4995 + 4887d9 + 4d87f9 + "); } #[test] fn test_xor() { let cb = compile(|cb| xor(cb, EAX, EAX)); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xor eax, eax")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: xor eax, eax"); assert_snapshot!(cb.hexdump(), @"31c0"); } diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 7a8c0eeaa0d009..5ac62c059986d5 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1422,6 +1422,10 @@ fn merge_three_reg_mov( #[cfg(test)] mod tests { + #[cfg(feature = "disasm")] + use crate::disasms_with; + use crate::{assert_disasm_snapshot, hexdumps}; + use super::*; use insta::assert_snapshot; @@ -1439,11 +1443,11 @@ mod tests { asm.mov(Opnd::Reg(TEMP_REGS[0]), out); asm.compile_with_num_regs(&mut cb, 2); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #3 0x4: mul x0, x9, x0 0x8: mov x1, x0 - ")); + "); assert_snapshot!(cb.hexdump(), @"600080d2207d009be10300aa"); } @@ -1458,10 +1462,10 @@ mod tests { asm.mov(sp, new_sp); asm.compile_with_num_regs(&mut cb, 2); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add sp, sp, #0x20 0x4: sub sp, sp, #0x20 - ")); + "); assert_snapshot!(cb.hexdump(), @"ff830091ff8300d1"); } @@ -1474,10 +1478,10 @@ mod tests { asm.add_into(Opnd::Reg(X20_REG), 0x20.into()); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add sp, sp, #8 0x4: adds x20, x20, #0x20 - ")); + "); assert_snapshot!(cb.hexdump(), @"ff230091948200b1"); } @@ -1489,11 +1493,11 @@ mod tests { asm.load_into(Opnd::Reg(X1_REG), difference); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #8 0x4: subs x0, x0, x5 0x8: mov x1, x0 - ")); + "); assert_snapshot!(cb.hexdump(), @"000180d2000005ebe10300aa"); } @@ -1505,10 +1509,10 @@ mod tests { asm.cret(ret_val); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [x0] 0x4: ret - ")); + "); assert_snapshot!(cb.hexdump(), @"000040f8c0035fd6"); } @@ -1521,7 +1525,11 @@ mod tests { asm.compile_with_regs(&mut cb, vec![X3_REG]).unwrap(); // Assert that only 2 instructions were written. - assert_eq!(8, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: adds x3, x0, x1 + 0x4: stur x3, [x2] + "); + assert_snapshot!(cb.hexdump(), @"030001ab430000f8"); } #[test] @@ -1533,7 +1541,13 @@ mod tests { // Testing that we pad the string to the nearest 4-byte boundary to make // it easier to jump over. - assert_eq!(16, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldnp d8, d25, [x10, #-0x140] + 0x4: .byte 0x6f, 0x2c, 0x20, 0x77 + 0x8: .byte 0x6f, 0x72, 0x6c, 0x64 + 0xc: .byte 0x21, 0x00, 0x00, 0x00 + "); + assert_snapshot!(cb.hexdump(), @"48656c6c6f2c20776f726c6421000000"); } #[test] @@ -1542,6 +1556,20 @@ mod tests { asm.cpush_all(); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: str x1, [sp, #-0x10]! + 0x4: str x9, [sp, #-0x10]! + 0x8: str x10, [sp, #-0x10]! + 0xc: str x11, [sp, #-0x10]! + 0x10: str x12, [sp, #-0x10]! + 0x14: str x13, [sp, #-0x10]! + 0x18: str x14, [sp, #-0x10]! + 0x1c: str x15, [sp, #-0x10]! + 0x20: mrs x16, nzcv + 0x24: str x16, [sp, #-0x10]! + "); + assert_snapshot!(cb.hexdump(), @"e10f1ff8e90f1ff8ea0f1ff8eb0f1ff8ec0f1ff8ed0f1ff8ee0f1ff8ef0f1ff810423bd5f00f1ff8"); } #[test] @@ -1550,6 +1578,20 @@ mod tests { asm.cpop_all(); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: msr nzcv, x16 + 0x4: ldr x16, [sp], #0x10 + 0x8: ldr x15, [sp], #0x10 + 0xc: ldr x14, [sp], #0x10 + 0x10: ldr x13, [sp], #0x10 + 0x14: ldr x12, [sp], #0x10 + 0x18: ldr x11, [sp], #0x10 + 0x1c: ldr x10, [sp], #0x10 + 0x20: ldr x9, [sp], #0x10 + 0x24: ldr x1, [sp], #0x10 + "); + assert_snapshot!(cb.hexdump(), @"10421bd5f00741f8ef0741f8ee0741f8ed0741f8ec0741f8eb0741f8ea0741f8e90741f8e10741f8"); } #[test] @@ -1559,71 +1601,83 @@ mod tests { asm.frame_setup(&[], 0); asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: mov sp, x29 + 0xc: ldp x29, x30, [sp], #0x10 + "); + assert_snapshot!(cb.hexdump(), @"fd7bbfa9fd030091bf030091fd7bc1a8"); } #[test] fn frame_setup_and_teardown() { const THREE_REGS: &[Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG)]; // Test 3 preserved regs (odd), odd slot_count - { + let cb1 = { let (mut asm, mut cb) = setup_asm(); asm.frame_setup(THREE_REGS, 3); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" - 0x0: stp x29, x30, [sp, #-0x10]! - 0x4: mov x29, sp - 0x8: stp x20, x19, [sp, #-0x10]! - 0xc: stur x21, [sp, #-8] - 0x10: sub sp, sp, #0x20 - 0x14: ldp x20, x19, [x29, #-0x10] - 0x18: ldur x21, [x29, #-0x18] - 0x1c: mov sp, x29 - 0x20: ldp x29, x30, [sp], #0x10 - ")); - assert_snapshot!(cb.hexdump(), @"fd7bbfa9fd030091f44fbfa9f5831ff8ff8300d1b44f7fa9b5835ef8bf030091fd7bc1a8"); - } + cb + }; // Test 3 preserved regs (odd), even slot_count - { + let cb2 = { let (mut asm, mut cb) = setup_asm(); asm.frame_setup(THREE_REGS, 4); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" - 0x0: stp x29, x30, [sp, #-0x10]! - 0x4: mov x29, sp - 0x8: stp x20, x19, [sp, #-0x10]! - 0xc: stur x21, [sp, #-8] - 0x10: sub sp, sp, #0x30 - 0x14: ldp x20, x19, [x29, #-0x10] - 0x18: ldur x21, [x29, #-0x18] - 0x1c: mov sp, x29 - 0x20: ldp x29, x30, [sp], #0x10 - ")); - assert_snapshot!(cb.hexdump(), @"fd7bbfa9fd030091f44fbfa9f5831ff8ffc300d1b44f7fa9b5835ef8bf030091fd7bc1a8"); - } + cb + }; // Test 4 preserved regs (even), odd slot_count - { + let cb3 = { static FOUR_REGS: &[Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG), Opnd::Reg(X22_REG)]; let (mut asm, mut cb) = setup_asm(); asm.frame_setup(FOUR_REGS, 3); asm.frame_teardown(FOUR_REGS); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" - 0x0: stp x29, x30, [sp, #-0x10]! - 0x4: mov x29, sp - 0x8: stp x20, x19, [sp, #-0x10]! - 0xc: stp x22, x21, [sp, #-0x10]! - 0x10: sub sp, sp, #0x20 - 0x14: ldp x20, x19, [x29, #-0x10] - 0x18: ldp x22, x21, [x29, #-0x20] - 0x1c: mov sp, x29 - 0x20: ldp x29, x30, [sp], #0x10 - ")); - assert_snapshot!(cb.hexdump(), @"fd7bbfa9fd030091f44fbfa9f657bfa9ff8300d1b44f7fa9b6577ea9bf030091fd7bc1a8"); - } + cb + }; + + assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2, cb3), @r" + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stur x21, [sp, #-8] + 0x10: sub sp, sp, #0x20 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldur x21, [x29, #-0x18] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stur x21, [sp, #-8] + 0x10: sub sp, sp, #0x30 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldur x21, [x29, #-0x18] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stp x22, x21, [sp, #-0x10]! + 0x10: sub sp, sp, #0x20 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldp x22, x21, [x29, #-0x20] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + "); + assert_snapshot!(hexdumps!(cb1, cb2, cb3), @r" + fd7bbfa9fd030091f44fbfa9f5831ff8ff8300d1b44f7fa9b5835ef8bf030091fd7bc1a8 + fd7bbfa9fd030091f44fbfa9f5831ff8ffc300d1b44f7fa9b5835ef8bf030091fd7bc1a8 + fd7bbfa9fd030091f44fbfa9f657bfa9ff8300d1b44f7fa9b6577ea9bf030091fd7bc1a8 + "); } #[test] @@ -1634,6 +1688,16 @@ mod tests { asm.je(Target::CodePtr(target)); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: b.eq #0x50 + 0x4: nop + 0x8: nop + 0xc: nop + 0x10: nop + 0x14: nop + "); + assert_snapshot!(cb.hexdump(), @"800200541f2003d51f2003d51f2003d51f2003d51f2003d5"); } #[test] @@ -1645,6 +1709,16 @@ mod tests { asm.je(Target::CodePtr(target)); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: b.ne #8 + 0x4: b #0x200000 + 0x8: nop + 0xc: nop + 0x10: nop + 0x14: nop + "); + assert_snapshot!(cb.hexdump(), @"41000054ffff07141f2003d51f2003d51f2003d51f2003d5"); } #[test] @@ -1662,7 +1736,7 @@ mod tests { } asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: orr x0, xzr, #0x7fffffff 0x4: add x0, sp, x0 0x8: mov x0, #8 @@ -1681,7 +1755,7 @@ mod tests { 0x3c: add x0, sp, x0 0x40: orr x0, xzr, #0xffffffff80000000 0x44: add x0, sp, x0 - ")); + "); assert_snapshot!(cb.hexdump(), @"e07b40b2e063208b000180d22000a0f2e063208b000083d2e063208be0230891e02308d1e0ff8292e063208b00ff9fd2c0ffbff2e0ffdff2e0fffff2e063208be08361b2e063208b"); } @@ -1697,7 +1771,7 @@ mod tests { asm.store(large_mem, large_mem); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x16, sp, #0x305 0x4: ldur x16, [x16] 0x8: stur x16, [x0] @@ -1708,7 +1782,7 @@ mod tests { 0x1c: ldur x16, [x16] 0x20: sub x17, sp, #0x305 0x24: stur x16, [x17] - ")); + "); assert_snapshot!(cb.hexdump(), @"f0170cd1100240f8100000f8100040f8f1170cd1300200f8f0170cd1100240f8f1170cd1300200f8"); } @@ -1725,13 +1799,13 @@ mod tests { let gc_offsets = asm.arm64_emit(&mut cb).unwrap(); assert_eq!(1, gc_offsets.len(), "VALUE source operand should be reported as gc offset"); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x16, #8 0x4: b #0x10 0x8: .byte 0x00, 0x10, 0x00, 0x00 0xc: .byte 0x00, 0x00, 0x00, 0x00 0x10: stur x16, [x21] - ")); + "); assert_snapshot!(cb.hexdump(), @"50000058030000140010000000000000b00200f8"); } @@ -1757,6 +1831,16 @@ mod tests { asm.store(Opnd::mem(64, SP, 0), opnd); asm.compile_with_num_regs(&mut cb, 1); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: adr x16, #8 + 0x4: mov x0, x16 + 0x8: ldnp d8, d25, [x10, #-0x140] + 0xc: .byte 0x6f, 0x2c, 0x20, 0x77 + 0x10: .byte 0x6f, 0x72, 0x6c, 0x64 + 0x14: .byte 0x21, 0x00, 0x00, 0x00 + 0x18: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"50000010e00310aa48656c6c6f2c20776f726c6421000000a00200f8"); } #[test] @@ -1768,7 +1852,11 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that two instructions were written: LDUR and STUR. - assert_eq!(8, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldur x0, [x21] + 0x4: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"a00240f8a00200f8"); } #[test] @@ -1780,7 +1868,12 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that three instructions were written: ADD, LDUR, and STUR. - assert_eq!(12, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: add x0, x21, #0x400 + 0x4: ldur x0, [x0] + 0x8: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"a0021091000040f8a00200f8"); } #[test] @@ -1792,7 +1885,13 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that three instructions were written: MOVZ, ADD, LDUR, and STUR. - assert_eq!(16, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x0, #0x1001 + 0x4: add x0, x21, x0, uxtx + 0x8: ldur x0, [x0] + 0xc: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"200082d2a062208b000040f8a00200f8"); } #[test] @@ -1805,7 +1904,11 @@ mod tests { // Assert that only two instructions were written since the value is an // immediate. - assert_eq!(8, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x0, #4 + 0x4: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"800080d2a00200f8"); } #[test] @@ -1818,7 +1921,14 @@ mod tests { // Assert that five instructions were written since the value is not an // immediate and needs to be loaded into a register. - assert_eq!(20, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldr x0, #8 + 0x4: b #0x10 + 0x8: eon x0, x0, x30, ror #0 + 0xc: eon x30, x23, x30, ror #50 + 0x10: stur x0, [x21] + "); + assert_snapshot!(cb.hexdump(), @"40000058030000140000fecafecafecaa00200f8"); } #[test] @@ -1829,6 +1939,12 @@ mod tests { // All ones is not encodable with a bitmask immediate, // so this needs one register asm.compile_with_num_regs(&mut cb, 1); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: orr x0, xzr, #0xffffffff + 0x4: tst w0, w0 + "); + assert_snapshot!(cb.hexdump(), @"e07f40b21f00006a"); } #[test] @@ -1837,6 +1953,9 @@ mod tests { let w0 = Opnd::Reg(X0_REG).with_num_bits(32); asm.test(w0, Opnd::UImm(0x80000001)); asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst w0, #0x80000001"); + assert_snapshot!(cb.hexdump(), @"1f040172"); } #[test] @@ -1846,6 +1965,12 @@ mod tests { let opnd = asm.or(Opnd::Reg(X0_REG), Opnd::Reg(X1_REG)); asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: orr x0, x0, x1 + 0x4: stur x0, [x2] + "); + assert_snapshot!(cb.hexdump(), @"000001aa400000f8"); } #[test] @@ -1855,6 +1980,12 @@ mod tests { let opnd = asm.lshift(Opnd::Reg(X0_REG), Opnd::UImm(5)); asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: lsl x0, x0, #5 + 0x4: stur x0, [x2] + "); + assert_snapshot!(cb.hexdump(), @"00e87bd3400000f8"); } #[test] @@ -1864,6 +1995,12 @@ mod tests { let opnd = asm.rshift(Opnd::Reg(X0_REG), Opnd::UImm(5)); asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: asr x0, x0, #5 + 0x4: stur x0, [x2] + "); + assert_snapshot!(cb.hexdump(), @"00fc4593400000f8"); } #[test] @@ -1873,6 +2010,12 @@ mod tests { let opnd = asm.urshift(Opnd::Reg(X0_REG), Opnd::UImm(5)); asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: lsr x0, x0, #5 + 0x4: stur x0, [x2] + "); + assert_snapshot!(cb.hexdump(), @"00fc45d3400000f8"); } #[test] @@ -1883,7 +2026,8 @@ mod tests { asm.compile_with_num_regs(&mut cb, 0); // Assert that only one instruction was written. - assert_eq!(4, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x0, x1"); + assert_snapshot!(cb.hexdump(), @"1f0001ea"); } #[test] @@ -1894,7 +2038,8 @@ mod tests { asm.compile_with_num_regs(&mut cb, 0); // Assert that only one instruction was written. - assert_eq!(4, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x0, #7"); + assert_snapshot!(cb.hexdump(), @"1f0840f2"); } #[test] @@ -1905,7 +2050,11 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that a load and a test instruction were written. - assert_eq!(8, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x0, #5 + 0x4: tst x0, x0 + "); + assert_snapshot!(cb.hexdump(), @"a00080d21f0000ea"); } #[test] @@ -1916,7 +2065,8 @@ mod tests { asm.compile_with_num_regs(&mut cb, 0); // Assert that only one instruction was written. - assert_eq!(4, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x0, #7"); + assert_snapshot!(cb.hexdump(), @"1f0840f2"); } #[test] @@ -1927,7 +2077,11 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that a load and a test instruction were written. - assert_eq!(8, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x0, #5 + 0x4: tst x0, x0 + "); + assert_snapshot!(cb.hexdump(), @"a00080d21f0000ea"); } #[test] @@ -1938,7 +2092,8 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that a test instruction is written. - assert_eq!(4, cb.get_write_pos()); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: tst x0, #-7"); + assert_snapshot!(cb.hexdump(), @"1ff47df2"); } #[test] @@ -1948,6 +2103,13 @@ mod tests { let shape_opnd = Opnd::mem(32, Opnd::Reg(X0_REG), 6); asm.cmp(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldur w0, [x0, #6] + 0x4: mov x1, #0x1001 + 0x8: cmp w0, w1 + "); + assert_snapshot!(cb.hexdump(), @"006040b8210082d21f00016b"); } #[test] @@ -1957,6 +2119,12 @@ mod tests { let shape_opnd = Opnd::mem(16, Opnd::Reg(X0_REG), 0); asm.store(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x16, #0x1001 + 0x4: sturh w16, [x0] + "); + assert_snapshot!(cb.hexdump(), @"300082d210000078"); } #[test] @@ -1966,6 +2134,12 @@ mod tests { let shape_opnd = Opnd::mem(32, Opnd::Reg(X0_REG), 6); asm.store(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x16, #0x1001 + 0x4: stur w16, [x0, #6] + "); + assert_snapshot!(cb.hexdump(), @"300082d2106000b8"); } #[test] @@ -1977,10 +2151,10 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: eor x0, x0, x1 0x4: stur x0, [x2] - ")); + "); assert_snapshot!(cb.hexdump(), @"000001ca400000f8"); } @@ -2015,7 +2189,7 @@ mod tests { asm.mov(Opnd::Reg(TEMP_REGS[0]), Opnd::mem(64, CFP, 8)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: ldur x1, [x19, #8]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x1, [x19, #8]"); assert_snapshot!(cb.hexdump(), @"618240f8"); } @@ -2027,10 +2201,10 @@ mod tests { asm.mov(Opnd::Reg(TEMP_REGS[0]), Opnd::UImm(0x10000)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x1, #0xffff 0x4: orr x1, xzr, #0x10000 - ")); + "); assert_snapshot!(cb.hexdump(), @"e1ff9fd2e10370b2"); } @@ -2042,11 +2216,11 @@ mod tests { asm.mov(Opnd::Reg(TEMP_REGS[0]), out); asm.compile_with_num_regs(&mut cb, 2); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #0x14 0x4: mov x1, #0 0x8: csel x1, x0, x1, lt - ")); + "); assert_snapshot!(cb.hexdump(), @"800280d2010080d201b0819a"); } @@ -2087,10 +2261,10 @@ mod tests { asm.mov(Opnd::Reg(TEMP_REGS[0]), out); asm.compile_with_num_regs(&mut cb, 2); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x0, x9, #1 0x4: adds x1, x0, #1 - ")); + "); assert_snapshot!(cb.hexdump(), @"200500b1010400b1"); } @@ -2105,10 +2279,10 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, #0 0x4: blr x16 - ")); + "); assert_snapshot!(cb.hexdump(), @"100080d200023fd6"); } @@ -2125,13 +2299,13 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, x0 0x4: mov x0, x1 0x8: mov x1, x16 0xc: mov x16, #0 0x10: blr x16 - ")); + "); assert_snapshot!(cb.hexdump(), @"f00300aae00301aae10310aa100080d200023fd6"); } @@ -2149,7 +2323,7 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, x2 0x4: mov x2, x3 0x8: mov x3, x16 @@ -2158,7 +2332,7 @@ mod tests { 0x14: mov x1, x16 0x18: mov x16, #0 0x1c: blr x16 - ")); + "); assert_snapshot!(cb.hexdump(), @"f00302aae20303aae30310aaf00300aae00301aae10310aa100080d200023fd6"); } @@ -2175,14 +2349,14 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, x0 0x4: mov x0, x1 0x8: mov x1, x2 0xc: mov x2, x16 0x10: mov x16, #0 0x14: blr x16 - ")); + "); assert_snapshot!(cb.hexdump(), @"f00300aae00301aae10302aae20310aa100080d200023fd6"); } } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index d857c017dc72d4..2edd15380871e1 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -919,6 +919,7 @@ impl Assembler #[cfg(test)] mod tests { use insta::assert_snapshot; + use crate::assert_disasm_snapshot; use super::*; fn setup_asm() -> (Assembler, CodeBlock) { @@ -933,10 +934,10 @@ mod tests { let _ = asm.add(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: add rax, 0xff - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c04881c0ff000000"); } @@ -948,11 +949,11 @@ mod tests { let _ = asm.add(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: add rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c049bbffffffffffff00004c01d8"); } @@ -964,10 +965,10 @@ mod tests { let _ = asm.and(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: and rax, 0xff - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c04881e0ff000000"); } @@ -979,11 +980,11 @@ mod tests { let _ = asm.and(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: and rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c049bbffffffffffff00004c21d8"); } @@ -994,7 +995,7 @@ mod tests { asm.cmp(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp rax, 0xff")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp rax, 0xff"); assert_snapshot!(cb.hexdump(), @"4881f8ff000000"); } @@ -1005,10 +1006,10 @@ mod tests { asm.cmp(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: movabs r11, 0xffffffffffff 0xa: cmp rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"49bbffffffffffff00004c39d8"); } @@ -1019,7 +1020,7 @@ mod tests { asm.cmp(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp rax, -1")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp rax, -1"); assert_snapshot!(cb.hexdump(), @"4883f8ff"); } @@ -1032,7 +1033,7 @@ mod tests { asm.cmp(shape_opnd, Opnd::UImm(0xF000)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp word ptr [rax + 6], 0xf000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp word ptr [rax + 6], 0xf000"); assert_snapshot!(cb.hexdump(), @"6681780600f0"); } @@ -1045,7 +1046,7 @@ mod tests { asm.cmp(shape_opnd, Opnd::UImm(0xF000_0000)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: cmp dword ptr [rax + 4], 0xf0000000")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp dword ptr [rax + 4], 0xf0000000"); assert_snapshot!(cb.hexdump(), @"817804000000f0"); } @@ -1057,10 +1058,10 @@ mod tests { let _ = asm.or(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: or rax, 0xff - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c04881c8ff000000"); } @@ -1072,11 +1073,11 @@ mod tests { let _ = asm.or(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: or rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c049bbffffffffffff00004c09d8"); } @@ -1088,10 +1089,10 @@ mod tests { let _ = asm.sub(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: sub rax, 0xff - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c04881e8ff000000"); } @@ -1103,11 +1104,11 @@ mod tests { let _ = asm.sub(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: sub rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c049bbffffffffffff00004c29d8"); } @@ -1118,7 +1119,7 @@ mod tests { asm.test(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: test rax, 0xff")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: test rax, 0xff"); assert_snapshot!(cb.hexdump(), @"48f7c0ff000000"); } @@ -1129,10 +1130,10 @@ mod tests { asm.test(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: movabs r11, 0xffffffffffff 0xa: test rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"49bbffffffffffff00004c85d8"); } @@ -1144,10 +1145,10 @@ mod tests { let _ = asm.xor(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: xor rax, 0xff - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c04881f0ff000000"); } @@ -1159,11 +1160,11 @@ mod tests { let _ = asm.xor(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: xor rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"4889c049bbffffffffffff00004c31d8"); } @@ -1175,7 +1176,7 @@ mod tests { asm.mov(SP, sp); // should be merged to lea asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: lea rbx, [rbx + 8]")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: lea rbx, [rbx + 8]"); assert_snapshot!(cb.hexdump(), @"488d5b08"); } @@ -1188,10 +1189,10 @@ mod tests { asm.mov(Opnd::mem(64, SP, 0), sp); // should NOT be merged to lea asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: movabs r11, 0xffffffffffff 0xa: cmp rax, r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"49bbffffffffffff00004c39d8"); } @@ -1206,14 +1207,14 @@ mod tests { asm.mov(Opnd::Reg(RAX_REG), result); asm.compile_with_num_regs(&mut cb, 2); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov rax, qword ptr [rbx + 8] 0x4: test rax, rax 0x7: mov eax, 0x14 0xc: mov ecx, 0 0x11: cmovne rax, rcx 0x15: mov rax, rax - ")); + "); assert_snapshot!(cb.hexdump(), @"488b43084885c0b814000000b900000000480f45c14889c0"); } @@ -1225,7 +1226,7 @@ mod tests { asm.mov(CFP, sp); // should be merged to add asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983c540"); } @@ -1236,7 +1237,7 @@ mod tests { asm.add_into(CFP, Opnd::UImm(0x40)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: add r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983c540"); } @@ -1248,7 +1249,7 @@ mod tests { asm.mov(CFP, sp); // should be merged to add asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983ed40"); } @@ -1259,7 +1260,7 @@ mod tests { asm.sub_into(CFP, Opnd::UImm(0x40)); asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: sub r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983ed40"); } @@ -1271,7 +1272,7 @@ mod tests { asm.mov(CFP, sp); // should be merged to add asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: and r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: and r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983e540"); } @@ -1283,7 +1284,7 @@ mod tests { asm.mov(CFP, sp); // should be merged to add asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: or r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: or r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983cd40"); } @@ -1295,7 +1296,7 @@ mod tests { asm.mov(CFP, sp); // should be merged to add asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" 0x0: xor r13, 0x40")); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: xor r13, 0x40"); assert_snapshot!(cb.hexdump(), @"4983f540"); } @@ -1310,10 +1311,10 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @r" + assert_disasm_snapshot!(cb.disasm(), @r" 0x0: mov eax, 0 0x5: call rax - ")); + "); assert_snapshot!(cb.hexdump(), @"b800000000ffd0"); } @@ -1330,13 +1331,13 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov r11, rsi 0x3: mov rsi, rdi 0x6: mov rdi, r11 0x9: mov eax, 0 0xe: call rax - ")); + "); assert_snapshot!(cb.hexdump(), @"4989f34889fe4c89dfb800000000ffd0"); } @@ -1354,7 +1355,7 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov r11, rsi 0x3: mov rsi, rdi 0x6: mov rdi, r11 @@ -1363,7 +1364,7 @@ mod tests { 0xf: mov rdx, r11 0x12: mov eax, 0 0x17: call rax - ")); + "); assert_snapshot!(cb.hexdump(), @"4989f34889fe4c89df4989cb4889d14c89dab800000000ffd0"); } @@ -1380,14 +1381,14 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov r11, rsi 0x3: mov rsi, rdx 0x6: mov rdx, rdi 0x9: mov rdi, r11 0xc: mov eax, 0 0x11: call rax - ")); + "); assert_snapshot!(cb.hexdump(), @"4989f34889d64889fa4c89dfb800000000ffd0"); } @@ -1408,7 +1409,7 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, 3); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov eax, 1 0x5: mov ecx, 2 0xa: mov edx, 3 @@ -1419,7 +1420,7 @@ mod tests { 0x1b: mov rdx, r11 0x1e: mov eax, 0 0x23: call rax - ")); + "); assert_snapshot!(cb.hexdump(), @"b801000000b902000000ba030000004889c74889ce4989cb4889d14c89dab800000000ffd0"); } @@ -1437,12 +1438,12 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cmp qword ptr [rbx + 0x10], 1 0x5: mov edi, 4 0xa: cmovg rdi, qword ptr [rbx] 0xe: mov qword ptr [rbx], rdi - ")); + "); assert_snapshot!(cb.hexdump(), @"48837b1001bf04000000480f4f3b48893b"); } @@ -1457,13 +1458,12 @@ mod tests { asm.compile_with_num_regs(&mut cb, 3); - #[cfg(feature = "disasm")] - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movabs rax, 0x7f22c88d1930 0xa: mov ecx, 4 0xf: cmove rax, rcx 0x13: mov qword ptr [rbx], rax - ")); + "); assert_snapshot!(cb.hexdump(), @"48b830198dc8227f0000b904000000480f44c1488903"); } @@ -1476,11 +1476,11 @@ mod tests { asm.mov(shape_opnd, Opnd::Imm(0x8000_0001)); asm.compile_with_num_regs(&mut cb, 0); - #[cfg(feature = "disasm")] - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov dword ptr [rax], 0x80000001 0x6: mov dword ptr [rax], 0x80000001 - ")); + "); assert_snapshot!(cb.hexdump(), @"c70001000080c70001000080"); } @@ -1496,7 +1496,8 @@ mod tests { asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + + assert_disasm_snapshot!(cb.disasm(), @" 0x0: push rbp 0x1: mov rbp, rsp 0x4: push r13 @@ -1514,7 +1515,7 @@ mod tests { 0x22: sub rsp, 0x30 0x26: mov rsp, rbp 0x29: pop rbp - ")); + "); assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3554889e54883ec304889ec5d"); } @@ -1529,10 +1530,10 @@ mod tests { let gc_offsets = asm.x86_emit(&mut cb).unwrap(); assert_eq!(1, gc_offsets.len(), "VALUE source operand should be reported as gc offset"); - cb.with_disasm(|disasm| assert_snapshot!(disasm, @" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movabs r11, 0x1000 0xa: mov qword ptr [rbx], r11 - ")); + "); assert_snapshot!(cb.hexdump(), @"49bb00100000000000004c891b"); } } From ff198ad904652ddea3af0a976867ae2c2b6cf5b8 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Fri, 3 Oct 2025 12:25:20 -0400 Subject: [PATCH 0091/2435] Add assertion to rb_gc_impl_writebarrier We should only be executing WBs when GC is not running. We ran into this issue when debugging 3cd2407045a67838cf2ab949e5164676b6870958. --- gc/default/default.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gc/default/default.c b/gc/default/default.c index 9b40fed09768a6..c23adae0627988 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6044,6 +6044,7 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (SPECIAL_CONST_P(b)) return; + GC_ASSERT(!during_gc); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE); From 8337de95bc6f89c20cf16ac98aaef8e86c9a4992 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 3 Oct 2025 23:12:19 +0100 Subject: [PATCH 0092/2435] ZJIT: Add HIR for calling Cfunc with frame (#14661) * ZJIT: Add HIR for CCallWithFrame * ZJIT: Update stats to count not inlined cfunc calls * ZJIT: Stops optimizing SendWithoutBlock when TracePoint is activated * ZJIT: Fallback to SendWithoutBlock when CCallWithFrame has too many args * ZJIT: Rename cfun -> cfunc --- zjit.rb | 2 +- zjit/src/codegen.rs | 52 +++++++++++-- zjit/src/hir.rs | 185 +++++++++++++++++++++++++++----------------- zjit/src/state.rs | 12 +-- zjit/src/stats.rs | 10 ++- 5 files changed, 170 insertions(+), 91 deletions(-) diff --git a/zjit.rb b/zjit.rb index 8a037e35a0a007..1dccdefca273b1 100644 --- a/zjit.rb +++ b/zjit.rb @@ -153,7 +153,7 @@ def stats_string stats = self.stats # Show counters independent from exit_* or dynamic_send_* - print_counters_with_prefix(prefix: 'not_optimized_cfuncs_', prompt: 'unoptimized sends to C functions', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b7c3dc3532e903..1c1bd2e07f3ebb 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -404,9 +404,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), - Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args)), - Insn::CCallVariadic { cfun, recv, args, name: _, cme, state } => { - gen_ccall_variadic(jit, asm, *cfun, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) + Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), + Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self + gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), + Insn::CCallWithFrame { cfunc, args, cme, state, .. } => gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, &function.frame_state(*state)), + Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state } => { + gen_ccall_variadic(jit, asm, *cfunc, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), @@ -664,11 +667,46 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian }); } +/// Generate code for a C function call that pushes a frame +fn gen_ccall_with_frame(jit: &mut JITState, asm: &mut Assembler, cfunc: *const u8, args: Vec, cme: *const rb_callable_method_entry_t, state: &FrameState) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + gen_push_frame(asm, args.len(), state, ControlFrame { + recv: args[0], + iseq: None, + cme, + frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, + }); + + asm_comment!(asm, "switch to new SP register"); + let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let new_sp = asm.add(SP, sp_offset.into()); + asm.mov(SP, new_sp); + + asm_comment!(asm, "switch to new CFP"); + let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); + asm.mov(CFP, new_cfp); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + + let result = asm.ccall(cfunc, args); + + asm_comment!(asm, "pop C frame"); + let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); + asm.mov(CFP, new_cfp); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + + asm_comment!(asm, "restore SP register for the caller"); + let new_sp = asm.sub(SP, sp_offset.into()); + asm.mov(SP, new_sp); + + result +} + /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. -fn gen_ccall(asm: &mut Assembler, cfun: *const u8, args: Vec) -> lir::Opnd { +fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, args: Vec) -> lir::Opnd { gen_incr_counter(asm, Counter::inline_cfunc_optimized_send_count); - asm.ccall(cfun, args) + asm.ccall(cfunc, args) } /// Generate code for a variadic C function call @@ -676,7 +714,7 @@ fn gen_ccall(asm: &mut Assembler, cfun: *const u8, args: Vec) -> lir::Opnd fn gen_ccall_variadic( jit: &mut JITState, asm: &mut Assembler, - cfun: *const u8, + cfunc: *const u8, recv: Opnd, args: Vec, cme: *const rb_callable_method_entry_t, @@ -707,7 +745,7 @@ fn gen_ccall_variadic( asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); let argv_ptr = gen_push_opnds(jit, asm, &args); - let result = asm.ccall(cfun, vec![args.len().into(), argv_ptr, recv]); + let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); gen_pop_opnds(asm, &args); asm_comment!(asm, "pop C frame"); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4c123f5621fa00..26145f46226fbb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -535,6 +535,7 @@ pub enum SendFallbackReason { SendWithoutBlockCfuncArrayVariadic, SendWithoutBlockNotOptimizedMethodType(MethodType), SendWithoutBlockDirectTooManyArgs, + CCallWithFrameTooManyArgs, ObjToStringNotString, /// Initial fallback reason for every instruction, which should be mutated to /// a more actionable reason when an attempt to specialize the instruction fails. @@ -644,14 +645,24 @@ pub enum Insn { IfTrue { val: InsnId, target: BranchEdge }, IfFalse { val: InsnId, target: BranchEdge }, - /// Call a C function + /// Call a C function without pushing a frame /// `name` is for printing purposes only - CCall { cfun: *const u8, args: Vec, name: ID, return_type: Type, elidable: bool }, + CCall { cfunc: *const u8, args: Vec, name: ID, return_type: Type, elidable: bool }, + + /// Call a C function that pushes a frame + CCallWithFrame { + cd: *const rb_call_data, // cd for falling back to SendWithoutBlock + cfunc: *const u8, + args: Vec, + cme: *const rb_callable_method_entry_t, + name: ID, + state: InsnId + }, /// Call a variadic C function with signature: func(int argc, VALUE *argv, VALUE recv) /// This handles frame setup, argv creation, and frame teardown all in one CCallVariadic { - cfun: *const u8, + cfunc: *const u8, recv: InsnId, args: Vec, cme: *const rb_callable_method_entry_t, @@ -1020,15 +1031,22 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, - Insn::CCall { cfun, args, name, return_type: _, elidable: _ } => { - write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfun))?; + Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => { + write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } Ok(()) }, - Insn::CCallVariadic { cfun, recv, args, name, .. } => { - write!(f, "CCallVariadic {}@{:p}, {recv}", name.contents_lossy(), self.ptr_map.map_ptr(cfun))?; + Insn::CCallWithFrame { cfunc, args, name, .. } => { + write!(f, "CallCFunc {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + for arg in args { + write!(f, ", {arg}")?; + } + Ok(()) + }, + Insn::CCallVariadic { cfunc, recv, args, name, .. } => { + write!(f, "CCallVariadic {}@{:p}, {recv}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } @@ -1545,9 +1563,10 @@ impl Function { &HashDup { val, state } => HashDup { val: find!(val), state }, &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, - &CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun, args: find_vec!(args), name, return_type, elidable }, - &CCallVariadic { cfun, recv, ref args, cme, name, state } => CCallVariadic { - cfun, recv: find!(recv), args: find_vec!(args), cme, name, state + &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, + &CCallWithFrame { cd, cfunc, ref args, cme, name, state } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state) }, + &CCallVariadic { cfunc, recv, ref args, cme, name, state } => CCallVariadic { + cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state }, &Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, @@ -1646,6 +1665,7 @@ impl Function { Insn::NewRangeFixnum { .. } => types::RangeExact, Insn::ObjectAlloc { .. } => types::HeapObject, Insn::ObjectAllocClass { class, .. } => Type::from_class(*class), + Insn::CCallWithFrame { .. } => types::BasicObject, Insn::CCall { return_type, .. } => *return_type, Insn::CCallVariadic { .. } => types::BasicObject, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), @@ -2242,6 +2262,10 @@ impl Function { /// Optimize SendWithoutBlock that land in a C method to a direct CCall without /// runtime lookup. fn optimize_c_calls(&mut self) { + if unsafe { rb_zjit_method_tracing_currently_enabled() } { + return; + } + fn gen_patch_points_for_optimized_ccall(fun: &mut Function, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); @@ -2254,9 +2278,9 @@ impl Function { self_type: Type, send: Insn, send_insn_id: InsnId, - ) -> Result<(), Option<*const rb_callable_method_entry_struct>> { + ) -> Result<(), ()> { let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else { - return Err(None); + return Err(()); }; let call_info = unsafe { (*cd).ci }; @@ -2268,20 +2292,20 @@ impl Function { (class, None) } else { let iseq_insn_idx = fun.frame_state(state).insn_idx; - let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(None) }; + let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) }; (recv_type.class(), Some(recv_type)) }; // Do method lookup let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; if method.is_null() { - return Err(None); + return Err(()); } // Filter for C methods let def_type = unsafe { get_cme_def_type(method) }; if def_type != VM_METHOD_TYPE_CFUNC { - return Err(None); + return Err(()); } // Find the `argc` (arity) of the C method, which describes the parameters it expects @@ -2293,48 +2317,53 @@ impl Function { // // Bail on argc mismatch if argc != cfunc_argc as u32 { - return Err(Some(method)); + return Err(()); } + let ci_flags = unsafe { vm_ci_flag(call_info) }; + + if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { + return Err(()); + } + + gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); + } + if let Some(profiled_type) = profiled_type { + // Guard receiver class + recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + let cfunc = unsafe { get_mct_func(cfunc) }.cast(); + let mut cfunc_args = vec![recv]; + cfunc_args.append(&mut args); + // Filter for a leaf and GC free function use crate::cruby_methods::FnProperties; - let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = - ZJITState::get_method_annotations().get_cfunc_properties(method) - else { - fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockCfuncNotVariadic); - return Err(Some(method)); - }; - - let ci_flags = unsafe { vm_ci_flag(call_info) }; // Filter for simple call sites (i.e. no splats etc.) - if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); - - if recv_class.instance_can_have_singleton_class() { - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); - } - if let Some(profiled_type) = profiled_type { - // Guard receiver class - recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + // Commit to the replacement. Put PatchPoint. + if let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = ZJITState::get_method_annotations().get_cfunc_properties(method) { + let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); + fun.make_equal_to(send_insn_id, ccall); + } else { + if get_option!(stats) { + count_not_inlined_cfunc(fun, block, method); } - - let cfun = unsafe { get_mct_func(cfunc) }.cast(); - let mut cfunc_args = vec![recv]; - cfunc_args.append(&mut args); - let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable }); + let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state }); fun.make_equal_to(send_insn_id, ccall); - return Ok(()) } + + return Ok(()); } // Variadic method -1 => { - if unsafe { rb_zjit_method_tracing_currently_enabled() } { - return Err(None); - } // The method gets a pointer to the first argument // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { + if get_option!(stats) { + count_not_inlined_cfunc(fun, block, method); + } gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { @@ -2345,9 +2374,9 @@ impl Function { recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } - let cfun = unsafe { get_mct_func(cfunc) }.cast(); + let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let ccall = fun.push_insn(block, Insn::CCallVariadic { - cfun, + cfunc, recv, args, cme: method, @@ -2369,7 +2398,20 @@ impl Function { _ => unreachable!("unknown cfunc kind: argc={argc}") } - Err(Some(method)) + Err(()) + } + + fn count_not_inlined_cfunc(fun: &mut Function, block: BlockId, cme: *const rb_callable_method_entry_t) { + let owner = unsafe { (*cme).owner }; + let called_id = unsafe { (*cme).called_id }; + let class_name = get_class_name(owner); + let method_name = called_id.contents_lossy(); + let qualified_method_name = format!("{}#{}", class_name, method_name); + let not_inlined_cfunc_counter_pointers = ZJITState::get_not_inlined_cfunc_counter_pointers(); + let counter_ptr = not_inlined_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); + let counter_ptr = &mut **counter_ptr as *mut u64; + + fun.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); } for block in self.rpo() { @@ -2378,23 +2420,8 @@ impl Function { for insn_id in old_insns { if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) { let recv_type = self.type_of(recv); - match reduce_to_ccall(self, block, recv_type, send, insn_id) { - Ok(()) => continue, - Err(Some(cme)) => { - if get_option!(stats) { - let owner = unsafe { (*cme).owner }; - let called_id = unsafe { (*cme).called_id }; - let class_name = get_class_name(owner); - let method_name = called_id.contents_lossy(); - let qualified_method_name = format!("{}#{}", class_name, method_name); - let unoptimized_cfunc_counter_pointers = ZJITState::get_unoptimized_cfunc_counter_pointers(); - let counter_ptr = unoptimized_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); - let counter_ptr = &mut **counter_ptr as *mut u64; - - self.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); - } - } - _ => {} + if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() { + continue; } } self.push_insn_id(block, insn_id); @@ -2637,7 +2664,8 @@ impl Function { worklist.extend(args); worklist.push_back(state); } - &Insn::InvokeBuiltin { ref args, state, .. } + &Insn::CCallWithFrame { ref args, state, .. } + | &Insn::InvokeBuiltin { ref args, state, .. } | &Insn::InvokeBlock { ref args, state, .. } => { worklist.extend(args); worklist.push_back(state) @@ -10864,8 +10892,10 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:HashExact = NewHash - v13:BasicObject = SendWithoutBlock v11, :dup - v15:BasicObject = SendWithoutBlock v13, :freeze + PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v24:BasicObject = CallCFunc dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 "); @@ -10955,8 +10985,10 @@ mod opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:ArrayExact = NewArray - v13:BasicObject = SendWithoutBlock v11, :dup - v15:BasicObject = SendWithoutBlock v13, :freeze + PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:BasicObject = CallCFunc dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 "); @@ -11047,8 +11079,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v10 - v14:BasicObject = SendWithoutBlock v12, :dup - v16:BasicObject = SendWithoutBlock v14, :freeze + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CallCFunc dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :freeze CheckInterrupts Return v16 "); @@ -11140,8 +11174,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v10 - v14:BasicObject = SendWithoutBlock v12, :dup - v16:BasicObject = SendWithoutBlock v14, :-@ + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CallCFunc dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :-@ CheckInterrupts Return v16 "); @@ -11279,8 +11315,11 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v25:BasicObject = GuardTypeNot v9, String - v26:BasicObject = SendWithoutBlock v9, :to_s - v17:String = AnyToString v9, str: v26 + PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v30:ArrayExact = GuardType v9, ArrayExact + v31:BasicObject = CallCFunc to_s@0x1040, v30 + v17:String = AnyToString v9, str: v31 v19:StringExact = StringConcat v13, v17 CheckInterrupts Return v19 diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 8f88d2424436ab..409cac7e9bb421 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -51,8 +51,8 @@ pub struct ZJITState { /// Trampoline to call function_stub_hit function_stub_hit_trampoline: CodePtr, - /// Counter pointers for unoptimized C functions - unoptimized_cfunc_counter_pointers: HashMap>, + /// Counter pointers for full frame C functions + full_frame_cfunc_counter_pointers: HashMap>, /// Locations of side exists within generated code exit_locations: Option, @@ -97,7 +97,7 @@ impl ZJITState { exit_trampoline, function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, - unoptimized_cfunc_counter_pointers: HashMap::new(), + full_frame_cfunc_counter_pointers: HashMap::new(), exit_locations, }; unsafe { ZJIT_STATE = Some(zjit_state); } @@ -162,9 +162,9 @@ impl ZJITState { &mut ZJITState::get_instance().send_fallback_counters } - /// Get a mutable reference to unoptimized cfunc counter pointers - pub fn get_unoptimized_cfunc_counter_pointers() -> &'static mut HashMap> { - &mut ZJITState::get_instance().unoptimized_cfunc_counter_pointers + /// Get a mutable reference to full frame cfunc counter pointers + pub fn get_not_inlined_cfunc_counter_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().full_frame_cfunc_counter_pointers } /// Was --zjit-save-compiled-iseqs specified? diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index d1c1aa7e032a3d..3bfeb46e6c871e 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -148,6 +148,7 @@ make_counters! { send_fallback_send_without_block_cfunc_array_variadic, send_fallback_send_without_block_not_optimized_method_type, send_fallback_send_without_block_direct_too_many_args, + send_fallback_ccall_with_frame_too_many_args, send_fallback_obj_to_string_not_string, send_fallback_not_optimized_instruction, } @@ -318,6 +319,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, + CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, ObjToStringNotString => send_fallback_obj_to_string_not_string, NotOptimizedInstruction(_) => send_fallback_not_optimized_instruction, } @@ -470,10 +472,10 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_f64!(hash, "ratio_in_zjit", 100.0 * zjit_insn_count as f64 / total_insn_count as f64); } - // Set unoptimized cfunc counters - let unoptimized_cfuncs = ZJITState::get_unoptimized_cfunc_counter_pointers(); - for (signature, counter) in unoptimized_cfuncs.iter() { - let key_string = format!("not_optimized_cfuncs_{}", signature); + // Set not inlined cfunc counters + let not_inlined_cfuncs = ZJITState::get_not_inlined_cfunc_counter_pointers(); + for (signature, counter) in not_inlined_cfuncs.iter() { + let key_string = format!("not_inlined_cfuncs_{}", signature); set_stat_usize!(hash, &key_string, **counter); } From 8eead759c1b2a93c66d80089ad9acf166f37d507 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 15:15:05 -0700 Subject: [PATCH 0093/2435] ZJIT: Relax the limit of cfunc args by 1 Follow-up on https://github.com/ruby/ruby/pull/14661 Unlike SendWithoutBlockDirect, `args` has every argument given to the C call. So there's no `+ 1` for this HIR. --- zjit/src/codegen.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1c1bd2e07f3ebb..0c720734daaba8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -405,7 +405,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), - Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self + // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. + Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, args, cme, state, .. } => gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, &function.frame_state(*state)), Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state } => { From 77331b99c606d187b6df32261bc99493484f36ac Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 15:36:19 -0700 Subject: [PATCH 0094/2435] ZJIT: Count CCallWithFrame as optimized_send_count (#14722) --- zjit.rb | 1 + zjit/src/codegen.rs | 2 ++ zjit/src/stats.rs | 1 + 3 files changed, 4 insertions(+) diff --git a/zjit.rb b/zjit.rb index 1dccdefca273b1..b44154ad9cef1c 100644 --- a/zjit.rb +++ b/zjit.rb @@ -172,6 +172,7 @@ def stats_string :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, + :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0c720734daaba8..b1a2cb672641ef 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -670,6 +670,8 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian /// Generate code for a C function call that pushes a frame fn gen_ccall_with_frame(jit: &mut JITState, asm: &mut Assembler, cfunc: *const u8, args: Vec, cme: *const rb_callable_method_entry_t, state: &FrameState) -> lir::Opnd { + gen_incr_counter(asm, Counter::non_variadic_cfunc_optimized_send_count); + gen_prepare_non_leaf_call(jit, asm, state); gen_push_frame(asm, args.len(), state, ControlFrame { diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 3bfeb46e6c871e..323af0f3ed2695 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -157,6 +157,7 @@ make_counters! { optimized_send { iseq_optimized_send_count, inline_cfunc_optimized_send_count, + non_variadic_cfunc_optimized_send_count, variadic_cfunc_optimized_send_count, } From e8f879e9f5d53aea7cd29ef064e25930323cb857 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 3 Oct 2025 20:33:21 -0400 Subject: [PATCH 0095/2435] Use LSAN_OPTIONS instead of ASAN_OPTIONS in mkmf Newer versions of clang's LSAN uses LSAN_OPTIONS environment variable instead of ASAN_OPTIONS. --- lib/mkmf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mkmf.rb b/lib/mkmf.rb index d0974b05436928..201732d274ebf4 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -419,7 +419,7 @@ def expand_command(commands, envs = libpath_env) # disable ASAN leak reporting - conftest programs almost always don't bother # to free their memory. - envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS') + envs['LSAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('LSAN_OPTIONS') return envs, expand[commands] end From 269ada2421818c0216064271fd22a497bf266552 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 22:38:04 -0700 Subject: [PATCH 0096/2435] Migrate notify-slack-commits.rb to ruby/ruby from ruby/git.ruby-lang.org as of: https://github.com/ruby/git.ruby-lang.org/commit/b0dfa734297cc9aea33f24a1e29f8853cc5761e9 --- .github/workflows/check_misc.yml | 11 +++- tool/notify-slack-commits.rb | 87 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tool/notify-slack-commits.rb diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 7c745acf568202..4b62a937cbaede 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -28,7 +28,16 @@ jobs: # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - # Run this step first to make sure auto-style commits are pushed + # Run this step first to make the notification available before any other failure + - name: Notify commit to Slack + run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + continue-on-error: true # The next auto-style should always run + + # Run this step early to make sure auto-style commits are pushed - name: Auto-correct code styles run: | set -x diff --git a/tool/notify-slack-commits.rb b/tool/notify-slack-commits.rb new file mode 100644 index 00000000000000..39a1ab71628b01 --- /dev/null +++ b/tool/notify-slack-commits.rb @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require "net/https" +require "open3" +require "json" +require "digest/md5" + +SLACK_WEBHOOK_URLS = [ + ENV.fetch("SLACK_WEBHOOK_URL").chomp, # ruby-lang#alerts + ENV.fetch("SLACK_WEBHOOK_URL_COMMITS").chomp, # ruby-lang#commits + ENV.fetch("SLACK_WEBHOOK_URL_RUBY_JP").chomp, # ruby-jp#ruby-commits +] +GRAVATAR_OVERRIDES = { + "nagachika@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars0.githubusercontent.com/u/21976", + "noreply@github.com" => "https://avatars1.githubusercontent.com/u/9919", + "nurse@users.noreply.github.com" => "https://avatars1.githubusercontent.com/u/13423", + "svn-admin@ruby-lang.org" => "https://avatars1.githubusercontent.com/u/29403229", + "svn@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars1.githubusercontent.com/u/29403229", + "usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars2.githubusercontent.com/u/17790", + "usa@ruby-lang.org" => "https://avatars2.githubusercontent.com/u/17790", + "yui-knk@ruby-lang.org" => "https://avatars0.githubusercontent.com/u/5356517", + "znz@users.noreply.github.com" => "https://avatars3.githubusercontent.com/u/11857", +} + +def escape(s) + s.gsub(/[&<>]/, "&" => "&", "<" => "<", ">" => ">") +end + +ARGV.each_slice(3) do |oldrev, newrev, refname| + out, = Open3.capture2("git", "rev-parse", "--symbolic", "--abbrev-ref", refname) + branch = out.strip + + out, = Open3.capture2("git", "log", "--pretty=format:%H\n%h\n%cn\n%ce\n%ct\n%B", "--abbrev=10", "-z", "#{oldrev}..#{newrev}") + + attachments = [] + out.split("\0").reverse_each do |s| + sha, sha_abbr, committer, committeremail, committertime, body = s.split("\n", 6) + subject, body = body.split("\n", 2) + + # Append notes content to `body` if it's notes + if refname.match(%r[\Arefs/notes/\w+\z]) + # `--diff-filter=AM -M` to exclude rename by git's directory optimization + object = IO.popen(["git", "diff", "--diff-filter=AM", "-M", "--name-only", "#{sha}^..#{sha}"], &:read).chomp + if md = object.match(/\A(?\h{2})\/?(?\h{38})\z/) + body = [body, IO.popen(["git", "notes", "show", md[:prefix] + md[:rest]], &:read)].join + end + end + + gravatar = GRAVATAR_OVERRIDES.fetch(committeremail) do + "https://www.gravatar.com/avatar/#{ Digest::MD5.hexdigest(committeremail.downcase) }" + end + + attachments << { + title: "#{ sha_abbr } (#{ branch }): #{ escape(subject) }", + title_link: "https://github.com/ruby/ruby/commit/#{ sha }", + text: escape((body || "").strip), + footer: committer, + footer_icon: gravatar, + ts: committertime.to_i, + color: '#24282D', + } + end + + # 100 attachments cannot be exceeded. 20 is recommended. https://api.slack.com/docs/message-attachments + attachments.each_slice(20).each do |attachments_group| + payload = { attachments: attachments_group } + + #Net::HTTP.post( + # URI.parse(SLACK_WEBHOOK_URL), + # JSON.generate(payload), + # "Content-Type" => "application/json" + #) + responses = SLACK_WEBHOOK_URLS.map do |url| + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.start do + req = Net::HTTP::Post.new(uri.path) + req.set_form_data(payload: payload.to_json) + http.request(req) + end + end + + results = responses.map { |resp| "#{resp.code} (#{resp.body})" }.join(', ') + puts "#{results} -- #{payload.to_json}" + end +end From ba48e6c9ca91653cf791e85cc1b245876cda18a4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 22:41:32 -0700 Subject: [PATCH 0097/2435] Propagate secrets to environment variables --- .github/workflows/check_misc.yml | 3 +++ tool/notify-slack-commits.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 4b62a937cbaede..3275e4ccc32945 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -34,6 +34,9 @@ jobs: env: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} + SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }} + SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} continue-on-error: true # The next auto-style should always run diff --git a/tool/notify-slack-commits.rb b/tool/notify-slack-commits.rb index 39a1ab71628b01..73e22b9a03a83c 100644 --- a/tool/notify-slack-commits.rb +++ b/tool/notify-slack-commits.rb @@ -6,7 +6,7 @@ require "digest/md5" SLACK_WEBHOOK_URLS = [ - ENV.fetch("SLACK_WEBHOOK_URL").chomp, # ruby-lang#alerts + ENV.fetch("SLACK_WEBHOOK_URL_ALERTS").chomp, # ruby-lang#alerts ENV.fetch("SLACK_WEBHOOK_URL_COMMITS").chomp, # ruby-lang#commits ENV.fetch("SLACK_WEBHOOK_URL_RUBY_JP").chomp, # ruby-jp#ruby-commits ] From 54c716dad6a7ce8a350176397469792b32a0f27a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 22:49:46 -0700 Subject: [PATCH 0098/2435] Fetch more commits to fix notify-slack-commits --- .github/workflows/check_misc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 3275e4ccc32945..31d6695d446e85 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -20,6 +20,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: + fetch-depth: 100 # for notify-slack-commits token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - uses: ./.github/actions/setup/directories From 63de26c4ec9514d54545fe55d41f060c0acacf20 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 22:50:49 -0700 Subject: [PATCH 0099/2435] Run notify-slack-commits before `make up` too --- .github/workflows/check_misc.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 31d6695d446e85..9bcc6800967060 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -23,13 +23,7 @@ jobs: fetch-depth: 100 # for notify-slack-commits token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ./.github/actions/setup/directories - with: - makeup: true - # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - - # Run this step first to make the notification available before any other failure + # Run this step first (even before `make up` in the next step) to make the notification available before any other failure - name: Notify commit to Slack run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master env: @@ -41,6 +35,12 @@ jobs: if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} continue-on-error: true # The next auto-style should always run + - uses: ./.github/actions/setup/directories + with: + makeup: true + # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN + checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) + # Run this step early to make sure auto-style commits are pushed - name: Auto-correct code styles run: | From 5941659e9b860a0a4bc4c64ef990f05a9dcebbe6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 22:54:24 -0700 Subject: [PATCH 0100/2435] Change the webhook URL used for alerts That secret appears use a different configuration from the intended one. --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 9bcc6800967060..5ff6c0b5b0e337 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -29,7 +29,7 @@ jobs: env: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} - SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL_ALERTS }} SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }} SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} From ecc5ebc69a76f3a267a90b9af3d6754b3cc21265 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:09:43 -0700 Subject: [PATCH 0101/2435] Migrate notes-github-pr to ruby/ruby (#14725) from ruby/git.ruby-lang.org as of: https://github.com/ruby/git.ruby-lang.org/commit/f3ed893e946ec66cac77af5859ac879c5983d3a3 --- .github/workflows/check_misc.yml | 7 ++ tool/notes-github-pr.rb | 146 +++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 tool/notes-github-pr.rb diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 5ff6c0b5b0e337..d4da4ecc27b62f 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -132,6 +132,13 @@ jobs: name: ${{ steps.docs.outputs.htmlout }} if: ${{ steps.docs.outcome == 'success' }} + - name: Push PR notes to GitHub + run: ruby tool/notify-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/tool/notes-github-pr.rb b/tool/notes-github-pr.rb new file mode 100644 index 00000000000000..bc888f548e03c5 --- /dev/null +++ b/tool/notes-github-pr.rb @@ -0,0 +1,146 @@ +#!/usr/bin/env ruby +# Add GitHub pull request reference / author info to git notes. + +require 'net/http' +require 'uri' +require 'tmpdir' +require 'json' +require 'yaml' + +# Conversion for people whose GitHub account name and SVN_ACCOUNT_NAME are different. +GITHUB_TO_SVN = { + 'amatsuda' => 'a_matsuda', + 'matzbot' => 'git', + 'jeremyevans' => 'jeremy', + 'znz' => 'kazu', + 'k-tsj' => 'ktsj', + 'nurse' => 'naruse', + 'ioquatix' => 'samuel', + 'suketa' => 'suke', + 'unak' => 'usa', +} + +SVN_TO_EMAILS = YAML.safe_load(File.read(File.expand_path('../config/email.yml', __dir__))) + +class GitHub + ENDPOINT = URI.parse('https://api.github.com') + + def initialize(access_token) + @access_token = access_token + end + + # https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/ + def pulls(owner:, repo:, commit_sha:) + resp = get("/repos/#{owner}/#{repo}/commits/#{commit_sha}/pulls", accept: 'application/vnd.github.groot-preview+json') + JSON.parse(resp.body) + end + + # https://developer.github.com/v3/pulls/#get-a-single-pull-request + def pull_request(owner:, repo:, number:) + resp = get("/repos/#{owner}/#{repo}/pulls/#{number}") + JSON.parse(resp.body) + end + + # https://developer.github.com/v3/users/#get-a-single-user + def user(username:) + resp = get("/users/#{username}") + JSON.parse(resp.body) + end + + private + + def get(path, accept: 'application/vnd.github.v3+json') + Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: ENDPOINT.scheme == 'https') do |http| + headers = { 'Accept': accept, 'Authorization': "bearer #{@access_token}" } + http.get(path, headers).tap(&:value) + end + end +end + +module Git + class << self + def abbrev_ref(refname, repo_path:) + git('rev-parse', '--symbolic', '--abbrev-ref', refname, repo_path: repo_path).strip + end + + def rev_list(arg, first_parent: false, repo_path: nil) + git('rev-list', *[('--first-parent' if first_parent)].compact, arg, repo_path: repo_path).lines.map(&:chomp) + end + + def commit_message(sha) + git('log', '-1', '--pretty=format:%B', sha) + end + + def notes_message(sha) + git('log', '-1', '--pretty=format:%N', sha) + end + + def committer_name(sha) + git('log', '-1', '--pretty=format:%cn', sha) + end + + def committer_email(sha) + git('log', '-1', '--pretty=format:%cE', sha) + end + + private + + def git(*cmd, repo_path: nil) + env = {} + if repo_path + env['GIT_DIR'] = repo_path + end + out = IO.popen(env, ['git', *cmd], &:read) + unless $?.success? + abort "Failed to execute: git #{cmd.join(' ')}\n#{out}" + end + out + end + end +end + +github = GitHub.new(ENV.fetch("GITHUB_TOKEN")) + +repo_path, *rest = ARGV +rest.each_slice(3).map do |oldrev, newrev, refname| + branch = Git.abbrev_ref(refname, repo_path: repo_path) + next if branch != 'master' # we use pull requests only for master branches + + Dir.mktmpdir do |workdir| + # Clone a branch and fetch notes + depth = Git.rev_list("#{oldrev}..#{newrev}", repo_path: repo_path).size + 50 + system('git', 'clone', "--depth=#{depth}", "--branch=#{branch}", "file://#{repo_path}", workdir) + Dir.chdir(workdir) + system('git', 'fetch', 'origin', 'refs/notes/commits:refs/notes/commits') + + updated = false + Git.rev_list("#{oldrev}..#{newrev}", first_parent: true).each do |sha| + github.pulls(owner: 'ruby', repo: 'ruby', commit_sha: sha).each do |pull| + number = pull.fetch('number') + url = pull.fetch('html_url') + next unless url.start_with?('https://github.com/ruby/ruby/pull/') + + # "Merged" notes for "Squash and merge" + message = Git.commit_message(sha) + notes = Git.notes_message(sha) + if !message.include?(url) && !message.match(/[ (]##{number}[) ]/) && !notes.include?(url) + system('git', 'notes', 'append', '-m', "Merged: #{url}", sha) + updated = true + end + + # "Merged-By" notes for "Rebase and merge" + if Git.committer_name(sha) == 'GitHub' && Git.committer_email(sha) == 'noreply@github.com' + username = github.pull_request(owner: 'ruby', repo: 'ruby', number: number).fetch('merged_by').fetch('login') + email = github.user(username: username).fetch('email') + email ||= SVN_TO_EMAILS[GITHUB_TO_SVN.fetch(username, username)]&.first + system('git', 'notes', 'append', '-m', "Merged-By: #{username}#{(" <#{email}>" if email)}", sha) + updated = true + end + end + end + + if updated + system('git', 'push', 'origin', 'refs/notes/commits') + end + end +end From e40d3c5bd8d8c1a26a75717aa7fbe39622a715bb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:16:16 -0700 Subject: [PATCH 0102/2435] Fix the path of notes-github-pr --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index d4da4ecc27b62f..347e8a9b1984bf 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -133,7 +133,7 @@ jobs: if: ${{ steps.docs.outcome == 'success' }} - name: Push PR notes to GitHub - run: ruby tool/notify-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + run: ruby tool/notes-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master env: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} From 4ea84bf58b3690c3e7e61cbf30ad6252863ce133 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:29:56 -0700 Subject: [PATCH 0103/2435] Fix a missing reference to config/email.yml --- tool/notes-github-pr.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/notes-github-pr.rb b/tool/notes-github-pr.rb index bc888f548e03c5..2bea10575ba0ff 100644 --- a/tool/notes-github-pr.rb +++ b/tool/notes-github-pr.rb @@ -20,7 +20,8 @@ 'unak' => 'usa', } -SVN_TO_EMAILS = YAML.safe_load(File.read(File.expand_path('../config/email.yml', __dir__))) +EMAIL_YML_URL = 'https://raw.githubusercontent.com/ruby/git.ruby-lang.org/refs/heads/master/config/email.yml' +SVN_TO_EMAILS = YAML.safe_load(Net::HTTP.get_response(URI(EMAIL_YML_URL)).tap(&:value).body) class GitHub ENDPOINT = URI.parse('https://api.github.com') From 72f8e3e71b1e1e816f4f7fbaf7e4fafdeca87881 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:38:55 -0700 Subject: [PATCH 0104/2435] Make sure GITHUB_TOKEN is set --- .github/workflows/check_misc.yml | 1 + tool/notes-github-pr.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 347e8a9b1984bf..b332eef541468f 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -137,6 +137,7 @@ jobs: env: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - uses: ./.github/actions/slack diff --git a/tool/notes-github-pr.rb b/tool/notes-github-pr.rb index 2bea10575ba0ff..e282386eba15d4 100644 --- a/tool/notes-github-pr.rb +++ b/tool/notes-github-pr.rb @@ -100,7 +100,7 @@ def git(*cmd, repo_path: nil) end end -github = GitHub.new(ENV.fetch("GITHUB_TOKEN")) +github = GitHub.new(ENV.fetch('GITHUB_TOKEN')) repo_path, *rest = ARGV rest.each_slice(3).map do |oldrev, newrev, refname| From 6fd50e38691a76ee0aef90129f89c73aeae06f34 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:47:23 -0700 Subject: [PATCH 0105/2435] Run notes-github-pr while .git is still available https://github.com/ruby/ruby/actions/runs/18240911019/job/51942567201 --- .github/workflows/check_misc.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index b332eef541468f..62b0a472ab32d6 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -62,6 +62,14 @@ jobs: GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} + - name: Push PR notes to GitHub + run: ruby tool/notes-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + - name: Check if C-sources are US-ASCII run: | grep -r -n --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : @@ -132,14 +140,6 @@ jobs: name: ${{ steps.docs.outputs.htmlout }} if: ${{ steps.docs.outcome == 'success' }} - - name: Push PR notes to GitHub - run: ruby tool/notes-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master - env: - GITHUB_OLD_SHA: ${{ github.event.before }} - GITHUB_NEW_SHA: ${{ github.event.after }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot From 4da1fa3ff48c2e8db50ef9e7980c22f5f80ab876 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:51:55 -0700 Subject: [PATCH 0106/2435] Fix the GIT_DIR given to notes-github-pr --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 62b0a472ab32d6..e2eee89435dd4d 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -63,7 +63,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - name: Push PR notes to GitHub - run: ruby tool/notes-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master env: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} From 9c9f3bb6e2f5844867bf2a9c2784be1d92d1b566 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 3 Oct 2025 23:56:12 -0700 Subject: [PATCH 0107/2435] Configure git user for notes-github-pr --- .github/workflows/check_misc.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index e2eee89435dd4d..b820b9ce4c4cdd 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -68,6 +68,9 @@ jobs: GITHUB_OLD_SHA: ${{ github.event.before }} GITHUB_NEW_SHA: ${{ github.event.after }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git + EMAIL: svn-admin@ruby-lang.org if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - name: Check if C-sources are US-ASCII From d29b1435cd4fca619f5a8a0868650bd68b82811a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 4 Oct 2025 00:18:02 -0700 Subject: [PATCH 0108/2435] Stop cloning the repository into another path which seems to prevent it from fetching notes when the path is not the actual repository but a shallow-cloned repository. --- .github/workflows/check_misc.yml | 22 +++++------ tool/notes-github-pr.rb | 65 ++++++++++++++------------------ 2 files changed, 39 insertions(+), 48 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index b820b9ce4c4cdd..07c43a0c3b0788 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -62,17 +62,6 @@ jobs: GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - - name: Push PR notes to GitHub - run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master - env: - GITHUB_OLD_SHA: ${{ github.event.before }} - GITHUB_NEW_SHA: ${{ github.event.after }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - EMAIL: svn-admin@ruby-lang.org - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - - name: Check if C-sources are US-ASCII run: | grep -r -n --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : @@ -143,6 +132,17 @@ jobs: name: ${{ steps.docs.outputs.htmlout }} if: ${{ steps.docs.outcome == 'success' }} + - name: Push PR notes to GitHub + run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git + EMAIL: svn-admin@ruby-lang.org + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/tool/notes-github-pr.rb b/tool/notes-github-pr.rb index e282386eba15d4..d69d479cdf79f1 100644 --- a/tool/notes-github-pr.rb +++ b/tool/notes-github-pr.rb @@ -103,45 +103,36 @@ def git(*cmd, repo_path: nil) github = GitHub.new(ENV.fetch('GITHUB_TOKEN')) repo_path, *rest = ARGV -rest.each_slice(3).map do |oldrev, newrev, refname| - branch = Git.abbrev_ref(refname, repo_path: repo_path) - next if branch != 'master' # we use pull requests only for master branches - - Dir.mktmpdir do |workdir| - # Clone a branch and fetch notes - depth = Git.rev_list("#{oldrev}..#{newrev}", repo_path: repo_path).size + 50 - system('git', 'clone', "--depth=#{depth}", "--branch=#{branch}", "file://#{repo_path}", workdir) - Dir.chdir(workdir) - system('git', 'fetch', 'origin', 'refs/notes/commits:refs/notes/commits') - - updated = false - Git.rev_list("#{oldrev}..#{newrev}", first_parent: true).each do |sha| - github.pulls(owner: 'ruby', repo: 'ruby', commit_sha: sha).each do |pull| - number = pull.fetch('number') - url = pull.fetch('html_url') - next unless url.start_with?('https://github.com/ruby/ruby/pull/') - - # "Merged" notes for "Squash and merge" - message = Git.commit_message(sha) - notes = Git.notes_message(sha) - if !message.include?(url) && !message.match(/[ (]##{number}[) ]/) && !notes.include?(url) - system('git', 'notes', 'append', '-m', "Merged: #{url}", sha) - updated = true - end - - # "Merged-By" notes for "Rebase and merge" - if Git.committer_name(sha) == 'GitHub' && Git.committer_email(sha) == 'noreply@github.com' - username = github.pull_request(owner: 'ruby', repo: 'ruby', number: number).fetch('merged_by').fetch('login') - email = github.user(username: username).fetch('email') - email ||= SVN_TO_EMAILS[GITHUB_TO_SVN.fetch(username, username)]&.first - system('git', 'notes', 'append', '-m', "Merged-By: #{username}#{(" <#{email}>" if email)}", sha) - updated = true - end +rest.each_slice(3).map do |oldrev, newrev, _refname| + system('git', 'fetch', 'origin', 'refs/notes/commits:refs/notes/commits', exception: true) + + updated = false + Git.rev_list("#{oldrev}..#{newrev}", first_parent: true).each do |sha| + github.pulls(owner: 'ruby', repo: 'ruby', commit_sha: sha).each do |pull| + number = pull.fetch('number') + url = pull.fetch('html_url') + next unless url.start_with?('https://github.com/ruby/ruby/pull/') + + # "Merged" notes for "Squash and merge" + message = Git.commit_message(sha) + notes = Git.notes_message(sha) + if !message.include?(url) && !message.match(/[ (]##{number}[) ]/) && !notes.include?(url) + system('git', 'notes', 'append', '-m', "Merged: #{url}", sha, exception: true) + updated = true end - end - if updated - system('git', 'push', 'origin', 'refs/notes/commits') + # "Merged-By" notes for "Rebase and merge" + if Git.committer_name(sha) == 'GitHub' && Git.committer_email(sha) == 'noreply@github.com' + username = github.pull_request(owner: 'ruby', repo: 'ruby', number: number).fetch('merged_by').fetch('login') + email = github.user(username: username).fetch('email') + email ||= SVN_TO_EMAILS[GITHUB_TO_SVN.fetch(username, username)]&.first + system('git', 'notes', 'append', '-m', "Merged-By: #{username}#{(" <#{email}>" if email)}", sha, exception: true) + updated = true + end end end + + if updated + system('git', 'push', 'origin', 'refs/notes/commits', exception: true) + end end From 8eb28da3e73cd571cb06d1450ee87384e37b83a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 03:28:32 +0000 Subject: [PATCH 0109/2435] Bump actions/labeler from 5 to 6 Bumps [actions/labeler](https://github.com/actions/labeler) from 5 to 6. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/labeler dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e57cd86e2b3c7f..16dbac1afa0f18 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,4 +9,4 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 From 1858233ffaee482a73a91b796f02ebb7ae1306b9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 3 Oct 2025 19:51:01 -0400 Subject: [PATCH 0110/2435] Free the native thread of the main thread on FREE_AT_EXIT --- internal/thread.h | 2 ++ thread.c | 9 +++++++++ vm.c | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/thread.h b/internal/thread.h index 928126c3b0dd25..21efeeebc085d7 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -64,6 +64,8 @@ void rb_thread_io_close_wait(struct rb_io *); void rb_ec_check_ints(struct rb_execution_context_struct *ec); +void rb_thread_free_native_thread(void *th_ptr); + RUBY_SYMBOL_EXPORT_BEGIN void *rb_thread_prevent_fork(void *(*func)(void *), void *data); /* for ext/socket/raddrinfo.c */ diff --git a/thread.c b/thread.c index 55561001023dfe..0419fdf0544288 100644 --- a/thread.c +++ b/thread.c @@ -529,6 +529,15 @@ thread_cleanup_func(void *th_ptr, int atfork) rb_native_mutex_destroy(&th->interrupt_lock); } +void +rb_thread_free_native_thread(void *th_ptr) +{ + rb_thread_t *th = th_ptr; + + native_thread_destroy_atfork(th->nt); + th->nt = NULL; +} + static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *); static VALUE rb_thread_to_s(VALUE thread); diff --git a/vm.c b/vm.c index 8bdf9e4fc20ae2..c9bf3dce36d76c 100644 --- a/vm.c +++ b/vm.c @@ -3306,8 +3306,7 @@ ruby_vm_destruct(rb_vm_t *vm) rb_id_table_free(vm->constant_cache); set_free_table(vm->unused_block_warning_table); - xfree(th->nt); - th->nt = NULL; + rb_thread_free_native_thread(th); #ifndef HAVE_SETPROCTITLE ruby_free_proctitle(); From 8cc5e5c11d26b2af9acff9898c2b226e2e781e36 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 3 Sep 2025 20:36:42 +0900 Subject: [PATCH 0111/2435] Win32: Fix fallback parsing of CSI SGR sequences --- win32/win32.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index 72539e8a63b209..1a5308337a05d3 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -6793,47 +6793,54 @@ constat_attr(int count, const int *seq, WORD attr, WORD default_attr, int *rever case 1: bold = FOREGROUND_INTENSITY; break; + case 22: + bold = 0; + break; case 4: #ifndef COMMON_LVB_UNDERSCORE #define COMMON_LVB_UNDERSCORE 0x8000 #endif attr |= COMMON_LVB_UNDERSCORE; break; + case 24: + attr &= ~COMMON_LVB_UNDERSCORE; + break; case 7: rev = 1; break; + case 27: + rev = 0; + break; case 30: attr &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); break; - case 17: case 31: attr = (attr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN)) | FOREGROUND_RED; break; - case 18: case 32: attr = (attr & ~(FOREGROUND_BLUE | FOREGROUND_RED)) | FOREGROUND_GREEN; break; - case 19: case 33: attr = (attr & ~FOREGROUND_BLUE) | FOREGROUND_GREEN | FOREGROUND_RED; break; - case 20: case 34: attr = (attr & ~(FOREGROUND_GREEN | FOREGROUND_RED)) | FOREGROUND_BLUE; break; - case 21: case 35: attr = (attr & ~FOREGROUND_GREEN) | FOREGROUND_BLUE | FOREGROUND_RED; break; - case 22: case 36: attr = (attr & ~FOREGROUND_RED) | FOREGROUND_BLUE | FOREGROUND_GREEN; break; - case 23: case 37: attr |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; break; + case 38: /* 256-color or true color; N/A on old Command Prompt */ + break; + case 39: + attr = (attr & ~FOREGROUND_MASK) | (default_attr & FOREGROUND_MASK); + break; case 40: attr &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED); @@ -6859,6 +6866,11 @@ constat_attr(int count, const int *seq, WORD attr, WORD default_attr, int *rever case 47: attr |= BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED; break; + case 48: /* 256-color or true color; N/A on old Command Prompt */ + break; + case 49: + attr = (attr & ~BACKGROUND_MASK) | (default_attr & BACKGROUND_MASK); + break; } } attr |= bold; From 80a18e8f422e30204e7386fc9a1fc37667b20b2a Mon Sep 17 00:00:00 2001 From: Jason Frey Date: Wed, 13 Aug 2025 13:37:34 -0400 Subject: [PATCH 0112/2435] [ruby/pp] Support new instance_variables_to_inspect method from Ruby core This supports the new `instance_variables_to_inspect` method from Ruby core that was added in ruby/ruby#13555. If `instance_variables_to_inspect` is defined, then `pretty_print_instance_variables` will use it. Additionally, this commit introduces tests for both `pretty_print_instance_variables` and `instance_variables_to_inspect`. https://github.com/ruby/pp/commit/9cea466c95 --- lib/pp.rb | 3 ++- test/test_pp.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/pp.rb b/lib/pp.rb index 531839563167cf..eb9f80db4e4b25 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -399,7 +399,8 @@ def pretty_print_cycle(q) # This method should return an array of names of instance variables as symbols or strings as: # +[:@a, :@b]+. def pretty_print_instance_variables - instance_variables.sort + ivars = respond_to?(:instance_variables_to_inspect) ? instance_variables_to_inspect : instance_variables + ivars.sort end # Is #inspect implementation using #pretty_print. diff --git a/test/test_pp.rb b/test/test_pp.rb index c71445c9bc90ee..e721260e01ea38 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -130,6 +130,20 @@ def a.to_s() "aaa" end assert_equal("#{a.inspect}\n", result) end + def test_iv_hiding + a = Object.new + def a.pretty_print_instance_variables() [:@b] end + a.instance_eval { @a = "aaa"; @b = "bbb" } + assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) + end + + def test_iv_hiding_via_ruby + a = Object.new + def a.instance_variables_to_inspect() [:@b] end + a.instance_eval { @a = "aaa"; @b = "bbb" } + assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) + end + def test_basic_object a = BasicObject.new assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) From 340777078c88478bae1e39b10bd6409491fd584b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 4 Oct 2025 15:30:47 +0200 Subject: [PATCH 0113/2435] [ruby/pp] Fix ::Data warning on Ruby 2.7 * It was showing on require 'pp': lib/pp.rb:525: warning: constant ::Data is deprecated * Fixes https://github.com/ruby/pp/issues/51 https://github.com/ruby/pp/commit/4fd8f4e0bb --- lib/pp.rb | 9 ++++++++- test/test_pp.rb | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index eb9f80db4e4b25..f10fe7f2f0e3f9 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -490,6 +490,13 @@ def pretty_print_cycle(q) # :nodoc: end end +verbose, $VERBOSE = $VERBOSE, nil +begin + has_data_define = defined?(Data.define) +ensure + $VERBOSE = verbose +end + class Data # :nodoc: def pretty_print(q) # :nodoc: class_name = PP.mcall(self, Kernel, :class).name @@ -522,7 +529,7 @@ def pretty_print(q) # :nodoc: def pretty_print_cycle(q) # :nodoc: q.text sprintf("#", PP.mcall(self, Kernel, :class).name) end -end if defined?(Data.define) +end if has_data_define class Range # :nodoc: def pretty_print(q) # :nodoc: diff --git a/test/test_pp.rb b/test/test_pp.rb index e721260e01ea38..28da00e3e7769a 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -172,7 +172,14 @@ def test_struct assert_equal("#{a.inspect}\n", PP.pp(a, ''.dup)) unless RUBY_ENGINE == "truffleruby" end - if defined?(Data.define) + verbose, $VERBOSE = $VERBOSE, nil + begin + has_data_define = defined?(Data.define) + ensure + $VERBOSE = verbose + end + + if has_data_define D = Data.define(:aaa, :bbb) def test_data a = D.new("aaa", "bbb") From 4b50d0b41cc0a4c808d0d128e7068e4b3b9331bf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 00:06:25 +0900 Subject: [PATCH 0114/2435] [ruby/pp] Do not override the methods in set.rb These methods are defined for built-in `Set` class on Ruby 3.5. https://github.com/ruby/pp/commit/352081dbbf --- lib/pp.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index f10fe7f2f0e3f9..0e55441e574897 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -453,11 +453,11 @@ def pretty_print(pp) # :nodoc: } } } - end + end unless method_defined?(:pretty_print) def pretty_print_cycle(pp) # :nodoc: pp.text sprintf('#', empty? ? '' : '...') - end + end unless method_defined?(:pretty_print_cycle) end class << ENV # :nodoc: From 4ddbee33097c431e83d4dbc6a130349067bf7c0a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 00:52:32 +0900 Subject: [PATCH 0115/2435] [ruby/pp] Refine `Set#pretty_print` check https://github.com/ruby/pp/commit/6615b62d7b --- lib/pp.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index 0e55441e574897..ed801f010afb02 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -443,6 +443,13 @@ def pretty_print_cycle(q) # :nodoc: end end +if defined?(Set) + if set_pp = Set.instance_method(:initialize).source_location + set_pp = !set_pp.first.end_with?("/set.rb") # not defined in set.rb + else + set_pp = true # defined in C + end +end class Set # :nodoc: def pretty_print(pp) # :nodoc: pp.group(1, '#') { @@ -453,12 +460,12 @@ def pretty_print(pp) # :nodoc: } } } - end unless method_defined?(:pretty_print) + end def pretty_print_cycle(pp) # :nodoc: pp.text sprintf('#', empty? ? '' : '...') - end unless method_defined?(:pretty_print_cycle) -end + end +end if set_pp class << ENV # :nodoc: def pretty_print(q) # :nodoc: From ec1655d52019e66725efe94b5bc88b3a98af284e Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 5 Oct 2025 01:06:07 +0900 Subject: [PATCH 0116/2435] [ruby/pp] Update pp for Set to use new inspect format (https://github.com/ruby/pp/pull/43) Ruby 3.5 will use `Set[1, 2, 3]`. This updates pp to use the same format. https://github.com/ruby/pp/commit/507eebf711 --- lib/pp.rb | 12 +++++------- test/test_pp.rb | 13 +++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index ed801f010afb02..60602826e19f51 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -452,18 +452,16 @@ def pretty_print_cycle(q) # :nodoc: end class Set # :nodoc: def pretty_print(pp) # :nodoc: - pp.group(1, '#') { - pp.breakable - pp.group(1, '{', '}') { - pp.seplist(self) { |o| - pp.pp o - } + pp.group(1, "#{self.class.name}[", ']') { + pp.seplist(self) { |o| + pp.pp o } } end def pretty_print_cycle(pp) # :nodoc: - pp.text sprintf('#', empty? ? '' : '...') + name = self.class.name + pp.text(empty? ? "#{name}[]" : "#{name}[...]") end end if set_pp diff --git a/test/test_pp.rb b/test/test_pp.rb index 28da00e3e7769a..c5340a3e65ca10 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -2,11 +2,14 @@ require 'pp' require 'delegate' +require 'set' require 'test/unit' require 'ruby2_keywords' module PPTestModule +SetPP = Set.instance_method(:pretty_print).source_location[0].end_with?("/pp.rb") + class PPTest < Test::Unit::TestCase def test_list0123_12 assert_equal("[0, 1, 2, 3]\n", PP.pp([0,1,2,3], ''.dup, 12)) @@ -16,6 +19,10 @@ def test_list0123_11 assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], ''.dup, 11)) end + def test_set + assert_equal("Set[0, 1, 2, 3]\n", PP.pp(Set[0,1,2,3], ''.dup, 16)) + end if SetPP + OverriddenStruct = Struct.new("OverriddenStruct", :members, :class) def test_struct_override_members # [ruby-core:7865] a = OverriddenStruct.new(1,2) @@ -164,6 +171,12 @@ def test_hash assert_equal("#{a.inspect}\n", PP.pp(a, ''.dup)) end + def test_set + s = Set[] + s.add s + assert_equal("Set[Set[...]]\n", PP.pp(s, ''.dup)) + end if SetPP + S = Struct.new("S", :a, :b) def test_struct a = S.new(1,2) From deb9f45229bc32f3c607b001d46092f69f86664f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 10:03:19 +0900 Subject: [PATCH 0117/2435] [ruby/pp] Exclude out-of-scope test instead of omitting https://github.com/ruby/pp/commit/40b713d70f --- test/test_pp.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_pp.rb b/test/test_pp.rb index c5340a3e65ca10..8345e5e9873014 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -257,7 +257,6 @@ def test_hash end def test_hash_symbol_colon_key - omit if RUBY_VERSION < "3.4." no_quote = "{a: 1, a!: 1, a?: 1}" unicode_quote = "{\u{3042}: 1}" quote0 = '{"": 1}' @@ -270,7 +269,7 @@ def test_hash_symbol_colon_key assert_equal(quote1, PP.singleline_pp(eval(quote1), ''.dup)) assert_equal(quote2, PP.singleline_pp(eval(quote2), ''.dup)) assert_equal(quote3, PP.singleline_pp(eval(quote3), ''.dup)) - end + end if RUBY_VERSION >= "3.4." def test_hash_in_array omit if RUBY_ENGINE == "jruby" From 252c253b7537d6f5ac9ab612afcf6d8938cbb529 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 10:22:35 +0900 Subject: [PATCH 0118/2435] [ruby/pp] Simplify recursive state handling https://github.com/ruby/pp/commit/0e89466269 --- lib/pp.rb | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index 60602826e19f51..2c7f41eadda188 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -145,21 +145,13 @@ module PPMethods # Yields to a block # and preserves the previous set of objects being printed. def guard_inspect_key - if Thread.current[:__recursive_key__] == nil - Thread.current[:__recursive_key__] = {}.compare_by_identity - end - - if Thread.current[:__recursive_key__][:inspect] == nil - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity - end - - save = Thread.current[:__recursive_key__][:inspect] - + recursive_state = Thread.current[:__recursive_key__] ||= {}.compare_by_identity + save = recursive_state[:inspect] ||= {}.compare_by_identity begin - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity + recursive_state[:inspect] = {}.compare_by_identity yield ensure - Thread.current[:__recursive_key__][:inspect] = save + recursive_state[:inspect] = save end end @@ -167,9 +159,8 @@ def guard_inspect_key # to be pretty printed. Used to break cycles in chains of objects to be # pretty printed. def check_inspect_key(id) - Thread.current[:__recursive_key__] && - Thread.current[:__recursive_key__][:inspect] && - Thread.current[:__recursive_key__][:inspect].include?(id) + recursive_state = Thread.current[:__recursive_key__] or return false + recursive_state[:inspect]&.include?(id) end # Adds the object_id +id+ to the set of objects being pretty printed, so @@ -186,7 +177,7 @@ def pop_inspect_key(id) private def guard_inspect(object) recursive_state = Thread.current[:__recursive_key__] - if recursive_state && recursive_state.key?(:inspect) + if recursive_state&.key?(:inspect) begin push_inspect_key(object) yield From 1dd11fe8c8a238ea88f5abbdb673de313a82ede2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 10:25:23 +0900 Subject: [PATCH 0119/2435] [ruby/pp] Suppress warnings in test on Ruby 2.7 TODO: Revert when dropping Ruby 2.7 support. https://github.com/ruby/pp/commit/feb417e152 --- test/test_pp.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/test_pp.rb b/test/test_pp.rb index 8345e5e9873014..4a273e6edd0aec 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -273,8 +273,21 @@ def test_hash_symbol_colon_key def test_hash_in_array omit if RUBY_ENGINE == "jruby" - assert_equal("[{}]", PP.singleline_pp([->(*a){a.last.clear}.ruby2_keywords.call(a: 1)], ''.dup)) - assert_equal("[{}]", PP.singleline_pp([Hash.ruby2_keywords_hash({})], ''.dup)) + assert_equal("[{}]", passing_keywords {PP.singleline_pp([->(*a){a.last.clear}.ruby2_keywords.call(a: 1)], ''.dup)}) + assert_equal("[{}]", passing_keywords {PP.singleline_pp([Hash.ruby2_keywords_hash({})], ''.dup)}) + end + + if RUBY_VERSION >= "3.0" + def passing_keywords(&_) + yield + end + else + def passing_keywords(&_) + verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = verbose + end end def test_direct_pp From 674e2ca9452121a3e3066e04ecd51ba8d863cf2b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 12:59:52 +0900 Subject: [PATCH 0120/2435] [ruby/pp] Reduce substring creations https://github.com/ruby/pp/commit/fee2d39099 --- lib/pp.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index 2c7f41eadda188..e790f499d42041 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -313,12 +313,10 @@ def pp_hash(obj) # A pretty print for a pair of Hash def pp_hash_pair(k, v) if Symbol === k - sym_s = k.inspect - if sym_s[1].match?(/["$@!]/) || sym_s[-1].match?(/[%&*+\-\/<=>@\]^`|~]/) - text "#{k.to_s.inspect}:" - else - text "#{k}:" + if k.inspect.match?(%r[\A:["$@!]|[%&*+\-\/<=>@\]^`|~]\z]) + k = k.to_s.inspect end + text "#{k}:" else pp k text ' ' From ab49e8a0f0d61ef38b4006d2e8f3976550ba23f8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 20:12:35 +0900 Subject: [PATCH 0121/2435] [ruby/pp] [Feature #21389] Update rubyspec --- spec/ruby/core/set/pretty_print_cycle_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/ruby/core/set/pretty_print_cycle_spec.rb b/spec/ruby/core/set/pretty_print_cycle_spec.rb index c3b383fe808333..d4cca515e2a2d3 100644 --- a/spec/ruby/core/set/pretty_print_cycle_spec.rb +++ b/spec/ruby/core/set/pretty_print_cycle_spec.rb @@ -3,7 +3,12 @@ describe "Set#pretty_print_cycle" do it "passes the 'pretty print' representation of a self-referencing Set to the pretty print writer" do pp = mock("PrettyPrint") - pp.should_receive(:text).with("#") + ruby_version_is(""..."3.5") do + pp.should_receive(:text).with("#") + end + ruby_version_is("3.5") do + pp.should_receive(:text).with("Set[...]") + end Set[1, 2, 3].pretty_print_cycle(pp) end end From 704677257ecb01c7ee10aa0dfc55ca1d4fc4636d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 5 Oct 2025 11:15:09 -0400 Subject: [PATCH 0122/2435] Also add LSAN_OPTIONS=handle_segv=0 in assert_segv Just like ASAN, when running with LSAN, we also want to set handle_segv=0 in assert_segv to make sure that the tests pass. --- test/ruby/test_rubyoptions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 2b7cf1c8999a4c..4ee6633c2b6560 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -858,7 +858,7 @@ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &b env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting # ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when # catching sigsegv; we don't expect that output, so suppress it. - env.update({'ASAN_OPTIONS' => 'handle_segv=0'}) + env.update({'ASAN_OPTIONS' => 'handle_segv=0', 'LSAN_OPTIONS' => 'handle_segv=0'}) args.unshift(env) test_stdin = "" From b22fd7c40d875b136693d53bcd36e756feef2c6d Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 5 Oct 2025 02:26:46 +0900 Subject: [PATCH 0123/2435] [ruby/json] Fix sliced string escaping https://github.com/ruby/json/commit/d7baf015d9 --- test/json/json_encoding_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb index 8dce81da590e76..2789e94b5b3f2b 100644 --- a/test/json/json_encoding_test.rb +++ b/test/json/json_encoding_test.rb @@ -35,6 +35,8 @@ def test_generate_shared_string # Ref: https://github.com/ruby/json/issues/859 s = "01234567890" assert_equal '"234567890"', JSON.dump(s[2..-1]) + s = '01234567890123456789"a"b"c"d"e"f"g"h' + assert_equal '"\"a\"b\"c\"d\"e\"f\"g\""', JSON.dump(s[20, 15]) end def test_unicode From f13e68e252ee96ee01e3b6eb11ad4109d5e033b1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Oct 2025 21:18:53 +0900 Subject: [PATCH 0124/2435] [ruby/date] Do not repeat conversions to string https://github.com/ruby/date/commit/159e1ebb7f https://github.com/ruby/date/commit/4f7b6c9b42 --- ext/date/date_core.c | 20 ++++++++++---------- test/date/test_date_parse.rb | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 457838e41f8d55..8a7859704bf00f 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -4464,11 +4464,11 @@ get_limit(VALUE opt) #define rb_category_warn(category, fmt) rb_warn(fmt) #endif -static void +static VALUE check_limit(VALUE str, VALUE opt) { size_t slen, limit; - if (NIL_P(str)) return; + if (NIL_P(str)) return str; StringValue(str); slen = RSTRING_LEN(str); limit = get_limit(opt); @@ -4476,6 +4476,7 @@ check_limit(VALUE str, VALUE opt) rb_raise(rb_eArgError, "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); } + return str; } static VALUE @@ -4484,8 +4485,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) VALUE vstr, vcomp, hash, opt; argc = rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); - check_limit(vstr, opt); - StringValue(vstr); + vstr = check_limit(vstr, opt); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, "string should have ASCII compatible encoding"); @@ -4620,7 +4620,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__iso8601(str); } @@ -4690,7 +4690,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__rfc3339(str); } @@ -4759,7 +4759,7 @@ date_s__xmlschema(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__xmlschema(str); } @@ -4828,7 +4828,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__rfc2822(str); } @@ -4896,7 +4896,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__httpdate(str); } @@ -4965,7 +4965,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - check_limit(str, opt); + str = check_limit(str, opt); return date__jisx0301(str); } diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb index cc29771cf85b22..29e55977c56cfe 100644 --- a/test/date/test_date_parse.rb +++ b/test/date/test_date_parse.rb @@ -1304,4 +1304,20 @@ def test_length_limit assert_raise(ArgumentError) { Date._parse("Jan " + "9" * 1000000) } end + + def test_string_argument + s = '2001-02-03T04:05:06Z' + obj = Class.new(Struct.new(:to_str, :count)) do + def to_str + self.count +=1 + super + end + end.new(s, 0) + + all_assertions_foreach(nil, :_parse, :_iso8601, :_rfc3339, :_xmlschema) do |m| + obj.count = 0 + assert_not_equal({}, Date.__send__(m, obj)) + assert_equal(1, obj.count) + end + end end From e6188c45e1114be3be4955971464f1b39d567d71 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Oct 2025 14:17:44 +0900 Subject: [PATCH 0125/2435] [ruby/date] `Date._parse` does not accept `nil` https://github.com/ruby/date/commit/545066ca28 --- ext/date/date_core.c | 13 ++++++------- test/date/test_date_parse.rb | 2 ++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 8a7859704bf00f..e4021051731714 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -4468,7 +4468,6 @@ static VALUE check_limit(VALUE str, VALUE opt) { size_t slen, limit; - if (NIL_P(str)) return str; StringValue(str); slen = RSTRING_LEN(str); limit = get_limit(opt); @@ -4620,7 +4619,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__iso8601(str); } @@ -4690,7 +4689,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__rfc3339(str); } @@ -4759,7 +4758,7 @@ date_s__xmlschema(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__xmlschema(str); } @@ -4828,7 +4827,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__rfc2822(str); } @@ -4896,7 +4895,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__httpdate(str); } @@ -4965,7 +4964,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) VALUE str, opt; rb_scan_args(argc, argv, "1:", &str, &opt); - str = check_limit(str, opt); + if (!NIL_P(str)) str = check_limit(str, opt); return date__jisx0301(str); } diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb index 29e55977c56cfe..720624c02e338b 100644 --- a/test/date/test_date_parse.rb +++ b/test/date/test_date_parse.rb @@ -544,6 +544,8 @@ def test__parse__2 h = Date._parse('') assert_equal({}, h) + + assert_raise(TypeError) {Date._parse(nil)} end def test_parse From 7863389ad0e31bee853ba5c7399637edbead31a1 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 5 Oct 2025 19:38:47 +0900 Subject: [PATCH 0126/2435] [ruby/openssl] ssl: remove OpenSSL::X509::V_FLAG_CRL_CHECK_ALL from the default store With OpenSSL 3.6.0, it causes nearly every certificate verification to fail with the message "certificate verify failed (unable to get certificate CRL)" because the CRLs are typically unavailable in the default store used by OpenSSL::SSL::SSLContext#set_params. OpenSSL::X509::V_FLAG_CRL_CHECK_ALL is a flag that extends the CRL checking to all certificates in the chain. In OpenSSL < 3.6.0, the flag alone has no effect, and OpenSSL::X509::V_FLAG_CRL_CHECK must also be set to enable CRL checking. In OpenSSL 3.6.0, OpenSSL::X509::V_FLAG_CRL_CHECK_ALL now implies OpenSSL::X509::V_FLAG_CRL_CHECK. This is inconsistent with the man page and may be fixed in a future OpenSSL 3.6.x release, but this flag is not needed and should not be set by default. Fixes https://github.com/ruby/openssl/issues/949 https://github.com/ruby/openssl/commit/e8481cd687 --- ext/openssl/lib/openssl/ssl.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index a0ad5dc3a670dc..46509c333e0857 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -91,7 +91,6 @@ class SSLContext DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL # A callback invoked when DH parameters are required for ephemeral DH key # exchange. From 224c17876ca5e9ae9aed9d9a219c74e22e79be11 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 6 Oct 2025 16:16:57 +0900 Subject: [PATCH 0127/2435] [ruby/openssl] Ruby/OpenSSL 3.3.1 https://github.com/ruby/openssl/commit/2b88a6d444 --- ext/openssl/History.md | 29 +++++++++++++++++++++++++++++ ext/openssl/lib/openssl/version.rb | 2 +- ext/openssl/openssl.gemspec | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/ext/openssl/History.md b/ext/openssl/History.md index ad3417f9d0f859..ecc53fad3beb7c 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,9 @@ +Version 3.3.1 +============= + +Merged changes in 3.1.2 and 3.2.2. + + Version 3.3.0 ============= @@ -74,6 +80,12 @@ And various non-user-visible changes and bug fixes. Please see the commit history for more details. +Version 3.2.2 +============= + +Merged changes in 3.1.2. + + Version 3.2.1 ============= @@ -120,6 +132,23 @@ Notable changes [[GitHub #141]](https://github.com/ruby/openssl/pull/141) +Version 3.1.2 +============= + +Bug fixes +--------- + +* Fix crash when attempting to export an incomplete `OpenSSL::PKey::DSA` key. + [[GitHub #845]](https://github.com/ruby/openssl/issues/845) + [[GitHub #847]](https://github.com/ruby/openssl/pull/847) +* Remove the `OpenSSL::X509::V_FLAG_CRL_CHECK_ALL` flag from the default store + used by `OpenSSL::SSL::SSLContext#set_params`. It causes certificate + verification to fail with OpenSSL 3.6.0. It has no effect with any other + OpenSSL versions. + [[GitHub #949]](https://github.com/ruby/openssl/issues/949) + [[GitHub #950]](https://github.com/ruby/openssl/pull/950) + + Version 3.1.1 ============= diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 3398fe39ccc8c6..8672154c89c303 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenSSL - VERSION = "3.3.0" + VERSION = "3.3.1" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 2ec1551885b201..aec091f0e0fbc3 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "3.3.0" + spec.version = "3.3.1" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} From 33808e0f7ccff30fd1d0d9565f0c15690d6e55c7 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 6 Oct 2025 16:59:35 +0900 Subject: [PATCH 0128/2435] [ruby/openssl] Bump version number to 4.0.0.pre https://github.com/ruby/openssl/commit/64f4aae6bd --- ext/openssl/lib/openssl/version.rb | 2 +- ext/openssl/openssl.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 8672154c89c303..784f8885066091 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenSSL - VERSION = "3.3.1" + VERSION = "4.0.0.pre" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index aec091f0e0fbc3..061a9c5a6ab626 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "3.3.1" + spec.version = "4.0.0.pre" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} From 1f542c12669e44eea435735a9e3a07362b833465 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 6 Oct 2025 08:05:09 +0000 Subject: [PATCH 0129/2435] Update default gems list at 33808e0f7ccff30fd1d0d9565f0c15 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 2f223e00921a56..e70863784cd942 100644 --- a/NEWS.md +++ b/NEWS.md @@ -189,6 +189,7 @@ The following default gems are updated. * io-nonblock 0.3.2 * io-wait 0.3.2 * json 2.15.0 +* openssl 4.0.0.pre * optparse 0.7.0.dev.2 * prism 1.5.1 * psych 5.2.6 From 443b17a58724a806c4b772ed62ee7b5e6a5002ef Mon Sep 17 00:00:00 2001 From: YO4 Date: Wed, 1 Oct 2025 19:53:10 +0900 Subject: [PATCH 0130/2435] test-bundled-gems property fails if timed out on Windows Use spawn with array to make SIGINT working effectively on Windows --- tool/test-bundled-gems.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index e73a29c54bb6ae..dcf4d6fdf47062 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -33,7 +33,7 @@ next if bundled_gems&.none? {|pat| File.fnmatch?(pat, gem)} next unless File.directory?("#{gem_dir}/src/#{gem}/test") - test_command = "#{ruby} -C #{gem_dir}/src/#{gem} #{rake} test" + test_command = [ruby, "-C", "#{gem_dir}/src/#{gem}", rake, "test"] first_timeout = 600 # 10min toplib = gem @@ -61,7 +61,7 @@ rbs_skip_tests << File.join(__dir__, "/rbs_skip_tests_windows") end - test_command << " stdlib_test validate RBS_SKIP_TESTS=#{rbs_skip_tests.join(File::PATH_SEPARATOR)} SKIP_RBS_VALIDATION=true" + test_command.concat %W[stdlib_test validate RBS_SKIP_TESTS=#{rbs_skip_tests.join(File::PATH_SEPARATOR)} SKIP_RBS_VALIDATION=true] first_timeout *= 3 when "debug" @@ -71,7 +71,7 @@ load_path = true when "test-unit" - test_command = "#{ruby} -C #{gem_dir}/src/#{gem} test/run.rb" + test_command = [ruby, "-C", "#{gem_dir}/src/#{gem}", "test/run.rb"] when "win32ole" next unless /mswin|mingw/ =~ RUBY_PLATFORM @@ -89,18 +89,17 @@ # 93(bright yellow) is copied from .github/workflows/mingw.yml puts "#{github_actions ? "::group::\e\[93m" : "\n"}Testing the #{gem} gem#{github_actions ? "\e\[m" : ""}" print "[command]" if github_actions - puts test_command + p test_command timeouts = {nil => first_timeout, INT: 30, TERM: 10, KILL: nil} if /mingw|mswin/ =~ RUBY_PLATFORM timeouts.delete(:TERM) # Inner process signal on Windows - timeouts.delete(:INT) # root process will be terminated too group = :new_pgroup pg = "" else group = :pgroup pg = "-" end - pid = Process.spawn(test_command, group => true) + pid = Process.spawn(*test_command, group => true) timeouts.each do |sig, sec| if sig puts "Sending #{sig} signal" From 3d6d6760c85ea9680179ce3e6529cc6682f312eb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Oct 2025 19:00:57 +0900 Subject: [PATCH 0131/2435] [ruby/pp] Bump up to 0.6.3 https://github.com/ruby/pp/commit/c1992ce07d --- lib/pp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pp.rb b/lib/pp.rb index e790f499d42041..700a39cdc906a2 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -64,7 +64,7 @@ class PP < PrettyPrint # The version string - VERSION = "0.6.2" + VERSION = "0.6.3" # Returns the usable width for +out+. # As the width of +out+: From 68e073d719d0be9bf6c73e06f213b38df7a547a5 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 6 Oct 2025 11:04:25 +0000 Subject: [PATCH 0132/2435] Update default gems list at 3d6d6760c85ea9680179ce3e6529cc [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index e70863784cd942..6c39404721c887 100644 --- a/NEWS.md +++ b/NEWS.md @@ -191,6 +191,7 @@ The following default gems are updated. * json 2.15.0 * openssl 4.0.0.pre * optparse 0.7.0.dev.2 +* pp 0.6.3 * prism 1.5.1 * psych 5.2.6 * resolv 0.6.2 From f3020d7be389d411506d1d24f985ddac4c9a5a76 Mon Sep 17 00:00:00 2001 From: Hoa Nguyen Date: Tue, 7 Oct 2025 02:59:22 +1100 Subject: [PATCH 0133/2435] ZJIT: strengthen test_reset_stats (#14738) --- test/ruby/test_zjit.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 83af7347d61da9..c6dbc01dc2e129 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2127,6 +2127,7 @@ def test = 1 # After reset, counters should be zero or at least much smaller # (some instructions might execute between reset and reading stats) :zjit_insn_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] }, + :compiled_iseq_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] } ].all? }, stats: true end From 7333a2710e502be1c70374d4bb5b0247a4a5a5e6 Mon Sep 17 00:00:00 2001 From: Hoa Nguyen Date: Tue, 7 Oct 2025 03:01:12 +1100 Subject: [PATCH 0134/2435] ZJIT: reduce string allocation in the Counter::name() (#14743) The Counter::name() method creates a new String on every call, each call allocates memory and copies the string. Using %'static str would reduce memory pressure. The change is safe as no breaking changes to the API --- zjit/src/stats.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 323af0f3ed2695..a9b7270444a7a5 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -48,13 +48,13 @@ macro_rules! make_counters { } impl Counter { - pub fn name(&self) -> String { + pub fn name(&self) -> &'static str { match self { - $( Counter::$default_counter_name => stringify!($default_counter_name).to_string(), )+ - $( Counter::$exit_counter_name => stringify!($exit_counter_name).to_string(), )+ - $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name).to_string(), )+ - $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name).to_string(), )+ - $( Counter::$counter_name => stringify!($counter_name).to_string(), )+ + $( Counter::$default_counter_name => stringify!($default_counter_name), )+ + $( Counter::$exit_counter_name => stringify!($exit_counter_name), )+ + $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name), )+ + $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name), )+ + $( Counter::$counter_name => stringify!($counter_name), )+ } } } From 56b3b916af6952e75602b9b63d5a2efa0e578b1f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 10:53:24 -0700 Subject: [PATCH 0135/2435] tool/merger.rb: Fetch diff from GitHub instead of cgit Our cgit server has been shut down. --- tool/merger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/merger.rb b/tool/merger.rb index 8b12334b735df4..934a265094c377 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -263,7 +263,7 @@ def execute(*cmd, interactive: false) end # Merge revision from Git patch - git_uri = "https://git.ruby-lang.org/ruby.git/patch/?id=#{git_rev}" + git_uri = "https://github.com/ruby/ruby/commit/#{git_rev}.diff" resp = Net::HTTP.get_response(URI(git_uri)) if resp.code != '200' abort "'#{git_uri}' returned status '#{resp.code}':\n#{resp.body}" From cc982346417ad7cc4dcfeaae5e832d78571820db Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 11:21:32 -0700 Subject: [PATCH 0136/2435] tool/merger.rb: Fetch a diff in the patch format It expects "Subject:", so it needs to be a patch file. --- tool/merger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/merger.rb b/tool/merger.rb index 934a265094c377..795e97a86ee529 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -263,7 +263,7 @@ def execute(*cmd, interactive: false) end # Merge revision from Git patch - git_uri = "https://github.com/ruby/ruby/commit/#{git_rev}.diff" + git_uri = "https://github.com/ruby/ruby/commit/#{git_rev}.patch" resp = Net::HTTP.get_response(URI(git_uri)) if resp.code != '200' abort "'#{git_uri}' returned status '#{resp.code}':\n#{resp.body}" From 3ec49b9870d8c26d552bd6ae7aa6a0f452daec25 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 6 Oct 2025 14:11:32 -0400 Subject: [PATCH 0137/2435] ZJIT: Make documentation command target ZJIT specifically --- doc/zjit.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/zjit.md b/doc/zjit.md index 65151051c8ea98..d0e66dfe85645d 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -26,7 +26,10 @@ in a way that can be easily shared with other team members. ## Documentation -You can generate and open the source level documentation in your browser using `cargo doc --open --document-private-items`. +You can generate and open the source level documentation in your browser using: +``` +cargo doc --document-private-items -p zjit --open +``` ## Testing From 5f4877ab9e2892e294d3f07b13c8e4ec1bb4d70a Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 6 Oct 2025 14:12:18 -0400 Subject: [PATCH 0138/2435] ZJIT: Simplify cargo install commands for nextest and insta --- doc/zjit.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index d0e66dfe85645d..86a27d71194d09 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -34,8 +34,11 @@ cargo doc --document-private-items -p zjit --open ## Testing Make sure you have a `--enable-zjit=dev` build, and install the following tools: -- `brew install cargo-nextest` - Required for running tests -- `cargo install cargo-insta` - Required for updating snapshots +``` +cargo install cargo-nextest +cargo install cargo-insta +``` +`cargo-nextest` is required for running tests, whereas `cargo-insta` is used for updating snapshots. ### make zjit-check From 11f625f9f7ce61a3ddac98e99776350439b9d4fe Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 6 Oct 2025 15:42:07 -0400 Subject: [PATCH 0139/2435] ZJIT: Format the term-definition table * Using extra whitespace should not harm rendering it on github.com or docs.ruby-lang.org, but will make it easier for those in a text editor to read it. --- doc/zjit.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index 86a27d71194d09..b7a3e1827c1e79 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -179,26 +179,26 @@ This glossary contains terms that are helpful for understanding ZJIT. Please note that some terms may appear in CRuby internals too but with different meanings. -| Term | Definition | -| --- | -----------| -| HIR | High-level Intermediate Representation. High-level (Ruby semantics) graph representation in static single-assignment (SSA) form | -| LIR | Low-level Intermediate Representation. Low-level IR used in the backend for assembly generation | -| SSA | Static Single Assignment. A form where each variable is assigned exactly once | -| `opnd` | Operand. An operand to an IR instruction (can be register, memory, immediate, etc.) | -| `dst` | Destination. The output operand of an instruction where the result is stored | -| VReg | Virtual Register. A virtual register that gets lowered to physical register or memory | -| `insn_id` | Instruction ID. An index of an instruction in a function | -| `block_id` | The index of a basic block, which effectively acts like a pointer | -| `branch` | Control flow edge between basic blocks in the compiled code | -| `cb` | Code Block. Memory region for generated machine code | -| `entry` | The starting address of compiled code for an ISEQ | -| Patch Point | Location in generated code that can be modified later in case assumptions get invalidated | -| Frame State | Captured state of the Ruby stack frame at a specific point for deoptimization | -| Guard | A run-time check that ensures assumptions are still valid | -| `invariant` | An assumption that JIT code relies on, requiring invalidation if broken | -| Deopt | Deoptimization. Process of falling back from JIT code to interpreter | -| Side Exit | Exit from JIT code back to interpreter | -| Type Lattice | Hierarchy of types used for type inference and optimization | -| Constant Folding | Optimization that evaluates constant expressions at compile time | -| RSP | x86-64 stack pointer register used for native stack operations | -| Register Spilling | Process of moving register values to memory when running out of physical registers | +| Term | Definition | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| HIR | High-level Intermediate Representation. High-level (Ruby semantics) graph representation in static single-assignment (SSA) form | +| LIR | Low-level Intermediate Representation. Low-level IR used in the backend for assembly generation | +| SSA | Static Single Assignment. A form where each variable is assigned exactly once | +| `opnd` | Operand. An operand to an IR instruction (can be register, memory, immediate, etc.) | +| `dst` | Destination. The output operand of an instruction where the result is stored | +| VReg | Virtual Register. A virtual register that gets lowered to physical register or memory | +| `insn_id` | Instruction ID. An index of an instruction in a function | +| `block_id` | The index of a basic block, which effectively acts like a pointer | +| `branch` | Control flow edge between basic blocks in the compiled code | +| `cb` | Code Block. Memory region for generated machine code | +| `entry` | The starting address of compiled code for an ISEQ | +| Patch Point | Location in generated code that can be modified later in case assumptions get invalidated | +| Frame State | Captured state of the Ruby stack frame at a specific point for deoptimization | +| Guard | A run-time check that ensures assumptions are still valid | +| `invariant` | An assumption that JIT code relies on, requiring invalidation if broken | +| Deopt | Deoptimization. Process of falling back from JIT code to interpreter | +| Side Exit | Exit from JIT code back to interpreter | +| Type Lattice | Hierarchy of types used for type inference and optimization | +| Constant Folding | Optimization that evaluates constant expressions at compile time | +| RSP | x86-64 stack pointer register used for native stack operations | +| Register Spilling | Process of moving register values to memory when running out of physical registers | From a3d1752c20c477816b835fcec24e0d6ad0d773b5 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 6 Oct 2025 15:44:09 -0400 Subject: [PATCH 0140/2435] ZJIT: Escape $HOME and format multiline configure command --- doc/zjit.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/zjit.md b/doc/zjit.md index b7a3e1827c1e79..6a8dbd629a7c81 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -5,7 +5,13 @@ To build ZJIT on macOS: ``` ./autogen.sh -./configure --enable-zjit=dev --prefix=$HOME/.rubies/ruby-zjit --disable-install-doc --with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)" + +./configure \ + --enable-zjit=dev \ + --prefix="$HOME"/.rubies/ruby-zjit \ + --disable-install-doc \ + --with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)" + make -j miniruby ``` From 4a7ca3d836a2bbbd2e3c6cbaf3002ffa5a2b0078 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 6 Oct 2025 15:52:52 -0400 Subject: [PATCH 0141/2435] ZJIT: Reformat and add highlighting to ZJIT documentation * Add bash above code blocks that can use highlighting * Move Useful dev commands below documentation, testing, and building * Rewrite testing documentation to better explain what each form of test does --- doc/zjit.md | 108 +++++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index 6a8dbd629a7c81..c0378031b24bd2 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -3,7 +3,8 @@ ## Build Instructions To build ZJIT on macOS: -``` + +```bash ./autogen.sh ./configure \ @@ -15,62 +16,48 @@ To build ZJIT on macOS: make -j miniruby ``` -## Useful dev commands - -To view YARV output for code snippets: -``` -./miniruby --dump=insns -e0 -``` - -To run code snippets with ZJIT: -``` -./miniruby --zjit -e0 -``` - -You can also try https://www.rubyexplorer.xyz/ to view Ruby YARV disasm output with syntax highlighting -in a way that can be easily shared with other team members. - ## Documentation You can generate and open the source level documentation in your browser using: -``` + +```bash cargo doc --document-private-items -p zjit --open ``` ## Testing -Make sure you have a `--enable-zjit=dev` build, and install the following tools: -``` -cargo install cargo-nextest -cargo install cargo-insta -``` -`cargo-nextest` is required for running tests, whereas `cargo-insta` is used for updating snapshots. +Note that tests link against CRuby, so directly calling `cargo test`, or `cargo nextest` should not build. All tests are instead accessed through `make`. -### make zjit-check +### Setup -This command runs all ZJIT tests: `make zjit-test` and `test/ruby/test_zjit.rb`. +First, ensure you have `cargo` installed. If you do not already have it, you can use [rustup.rs](https://rustup.rs/). -``` -make zjit-check +Make sure to add `--enable-zjit=dev` when you run `configure`, then install the following tools: + +```bash +cargo install cargo-nextest +cargo install cargo-insta ``` -### make zjit-test +`cargo-insta` is used for updating snapshots. `cargo-nextest` runs each test in its own process, which is valuable since CRuby only supports booting once per process, and most APIs are not thread safe. -This command runs Rust unit tests using `insta` for snapshot testing. +### Running unit tests -``` +For testing functionality within ZJIT, use: + +```bash make zjit-test ``` You can also run a single test case by specifying the function name: -``` +```bash make zjit-test ZJIT_TESTS=test_putobject ``` #### Snapshot Testing -ZJIT uses [insta](https://insta.rs/) for snapshot testing. When tests fail due to snapshot mismatches, pending snapshots are created. The test command will notify you if there are pending snapshots: +ZJIT uses [insta](https://insta.rs/) for snapshot testing within unit tests. When tests fail due to snapshot mismatches, pending snapshots are created. The test command will notify you if there are pending snapshots: ``` Pending snapshots found. Accept with: make zjit-test-update @@ -78,50 +65,40 @@ Pending snapshots found. Accept with: make zjit-test-update To update/accept all the snapshot changes: -``` +```bash make zjit-test-update ``` You can also review snapshot changes interactively one by one: -``` +```bash cd zjit && cargo insta review ``` Test changes will be reviewed alongside code changes. -
- -Setting up zjit-test - -ZJIT uses `cargo-nextest` for Rust unit tests instead of `cargo test`. -`cargo-nextest` runs each test in its own process, which is valuable since -CRuby only supports booting once per process, and most APIs are not thread -safe. Use `brew install cargo-nextest` to install it on macOS, otherwise, refer -to for installation -instructions. - -Since it uses Cargo, you'll also need a `configure --enable-zjit=dev ...` build -for `make zjit-test`. Since the tests need to link against CRuby, directly -calling `cargo test`, or `cargo nextest` likely won't build. Make sure to -use `make`. - -
- -### test/ruby/test\_zjit.rb +### Running integration tests This command runs Ruby execution tests. -``` +```bash make test-all TESTS="test/ruby/test_zjit.rb" ``` You can also run a single test case by matching the method name: -``` +```bash make test-all TESTS="test/ruby/test_zjit.rb -n TestZJIT#test_putobject" ``` +### Running all tests + +Runs both `make zjit-test` and `test/ruby/test_zjit.rb`: + +```bash +make zjit-check +``` + ## Statistics Collection ZJIT provides detailed statistics about JIT compilation and execution behavior. @@ -173,12 +150,29 @@ Through [Stackprof](https://github.com/tmm1/stackprof), detailed information abo ./miniruby --zjit-trace-exits script.rb ``` -A file called `zjit_exit_locations.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: +A file called `zjit_exit_locations{timestamp}.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: + +```bash +stackprof path/to/zjit_exit_locations{timestamp}.dump +``` + +## Useful dev commands + +To view YARV output for code snippets: + +```bash +./miniruby --dump=insns -e0 +``` + +To run code snippets with ZJIT: ```bash -stackprof path/to/zjit_exit_locations.dump +./miniruby --zjit -e0 ``` +You can also try https://www.rubyexplorer.xyz/ to view Ruby YARV disasm output with syntax highlighting +in a way that can be easily shared with other team members. + ## ZJIT Glossary This glossary contains terms that are helpful for understanding ZJIT. From 3ba5cfd1cb77b61b2b1ad1d03271bc1fe7b71969 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 13:25:00 -0700 Subject: [PATCH 0142/2435] Add a workflow to sync default gems (#14749) --- ...default_gems.yml => default_gems_list.yml} | 2 +- .github/workflows/sync_default_gems.yml | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) rename .github/workflows/{default_gems.yml => default_gems_list.yml} (99%) create mode 100644 .github/workflows/sync_default_gems.yml diff --git a/.github/workflows/default_gems.yml b/.github/workflows/default_gems_list.yml similarity index 99% rename from .github/workflows/default_gems.yml rename to .github/workflows/default_gems_list.yml index 7935afc44a333c..37a2af03c7b980 100644 --- a/.github/workflows/default_gems.yml +++ b/.github/workflows/default_gems_list.yml @@ -9,7 +9,7 @@ permissions: contents: read jobs: - update_default_gems: + update_default_gems_list: name: Update default gems list permissions: diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml new file mode 100644 index 00000000000000..f1ae1f7d51bdb8 --- /dev/null +++ b/.github/workflows/sync_default_gems.yml @@ -0,0 +1,67 @@ +name: Sync default gems +on: + # Main use of this workflow. Called by default gem repositories by API. + repository_dispatch: + types: + - sync_default_gems + + # Web UI dispatch for testing and manual operations. + workflow_dispatch: + inputs: + gem: + required: true + description: 'Name of the gem to be synchronized' + type: string + before: + required: true + description: 'Gem commit SHA before sync' + type: string + after: + required: true + description: 'Gem commit SHA after sync' + type: string + +jobs: + sync_default_gems: + name: Update default gems list + + permissions: + contents: write # for Git to git push + + runs-on: ubuntu-latest + + if: ${{ github.repository == 'ruby/ruby' }} + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + name: Check out ruby/ruby + with: + token: ${{ (github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Run tool/sync_default_gems.rb + run: ruby tool/sync_default_gems.rb "${gem_name}" "${gem_before}..${gem_after}" + env: + gem_name: ${{ github.event.client_payload.gem || github.event.inputs.gem }} + gem_before: ${{ github.event.client_payload.before || github.event.inputs.before }} + gem_after: ${{ github.event.client_payload.after || github.event.inputs.after }} + + - name: Check diffs + id: diff + run: | + git diff --color --no-ext-diff --ignore-submodules --exit-code || + echo update=true >> $GITHUB_OUTPUT + + - name: Push + run: | + git pull --ff-only origin ${GITHUB_REF#refs/heads/} + git push origin ${GITHUB_REF#refs/heads/} + env: + EMAIL: svn-admin@ruby-lang.org + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git + if: ${{ steps.diff.outputs.update }} + + - uses: ./.github/actions/slack + with: + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} From c9b726028cfe9f18bf5d4e5bb4c64d9a0b481448 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 13:43:25 -0700 Subject: [PATCH 0143/2435] sync_default_gems.yml: Remove an unmatched paren --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index f1ae1f7d51bdb8..324c5d27a5ee88 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 name: Check out ruby/ruby with: - token: ${{ (github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - name: Run tool/sync_default_gems.rb run: ruby tool/sync_default_gems.rb "${gem_name}" "${gem_before}..${gem_after}" From 43eb41ec949229eaa1b8da0b1ac7beebe247186b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 13:46:00 -0700 Subject: [PATCH 0144/2435] sync_default_gems.yml: Fix a wrong job name --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 324c5d27a5ee88..c74f73ad589c5b 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -23,7 +23,7 @@ on: jobs: sync_default_gems: - name: Update default gems list + name: Sync default gems permissions: contents: write # for Git to git push From 03030bf112db6346b4055adb098bc93b95b6321a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 6 Oct 2025 16:37:29 +0200 Subject: [PATCH 0145/2435] Remove unused variable warning $ make test/ruby/test_thread.rb RUBYOPT=-w >/dev/null test/ruby/test_thread.rb:1595: warning: assigned but unused variable - bug21127 --- test/ruby/test_thread.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 01a1e51025181c..c08d41cb863a2c 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1592,7 +1592,6 @@ def frame_for_deadlock_test_2 # [Bug #21342] def test_unlock_locked_mutex_with_collected_fiber - bug21127 = '[ruby-core:120930] [Bug #21127]' assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; 5.times do From 57ea1c0be116db77834b9bd9850f3ee81d495247 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 13:53:21 -0700 Subject: [PATCH 0146/2435] sync_default_gems.yml: Detect past renames --- .github/workflows/sync_default_gems.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index c74f73ad589c5b..d3aab34edb9644 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,8 +36,12 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 name: Check out ruby/ruby with: + fetch-depth: 0 # Fetch all history to recognize past renames token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + - name: Increase rename limit + run: git config merge.renameLimit 999999 + - name: Run tool/sync_default_gems.rb run: ruby tool/sync_default_gems.rb "${gem_name}" "${gem_before}..${gem_after}" env: @@ -45,12 +49,6 @@ jobs: gem_before: ${{ github.event.client_payload.before || github.event.inputs.before }} gem_after: ${{ github.event.client_payload.after || github.event.inputs.after }} - - name: Check diffs - id: diff - run: | - git diff --color --no-ext-diff --ignore-submodules --exit-code || - echo update=true >> $GITHUB_OUTPUT - - name: Push run: | git pull --ff-only origin ${GITHUB_REF#refs/heads/} @@ -59,7 +57,6 @@ jobs: EMAIL: svn-admin@ruby-lang.org GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git - if: ${{ steps.diff.outputs.update }} - uses: ./.github/actions/slack with: From 7f6e9a0b1beec5d6d50d50a15b95e2360e2ce493 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 14:01:25 -0700 Subject: [PATCH 0147/2435] sync_default_gems.yml: Avoid fetching tags/branches --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index d3aab34edb9644..7528576eaf1213 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 name: Check out ruby/ruby with: - fetch-depth: 0 # Fetch all history to recognize past renames + fetch-depth: 999999 # Fetch all history to follow past renames. Not using 0 to avoid fetching tags/branches. token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - name: Increase rename limit From 6c1b5887149e731906e003af6fc8bfc1bc112c28 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 14:03:48 -0700 Subject: [PATCH 0148/2435] sync_default_gems.yml: Move the git config to the script --- .github/workflows/sync_default_gems.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 7528576eaf1213..301a59e3968e8a 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -48,15 +48,14 @@ jobs: gem_name: ${{ github.event.client_payload.gem || github.event.inputs.gem }} gem_before: ${{ github.event.client_payload.before || github.event.inputs.before }} gem_after: ${{ github.event.client_payload.after || github.event.inputs.after }} + EMAIL: svn-admin@ruby-lang.org + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git - name: Push run: | git pull --ff-only origin ${GITHUB_REF#refs/heads/} git push origin ${GITHUB_REF#refs/heads/} - env: - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - uses: ./.github/actions/slack with: From bc8732b6c81c761a6ed693effb0f497f39f43ea3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 14:08:03 -0700 Subject: [PATCH 0149/2435] sync_default_gems.yml: Attempt push only if needed --- .github/workflows/sync_default_gems.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 301a59e3968e8a..211d75dcf79de5 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -43,7 +43,14 @@ jobs: run: git config merge.renameLimit 999999 - name: Run tool/sync_default_gems.rb - run: ruby tool/sync_default_gems.rb "${gem_name}" "${gem_before}..${gem_after}" + id: sync + run: | + ruby_before=$(git rev-parse HEAD) + set -x + ruby tool/sync_default_gems.rb "${gem_name}" "${gem_before}..${gem_after}" + if [[ "$(git rev-parse HEAD)" != "$ruby_before" ]]; then + echo update=true >> $GITHUB_OUTPUT + fi env: gem_name: ${{ github.event.client_payload.gem || github.event.inputs.gem }} gem_before: ${{ github.event.client_payload.before || github.event.inputs.before }} @@ -56,6 +63,7 @@ jobs: run: | git pull --ff-only origin ${GITHUB_REF#refs/heads/} git push origin ${GITHUB_REF#refs/heads/} + if: ${{ steps.sync.outputs.update }} - uses: ./.github/actions/slack with: From 2a484ce3c313fa4cf050e599220d9a870c901f29 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 5 Oct 2025 17:29:30 -0400 Subject: [PATCH 0150/2435] [ruby/prism] Free current_block_exits for the program We need to free the current_block_exits in parse_program when we're done with it to prevent memory leaks. This fixes the following memory leak detected when running Ruby using `RUBY_FREE_AT_EXIT=1 ruby -nc -e "break"`: Direct leak of 32 byte(s) in 1 object(s) allocated from: #0 0x5bd3c5bc66c8 in realloc (miniruby+0x616c8) (BuildId: https://github.com/ruby/prism/commit/ba6a96e5a060) #1 0x5bd3c5f91fd9 in pm_node_list_grow prism/templates/src/node.c.erb:35:40 #2 0x5bd3c5f91e9d in pm_node_list_append prism/templates/src/node.c.erb:48:9 #3 0x5bd3c6001fa0 in parse_block_exit prism/prism.c:15788:17 #4 0x5bd3c5fee155 in parse_expression_prefix prism/prism.c:19221:50 #5 0x5bd3c5fe9970 in parse_expression prism/prism.c:22235:23 #6 0x5bd3c5fe0586 in parse_statements prism/prism.c:13976:27 #7 0x5bd3c5fd6792 in parse_program prism/prism.c:22508:40 https://github.com/ruby/prism/commit/fdf9b8d24a --- prism/prism.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 22ac19b5e469fe..f85a69332ed95c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22524,9 +22524,10 @@ parse_program(pm_parser_t *parser) { statements = wrap_statements(parser, statements); } else { flush_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); } + pm_node_list_free(¤t_block_exits); + // If this is an empty file, then we're still going to parse all of the // statements in order to gather up all of the comments and such. Here we'll // correct the location information. From dad064a0ea823222f729367b501e7d6e5ad0e505 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 15:01:19 -0700 Subject: [PATCH 0151/2435] [ruby/erb] Version 5.0.3 https://github.com/ruby/erb/commit/ddfc1ba57e --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 7d0b384f71c741..521e77ce4aac5b 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '5.0.2' + VERSION = '5.0.3' end From 854491fe998d531006d2f2447252975d5f764f11 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 6 Oct 2025 22:05:03 +0000 Subject: [PATCH 0152/2435] Update default gems list at dad064a0ea823222f729367b501e7d [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6c39404721c887..dafce164998b18 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,7 +182,7 @@ The following default gems are updated. * RubyGems 3.8.0.dev * bundler 2.8.0.dev -* erb 5.0.2 +* erb 5.0.3 * etc 1.4.6 * fcntl 1.3.0 * io-console 0.8.1 From 8c0fc05832111055c5f01014c0b831fc9f97e4f3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 16:17:45 -0700 Subject: [PATCH 0153/2435] sync_default_gems.yml: Remove unused repository_dispatch We actually use the workflow-level dispatch from API as well --- .github/workflows/sync_default_gems.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 211d75dcf79de5..edbb4399d4ec70 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -1,11 +1,5 @@ name: Sync default gems on: - # Main use of this workflow. Called by default gem repositories by API. - repository_dispatch: - types: - - sync_default_gems - - # Web UI dispatch for testing and manual operations. workflow_dispatch: inputs: gem: From b1e672bb4840d50b90031046d92f787d060a358a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 16:18:26 -0700 Subject: [PATCH 0154/2435] sync_default_gems.yml: Remove client_payload references Now that repository_dispatch is gone, we don't need them either. --- .github/workflows/sync_default_gems.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index edbb4399d4ec70..bd5b30b8613898 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -46,9 +46,9 @@ jobs: echo update=true >> $GITHUB_OUTPUT fi env: - gem_name: ${{ github.event.client_payload.gem || github.event.inputs.gem }} - gem_before: ${{ github.event.client_payload.before || github.event.inputs.before }} - gem_after: ${{ github.event.client_payload.after || github.event.inputs.after }} + gem_name: ${{ github.event.inputs.gem }} + gem_before: ${{ github.event.inputs.before }} + gem_after: ${{ github.event.inputs.after }} EMAIL: svn-admin@ruby-lang.org GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git From e3d4cb5de52332b475635949f48b5cc5620a9a5e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 17:41:55 -0700 Subject: [PATCH 0155/2435] Sync Prism (#14751) to https://github.com/ruby/prism/commit/c89ca2af12ba20b4fd2c5ff43ebe25da1d81d8db --- test/prism/fixtures/string_concatination_frozen_false.txt | 5 +++++ test/prism/fixtures/string_concatination_frozen_true.txt | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 test/prism/fixtures/string_concatination_frozen_false.txt create mode 100644 test/prism/fixtures/string_concatination_frozen_true.txt diff --git a/test/prism/fixtures/string_concatination_frozen_false.txt b/test/prism/fixtures/string_concatination_frozen_false.txt new file mode 100644 index 00000000000000..abe9301408e352 --- /dev/null +++ b/test/prism/fixtures/string_concatination_frozen_false.txt @@ -0,0 +1,5 @@ +# frozen_string_literal: false + +'foo' 'bar' + +'foo' 'bar' "baz#{bat}" diff --git a/test/prism/fixtures/string_concatination_frozen_true.txt b/test/prism/fixtures/string_concatination_frozen_true.txt new file mode 100644 index 00000000000000..829777f0a70259 --- /dev/null +++ b/test/prism/fixtures/string_concatination_frozen_true.txt @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +'foo' 'bar' + +'foo' 'bar' "baz#{bat}" From d0395bd0ead968e194e268f8c5f9db59d8831c02 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 12 Jul 2025 11:51:31 +0900 Subject: [PATCH 0156/2435] [ruby/uri] Clear user info totally at setting any of authority info Fix CVE-2025-27221. https://hackerone.com/reports/3221142 https://github.com/ruby/uri/commit/5cec76b9e8 --- lib/uri/generic.rb | 10 ++++++---- test/uri/test_generic.rb | 15 ++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index d811c5b9440bf1..abdf5b437729c9 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -186,18 +186,18 @@ def initialize(scheme, if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil) user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil) # See also URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -639,6 +639,7 @@ def set_host(v) def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -729,6 +730,7 @@ def set_port(v) def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index c725116e964e50..94eea71b511161 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -283,6 +283,9 @@ def test_merge_authority u0 = URI.parse('http://new.example.org/path') u1 = u.merge('//new.example.org/path') assert_equal(u0, u1) + u0 = URI.parse('http://other@example.net') + u1 = u.merge('//other@example.net') + assert_equal(u0, u1) end def test_route @@ -748,17 +751,18 @@ def test_join def test_set_component uri = URI.parse('http://foo:bar@baz') assert_equal('oof', uri.user = 'oof') - assert_equal('http://oof:bar@baz', uri.to_s) + assert_equal('http://oof@baz', uri.to_s) assert_equal('rab', uri.password = 'rab') assert_equal('http://oof:rab@baz', uri.to_s) assert_equal('foo', uri.userinfo = 'foo') - assert_equal('http://foo:rab@baz', uri.to_s) + assert_equal('http://foo@baz', uri.to_s) assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar']) assert_equal('http://foo:bar@baz', uri.to_s) assert_equal(['foo'], uri.userinfo = ['foo']) - assert_equal('http://foo:bar@baz', uri.to_s) + assert_equal('http://foo@baz', uri.to_s) assert_equal('zab', uri.host = 'zab') - assert_equal('http://foo:bar@zab', uri.to_s) + assert_equal('http://zab', uri.to_s) + uri.userinfo = ['foo', 'bar'] uri.port = "" assert_nil(uri.port) uri.port = "80" @@ -768,7 +772,8 @@ def test_set_component uri.port = " 080 " assert_equal(80, uri.port) assert_equal(8080, uri.port = 8080) - assert_equal('http://foo:bar@zab:8080', uri.to_s) + assert_equal('http://zab:8080', uri.to_s) + uri = URI.parse('http://foo:bar@zab:8080') assert_equal('/', uri.path = '/') assert_equal('http://foo:bar@zab:8080/', uri.to_s) assert_equal('a=1', uri.query = 'a=1') From eccc54b4fa437f896cde1bdee7f855b6e541cb82 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 26 Jun 2025 01:21:50 +0900 Subject: [PATCH 0157/2435] [ruby/uri] Add authority accessor https://github.com/ruby/uri/commit/6c6449e15f --- lib/uri/generic.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index abdf5b437729c9..634da49fe98f90 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -574,6 +574,12 @@ def password @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after URI decoding. def decoded_user URI.decode_uri_component(@user) if @user @@ -615,6 +621,13 @@ def set_host(v) end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -1123,7 +1136,7 @@ def merge(oth) base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1136,9 +1149,7 @@ def merge(oth) # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) From 6a58c4fbb653ad05e2da2f85d79797f6d5c87251 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 7 Oct 2025 09:48:35 +0900 Subject: [PATCH 0158/2435] [ruby/uri] Bump up to v1.0.4 https://github.com/ruby/uri/commit/e5074739c3 --- lib/uri/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/version.rb b/lib/uri/version.rb index b6a8ce15435601..60ada985f91ca8 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '010003'.freeze + VERSION_CODE = '010004'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end From def07dca82736bddb123c9adb02b8e1df02e3c5c Mon Sep 17 00:00:00 2001 From: git Date: Tue, 7 Oct 2025 01:14:41 +0000 Subject: [PATCH 0159/2435] Update default gems list at 6a58c4fbb653ad05e2da2f85d79797 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index dafce164998b18..6c9de939704722 100644 --- a/NEWS.md +++ b/NEWS.md @@ -197,7 +197,7 @@ The following default gems are updated. * resolv 0.6.2 * stringio 3.1.8.dev * strscan 3.1.6.dev -* uri 1.0.3 +* uri 1.0.4 * weakref 0.1.4 The following bundled gems are added. From c6a119c751305ce75133dad3d14be69f859cd6bb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 10:30:16 +0900 Subject: [PATCH 0160/2435] Update rubyspec as of CVE-2025-27221 --- spec/ruby/library/uri/set_component_spec.rb | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/spec/ruby/library/uri/set_component_spec.rb b/spec/ruby/library/uri/set_component_spec.rb index 642a5d6fcf0543..1d4165c5351b13 100644 --- a/spec/ruby/library/uri/set_component_spec.rb +++ b/spec/ruby/library/uri/set_component_spec.rb @@ -6,25 +6,27 @@ it "conforms to the MatzRuby tests" do uri = URI.parse('http://foo:bar@baz') (uri.user = 'oof').should == 'oof' - uri.to_s.should == 'http://oof:bar@baz' - (uri.password = 'rab').should == 'rab' - uri.to_s.should == 'http://oof:rab@baz' - (uri.userinfo = 'foo').should == 'foo' - uri.to_s.should == 'http://foo:rab@baz' - (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar'] - uri.to_s.should == 'http://foo:bar@baz' - (uri.userinfo = ['foo']).should == ['foo'] - uri.to_s.should == 'http://foo:bar@baz' - (uri.host = 'zab').should == 'zab' - uri.to_s.should == 'http://foo:bar@zab' - (uri.port = 8080).should == 8080 - uri.to_s.should == 'http://foo:bar@zab:8080' - (uri.path = '/').should == '/' - uri.to_s.should == 'http://foo:bar@zab:8080/' - (uri.query = 'a=1').should == 'a=1' - uri.to_s.should == 'http://foo:bar@zab:8080/?a=1' - (uri.fragment = 'b123').should == 'b123' - uri.to_s.should == 'http://foo:bar@zab:8080/?a=1#b123' + version_is(URI::VERSION, "1.0.4") do + uri.to_s.should == 'http://oof@baz' + (uri.password = 'rab').should == 'rab' + uri.to_s.should == 'http://oof:rab@baz' + (uri.userinfo = 'foo').should == 'foo' + uri.to_s.should == 'http://foo@baz' + (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar'] + uri.to_s.should == 'http://foo:bar@baz' + (uri.userinfo = ['foo']).should == ['foo'] + uri.to_s.should == 'http://foo@baz' + (uri.host = 'zab').should == 'zab' + uri.to_s.should == 'http://zab' + (uri.port = 8080).should == 8080 + uri.to_s.should == 'http://zab:8080' + (uri.path = '/').should == '/' + uri.to_s.should == 'http://zab:8080/' + (uri.query = 'a=1').should == 'a=1' + uri.to_s.should == 'http://zab:8080/?a=1' + (uri.fragment = 'b123').should == 'b123' + uri.to_s.should == 'http://zab:8080/?a=1#b123' + end uri = URI.parse('http://example.com') -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError) From 03f714de626cad7c8c45a60bffad2ce66228b524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 6 Oct 2025 16:50:01 +0200 Subject: [PATCH 0161/2435] Remove warning when generating $(arch)-fake.rb This happens if BASERUBY is Ruby 3.4. $ rm -f *-fake.rb && make test-precheck RUBYOPT=-w >/dev/null build/arm64-darwin24-fake.rb:28: warning: ::Ruby is reserved for Ruby 3.5 --- template/fake.rb.in | 1 + 1 file changed, 1 insertion(+) diff --git a/template/fake.rb.in b/template/fake.rb.in index a02582a9dc012f..fed640aee7e02b 100644 --- a/template/fake.rb.in +++ b/template/fake.rb.in @@ -49,6 +49,7 @@ class Object else%><%=v.inspect%><%end%> % } end +v=$VERBOSE;$VERBOSE=nil;module Ruby; end;$VERBOSE=v module Ruby constants.each {|n| remove_const n} % arg['versions'].each {|n, v| From 4cdf5f493362df1261ecc87f9644f2b0c2f2a409 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 12:44:13 +0900 Subject: [PATCH 0162/2435] Verify that RubyGems is enabled by default --- test/ruby/test_rubyoptions.rb | 4 +++- tool/lib/core_assertions.rb | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 4ee6633c2b6560..8126cb3c268b37 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -210,6 +210,8 @@ def test_enable assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [], /unknown argument for --enable: 'foobarbazqux'/) assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil) end def test_disable @@ -219,7 +221,7 @@ def test_disable assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [], /unknown argument for --disable: 'foobarbazqux'/) assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/) - assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], []) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false) assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], []) assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], []) end diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 47cc6574c878d1..934ab080372d67 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -97,9 +97,11 @@ def assert_file end def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, - success: nil, failed: nil, **opt) + success: nil, failed: nil, gems: false, **opt) args = Array(args).dup - args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + unless gems.nil? + args.insert((Hash === args[0] ? 1 : 0), "--#{gems ? 'enable' : 'disable'}=gems") + end stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) desc = failed[status, message, stderr] if failed desc ||= FailDesc[status, message, stderr] From 0f059792997dc9cb3007064d1e21eebe5872edc0 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 6 Oct 2025 11:35:13 +0900 Subject: [PATCH 0163/2435] ns_id of main is already initialized in Namespace.new --- namespace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/namespace.c b/namespace.c index b48914ac0ab004..0c437be7711989 100644 --- a/namespace.c +++ b/namespace.c @@ -755,7 +755,6 @@ rb_initialize_main_namespace(void) VM_ASSERT(NAMESPACE_OBJ_P(main_ns)); ns = rb_get_namespace_t(main_ns); ns->ns_object = main_ns; - ns->ns_id = namespace_generate_id(); ns->is_user = true; ns->is_optional = false; From 52c6b32f806b1b812069235fde48e68167eaa0d1 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 7 Oct 2025 12:38:27 +0900 Subject: [PATCH 0164/2435] Initialize the main namespace after loading builtin libraries * For having the common set of loaded libraries between root and main namespaces * To have the consistent $LOADED_FEATURES in the main namespace --- namespace.c | 3 +++ ruby.c | 5 +++- test/ruby/test_namespace.rb | 49 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index 0c437be7711989..577a1759d25573 100644 --- a/namespace.c +++ b/namespace.c @@ -761,6 +761,9 @@ rb_initialize_main_namespace(void) rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_ns); vm->main_namespace = main_namespace = ns; + + // create the writable classext of ::Object explicitly to finalize the set of visible top-level constants + RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns); } static VALUE diff --git a/ruby.c b/ruby.c index 8c1fb5719bc02e..05a9fd4191d8bb 100644 --- a/ruby.c +++ b/ruby.c @@ -1826,10 +1826,13 @@ ruby_opt_init(ruby_cmdline_options_t *opt) GET_VM()->running = 1; memset(ruby_vm_redefined_flag, 0, sizeof(ruby_vm_redefined_flag)); + ruby_init_prelude(); + + /* Initialize the main namespace after loading libraries (including rubygems) + * to enable those in both root and main */ if (rb_namespace_available()) rb_initialize_main_namespace(); rb_namespace_init_done(); - ruby_init_prelude(); // Initialize JITs after ruby_init_prelude() because JITing prelude is typically not optimal. #if USE_YJIT diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index cd593068675de6..af308ab15c25e3 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -3,6 +3,10 @@ require 'test/unit' class TestNamespace < Test::Unit::TestCase + EXPERIMENTAL_WARNINGS = [ + "warning: Namespace is experimental, and the behavior may change in the future!", + "See doc/namespace.md for known issues, etc." + ].join("\n") ENV_ENABLE_NAMESPACE = {'RUBY_NAMESPACE' => '1'} def setup @@ -534,6 +538,51 @@ def test_load_path_and_loaded_features assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb')) end + def test_prelude_gems_and_loaded_features + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + # No additional warnings except for experimental warnings + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal error.size, 2 + + assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_prelude_gems_and_loaded_features_with_disable_gems + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal error.size, 2 + + refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + def test_eval_basic pend unless Namespace.enabled? From 2548c476a379cebe85166f20e264ae68c2a68dc4 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 7 Oct 2025 12:42:03 +0900 Subject: [PATCH 0165/2435] Add namespace debug methods and assertions --- internal/class.h | 14 ++++++++++---- namespace.c | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/internal/class.h b/internal/class.h index 29b3526cf55cfd..f5c5142b452d78 100644 --- a/internal/class.h +++ b/internal/class.h @@ -336,10 +336,14 @@ RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t return first_set; } +#define VM_ASSERT_NAMESPACEABLE_TYPE(klass) \ + VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS), "%s is not namespaceable type", rb_type_str(BUILTIN_TYPE(klass))) + static inline bool RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); // if the lookup table exists, then it means the prime classext is NOT directly readable. return !FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE) || RCLASS_CLASSEXT_TBL(klass) == NULL; } @@ -347,15 +351,16 @@ RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) static inline bool RCLASS_PRIME_CLASSEXT_WRITABLE_P(VALUE klass) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); return FL_TEST(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } static inline void RCLASS_SET_PRIME_CLASSEXT_WRITABLE(VALUE klass, bool writable) { - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); - + VM_ASSERT(klass != 0, "klass should be a valid object"); + VM_ASSERT_NAMESPACEABLE_TYPE(klass); if (writable) { FL_SET(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } @@ -429,6 +434,7 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) ext = rb_class_duplicate_classext(RCLASS_EXT_PRIME(obj), obj, ns); first_set = RCLASS_SET_NAMESPACE_CLASSEXT(obj, ns, ext); if (first_set) { + // TODO: are there any case that a class/module become non-writable after its birthtime? RCLASS_SET_PRIME_CLASSEXT_WRITABLE(obj, false); } } diff --git a/namespace.c b/namespace.c index 577a1759d25573..32249d00bc161b 100644 --- a/namespace.c +++ b/namespace.c @@ -845,11 +845,15 @@ rb_namespace_s_main(VALUE recv) static const char * classname(VALUE klass) { - VALUE p = RCLASS_CLASSPATH(klass); + VALUE p; + if (!klass) { + return "Qfalse"; + } + p = RCLASSEXT_CLASSPATH(RCLASS_EXT_PRIME(klass)); if (RTEST(p)) return RSTRING_PTR(p); if (RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)) - return RSTRING_PTR(rb_inspect(klass)); + return "AnyClassValue"; return "NonClassValue"; } @@ -973,6 +977,27 @@ rb_f_dump_classext(VALUE recv, VALUE klass) return res; } +static VALUE +rb_namespace_root_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_ROOT_P(ns)); +} + +static VALUE +rb_namespace_main_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_MAIN_P(ns)); +} + +static VALUE +rb_namespace_user_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_USER_P(ns)); +} + #endif /* RUBY_DEBUG */ /* @@ -1010,6 +1035,10 @@ Init_Namespace(void) rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); rb_define_global_function("dump_classext", rb_f_dump_classext, 1); + + rb_define_method(rb_cNamespace, "root?", rb_namespace_root_p, 0); + rb_define_method(rb_cNamespace, "main?", rb_namespace_main_p, 0); + rb_define_method(rb_cNamespace, "user?", rb_namespace_user_p, 0); #endif } From e5b2e5227b4ebf0a0635126e0fb335f8f607bc96 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 7 Oct 2025 10:30:28 +0200 Subject: [PATCH 0166/2435] [ruby/json] Release 2.15.1 https://github.com/ruby/json/commit/9e6067bb55 --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index e2db923e309cac..be5daf4c5b6902 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.15.0' + VERSION = '2.15.1' end From 71e231847b8728db109f65b673818e14f691c517 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 7 Oct 2025 08:35:07 +0000 Subject: [PATCH 0167/2435] Update default gems list at e5b2e5227b4ebf0a0635126e0fb335 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6c9de939704722..9639c2908a81f2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,7 +188,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 -* json 2.15.0 +* json 2.15.1 * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 From c693b0e4771915127c8e097ae2d84dea53c2b339 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 7 Oct 2025 18:01:02 +0900 Subject: [PATCH 0168/2435] [ruby/error_highlight] Improve English comments and messages https://github.com/ruby/error_highlight/commit/5f976265ef --- lib/error_highlight/base.rb | 9 ++++----- lib/error_highlight/core_ext.rb | 2 +- lib/error_highlight/formatter.rb | 4 ++-- test/error_highlight/test_error_highlight.rb | 14 +++++++------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index a4c65c63e687fd..22e0bf0dc39738 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -1,13 +1,13 @@ require_relative "version" module ErrorHighlight - # Identify the code fragment at that a given exception occurred. + # Identify the code fragment where a given exception occurred. # # Options: # # point_type: :name | :args - # :name (default) points the method/variable name that the exception occurred. - # :args points the arguments of the method call that the exception occurred. + # :name (default) points to the method/variable name where the exception occurred. + # :args points to the arguments of the method call where the exception occurred. # # backtrace_location: Thread::Backtrace::Location # It locates the code fragment of the given backtrace_location. @@ -113,7 +113,7 @@ def initialize(node, point_type: :name, name: nil) snippet = @node.script_lines[lineno - 1 .. last_lineno - 1].join("") snippet += "\n" unless snippet.end_with?("\n") - # It require some work to support Unicode (or multibyte) characters. + # It requires some work to support Unicode (or multibyte) characters. # Tentatively, we stop highlighting if the code snippet has non-ascii characters. # See https://github.com/ruby/error_highlight/issues/4 raise NonAscii unless snippet.ascii_only? @@ -504,7 +504,6 @@ def spot_fcall_for_name def spot_fcall_for_args _mid, nd_args = @node.children if nd_args && nd_args.first_lineno == nd_args.last_lineno - # binary operator fetch_line(nd_args.first_lineno) @beg_column = nd_args.first_column @end_column = nd_args.last_column diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index 2fb07f2e65bc7c..d4f91e118567bc 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -24,7 +24,7 @@ module CoreExt _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines out += "\n | #{ snippet } #{ highlight }" else - out += "\n (cannot create a snippet of the method definition; use Ruby 3.5 or later)" + out += "\n (cannot highlight method definition; try Ruby 3.5 or later)" end end ret << "\n" + out if out diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb index 5180576405b17d..d2fad9e75c747d 100644 --- a/lib/error_highlight/formatter.rb +++ b/lib/error_highlight/formatter.rb @@ -56,11 +56,11 @@ def self.max_snippet_width=(width) end def self.terminal_width - # lazy load io/console, so it's not loaded when 'max_snippet_width' is set + # lazy load io/console to avoid loading it when 'max_snippet_width' is manually set require "io/console" $stderr.winsize[1] if $stderr.tty? rescue LoadError, NoMethodError, SystemCallError - # do not truncate when window size is not available + # skip truncation when terminal window size is unavailable end end diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index be3e3607332ca4..208dea8ea97128 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1468,7 +1468,7 @@ def test_wrong_number_of_arguments_for_method MethodDefLocationSupported ? "| def wrong_number_of_arguments_test(x, y) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1494,7 +1494,7 @@ def test_missing_keyword MethodDefLocationSupported ? "| def keyword_test(kw1:, kw2:, kw3:) ^^^^^^^^^^^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1515,7 +1515,7 @@ def test_unknown_keyword MethodDefLocationSupported ? "| def keyword_test(kw1:, kw2:, kw3:) ^^^^^^^^^^^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1545,7 +1545,7 @@ def test_wrong_number_of_arguments_for_method2 MethodDefLocationSupported ? "| def wrong_number_of_arguments_test2( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1567,7 +1567,7 @@ def test_wrong_number_of_arguments_for_lambda_literal MethodDefLocationSupported ? "| v = -> {} ^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1589,7 +1589,7 @@ def test_wrong_number_of_arguments_for_lambda_method MethodDefLocationSupported ? "| v = lambda { } ^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END @@ -1615,7 +1615,7 @@ def test_wrong_number_of_arguments_for_define_method MethodDefLocationSupported ? "| define_method :define_method_test do |x, y| ^^" : - "(cannot create a snippet of the method definition; use Ruby 3.5 or later)" + "(cannot highlight method definition; try Ruby 3.5 or later)" } END From 9a0e857c3590412b5de6d029966d0aa67344c450 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 7 Oct 2025 18:36:08 +0900 Subject: [PATCH 0169/2435] Stop displaying current namespace when it crashed To avoid crashes during displaying crash reports. --- internal/namespace.h | 2 ++ namespace.c | 9 +++++++++ vm.c | 37 +++++++++++++++++++++++-------------- vm_dump.c | 27 ++++++++++++++++++++------- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/internal/namespace.h b/internal/namespace.h index 9ffc9a5c8be8bb..c5a495fdf003ba 100644 --- a/internal/namespace.h +++ b/internal/namespace.h @@ -54,6 +54,7 @@ typedef struct rb_namespace_struct rb_namespace_t; RUBY_EXTERN bool ruby_namespace_enabled; RUBY_EXTERN bool ruby_namespace_init_done; +RUBY_EXTERN bool ruby_namespace_crashed; static inline bool rb_namespace_available(void) @@ -65,6 +66,7 @@ const rb_namespace_t * rb_root_namespace(void); const rb_namespace_t * rb_main_namespace(void); const rb_namespace_t * rb_current_namespace(void); const rb_namespace_t * rb_loading_namespace(void); +const rb_namespace_t * rb_current_namespace_in_crash_report(void); void rb_namespace_entry_mark(void *); void rb_namespace_gc_update_references(void *ptr); diff --git a/namespace.c b/namespace.c index 32249d00bc161b..059642dd94c451 100644 --- a/namespace.c +++ b/namespace.c @@ -50,6 +50,7 @@ static bool tmp_dir_has_dirsep; bool ruby_namespace_enabled = false; // extern bool ruby_namespace_init_done = false; // extern +bool ruby_namespace_crashed = false; // extern, changed only in vm.c VALUE rb_resolve_feature_path(VALUE klass, VALUE fname); static VALUE rb_namespace_inspect(VALUE obj); @@ -97,6 +98,14 @@ rb_loading_namespace(void) return rb_vm_loading_namespace(GET_EC()); } +const rb_namespace_t * +rb_current_namespace_in_crash_report(void) +{ + if (ruby_namespace_crashed) + return NULL; + return rb_current_namespace(); +} + static long namespace_id_counter = 0; static long diff --git a/vm.c b/vm.c index c9bf3dce36d76c..76c6f869eb86ae 100644 --- a/vm.c +++ b/vm.c @@ -95,6 +95,16 @@ rb_vm_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame } } +#if VM_CHECK_MODE > 0 +// ruby_namespace_crashed defined in internal/namespace.h +#define VM_NAMESPACE_CRASHED() {ruby_namespace_crashed = true;} +#define VM_NAMESPACE_ASSERT(expr, msg) \ + if (!(expr)) { ruby_namespace_crashed = true; rb_bug(msg); } +#else +#define VM_NAMESPACE_CRASHED() {} +#define VM_NAMESPACE_ASSERT(expr, msg) ((void)0) +#endif + static const VALUE * VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp) { @@ -109,23 +119,22 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { if (!cfp) { cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); - VM_ASSERT(cfp, "rb_vm_search_cf_from_ep should return a valid cfp for the ep"); - VM_ASSERT(cfp->ep == ep); + VM_NAMESPACE_ASSERT(cfp, "rb_vm_search_cf_from_ep should return a valid cfp for the ep, but NULL"); + VM_NAMESPACE_ASSERT(cfp->ep == ep, "rb_vm_search_cf_from_ep returns an unmatched cfp"); } if (!cfp) { return NULL; } - VM_ASSERT(cfp->ep); - VM_ASSERT(cfp->ep == ep); + VM_NAMESPACE_ASSERT(cfp->ep, "cfp->ep == NULL"); + VM_NAMESPACE_ASSERT(cfp->ep == ep, "cfp->ep != ep"); + + VM_NAMESPACE_ASSERT(!VM_FRAME_FINISHED_P(cfp), "CFUNC frame should not FINISHED"); - if (VM_FRAME_FINISHED_P(cfp)) { - rb_bug("CFUNC frame should not FINISHED"); - } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); if (cfp >= eocfp) { return NULL; } - VM_ASSERT(cfp, "CFUNC should have a valid previous control frame"); + VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); ep = cfp->ep; if (!ep) { return NULL; @@ -3061,17 +3070,17 @@ current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_fram rb_callable_method_entry_t *cme; const rb_namespace_t *ns; const VALUE *lep = VM_EP_RUBY_LEP(ec, cfp); - VM_ASSERT(lep); - VM_ASSERT(rb_namespace_available()); + VM_NAMESPACE_ASSERT(lep, "lep should be valid"); + VM_NAMESPACE_ASSERT(rb_namespace_available(), "namespace should be available here"); if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD)) { cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); - VM_ASSERT(cme); - VM_ASSERT(cme->def); + VM_NAMESPACE_ASSERT(cme, "cme should be valid"); + VM_NAMESPACE_ASSERT(cme->def, "cme->def shold be valid"); return cme->def->ns; } else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_TOP) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CLASS)) { - VM_ASSERT(VM_ENV_LOCAL_P(lep)); + VM_NAMESPACE_ASSERT(VM_ENV_LOCAL_P(lep), "lep should be local on MAGIC_TOP or MAGIC_CLASS frames"); return VM_ENV_NAMESPACE(lep); } else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_DUMMY)) { @@ -3083,6 +3092,7 @@ current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_fram return rb_root_namespace(); } else { + VM_NAMESPACE_CRASHED(); rb_bug("BUG: Local ep without cme/namespace, flags: %08lX", (unsigned long)lep[VM_ENV_DATA_INDEX_FLAGS]); } UNREACHABLE_RETURN(0); @@ -3091,7 +3101,6 @@ current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_fram const rb_namespace_t * rb_vm_current_namespace(const rb_execution_context_t *ec) { - VM_ASSERT(rb_namespace_available()); return current_namespace_on_cfp(ec, ec->cfp); } diff --git a/vm_dump.c b/vm_dump.c index 03f573a516fe61..f5aaaeeabf37c4 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1150,8 +1150,16 @@ rb_vm_bugreport(const void *ctx, FILE *errout) enum {other_runtime_info = 0}; #endif const rb_vm_t *const vm = GET_VM(); - const rb_namespace_t *ns = rb_current_namespace(); + const rb_namespace_t *current_ns = rb_current_namespace_in_crash_report(); const rb_execution_context_t *ec = rb_current_execution_context(false); + VALUE loaded_features; + + if (current_ns) { + loaded_features = current_ns->loaded_features; + } + else { + loaded_features = rb_root_namespace()->loaded_features; + } if (vm && ec) { rb_vmdebug_stack_dump_raw(ec, ec->cfp, errout); @@ -1201,17 +1209,22 @@ rb_vm_bugreport(const void *ctx, FILE *errout) } if (rb_namespace_available()) { kprintf("* Namespace: enabled\n"); - kprintf("* Current namespace id: %ld, type: %s\n", - ns->ns_id, - NAMESPACE_USER_P(ns) ? (NAMESPACE_MAIN_P(ns) ? "main" : "user") : "root"); + if (current_ns) { + kprintf("* Current namespace id: %ld, type: %s\n", + current_ns->ns_id, + NAMESPACE_USER_P(current_ns) ? (NAMESPACE_MAIN_P(current_ns) ? "main" : "user") : "root"); + } + else { + kprintf("* Current namespace: NULL (crashed)\n"); + } } else { kprintf("* Namespace: disabled\n"); } - if (ns->loaded_features) { + if (loaded_features) { kprintf("* Loaded features:\n\n"); - for (i=0; iloaded_features); i++) { - name = RARRAY_AREF(ns->loaded_features, i); + for (i=0; i Date: Tue, 7 Oct 2025 21:04:08 +0900 Subject: [PATCH 0170/2435] Add a control frame column "n:xxxx" as namespace id in crash reports --- vm.c | 4 ++-- vm_core.h | 6 ++++++ vm_dump.c | 12 ++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/vm.c b/vm.c index 76c6f869eb86ae..06e85a1d1a48e2 100644 --- a/vm.c +++ b/vm.c @@ -119,8 +119,8 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { if (!cfp) { cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); - VM_NAMESPACE_ASSERT(cfp, "rb_vm_search_cf_from_ep should return a valid cfp for the ep, but NULL"); - VM_NAMESPACE_ASSERT(cfp->ep == ep, "rb_vm_search_cf_from_ep returns an unmatched cfp"); + VM_NAMESPACE_ASSERT(cfp, "Failed to search cfp from ep"); + VM_NAMESPACE_ASSERT(cfp->ep == ep, "Searched cfp's ep is not equal to ep"); } if (!cfp) { return NULL; diff --git a/vm_core.h b/vm_core.h index da0249e567977d..5068e200575c18 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1585,6 +1585,12 @@ VM_ENV_NAMESPACE(const VALUE *ep) return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); } +static inline const rb_namespace_t * +VM_ENV_NAMESPACE_UNCHECKED(const VALUE *ep) +{ + return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); +} + #if VM_CHECK_MODE > 0 int rb_vm_ep_in_heap_p(const VALUE *ep); #endif diff --git a/vm_dump.c b/vm_dump.c index f5aaaeeabf37c4..460a0f7b8ccd82 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -62,6 +62,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c VALUE tmp; const rb_iseq_t *iseq = NULL; const rb_callable_method_entry_t *me = rb_vm_frame_method_entry_unchecked(cfp); + const rb_namespace_t *ns = NULL; if (ep < 0 || (size_t)ep > ec->vm_stack_size) { ep = (ptrdiff_t)cfp->ep; @@ -71,12 +72,17 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c switch (VM_FRAME_TYPE_UNCHECKED(cfp)) { case VM_FRAME_MAGIC_TOP: magic = "TOP"; + ns = VM_ENV_NAMESPACE_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_METHOD: magic = "METHOD"; + if (me) { + ns = me->def->ns; + } break; case VM_FRAME_MAGIC_CLASS: magic = "CLASS"; + ns = VM_ENV_NAMESPACE_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_BLOCK: magic = "BLOCK"; @@ -156,6 +162,12 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c } kprintf("s:%04"PRIdPTRDIFF" ", cfp->sp - ec->vm_stack); kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); + if (ns) { + kprintf("n:%04ld ", ns->ns_id % 10000); + } + else { + kprintf("n:---- "); + } kprintf("%-6s", magic); if (line) { kprintf(" %s", posbuf); From a6938eb46a10021642213b98d648544129a7dbb3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 22:36:32 +0900 Subject: [PATCH 0171/2435] Skip files that are "deleted by us" "Deleted" means that file is only for the upstream but not for ruby. --- tool/sync_default_gems.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 366f64ddfcec9b..1f6ae06b61f7f9 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -83,6 +83,10 @@ def pipe_readlines(args, rs: "\0", chomp: true) end end + def porcelain_status(*pattern) + pipe_readlines(%W"git status --porcelain -z --" + pattern) + end + def replace_rdoc_ref(file) src = File.binread(file) changed = false @@ -101,7 +105,7 @@ def replace_rdoc_ref(file) end def replace_rdoc_ref_all - result = pipe_readlines(%W"git status --porcelain -z -- *.c *.rb *.rdoc") + result = porcelain_status("*.c", "*.rb", "*.rdoc") result.map! {|line| line[/\A.M (.*)/, 1]} result.compact! return if result.empty? @@ -489,14 +493,16 @@ def commits_in_ranges(gem, repo, default_branch, ranges) def resolve_conflicts(gem, sha, edit) # Skip this commit if everything has been removed as `ignored_paths`. - changes = pipe_readlines(%W"git status --porcelain -z") + changes = porcelain_status() if changes.empty? puts "Skip empty commit #{sha}" return false end - # We want to skip DD: deleted by both. - deleted = changes.grep(/^DD /) {$'} + # We want to skip + # DD: deleted by both + # DU: deleted by us + deleted = changes.grep(/^D[DU] /) {$'} system(*%W"git rm -f --", *deleted) unless deleted.empty? # Import UA: added by them @@ -505,10 +511,9 @@ def resolve_conflicts(gem, sha, edit) # Discover unmerged files # AU: unmerged, added by us - # DU: unmerged, deleted by us # UU: unmerged, both modified # AA: unmerged, both added - conflict = changes.grep(/\A(?:.U|AA) /) {$'} + conflict = changes.grep(/\A(?:A[AU]|UU) /) {$'} # If -e option is given, open each conflicted file with an editor unless conflict.empty? if edit @@ -624,6 +629,8 @@ def pickup_commit(gem, sha, edit) # Commit cherry-picked commit if picked system(*%w"git commit --amend --no-edit") + elsif porcelain_status().empty? + system(*%w"git cherry-pick --skip") else system(*%w"git cherry-pick --continue --no-edit") end or return nil From 78dbc6c0b793805bb0d10e7d6d2fe8a5c0069a64 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 7 Oct 2025 23:51:00 +0900 Subject: [PATCH 0172/2435] Shorten timeout for csv It usually ends in a few seconds, and less than 10 seconds even on Windows. But recently it stalls 10 minutes and times out. --- tool/test-bundled-gems.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index dcf4d6fdf47062..86bb747d946320 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -73,6 +73,9 @@ when "test-unit" test_command = [ruby, "-C", "#{gem_dir}/src/#{gem}", "test/run.rb"] + when "csv" + first_timeout = 30 + when "win32ole" next unless /mswin|mingw/ =~ RUBY_PLATFORM From 40d1603e549b12808ed7f94064014658b91660e0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 00:03:48 +0900 Subject: [PATCH 0173/2435] [ruby/io-console] Skip emptied commits https://github.com/ruby/io-console/commit/431c3f3369 --- tool/sync_default_gems.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 1f6ae06b61f7f9..e02a2343b96f10 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -626,11 +626,14 @@ def pickup_commit(gem, sha, edit) return picked || nil # Fail unless cherry-picked end + if porcelain_status().empty? + system(*%w"git cherry-pick --skip") + return true + end + # Commit cherry-picked commit if picked system(*%w"git commit --amend --no-edit") - elsif porcelain_status().empty? - system(*%w"git cherry-pick --skip") else system(*%w"git cherry-pick --continue --no-edit") end or return nil From c951e1c4e058c3525498d227039d32c98e11062c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 00:09:44 +0900 Subject: [PATCH 0174/2435] Return false to skip emptied commits --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index e02a2343b96f10..64ed89145123d3 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -628,7 +628,7 @@ def pickup_commit(gem, sha, edit) if porcelain_status().empty? system(*%w"git cherry-pick --skip") - return true + return false end # Commit cherry-picked commit From 7089a4e2d83a3cb1bc394c4ce3638cbc777f4cb9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 00:50:00 +0900 Subject: [PATCH 0175/2435] Fix not to skip necessary commits --- tool/sync_default_gems.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 64ed89145123d3..f404b7ff5061de 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -626,14 +626,12 @@ def pickup_commit(gem, sha, edit) return picked || nil # Fail unless cherry-picked end - if porcelain_status().empty? - system(*%w"git cherry-pick --skip") - return false - end - # Commit cherry-picked commit if picked system(*%w"git commit --amend --no-edit") + elsif porcelain_status().empty? + system(*%w"git cherry-pick --skip") + return false else system(*%w"git cherry-pick --continue --no-edit") end or return nil From 446257c84b92c63d84282eadca32b56ed1281a3d Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 7 Oct 2025 10:28:37 -0400 Subject: [PATCH 0176/2435] Add debug #define to call sched_yield before each pthread_mutex_lock This is useful for debugging mutex issues as it increases contention for locks. It is off by default. --- thread_pthread.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 5150a6173e6e6b..323659b64945b8 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -90,9 +90,16 @@ static const void *const condattr_monotonic = NULL; #endif #endif +#ifdef HAVE_SCHED_YIELD +#define native_thread_yield() (void)sched_yield() +#else +#define native_thread_yield() ((void)0) +#endif + // native thread wrappers #define NATIVE_MUTEX_LOCK_DEBUG 0 +#define NATIVE_MUTEX_LOCK_DEBUG_YIELD 0 static void mutex_debug(const char *msg, void *lock) @@ -111,6 +118,9 @@ void rb_native_mutex_lock(pthread_mutex_t *lock) { int r; +#if NATIVE_MUTEX_LOCK_DEBUG_YIELD + native_thread_yield(); +#endif mutex_debug("lock", lock); if ((r = pthread_mutex_lock(lock)) != 0) { rb_bug_errno("pthread_mutex_lock", r); @@ -310,12 +320,6 @@ static rb_serial_t current_fork_gen = 1; /* We can't use GET_VM()->fork_gen */ static void threadptr_trap_interrupt(rb_thread_t *); -#ifdef HAVE_SCHED_YIELD -#define native_thread_yield() (void)sched_yield() -#else -#define native_thread_yield() ((void)0) -#endif - static void native_thread_dedicated_inc(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt); static void native_thread_dedicated_dec(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt); static void native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th); From b78270a6c6181e02acf281bcbb0c6a5429a9537a Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 7 Oct 2025 10:44:28 -0400 Subject: [PATCH 0177/2435] ZJIT: Remove unnecessary .dup calls in exit_locations * Using https://www.rubyexplorer.xyz/?c=frames+%3D+results%5B%3Aframes%5D.dup shows dup is called regardless --- zjit.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zjit.rb b/zjit.rb index b44154ad9cef1c..fad6d7d8070092 100644 --- a/zjit.rb +++ b/zjit.rb @@ -35,9 +35,9 @@ def exit_locations return unless trace_exit_locations_enabled? results = Primitive.rb_zjit_get_exit_locations - raw_samples = results[:raw].dup - line_samples = results[:lines].dup - frames = results[:frames].dup + raw_samples = results[:raw] + line_samples = results[:lines] + frames = results[:frames] samples_count = 0 # Loop through the instructions and set the frame hash with the data. From 4d0f53520c09eab591ba69fb3fa16d7f55103bc8 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 7 Oct 2025 10:56:29 -0400 Subject: [PATCH 0178/2435] ZJIT: Change name format of zjit_exit_locations dump file --- doc/zjit.md | 4 ++-- zjit.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index c0378031b24bd2..d0d3e361913039 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -150,10 +150,10 @@ Through [Stackprof](https://github.com/tmm1/stackprof), detailed information abo ./miniruby --zjit-trace-exits script.rb ``` -A file called `zjit_exit_locations{timestamp}.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: +A file called `zjit_exits_{pid}.dump` will be created in the same directory as `script.rb`. Viewing the side exited methods can be done with Stackprof: ```bash -stackprof path/to/zjit_exit_locations{timestamp}.dump +stackprof path/to/zjit_exits_{pid}.dump ``` ## Useful dev commands diff --git a/zjit.rb b/zjit.rb index fad6d7d8070092..9e1e50253667df 100644 --- a/zjit.rb +++ b/zjit.rb @@ -277,7 +277,7 @@ def print_stats def dump_locations # :nodoc: return unless trace_exit_locations_enabled? - filename = "zjit_exits_#{Time.now.to_i}.dump" + filename = "zjit_exits_#{Process.pid}.dump" n_bytes = dump_exit_locations(filename) $stderr.puts("#{n_bytes} bytes written to #{filename}.") From c1cb034356a96cc378106de91142784de08c6fc6 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 7 Oct 2025 15:38:00 -0400 Subject: [PATCH 0179/2435] ZJIT: Refactor comments and rewrite frames handling --- zjit.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/zjit.rb b/zjit.rb index 9e1e50253667df..e3e2eae14448f3 100644 --- a/zjit.rb +++ b/zjit.rb @@ -40,13 +40,12 @@ def exit_locations frames = results[:frames] samples_count = 0 - # Loop through the instructions and set the frame hash with the data. - # We use nonexistent.def for the file name, otherwise insns.def will be displayed - # and that information isn't useful in this context. + # Use nonexistent.def as a dummy file name. + frame_template = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } + + # Loop through all possible instructions and setup the frame hash. RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| - frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } - results[:frames][frame_id] = frame_hash - frames[frame_id] = frame_hash + frames[frame_id] = frame_template.dup.tap { |h| h[:name] = name } end # Loop through the raw_samples and build the hashes for StackProf. @@ -100,8 +99,8 @@ def exit_locations end results[:samples] = samples_count - # Set missed_samples and gc_samples to 0 as their values - # don't matter to us in this context. + + # These values are mandatory to include for stackprof, but we don't use them. results[:missed_samples] = 0 results[:gc_samples] = 0 results From 9a75c05b5ab7eaee9e3db14b5651034bb414aa5e Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 7 Oct 2025 15:38:28 -0400 Subject: [PATCH 0180/2435] ZJIT: Ignore results with no samples --- zjit.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zjit.rb b/zjit.rb index e3e2eae14448f3..75c57f9a35c195 100644 --- a/zjit.rb +++ b/zjit.rb @@ -103,6 +103,9 @@ def exit_locations # These values are mandatory to include for stackprof, but we don't use them. results[:missed_samples] = 0 results[:gc_samples] = 0 + + results[:frames].reject! { |k, v| v[:samples] == 0 } + results end From 42ba82424d908c290a4a34ced8853f0a403b734b Mon Sep 17 00:00:00 2001 From: Ricardo Trindade Date: Tue, 7 Oct 2025 21:40:36 +0200 Subject: [PATCH 0181/2435] Fix typo in comment in array#zip docs Duplicate the was found in the documentation --- array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/array.c b/array.c index 2ba95bef186dae..378da150ee3e3e 100644 --- a/array.c +++ b/array.c @@ -4569,7 +4569,7 @@ take_items(VALUE obj, long n) * [:c3, :b3, :a3]] * * For an *object* in *other_arrays* that is not actually an array, - * forms the the "other array" as object.to_ary, if defined, + * forms the "other array" as object.to_ary, if defined, * or as object.each.to_a otherwise. * * Related: see {Methods for Converting}[rdoc-ref:Array@Methods+for+Converting]. From 6c7aa118cc36f1d36ef38b54c887eda2e4d6cf2b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Sep 2025 18:55:37 +0100 Subject: [PATCH 0182/2435] ZJIT: Test against bundled gems on CI --- .github/workflows/zjit-macos.yml | 3 --- .github/workflows/zjit-ubuntu.yml | 4 ++++ tool/test-bundled-gems.rb | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index a638a3b1b353dd..0d2b564c41c802 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -101,9 +101,6 @@ jobs: - name: Run configure run: ../src/configure -C --disable-install-doc ${{ matrix.configure }} - - run: make prepare-gems - if: ${{ matrix.test_task == 'test-bundled-gems' }} - - run: make - name: Verify that --zjit-dump-disasm works diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 99d5dbfdedc390..a30ff1df88fdd9 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -72,6 +72,10 @@ jobs: configure: '--enable-zjit=dev --with-gcc=clang-14' libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' + - test_task: 'test-bundled-gems' + configure: '--enable-zjit=dev' + run_opts: '--zjit-call-threshold=1' + env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUN_OPTS: ${{ matrix.run_opts }} diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index 86bb747d946320..006ebd981af913 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -1,6 +1,7 @@ require 'rbconfig' require 'timeout' require 'fileutils' +require 'shellwords' require_relative 'lib/colorize' require_relative 'lib/gem_env' @@ -25,6 +26,7 @@ rake = File.realpath("../../.bundle/bin/rake", __FILE__) gem_dir = File.realpath('../../gems', __FILE__) rubylib = [gem_dir+'/lib', ENV["RUBYLIB"]].compact.join(File::PATH_SEPARATOR) +run_opts = ENV["RUN_OPTS"]&.shellsplit exit_code = 0 ruby = ENV['RUBY'] || RbConfig.ruby failed = [] @@ -33,7 +35,7 @@ next if bundled_gems&.none? {|pat| File.fnmatch?(pat, gem)} next unless File.directory?("#{gem_dir}/src/#{gem}/test") - test_command = [ruby, "-C", "#{gem_dir}/src/#{gem}", rake, "test"] + test_command = [ruby, *run_opts, "-C", "#{gem_dir}/src/#{gem}", rake, "test"] first_timeout = 600 # 10min toplib = gem @@ -71,7 +73,7 @@ load_path = true when "test-unit" - test_command = [ruby, "-C", "#{gem_dir}/src/#{gem}", "test/run.rb"] + test_command = [ruby, *run_opts, "-C", "#{gem_dir}/src/#{gem}", "test/run.rb"] when "csv" first_timeout = 30 From 5a9aa9013ffa3e1457d8aca606313f8ee4742399 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 30 Sep 2025 22:35:06 +0100 Subject: [PATCH 0183/2435] Use master commit of irb This version of IRB has higher test timeout on CI, which is needed for ZJIT to pass IRB integration tests. --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index d37d90bbf14eb2..4b95fa525004f7 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -41,7 +41,7 @@ benchmark 0.4.1 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger rdoc 6.14.2 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole -irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36 +irb 1.15.2 https://github.com/ruby/irb d43c3d764ae439706aa1b26a3ec299cc45eaed5b reline 0.6.2 https://github.com/ruby/reline readline 0.0.4 https://github.com/ruby/readline fiddle 1.1.8 https://github.com/ruby/fiddle From b05d64aa501ecfe321443ed7e5f246dd88abf01e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 17:12:01 -0700 Subject: [PATCH 0184/2435] Resurrect sync from GitHub to git.ruby-lang.org (#14765) --- .github/workflows/check_misc.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 07c43a0c3b0788..1bb6592423cfd5 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -35,6 +35,18 @@ jobs: if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} continue-on-error: true # The next auto-style should always run + # Sync git.ruby-lang.org before pushing new commits to avoid duplicated syncs + - name: Sync git.ruby-lang.org + env: + RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} + run: | + mkdir -p ~/.ssh + echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts + ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org 'sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh' + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + - uses: ./.github/actions/setup/directories with: makeup: true From 337189f4ba4ac665fec6fb9a5ffc9a510697c38a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 17:14:22 -0700 Subject: [PATCH 0185/2435] check_misc.yml: Add a missing argument to update-ruby.sh https://github.com/ruby/ruby/pull/14765 --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 1bb6592423cfd5..67a350db1c887f 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -44,7 +44,7 @@ jobs: echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts - ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org 'sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh' + ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org 'sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh master' if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - uses: ./.github/actions/setup/directories From 68e03213271f44ae63ad4a4e1ebe3ed59d589a9c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 6 Oct 2025 21:22:52 -0400 Subject: [PATCH 0186/2435] Always load -test-/asan in tests -test-/asan should always be available. --- tool/lib/core_assertions.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 934ab080372d67..cc4eb327d41106 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -74,10 +74,7 @@ def message msg = nil, ending = nil, &default module CoreAssertions require_relative 'envutil' require 'pp' - begin - require '-test-/asan' - rescue LoadError - end + require '-test-/asan' nil.pretty_inspect @@ -162,7 +159,7 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # ASAN has the same problem - its shadow memory greatly increases memory usage # (plus asan has better ways to detect memory leaks than this assertion) - pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if defined?(Test::ASAN) && Test::ASAN.enabled? + pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::ASAN.enabled? require_relative 'memory_status' raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) From d4a762e005503347ffdafb274ecbf02879763149 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 20:33:01 -0700 Subject: [PATCH 0187/2435] check_misc.yml: Support non-master branches See also: https://github.com/ruby/git.ruby-lang.org/commit/0b0eae90f67e9889b133b86b1f2e4526a2882161 --- .github/workflows/check_misc.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 67a350db1c887f..6638b0ec875fa5 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -37,15 +37,16 @@ jobs: # Sync git.ruby-lang.org before pushing new commits to avoid duplicated syncs - name: Sync git.ruby-lang.org - env: - RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} run: | mkdir -p ~/.ssh echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts - ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org 'sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh master' - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" + env: + GITHUB_REF: ${{ github.ref }} + RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} + if: ${{ github.repository == 'ruby/ruby' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_')) && github.event_name == 'push' }} - uses: ./.github/actions/setup/directories with: From 76b10394856d62e56ab2f31f4bbfded320ce813d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 20:54:37 -0700 Subject: [PATCH 0188/2435] Carve out a workflow for post-push hooks (#14768) from check_misc.yml. These steps originally came from git.ruby-lang.org/ruby.git's post-receive hooks. Because it handles a different set of events from what the original check_misc.yml does, it probably allows them to be simpler if they are separated. --- .github/workflows/check_misc.yml | 50 +-------------------- .github/workflows/post_push.yml | 74 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/post_push.yml diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 6638b0ec875fa5..1c0120252fdf21 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -20,52 +20,14 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - fetch-depth: 100 # for notify-slack-commits token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - # Run this step first (even before `make up` in the next step) to make the notification available before any other failure - - name: Notify commit to Slack - run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master - env: - GITHUB_OLD_SHA: ${{ github.event.before }} - GITHUB_NEW_SHA: ${{ github.event.after }} - SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL_ALERTS }} - SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }} - SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - continue-on-error: true # The next auto-style should always run - - # Sync git.ruby-lang.org before pushing new commits to avoid duplicated syncs - - name: Sync git.ruby-lang.org - run: | - mkdir -p ~/.ssh - echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts - ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" - env: - GITHUB_REF: ${{ github.ref }} - RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} - if: ${{ github.repository == 'ruby/ruby' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_')) && github.event_name == 'push' }} - - uses: ./.github/actions/setup/directories with: makeup: true # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - # Run this step early to make sure auto-style commits are pushed - - name: Auto-correct code styles - run: | - set -x - ruby tool/auto-style.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master - env: - GITHUB_OLD_SHA: ${{ github.event.before }} - GITHUB_NEW_SHA: ${{ github.event.after }} - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - EMAIL: svn-admin@ruby-lang.org - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - name: Check for code styles run: | set -x @@ -73,6 +35,7 @@ jobs: env: GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }} GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} + # Skip 'push' events because post_push.yml fixes them on push if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - name: Check if C-sources are US-ASCII @@ -145,17 +108,6 @@ jobs: name: ${{ steps.docs.outputs.htmlout }} if: ${{ steps.docs.outcome == 'success' }} - - name: Push PR notes to GitHub - run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master - env: - GITHUB_OLD_SHA: ${{ github.event.before }} - GITHUB_NEW_SHA: ${{ github.event.after }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - EMAIL: svn-admin@ruby-lang.org - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml new file mode 100644 index 00000000000000..724a67017aa49f --- /dev/null +++ b/.github/workflows/post_push.yml @@ -0,0 +1,74 @@ +name: Post-push +on: push +jobs: + hooks: + name: Post-push hooks + + permissions: + contents: write # for Git to git push + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 500 # for notify-slack-commits + token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + + # Run this step first (even before `make up` in the next step) to make the notification available before any other failure + - name: Notify commit to Slack + run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL_ALERTS }} + SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }} + SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + continue-on-error: true # The next auto-style should always run + + - name: Sync git.ruby-lang.org + run: | + mkdir -p ~/.ssh + echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts + ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" + env: + GITHUB_REF: ${{ github.ref }} + RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} + if: ${{ github.repository == 'ruby/ruby' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_')) && github.event_name == 'push' }} + + - uses: ./.github/actions/setup/directories + with: + makeup: true + # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN + checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) + + - name: Auto-correct code styles + run: | + set -x + ruby tool/auto-style.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git + EMAIL: svn-admin@ruby-lang.org + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + + - name: Push PR notes to GitHub + run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: git + GIT_COMMITTER_NAME: git + EMAIL: svn-admin@ruby-lang.org + if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + + - uses: ./.github/actions/slack + with: + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} From f063427c45ecf35f98efc27fe92a49b035016063 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 20:56:14 -0700 Subject: [PATCH 0189/2435] post_push.yml: Skip .github/actions/setup/directories These scripts are made to be fairly portable, so it shouldn't need all these steps to make them work. --- .github/workflows/post_push.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 724a67017aa49f..0ddcc2e283619a 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -39,12 +39,6 @@ jobs: RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} if: ${{ github.repository == 'ruby/ruby' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_')) && github.event_name == 'push' }} - - uses: ./.github/actions/setup/directories - with: - makeup: true - # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - - name: Auto-correct code styles run: | set -x From 000165a51cb845c5af4b735a8ca42994708551af Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Oct 2025 21:07:54 -0700 Subject: [PATCH 0190/2435] post_push.yml: Simplify the overall workflow --- .github/workflows/post_push.yml | 46 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 0ddcc2e283619a..e252239c3fbf4e 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -1,21 +1,32 @@ name: Post-push -on: push +on: + push: + branches: + - master + - 'ruby_*_*' jobs: hooks: name: Post-push hooks - - permissions: - contents: write # for Git to git push - runs-on: ubuntu-latest - + if: ${{ github.repository == 'ruby/ruby' }} steps: + - name: Sync git.ruby-lang.org + run: | + mkdir -p ~/.ssh + echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts + ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" + env: + GITHUB_REF: ${{ github.ref }} + RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} + if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 500 # for notify-slack-commits - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + token: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} - # Run this step first (even before `make up` in the next step) to make the notification available before any other failure - name: Notify commit to Slack run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master env: @@ -24,20 +35,7 @@ jobs: SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL_ALERTS }} SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }} SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} - continue-on-error: true # The next auto-style should always run - - - name: Sync git.ruby-lang.org - run: | - mkdir -p ~/.ssh - echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts - ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" - env: - GITHUB_REF: ${{ github.ref }} - RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} - if: ${{ github.repository == 'ruby/ruby' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_')) && github.event_name == 'push' }} + if: ${{ github.ref == 'refs/heads/master' }} - name: Auto-correct code styles run: | @@ -49,7 +47,7 @@ jobs: GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git EMAIL: svn-admin@ruby-lang.org - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + if: ${{ github.ref == 'refs/heads/master' }} - name: Push PR notes to GitHub run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master @@ -60,7 +58,7 @@ jobs: GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git EMAIL: svn-admin@ruby-lang.org - if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} + if: ${{ github.ref == 'refs/heads/master' }} - uses: ./.github/actions/slack with: From ac01ac11f951336702b347c81855aa60ff27c177 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 02:03:41 +0000 Subject: [PATCH 0191/2435] Bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/scorecards.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0b4103913bcae5..3ac1ed59cb2b5f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -75,17 +75,17 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 with: languages: ${{ matrix.language }} trap-caching: false debug: true - name: Autobuild - uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 with: category: '/language:${{ matrix.language }}' upload: False @@ -115,7 +115,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 99c563a25b3728..34a301ddb020d5 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: results.sarif From a0e7341bfd424010f31876e42ca2b553404b35fc Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 00:53:09 -0700 Subject: [PATCH 0192/2435] post_push.yml: Migrate fetch_changesets from post-receive.sh --- .github/workflows/post_push.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index e252239c3fbf4e..d4b5cec68fe5e0 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -22,6 +22,12 @@ jobs: RUBY_GIT_SYNC_PRIVATE_KEY: ${{ secrets.RUBY_GIT_SYNC_PRIVATE_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} + - name: Fetch changesets on bugs.ruby-lang.org + run: curl "https://bugs.ruby-lang.org/sys/fetch_changesets?key=${REDMINE_SYS_API_KEY}" --fail-with-body -w '* status: %{http_code}\n' + env: + REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} + if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 500 # for notify-slack-commits From 949717efb1de61dadfce7a0dde73ca72c417ac10 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 00:59:31 -0700 Subject: [PATCH 0193/2435] post_push.yml: Fix an invalid YAML syntax --- .github/workflows/post_push.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index d4b5cec68fe5e0..7d76107d13469a 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -23,7 +23,8 @@ jobs: if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} - name: Fetch changesets on bugs.ruby-lang.org - run: curl "https://bugs.ruby-lang.org/sys/fetch_changesets?key=${REDMINE_SYS_API_KEY}" --fail-with-body -w '* status: %{http_code}\n' + run: | + curl "https://bugs.ruby-lang.org/sys/fetch_changesets?key=${REDMINE_SYS_API_KEY}" --fail-with-body -w '* status: %{http_code}\n' env: REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} From 9ae3e20953edc20d8e21075e13c877ae42f47fa2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 01:01:51 -0700 Subject: [PATCH 0194/2435] push_push.yml: Suppress progress of requests which also disables some error messages, but --fail-with-body -w '%{http_code}' seems to show everything we need anyway. --- .github/workflows/post_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 7d76107d13469a..32d74f644e6bf0 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -24,7 +24,7 @@ jobs: - name: Fetch changesets on bugs.ruby-lang.org run: | - curl "https://bugs.ruby-lang.org/sys/fetch_changesets?key=${REDMINE_SYS_API_KEY}" --fail-with-body -w '* status: %{http_code}\n' + curl "https://bugs.ruby-lang.org/sys/fetch_changesets?key=${REDMINE_SYS_API_KEY}" -s --fail-with-body -w '* status: %{http_code}\n' env: REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} From 43dbb9a93f4de3f1170d7d18641c30e81cc08365 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 10:21:20 +0900 Subject: [PATCH 0195/2435] [Bug #21629] Enable `nonstring` attribute on clang 21 --- include/ruby/internal/attr/nonstring.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/ruby/internal/attr/nonstring.h b/include/ruby/internal/attr/nonstring.h index 9a66180e19e2d2..5ad6ef2a86507e 100644 --- a/include/ruby/internal/attr/nonstring.h +++ b/include/ruby/internal/attr/nonstring.h @@ -27,6 +27,8 @@ # define RBIMPL_ATTR_NONSTRING() __attribute__((nonstring)) # if RBIMPL_COMPILER_SINCE(GCC, 15, 0, 0) # define RBIMPL_ATTR_NONSTRING_ARRAY() RBIMPL_ATTR_NONSTRING() +# elif RBIMPL_COMPILER_SINCE(Clang, 21, 0, 0) +# define RBIMPL_ATTR_NONSTRING_ARRAY() RBIMPL_ATTR_NONSTRING() # else # define RBIMPL_ATTR_NONSTRING_ARRAY() /* void */ # endif From 2bb6fe3854e2a4854bb89bfce4eaaea9d848fd1b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 18:19:47 +0900 Subject: [PATCH 0196/2435] [Bug #21629] Initialize `struct RString` --- error.c | 2 +- ext/-test-/string/fstring.c | 2 +- include/ruby/internal/core/rbasic.h | 3 +++ include/ruby/internal/core/rstring.h | 2 +- load.c | 4 ++-- marshal.c | 2 +- string.c | 12 ++++++------ symbol.c | 8 ++++---- 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/error.c b/error.c index c510432c9d8ae4..2f24929d44ea91 100644 --- a/error.c +++ b/error.c @@ -2619,7 +2619,7 @@ name_err_mesg_to_str(VALUE obj) VALUE mesg = ptr->mesg; if (NIL_P(mesg)) return Qnil; else { - struct RString s_str, c_str, d_str; + struct RString s_str = {RBASIC_INIT}, c_str = {RBASIC_INIT}, d_str = {RBASIC_INIT}; VALUE c, s, d = 0, args[4], c2; int state = 0; rb_encoding *usascii = rb_usascii_encoding(); diff --git a/ext/-test-/string/fstring.c b/ext/-test-/string/fstring.c index 71c4b7f97eee44..92a846a93c2e5c 100644 --- a/ext/-test-/string/fstring.c +++ b/ext/-test-/string/fstring.c @@ -12,7 +12,7 @@ VALUE bug_s_fstring_fake_str(VALUE self) { static const char literal[] = "abcdefghijklmnopqrstuvwxyz"; - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return rb_str_to_interned_str(rb_setup_fake_str(&fake_str, literal, sizeof(literal) - 1, 0)); } diff --git a/include/ruby/internal/core/rbasic.h b/include/ruby/internal/core/rbasic.h index 35af03f7c807e3..63cdff8e09586c 100644 --- a/include/ruby/internal/core/rbasic.h +++ b/include/ruby/internal/core/rbasic.h @@ -115,6 +115,9 @@ RBasic { #endif { } +# define RBASIC_INIT RBasic() +#else +# define RBASIC_INIT {RBIMPL_VALUE_NULL} #endif }; diff --git a/include/ruby/internal/core/rstring.h b/include/ruby/internal/core/rstring.h index 0bca74e688fe44..9cf9daa97c87e7 100644 --- a/include/ruby/internal/core/rstring.h +++ b/include/ruby/internal/core/rstring.h @@ -395,7 +395,7 @@ rbimpl_rstring_getmem(VALUE str) } else { /* Expecting compilers to optimize this on-stack struct away. */ - struct RString retval; + struct RString retval = {RBASIC_INIT}; retval.len = RSTRING_LEN(str); retval.as.heap.ptr = RSTRING(str)->as.embed.ary; return retval; diff --git a/load.c b/load.c index 1928721c9e9cb2..9a1ff53f5415da 100644 --- a/load.c +++ b/load.c @@ -1434,7 +1434,7 @@ rb_require_internal(VALUE fname) int ruby_require_internal(const char *fname, unsigned int len) { - struct RString fake; + struct RString fake = {RBASIC_INIT}; VALUE str = rb_setup_fake_str(&fake, fname, len, 0); rb_execution_context_t *ec = GET_EC(); int result = require_internal(ec, str, 0, RTEST(ruby_verbose)); @@ -1476,7 +1476,7 @@ rb_require_string_internal(VALUE fname, bool resurrect) VALUE rb_require(const char *fname) { - struct RString fake; + struct RString fake = {RBASIC_INIT}; VALUE str = rb_setup_fake_str(&fake, fname, strlen(fname), 0); return rb_require_string_internal(str, true); } diff --git a/marshal.c b/marshal.c index f7474ca60edf07..4372960a7190ef 100644 --- a/marshal.c +++ b/marshal.c @@ -1428,7 +1428,7 @@ long ruby_marshal_read_long(const char **buf, long len) { long x; - struct RString src; + struct RString src = {RBASIC_INIT}; struct load_arg arg; memset(&arg, 0, sizeof(arg)); arg.src = rb_setup_fake_str(&src, *buf, len, 0); diff --git a/string.c b/string.c index a31aaece18f7a1..331f48313600f4 100644 --- a/string.c +++ b/string.c @@ -655,14 +655,14 @@ rb_setup_fake_str(struct RString *fake_str, const char *name, long len, rb_encod VALUE rb_fstring_new(const char *ptr, long len) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII), false, false); } VALUE rb_fstring_enc_new(const char *ptr, long len, rb_encoding *enc) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), false, false); } @@ -6497,7 +6497,7 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) val = rb_obj_as_string(rb_yield(match0)); } else { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; VALUE key; if (mode == FAST_MAP) { // It is safe to use a fake_str here because we established that it won't escape, @@ -12747,7 +12747,7 @@ rb_str_to_interned_str(VALUE str) VALUE rb_interned_str(const char *ptr, long len) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII), true, false); } @@ -12764,7 +12764,7 @@ rb_enc_interned_str(const char *ptr, long len, rb_encoding *enc) rb_enc_autoload(enc); } - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, false); } @@ -12775,7 +12775,7 @@ rb_enc_literal_str(const char *ptr, long len, rb_encoding *enc) rb_enc_autoload(enc); } - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, true); } diff --git a/symbol.c b/symbol.c index e8eacd34c2b4b8..a498f742aa1600 100644 --- a/symbol.c +++ b/symbol.c @@ -845,7 +845,7 @@ lookup_id_str(ID id) ID rb_intern3(const char *name, long len, rb_encoding *enc) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; VALUE str = rb_setup_fake_str(&fake_str, name, len, enc); OBJ_FREEZE(str); @@ -1222,7 +1222,7 @@ rb_check_symbol(volatile VALUE *namep) ID rb_check_id_cstr(const char *ptr, long len, rb_encoding *enc) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; const VALUE name = rb_setup_fake_str(&fake_str, ptr, len, enc); sym_check_asciionly(name, true); @@ -1234,7 +1234,7 @@ VALUE rb_check_symbol_cstr(const char *ptr, long len, rb_encoding *enc) { VALUE sym; - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; const VALUE name = rb_setup_fake_str(&fake_str, ptr, len, enc); sym_check_asciionly(name, true); @@ -1258,7 +1258,7 @@ FUNC_MINIMIZED(VALUE rb_sym_intern_ascii_cstr(const char *ptr)); VALUE rb_sym_intern(const char *ptr, long len, rb_encoding *enc) { - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; const VALUE name = rb_setup_fake_str(&fake_str, ptr, len, enc); return rb_str_intern(name); } From 7c9dd0ecff61153b96473c6c51d5582e809da489 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 18:19:56 +0900 Subject: [PATCH 0197/2435] [Bug #21629] Initialize `struct RArray` --- vm_insnhelper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index f4f0314ed90ece..b99ffdc4fdbbe6 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6428,7 +6428,7 @@ static VALUE vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE target) { if (BASIC_OP_UNREDEFINED_P(BOP_INCLUDE_P, ARRAY_REDEFINED_OP_FLAG)) { - struct RArray fake_ary; + struct RArray fake_ary = {RBASIC_INIT}; VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num); return rb_ary_includes(ary, target); } @@ -6448,7 +6448,7 @@ static VALUE vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt, VALUE buffer) { if (BASIC_OP_UNREDEFINED_P(BOP_PACK, ARRAY_REDEFINED_OP_FLAG)) { - struct RArray fake_ary; + struct RArray fake_ary = {RBASIC_INIT}; VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num); return rb_ec_pack_ary(ec, ary, fmt, (UNDEF_P(buffer) ? Qnil : buffer)); } From b8f8d646a64f883652b44780c4174a85f98d1c82 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:54:14 +0200 Subject: [PATCH 0198/2435] [ruby/prism] For these special cases, there exists no optional argument type. Since a endless method is started with `=`, there was ambiguity here. We have to simply reject these in all cases. This adds a new error for the following reason: * `def foo arg = nil` is interpreted as a normal method call with optional `arg` without matching `end` * `def foo *arg = nil; end` is interpreted as a endless method call that has body `nil` with extraneous `end` `def foo *arg = nil` is somewhere inbetween and I don't know how to otherwise indicate the error. Now the second case above also shows the newly added error message. Fixes [Bug #21623] https://github.com/ruby/prism/commit/e1910d4492 --- prism/config.yml | 1 + prism/prism.c | 32 +++++++++++++++++++ prism/templates/src/diagnostic.c.erb | 1 + test/prism/errors/def_with_optional_splat.txt | 6 ++++ ...endless_method_command_call_parameters.txt | 24 ++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 test/prism/errors/def_with_optional_splat.txt create mode 100644 test/prism/errors/endless_method_command_call_parameters.txt diff --git a/prism/config.yml b/prism/config.yml index a8a8e70aace78e..ed5c9d8b9cde98 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -60,6 +60,7 @@ errors: - CONDITIONAL_WHILE_PREDICATE - CONSTANT_PATH_COLON_COLON_CONSTANT - DEF_ENDLESS + - DEF_ENDLESS_PARAMETERS - DEF_ENDLESS_SETTER - DEF_NAME - DEF_PARAMS_TERM diff --git a/prism/prism.c b/prism/prism.c index f85a69332ed95c..cc1896ee3486ed 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14612,6 +14612,18 @@ update_parameter_state(pm_parser_t *parser, pm_token_t *token, pm_parameters_ord return true; } +/** + * Ensures that after parsing a parameter, the next token is not `=`. + * Some parameters like `def(* = 1)` cannot become optional. When no parens + * are present like in `def * = 1`, this creates ambiguity with endless method definitions. + */ +static inline void +refute_optional_parameter(pm_parser_t *parser) { + if (match1(parser, PM_TOKEN_EQUAL)) { + pm_parser_err_previous(parser, PM_ERR_DEF_ENDLESS_PARAMETERS); + } +} + /** * Parse a list of parameters on a method definition. */ @@ -14664,6 +14676,10 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; } + if (!uses_parentheses) { + refute_optional_parameter(parser); + } + pm_block_parameter_node_t *param = pm_block_parameter_node_create(parser, &name, &operator); if (repeated) { pm_node_flag_set_repeated_parameter((pm_node_t *)param); @@ -14685,6 +14701,10 @@ parse_parameters( bool succeeded = update_parameter_state(parser, &parser->current, &order); parser_lex(parser); + if (!uses_parentheses) { + refute_optional_parameter(parser); + } + parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL; pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous); @@ -14866,6 +14886,10 @@ parse_parameters( context_pop(parser); pm_parameters_node_keywords_append(params, param); + if (!uses_parentheses) { + refute_optional_parameter(parser); + } + // If parsing the value of the parameter resulted in error recovery, // then we can put a missing node in its place and stop parsing the // parameters entirely now. @@ -14897,6 +14921,10 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS; } + if (!uses_parentheses) { + refute_optional_parameter(parser); + } + pm_node_t *param = (pm_node_t *) pm_rest_parameter_node_create(parser, &operator, &name); if (repeated) { pm_node_flag_set_repeated_parameter(param); @@ -14945,6 +14973,10 @@ parse_parameters( } } + if (!uses_parentheses) { + refute_optional_parameter(parser); + } + if (params->keyword_rest == NULL) { pm_parameters_node_keyword_rest_set(params, param); } else { diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 9a30a57e3b0eca..23732530854e50 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -144,6 +144,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_PARAMETERS] = { "could not parse the endless method parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors/def_with_optional_splat.txt b/test/prism/errors/def_with_optional_splat.txt new file mode 100644 index 00000000000000..74a833ceec0dac --- /dev/null +++ b/test/prism/errors/def_with_optional_splat.txt @@ -0,0 +1,6 @@ +def foo(*bar = nil); end + ^ unexpected '='; expected a `)` to close the parameters + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + ^~~ unexpected 'end', ignoring it + diff --git a/test/prism/errors/endless_method_command_call_parameters.txt b/test/prism/errors/endless_method_command_call_parameters.txt new file mode 100644 index 00000000000000..94c4f88fc835df --- /dev/null +++ b/test/prism/errors/endless_method_command_call_parameters.txt @@ -0,0 +1,24 @@ +def f x: = 1 + ^~ could not parse the endless method parameters + +def f ... = 1 + ^~~ could not parse the endless method parameters + +def f * = 1 + ^ could not parse the endless method parameters + +def f ** = 1 + ^~ could not parse the endless method parameters + +def f & = 1 + ^ could not parse the endless method parameters + +def f *a = 1 + ^ could not parse the endless method parameters + +def f **a = 1 + ^ could not parse the endless method parameters + +def f &a = 1 + ^ could not parse the endless method parameters + From 810b3a405bf7431c852778580d44c1421edfcad9 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 1 Aug 2025 18:39:41 +0900 Subject: [PATCH 0199/2435] [ruby/openssl] provider: load "default" provider in test_openssl_legacy_provider Update the test case to explicitly load both the "default" and the "legacy" providers. Currently, the "default" provider as a side effect by the OpenSSL::PKey::DH.new call in lib/openssl/ssl.rb. It will be cleaned up in a following patch. https://github.com/ruby/openssl/commit/013db02fb2 --- test/openssl/test_provider.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb index 6f85c00c988204..10081e208ced1b 100644 --- a/test/openssl/test_provider.rb +++ b/test/openssl/test_provider.rb @@ -46,6 +46,7 @@ def test_openssl_legacy_provider with_openssl(<<-'end;') begin + OpenSSL::Provider.load("default") OpenSSL::Provider.load("legacy") rescue OpenSSL::Provider::ProviderError omit "Only for OpenSSL with legacy provider" From 8dfe5403415fc1bd0c6ce56e5edd8749d081e33d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 20 Apr 2025 19:24:27 +0900 Subject: [PATCH 0200/2435] [ruby/openssl] ssl: fix extconf.rb check for SSL_CTX_set0_tmp_dh_pkey() Check for the function we actually use. Both SSL_set0_tmp_dh_pkey() and SSL_CTX_set0_tmp_dh_pkey() were added in OpenSSL 3.0. https://github.com/ruby/openssl/commit/a9b6a64e5f --- ext/openssl/extconf.rb | 2 +- ext/openssl/ossl_ssl.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 8aac52ef47e546..6c178c12f2b4f4 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -147,7 +147,7 @@ def find_openssl_library have_func("EVP_PKEY_check(NULL)", evp_h) # added in 3.0.0 -have_func("SSL_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) +have_func("SSL_CTX_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) have_func("ERR_get_error_all(NULL, NULL, NULL, NULL, NULL)", "openssl/err.h") have_func("SSL_CTX_load_verify_file(NULL, \"\")", ssl_h) have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h") diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 29564a8139ac2e..9e34bd2520e535 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -1148,7 +1148,7 @@ ossl_sslctx_set_client_sigalgs(VALUE self, VALUE v) * contained in the key object, if any, are ignored. The server will always * generate a new key pair for each handshake. * - * Added in version 3.0. See also the man page SSL_set0_tmp_dh_pkey(3). + * Added in version 3.0. See also the man page SSL_CTX_set0_tmp_dh_pkey(3). * * Example: * ctx = OpenSSL::SSL::SSLContext.new @@ -1169,7 +1169,7 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) rb_raise(eSSLError, "invalid pkey type %s (expected DH)", OBJ_nid2sn(EVP_PKEY_base_id(pkey))); -#ifdef HAVE_SSL_SET0_TMP_DH_PKEY +#ifdef HAVE_SSL_CTX_SET0_TMP_DH_PKEY if (!SSL_CTX_set0_tmp_dh_pkey(ctx, pkey)) ossl_raise(eSSLError, "SSL_CTX_set0_tmp_dh_pkey"); EVP_PKEY_up_ref(pkey); From ea79fe225cc28960595b53cf20e698ec5bbddb0e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 20 Apr 2025 20:26:00 +0900 Subject: [PATCH 0201/2435] [ruby/openssl] ssl: use SSL_CTX_set_dh_auto() by default Rely on OpenSSL's builtin DH parameters for TLS 1.2 and earlier instead of providing a default SSLContext#tmp_dh_callback proc. SSL_CTX_set_dh_auto() has been available since OpenSSL 1.1.0. The parameters can still be overridden by specifying SSLContext#tmp_dh_callback or #tmp_dh, as confirmed by existing tests. SSLContext#tmp_dh_callback depends on a deprecated OpenSSL feature. We also prefer not to hard-code parameters, which is a maintenance burden. This change also improves Ractor compatibility by removing the unshareable proc. https://github.com/ruby/openssl/commit/9cfec9bf5e --- ext/openssl/lib/openssl/ssl.rb | 21 +-------------------- ext/openssl/ossl_ssl.c | 12 ++++++++++-- test/openssl/test_ssl.rb | 6 +----- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index 46509c333e0857..59e379635d15e0 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -32,25 +32,6 @@ class SSLContext }.call } - if defined?(OpenSSL::PKey::DH) - DH_ffdhe2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz -+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a -87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 -YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi -7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD -ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== ------END DH PARAMETERS----- - _end_of_pem_ - private_constant :DH_ffdhe2048 - - DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: - warn "using default DH parameters." if $VERBOSE - DH_ffdhe2048 - } - end - if !OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") DEFAULT_PARAMS.merge!( min_version: OpenSSL::SSL::TLS1_VERSION, @@ -457,7 +438,7 @@ def client_cert_cb end def tmp_dh_callback - @context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK + @context.tmp_dh_callback end def session_new_cb diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 9e34bd2520e535..c7b9cb74a9dcae 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -47,7 +47,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, - id_i_verify_hostname, id_i_keylog_cb; + id_i_verify_hostname, id_i_keylog_cb, id_i_tmp_dh_callback; static ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_ptr_idx; @@ -90,6 +90,7 @@ ossl_sslctx_s_alloc(VALUE klass) ossl_raise(eSSLError, "SSL_CTX_new"); } SSL_CTX_set_mode(ctx, mode); + SSL_CTX_set_dh_auto(ctx, 1); RTYPEDDATA_DATA(obj) = ctx; SSL_CTX_set_ex_data(ctx, ossl_sslctx_ex_ptr_idx, (void *)obj); @@ -703,7 +704,10 @@ ossl_sslctx_setup(VALUE self) GetSSLCTX(self, ctx); #if !defined(OPENSSL_NO_DH) - SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); + if (!NIL_P(rb_attr_get(self, id_i_tmp_dh_callback))) { + SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); + SSL_CTX_set_dh_auto(ctx, 0); + } #endif #if !defined(OPENSSL_IS_AWSLC) /* AWS-LC has no support for TLS 1.3 PHA. */ @@ -1178,6 +1182,9 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) ossl_raise(eSSLError, "SSL_CTX_set_tmp_dh"); #endif + // Turn off the "auto" DH parameters set by ossl_sslctx_s_alloc() + SSL_CTX_set_dh_auto(ctx, 0); + return arg; } #endif @@ -3289,6 +3296,7 @@ Init_ossl_ssl(void) DefIVarID(servername_cb); DefIVarID(verify_hostname); DefIVarID(keylog_cb); + DefIVarID(tmp_dh_callback); DefIVarID(io); DefIVarID(context); diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 7abe2c6df5d330..d843b00f639319 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -2129,11 +2129,7 @@ def test_connect_works_when_setting_dh_callback_to_nil ctx.tmp_dh_callback = nil } start_server(ctx_proc: ctx_proc) do |port| - EnvUtil.suppress_warning { # uses default callback - assert_nothing_raised { - server_connect(port) { } - } - } + assert_nothing_raised { server_connect(port) { } } end end From e4f12808318d743642e6c0a579b35df2eededd3c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 20 Apr 2025 22:28:04 +0900 Subject: [PATCH 0202/2435] [ruby/openssl] ssl: refactor tmp_dh_callback handling tmp_dh_callback no longer has a default value. It also no longer has to share code with tmp_ecdh_callback, which has been removed in v3.0.0. https://github.com/ruby/openssl/commit/b7cde6df2a --- ext/openssl/lib/openssl/ssl.rb | 17 --------- ext/openssl/ossl_ssl.c | 69 +++++++++++++++++----------------- 2 files changed, 35 insertions(+), 51 deletions(-) diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index 59e379635d15e0..61573f7d476b7d 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -73,19 +73,6 @@ class SSLContext DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths - # A callback invoked when DH parameters are required for ephemeral DH key - # exchange. - # - # The callback is invoked with the SSLSocket, a - # flag indicating the use of an export cipher and the keylength - # required. - # - # The callback must return an OpenSSL::PKey::DH instance of the correct - # key length. - # - # Deprecated in version 3.0. Use #tmp_dh= instead. - attr_accessor :tmp_dh_callback - # A callback invoked at connect time to distinguish between multiple # server names. # @@ -437,10 +424,6 @@ def client_cert_cb @context.client_cert_cb end - def tmp_dh_callback - @context.tmp_dh_callback - end - def session_new_cb @context.session_new_cb end diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index c7b9cb74a9dcae..583396ebfc6f3b 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -36,8 +36,7 @@ VALUE cSSLSocket; static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; -static ID id_call, ID_callback_state, id_tmp_dh_callback, - id_npn_protocols_encoded, id_each; +static ID id_call, ID_callback_state, id_npn_protocols_encoded, id_each; static VALUE sym_exception, sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, @@ -134,8 +133,6 @@ ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) #if !defined(OPENSSL_NO_DH) struct tmp_dh_callback_args { VALUE ssl_obj; - ID id; - int type; int is_export; int keylength; }; @@ -144,48 +141,36 @@ static VALUE ossl_call_tmp_dh_callback(VALUE arg) { struct tmp_dh_callback_args *args = (struct tmp_dh_callback_args *)arg; - VALUE cb, dh; - EVP_PKEY *pkey; + VALUE ctx_obj, cb, obj; + const DH *dh; - cb = rb_funcall(args->ssl_obj, args->id, 0); + ctx_obj = rb_attr_get(args->ssl_obj, id_i_context); + cb = rb_attr_get(ctx_obj, id_i_tmp_dh_callback); if (NIL_P(cb)) - return (VALUE)NULL; - dh = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), - INT2NUM(args->keylength)); - pkey = GetPKeyPtr(dh); - if (EVP_PKEY_base_id(pkey) != args->type) - return (VALUE)NULL; + return (VALUE)NULL; + + obj = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), + INT2NUM(args->keylength)); + // TODO: We should riase if obj is not DH + dh = EVP_PKEY_get0_DH(GetPKeyPtr(obj)); + if (!dh) + ossl_clear_error(); - return (VALUE)pkey; + return (VALUE)dh; } -#endif -#if !defined(OPENSSL_NO_DH) static DH * ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) { - VALUE rb_ssl; - EVP_PKEY *pkey; - struct tmp_dh_callback_args args; int state; - - rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); - args.ssl_obj = rb_ssl; - args.id = id_tmp_dh_callback; - args.is_export = is_export; - args.keylength = keylength; - args.type = EVP_PKEY_DH; - - pkey = (EVP_PKEY *)rb_protect(ossl_call_tmp_dh_callback, - (VALUE)&args, &state); + VALUE rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + struct tmp_dh_callback_args args = {rb_ssl, is_export, keylength}; + VALUE ret = rb_protect(ossl_call_tmp_dh_callback, (VALUE)&args, &state); if (state) { rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); return NULL; } - if (!pkey) - return NULL; - - return (DH *)EVP_PKEY_get0_DH(pkey); + return (DH *)ret; } #endif /* OPENSSL_NO_DH */ @@ -2872,6 +2857,23 @@ Init_ossl_ssl(void) */ rb_attr(cSSLContext, rb_intern_const("client_cert_cb"), 1, 1, Qfalse); +#ifndef OPENSSL_NO_DH + /* + * A callback invoked when DH parameters are required for ephemeral DH key + * exchange. + * + * The callback is invoked with the SSLSocket, a + * flag indicating the use of an export cipher and the keylength + * required. + * + * The callback must return an OpenSSL::PKey::DH instance of the correct + * key length. + * + * Deprecated in version 3.0. Use #tmp_dh= instead. + */ + rb_attr(cSSLContext, rb_intern_const("tmp_dh_callback"), 1, 1, Qfalse); +#endif + /* * Sets the context in which a session can be reused. This allows * sessions for multiple applications to be distinguished, for example, by @@ -3265,7 +3267,6 @@ Init_ossl_ssl(void) sym_wait_readable = ID2SYM(rb_intern_const("wait_readable")); sym_wait_writable = ID2SYM(rb_intern_const("wait_writable")); - id_tmp_dh_callback = rb_intern_const("tmp_dh_callback"); id_npn_protocols_encoded = rb_intern_const("npn_protocols_encoded"); id_each = rb_intern_const("each"); From a8b34d9a9beb5c8edb59acf045968795c12d87b8 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 2 Aug 2025 00:48:38 +0900 Subject: [PATCH 0203/2435] [ruby/openssl] ssl: allow SSLContext#set_params to be used from non-main Ractors Freeze OpenSSL::SSL::SSLContext::DEFAULT_PARAMS so that it becomes Ractor-shareable. Also, prepare a new OpenSSL::X509::Store in Ractor-local storage, if called from a non-main Ractor. OpenSSL::X509::Store currently is not a shareable object. https://github.com/ruby/openssl/commit/3d5271327c --- ext/openssl/lib/openssl/ssl.rb | 12 ++++++++-- test/openssl/test_ssl.rb | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index 61573f7d476b7d..3268c126b9efad 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -66,9 +66,10 @@ class SSLContext AES256-SHA256 AES128-SHA AES256-SHA - }.join(":"), + }.join(":").freeze, ) end + DEFAULT_PARAMS.freeze DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths @@ -114,7 +115,14 @@ def set_params(params={}) params.each{|name, value| self.__send__("#{name}=", value) } if self.verify_mode != OpenSSL::SSL::VERIFY_NONE unless self.ca_file or self.ca_path or self.cert_store - self.cert_store = DEFAULT_CERT_STORE + if not defined?(Ractor) or Ractor.current == Ractor.main + self.cert_store = DEFAULT_CERT_STORE + else + self.cert_store = Ractor.current[:__openssl_default_store__] ||= + OpenSSL::X509::Store.new.tap { |store| + store.set_default_paths + } + end end end return params diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index d843b00f639319..3ec1a710436f38 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -2317,6 +2317,50 @@ def test_export_keying_material end end + # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 3.5) + # https://bugs.ruby-lang.org/issues/21109 + # + # Hangs on Windows + # https://bugs.ruby-lang.org/issues/21537 + if respond_to?(:ractor) && RUBY_VERSION >= "3.5" && RUBY_PLATFORM !~ /mswin|mingw/ + ractor + def test_ractor_client + start_server { |port| + s = Ractor.new(port, @ca_cert) { |port, ca_cert| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(ca_cert) + } + begin + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.connect + ssl.puts("abc") + ssl.gets + ensure + ssl.close + sock.close + end + }.value + assert_equal("abc\n", s) + } + end + + ractor + def test_ractor_set_params + # We cannot actually test default stores in the test suite as it depends + # on the environment, but at least check that it does not raise an + # exception + ok = Ractor.new { + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + ctx.cert_store.kind_of?(OpenSSL::X509::Store) + }.value + assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store") + end + end + private def server_connect(port, ctx = nil) From a0c6efdea46584b816afc47cbbe938668ed14476 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 8 Oct 2025 23:28:20 +0900 Subject: [PATCH 0204/2435] [ruby/zlib] Load the same loaded zlib Zlib is used by also rubygems, the built extension cannot be loaded after the default gem is loaded. https://github.com/ruby/zlib/commit/02a29dc7ea --- test/zlib/test_zlib.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 5a8463ad8e719e..bedf243b3e5919 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -9,6 +9,9 @@ begin require 'zlib' rescue LoadError +else + z = "/zlib.#{RbConfig::CONFIG["DLEXT"]}" + LOADED_ZLIB, = $".select {|f| f.end_with?(z)} end if defined? Zlib @@ -1525,7 +1528,7 @@ def test_gunzip_encoding end def test_gunzip_no_memory_leak - assert_no_memory_leak(%[-rzlib], "#{<<~"{#"}", "#{<<~'};'}") + assert_no_memory_leak(%W[-r#{LOADED_ZLIB}], "#{<<~"{#"}", "#{<<~'};'}") d = Zlib.gzip("data") {# 10_000.times {Zlib.gunzip(d)} From 5e7e6040938a37d1c320d69d2266f1b39e1b9f2a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 8 Oct 2025 17:59:51 +0200 Subject: [PATCH 0205/2435] Update to ruby/mspec@6a7b509 --- spec/mspec/lib/mspec/commands/mspec-ci.rb | 1 + spec/mspec/spec/commands/mspec_ci_spec.rb | 5 +++++ spec/mspec/spec/commands/mspec_run_spec.rb | 5 +++++ spec/mspec/tool/sync/sync-rubyspec.rb | 13 ++++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/spec/mspec/lib/mspec/commands/mspec-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb index 9959059ca1c7f2..8951572f693685 100644 --- a/spec/mspec/lib/mspec/commands/mspec-ci.rb +++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb @@ -18,6 +18,7 @@ def options(argv = ARGV) options.chdir options.prefix options.configure { |f| load f } + options.repeat options.pretend options.interrupt options.timeout diff --git a/spec/mspec/spec/commands/mspec_ci_spec.rb b/spec/mspec/spec/commands/mspec_ci_spec.rb index bcbc5b422496a9..b8dc9d062f4d61 100644 --- a/spec/mspec/spec/commands/mspec_ci_spec.rb +++ b/spec/mspec/spec/commands/mspec_ci_spec.rb @@ -78,6 +78,11 @@ @script.options [] end + it "enables the repeat option" do + expect(@options).to receive(:repeat) + @script.options @argv + end + it "calls #custom_options" do expect(@script).to receive(:custom_options).with(@options) @script.options [] diff --git a/spec/mspec/spec/commands/mspec_run_spec.rb b/spec/mspec/spec/commands/mspec_run_spec.rb index 62acd49d7fefac..f96be2b43ee66c 100644 --- a/spec/mspec/spec/commands/mspec_run_spec.rb +++ b/spec/mspec/spec/commands/mspec_run_spec.rb @@ -105,6 +105,11 @@ @script.options @argv end + it "enables the repeat option" do + expect(@options).to receive(:repeat) + @script.options @argv + end + it "exits if there are no files to process and './spec' is not a directory" do expect(File).to receive(:directory?).with("./spec").and_return(false) expect(@options).to receive(:parse).and_return([]) diff --git a/spec/mspec/tool/sync/sync-rubyspec.rb b/spec/mspec/tool/sync/sync-rubyspec.rb index effccc1567c156..617123733e5442 100644 --- a/spec/mspec/tool/sync/sync-rubyspec.rb +++ b/spec/mspec/tool/sync/sync-rubyspec.rb @@ -3,7 +3,7 @@ IMPLS = { truffleruby: { - git: "https://github.com/oracle/truffleruby.git", + git: "https://github.com/truffleruby/truffleruby.git", from_commit: "f10ab6988d", }, jruby: { @@ -34,6 +34,13 @@ SOURCE_REPO = MSPEC ? MSPEC_REPO : RUBYSPEC_REPO +# LAST_MERGE is a commit of ruby/spec or ruby/mspec +# which is the spec/mspec commit that was last imported in the Ruby implementation +# (i.e. the commit in "Update to ruby/spec@commit"). +# It is normally automatically computed, but can be manually set when +# e.g. the last update of specs wasn't merged in the Ruby implementation. +LAST_MERGE = ENV["LAST_MERGE"] + NOW = Time.now BRIGHT_RED = "\e[31;1m" @@ -142,8 +149,8 @@ def rebase_commits(impl) else sh "git", "checkout", impl.name - if ENV["LAST_MERGE"] - last_merge = `git log -n 1 --format='%H %ct' #{ENV["LAST_MERGE"]}` + if LAST_MERGE + last_merge = `git log -n 1 --format='%H %ct' #{LAST_MERGE}` else last_merge = `git log --grep='^#{impl.last_merge_message}' -n 1 --format='%H %ct'` end From 50593d51997eab4aa5a148c5e131d10ea535f955 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 8 Oct 2025 17:59:52 +0200 Subject: [PATCH 0206/2435] Update to ruby/spec@3d7e563 --- spec/ruby/.rubocop_todo.yml | 1 + spec/ruby/README.md | 2 +- spec/ruby/core/array/shared/unshift.rb | 2 +- spec/ruby/core/io/readpartial_spec.rb | 5 + spec/ruby/core/kernel/Float_spec.rb | 2 + spec/ruby/core/kernel/shared/sprintf.rb | 2 +- .../ruby/core/kernel/singleton_method_spec.rb | 46 ++++- spec/ruby/core/kernel/sprintf_spec.rb | 36 +++- spec/ruby/core/kernel/warn_spec.rb | 6 + spec/ruby/core/module/autoload_spec.rb | 15 ++ spec/ruby/core/regexp/compile_spec.rb | 2 +- spec/ruby/core/regexp/new_spec.rb | 2 +- spec/ruby/core/string/to_f_spec.rb | 12 ++ spec/ruby/core/string/unpack1_spec.rb | 13 ++ spec/ruby/core/thread/fixtures/classes.rb | 1 + spec/ruby/core/time/utc_spec.rb | 4 + spec/ruby/core/warning/warn_spec.rb | 28 +++ spec/ruby/language/assignments_spec.rb | 68 ++++++++ spec/ruby/language/method_spec.rb | 160 ++++++++++++++++++ .../library/socket/socket/getaddrinfo_spec.rb | 18 ++ .../library/socket/socket/getnameinfo_spec.rb | 18 ++ .../ruby/library/stringio/readpartial_spec.rb | 30 +++- spec/ruby/optional/capi/ext/set_spec.c | 1 + spec/ruby/shared/kernel/raise.rb | 4 +- 24 files changed, 456 insertions(+), 22 deletions(-) diff --git a/spec/ruby/.rubocop_todo.yml b/spec/ruby/.rubocop_todo.yml index 6b43db9b9a4dd7..bd30f3f14af1ba 100644 --- a/spec/ruby/.rubocop_todo.yml +++ b/spec/ruby/.rubocop_todo.yml @@ -44,6 +44,7 @@ Lint/FloatOutOfRange: Lint/ImplicitStringConcatenation: Exclude: - 'language/string_spec.rb' + - 'core/string/chilled_string_spec.rb' # Offense count: 4 Lint/IneffectiveAccessModifier: diff --git a/spec/ruby/README.md b/spec/ruby/README.md index 56d6c3c5426a7a..674ada4c9e4cc9 100644 --- a/spec/ruby/README.md +++ b/spec/ruby/README.md @@ -26,7 +26,7 @@ ruby/spec is known to be tested in these implementations for every commit: * [MRI](https://rubyci.org/) on 30 platforms and 4 versions * [JRuby](https://github.com/jruby/jruby/tree/master/spec/ruby) for both 1.7 and 9.x -* [TruffleRuby](https://github.com/oracle/truffleruby/tree/master/spec/ruby) +* [TruffleRuby](https://github.com/truffleruby/truffleruby/tree/master/spec/ruby) * [Opal](https://github.com/opal/opal/tree/master/spec) * [Artichoke](https://github.com/artichoke/spec/tree/artichoke-vendor) diff --git a/spec/ruby/core/array/shared/unshift.rb b/spec/ruby/core/array/shared/unshift.rb index 4941e098f665c1..9e0fe7556a43af 100644 --- a/spec/ruby/core/array/shared/unshift.rb +++ b/spec/ruby/core/array/shared/unshift.rb @@ -49,7 +49,7 @@ -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError) end - # https://github.com/oracle/truffleruby/issues/2772 + # https://github.com/truffleruby/truffleruby/issues/2772 it "doesn't rely on Array#[]= so it can be overridden" do subclass = Class.new(Array) do def []=(*) diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 2fcfaf520370e3..176c33cf9e43cd 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -93,6 +93,11 @@ @rd.readpartial(0).should == "" end + it "raises IOError if the stream is closed and the length argument is 0" do + @rd.close + -> { @rd.readpartial(0) }.should raise_error(IOError, "closed stream") + end + it "clears and returns the given buffer if the length argument is 0" do buffer = +"existing content" @rd.readpartial(0, buffer).should == buffer diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 1705205996a9ad..e00fe815726318 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -163,6 +163,7 @@ def to_f() 1.2 end -> { @object.send(:Float, "+1.") }.should raise_error(ArgumentError) -> { @object.send(:Float, "-1.") }.should raise_error(ArgumentError) -> { @object.send(:Float, "1.e+0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e-2") }.should raise_error(ArgumentError) end end @@ -172,6 +173,7 @@ def to_f() 1.2 end @object.send(:Float, "+1.").should == 1.0 @object.send(:Float, "-1.").should == -1.0 @object.send(:Float, "1.e+0").should == 1.0 + @object.send(:Float, "1.e-2").should be_close(0.01, TOLERANCE) end end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index a68389a7b407a2..2b2c6c9b63ff1e 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -449,7 +449,7 @@ def obj.to_str it "is escaped by %" do @method.call("%%").should == "%" - @method.call("%%d", 10).should == "%d" + @method.call("%%d").should == "%d" end end end diff --git a/spec/ruby/core/kernel/singleton_method_spec.rb b/spec/ruby/core/kernel/singleton_method_spec.rb index 0bdf125ad84898..7d63fa7cc61223 100644 --- a/spec/ruby/core/kernel/singleton_method_spec.rb +++ b/spec/ruby/core/kernel/singleton_method_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Kernel#singleton_method" do - it "find a method defined on the singleton class" do + it "finds a method defined on the singleton class" do obj = Object.new def obj.foo; end obj.singleton_method(:foo).should be_an_instance_of(Method) @@ -38,4 +38,48 @@ def foo e.class.should == NameError } end + + ruby_bug "#20620", ""..."3.4" do + it "finds a method defined in a module included in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.include(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module prepended in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.prepend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module that an object is extended with" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.extend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + end end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 9ef7f86f16d833..5a4a90ff7a2860 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -13,28 +13,52 @@ describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { - sprintf(format, *args) + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_encoding, -> format, *args { - sprintf(format, *args) + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_to_str, -> format, *args { - sprintf(format, *args) + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } end describe "Kernel.sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_encoding, -> format, *args { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_to_str, -> format, *args { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } end diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index 00164ad90b2004..e03498c6dc44de 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -112,6 +112,12 @@ ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" end + it "doesn't show the caller when the uplevel is `nil`" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4("foo", nil) }.should output(nil, "foo\n") + end + guard -> { Kernel.instance_method(:tap).source_location } do it "skips { ModuleSpecs::Autoload::DynClass }.should raise_error(NameError, /private constant/) + end + # [ruby-core:19127] [ruby-core:29941] it "does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name" do module ModuleSpecs::Autoload diff --git a/spec/ruby/core/regexp/compile_spec.rb b/spec/ruby/core/regexp/compile_spec.rb index c41399cfbb3539..887c8d77dc3c15 100644 --- a/spec/ruby/core/regexp/compile_spec.rb +++ b/spec/ruby/core/regexp/compile_spec.rb @@ -14,6 +14,6 @@ it_behaves_like :regexp_new_regexp, :compile end -describe "Regexp.new given a non-String/Regexp" do +describe "Regexp.compile given a non-String/Regexp" do it_behaves_like :regexp_new_non_string_or_regexp, :compile end diff --git a/spec/ruby/core/regexp/new_spec.rb b/spec/ruby/core/regexp/new_spec.rb index 65f612df55311f..79210e9a236a04 100644 --- a/spec/ruby/core/regexp/new_spec.rb +++ b/spec/ruby/core/regexp/new_spec.rb @@ -7,11 +7,11 @@ describe "Regexp.new given a String" do it_behaves_like :regexp_new_string, :new + it_behaves_like :regexp_new_string_binary, :new end describe "Regexp.new given a Regexp" do it_behaves_like :regexp_new_regexp, :new - it_behaves_like :regexp_new_string_binary, :new end describe "Regexp.new given a non-String/Regexp" do diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index a91ccc168ecfee..abfd2517b69327 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -127,4 +127,16 @@ }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") end end + + it "allows String representation without a fractional part" do + "1.".to_f.should == 1.0 + "+1.".to_f.should == 1.0 + "-1.".to_f.should == -1.0 + "1.e+0".to_f.should == 1.0 + "1.e+0".to_f.should == 1.0 + + ruby_bug "#20705", ""..."3.4" do + "1.e-2".to_f.should be_close(0.01, TOLERANCE) + end + end end diff --git a/spec/ruby/core/string/unpack1_spec.rb b/spec/ruby/core/string/unpack1_spec.rb index 3b3b879f75f946..cfb47fe6953365 100644 --- a/spec/ruby/core/string/unpack1_spec.rb +++ b/spec/ruby/core/string/unpack1_spec.rb @@ -31,4 +31,17 @@ it "raises an ArgumentError when the offset is larger than the string bytesize" do -> { "a".unpack1("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string") end + + context "with format 'm0'" do + # unpack1("m0") takes a special code path that calls Pack.unpackBase46Strict instead of Pack.unpack_m, + # which is why we repeat the tests for unpack("m0") here. + + it "decodes base64" do + "dGVzdA==".unpack1("m0").should == "test" + end + + it "raises an ArgumentError for an invalid base64 character" do + -> { "dGV%zdA==".unpack1("m0") }.should raise_error(ArgumentError) + end + end end diff --git a/spec/ruby/core/thread/fixtures/classes.rb b/spec/ruby/core/thread/fixtures/classes.rb index 54bd85fae30b6e..7c485660a8f522 100644 --- a/spec/ruby/core/thread/fixtures/classes.rb +++ b/spec/ruby/core/thread/fixtures/classes.rb @@ -27,6 +27,7 @@ def self.raise(*args, **kwargs, &block) thread.join ensure thread.kill if thread.alive? + Thread.pass while thread.alive? # Thread#kill may not terminate a thread immediately so it may be detected as a leaked one end end diff --git a/spec/ruby/core/time/utc_spec.rb b/spec/ruby/core/time/utc_spec.rb index 3d36e13ccfecdb..ab3c0df657e045 100644 --- a/spec/ruby/core/time/utc_spec.rb +++ b/spec/ruby/core/time/utc_spec.rb @@ -43,10 +43,14 @@ it "does not treat time with +00:00 offset as UTC" do Time.new(2022, 1, 1, 0, 0, 0, "+00:00").utc?.should == false + Time.now.localtime("+00:00").utc?.should == false + Time.at(Time.now, in: "+00:00").utc?.should == false end it "does not treat time with 0 offset as UTC" do Time.new(2022, 1, 1, 0, 0, 0, 0).utc?.should == false + Time.now.localtime(0).utc?.should == false + Time.at(Time.now, in: 0).utc?.should == false end end diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb index 572885c2b4ee6d..12677349874e5b 100644 --- a/spec/ruby/core/warning/warn_spec.rb +++ b/spec/ruby/core/warning/warn_spec.rb @@ -97,6 +97,20 @@ def Warning.warn(msg) end end + ruby_version_is "3.4" do + it "warns when category is :strict_unused_block but Warning[:strict_unused_block] is false" do + warn_experimental = Warning[:strict_unused_block] + Warning[:strict_unused_block] = true + begin + -> { + Warning.warn("foo", category: :strict_unused_block) + }.should complain("foo") + ensure + Warning[:strict_unused_block] = warn_experimental + end + end + end + it "doesn't print message when category is :deprecated but Warning[:deprecated] is false" do warn_deprecated = Warning[:deprecated] Warning[:deprecated] = false @@ -121,6 +135,20 @@ def Warning.warn(msg) end end + ruby_version_is "3.4" do + it "doesn't print message when category is :strict_unused_block but Warning[:strict_unused_block] is false" do + warn_experimental = Warning[:strict_unused_block] + Warning[:strict_unused_block] = false + begin + -> { + Warning.warn("foo", category: :strict_unused_block) + }.should_not complain + ensure + Warning[:strict_unused_block] = warn_experimental + end + end + end + ruby_bug '#20573', ''...'3.4' do it "isn't called by Kernel.warn when category is :deprecated but Warning[:deprecated] is false" do warn_deprecated = Warning[:deprecated] diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index 4ad9f4167bbad0..d469459c43d727 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -72,6 +72,34 @@ def []=(k, v, &block) @h[k] = v; end end end end + + context "given keyword arguments" do + before do + @klass = Class.new do + attr_reader :x + + def []=(*args, **kw) + @x = [args, kw] + end + end + end + + ruby_version_is ""..."3.4" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] = 5" + a.x.should == [[1, 2, 3, {b: 4}, 5], {}] + end + end + + ruby_version_is "3.4" do + it "raies SyntaxError when given keyword arguments in index assignments" do + a = @klass.new + -> { eval "a[1, 2, 3, b: 4] = 5" }.should raise_error(SyntaxError, + /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y + end + end + end end describe 'using +=' do @@ -176,6 +204,46 @@ def []=(k, v, &block) @h[k] = v; end end end + context "given keyword arguments" do + before do + @klass = Class.new do + attr_reader :x + + def [](*args) + 100 + end + + def []=(*args, **kw) + @x = [args, kw] + end + end + end + + ruby_version_is ""..."3.3" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] += 5" + a.x.should == [[1, 2, 3, {b: 4}, 105], {}] + end + end + + ruby_version_is "3.3"..."3.4" do + it "supports keyword arguments in index assignments" do + a = @klass.new + eval "a[1, 2, 3, b: 4] += 5" + a.x.should == [[1, 2, 3, 105], {b: 4}] + end + end + + ruby_version_is "3.4" do + it "raies SyntaxError when given keyword arguments in index assignments" do + a = @klass.new + -> { eval "a[1, 2, 3, b: 4] += 5" }.should raise_error(SyntaxError, + /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y + end + end + end + context 'splatted argument' do it 'correctly handles it' do @b[:m] = 10 diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 1d412a133edc30..39b245fe2a9de2 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1487,3 +1487,163 @@ def greet(person) = "Hi, ".dup.concat person greet("Homer").should == "Hi, Homer" end end + +describe "warning about not used block argument" do + ruby_version_is "3.4" do + it "warns when passing a block argument to a method that never uses it" do + def m_that_does_not_use_block + 42 + end + + -> { + m_that_does_not_use_block { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_that_does_not_use_block' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) + end + + it "does not warn when passing a block argument to a method that declares a block parameter" do + def m_with_block_parameter(&block) + 42 + end + + -> { m_with_block_parameter { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that declares an anonymous block parameter" do + def m_with_anonymous_block_parameter(&) + 42 + end + + -> { m_with_anonymous_block_parameter { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that yields an implicit block parameter" do + def m_with_yield + yield 42 + end + + -> { m_with_yield { } }.should_not complain(verbose: true) + end + + it "warns when passing a block argument to a method that calls #block_given?" do + def m_with_block_given + block_given? + end + + -> { + m_with_block_given { } + }.should complain( + /#{__FILE__}:#{__LINE__ - 2}: warning: the block passed to 'm_with_block_given' defined at #{__FILE__}:#{__LINE__ - 7} may be ignored/, + verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super" do + parent = Class.new do + def m + end + end + + child = Class.new(parent) do + def m + super + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super(...)" do + parent = Class.new do + def m(a) + end + end + + child = Class.new(parent) do + def m(...) + super(...) + end + end + + obj = child.new + -> { obj.m(42) { } }.should_not complain(verbose: true) + end + + it "does not warn when called #initialize()" do + klass = Class.new do + def initialize + end + end + + -> { klass.new {} }.should_not complain(verbose: true) + end + + it "does not warn when passing a block argument to a method that calls super()" do + parent = Class.new do + def m + end + end + + child = Class.new(parent) do + def m + super() + end + end + + obj = child.new + -> { obj.m { } }.should_not complain(verbose: true) + end + + it "warns only once per call site" do + def m_that_does_not_use_block + 42 + end + + def call_m_that_does_not_use_block + m_that_does_not_use_block {} + end + + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/, verbose: true) + + -> { + m_that_does_not_use_block { } + }.should_not complain(verbose: true) + end + + it "can be disabled with :strict_unused_block warning category" do + def m_that_does_not_use_block + 42 + end + + # ensure that warning is emitted + -> { m_that_does_not_use_block { } }.should complain(verbose: true) + + warn_experimental = Warning[:strict_unused_block] + Warning[:strict_unused_block] = false + begin + -> { m_that_does_not_use_block { } }.should_not complain(verbose: true) + ensure + Warning[:strict_unused_block] = warn_experimental + end + end + + it "can be enabled with :strict_unused_block = true warning category in not verbose mode" do + def m_that_does_not_use_block + 42 + end + + warn_experimental = Warning[:strict_unused_block] + Warning[:strict_unused_block] = true + begin + -> { + m_that_does_not_use_block { } + }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/) + ensure + Warning[:strict_unused_block] = warn_experimental + end + end + end +end diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb index 9f049597d0d2b4..a2330715ad9713 100644 --- a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb @@ -106,6 +106,24 @@ ] res.each { |a| expected.should include(a) } end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(Socket::ResolutionError) { |e| + e.error_code.should == Socket::EAI_FAMILY + } + end + end end end diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb index 4f13bf484df93e..b3105244a28f70 100644 --- a/spec/ruby/library/socket/socket/getnameinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb @@ -60,6 +60,24 @@ def should_be_valid_dns_name(name) name_info = Socket.getnameinfo ["AF_INET", 9, 'foo', '127.0.0.1'] name_info[1].should == 'discard' end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(Socket::ResolutionError) { |e| + e.error_code.should == Socket::EAI_FAMILY + } + end + end end describe 'Socket.getnameinfo' do diff --git a/spec/ruby/library/stringio/readpartial_spec.rb b/spec/ruby/library/stringio/readpartial_spec.rb index f25cef40148137..988c552235d5e6 100644 --- a/spec/ruby/library/stringio/readpartial_spec.rb +++ b/spec/ruby/library/stringio/readpartial_spec.rb @@ -10,13 +10,7 @@ @string.close unless @string.closed? end - it "raises IOError on closed stream" do - @string.close - -> { @string.readpartial(10) }.should raise_error(IOError) - end - it "reads at most the specified number of bytes" do - # buffered read @string.read(1).should == 'S' # return only specified number, not the whole buffer @@ -67,14 +61,34 @@ it "raises IOError if the stream is closed" do @string.close - -> { @string.readpartial(1) }.should raise_error(IOError) + -> { @string.readpartial(1) }.should raise_error(IOError, "not opened for reading") end it "raises ArgumentError if the negative argument is provided" do - -> { @string.readpartial(-1) }.should raise_error(ArgumentError) + -> { @string.readpartial(-1) }.should raise_error(ArgumentError, "negative length -1 given") end it "immediately returns an empty string if the length argument is 0" do @string.readpartial(0).should == "" end + + it "raises IOError if the stream is closed and the length argument is 0" do + @string.close + -> { @string.readpartial(0) }.should raise_error(IOError, "not opened for reading") + end + + it "clears and returns the given buffer if the length argument is 0" do + buffer = +"existing content" + @string.readpartial(0, buffer).should == buffer + buffer.should == "" + end + + version_is StringIO::VERSION, "3.1.2" do # ruby_version_is "3.4" + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @string.readpartial(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + end end diff --git a/spec/ruby/optional/capi/ext/set_spec.c b/spec/ruby/optional/capi/ext/set_spec.c index 72938be1d887f0..7af922fd49ea96 100644 --- a/spec/ruby/optional/capi/ext/set_spec.c +++ b/spec/ruby/optional/capi/ext/set_spec.c @@ -47,6 +47,7 @@ VALUE set_spec_rb_set_size(VALUE self, VALUE set) { void Init_set_spec(void) { VALUE cls = rb_define_class("CApiSetSpecs", rb_cObject); + rb_define_method(cls, "rb_set_foreach", set_spec_rb_set_foreach, 2); rb_define_method(cls, "rb_set_new", set_spec_rb_set_new, 0); rb_define_method(cls, "rb_set_new_capa", set_spec_rb_set_new_capa, 1); diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index d46c4b7b15f07e..8432c835946d6e 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -191,8 +191,8 @@ def e.exception -> do @object.raise(error, cause: error) - end.should raise_error(RuntimeError, "message") do |error| - error.cause.should == nil + end.should raise_error(RuntimeError, "message") do |e| + e.cause.should == nil end end From 40d704a2bf3324f4472c4436447af391f0987681 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 8 Oct 2025 17:13:21 +0100 Subject: [PATCH 0207/2435] Bump RDoc (#14747) --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 4b95fa525004f7..20f4379dbbb6d7 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.4.1 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.14.2 https://github.com/ruby/rdoc +rdoc 6.15.0 https://github.com/ruby/rdoc ac2a6fbf62b584a8325a665a9e7b368388bc7df6 win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.2 https://github.com/ruby/irb d43c3d764ae439706aa1b26a3ec299cc45eaed5b reline 0.6.2 https://github.com/ruby/reline From 77b019f656b33d8f8af359522d421d66cf4625ee Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 8 Oct 2025 17:22:56 +0100 Subject: [PATCH 0208/2435] ZJIT: Use type alias for num-profile and call-threshold's types (#14777) Co-authored-by: Alan Wu --- zjit/src/options.rs | 18 ++++++++++-------- zjit/src/profile.rs | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 44209b963f3bf9..4ef998acede36c 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -6,24 +6,26 @@ use crate::cruby::*; use std::collections::HashSet; /// Default --zjit-num-profiles -const DEFAULT_NUM_PROFILES: u32 = 5; +const DEFAULT_NUM_PROFILES: NumProfiles = 5; +pub type NumProfiles = u32; /// Default --zjit-call-threshold. This should be large enough to avoid compiling /// warmup code, but small enough to perform well on micro-benchmarks. -pub const DEFAULT_CALL_THRESHOLD: u64 = 30; +pub const DEFAULT_CALL_THRESHOLD: CallThreshold = 30; +pub type CallThreshold = u64; /// Number of calls to start profiling YARV instructions. /// They are profiled `rb_zjit_call_threshold - rb_zjit_profile_threshold` times, /// which is equal to --zjit-num-profiles. #[unsafe(no_mangle)] #[allow(non_upper_case_globals)] -pub static mut rb_zjit_profile_threshold: u64 = DEFAULT_CALL_THRESHOLD - DEFAULT_NUM_PROFILES as u64; +pub static mut rb_zjit_profile_threshold: CallThreshold = DEFAULT_CALL_THRESHOLD - DEFAULT_NUM_PROFILES as CallThreshold; /// Number of calls to compile ISEQ with ZJIT at jit_compile() in vm.c. /// --zjit-call-threshold=1 compiles on first execution without profiling information. #[unsafe(no_mangle)] #[allow(non_upper_case_globals)] -pub static mut rb_zjit_call_threshold: u64 = DEFAULT_CALL_THRESHOLD; +pub static mut rb_zjit_call_threshold: CallThreshold = DEFAULT_CALL_THRESHOLD; /// ZJIT command-line options. This is set before rb_zjit_init() sets /// ZJITState so that we can query some options while loading builtins. @@ -40,7 +42,7 @@ pub struct Options { pub mem_bytes: usize, /// Number of times YARV instructions should be profiled. - pub num_profiles: u32, + pub num_profiles: NumProfiles, /// Enable YJIT statsitics pub stats: bool, @@ -319,14 +321,14 @@ fn update_profile_threshold() { unsafe { rb_zjit_profile_threshold = 0; } } else { // Otherwise, profile instructions at least once. - let num_profiles = get_option!(num_profiles) as u64; - unsafe { rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(num_profiles).max(1) }; + let num_profiles = get_option!(num_profiles); + unsafe { rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(num_profiles.into()).max(1) }; } } /// Update --zjit-call-threshold for testing #[cfg(test)] -pub fn set_call_threshold(call_threshold: u64) { +pub fn set_call_threshold(call_threshold: CallThreshold) { unsafe { rb_zjit_call_threshold = call_threshold; } rb_zjit_prepare_options(); update_profile_threshold(); diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 471ff89e763148..9588a541827f35 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -3,7 +3,7 @@ // We use the YARV bytecode constants which have a CRuby-style name #![allow(non_upper_case_globals)] -use crate::{cruby::*, gc::get_or_create_iseq_payload, options::get_option}; +use crate::{cruby::*, gc::get_or_create_iseq_payload, options::{get_option, NumProfiles}}; use crate::distribution::{Distribution, DistributionSummary}; use crate::stats::Counter::profile_time_ns; use crate::stats::with_time_stat; @@ -278,7 +278,7 @@ pub struct IseqProfile { opnd_types: Vec>, /// Number of profiled executions for each YARV instruction, indexed by the instruction index - num_profiles: Vec, + num_profiles: Vec, } impl IseqProfile { From 501dd27eb249fa3b1546893ecaec033f1ce69fd4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 13:10:49 -0700 Subject: [PATCH 0209/2435] post_push.yml: Write the SSH key more securely Co-authored-by: Nobuyoshi Nakada --- .github/workflows/post_push.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 32d74f644e6bf0..317aad2e42eba1 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -13,8 +13,7 @@ jobs: - name: Sync git.ruby-lang.org run: | mkdir -p ~/.ssh - echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 + (umask 066; printenv RUBY_GIT_SYNC_PRIVATE_KEY > ~/.ssh/id_ed25519) ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" env: From 7ec03e12b4fa1f8a16e35131688bb7fd17e7d097 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:03:11 -0700 Subject: [PATCH 0210/2435] post_push.yml: Migrate commit-email.rb to post_push (#14779) from post-receive.sh as of https://github.com/ruby/git.ruby-lang.org/commit/8d24ac65b5aeb44f7a3212410d6911be621223d4. --- .github/workflows/post_push.yml | 13 ++ tool/commit-mail.rb | 399 ++++++++++++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 tool/commit-mail.rb diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 317aad2e42eba1..cbbcb107e375b6 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -43,6 +43,19 @@ jobs: SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} if: ${{ github.ref == 'refs/heads/master' }} + - name: Notify commit to ruby-cvs + run: | + SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org sendmail" \ + ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ + "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ + --viewer-uri "https://github.com/ruby/ruby/commit/" \ + --error-to cvs-admin@ruby-lang.org + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_REF: ${{ github.ref }} + if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} + - name: Auto-correct code styles run: | set -x diff --git a/tool/commit-mail.rb b/tool/commit-mail.rb new file mode 100644 index 00000000000000..29f096c0e2e2cc --- /dev/null +++ b/tool/commit-mail.rb @@ -0,0 +1,399 @@ +#!/usr/bin/env ruby + +require "optparse" +require "ostruct" +require "nkf" +require "shellwords" + +CommitEmailInfo = Struct.new( + :author, + :author_email, + :revision, + :entire_sha256, + :date, + :log, + :branch, + :diffs, + :added_files, :deleted_files, :updated_files, + :added_dirs, :deleted_dirs, :updated_dirs, +) + +class GitInfoBuilder + GitCommandFailure = Class.new(RuntimeError) + + def initialize(repo_path) + @repo_path = repo_path + end + + def build(oldrev, newrev, refname) + diffs = build_diffs(oldrev, newrev) + + info = CommitEmailInfo.new + info.author = git_show(newrev, format: '%an') + info.author_email = normalize_email(git_show(newrev, format: '%aE')) + info.revision = newrev[0...10] + info.entire_sha256 = newrev + info.date = Time.at(Integer(git_show(newrev, format: '%at'))) + info.log = git_show(newrev, format: '%B') + info.branch = git('rev-parse', '--symbolic', '--abbrev-ref', refname).strip + info.diffs = diffs + info.added_files = find_files(diffs, status: :added) + info.deleted_files = find_files(diffs, status: :deleted) + info.updated_files = find_files(diffs, status: :modified) + info.added_dirs = [] # git does not deal with directory + info.deleted_dirs = [] # git does not deal with directory + info.updated_dirs = [] # git does not deal with directory + info + end + + private + + # Force git-svn email address to @ruby-lang.org to avoid email bounce by invalid email address. + def normalize_email(email) + if email.match(/\A[^@]+@\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/) # git-svn + svn_user, _ = email.split('@', 2) + "#{svn_user}@ruby-lang.org" + else + email + end + end + + def find_files(diffs, status:) + files = [] + diffs.each do |path, values| + if values.keys.first == status + files << path + end + end + files + end + + # SVN version: + # { + # "filename" => { + # "[modified|added|deleted|copied|property_changed]" => { + # type: "[modified|added|deleted|copied|property_changed]", + # body: "diff body", # not implemented because not used + # added: Integer, + # deleted: Integer, + # } + # } + # } + def build_diffs(oldrev, newrev) + diffs = {} + + numstats = git('diff', '--numstat', oldrev, newrev).lines.map { |l| l.strip.split("\t", 3) } + git('diff', '--name-status', oldrev, newrev).each_line do |line| + status, path, _newpath = line.strip.split("\t", 3) + diff = build_diff(path, numstats) + + case status + when 'A' + diffs[path] = { added: { type: :added, **diff } } + when 'M' + diffs[path] = { modified: { type: :modified, **diff } } + when 'C' + diffs[path] = { copied: { type: :copied, **diff } } + when 'D' + diffs[path] = { deleted: { type: :deleted, **diff } } + when /\AR/ # R100 (which does not exist in git.ruby-lang.org's git 2.1.4) + # TODO: implement something + else + $stderr.puts "unexpected git diff status: #{status}" + end + end + + diffs + end + + def build_diff(path, numstats) + diff = { added: 0, deleted: 0 } # :body not implemented because not used + line = numstats.find { |(_added, _deleted, file, *)| file == path } + return diff if line.nil? + + added, deleted, _ = line + if added + diff[:added] = Integer(added) + end + if deleted + diff[:deleted] = Integer(deleted) + end + diff + end + + def git_show(revision, format:) + git('show', "--pretty=#{format}", '--no-patch', revision).strip + end + + def git(*args) + command = ['git', '-C', @repo_path, *args] + output = with_gitenv { IO.popen(command, external_encoding: 'UTF-8', &:read) } + unless $?.success? + raise GitCommandFailure, "failed to execute '#{command.join(' ')}':\n#{output}" + end + output + end + + def with_gitenv + orig = ENV.to_h.dup + begin + ENV.delete('GIT_DIR') + yield + ensure + ENV.replace(orig) + end + end +end + +CommitEmail = Module.new +class << CommitEmail + SENDMAIL = ENV.fetch('SENDMAIL', '/usr/sbin/sendmail') + private_constant :SENDMAIL + + def parse(args) + options = OpenStruct.new + options.error_to = nil + options.viewvc_uri = nil + + opts = OptionParser.new do |opts| + opts.separator('') + + opts.on('-e', '--error-to [TO]', + 'Add [TO] to to address when error is occurred') do |to| + options.error_to = to + end + + opts.on('--viewer-uri [URI]', + 'Use [URI] as URI of revision viewer') do |uri| + options.viewer_uri = uri + end + + opts.on_tail('--help', 'Show this message') do + puts opts + exit + end + end + + return opts.parse(args), options + end + + def main(repo_path, to, rest) + args, options = parse(rest) + + infos = args.each_slice(3).flat_map do |oldrev, newrev, refname| + revisions = IO.popen(['git', 'log', '--reverse', '--pretty=%H', "#{oldrev}^..#{newrev}"], &:read).lines.map(&:strip) + revisions[0..-2].zip(revisions[1..-1]).map do |old, new| + GitInfoBuilder.new(repo_path).build(old, new, refname) + end + end + + infos.each do |info| + next if info.branch.start_with?('notes/') + puts "#{info.branch}: #{info.revision} (#{info.author})" + + from = make_from(name: info.author, email: "noreply@ruby-lang.org") + sendmail(to, from, make_mail(to, from, info, viewer_uri: options.viewer_uri)) + end + end + + def sendmail(to, from, mail) + IO.popen([*SENDMAIL.shellsplit, to], 'w') do |f| + f.print(mail) + end + unless $?.success? + raise "Failed to run `#{SENDMAIL} #{to}` with: '#{mail}'" + end + end + + private + + def b_encode(str) + NKF.nkf('-WwM', str) + end + + def make_body(info, viewer_uri:) + body = '' + body << "#{info.author}\t#{format_time(info.date)}\n" + body << "\n" + body << " New Revision: #{info.revision}\n" + body << "\n" + body << " #{viewer_uri}#{info.revision}\n" + body << "\n" + body << " Log:\n" + body << info.log.lstrip.gsub(/^\t*/, ' ').rstrip + body << "\n\n" + body << added_dirs(info) + body << added_files(info) + body << deleted_dirs(info) + body << deleted_files(info) + body << modified_dirs(info) + body << modified_files(info) + [body.rstrip].pack('M') + end + + def format_time(time) + time.strftime('%Y-%m-%d %X %z (%a, %d %b %Y)') + end + + def changed_items(title, type, items) + rv = '' + unless items.empty? + rv << " #{title} #{type}:\n" + rv << items.collect {|item| " #{item}\n"}.join('') + end + rv + end + + def changed_files(title, files) + changed_items(title, 'files', files) + end + + def added_files(info) + changed_files('Added', info.added_files) + end + + def deleted_files(info) + changed_files('Removed', info.deleted_files) + end + + def modified_files(info) + changed_files('Modified', info.updated_files) + end + + def changed_dirs(title, files) + changed_items(title, 'directories', files) + end + + def added_dirs(info) + changed_dirs('Added', info.added_dirs) + end + + def deleted_dirs(info) + changed_dirs('Removed', info.deleted_dirs) + end + + def modified_dirs(info) + changed_dirs('Modified', info.updated_dirs) + end + + def changed_dirs_info(info, uri) + rev = info.revision + (info.added_dirs.collect do |dir| + " Added: #{dir}\n" + end + info.deleted_dirs.collect do |dir| + " Deleted: #{dir}\n" + end + info.updated_dirs.collect do |dir| + " Modified: #{dir}\n" + end).join("\n") + end + + def diff_info(info, uri) + info.diffs.collect do |key, values| + [ + key, + values.collect do |type, value| + case type + when :added + command = 'cat' + rev = "?revision=#{info.revision}&view=markup" + when :modified, :property_changed + command = 'diff' + prev_revision = (info.revision.is_a?(Integer) ? info.revision - 1 : "#{info.revision}^") + rev = "?r1=#{info.revision}&r2=#{prev_revision}&diff_format=u" + when :deleted, :copied + command = 'cat' + rev = '' + else + raise "unknown diff type: #{value[:type]}" + end + + link = [uri, key.sub(/ .+/, '') || ''].join('/') + rev + + desc = '' + + [desc, link] + end + ] + end + end + + def make_header(to, from, info) + headers = [] + headers << x_author(info) + headers << x_repository(info) + headers << x_revision(info) + headers << x_id(info) + headers << 'Mime-Version: 1.0' + headers << 'Content-Type: text/plain; charset=utf-8' + headers << 'Content-Transfer-Encoding: quoted-printable' + headers << "From: #{from}" + headers << "To: #{to}" + headers << "Subject: #{make_subject(info)}" + headers.find_all do |header| + /\A\s*\z/ !~ header + end.join("\n") + end + + def make_subject(info) + subject = '' + subject << "#{info.revision}" + subject << " (#{info.branch})" + subject << ': ' + subject << info.log.lstrip.lines.first.to_s.strip + b_encode(subject) + end + + # https://tools.ietf.org/html/rfc822#section-4.1 + # https://tools.ietf.org/html/rfc822#section-6.1 + # https://tools.ietf.org/html/rfc822#appendix-D + # https://tools.ietf.org/html/rfc2047 + def make_from(name:, email:) + if name.ascii_only? + escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } + %Q["#{escaped_name}" <#{email}>] + else + escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" + %Q[#{escaped_name} <#{email}>] + end + end + + def x_author(info) + "X-SVN-Author: #{b_encode(info.author)}" + end + + def x_repository(info) + 'X-SVN-Repository: XXX' + end + + def x_id(info) + "X-SVN-Commit-Id: #{info.entire_sha256}" + end + + def x_revision(info) + "X-SVN-Revision: #{info.revision}" + end + + def make_mail(to, from, info, viewer_uri:) + "#{make_header(to, from, info)}\n#{make_body(info, viewer_uri: viewer_uri)}" + end +end + +repo_path, to, *rest = ARGV +begin + CommitEmail.main(repo_path, to, rest) +rescue StandardError => e + $stderr.puts "#{e.class}: #{e.message}" + $stderr.puts e.backtrace + + _, options = CommitEmail.parse(rest) + to = options.error_to + CommitEmail.sendmail(to, to, <<-MAIL) +From: #{to} +To: #{to} +Subject: Error + +#{$!.class}: #{$!.message} +#{$@.join("\n")} +MAIL + exit 1 +end From 5b8f47fa64735fb5ee8c0e82eae3a9704ab4efbd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:06:01 -0700 Subject: [PATCH 0211/2435] post_push.yml: Specify the full path of sendmail --- .github/workflows/post_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index cbbcb107e375b6..91d4a090554974 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -45,7 +45,7 @@ jobs: - name: Notify commit to ruby-cvs run: | - SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org sendmail" \ + SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org /usr/sbin/sendmail" \ ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ --viewer-uri "https://github.com/ruby/ruby/commit/" \ From 86d97331da420069b257c5af8f32ead3a2474cc1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 6 Oct 2025 17:17:14 -0400 Subject: [PATCH 0212/2435] Add RUBY_FREE_AT_EXIT to MANDATORY_ENVS in test_process.rb We need to keep RUBY_FREE_AT_EXIT in these tests. --- test/ruby/test_process.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 221ff37c6b6946..7cea0b3a7027e7 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -280,7 +280,7 @@ def test_overwrite_ENV end; end - MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH] + MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT] case RbConfig::CONFIG['target_os'] when /linux/ MANDATORY_ENVS << 'LD_PRELOAD' From a48592a754299f3b764ac7311fa5feff8e7ada0a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:53:11 -0700 Subject: [PATCH 0213/2435] Let test-tool accept $(TESTS) like test-all does --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 15e314cd1bbe8a..58a8da14021fff 100644 --- a/common.mk +++ b/common.mk @@ -887,7 +887,7 @@ no-test-testframework: PHONY test-tool: $(TEST_RUNNABLE)-test-tool yes-test-tool: prog PHONY $(ACTIONS_GROUP) - $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) "$(TOOL_TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" $(TESTOPTS) + $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) "$(TOOL_TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" $(TESTOPTS) $(TESTS) $(ACTIONS_ENDGROUP) no-test-tool: PHONY From fc08d36a1521e5236cc10ef6bad9cb15693bac9d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 15:04:42 -0700 Subject: [PATCH 0214/2435] post_push.yml: Rename commit-mail.rb back to commit-email.rb. I didn't realize I dropped a letter when I moved it. It wasn't really intended, so I change it back. --- .github/workflows/post_push.yml | 2 +- tool/{commit-mail.rb => commit-email.rb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tool/{commit-mail.rb => commit-email.rb} (100%) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 91d4a090554974..da109ed3bb1897 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -46,7 +46,7 @@ jobs: - name: Notify commit to ruby-cvs run: | SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org /usr/sbin/sendmail" \ - ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ + ruby tool/commit-email.rb . ruby-cvs@g.ruby-lang.org \ "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ --viewer-uri "https://github.com/ruby/ruby/commit/" \ --error-to cvs-admin@ruby-lang.org diff --git a/tool/commit-mail.rb b/tool/commit-email.rb similarity index 100% rename from tool/commit-mail.rb rename to tool/commit-email.rb From 0508786b73d572c5c2f56492823132c439ef2200 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:16:02 -0700 Subject: [PATCH 0215/2435] Migrate a test for commit-email.rb (#14784) from https://github.com/ruby/git.ruby-lang.org/commit/2c4628e489ed00732a5bcde3373d784307c54280. Also drop ostruct from dependencies. We could remove nkf if we use base64, but it's also a bundled gem, so it doesn't really help. --- tool/commit-email.rb | 7 ++-- tool/test/test_commit_mail.rb | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) mode change 100644 => 100755 tool/commit-email.rb create mode 100644 tool/test/test_commit_mail.rb diff --git a/tool/commit-email.rb b/tool/commit-email.rb old mode 100644 new mode 100755 index 29f096c0e2e2cc..d9f79f41c9982a --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby require "optparse" -require "ostruct" require "nkf" require "shellwords" @@ -145,15 +144,15 @@ def with_gitenv end end +CommitEmailOptions = Struct.new(:error_to, :viewer_uri) + CommitEmail = Module.new class << CommitEmail SENDMAIL = ENV.fetch('SENDMAIL', '/usr/sbin/sendmail') private_constant :SENDMAIL def parse(args) - options = OpenStruct.new - options.error_to = nil - options.viewvc_uri = nil + options = CommitEmailOptions.new opts = OptionParser.new do |opts| opts.separator('') diff --git a/tool/test/test_commit_mail.rb b/tool/test/test_commit_mail.rb new file mode 100644 index 00000000000000..4180a71d7bc729 --- /dev/null +++ b/tool/test/test_commit_mail.rb @@ -0,0 +1,63 @@ +require 'test/unit' +require 'shellwords' +require 'tmpdir' +require 'fileutils' +require 'open3' + +class TestCommitEmail < Test::Unit::TestCase + def setup + @ruby = Dir.mktmpdir + Dir.chdir(@ruby) do + git('init') + git('config', 'user.name', 'Jóhän Grübél') + git('config', 'user.email', 'johan@example.com') + git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.') + git('commit', '--allow-empty', '-m', 'Initial revision') + git('commit', '--allow-empty', '-m', 'version 1.0.0') + end + + @sendmail = File.join(Dir.mktmpdir, 'sendmail') + File.write(@sendmail, <<~SENDMAIL) + #!/usr/bin/env ruby + p ARGV + puts STDIN.read + SENDMAIL + FileUtils.chmod(0755, @sendmail) + + @commit_email = File.expand_path('../../tool/commit-email.rb', __dir__) + end + + # Just testing an exit status :p + # TODO: prepare something in test/fixtures/xxx and test output + def test_successful_run + unless EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', true).last.success? + omit "bundled gems are not available" + end + + Dir.chdir(@ruby) do + out, _, status = EnvUtil.invoke_ruby([ + { 'SENDMAIL' => @sendmail }.merge!(gem_env), + @commit_email, './', 'cvs-admin@ruby-lang.org', + git('rev-parse', 'HEAD^').chomp, git('rev-parse', 'HEAD').chomp, 'refs/heads/master', + '--viewer-uri', 'https://github.com/ruby/ruby/commit/', + '--error-to', 'cvs-admin@ruby-lang.org', + ], '', true) + assert_true(status.success?, out) + end + end + + private + + # Cancel the gem environments set by tool/test/init.rb + def gem_env + { 'GEM_PATH' => nil, 'GEM_HOME' => nil } + end + + def git(*cmd) + out, status = Open3.capture2('git', *cmd) + unless status.success? + raise "git #{cmd.shelljoin}\n#{out}" + end + out + end +end From afb21f34987e47932b92530f48a19992863b29b1 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:17:37 -0700 Subject: [PATCH 0216/2435] test_commit_email.rb: Stop printing LoadError on a require attempt --- tool/test/{test_commit_mail.rb => test_commit_email.rb} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename tool/test/{test_commit_mail.rb => test_commit_email.rb} (90%) diff --git a/tool/test/test_commit_mail.rb b/tool/test/test_commit_email.rb similarity index 90% rename from tool/test/test_commit_mail.rb rename to tool/test/test_commit_email.rb index 4180a71d7bc729..539b0b071cd42c 100644 --- a/tool/test/test_commit_mail.rb +++ b/tool/test/test_commit_email.rb @@ -30,8 +30,9 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - unless EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', true).last.success? - omit "bundled gems are not available" + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) + unless status.success? + omit "bundled gems are not available: #{err}" end Dir.chdir(@ruby) do From dbb5972b340f24d9ff4f3996f57439d5a6b3454e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:30:58 -0700 Subject: [PATCH 0217/2435] commit-email.rb: Use base64 instead of nkf which makes it more obvious what it's doing. --- tool/commit-email.rb | 7 ++++--- tool/test/test_commit_email.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index d9f79f41c9982a..d5b17a09c23cb0 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby require "optparse" -require "nkf" +require "base64" require "shellwords" CommitEmailInfo = Struct.new( @@ -207,7 +207,8 @@ def sendmail(to, from, mail) private def b_encode(str) - NKF.nkf('-WwM', str) + base64_str = Base64.encode64(str.force_encoding('UTF-8')).strip # NKF.nkf('-WwMB', str) + "=?UTF-8?B?#{base64_str}?=" end def make_body(info, viewer_uri:) @@ -351,7 +352,7 @@ def make_from(name:, email:) escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } %Q["#{escaped_name}" <#{email}>] else - escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" + escaped_name = b_encode(name) %Q[#{escaped_name} <#{email}>] end end diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 539b0b071cd42c..2d93f11c3e12cf 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -30,7 +30,7 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "base64"'], '', false, true) unless status.success? omit "bundled gems are not available: #{err}" end From 77b62a8292b14e1c1640376a413b9e7e4080b32d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 17:03:17 -0700 Subject: [PATCH 0218/2435] Revert "commit-email.rb: Use base64 instead of nkf" This reverts commit dbb5972b340f24d9ff4f3996f57439d5a6b3454e. It didn't work, sorry. --- tool/commit-email.rb | 7 +++---- tool/test/test_commit_email.rb | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index d5b17a09c23cb0..d9f79f41c9982a 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby require "optparse" -require "base64" +require "nkf" require "shellwords" CommitEmailInfo = Struct.new( @@ -207,8 +207,7 @@ def sendmail(to, from, mail) private def b_encode(str) - base64_str = Base64.encode64(str.force_encoding('UTF-8')).strip # NKF.nkf('-WwMB', str) - "=?UTF-8?B?#{base64_str}?=" + NKF.nkf('-WwM', str) end def make_body(info, viewer_uri:) @@ -352,7 +351,7 @@ def make_from(name:, email:) escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } %Q["#{escaped_name}" <#{email}>] else - escaped_name = b_encode(name) + escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" %Q[#{escaped_name} <#{email}>] end end diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 2d93f11c3e12cf..539b0b071cd42c 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -30,7 +30,7 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "base64"'], '', false, true) + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) unless status.success? omit "bundled gems are not available: #{err}" end From 3d2ee31fdc1917fd754884fc12f71ed2f348d73e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 09:18:39 +0900 Subject: [PATCH 0219/2435] Check core doc coverage always --- .github/workflows/check_misc.yml | 33 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 1c0120252fdf21..38c23b41107a28 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -60,23 +60,16 @@ jobs: - name: Check if to generate documents id: rdoc run: | - ref=$(sed 's/#.*//;/^rdoc /!d' gems/bundled_gems | awk '{print $4}') - echo ref=$ref >> $GITHUB_OUTPUT - # Generate only when document commit/PR - if: >- - ${{false - || contains(github.event.head_commit.message, '[ruby/rdoc]') - || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') - }} + set -- $(sed 's/#.*//;/^rdoc /!d' gems/bundled_gems) + { echo version=$2; echo ref=$4; } >> $GITHUB_OUTPUT + echo RDOC='ruby -W0 --disable-gems tool/rdoc-srcdir -q' >> $GITHUB_ENV - name: Checkout rdoc uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: ruby/rdoc ref: ${{ steps.rdoc.outputs.ref }} - path: .bundle/gems/rdoc-0 + path: .bundle/gems/rdoc-${{ steps.rdoc.outputs.version }} if: ${{ steps.rdoc.outputs.ref != '' }} - name: Generate rdoc @@ -88,18 +81,26 @@ jobs: bundle config --local path vendor/bundle bundle install --jobs 4 bundle exec rake generate - working-directory: .bundle/gems/rdoc-0 + working-directory: .bundle/gems/rdoc-${{ steps.rdoc.outputs.version }} if: ${{ steps.rdoc.outputs.ref != '' }} + - name: Core docs coverage + run: | + $RDOC -C -x ^ext -x ^lib . + - name: Generate docs id: docs run: | - $RDOC -C -x ^ext -x ^lib . $RDOC --op html . echo htmlout=ruby-html-${GITHUB_SHA:0:10} >> $GITHUB_OUTPUT - env: - RDOC: ruby -W0 --disable-gems tool/rdoc-srcdir -q - if: ${{ steps.rdoc.outcome == 'success' }} + # Generate only when document commit/PR + if: >- + ${{false + || contains(github.event.head_commit.message, '[ruby/rdoc]') + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + }} - name: Upload docs uses: actions/upload-artifact@v4 From 44215c1ad904fecb118a172dd1ffc490de0f44b3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 08:44:06 +0900 Subject: [PATCH 0220/2435] [DOC] Mark `Namespace` debug methods to be "nodoc" --- namespace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/namespace.c b/namespace.c index 059642dd94c451..6a8f4abc9d87bf 100644 --- a/namespace.c +++ b/namespace.c @@ -986,6 +986,7 @@ rb_f_dump_classext(VALUE recv, VALUE klass) return res; } +/* :nodoc: */ static VALUE rb_namespace_root_p(VALUE namespace) { @@ -993,6 +994,7 @@ rb_namespace_root_p(VALUE namespace) return RBOOL(NAMESPACE_ROOT_P(ns)); } +/* :nodoc: */ static VALUE rb_namespace_main_p(VALUE namespace) { @@ -1000,6 +1002,7 @@ rb_namespace_main_p(VALUE namespace) return RBOOL(NAMESPACE_MAIN_P(ns)); } +/* :nodoc: */ static VALUE rb_namespace_user_p(VALUE namespace) { From 317c9412a003f24d2b4beb07e55b6e07a9f248cc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 11:40:19 +0900 Subject: [PATCH 0221/2435] Update bundled gems list as of 2025-10-09 bigdecimal fails test-bundled-gems-spec. --- gems/bundled_gems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 20f4379dbbb6d7..d8fa9642ce1eb8 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,14 +6,14 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.25.5 https://github.com/minitest/minitest +minitest 5.26.0 https://github.com/minitest/minitest power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a rake 13.3.0 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp -net-imap 0.5.10 https://github.com/ruby/net-imap 71c0288b9a8f78a7125a4ce2980ab421acdf5836 +net-imap 0.5.12 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix From 5de042f4afb040d9760abcd8f0cf45521409b537 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 12:19:00 +0900 Subject: [PATCH 0222/2435] Revert "[DOC] Fix rendering of $\ in globals.md" This reverts commit 598a8f8914a4f7dd4694963c6de3714f49b3b64e, as RDoc 6.15 handles backslash in backquotes properly, and the previous commit rather rendered an extra backslash. --- doc/globals.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/globals.md b/doc/globals.md index 419dcb3acdfa0e..b9315f5ff975e4 100644 --- a/doc/globals.md +++ b/doc/globals.md @@ -34,10 +34,10 @@ require 'English' ### Separators -| Variable | English | Contains | -|-------------|----------------------------|--------------------------------------------| -| `$/` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | -| `$\\\\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | +| Variable | English | Contains | +|----------|----------------------------|--------------------------------------------| +| `$/` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | +| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | ### Streams @@ -179,7 +179,7 @@ English - `$INPUT_RECORD_SEPARATOR`, `$RS`. Aliased as `$-0`. -### `$\\` (Output Record Separator) +### `$\` (Output Record Separator) An output record separator, initially `nil`. From 6922e969f1441ded72d1595397ae50687944827d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:07:11 -0700 Subject: [PATCH 0223/2435] Allow test-tool to use bundled gems in child processes (#14794) --- tool/test/init.rb | 12 ++++++++++-- tool/test/test_commit_email.rb | 12 ++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tool/test/init.rb b/tool/test/init.rb index 3a1143d01db281..3fd1419a9c85ff 100644 --- a/tool/test/init.rb +++ b/tool/test/init.rb @@ -1,7 +1,15 @@ -# This file includes the settings for "make test-all". +# This file includes the settings for "make test-all" and "make test-tool". # Note that this file is loaded not only by test/runner.rb but also by tool/lib/test/unit/parallel.rb. -ENV["GEM_SKIP"] = ENV["GEM_HOME"] = ENV["GEM_PATH"] = "".freeze +# Prevent test-all from using bundled gems +["GEM_HOME", "GEM_PATH"].each do |gem_env| + # Preserve the gem environment prepared by tool/runruby.rb for test-tool, which uses bundled gems. + ENV["BUNDLED_#{gem_env}"] = ENV[gem_env] + + ENV[gem_env] = "".freeze +end +ENV["GEM_SKIP"] = "".freeze + ENV.delete("RUBY_CODESIGN") Warning[:experimental] = false diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 539b0b071cd42c..7b258734d05231 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -8,7 +8,7 @@ class TestCommitEmail < Test::Unit::TestCase def setup @ruby = Dir.mktmpdir Dir.chdir(@ruby) do - git('init') + git('init', '--initial-branch=master') git('config', 'user.name', 'Jóhän Grübél') git('config', 'user.email', 'johan@example.com') git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.') @@ -30,11 +30,6 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) - unless status.success? - omit "bundled gems are not available: #{err}" - end - Dir.chdir(@ruby) do out, _, status = EnvUtil.invoke_ruby([ { 'SENDMAIL' => @sendmail }.merge!(gem_env), @@ -49,9 +44,10 @@ def test_successful_run private - # Cancel the gem environments set by tool/test/init.rb + # Resurrect the gem environment preserved by tool/test/init.rb. + # This should work as long as you have run `make up` or `make install`. def gem_env - { 'GEM_PATH' => nil, 'GEM_HOME' => nil } + { 'GEM_PATH' => ENV['BUNDLED_GEM_PATH'], 'GEM_HOME' => ENV['BUNDLED_GEM_HOME'] } end def git(*cmd) From 438ea7d69ebc4c485b606e0a8d6cf20a446f2596 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 20:53:31 -0700 Subject: [PATCH 0224/2435] test_commit_email.rb: Test the content of an email --- tool/test/test_commit_email.rb | 52 +++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 7b258734d05231..38fa60394e1126 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -11,15 +11,16 @@ def setup git('init', '--initial-branch=master') git('config', 'user.name', 'Jóhän Grübél') git('config', 'user.email', 'johan@example.com') - git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.') - git('commit', '--allow-empty', '-m', 'Initial revision') - git('commit', '--allow-empty', '-m', 'version 1.0.0') + env = { 'GIT_AUTHOR_DATE' => '2025-10-08T12:00:00Z' } + git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.', env:) + git('commit', '--allow-empty', '-m', 'Initial revision', env:) + git('commit', '--allow-empty', '-m', 'version 1.0.0', env:) end @sendmail = File.join(Dir.mktmpdir, 'sendmail') File.write(@sendmail, <<~SENDMAIL) #!/usr/bin/env ruby - p ARGV + puts "---" puts STDIN.read SENDMAIL FileUtils.chmod(0755, @sendmail) @@ -27,18 +28,47 @@ def setup @commit_email = File.expand_path('../../tool/commit-email.rb', __dir__) end - # Just testing an exit status :p - # TODO: prepare something in test/fixtures/xxx and test output - def test_successful_run + def test_sendmail_encoding Dir.chdir(@ruby) do + before_rev = git('rev-parse', 'HEAD^').chomp + long_rev = git('rev-parse', 'HEAD').chomp + short_rev = long_rev[0...10] + + env = { + 'SENDMAIL' => @sendmail, + }.merge!(gem_env) out, _, status = EnvUtil.invoke_ruby([ { 'SENDMAIL' => @sendmail }.merge!(gem_env), @commit_email, './', 'cvs-admin@ruby-lang.org', - git('rev-parse', 'HEAD^').chomp, git('rev-parse', 'HEAD').chomp, 'refs/heads/master', + before_rev, long_rev, 'refs/heads/master', '--viewer-uri', 'https://github.com/ruby/ruby/commit/', '--error-to', 'cvs-admin@ruby-lang.org', ], '', true) - assert_true(status.success?, out) + + assert_true(status.success?) + assert_equal(out, <<~EOS) + master: #{short_rev} (Jóhän Grübél) + --- + X-SVN-Author: =?UTF-8?B?SsOzaMOkbiBHcsO8YsOpbA==?= + X-SVN-Repository: XXX + X-SVN-Revision: #{short_rev} + X-SVN-Commit-Id: #{long_rev} + Mime-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + From: =?UTF-8?B?SsOzaMOkbiBHcsO8YsOpbA==?= + To: cvs-admin@ruby-lang.org + Subject: #{short_rev} (master): version 1.0.0 + J=C3=B3h=C3=A4n Gr=C3=BCb=C3=A9l\t2025-10-08 05:00:00 -0700 (Wed, 08 Oct 2= + 025) + + New Revision: #{short_rev} + + https://github.com/ruby/ruby/commit/#{short_rev} + + Log: + version 1.0.0= + EOS end end @@ -50,8 +80,8 @@ def gem_env { 'GEM_PATH' => ENV['BUNDLED_GEM_PATH'], 'GEM_HOME' => ENV['BUNDLED_GEM_HOME'] } end - def git(*cmd) - out, status = Open3.capture2('git', *cmd) + def git(*cmd, env: {}) + out, status = Open3.capture2(env, 'git', *cmd) unless status.success? raise "git #{cmd.shelljoin}\n#{out}" end From 08b34bf9098bc00778752388b895ab898f8db5cb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:16:02 -0700 Subject: [PATCH 0225/2435] test_commit_email.rb: Remove an unused local variable --- tool/test/test_commit_email.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 38fa60394e1126..604c18e839af2a 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -34,9 +34,6 @@ def test_sendmail_encoding long_rev = git('rev-parse', 'HEAD').chomp short_rev = long_rev[0...10] - env = { - 'SENDMAIL' => @sendmail, - }.merge!(gem_env) out, _, status = EnvUtil.invoke_ruby([ { 'SENDMAIL' => @sendmail }.merge!(gem_env), @commit_email, './', 'cvs-admin@ruby-lang.org', From 53d1731b68e2b18abd095f8e74dc6de9ab7276e1 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:20:12 -0700 Subject: [PATCH 0226/2435] commit-email.rb: Remove legacy SVN headers --- tool/commit-email.rb | 40 ++++++++-------------------------- tool/test/test_commit_email.rb | 4 ---- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index d9f79f41c9982a..f738261dcca6cd 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -317,20 +317,14 @@ def diff_info(info, uri) end def make_header(to, from, info) - headers = [] - headers << x_author(info) - headers << x_repository(info) - headers << x_revision(info) - headers << x_id(info) - headers << 'Mime-Version: 1.0' - headers << 'Content-Type: text/plain; charset=utf-8' - headers << 'Content-Transfer-Encoding: quoted-printable' - headers << "From: #{from}" - headers << "To: #{to}" - headers << "Subject: #{make_subject(info)}" - headers.find_all do |header| - /\A\s*\z/ !~ header - end.join("\n") + <<~EOS + Mime-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + From: #{from} + To: #{to} + Subject: #{make_subject(info)} + EOS end def make_subject(info) @@ -356,24 +350,8 @@ def make_from(name:, email:) end end - def x_author(info) - "X-SVN-Author: #{b_encode(info.author)}" - end - - def x_repository(info) - 'X-SVN-Repository: XXX' - end - - def x_id(info) - "X-SVN-Commit-Id: #{info.entire_sha256}" - end - - def x_revision(info) - "X-SVN-Revision: #{info.revision}" - end - def make_mail(to, from, info, viewer_uri:) - "#{make_header(to, from, info)}\n#{make_body(info, viewer_uri: viewer_uri)}" + make_header(to, from, info) + make_body(info, viewer_uri: viewer_uri) end end diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 604c18e839af2a..b14bf4e0ce2023 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -46,10 +46,6 @@ def test_sendmail_encoding assert_equal(out, <<~EOS) master: #{short_rev} (Jóhän Grübél) --- - X-SVN-Author: =?UTF-8?B?SsOzaMOkbiBHcsO8YsOpbA==?= - X-SVN-Repository: XXX - X-SVN-Revision: #{short_rev} - X-SVN-Commit-Id: #{long_rev} Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable From f8c841dbd532ea2d834c343467c10bbd4d782abb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:26:54 -0700 Subject: [PATCH 0227/2435] test_commit_email.rb: Use a fixed timezone --- tool/test/test_commit_email.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index b14bf4e0ce2023..216c0823dc28f2 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -11,7 +11,7 @@ def setup git('init', '--initial-branch=master') git('config', 'user.name', 'Jóhän Grübél') git('config', 'user.email', 'johan@example.com') - env = { 'GIT_AUTHOR_DATE' => '2025-10-08T12:00:00Z' } + env = { 'GIT_AUTHOR_DATE' => '2025-10-08T12:00:00Z', 'TZ' => 'UTC' } git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.', env:) git('commit', '--allow-empty', '-m', 'Initial revision', env:) git('commit', '--allow-empty', '-m', 'version 1.0.0', env:) @@ -52,7 +52,7 @@ def test_sendmail_encoding From: =?UTF-8?B?SsOzaMOkbiBHcsO8YsOpbA==?= To: cvs-admin@ruby-lang.org Subject: #{short_rev} (master): version 1.0.0 - J=C3=B3h=C3=A4n Gr=C3=BCb=C3=A9l\t2025-10-08 05:00:00 -0700 (Wed, 08 Oct 2= + J=C3=B3h=C3=A4n Gr=C3=BCb=C3=A9l\t2025-10-08 12:00:00 +0000 (Wed, 08 Oct 2= 025) New Revision: #{short_rev} From a21cde942c94640f2cc46c2a50d71ec758d70d31 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:31:04 -0700 Subject: [PATCH 0228/2435] test_commit_email.rb: Fix the timezone for commit-email.rb as well --- tool/test/test_commit_email.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 216c0823dc28f2..3d53d957b76e87 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -35,7 +35,7 @@ def test_sendmail_encoding short_rev = long_rev[0...10] out, _, status = EnvUtil.invoke_ruby([ - { 'SENDMAIL' => @sendmail }.merge!(gem_env), + { 'SENDMAIL' => @sendmail, 'TZ' => 'UTC' }.merge!(gem_env), @commit_email, './', 'cvs-admin@ruby-lang.org', before_rev, long_rev, 'refs/heads/master', '--viewer-uri', 'https://github.com/ruby/ruby/commit/', From cd8a4406c1ad6c1fed82acad6899b02478246100 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:32:26 -0700 Subject: [PATCH 0229/2435] test_commit_email.rb: Skip the sendmail test on Windows We use only ubuntu-latest on post_push.yml anyway. --- tool/test/test_commit_email.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 3d53d957b76e87..58f0537f43eb17 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -29,6 +29,8 @@ def setup end def test_sendmail_encoding + omit 'the sendmail script does not work on windows' if windows? + Dir.chdir(@ruby) do before_rev = git('rev-parse', 'HEAD^').chomp long_rev = git('rev-parse', 'HEAD').chomp From 127318f4cb08dfbf7f515bdec2d1f0192b03296f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:40:39 -0700 Subject: [PATCH 0230/2435] test_commit_email.rb: Test the encoding of commit messages --- tool/test/test_commit_email.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 58f0537f43eb17..2e3b444a028257 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -14,7 +14,7 @@ def setup env = { 'GIT_AUTHOR_DATE' => '2025-10-08T12:00:00Z', 'TZ' => 'UTC' } git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.', env:) git('commit', '--allow-empty', '-m', 'Initial revision', env:) - git('commit', '--allow-empty', '-m', 'version 1.0.0', env:) + git('commit', '--allow-empty', '-m', 'version 1.0.0', env:) end @sendmail = File.join(Dir.mktmpdir, 'sendmail') @@ -33,13 +33,13 @@ def test_sendmail_encoding Dir.chdir(@ruby) do before_rev = git('rev-parse', 'HEAD^').chomp - long_rev = git('rev-parse', 'HEAD').chomp - short_rev = long_rev[0...10] + after_rev = git('rev-parse', 'HEAD').chomp + short_rev = after_rev[0...10] out, _, status = EnvUtil.invoke_ruby([ { 'SENDMAIL' => @sendmail, 'TZ' => 'UTC' }.merge!(gem_env), @commit_email, './', 'cvs-admin@ruby-lang.org', - before_rev, long_rev, 'refs/heads/master', + before_rev, after_rev, 'refs/heads/master', '--viewer-uri', 'https://github.com/ruby/ruby/commit/', '--error-to', 'cvs-admin@ruby-lang.org', ], '', true) @@ -53,7 +53,7 @@ def test_sendmail_encoding Content-Transfer-Encoding: quoted-printable From: =?UTF-8?B?SsOzaMOkbiBHcsO8YsOpbA==?= To: cvs-admin@ruby-lang.org - Subject: #{short_rev} (master): version 1.0.0 + Subject: #{short_rev} (master): =?UTF-8?B?dmVyc2lvbuOAgDEuMC4w?= J=C3=B3h=C3=A4n Gr=C3=BCb=C3=A9l\t2025-10-08 12:00:00 +0000 (Wed, 08 Oct 2= 025) @@ -62,7 +62,7 @@ def test_sendmail_encoding https://github.com/ruby/ruby/commit/#{short_rev} Log: - version 1.0.0= + version=E3=80=801.0.0= EOS end end From f01254398931256e62d513a098faa29b1f56f636 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 21:59:02 -0700 Subject: [PATCH 0231/2435] test_commit_email.rb: Stop testing the un-encoded name Hoping to work around failures on --with-gmp CI: https://github.com/ruby/ruby/actions/runs/18365603616/job/52317792903 --- tool/test/test_commit_email.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 2e3b444a028257..dcdf014b719481 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -5,6 +5,8 @@ require 'open3' class TestCommitEmail < Test::Unit::TestCase + STDIN_DELIMITER = "---\n" + def setup @ruby = Dir.mktmpdir Dir.chdir(@ruby) do @@ -20,7 +22,7 @@ def setup @sendmail = File.join(Dir.mktmpdir, 'sendmail') File.write(@sendmail, <<~SENDMAIL) #!/usr/bin/env ruby - puts "---" + print #{STDIN_DELIMITER.dump} puts STDIN.read SENDMAIL FileUtils.chmod(0755, @sendmail) @@ -43,11 +45,10 @@ def test_sendmail_encoding '--viewer-uri', 'https://github.com/ruby/ruby/commit/', '--error-to', 'cvs-admin@ruby-lang.org', ], '', true) + stdin = out.split(STDIN_DELIMITER, 2).last assert_true(status.success?) - assert_equal(out, <<~EOS) - master: #{short_rev} (Jóhän Grübél) - --- + assert_equal(stdin, <<~EOS) Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable From a59c5860a6bdaedc5886ee2627dc5c98d5924af5 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 22:30:42 -0700 Subject: [PATCH 0232/2435] test_commit_email.rb: Split out as binary for --with-gmp https://github.com/ruby/ruby/actions/runs/18365998053/job/52318906076 --- tool/test/test_commit_email.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index dcdf014b719481..efd45022be0bab 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -45,7 +45,7 @@ def test_sendmail_encoding '--viewer-uri', 'https://github.com/ruby/ruby/commit/', '--error-to', 'cvs-admin@ruby-lang.org', ], '', true) - stdin = out.split(STDIN_DELIMITER, 2).last + stdin = out.b.split(STDIN_DELIMITER.b, 2).last.force_encoding('UTF-8') assert_true(status.success?) assert_equal(stdin, <<~EOS) From 2223ca1fd6958b278cf535a1e4c6d5b794c06595 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 23:55:14 -0700 Subject: [PATCH 0233/2435] compilers.yml: Run only specified tests for --with-gmp (#14798) It's weird that --with-gmp runs test-tool just because it needs to run test/ruby/test_bignum.rb and spec/ruby/core/integer/*_spec.rb. --- .github/actions/compilers/action.yml | 13 +++++++++--- .github/actions/compilers/entrypoint.sh | 27 ++++++++++--------------- .github/workflows/compilers.yml | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/actions/compilers/action.yml b/.github/actions/compilers/action.yml index 30ccd25a12f487..daad5b694766f9 100644 --- a/.github/actions/compilers/action.yml +++ b/.github/actions/compilers/action.yml @@ -60,11 +60,17 @@ inputs: description: >- Whether to run `make check` - mspecopt: + test_all: required: false default: '' description: >- - Additional options for mspec. + Whether to run `make test-all` with options for test-all. + + test_spec: + required: false + default: '' + description: >- + Whether to run `make test-spec` with options for mspec. static_exts: required: false @@ -105,7 +111,8 @@ runs: --env INPUT_CPPFLAGS='${{ inputs.cppflags }}' --env INPUT_APPEND_CONFIGURE='${{ inputs.append_configure }}' --env INPUT_CHECK='${{ inputs.check }}' - --env INPUT_MSPECOPT='${{ inputs.mspecopt }}' + --env INPUT_TEST_ALL='${{ inputs.test_all }}' + --env INPUT_TEST_SPEC='${{ inputs.test_spec }}' --env INPUT_ENABLE_SHARED='${{ inputs.enable_shared }}' --env INPUT_STATIC_EXTS='${{ inputs.static_exts }}' --env LAUNCHABLE_ORGANIZATION='${{ github.repository_owner }}' diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index 4f9d1304ee2ec2..9a90376d706b7b 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -70,25 +70,20 @@ if [[ -n "${INPUT_STATIC_EXTS}" ]]; then echo "::endgroup::" fi -btests='' -tests='' -spec_opts='' +if [ -n "$INPUT_TEST_ALL" ]; then + tests=" -- $INPUT_TEST_ALL" +else + tests=" -- ruby -ext-" +fi pushd ${builddir} grouped make showflags grouped make all -grouped make test BTESTS="${btests}" - -[[ -z "${INPUT_CHECK}" ]] && exit 0 - -if [ "$INPUT_CHECK" = "true" ]; then - tests+=" -- ruby -ext-" -else - tests+=" -- $INPUT_CHECK" -fi - # grouped make install -grouped make test-tool -grouped make test-all TESTS="$tests" -grouped env CHECK_LEAKS=true make test-spec MSPECOPT="$INPUT_MSPECOPT" SPECOPTS="${spec_opts}" + +# Run only `make test` by default. Run other tests if specified. +grouped make test +if [[ -n "$INPUT_CHECK" ]]; then grouped make test-tool; fi +if [[ -n "$INPUT_CHECK" || -n "$INPUT_TEST_ALL" ]]; then grouped make test-all TESTS="$tests"; fi +if [[ -n "$INPUT_CHECK" || -n "$INPUT_TEST_SPEC" ]]; then grouped env CHECK_LEAKS=true make test-spec MSPECOPT="$INPUT_TEST_SPEC"; fi diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index c53be6f410c5d1..72e908a03c06cf 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -176,7 +176,7 @@ jobs: - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - { uses: './.github/actions/compilers', name: 'C++23', with: { CXXFLAGS: '-std=c++23 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - { uses: './.github/actions/compilers', name: 'C++26', with: { CXXFLAGS: '-std=c++26 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'gmp', with: { append_configure: '--with-gmp', check: 'ruby/test_bignum.rb', mspecopt: "/github/workspace/src/spec/ruby/core/integer" } } + - { uses: './.github/actions/compilers', name: 'gmp', with: { append_configure: '--with-gmp', test_all: 'ruby/test_bignum.rb', test_spec: "/github/workspace/src/spec/ruby/core/integer" } } - { uses: './.github/actions/compilers', name: 'jemalloc', with: { append_configure: '--with-jemalloc' } } - { uses: './.github/actions/compilers', name: 'valgrind', with: { append_configure: '--with-valgrind' } } - { uses: './.github/actions/compilers', name: 'coroutine=ucontext', with: { append_configure: '--with-coroutine=ucontext' } } From a9adc2fcb4ea5b5292e3594cda120a1f3a94b143 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 00:08:17 -0700 Subject: [PATCH 0234/2435] sync_default_gems.yml: Fix the notification condition --- .github/actions/slack/action.yml | 8 +++++++- .github/workflows/sync_default_gems.yml | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/slack/action.yml b/.github/actions/slack/action.yml index 98171efc5eed37..c785b35aa40f3a 100644 --- a/.github/actions/slack/action.yml +++ b/.github/actions/slack/action.yml @@ -18,6 +18,12 @@ inputs: Human-readable description of the run, something like "DEBUG=1". This need not be unique among runs. + event_name: + required: false + default: 'push' + description: >- + Target event to trigger notification. Notify only push by default. + outputs: {} # Nothing? runs: @@ -36,4 +42,4 @@ runs: } env: SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }} - if: ${{github.event_name == 'push' && startsWith(github.repository, 'ruby/')}} + if: ${{ github.event_name == inputs.event_name && startsWith(github.repository, 'ruby/') }} diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index bd5b30b8613898..c6523253934df3 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -62,4 +62,5 @@ jobs: - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + event_name: workflow_dispatch if: ${{ failure() }} From 8cd50a14702fb865b4ae9a0a3568efa34f34672e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 00:35:52 -0700 Subject: [PATCH 0235/2435] sync_default_gems.yml: Notify an extra channel which git.ruby-lang.org used to also notify. --- .github/actions/slack/action.yml | 6 ++++++ .github/workflows/sync_default_gems.yml | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/actions/slack/action.yml b/.github/actions/slack/action.yml index c785b35aa40f3a..4a398da1d1dab3 100644 --- a/.github/actions/slack/action.yml +++ b/.github/actions/slack/action.yml @@ -24,6 +24,11 @@ inputs: description: >- Target event to trigger notification. Notify only push by default. + extra_channel_id: + required: false + description: >- + Slack channel ID to notify besides #alerts and #alerts-emoji. + outputs: {} # Nothing? runs: @@ -39,6 +44,7 @@ runs: "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", "branch": "${{ github.ref_name }}" + ${{ inputs.extra_channel_id && format(', "extra_channel_id": "{0}"', inputs.extra_channel_id) }} } env: SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index c6523253934df3..7110b26d871400 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -63,4 +63,5 @@ jobs: with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot event_name: workflow_dispatch + extra_channel_id: C05FPKAU743 # alerts-sync if: ${{ failure() }} From 5d3bd790348e4228aaf63376fb09b0021851128f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 00:46:44 -0700 Subject: [PATCH 0236/2435] sync_default_gems.yml: Notify which gem failed --- .github/workflows/sync_default_gems.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 7110b26d871400..94e34ec7139718 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -62,6 +62,7 @@ jobs: - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + label: ${{ github.event.inputs.gem }} ${{ github.event.inputs.before[0:10] }}..${{ github.event.inputs.after[0:10] }} event_name: workflow_dispatch extra_channel_id: C05FPKAU743 # alerts-sync if: ${{ failure() }} From baa1aad28bf82a4dfbb3b0a05aca3cb5cfd0027f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 00:55:09 -0700 Subject: [PATCH 0237/2435] sync_default_gems.yml: Link the failed diff --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 94e34ec7139718..ac436695272be1 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -62,7 +62,7 @@ jobs: - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - label: ${{ github.event.inputs.gem }} ${{ github.event.inputs.before[0:10] }}..${{ github.event.inputs.after[0:10] }} + label: "${{ github.event.inputs.gem }} ()" event_name: workflow_dispatch extra_channel_id: C05FPKAU743 # alerts-sync if: ${{ failure() }} From a29c90c3b0bdc355b8b6795488db3aeba2996575 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 01:06:19 -0700 Subject: [PATCH 0238/2435] sync_default_gems.yml: Include the gem name in the job name --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index ac436695272be1..3912f567e388cc 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -17,7 +17,7 @@ on: jobs: sync_default_gems: - name: Sync default gems + name: Sync default gem ${{ github.event.inputs.gem }} permissions: contents: write # for Git to git push From 960c28a4f84e8982fba61702a2ac7a89643ac4f6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 9 Oct 2025 11:26:51 +0200 Subject: [PATCH 0239/2435] FreeBSD returns EAI_FAIL instead of EAI_FAMILY in getaddrinfo and getnameinfo specs --- spec/ruby/library/socket/socket/getaddrinfo_spec.rb | 2 +- spec/ruby/library/socket/socket/getnameinfo_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb index a2330715ad9713..6576af52eeadc7 100644 --- a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb @@ -120,7 +120,7 @@ -> { Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") }.should raise_error(Socket::ResolutionError) { |e| - e.error_code.should == Socket::EAI_FAMILY + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) } end end diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb index b3105244a28f70..af4a10c9c2baa5 100644 --- a/spec/ruby/library/socket/socket/getnameinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb @@ -74,7 +74,7 @@ def should_be_valid_dns_name(name) -> { Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) }.should raise_error(Socket::ResolutionError) { |e| - e.error_code.should == Socket::EAI_FAMILY + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) } end end From f96c332f5c00cd0fee0da79b6d415785bfa8d1f8 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 29 Sep 2025 02:20:57 +0200 Subject: [PATCH 0240/2435] [rubygems/rubygems] Fix `bundle install` when the Gemfile contains "install_if" git gems: - Fix https://github.com/rubygems/rubygems/pull/8985 - ### Problem If you have a Gemfile that contains a `install_if` git gem, it will be impossible to add other gems in the Gemfile and run `bundle install`, you'll get a "The git source [...] is not yet checked out". ### Context The change that modified this behaviour was in https://github.com/rubygems/rubygems/commit/abbea0cc94dd, and the issue is about the call to `current_dependencies`. This call filters out irrelevant dependencies such as the one that get condtionnally installed. By doing so, we skip over setting the source based of the lockfile for that dependency https://github.com/rubygems/rubygems/blob/ade324bdc8ea77b342f203cb7f3929a456d725ed/bundler/lib/bundler/definition.rb#L978 Ultimately, because of this, the dependency source doesn't have any additional information such as the `revision`. Down the line, when we end up to converge the spec, Bundler will attempt to get the revision for that spec but won't be able to because the git source isn't configured to allow running git operations. ### Solution Filter out the irrelevant only spec only after we have set its source. https://github.com/rubygems/rubygems/commit/d2af439671 --- lib/bundler/definition.rb | 12 ++++-- spec/bundler/lock/lockfile_spec.rb | 68 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 49627cc56237ce..cc2394fda60f0a 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -282,12 +282,17 @@ def current_locked_dependencies end def filter_relevant(dependencies) - platforms_array = [Bundler.generic_local_platform].freeze dependencies.select do |d| - d.should_include? && !d.gem_platforms(platforms_array).empty? + relevant_deps?(d) end end + def relevant_deps?(dep) + platforms_array = [Bundler.generic_local_platform].freeze + + dep.should_include? && !dep.gem_platforms(platforms_array).empty? + end + def locked_dependencies @locked_deps.values end @@ -973,10 +978,11 @@ def converge_dependencies @missing_lockfile_dep = nil @changed_dependencies = [] - current_dependencies.each do |dep| + @dependencies.each do |dep| if dep.source dep.source = sources.get(dep.source) end + next unless relevant_deps?(dep) name = dep.name diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 4b767c7415b656..02e53454d89a99 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -2111,6 +2111,74 @@ L end + it "successfully updates the lockfile when a new gem is added in the Gemfile includes a gem that shouldn't be included" do + build_repo4 do + build_gem "logger", "1.7.0" + build_gem "rack", "3.2.0" + build_gem "net-smtp", "0.5.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "logger" + gem "net-smtp" + + install_if -> { false } do + gem 'rack', github: 'rack/rack' + end + G + + lockfile <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + net-smtp + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + shared_examples_for "a lockfile missing dependent specs" do it "auto-heals" do build_repo4 do From 85896219b67177e5d8aee6638104b74594ffa901 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 13:51:13 +0900 Subject: [PATCH 0241/2435] [rubygems/rubygems] Bump up to RubyGems 4.0.0.dev that is next major version https://github.com/rubygems/rubygems/commit/5b963fb7d3 --- lib/rubygems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 8bb8cdfc0486de..b523544e198fa0 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.8.0.dev" + VERSION = "4.0.0.dev" end require_relative "rubygems/defaults" From afe40df42331e338411c84714ca5d21383dac3d4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 14:10:55 +0900 Subject: [PATCH 0242/2435] [rubygems/rubygems] Bump up to Bundler 4.0.0.dev that is next major version https://github.com/rubygems/rubygems/commit/a51334ba99 --- lib/bundler/version.rb | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 5a55b23ac18bcb..0715f6dfee58ba 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.8.0.dev".freeze + VERSION = "4.0.0.dev".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index c26b12dbf1cb87..04a721296c5eb7 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -148,4 +148,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.8.0.dev + 4.0.0.dev diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index cfc47e25fd9a28..a8a09541c80cce 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -168,4 +168,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.8.0.dev + 4.0.0.dev diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 7e2e15e9159ae2..6d68728a85ee02 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -100,4 +100,4 @@ CHECKSUMS tilt (2.6.0) sha256=263d748466e0d83e510aa1a2e2281eff547937f0ef06be33d3632721e255f76b BUNDLED WITH - 2.8.0.dev + 4.0.0.dev From a6faf04092ddf663153f3b5c263a3a086907812c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 14:25:53 +0900 Subject: [PATCH 0243/2435] [rubygems/rubygems] Fixed failing examples with 4.0.0.dev version https://github.com/rubygems/rubygems/commit/0e553c4425 --- spec/bundler/bundler/cli_spec.rb | 6 +++--- spec/bundler/commands/version_spec.rb | 28 +++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 41cd8c636d4041..927906e8371609 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -266,10 +266,10 @@ def out_with_macos_man_workaround RSpec.describe "bundler executable" do it "shows the bundler version just as the `bundle` executable does" do bundler "--version" - expect(out).to eq("Bundler version #{Bundler::VERSION}") + expect(out).to eq("#{Bundler::VERSION}") - bundle "config simulate_version 4" + bundle "config simulate_version 5" bundler "--version" - expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 1019803c8705d4..c683cc0790aa3c 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -12,53 +12,53 @@ context "with -v" do it "outputs the version and virtual version if set" do bundle "-v" - expect(out).to eq("Bundler version #{Bundler::VERSION}") + expect(out).to eq("#{Bundler::VERSION}") - bundle "config simulate_version 4" + bundle "config simulate_version 5" bundle "-v" - expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end end context "with --version" do it "outputs the version and virtual version if set" do bundle "--version" - expect(out).to eq("Bundler version #{Bundler::VERSION}") + expect(out).to eq("#{Bundler::VERSION}") - bundle "config simulate_version 4" + bundle "config simulate_version 5" bundle "--version" - expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end end context "with version" do context "when released", :ruby_repo do before do - system_gems "bundler-2.9.9", released: true + system_gems "bundler-4.9.9", released: true end it "outputs the version, virtual version if set, and build metadata" do bundle "version" - expect(out).to match(/\ABundler version 2\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + expect(out).to match(/\A4\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) - bundle "config simulate_version 4" + bundle "config simulate_version 5" bundle "version" - expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) end end context "when not released" do before do - system_gems "bundler-2.9.9", released: false + system_gems "bundler-4.9.9", released: false end it "outputs the version, virtual version if set, and build metadata" do bundle "version" - expect(out).to match(/\ABundler version 2\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + expect(out).to match(/\A4\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) - bundle "config simulate_version 4" + bundle "config simulate_version 5" bundle "version" - expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) end end end From 8a213f74e3eb2ac51e33cf02e5e11eafb0cf005a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 14:51:31 +0900 Subject: [PATCH 0244/2435] [rubygems/rubygems] Removed Bundler.current_ruby.maglev*? and raise Bundler::RemovedError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/0d4e77d798 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/current_ruby.rb | 16 ++-------------- spec/bundler/bundler/current_ruby_spec.rb | 14 ++++---------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index faec695369722a..b350baa24fd528 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -50,19 +50,10 @@ def jruby? end def maglev? - message = - "`CurrentRuby#maglev?` is deprecated with no replacement. Please use the " \ - "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on." removed_message = "`CurrentRuby#maglev?` was removed with no replacement. Please use the " \ "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on." - internally_exempted = caller_locations(1, 1).first.path == __FILE__ - - unless internally_exempted - SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - end - - RUBY_ENGINE == "maglev" + SharedHelpers.feature_removed!(removed_message) end def truffleruby? @@ -90,14 +81,11 @@ def windows? end define_method(:"maglev_#{trimmed_version}?") do - message = - "`CurrentRuby##{__method__}` is deprecated with no replacement. Please use the " \ - "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check." removed_message = "`CurrentRuby##{__method__}` was removed with no replacement. Please use the " \ "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check." - SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) + SharedHelpers.feature_removed!(removed_message) send(:"maglev?") && send(:"on_#{trimmed_version}?") end diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb index 8764c4971fe83a..83c42e73e1efe4 100644 --- a/spec/bundler/bundler/current_ruby_spec.rb +++ b/spec/bundler/bundler/current_ruby_spec.rb @@ -139,18 +139,12 @@ end describe "Deprecated platform" do - it "Outputs a deprecation warning when calling maglev?" do - expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev\?` is deprecated with no replacement./) - - Bundler.current_ruby.maglev? + it "outputs an error and aborts when calling maglev?" do + expect { Bundler.current_ruby.maglev? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev\?` was removed with no replacement./) end - it "Outputs a deprecation warning when calling maglev_31?" do - expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev_31\?` is deprecated with no replacement./) - - Bundler.current_ruby.maglev_31? + it "outputs an error and aborts when calling maglev_31?" do + expect { Bundler.current_ruby.maglev_31? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev_31\?` was removed with no replacement./) end - - pending "is removed and shows a helpful error message about it", bundler: "4" end end From 89a4b684d9f4d2c4f4f2a6be7d98fa0b97d3dd28 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 14:59:23 +0900 Subject: [PATCH 0245/2435] [rubygems/rubygems] Removed obsoleted example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/b9960f2c6a Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- spec/bundler/install/gemfile/path_spec.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index ea59f11bbe9773..31d79ed41ca5da 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -1,17 +1,6 @@ # frozen_string_literal: true RSpec.describe "bundle install with explicit source paths" do - it "fetches gems with a global path source" do - build_lib "foo" - - install_gemfile <<-G - path "#{lib_path("foo-1.0")}" - gem 'foo' - G - - expect(the_bundle).to include_gems("foo 1.0") - end - it "fetches gems" do build_lib "foo" From 45e6dcd919cd4a813b966998a5f8554596f349f6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 15:20:11 +0900 Subject: [PATCH 0246/2435] [rubygems/rubygems] Removed obsoleted windows platform example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/edd6b1d335 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- spec/bundler/commands/cache_spec.rb | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 80c2ebf68f34ff..1e90f01ce7f8c9 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -207,18 +207,7 @@ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "puts the gems in vendor/cache even for legacy windows rubies, but prints a warning" do - gemfile <<-D - source "https://gem.repo1" - gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] - D - - bundle "cache --all-platforms" - expect(err).to include("deprecated") - expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist - end - - it "prints an error when using legacy windows rubies", bundler: "4" do + it "prints an error when using legacy windows rubies" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] From ccfea54d9bf8b5024b906086b94f20fef88d6693 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 15:23:47 +0900 Subject: [PATCH 0247/2435] [rubygems/rubygems] Catch error instead of deprecated message at --no-keep-file-descriptors option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/8945e0872b Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- spec/bundler/other/major_deprecation_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 6117ff61374845..f61dc1bc8859bc 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -83,11 +83,9 @@ bundle "exec --no-keep-file-descriptors -e 1", raise_on_error: false end - it "is deprecated" do - expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" + it "is removed and shows a helpful error message about it" do + expect(err).to include "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" end - - pending "is removed and shows a helpful error message about it", bundler: "4" end describe "bundle update --quiet" do From 787d0227127f38e7a219714d754eb8506b005e0d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 16:12:02 +0900 Subject: [PATCH 0248/2435] [rubygems/rubygems] bin/rubocop -A https://github.com/rubygems/rubygems/commit/12753b3262 --- spec/bundler/bundler/cli_spec.rb | 2 +- spec/bundler/commands/version_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 927906e8371609..07f589bd5de359 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -266,7 +266,7 @@ def out_with_macos_man_workaround RSpec.describe "bundler executable" do it "shows the bundler version just as the `bundle` executable does" do bundler "--version" - expect(out).to eq("#{Bundler::VERSION}") + expect(out).to eq(Bundler::VERSION.to_s) bundle "config simulate_version 5" bundler "--version" diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index c683cc0790aa3c..995a6e1e2063c3 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -12,7 +12,7 @@ context "with -v" do it "outputs the version and virtual version if set" do bundle "-v" - expect(out).to eq("#{Bundler::VERSION}") + expect(out).to eq(Bundler::VERSION.to_s) bundle "config simulate_version 5" bundle "-v" @@ -23,7 +23,7 @@ context "with --version" do it "outputs the version and virtual version if set" do bundle "--version" - expect(out).to eq("#{Bundler::VERSION}") + expect(out).to eq(Bundler::VERSION.to_s) bundle "config simulate_version 5" bundle "--version" From a05a5263f3788ad452a1240d8c1c15edde9b1a25 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 9 Oct 2025 16:22:19 +0900 Subject: [PATCH 0249/2435] [rubygems/rubygems] Update lockfiles with 4.0.0.dev https://github.com/rubygems/rubygems/commit/82d46d3b28 --- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/vendor_gems.rb.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index ccb51b6fd31eb3..1b6a649c1faa3b 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 2.8.0.dev + 4.0.0.dev diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index d84e09f4337a04..3a8fb336ff821d 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 2.8.0.dev + 4.0.0.dev diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index ff1bcfcf63e1fb..bc1d2acfc98da8 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -119,4 +119,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 2.8.0.dev + 4.0.0.dev diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index a9208d0971ca32..d911764a472649 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -83,4 +83,4 @@ CHECKSUMS uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 BUNDLED WITH - 2.8.0.dev + 4.0.0.dev From faf86fa14bd350c77717f7d0fc64dc9d24f9637a Mon Sep 17 00:00:00 2001 From: git Date: Thu, 9 Oct 2025 11:39:08 +0000 Subject: [PATCH 0250/2435] Update default gems list at a05a5263f3788ad452a1240d8c1c15 [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9639c2908a81f2..f41809097d5b4f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -180,8 +180,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 3.8.0.dev -* bundler 2.8.0.dev +* RubyGems 4.0.0.dev +* bundler 4.0.0.dev * erb 5.0.3 * etc 1.4.6 * fcntl 1.3.0 From aae2e0d45617b8dad66a7b3f49fdd01272caf1bc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 21:56:47 +0900 Subject: [PATCH 0251/2435] [DOC] Update bundled gems list at faf86fa14bd350c77717f7d0fc64dc --- NEWS.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index f41809097d5b4f..6a4bf2831564a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -161,7 +161,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.4.1 * logger 1.7.0 -* rdoc 6.14.2 +* rdoc 6.15.0 * win32ole 1.9.2 * irb 1.15.2 * reline 0.6.2 @@ -205,15 +205,15 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.25.5 +* minitest 5.26.0 * rake 13.3.0 * test-unit 3.7.0 -* rexml 3.4.2 -* net-imap 0.5.10 +* rexml 3.4.4 +* net-imap 0.5.12 * net-smtp 0.5.1 * matrix 0.4.3 * prime 0.1.4 -* rbs 3.9.4 +* rbs 3.9.5 * debug 1.11.0 * base64 0.3.0 * bigdecimal 3.2.2 From 0b0947f84bf9f9a5e80be6b5189e3bd649f90dc7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 18:01:42 +0900 Subject: [PATCH 0252/2435] missing-baseruby.bat: Accept CRuby only `RubyVM::InstructionSequence` is necessary to generate rbinc files. --- tool/missing-baseruby.bat | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tool/missing-baseruby.bat b/tool/missing-baseruby.bat index 9274482a4c9ca6..45cee8c8788ed0 100755 --- a/tool/missing-baseruby.bat +++ b/tool/missing-baseruby.bat @@ -20,4 +20,6 @@ call :warn "executable host ruby is required. use --with-baseruby option." call :warn "Note that BASERUBY must be Ruby 3.1.0 or later." call :abort -: || (:^; abort if RUBY_VERSION < s[%r"warn .*Ruby ([\d.]+)(?:\.0)?",1]) +(goto :eof ^;) +abort unless defined?(RubyVM::InstructionSequence) +abort if RUBY_VERSION < s[%r"warn .*Ruby ([\d.]+)(?:\.0)?",1] From abad1f423ba4ee2f8b5a8fc50b8d803087a0e172 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 9 Oct 2025 22:20:16 +0900 Subject: [PATCH 0253/2435] [DOC] Update required baseruby version --- doc/contributing/building_ruby.md | 2 +- doc/windows.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index eac83fc00ef978..4c69515684afbc 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -17,7 +17,7 @@ * [autoconf] - 2.67 or later * [gperf] - 3.1 or later * Usually unneeded; only if you edit some source files using gperf - * ruby - 3.0 or later + * ruby - 3.1 or later * We can upgrade this version to system ruby version of the latest Ubuntu LTS. * git - 2.32 or later diff --git a/doc/windows.md b/doc/windows.md index dd2ed273ae1f30..26a727d7adb26a 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -122,7 +122,7 @@ sh ../../ruby/configure -C --disable-install-doc --with-opt-dir=C:\Users\usernam 4. If you want to build from GIT source, following commands are required. * `git` - * `ruby` 3.0 or later + * `ruby` 3.1 or later You can use [scoop](https://scoop.sh/) to install them like: From f486b3905f27aefa6063b8f9da0464b08d354c79 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 9 Oct 2025 09:18:12 -0400 Subject: [PATCH 0254/2435] [ruby/prism] Bump to v https://github.com/ruby/prism/commit/7574837b7b --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 65305d0ec1a244..8f6075ad9643d4 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.5.1" + spec.version = "1.5.2" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index b18e770d9213f7..1f15b775ff0266 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.5.1" +#define EXPECTED_PRISM_VERSION "1.5.2" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 366878f709250d..271631b5ace817 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -14,7 +14,7 @@ module Prism # The patch version of prism that we are expecting to find in the serialized # strings. - PATCH_VERSION = 1 + PATCH_VERSION = 2 # Deserialize the dumped output from a request to parse or parse_file. # diff --git a/prism/version.h b/prism/version.h index 697ba0647df1ec..c0d82dc8e2b2f4 100644 --- a/prism/version.h +++ b/prism/version.h @@ -19,11 +19,11 @@ /** * The patch version of the Prism library as an int. */ -#define PRISM_VERSION_PATCH 1 +#define PRISM_VERSION_PATCH 2 /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.5.1" +#define PRISM_VERSION "1.5.2" #endif From fa409d5f3af507a1e4f31642924dd694f37b1c33 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 9 Oct 2025 13:33:33 +0000 Subject: [PATCH 0255/2435] Update default gems list at f486b3905f27aefa6063b8f9da0464 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6a4bf2831564a3..117aae5736bcb0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -192,7 +192,7 @@ The following default gems are updated. * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 -* prism 1.5.1 +* prism 1.5.2 * psych 5.2.6 * resolv 0.6.2 * stringio 3.1.8.dev From d7f2a1ec9a1ae569bf79f06ad44e0f76292c9b06 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 9 Oct 2025 12:42:09 -0400 Subject: [PATCH 0256/2435] ZJIT: Profile opt_aref (#14778) * ZJIT: Profile opt_aref * ZJIT: Add test for opt_aref * ZJIT: Move test and add hash opt test * ZJIT: Update zjit bindgen * ZJIT: Add inspect calls to opt_aref tests --- insns.def | 1 + zjit/src/cruby_bindings.inc.rs | 7 ++-- zjit/src/hir.rs | 58 ++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 1 + 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/insns.def b/insns.def index 239fe85aa51439..eef0d3f5dc1124 100644 --- a/insns.def +++ b/insns.def @@ -1519,6 +1519,7 @@ opt_aref * default_proc. This is a method call. So opt_aref is * (surprisingly) not leaf. */ // attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */ +// attr bool zjit_profile = true; { val = vm_opt_aref(recv, obj); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2d8a8eb11e7036..8eac87c965dbea 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -696,9 +696,10 @@ pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 231; pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 232; pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 233; pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 236; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 237; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 238; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 26145f46226fbb..248c2af7abd5c8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -9069,6 +9069,64 @@ mod opt_tests { "); } + #[test] + fn test_opt_aref_array() { + eval(" + arr = [1,2,3] + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic []@0x1038, v26, v13 + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test [1,2,3]"), @"1"); + } + + #[test] + fn test_opt_aref_hash() { + eval(" + arr = {0 => 4} + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v26:HashExact = GuardType v9, HashExact + v27:BasicObject = CallCFunc []@0x1038, v26, v13 + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test({0 => 4})"), @"4"); + } + #[test] fn test_eliminate_new_range() { eval(" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 9588a541827f35..67f2fdc7403822 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -73,6 +73,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_and => profile_operands(profiler, profile, 2), YARVINSN_opt_or => profile_operands(profiler, profile, 2), YARVINSN_opt_empty_p => profile_operands(profiler, profile, 1), + YARVINSN_opt_aref => profile_operands(profiler, profile, 2), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), YARVINSN_objtostring => profile_operands(profiler, profile, 1), From 09e5c5eed1ee9a58ffa37ecdda999a6ffaea6eb4 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 19:06:49 +0200 Subject: [PATCH 0257/2435] ZJIT: Name enum for bindgen (#14802) Relying on having the same compiler version and behavior across platforms is brittle, as Kokubun points out. Instead, name the enum so we don't have to rely on gensym stability. Fix https://github.com/Shopify/ruby/issues/787 --- zjit.c | 2 +- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby_bindings.inc.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zjit.c b/zjit.c index d877c0bacbd507..e17abc1b37ff29 100644 --- a/zjit.c +++ b/zjit.c @@ -235,7 +235,7 @@ rb_zjit_print_exception(void) rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0)); } -enum { +enum zjit_exported_constants { RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID, }; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index e1d19f9442c62b..64b235b838baff 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -290,7 +290,7 @@ fn main() { .allowlist_function("rb_zjit_insn_leaf") .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") - .allowlist_var("RB_INVALID_SHAPE_ID") + .allowlist_type("zjit_exported_constants") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 8eac87c965dbea..ab442841ff267a 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -723,8 +723,8 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const RB_INVALID_SHAPE_ID: _bindgen_ty_38 = 4294967295; -pub type _bindgen_ty_38 = u32; +pub const RB_INVALID_SHAPE_ID: zjit_exported_constants = 4294967295; +pub type zjit_exported_constants = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: robject_offsets = 16; pub const ROBJECT_OFFSET_AS_ARY: robject_offsets = 16; pub type robject_offsets = u32; From 3c16f321cb217e53e3e4aa9205525c4775f47a44 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 09:55:12 +0200 Subject: [PATCH 0258/2435] ZJIT: Add default FnProperties for unknown functions --- zjit/src/hir.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 248c2af7abd5c8..d132e6662dd733 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2342,7 +2342,14 @@ impl Function { use crate::cruby_methods::FnProperties; // Filter for simple call sites (i.e. no splats etc.) // Commit to the replacement. Put PatchPoint. - if let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = ZJITState::get_method_annotations().get_cfunc_properties(method) { + let props = ZJITState::get_method_annotations().get_cfunc_properties(method) + .unwrap_or(FnProperties { leaf: false, + no_gc: false, + return_type: types::BasicObject, + elidable: false }); + if props.leaf && props.no_gc { + let return_type = props.return_type; + let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { From a47048d5cfed9d51115ee91d0b30c5bbd4569abf Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 09:58:23 +0200 Subject: [PATCH 0259/2435] ZJIT: Add return_type to CCallWithFrame --- zjit/src/hir.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d132e6662dd733..e247b85bed0d38 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -656,7 +656,8 @@ pub enum Insn { args: Vec, cme: *const rb_callable_method_entry_t, name: ID, - state: InsnId + state: InsnId, + return_type: Type, }, /// Call a variadic C function with signature: func(int argc, VALUE *argv, VALUE recv) @@ -1564,7 +1565,7 @@ impl Function { &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, - &CCallWithFrame { cd, cfunc, ref args, cme, name, state } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state) }, + &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type }, &CCallVariadic { cfunc, recv, ref args, cme, name, state } => CCallVariadic { cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state }, @@ -1665,7 +1666,7 @@ impl Function { Insn::NewRangeFixnum { .. } => types::RangeExact, Insn::ObjectAlloc { .. } => types::HeapObject, Insn::ObjectAllocClass { class, .. } => Type::from_class(*class), - Insn::CCallWithFrame { .. } => types::BasicObject, + &Insn::CCallWithFrame { return_type, .. } => return_type, Insn::CCall { return_type, .. } => *return_type, Insn::CCallVariadic { .. } => types::BasicObject, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), @@ -2347,8 +2348,8 @@ impl Function { no_gc: false, return_type: types::BasicObject, elidable: false }); + let return_type = props.return_type; if props.leaf && props.no_gc { - let return_type = props.return_type; let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); @@ -2356,7 +2357,7 @@ impl Function { if get_option!(stats) { count_not_inlined_cfunc(fun, block, method); } - let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state }); + let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state, return_type }); fun.make_equal_to(send_insn_id, ccall); } From 5c986c7da275df0de3107fbea28e8f1ee592444b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:07:13 +0200 Subject: [PATCH 0260/2435] ZJIT: Allow no properties to annotate! macro --- zjit/src/cruby_methods.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 41cff3403bcdfa..ecfaadd8e344bc 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -140,11 +140,12 @@ pub fn init() -> Annotations { let builtin_funcs = &mut HashMap::new(); macro_rules! annotate { - ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { + ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { + #[allow(unused_mut)] let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type }; $( props.$properties = true; - )+ + )* annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); } } From e1c998ab917ba3a319ca0fd32f0857f1dbace906 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:07:27 +0200 Subject: [PATCH 0261/2435] ZJIT: Annotate Array#reverse as returning ArrayExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index ecfaadd8e344bc..1df348bc9eef82 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -173,6 +173,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cArray, "reverse", types::ArrayExact); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e247b85bed0d38..32024102cf218b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12437,4 +12437,28 @@ mod opt_tests { Return v14 "); } + + #[test] + fn test_array_reverse_returns_array() { + eval(r#" + def test = [].reverse + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v22:ArrayExact = CallCFunc reverse@0x1038, v11 + CheckInterrupts + Return v22 + "); + } } From fc735e257d65ba09ebc62dd698fda35bf4f0a585 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:12:46 +0200 Subject: [PATCH 0262/2435] ZJIT: Allow marking CCallWithFrame elidable Also mark Array#reverse as elidable. --- zjit/src/cruby_methods.rs | 2 +- zjit/src/hir.rs | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 1df348bc9eef82..334d4e2337e60b 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -173,7 +173,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cArray, "reverse", types::ArrayExact); + annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 32024102cf218b..ec6746ec398c74 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -658,6 +658,7 @@ pub enum Insn { name: ID, state: InsnId, return_type: Type, + elidable: bool, }, /// Call a variadic C function with signature: func(int argc, VALUE *argv, VALUE recv) @@ -846,6 +847,7 @@ impl Insn { Insn::LoadIvarEmbedded { .. } => false, Insn::LoadIvarExtended { .. } => false, Insn::CCall { elidable, .. } => !elidable, + Insn::CCallWithFrame { elidable, .. } => !elidable, Insn::ObjectAllocClass { .. } => false, // TODO: NewRange is effects free if we can prove the two ends to be Fixnum, // but we don't have type information here in `impl Insn`. See rb_range_new(). @@ -1565,7 +1567,7 @@ impl Function { &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, - &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type }, + &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type, elidable }, &CCallVariadic { cfunc, recv, ref args, cme, name, state } => CCallVariadic { cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state }, @@ -2349,15 +2351,15 @@ impl Function { return_type: types::BasicObject, elidable: false }); let return_type = props.return_type; + let elidable = props.elidable; if props.leaf && props.no_gc { - let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { if get_option!(stats) { count_not_inlined_cfunc(fun, block, method); } - let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state, return_type }); + let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } @@ -12461,4 +12463,31 @@ mod opt_tests { Return v22 "); } + + #[test] + fn test_array_reverse_is_elidable() { + eval(r#" + def test + [].reverse + 5 + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } } From d798e3c46ffb25ec1000893d7e41ea8bc4dffed9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:21:46 +0200 Subject: [PATCH 0263/2435] ZJIT: Allow annotating CCallVariadic --- zjit/src/codegen.rs | 2 +- zjit/src/cruby_methods.rs | 12 ++++++++++++ zjit/src/hir.rs | 26 ++++++++++++++------------ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b1a2cb672641ef..4b9331e05b0892 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -409,7 +409,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, args, cme, state, .. } => gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, &function.frame_state(*state)), - Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state } => { + Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 334d4e2337e60b..7721ca844035ad 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -31,6 +31,18 @@ pub struct FnProperties { pub elidable: bool, } +/// A safe default for un-annotated Ruby methods: we can't optimize them or their returned values. +impl Default for FnProperties { + fn default() -> Self { + Self { + no_gc: false, + leaf: false, + return_type: types::BasicObject, + elidable: false, + } + } +} + impl Annotations { /// Query about properties of a C method pub fn get_cfunc_properties(&self, method: *const rb_callable_method_entry_t) -> Option { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ec6746ec398c74..cee546b0261307 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -670,6 +670,8 @@ pub enum Insn { cme: *const rb_callable_method_entry_t, name: ID, state: InsnId, + return_type: Type, + elidable: bool, }, /// Un-optimized fallback implementation (dynamic dispatch) for send-ish instructions @@ -1568,8 +1570,8 @@ impl Function { &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type, elidable }, - &CCallVariadic { cfunc, recv, ref args, cme, name, state } => CCallVariadic { - cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state + &CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable } => CCallVariadic { + cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable }, &Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, @@ -1670,7 +1672,7 @@ impl Function { Insn::ObjectAllocClass { class, .. } => Type::from_class(*class), &Insn::CCallWithFrame { return_type, .. } => return_type, Insn::CCall { return_type, .. } => *return_type, - Insn::CCallVariadic { .. } => types::BasicObject, + &Insn::CCallVariadic { return_type, .. } => return_type, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_value(*expected)), @@ -2325,10 +2327,12 @@ impl Function { let ci_flags = unsafe { vm_ci_flag(call_info) }; + // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { return Err(()); } + // Commit to the replacement. Put PatchPoint. gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); @@ -2341,17 +2345,10 @@ impl Function { let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); - // Filter for a leaf and GC free function - use crate::cruby_methods::FnProperties; - // Filter for simple call sites (i.e. no splats etc.) - // Commit to the replacement. Put PatchPoint. - let props = ZJITState::get_method_annotations().get_cfunc_properties(method) - .unwrap_or(FnProperties { leaf: false, - no_gc: false, - return_type: types::BasicObject, - elidable: false }); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method).unwrap_or_default(); let return_type = props.return_type; let elidable = props.elidable; + // Filter for a leaf and GC free function if props.leaf && props.no_gc { let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); @@ -2385,6 +2382,9 @@ impl Function { } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method).unwrap_or_default(); + let return_type = props.return_type; + let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCallVariadic { cfunc, recv, @@ -2392,6 +2392,8 @@ impl Function { cme: method, name: method_id, state, + return_type, + elidable, }); fun.make_equal_to(send_insn_id, ccall); From 9020341bb4847d21339f6f45dceb2e498efd002c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:23:15 +0200 Subject: [PATCH 0264/2435] ZJIT: Annotate Array#join as returning StringExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 7721ca844035ad..5cb42291b02b22 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -186,6 +186,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); + annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index cee546b0261307..5a8ebaf2f26d61 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12492,4 +12492,30 @@ mod opt_tests { Return v16 "); } + + #[test] + fn test_array_join_returns_string() { + eval(r#" + def test = [].join "," + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v14:StringExact = StringCopy v12 + PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:StringExact = CCallVariadic join@0x1040, v11, v14 + CheckInterrupts + Return v25 + "); + } } From 6a25a8b1e28cc5c1b28fc12717a0fade4da23a7c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:38:26 +0200 Subject: [PATCH 0265/2435] ZJIT: Get stats for which C functions are not annotated --- zjit.rb | 1 + zjit/src/hir.rs | 25 +++++++++++++++++++++++-- zjit/src/state.rs | 9 +++++++++ zjit/src/stats.rs | 7 +++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/zjit.rb b/zjit.rb index 75c57f9a35c195..87ff52f55a1c21 100644 --- a/zjit.rb +++ b/zjit.rb @@ -156,6 +156,7 @@ def stats_string # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5a8ebaf2f26d61..23f4e828c821eb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2345,7 +2345,11 @@ impl Function { let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); - let props = ZJITState::get_method_annotations().get_cfunc_properties(method).unwrap_or_default(); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); let return_type = props.return_type; let elidable = props.elidable; // Filter for a leaf and GC free function @@ -2382,7 +2386,11 @@ impl Function { } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); - let props = ZJITState::get_method_annotations().get_cfunc_properties(method).unwrap_or_default(); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); let return_type = props.return_type; let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCallVariadic { @@ -2426,6 +2434,19 @@ impl Function { fun.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); } + fn count_not_annotated_cfunc(fun: &mut Function, block: BlockId, cme: *const rb_callable_method_entry_t) { + let owner = unsafe { (*cme).owner }; + let called_id = unsafe { (*cme).called_id }; + let class_name = get_class_name(owner); + let method_name = called_id.contents_lossy(); + let qualified_method_name = format!("{}#{}", class_name, method_name); + let not_annotated_cfunc_counter_pointers = ZJITState::get_not_annotated_cfunc_counter_pointers(); + let counter_ptr = not_annotated_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); + let counter_ptr = &mut **counter_ptr as *mut u64; + + fun.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); + } + for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); assert!(self.blocks[block.0].insns.is_empty()); diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 409cac7e9bb421..1b766d5bc4b5aa 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -54,6 +54,9 @@ pub struct ZJITState { /// Counter pointers for full frame C functions full_frame_cfunc_counter_pointers: HashMap>, + /// Counter pointers for un-annotated C functions + not_annotated_frame_cfunc_counter_pointers: HashMap>, + /// Locations of side exists within generated code exit_locations: Option, } @@ -98,6 +101,7 @@ impl ZJITState { function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, full_frame_cfunc_counter_pointers: HashMap::new(), + not_annotated_frame_cfunc_counter_pointers: HashMap::new(), exit_locations, }; unsafe { ZJIT_STATE = Some(zjit_state); } @@ -167,6 +171,11 @@ impl ZJITState { &mut ZJITState::get_instance().full_frame_cfunc_counter_pointers } + /// Get a mutable reference to non-annotated cfunc counter pointers + pub fn get_not_annotated_cfunc_counter_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().not_annotated_frame_cfunc_counter_pointers + } + /// Was --zjit-save-compiled-iseqs specified? pub fn should_log_compiled_iseqs() -> bool { get_option!(log_compiled_iseqs).is_some() diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index a9b7270444a7a5..6898053dca789b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -480,6 +480,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, &key_string, **counter); } + // Set not annotated cfunc counters + let not_annotated_cfuncs = ZJITState::get_not_annotated_cfunc_counter_pointers(); + for (signature, counter) in not_annotated_cfuncs.iter() { + let key_string = format!("not_annotated_cfuncs_{}", signature); + set_stat_usize!(hash, &key_string, **counter); + } + hash } From d25d993aa32cb963d9d4d9d9a8220c1fc69e1e19 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:44:02 +0200 Subject: [PATCH 0266/2435] ZJIT: Annotate String#to_s as returning StringExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 5cb42291b02b22..7467cb50c85446 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -180,6 +180,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", types::BasicObject, no_gc, leaf, elidable); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); + annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 23f4e828c821eb..f8e8c7539d92e3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12539,4 +12539,29 @@ mod opt_tests { Return v25 "); } + + #[test] + fn test_string_to_s_returns_string() { + eval(r#" + def test = "".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v23:StringExact = CallCFunc to_s@0x1040, v12 + CheckInterrupts + Return v23 + "); + } } From 117e5b68c84f48c22057f19fe60ed5468c988b76 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 10:47:57 +0200 Subject: [PATCH 0267/2435] ZJIT: Print CCallWithFrame as CCallWithFrame, not CallCFunc --- zjit/src/hir.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index f8e8c7539d92e3..f9283cba79b6c2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1044,7 +1044,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) }, Insn::CCallWithFrame { cfunc, args, name, .. } => { - write!(f, "CallCFunc {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + write!(f, "CCallWithFrame {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } @@ -10985,7 +10985,7 @@ mod opt_tests { v11:HashExact = NewHash PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) - v24:BasicObject = CallCFunc dup@0x1038, v11 + v24:BasicObject = CCallWithFrame dup@0x1038, v11 v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 @@ -11078,7 +11078,7 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v24:BasicObject = CallCFunc dup@0x1038, v11 + v24:BasicObject = CCallWithFrame dup@0x1038, v11 v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 @@ -11172,7 +11172,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CallCFunc dup@0x1040, v12 + v25:BasicObject = CCallWithFrame dup@0x1040, v12 v16:BasicObject = SendWithoutBlock v25, :freeze CheckInterrupts Return v16 @@ -11267,7 +11267,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CallCFunc dup@0x1040, v12 + v25:BasicObject = CCallWithFrame dup@0x1040, v12 v16:BasicObject = SendWithoutBlock v25, :-@ CheckInterrupts Return v16 @@ -11409,7 +11409,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) v30:ArrayExact = GuardType v9, ArrayExact - v31:BasicObject = CallCFunc to_s@0x1040, v30 + v31:BasicObject = CCallWithFrame to_s@0x1040, v30 v17:String = AnyToString v9, str: v31 v19:StringExact = StringConcat v13, v17 CheckInterrupts @@ -12481,7 +12481,7 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v22:ArrayExact = CallCFunc reverse@0x1038, v11 + v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 CheckInterrupts Return v22 "); @@ -12559,7 +12559,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:StringExact = CallCFunc to_s@0x1040, v12 + v23:StringExact = CCallWithFrame to_s@0x1040, v12 CheckInterrupts Return v23 "); From b999ca0fce8116e9a218731bbbc171a849e53a86 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 9 Oct 2025 19:25:08 +0200 Subject: [PATCH 0268/2435] ZJIT: Fix land race --- zjit/src/hir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index f9283cba79b6c2..2fe8eb79700349 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -9153,7 +9153,7 @@ mod opt_tests { PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) v26:HashExact = GuardType v9, HashExact - v27:BasicObject = CallCFunc []@0x1038, v26, v13 + v27:BasicObject = CCallWithFrame []@0x1038, v26, v13 CheckInterrupts Return v27 "); From 83d0b064c88df718e13bb8d6b4182ec635f7b03b Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 9 Oct 2025 13:02:10 -0400 Subject: [PATCH 0269/2435] ZJIT: Use clang-16 for bindgen on CI Since many of us developing ZJIT are on at least Clang 16 locally now due to recent macOS update, let's use Clang 16 on CI, too. There is some differences between https://github.com/llvm/llvm-project Clang and Apple Clang, even when the version number match, but I figure it's good to shrink the difference anyways. --- .github/workflows/zjit-ubuntu.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index a30ff1df88fdd9..02afda470aec47 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -69,8 +69,9 @@ jobs: - test_task: 'zjit-bindgen' hint: 'To fix: use patch in logs' - configure: '--enable-zjit=dev --with-gcc=clang-14' - libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' + configure: '--enable-zjit=dev --with-gcc=clang-16' + clang_path: '/usr/bin/clang-16' + runs-on: 'ubuntu-24.04' # for clang-16 - test_task: 'test-bundled-gems' configure: '--enable-zjit=dev' @@ -87,7 +88,7 @@ jobs: RUST_BACKTRACE: 1 ZJIT_RB_BUG: 1 - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.runs-on || 'ubuntu-22.04' }} if: >- ${{!(false @@ -175,7 +176,7 @@ jobs: PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' ZJIT_BINDGEN_DIFF_OPTS: '--exit-code' - LIBCLANG_PATH: ${{ matrix.libclang_path }} + CLANG_PATH: ${{ matrix.clang_path }} TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} From 864e8fb029b10bfe2af14d679600d22e33d7ea35 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 10 Oct 2025 07:37:27 +0900 Subject: [PATCH 0270/2435] win32: Enable extensions explicitly --- win32/configure.bat | 2 +- win32/ifchange.bat | 2 ++ win32/install-buildtools.cmd | 2 +- win32/lastrev.bat | 3 ++- win32/makedirs.bat | 2 +- win32/rm.bat | 2 +- win32/rmdirs.bat | 2 +- win32/rtname.cmd | 1 + win32/vssetup.cmd | 2 +- 9 files changed, 11 insertions(+), 7 deletions(-) diff --git a/win32/configure.bat b/win32/configure.bat index 79384a87590067..8f767ede73256a 100755 --- a/win32/configure.bat +++ b/win32/configure.bat @@ -1,5 +1,5 @@ @echo off -@setlocal disabledelayedexpansion +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 set PROMPT=$E[94m+$E[m$S set witharg= diff --git a/win32/ifchange.bat b/win32/ifchange.bat index c7a57fad3583c4..f3fc9ea37c6203 100755 --- a/win32/ifchange.bat +++ b/win32/ifchange.bat @@ -1,6 +1,8 @@ @echo off :: usage: ifchange target temporary +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 + :: @set PROMPT=$T:$S for %%I in (%0) do set progname=%%~nI set timestamp= diff --git a/win32/install-buildtools.cmd b/win32/install-buildtools.cmd index 6ec147528000db..fbbe051f717b3e 100755 --- a/win32/install-buildtools.cmd +++ b/win32/install-buildtools.cmd @@ -1,5 +1,5 @@ @echo off -setlocal +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 set components=VC.Tools.x86.x64 VC.Redist.14.Latest CoreBuildTools set components=%components% Windows11SDK.26100 diff --git a/win32/lastrev.bat b/win32/lastrev.bat index f1c799f8976f2c..c4ce61e34abe82 100755 --- a/win32/lastrev.bat +++ b/win32/lastrev.bat @@ -1,5 +1,6 @@ -@setlocal @echo off +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 + if "%1" == "" (set gitdir=.) else (set gitdir=%1) set TZ=UTC for /f "usebackq tokens=1-3" %%I in ( diff --git a/win32/makedirs.bat b/win32/makedirs.bat index 13740d877809ed..8c06d94041c46b 100755 --- a/win32/makedirs.bat +++ b/win32/makedirs.bat @@ -1,3 +1,3 @@ @echo off -setlocal EnableExtensions +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 for %%I in (%*) do if not exist "%%~I/." mkdir "%%~I" diff --git a/win32/rm.bat b/win32/rm.bat index fefc0305458e57..500a4abe2e5562 100755 --- a/win32/rm.bat +++ b/win32/rm.bat @@ -1,5 +1,5 @@ @echo off -setlocal +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 set recursive= :optloop if "%1" == "-f" shift diff --git a/win32/rmdirs.bat b/win32/rmdirs.bat index 308b6483229353..c3d7b637b3ea2a 100755 --- a/win32/rmdirs.bat +++ b/win32/rmdirs.bat @@ -1,5 +1,5 @@ -::-*- batch -*- @echo off +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 if "%1" == "-p" shift :begin if "%1" == "" goto :end diff --git a/win32/rtname.cmd b/win32/rtname.cmd index 775e81681a5c6c..1ac008ebf06d3c 100755 --- a/win32/rtname.cmd +++ b/win32/rtname.cmd @@ -1,4 +1,5 @@ @echo off +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 set rt= set rtver= set osver= diff --git a/win32/vssetup.cmd b/win32/vssetup.cmd index c67bb0ad7c07de..2bbfbf1384f27e 100755 --- a/win32/vssetup.cmd +++ b/win32/vssetup.cmd @@ -1,4 +1,4 @@ -@setlocal ENABLEEXTENSIONS +@setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 ::- do not `echo off` that affects the called batch files ::- check for vswhere From 04ed9c1a7c44c780305a33ad5e7871f475fc181f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 7 Oct 2025 20:58:49 -0400 Subject: [PATCH 0271/2435] Rename ext/-test-/asan to ext/-test-/sanitizers --- ext/-test-/asan/depend | 162 ------------------ ext/-test-/asan/extconf.rb | 2 - ext/-test-/sanitizers/depend | 162 ++++++++++++++++++ ext/-test-/sanitizers/extconf.rb | 2 + .../{asan/asan.c => sanitizers/sanitizers.c} | 4 +- tool/lib/core_assertions.rb | 4 +- 6 files changed, 168 insertions(+), 168 deletions(-) delete mode 100644 ext/-test-/asan/depend delete mode 100644 ext/-test-/asan/extconf.rb create mode 100644 ext/-test-/sanitizers/depend create mode 100644 ext/-test-/sanitizers/extconf.rb rename ext/-test-/{asan/asan.c => sanitizers/sanitizers.c} (84%) diff --git a/ext/-test-/asan/depend b/ext/-test-/asan/depend deleted file mode 100644 index 93cdc739ec63b7..00000000000000 --- a/ext/-test-/asan/depend +++ /dev/null @@ -1,162 +0,0 @@ -# AUTOGENERATED DEPENDENCIES START -asan.o: $(RUBY_EXTCONF_H) -asan.o: $(arch_hdrdir)/ruby/config.h -asan.o: $(hdrdir)/ruby/assert.h -asan.o: $(hdrdir)/ruby/backward.h -asan.o: $(hdrdir)/ruby/backward/2/assume.h -asan.o: $(hdrdir)/ruby/backward/2/attributes.h -asan.o: $(hdrdir)/ruby/backward/2/bool.h -asan.o: $(hdrdir)/ruby/backward/2/inttypes.h -asan.o: $(hdrdir)/ruby/backward/2/limits.h -asan.o: $(hdrdir)/ruby/backward/2/long_long.h -asan.o: $(hdrdir)/ruby/backward/2/stdalign.h -asan.o: $(hdrdir)/ruby/backward/2/stdarg.h -asan.o: $(hdrdir)/ruby/defines.h -asan.o: $(hdrdir)/ruby/intern.h -asan.o: $(hdrdir)/ruby/internal/abi.h -asan.o: $(hdrdir)/ruby/internal/anyargs.h -asan.o: $(hdrdir)/ruby/internal/arithmetic.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/char.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/double.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/int.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/long.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/short.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h -asan.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h -asan.o: $(hdrdir)/ruby/internal/assume.h -asan.o: $(hdrdir)/ruby/internal/attr/alloc_size.h -asan.o: $(hdrdir)/ruby/internal/attr/artificial.h -asan.o: $(hdrdir)/ruby/internal/attr/cold.h -asan.o: $(hdrdir)/ruby/internal/attr/const.h -asan.o: $(hdrdir)/ruby/internal/attr/constexpr.h -asan.o: $(hdrdir)/ruby/internal/attr/deprecated.h -asan.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h -asan.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h -asan.o: $(hdrdir)/ruby/internal/attr/error.h -asan.o: $(hdrdir)/ruby/internal/attr/flag_enum.h -asan.o: $(hdrdir)/ruby/internal/attr/forceinline.h -asan.o: $(hdrdir)/ruby/internal/attr/format.h -asan.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h -asan.o: $(hdrdir)/ruby/internal/attr/noalias.h -asan.o: $(hdrdir)/ruby/internal/attr/nodiscard.h -asan.o: $(hdrdir)/ruby/internal/attr/noexcept.h -asan.o: $(hdrdir)/ruby/internal/attr/noinline.h -asan.o: $(hdrdir)/ruby/internal/attr/nonnull.h -asan.o: $(hdrdir)/ruby/internal/attr/noreturn.h -asan.o: $(hdrdir)/ruby/internal/attr/packed_struct.h -asan.o: $(hdrdir)/ruby/internal/attr/pure.h -asan.o: $(hdrdir)/ruby/internal/attr/restrict.h -asan.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h -asan.o: $(hdrdir)/ruby/internal/attr/warning.h -asan.o: $(hdrdir)/ruby/internal/attr/weakref.h -asan.o: $(hdrdir)/ruby/internal/cast.h -asan.o: $(hdrdir)/ruby/internal/compiler_is.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/apple.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/clang.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/intel.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h -asan.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h -asan.o: $(hdrdir)/ruby/internal/compiler_since.h -asan.o: $(hdrdir)/ruby/internal/config.h -asan.o: $(hdrdir)/ruby/internal/constant_p.h -asan.o: $(hdrdir)/ruby/internal/core.h -asan.o: $(hdrdir)/ruby/internal/core/rarray.h -asan.o: $(hdrdir)/ruby/internal/core/rbasic.h -asan.o: $(hdrdir)/ruby/internal/core/rbignum.h -asan.o: $(hdrdir)/ruby/internal/core/rclass.h -asan.o: $(hdrdir)/ruby/internal/core/rdata.h -asan.o: $(hdrdir)/ruby/internal/core/rfile.h -asan.o: $(hdrdir)/ruby/internal/core/rhash.h -asan.o: $(hdrdir)/ruby/internal/core/robject.h -asan.o: $(hdrdir)/ruby/internal/core/rregexp.h -asan.o: $(hdrdir)/ruby/internal/core/rstring.h -asan.o: $(hdrdir)/ruby/internal/core/rstruct.h -asan.o: $(hdrdir)/ruby/internal/core/rtypeddata.h -asan.o: $(hdrdir)/ruby/internal/ctype.h -asan.o: $(hdrdir)/ruby/internal/dllexport.h -asan.o: $(hdrdir)/ruby/internal/dosish.h -asan.o: $(hdrdir)/ruby/internal/error.h -asan.o: $(hdrdir)/ruby/internal/eval.h -asan.o: $(hdrdir)/ruby/internal/event.h -asan.o: $(hdrdir)/ruby/internal/fl_type.h -asan.o: $(hdrdir)/ruby/internal/gc.h -asan.o: $(hdrdir)/ruby/internal/glob.h -asan.o: $(hdrdir)/ruby/internal/globals.h -asan.o: $(hdrdir)/ruby/internal/has/attribute.h -asan.o: $(hdrdir)/ruby/internal/has/builtin.h -asan.o: $(hdrdir)/ruby/internal/has/c_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h -asan.o: $(hdrdir)/ruby/internal/has/extension.h -asan.o: $(hdrdir)/ruby/internal/has/feature.h -asan.o: $(hdrdir)/ruby/internal/has/warning.h -asan.o: $(hdrdir)/ruby/internal/intern/array.h -asan.o: $(hdrdir)/ruby/internal/intern/bignum.h -asan.o: $(hdrdir)/ruby/internal/intern/class.h -asan.o: $(hdrdir)/ruby/internal/intern/compar.h -asan.o: $(hdrdir)/ruby/internal/intern/complex.h -asan.o: $(hdrdir)/ruby/internal/intern/cont.h -asan.o: $(hdrdir)/ruby/internal/intern/dir.h -asan.o: $(hdrdir)/ruby/internal/intern/enum.h -asan.o: $(hdrdir)/ruby/internal/intern/enumerator.h -asan.o: $(hdrdir)/ruby/internal/intern/error.h -asan.o: $(hdrdir)/ruby/internal/intern/eval.h -asan.o: $(hdrdir)/ruby/internal/intern/file.h -asan.o: $(hdrdir)/ruby/internal/intern/hash.h -asan.o: $(hdrdir)/ruby/internal/intern/io.h -asan.o: $(hdrdir)/ruby/internal/intern/load.h -asan.o: $(hdrdir)/ruby/internal/intern/marshal.h -asan.o: $(hdrdir)/ruby/internal/intern/numeric.h -asan.o: $(hdrdir)/ruby/internal/intern/object.h -asan.o: $(hdrdir)/ruby/internal/intern/parse.h -asan.o: $(hdrdir)/ruby/internal/intern/proc.h -asan.o: $(hdrdir)/ruby/internal/intern/process.h -asan.o: $(hdrdir)/ruby/internal/intern/random.h -asan.o: $(hdrdir)/ruby/internal/intern/range.h -asan.o: $(hdrdir)/ruby/internal/intern/rational.h -asan.o: $(hdrdir)/ruby/internal/intern/re.h -asan.o: $(hdrdir)/ruby/internal/intern/ruby.h -asan.o: $(hdrdir)/ruby/internal/intern/select.h -asan.o: $(hdrdir)/ruby/internal/intern/select/largesize.h -asan.o: $(hdrdir)/ruby/internal/intern/set.h -asan.o: $(hdrdir)/ruby/internal/intern/signal.h -asan.o: $(hdrdir)/ruby/internal/intern/sprintf.h -asan.o: $(hdrdir)/ruby/internal/intern/string.h -asan.o: $(hdrdir)/ruby/internal/intern/struct.h -asan.o: $(hdrdir)/ruby/internal/intern/thread.h -asan.o: $(hdrdir)/ruby/internal/intern/time.h -asan.o: $(hdrdir)/ruby/internal/intern/variable.h -asan.o: $(hdrdir)/ruby/internal/intern/vm.h -asan.o: $(hdrdir)/ruby/internal/interpreter.h -asan.o: $(hdrdir)/ruby/internal/iterator.h -asan.o: $(hdrdir)/ruby/internal/memory.h -asan.o: $(hdrdir)/ruby/internal/method.h -asan.o: $(hdrdir)/ruby/internal/module.h -asan.o: $(hdrdir)/ruby/internal/newobj.h -asan.o: $(hdrdir)/ruby/internal/scan_args.h -asan.o: $(hdrdir)/ruby/internal/special_consts.h -asan.o: $(hdrdir)/ruby/internal/static_assert.h -asan.o: $(hdrdir)/ruby/internal/stdalign.h -asan.o: $(hdrdir)/ruby/internal/stdbool.h -asan.o: $(hdrdir)/ruby/internal/stdckdint.h -asan.o: $(hdrdir)/ruby/internal/symbol.h -asan.o: $(hdrdir)/ruby/internal/value.h -asan.o: $(hdrdir)/ruby/internal/value_type.h -asan.o: $(hdrdir)/ruby/internal/variable.h -asan.o: $(hdrdir)/ruby/internal/warning_push.h -asan.o: $(hdrdir)/ruby/internal/xmalloc.h -asan.o: $(hdrdir)/ruby/missing.h -asan.o: $(hdrdir)/ruby/ruby.h -asan.o: $(hdrdir)/ruby/st.h -asan.o: $(hdrdir)/ruby/subst.h -asan.o: asan.c -# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/asan/extconf.rb b/ext/-test-/asan/extconf.rb deleted file mode 100644 index ec02742b8145e4..00000000000000 --- a/ext/-test-/asan/extconf.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'mkmf' -create_makefile('-test-/asan') diff --git a/ext/-test-/sanitizers/depend b/ext/-test-/sanitizers/depend new file mode 100644 index 00000000000000..0e6e632803307e --- /dev/null +++ b/ext/-test-/sanitizers/depend @@ -0,0 +1,162 @@ +# AUTOGENERATED DEPENDENCIES START +sanitizers.o: $(RUBY_EXTCONF_H) +sanitizers.o: $(arch_hdrdir)/ruby/config.h +sanitizers.o: $(hdrdir)/ruby/assert.h +sanitizers.o: $(hdrdir)/ruby/backward.h +sanitizers.o: $(hdrdir)/ruby/backward/2/assume.h +sanitizers.o: $(hdrdir)/ruby/backward/2/attributes.h +sanitizers.o: $(hdrdir)/ruby/backward/2/bool.h +sanitizers.o: $(hdrdir)/ruby/backward/2/inttypes.h +sanitizers.o: $(hdrdir)/ruby/backward/2/limits.h +sanitizers.o: $(hdrdir)/ruby/backward/2/long_long.h +sanitizers.o: $(hdrdir)/ruby/backward/2/stdalign.h +sanitizers.o: $(hdrdir)/ruby/backward/2/stdarg.h +sanitizers.o: $(hdrdir)/ruby/defines.h +sanitizers.o: $(hdrdir)/ruby/intern.h +sanitizers.o: $(hdrdir)/ruby/internal/abi.h +sanitizers.o: $(hdrdir)/ruby/internal/anyargs.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/char.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/double.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/int.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/long.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/short.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +sanitizers.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +sanitizers.o: $(hdrdir)/ruby/internal/assume.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/artificial.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/cold.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/const.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/constexpr.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/deprecated.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/error.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/forceinline.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/format.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noalias.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noexcept.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noinline.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/nonnull.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/noreturn.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/pure.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/restrict.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/warning.h +sanitizers.o: $(hdrdir)/ruby/internal/attr/weakref.h +sanitizers.o: $(hdrdir)/ruby/internal/cast.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +sanitizers.o: $(hdrdir)/ruby/internal/compiler_since.h +sanitizers.o: $(hdrdir)/ruby/internal/config.h +sanitizers.o: $(hdrdir)/ruby/internal/constant_p.h +sanitizers.o: $(hdrdir)/ruby/internal/core.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rarray.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rbasic.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rbignum.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rclass.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rdata.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rfile.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rhash.h +sanitizers.o: $(hdrdir)/ruby/internal/core/robject.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rregexp.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rstring.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rstruct.h +sanitizers.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +sanitizers.o: $(hdrdir)/ruby/internal/ctype.h +sanitizers.o: $(hdrdir)/ruby/internal/dllexport.h +sanitizers.o: $(hdrdir)/ruby/internal/dosish.h +sanitizers.o: $(hdrdir)/ruby/internal/error.h +sanitizers.o: $(hdrdir)/ruby/internal/eval.h +sanitizers.o: $(hdrdir)/ruby/internal/event.h +sanitizers.o: $(hdrdir)/ruby/internal/fl_type.h +sanitizers.o: $(hdrdir)/ruby/internal/gc.h +sanitizers.o: $(hdrdir)/ruby/internal/glob.h +sanitizers.o: $(hdrdir)/ruby/internal/globals.h +sanitizers.o: $(hdrdir)/ruby/internal/has/attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/builtin.h +sanitizers.o: $(hdrdir)/ruby/internal/has/c_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +sanitizers.o: $(hdrdir)/ruby/internal/has/extension.h +sanitizers.o: $(hdrdir)/ruby/internal/has/feature.h +sanitizers.o: $(hdrdir)/ruby/internal/has/warning.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/array.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/bignum.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/class.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/compar.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/complex.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/cont.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/dir.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/enum.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/enumerator.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/error.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/eval.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/file.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/hash.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/io.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/load.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/marshal.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/numeric.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/object.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/parse.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/proc.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/process.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/random.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/range.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/rational.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/re.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/ruby.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/select.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/set.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/signal.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/sprintf.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/string.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/struct.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/thread.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/time.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/variable.h +sanitizers.o: $(hdrdir)/ruby/internal/intern/vm.h +sanitizers.o: $(hdrdir)/ruby/internal/interpreter.h +sanitizers.o: $(hdrdir)/ruby/internal/iterator.h +sanitizers.o: $(hdrdir)/ruby/internal/memory.h +sanitizers.o: $(hdrdir)/ruby/internal/method.h +sanitizers.o: $(hdrdir)/ruby/internal/module.h +sanitizers.o: $(hdrdir)/ruby/internal/newobj.h +sanitizers.o: $(hdrdir)/ruby/internal/scan_args.h +sanitizers.o: $(hdrdir)/ruby/internal/special_consts.h +sanitizers.o: $(hdrdir)/ruby/internal/static_assert.h +sanitizers.o: $(hdrdir)/ruby/internal/stdalign.h +sanitizers.o: $(hdrdir)/ruby/internal/stdbool.h +sanitizers.o: $(hdrdir)/ruby/internal/stdckdint.h +sanitizers.o: $(hdrdir)/ruby/internal/symbol.h +sanitizers.o: $(hdrdir)/ruby/internal/value.h +sanitizers.o: $(hdrdir)/ruby/internal/value_type.h +sanitizers.o: $(hdrdir)/ruby/internal/variable.h +sanitizers.o: $(hdrdir)/ruby/internal/warning_push.h +sanitizers.o: $(hdrdir)/ruby/internal/xmalloc.h +sanitizers.o: $(hdrdir)/ruby/missing.h +sanitizers.o: $(hdrdir)/ruby/ruby.h +sanitizers.o: $(hdrdir)/ruby/st.h +sanitizers.o: $(hdrdir)/ruby/subst.h +sanitizers.o: sanitizers.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/sanitizers/extconf.rb b/ext/-test-/sanitizers/extconf.rb new file mode 100644 index 00000000000000..c94a96de6c5218 --- /dev/null +++ b/ext/-test-/sanitizers/extconf.rb @@ -0,0 +1,2 @@ +require 'mkmf' +create_makefile('-test-/sanitizers') diff --git a/ext/-test-/asan/asan.c b/ext/-test-/sanitizers/sanitizers.c similarity index 84% rename from ext/-test-/asan/asan.c rename to ext/-test-/sanitizers/sanitizers.c index 45b6253fdac8c8..2c55e7eb51e4e6 100644 --- a/ext/-test-/asan/asan.c +++ b/ext/-test-/sanitizers/sanitizers.c @@ -15,10 +15,10 @@ asan_enabled_p(VALUE self) } void -Init_asan(void) +Init_sanitizers(void) { VALUE m = rb_define_module("Test"); - VALUE c = rb_define_class_under(m, "ASAN", rb_cObject); + VALUE c = rb_define_class_under(m, "Sanitizers", rb_cObject); rb_define_singleton_method(c, "enabled?", asan_enabled_p, 0); } diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index cc4eb327d41106..5e2aefc7e0ef6a 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -74,7 +74,7 @@ def message msg = nil, ending = nil, &default module CoreAssertions require_relative 'envutil' require 'pp' - require '-test-/asan' + require '-test-/sanitizers' nil.pretty_inspect @@ -159,7 +159,7 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # ASAN has the same problem - its shadow memory greatly increases memory usage # (plus asan has better ways to detect memory leaks than this assertion) - pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::ASAN.enabled? + pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::Sanitizers.enabled? require_relative 'memory_status' raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) From fc7fd63880dd8e22ac253330d1d07b8896fb7f39 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 7 Oct 2025 21:01:41 -0400 Subject: [PATCH 0272/2435] Rename Test::Sanitizers.enabled? to Test::Sanitizers.asan_enabled? --- ext/-test-/sanitizers/sanitizers.c | 2 +- tool/lib/core_assertions.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/-test-/sanitizers/sanitizers.c b/ext/-test-/sanitizers/sanitizers.c index 2c55e7eb51e4e6..bd00f0be774f11 100644 --- a/ext/-test-/sanitizers/sanitizers.c +++ b/ext/-test-/sanitizers/sanitizers.c @@ -19,6 +19,6 @@ Init_sanitizers(void) { VALUE m = rb_define_module("Test"); VALUE c = rb_define_class_under(m, "Sanitizers", rb_cObject); - rb_define_singleton_method(c, "enabled?", asan_enabled_p, 0); + rb_define_singleton_method(c, "asan_enabled?", asan_enabled_p, 0); } diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 5e2aefc7e0ef6a..fc7b9402843303 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -159,7 +159,7 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # ASAN has the same problem - its shadow memory greatly increases memory usage # (plus asan has better ways to detect memory leaks than this assertion) - pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::Sanitizers.enabled? + pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::Sanitizers.asan_enabled? require_relative 'memory_status' raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) From c80ff8da25333bac6beaff7b0cffd07b023f78e9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 7 Oct 2025 21:01:59 -0400 Subject: [PATCH 0273/2435] Add Test::Sanitizers.lsan_enabled? --- ext/-test-/sanitizers/sanitizers.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/-test-/sanitizers/sanitizers.c b/ext/-test-/sanitizers/sanitizers.c index bd00f0be774f11..97a85b26ef3f83 100644 --- a/ext/-test-/sanitizers/sanitizers.c +++ b/ext/-test-/sanitizers/sanitizers.c @@ -14,11 +14,23 @@ asan_enabled_p(VALUE self) #endif } +static VALUE +lsan_enabled_p(VALUE self) +{ +#if defined(__has_feature) + /* clang uses __has_feature for determining LSAN */ + return __has_feature(leak_sanitizer) ? Qtrue : Qfalse; +#else + return Qfalse; +#endif +} + void Init_sanitizers(void) { VALUE m = rb_define_module("Test"); VALUE c = rb_define_class_under(m, "Sanitizers", rb_cObject); rb_define_singleton_method(c, "asan_enabled?", asan_enabled_p, 0); + rb_define_singleton_method(c, "lsan_enabled?", lsan_enabled_p, 0); } From 42bbe9a075807fe578fd2680a7c64b80b7c2e24f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 7 Oct 2025 21:04:13 -0400 Subject: [PATCH 0274/2435] Skip TestProcess#test_rlimit_nofile on LSAN --- test/ruby/test_process.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 7cea0b3a7027e7..3bbff73df8c070 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -58,6 +58,8 @@ def rlimit_exist? def test_rlimit_nofile return unless rlimit_exist? + omit "LSAN needs to open proc file" if Test::Sanitizers.lsan_enabled? + with_tmpchdir { File.write 's', <<-"End" # Too small RLIMIT_NOFILE, such as zero, causes problems. From 282b0e3a8342de5fb5466f7b85ead83a9f5ac9f6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Oct 2025 11:30:41 +0900 Subject: [PATCH 0275/2435] [rubygems/rubygems] Replaced Bundler.feature_flag.plugins? to Bundler.settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/ced8ef3a12 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/cli.rb | 4 ++-- lib/bundler/cli/install.rb | 2 +- lib/bundler/cli/update.rb | 2 +- lib/bundler/feature_flag.rb | 1 - lib/bundler/inline.rb | 2 +- lib/bundler/plugin.rb | 2 +- lib/bundler/settings.rb | 1 + 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index c577f4981470a4..6ee6bc76aec8ff 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -143,7 +143,7 @@ def help(cli = nil) end def self.handle_no_command_error(command, has_namespace = $thor_runner) - if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) + if Bundler.settings[:plugins] && Bundler::Plugin.command?(command) return Bundler::Plugin.exec_command(command, ARGV[1..-1]) end @@ -623,7 +623,7 @@ def pristine(*gems) end end - if Bundler.feature_flag.plugins? + if Bundler.settings[:plugins] require_relative "cli/plugin" desc "plugin", "Manage the bundler plugins" subcommand "plugin", Plugin diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 57c28379e5f57d..20e22155de8ee9 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -38,7 +38,7 @@ def run Bundler::Fetcher.disable_endpoint = options["full-index"] - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] # For install we want to enable strict validation # (rather than some optimizations we perform at app runtime). diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 13f576cfa75b15..cf0ceac0bd19c5 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -15,7 +15,7 @@ def run Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] sources = Array(options[:source]) groups = Array(options[:group]).map(&:to_sym) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index b2b134889573c1..2109f50d785ef0 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -28,7 +28,6 @@ def self.settings_method(name, key, &default) (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } settings_flag(:global_gem_cache) { bundler_5_mode? } - settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } settings_flag(:update_requires_all_flag) { bundler_5_mode? } settings_option(:default_cli_command) { bundler_4_mode? ? :cli_help : :install } diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index f2f5b22cd381a8..4e4b51e7a5dfb4 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -51,7 +51,7 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" - Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? + Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins] builder = Bundler::Dsl.new builder.instance_eval(&gemfile) diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 44129cc0ff5089..fd6da6cf6dec71 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -220,7 +220,7 @@ def add_hook(event, &block) # # @param [String] event def hook(event, *args, &arg_blk) - return unless Bundler.feature_flag.plugins? + return unless Bundler.settings[:plugins] unless Events.defined_event?(event) raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 7923ba51c36630..f8065ad277b252 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -83,6 +83,7 @@ class Settings "BUNDLE_VERSION" => "lockfile", "BUNDLE_LOCKFILE_CHECKSUMS" => true, "BUNDLE_CACHE_ALL" => true, + "BUNDLE_PLUGINS" => true, }.freeze def initialize(root = nil) From c37d4068834e7231e8a17046aa7010b77449b6ad Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 21:17:14 -0700 Subject: [PATCH 0276/2435] ubuntu.yml: Add matrix.os in the notification label At the moment, nothing in the notification tells you whether it was ibm jobs or not. `matrix.os` seems like the easiest way to include that information, so I added it in the label. --- .github/workflows/ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index af849720575652..86e951bcb77e46 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -173,7 +173,7 @@ jobs: - uses: ./.github/actions/slack with: - label: ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }} + label: ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }}${{ matrix.os }} SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot if: ${{ failure() }} From a5def9999c4caa19a54c143ff3be772a40d0448e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 9 Oct 2025 21:24:26 -0700 Subject: [PATCH 0277/2435] sync_default_gems.yml: Experiment with fetch-depth: 1 At the moment, we're not sure which gem relies on past renames. So we try this, and revert it if it turns out to be necessary. Given that it slows down every single sync, however, I'm thinking of making tool/sync_default_gems.rb responsible for implementing all necessary renames on cherry-picks using filter-branch (or any modification on commits before pushing them). --- .github/workflows/sync_default_gems.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 3912f567e388cc..3a811be18a3187 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -30,12 +30,8 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 name: Check out ruby/ruby with: - fetch-depth: 999999 # Fetch all history to follow past renames. Not using 0 to avoid fetching tags/branches. token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - name: Increase rename limit - run: git config merge.renameLimit 999999 - - name: Run tool/sync_default_gems.rb id: sync run: | From 1399134f6a504cf647c02a00c67b6a04ad7c76a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:21:52 +0200 Subject: [PATCH 0278/2435] [rubygems/rubygems] Consolidate removal of `Bundler::SpecSet#-` and `Bundler::SpecSet#<<` https://github.com/rubygems/rubygems/commit/aee50b31db --- lib/bundler/spec_set.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 411393ce1bdd7c..4ae03171dc5813 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -179,9 +179,7 @@ def insecurely_materialized_specs end def -(other) - SharedHelpers.major_deprecation 2, "SpecSet#- has been removed with no replacement" - - SpecSet.new(to_a - other.to_a) + SharedHelpers.feature_removed! "SpecSet#- has been removed with no replacement" end def find_by_name_and_platform(name, platform) @@ -212,9 +210,7 @@ def what_required(spec) end def <<(spec) - SharedHelpers.major_deprecation 2, "SpecSet#<< has been removed with no replacement" - - @specs << spec + SharedHelpers.feature_removed! "SpecSet#<< has been removed with no replacement" end def length From 77e32902db6fc6ef34c97bb8c0bc495267e636e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:21:29 +0200 Subject: [PATCH 0279/2435] [rubygems/rubygems] Consolidate removal of `Bundler.rubygems.all_specs` https://github.com/rubygems/rubygems/commit/73779331ce --- lib/bundler/rubygems_integration.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index 31f255d997a833..d8f95cffb8fcd6 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -416,11 +416,7 @@ def path_separator end def all_specs - SharedHelpers.major_deprecation 2, "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" - - Gem::Specification.stubs.map do |stub| - StubSpecification.from_stub(stub) - end + SharedHelpers.feature_removed! "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" end def installed_specs From b38846db18c34213077a9efaa7b28683b42f12a3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Oct 2025 14:10:45 +0900 Subject: [PATCH 0280/2435] [rubygems/rubygems] Make global_gem_cache flag to settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/bfe15a4712 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/feature_flag.rb | 1 - lib/bundler/settings.rb | 1 + lib/bundler/source.rb | 2 +- lib/bundler/source/git.rb | 2 +- lib/bundler/source/rubygems.rb | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 2109f50d785ef0..c3b8d790fbbce9 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -27,7 +27,6 @@ def self.settings_method(name, key, &default) (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - settings_flag(:global_gem_cache) { bundler_5_mode? } settings_flag(:update_requires_all_flag) { bundler_5_mode? } settings_option(:default_cli_command) { bundler_4_mode? ? :cli_help : :install } diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index f8065ad277b252..64f0c9900eb285 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -84,6 +84,7 @@ class Settings "BUNDLE_LOCKFILE_CHECKSUMS" => true, "BUNDLE_CACHE_ALL" => true, "BUNDLE_PLUGINS" => true, + "BUNDLE_GLOBAL_GEM_CACHE" => false, }.freeze def initialize(root = nil) diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 232873503b3933..2b90a0eff1bfbb 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -79,7 +79,7 @@ def path? end def extension_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless source_slug = extension_cache_slug(spec) Bundler.user_cache.join( "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope, diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index bb12ff52f514fb..bb669ebba39d21 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -238,7 +238,7 @@ def load_spec_files # across different projects, this cache will be shared. # When using local git repos, this is set to the local repo. def cache_path - @cache_path ||= if Bundler.feature_flag.global_gem_cache? + @cache_path ||= if Bundler.settings[:global_gem_cache] Bundler.user_cache else Bundler.bundle_path.join("cache", "bundler") diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index fdc3a77b248072..2631c860a010ba 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -493,7 +493,7 @@ def download_gem(spec, download_cache_path, previous_spec = nil) # @return [Pathname] The global cache path. # def download_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless remote = spec.remote return unless cache_slug = remote.cache_slug From aac6f0681536c8ca58638714c2cc8d7e35aa6a37 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Oct 2025 15:30:31 +0900 Subject: [PATCH 0281/2435] [rubygems/rubygems] Make default_cli_command flag to settings https://github.com/rubygems/rubygems/commit/31d67ecc05 --- lib/bundler/cli.rb | 2 +- lib/bundler/feature_flag.rb | 2 -- lib/bundler/settings.rb | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 6ee6bc76aec8ff..62225a352d71fa 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -107,7 +107,7 @@ def cli_help shell.say self.class.send(:class_options_help, shell) end - default_task(Bundler.feature_flag.default_cli_command) + default_task(Bundler.settings[:default_cli_command]) class_option "no-color", type: :boolean, desc: "Disable colorization in output" class_option "retry", type: :numeric, aliases: "-r", banner: "NUM", diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index c3b8d790fbbce9..593d704e2b3e0e 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -29,8 +29,6 @@ def self.settings_method(name, key, &default) settings_flag(:update_requires_all_flag) { bundler_5_mode? } - settings_option(:default_cli_command) { bundler_4_mode? ? :cli_help : :install } - def removed_major?(target_major_version) @major_version > target_major_version end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 64f0c9900eb285..b6bc6c5a8a5c70 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -83,6 +83,7 @@ class Settings "BUNDLE_VERSION" => "lockfile", "BUNDLE_LOCKFILE_CHECKSUMS" => true, "BUNDLE_CACHE_ALL" => true, + "BUNDLE_DEFAULT_CLI_COMMAND" => "cli_help", "BUNDLE_PLUGINS" => true, "BUNDLE_GLOBAL_GEM_CACHE" => false, }.freeze From 4a285dd91aaacc7da44ce63191f41a1a1c287828 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Oct 2025 15:32:29 +0900 Subject: [PATCH 0282/2435] [rubygems/rubygems] Added extra examples for cli_help default command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/b2472e7b82 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- spec/bundler/bundler/cli_spec.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 07f589bd5de359..b3a97e72ceb2d8 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,7 +87,7 @@ def out_with_macos_man_workaround end context "with no arguments" do - it "prints a concise help message", bundler: "4" do + it "prints a concise help message by default" do bundle "" expect(err).to be_empty expect(out).to include("Bundler version #{Bundler::VERSION}"). @@ -96,6 +96,23 @@ def out_with_macos_man_workaround and include("\n\n Utilities:\n"). and include("\n\nOptions:\n") end + + it "prints a concise help message when default_cli_command set to cli_help" do + bundle "config set default_cli_command cli_help" + bundle "" + expect(err).to be_empty + expect(out).to include("Bundler version #{Bundler::VERSION}"). + and include("\n\nBundler commands:\n\n"). + and include("\n\n Primary commands:\n"). + and include("\n\n Utilities:\n"). + and include("\n\nOptions:\n") + end + + it "runs bundle install when default_cli_command set to install" do + bundle "config set default_cli_command install" + bundle "", raise_on_error: false + expect(err).to include("Could not locate Gemfile") + end end context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do From 4bf14758336fd51c3c45a714c944b7d69ec9bed3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Oct 2025 15:47:10 +0900 Subject: [PATCH 0283/2435] [rubygems/rubygems] Make update_requires_all_flag to settings https://github.com/rubygems/rubygems/commit/631a55be91 --- lib/bundler/cli/update.rb | 2 +- lib/bundler/feature_flag.rb | 2 -- lib/bundler/settings.rb | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index cf0ceac0bd19c5..4b4ba3c64712be 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -23,7 +23,7 @@ def run full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler if full_update && !options[:all] - if Bundler.feature_flag.update_requires_all_flag? + if Bundler.settings[:update_requires_all_flag] raise InvalidOption, "To update everything, pass the `--all` flag." end SharedHelpers.major_deprecation 4, "Pass --all to `bundle update` to update everything" diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 593d704e2b3e0e..8ec62fc1c9e638 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -27,8 +27,6 @@ def self.settings_method(name, key, &default) (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - settings_flag(:update_requires_all_flag) { bundler_5_mode? } - def removed_major?(target_major_version) @major_version > target_major_version end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index b6bc6c5a8a5c70..81f1857eec7648 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -86,6 +86,7 @@ class Settings "BUNDLE_DEFAULT_CLI_COMMAND" => "cli_help", "BUNDLE_PLUGINS" => true, "BUNDLE_GLOBAL_GEM_CACHE" => false, + "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false, }.freeze def initialize(root = nil) From 0ba6379acadf00ee0c4c92cb60ae3724acb7e3c7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 11 Oct 2025 01:39:54 +0900 Subject: [PATCH 0284/2435] Update bundled bigdecimal version (#14809) * Update bigdecimal spec * Update bundled bigdecimal to 3.3.1 --- gems/bundled_gems | 2 +- .../library/bigdecimal/BigDecimal_spec.rb | 10 ++++++---- spec/ruby/library/bigdecimal/add_spec.rb | 8 -------- spec/ruby/library/bigdecimal/core_spec.rb | 7 +++++-- spec/ruby/library/bigdecimal/divmod_spec.rb | 19 +++++++++++++------ spec/ruby/library/bigdecimal/mult_spec.rb | 8 -------- .../ruby/library/bigdecimal/remainder_spec.rb | 8 +++++--- spec/ruby/library/bigdecimal/shared/modulo.rb | 14 ++++++++++---- spec/ruby/library/bigdecimal/shared/power.rb | 4 ++-- spec/ruby/library/bigdecimal/sub_spec.rb | 8 -------- 10 files changed, 42 insertions(+), 46 deletions(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index d8fa9642ce1eb8..9f49934794f930 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -25,7 +25,7 @@ racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong base64 0.3.0 https://github.com/ruby/base64 -bigdecimal 3.2.2 https://github.com/ruby/bigdecimal +bigdecimal 3.3.1 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.1.1 https://github.com/ruby/resolv-replace diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb index 45f5ebffc702f6..01772bf9af7d8e 100644 --- a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb +++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb @@ -156,8 +156,10 @@ BigDecimal("-12345.6E-1").should == -reference end - it "raises ArgumentError when Float is used without precision" do - -> { BigDecimal(1.0) }.should raise_error(ArgumentError) + version_is BigDecimal::VERSION, "3.3.0" do + it "allows Float without precision" do + BigDecimal(1.2).should == BigDecimal("1.2") + end end it "returns appropriate BigDecimal zero for signed zero" do @@ -259,8 +261,8 @@ def to_s; "cheese"; end end it "produces the expected result" do - @c.should == BigDecimal("-0.666667e-9") - @c.to_s.should == "-0.666667e-9" + @c.round(15).should == BigDecimal("-0.666667e-9") + @c.round(15).to_s.should == "-0.666667e-9" end it "produces the correct class for other arithmetic operators" do diff --git a/spec/ruby/library/bigdecimal/add_spec.rb b/spec/ruby/library/bigdecimal/add_spec.rb index 542713011d6a93..9cdab7d910844d 100644 --- a/spec/ruby/library/bigdecimal/add_spec.rb +++ b/spec/ruby/library/bigdecimal/add_spec.rb @@ -73,14 +73,6 @@ # BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9") # end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.add(object, 1).should == BigDecimal("0.1E16") - end - end - describe "with Rational" do it "produces a BigDecimal" do (@three + Rational(500, 2)).should == BigDecimal("0.253e3") diff --git a/spec/ruby/library/bigdecimal/core_spec.rb b/spec/ruby/library/bigdecimal/core_spec.rb index acee4dcf564ea6..5097d708656b73 100644 --- a/spec/ruby/library/bigdecimal/core_spec.rb +++ b/spec/ruby/library/bigdecimal/core_spec.rb @@ -20,9 +20,12 @@ describe "BigDecimal#log" do it "handles high-precision Rational arguments" do - result = BigDecimal('0.22314354220170971436137296411949880462556361100856391620766259404746040597133837784E0') + # log(BigDecimal(r, 50), 50) + result1 = BigDecimal('0.22314354220170971436137296411949880462556361100856e0') + # log(BigDecimal(r, 1000), 50) + result2 = BigDecimal('0.22314354220170971436137296411949880462556361100853e0') r = Rational(1_234_567_890, 987_654_321) - BigMath.log(r, 50).should == result + [result1, result2].should include(BigMath.log(r, 50).mult(1, 50)) end end diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb index 294f01cba01484..c519783d145a00 100644 --- a/spec/ruby/library/bigdecimal/divmod_spec.rb +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -154,12 +154,19 @@ class BigDecimal end end - it "returns an array of zero and the dividend if the divisor is Infinity" do - @regular_vals.each do |val| - array = val.divmod(@infinity) - array.length.should == 2 - array[0].should == @zero - array[1].should == val + version_is BigDecimal::VERSION, "3.3.0" do + it "returns an array of zero and the dividend or minus one and Infinity if the divisor is Infinity" do + @regular_vals.each do |val| + array = val.divmod(@infinity) + array.length.should == 2 + if val >= 0 + array[0].should == @zero + array[1].should == val + else + array[0].should == @one_minus + array[1].should == @infinity + end + end end end diff --git a/spec/ruby/library/bigdecimal/mult_spec.rb b/spec/ruby/library/bigdecimal/mult_spec.rb index b7f8044b0b2202..2353df9cb8e9a9 100644 --- a/spec/ruby/library/bigdecimal/mult_spec.rb +++ b/spec/ruby/library/bigdecimal/mult_spec.rb @@ -21,12 +21,4 @@ @e.mult(@one, 1).should be_close(@one, @tolerance) @e3_minus.mult(@one, 1).should be_close(0, @tolerance2) end - - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) - @e3_minus.mult(object, 1).should == BigDecimal("9") - end - end end diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb index bac5f37ba968c1..0eb06f7ef1d402 100644 --- a/spec/ruby/library/bigdecimal/remainder_spec.rb +++ b/spec/ruby/library/bigdecimal/remainder_spec.rb @@ -37,9 +37,11 @@ @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate end - it "returns NaN used with zero" do - @mixed.remainder(@zero).should.nan? - @zero.remainder(@zero).should.nan? + version_is BigDecimal::VERSION, "3.3.0" do + it "raises ZeroDivisionError used with zero" do + -> { @mixed.remainder(@zero) }.should raise_error(ZeroDivisionError) + -> { @zero.remainder(@zero) }.should raise_error(ZeroDivisionError) + end end it "returns zero if used on zero" do diff --git a/spec/ruby/library/bigdecimal/shared/modulo.rb b/spec/ruby/library/bigdecimal/shared/modulo.rb index aa5c5a640b2cc2..eeb030fd23c05c 100644 --- a/spec/ruby/library/bigdecimal/shared/modulo.rb +++ b/spec/ruby/library/bigdecimal/shared/modulo.rb @@ -101,10 +101,16 @@ @infinity_minus.send(@method, @infinity).should.nan? end - it "returns the dividend if the divisor is Infinity" do - @one.send(@method, @infinity).should == @one - @one.send(@method, @infinity_minus).should == @one - @frac_2.send(@method, @infinity_minus).should == @frac_2 + version_is BigDecimal::VERSION, "3.3.0" do + it "returns the dividend if the divisor is Infinity and signs are same" do + @one.send(@method, @infinity).should == @one + (-@frac_2).send(@method, @infinity_minus).should == -@frac_2 + end + + it "returns the divisor if the divisor is Infinity and signs are different" do + (-@one).send(@method, @infinity).should == @infinity + @frac_2.send(@method, @infinity_minus).should == @infinity_minus + end end it "raises TypeError if the argument cannot be coerced to BigDecimal" do diff --git a/spec/ruby/library/bigdecimal/shared/power.rb b/spec/ruby/library/bigdecimal/shared/power.rb index 568a08589b494f..6dafb638e27b22 100644 --- a/spec/ruby/library/bigdecimal/shared/power.rb +++ b/spec/ruby/library/bigdecimal/shared/power.rb @@ -10,8 +10,8 @@ e = BigDecimal("1.00000000000000000000123456789") one = BigDecimal("1") ten = BigDecimal("10") - # The tolerance is dependent upon the size of BASE_FIG - tolerance = BigDecimal("1E-70") + # Accuracy is at least ndigits(== 30) + DOUBLE_FIG(== 16) + tolerance = BigDecimal("1E-46") ten_powers = BigDecimal("1E10000") pi = BigDecimal("3.14159265358979") e3_minus.send(@method, 2).should == e3_minus_power_2 diff --git a/spec/ruby/library/bigdecimal/sub_spec.rb b/spec/ruby/library/bigdecimal/sub_spec.rb index bddfec2186d6a4..3b62a0c794b510 100644 --- a/spec/ruby/library/bigdecimal/sub_spec.rb +++ b/spec/ruby/library/bigdecimal/sub_spec.rb @@ -35,14 +35,6 @@ @frac_1.sub(@frac_1, 1000000).should == @zero end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.sub(object, 1).should == BigDecimal("-0.9E15") - end - end - describe "with Rational" do it "produces a BigDecimal" do (@three - Rational(500, 2)).should == BigDecimal('-0.247e3') From f8c90e45163ce65d20a37789a020b436624c450b Mon Sep 17 00:00:00 2001 From: git Date: Fri, 10 Oct 2025 16:41:29 +0000 Subject: [PATCH 0285/2435] [DOC] Update bundled gems list at 0ba6379acadf00ee0c4c92cb60ae37 --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 117aae5736bcb0..17cb9ba982d613 100644 --- a/NEWS.md +++ b/NEWS.md @@ -216,7 +216,7 @@ The following bundled gems are updated. * rbs 3.9.5 * debug 1.11.0 * base64 0.3.0 -* bigdecimal 3.2.2 +* bigdecimal 3.3.1 * drb 2.2.3 * syslog 0.3.0 * csv 3.3.5 From 17a5a5e2ef2b6253a68174a846972187dde6d547 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 9 Oct 2025 18:23:46 -0700 Subject: [PATCH 0286/2435] Take a full VM barrier in gc_rest This isn't (yet?) safe to do because it concurrently modifies GC structures and dfree functions are not necessarily safe to do without stopping all Ractors. If it was safe to do this we should also do it for gc_enter_event_continue. I do think sweeping could be done concurrently with the mutator and in parallel, but that requires more work first. --- gc/default/default.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index c23adae0627988..af386a9793ae1d 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6633,8 +6633,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ switch (event) { case gc_enter_event_rest: - if (!is_marking(objspace)) break; - // fall through case gc_enter_event_start: case gc_enter_event_continue: // stop other ractors From 50cd34c4e837466ce041adf114ea474e6627bb91 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 10 Oct 2025 13:22:15 -0400 Subject: [PATCH 0287/2435] ZJIT: Add Insn:: ArrayArefFixnum to accelerate Array#[] (#14717) * ZJIT: Add Insn:: ArrayArefFixnum to accelerate Array#[] * ZJIT: Use result from GuardType in ArrayArefFixnum * ZJIT: Unbox index for aref_fixnum * ZJIT: Change condition and add ArrayArefFixnum test * ZJIT: Fix ArrayArefFixnum display for InsnPrinter * ZJIT: Change insta test --- test/ruby/test_zjit.rb | 8 ++++++ zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 11 ++++++++ zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 51 ++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index c6dbc01dc2e129..faf717096a4c77 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1153,6 +1153,14 @@ def test = [1,2,3] } end + def test_array_fixnum_aref + assert_compiles '3', %q{ + def test(x) = [1,2,3][x] + test(2) + test(2) + }, call_threshold: 2, insns: [:opt_aref] + end + def test_new_range_inclusive assert_compiles '1..5', %q{ def test(a, b) = a..b diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 64b235b838baff..b40986c0f6e154 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -125,6 +125,7 @@ fn main() { .allowlist_function("rb_ary_unshift_m") .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") + .allowlist_function("rb_ary_entry") .allowlist_function("rb_class_attached_object") .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4b9331e05b0892..35791bc0d7ae2f 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -355,6 +355,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), + Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), &Insn::ObjectAllocClass { class, state } => gen_object_alloc_class(asm, class, &function.frame_state(state)), Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)), @@ -1241,6 +1242,16 @@ fn gen_new_array( new_array } +/// Compile array access (array[index]) +fn gen_aref_fixnum( + asm: &mut Assembler, + array: Opnd, + index: Opnd, +) -> lir::Opnd { + let unboxed_idx = asm.rshift(index, Opnd::UImm(1)); + asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) +} + /// Compile a new hash instruction fn gen_new_hash( jit: &mut JITState, diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index ab442841ff267a..ea1bf68acc4173 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -786,6 +786,7 @@ unsafe extern "C" { pub fn rb_ary_resurrect(ary: VALUE) -> VALUE; pub fn rb_ary_cat(ary: VALUE, train: *const VALUE, len: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_push(ary: VALUE, elem: VALUE) -> VALUE; + pub fn rb_ary_entry(ary: VALUE, off: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_ary_concat(lhs: VALUE, rhs: VALUE) -> VALUE; pub fn rb_hash_new() -> VALUE; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2fe8eb79700349..1ab603283230e5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -577,6 +577,7 @@ pub enum Insn { ArrayExtend { left: InsnId, right: InsnId, state: InsnId }, /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, + ArrayArefFixnum { array: InsnId, index: InsnId }, HashDup { val: InsnId, state: InsnId }, @@ -888,6 +889,10 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } + Insn::ArrayArefFixnum { array, index, .. } => { + write!(f, "ArrayArefFixnum {array}, {index}")?; + Ok(()) + } Insn::NewHash { elements, .. } => { write!(f, "NewHash")?; let mut prefix = " "; @@ -1579,6 +1584,7 @@ impl Function { &NewHash { ref elements, state } => NewHash { elements: find_vec!(elements), state: find!(state) }, &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, + &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, @@ -1664,6 +1670,7 @@ impl Function { Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, + Insn::ArrayArefFixnum { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, Insn::HashDup { .. } => types::HashExact, Insn::NewRange { .. } => types::RangeExact, @@ -1969,6 +1976,16 @@ impl Function { } } } + if self.type_of(idx_val).is_subtype(types::Fixnum) { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); + let fixnum_idx = self.push_insn(block, Insn::GuardType { val: idx_val, guard_type: types::Fixnum, state }); + let result = self.push_insn(block, Insn::ArrayArefFixnum { + array: self_val, + index: fixnum_idx, + }); + self.make_equal_to(orig_insn_id, result); + return; + } } self.push_insn_id(block, orig_insn_id); } @@ -2687,6 +2704,10 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + &Insn::ArrayArefFixnum { array, index } => { + worklist.push_back(array); + worklist.push_back(index); + } &Insn::Send { recv, ref args, state, .. } | &Insn::SendForward { recv, ref args, state, .. } | &Insn::SendWithoutBlock { recv, ref args, state, .. } @@ -12564,4 +12585,34 @@ mod opt_tests { Return v23 "); } + + #[test] + fn test_array_aref_fixnum() { + eval(" + def test + arr = [1, 2, 3] + arr[0] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) + v30:BasicObject = ArrayArefFixnum v15, v18 + CheckInterrupts + Return v30 + "); + } } From 2de13f4d094a92099de92b91cde8d5a7da8c38cc Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 10 Oct 2025 10:24:14 -0700 Subject: [PATCH 0288/2435] ZJIT: Remove an unneeded ? https://github.com/ruby/ruby/pull/14717 --- zjit/src/hir.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1ab603283230e5..8837457afaadee 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -890,8 +890,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) } Insn::ArrayArefFixnum { array, index, .. } => { - write!(f, "ArrayArefFixnum {array}, {index}")?; - Ok(()) + write!(f, "ArrayArefFixnum {array}, {index}") } Insn::NewHash { elements, .. } => { write!(f, "NewHash")?; From 0a6cd03b3d91f52c47242d2b45f5ac086a3c1fd8 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 9 Oct 2025 17:09:19 -0700 Subject: [PATCH 0289/2435] Add ASSERT_vm_locking_with_barrier Previously we just had a comment stating that the code required a barrier. Turns out it's not too difficult to properly assert that. Co-authored-by: Luke Gruber --- concurrent_set.c | 6 ++---- string.c | 3 +-- vm_sync.c | 14 ++++++++++++++ vm_sync.h | 3 +++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/concurrent_set.c b/concurrent_set.c index 19b4b6c373559f..87279384eb8893 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -352,8 +352,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) VALUE rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) { - // Assume locking and barrier (which there is no assert for). - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); @@ -391,8 +390,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) void rb_concurrent_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(); + ASSERT_vm_locking_with_barrier(); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); diff --git a/string.c b/string.c index 331f48313600f4..0ee0ab744856d7 100644 --- a/string.c +++ b/string.c @@ -600,8 +600,7 @@ rb_obj_is_fstring_table(VALUE obj) void rb_gc_free_fstring(VALUE obj) { - // Assume locking and barrier (which there is no assert for) - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); rb_concurrent_set_delete_by_identity(fstring_table_obj, obj); diff --git a/vm_sync.c b/vm_sync.c index ba311a00e97838..6d93720701bad9 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -25,6 +25,20 @@ RUBY_ASSERT_vm_locking(void) } } +void +RUBY_ASSERT_vm_locking_with_barrier(void) +{ + if (rb_multi_ractor_p()) { + rb_vm_t *vm = GET_VM(); + VM_ASSERT(vm_locked(vm)); + + if (vm->ractor.cnt > 1) { + /* Written to only when holding both ractor.sync and ractor.sched lock */ + VM_ASSERT(vm->ractor.sched.barrier_waiting); + } + } +} + void RUBY_ASSERT_vm_unlocking(void) { diff --git a/vm_sync.h b/vm_sync.h index 457be2c6b888aa..15dfff4d0b9fe1 100644 --- a/vm_sync.h +++ b/vm_sync.h @@ -142,11 +142,14 @@ rb_vm_lock_leave_cr(struct rb_ractor_struct *cr, unsigned int *levp, const char #if RUBY_DEBUG > 0 void RUBY_ASSERT_vm_locking(void); +void RUBY_ASSERT_vm_locking_with_barrier(void); void RUBY_ASSERT_vm_unlocking(void); #define ASSERT_vm_locking() RUBY_ASSERT_vm_locking() +#define ASSERT_vm_locking_with_barrier() RUBY_ASSERT_vm_locking_with_barrier() #define ASSERT_vm_unlocking() RUBY_ASSERT_vm_unlocking() #else #define ASSERT_vm_locking() +#define ASSERT_vm_locking_with_barrier() #define ASSERT_vm_unlocking() #endif From 0090311db21b4c0e67a00a381156d7a8a1f6a262 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 10 Oct 2025 15:39:05 -0500 Subject: [PATCH 0290/2435] [DOC] String slices doc (#14740) --- doc/string.rb | 140 --------------------------------- doc/string/aref.rdoc | 98 +++++++++++++++++++++++ doc/string/aset.rdoc | 183 +++++++++++++++++++++++++++++++++++++++++++ string.c | 50 ++++-------- 4 files changed, 298 insertions(+), 173 deletions(-) create mode 100644 doc/string/aref.rdoc create mode 100644 doc/string/aset.rdoc diff --git a/doc/string.rb b/doc/string.rb index a5d143cf369b45..3f3461573f5ca3 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -159,146 +159,6 @@ # - #rstrip, #rstrip!: Strip trailing whitespace. # - #strip, #strip!: Strip leading and trailing whitespace. # -# == +String+ Slices -# -# A _slice_ of a string is a substring selected by certain criteria. -# -# These instance methods utilize slicing: -# -# - String#[] (aliased as String#slice): Returns a slice copied from +self+. -# - String#[]=: Mutates +self+ with the slice replaced. -# - String#slice!: Mutates +self+ with the slice removed and returns the removed slice. -# -# Each of the above methods takes arguments that determine the slice -# to be copied or replaced. -# -# The arguments have several forms. -# For a string +string+, the forms are: -# -# - string[index] -# - string[start, length] -# - string[range] -# - string[regexp, capture = 0] -# - string[substring] -# -# string[index] -# -# When a non-negative integer argument +index+ is given, -# the slice is the 1-character substring found in +self+ at character offset +index+: -# -# 'bar'[0] # => "b" -# 'bar'[2] # => "r" -# 'bar'[20] # => nil -# 'тест'[2] # => "с" -# 'こんにちは'[4] # => "は" -# -# When a negative integer +index+ is given, -# the slice begins at the offset given by counting backward from the end of +self+: -# -# 'bar'[-3] # => "b" -# 'bar'[-1] # => "r" -# 'bar'[-20] # => nil -# -# string[start, length] -# -# When non-negative integer arguments +start+ and +length+ are given, -# the slice begins at character offset +start+, if it exists, -# and continues for +length+ characters, if available: -# -# 'foo'[0, 2] # => "fo" -# 'тест'[1, 2] # => "ес" -# 'こんにちは'[2, 2] # => "にち" -# # Zero length. -# 'foo'[2, 0] # => "" -# # Length not entirely available. -# 'foo'[1, 200] # => "oo" -# # Start out of range. -# 'foo'[4, 2] # => nil -# -# Special case: if +start+ equals the length of +self+, -# the slice is a new empty string: -# -# 'foo'[3, 2] # => "" -# 'foo'[3, 200] # => "" -# -# When a negative +start+ and non-negative +length+ are given, -# the slice begins by counting backward from the end of +self+, -# and continues for +length+ characters, if available: -# -# 'foo'[-2, 2] # => "oo" -# 'foo'[-2, 200] # => "oo" -# # Start out of range. -# 'foo'[-4, 2] # => nil -# -# When a negative +length+ is given, there is no slice: -# -# 'foo'[1, -1] # => nil -# 'foo'[-2, -1] # => nil -# -# string[range] -# -# When a Range argument +range+ is given, -# it creates a substring of +string+ using the indices in +range+. -# The slice is then determined as above: -# -# 'foo'[0..1] # => "fo" -# 'foo'[0, 2] # => "fo" -# -# 'foo'[2...2] # => "" -# 'foo'[2, 0] # => "" -# -# 'foo'[1..200] # => "oo" -# 'foo'[1, 200] # => "oo" -# -# 'foo'[4..5] # => nil -# 'foo'[4, 2] # => nil -# -# 'foo'[-4..-3] # => nil -# 'foo'[-4, 2] # => nil -# -# 'foo'[3..4] # => "" -# 'foo'[3, 2] # => "" -# -# 'foo'[-2..-1] # => "oo" -# 'foo'[-2, 2] # => "oo" -# -# 'foo'[-2..197] # => "oo" -# 'foo'[-2, 200] # => "oo" -# -# string[regexp, capture = 0] -# -# When the Regexp argument +regexp+ is given, -# and the +capture+ argument is 0, -# the slice is the first matching substring found in +self+: -# -# 'foo'[/o/] # => "o" -# 'foo'[/x/] # => nil -# s = 'hello there' -# s[/[aeiou](.)\1/] # => "ell" -# s[/[aeiou](.)\1/, 0] # => "ell" -# -# If the argument +capture+ is provided and not 0, -# it should be either a capture group index (integer) -# or a capture group name (String or Symbol); -# the slice is the specified capture -# (see {Groups and Captures}[rdoc-ref:Regexp@Groups+and+Captures]): -# -# s = 'hello there' -# s[/[aeiou](.)\1/, 1] # => "l" -# s[/(?[aeiou])(?[^aeiou])/, "non_vowel"] # => "l" -# s[/(?[aeiou])(?[^aeiou])/, :vowel] # => "e" -# -# If an invalid capture group index is given, there is no slice. -# If an invalid capture group name is given, +IndexError+ is raised. -# -# string[substring] -# -# When the single +String+ argument +substring+ is given, -# it returns the substring from +self+ if found, otherwise +nil+: -# -# 'foo'['oo'] # => "oo" -# 'foo'['xx'] # => nil -# # == What's Here # # First, what's elsewhere. Class +String+: diff --git a/doc/string/aref.rdoc b/doc/string/aref.rdoc new file mode 100644 index 00000000000000..bd33c4c0504523 --- /dev/null +++ b/doc/string/aref.rdoc @@ -0,0 +1,98 @@ +Returns the substring of +self+ specified by the arguments. + +Form self[index] + +With non-negative integer argument +index+ given, +returns the 1-character substring found in self at character offset index: + + 'hello'[0] # => "h" + 'hello'[4] # => "o" + 'hello'[5] # => nil + 'тест'[2] # => "с" + 'こんにちは'[4] # => "は" + +With negative integer argument +index+ given, +counts backward from the end of +self+: + + 'hello'[-1] # => "o" + 'hello'[-5] # => "h" + 'hello'[-6] # => nil + +Form self[start, length] + +With integer arguments +start+ and +length+ given, +returns a substring of size +length+ characters (as available) +beginning at character offset specified by +start+. + +If argument +start+ is non-negative, +the offset is +start+: + + 'hello'[0, 1] # => "h" + 'hello'[0, 5] # => "hello" + 'hello'[0, 6] # => "hello" + 'hello'[2, 3] # => "llo" + 'hello'[2, 0] # => "" + 'hello'[2, -1] # => nil + +If argument +start+ is negative, +counts backward from the end of +self+: + + 'hello'[-1, 1] # => "o" + 'hello'[-5, 5] # => "hello" + 'hello'[-1, 0] # => "" + 'hello'[-6, 5] # => nil + +Special case: if +start+ equals the length of +self+, +returns a new empty string: + + 'hello'[5, 3] # => "" + +Form self[range] + +With Range argument +range+ given, +forms substring self[range.start, range.size]: + + 'hello'[0..2] # => "hel" + 'hello'[0, 3] # => "hel" + + 'hello'[0...2] # => "he" + 'hello'[0, 2] # => "he" + + 'hello'[0, 0] # => "" + 'hello'[0...0] # => "" + +Form self[regexp, capture = 0] + +With Regexp argument +regexp+ given and +capture+ as zero, +searches for a matching substring in +self+; +updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: + + 'hello'[/ell/] # => "ell" + 'hello'[/l+/] # => "ll" + 'hello'[//] # => "" + 'hello'[/nosuch/] # => nil + +With +capture+ as a positive integer +n+, +returns the +n+th matched group: + + 'hello'[/(h)(e)(l+)(o)/] # => "hello" + 'hello'[/(h)(e)(l+)(o)/, 1] # => "h" + $1 # => "h" + 'hello'[/(h)(e)(l+)(o)/, 2] # => "e" + $2 # => "e" + 'hello'[/(h)(e)(l+)(o)/, 3] # => "ll" + 'hello'[/(h)(e)(l+)(o)/, 4] # => "o" + 'hello'[/(h)(e)(l+)(o)/, 5] # => nil + +Form self[substring] + +With string argument +substring+ given, +returns the matching substring of +self+, if found: + + 'hello'['ell'] # => "ell" + 'hello'[''] # => "" + 'hello'['nosuch'] # => nil + 'тест'['ес'] # => "ес" + 'こんにちは'['んにち'] # => "んにち" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/aset.rdoc b/doc/string/aset.rdoc new file mode 100644 index 00000000000000..e06680d16c5435 --- /dev/null +++ b/doc/string/aset.rdoc @@ -0,0 +1,183 @@ +Returns +self+ with all, a substring, or none of its contents replaced; +returns the argument +other_string+. + +Form self[index] = other_string + +With non-negative integer argument +index+ given, +searches for the 1-character substring found in self at character offset index: + + s = 'hello' + s[0] = 'foo' # => "foo" + s # => "fooello" + + s = 'hello' + s[4] = 'foo' # => "foo" + s # => "hellfoo" + + s = 'hello' + s[5] = 'foo' # => "foo" + s # => "hellofoo" + + s = 'hello' + s[6] = 'foo' # Raises IndexError: index 6 out of string. + +With negative integer argument +index+ given, +counts backward from the end of +self+: + + s = 'hello' + s[-1] = 'foo' # => "foo" + s # => "hellfoo" + + s = 'hello' + s[-5] = 'foo' # => "foo" + s # => "fooello" + + s = 'hello' + s[-6] = 'foo' # Raises IndexError: index -6 out of string. + +Form self[start, length] = other_string + +With integer arguments +start+ and +length+ given, +searches for a substring of size +length+ characters (as available) +beginning at character offset specified by +start+. + +If argument +start+ is non-negative, +the offset is +start': + + s = 'hello' + s[0, 1] = 'foo' # => "foo" + s # => "fooello" + + s = 'hello' + s[0, 5] = 'foo' # => "foo" + s # => "foo" + + s = 'hello' + s[0, 9] = 'foo' # => "foo" + s # => "foo" + + s = 'hello' + s[2, 0] = 'foo' # => "foo" + s # => "hefoollo" + + s = 'hello' + s[2, -1] = 'foo' # Raises IndexError: negative length -1. + +If argument +start+ is negative, +counts backward from the end of +self+: + + s = 'hello' + s[-1, 1] = 'foo' # => "foo" + s # => "hellfoo" + + s = 'hello' + s[-1, 9] = 'foo' # => "foo" + s # => "hellfoo" + + s = 'hello' + s[-5, 2] = 'foo' # => "foo" + s # => "foollo" + + s = 'hello' + s[-3, 0] = 'foo' # => "foo" + s # => "hefoollo" + + s = 'hello' + s[-6, 2] = 'foo' # Raises IndexError: index -6 out of string. + +Special case: if +start+ equals the length of +self+, +the argument is appended to +self+: + + s = 'hello' + s[5, 3] = 'foo' # => "foo" + s # => "hellofoo" + +Form self[range] = other_string + +With Range argument +range+ given, +equivalent to self[range.start, range.size] = other_string: + + s0 = 'hello' + s1 = 'hello' + s0[0..2] = 'foo' # => "foo" + s1[0, 3] = 'foo' # => "foo" + s0 # => "foolo" + s1 # => "foolo" + + s = 'hello' + s[0...2] = 'foo' # => "foo" + s # => "foollo" + + s = 'hello' + s[0...0] = 'foo' # => "foo" + s # => "foohello" + + s = 'hello' + s[9..10] = 'foo' # Raises RangeError: 9..10 out of range + +Form self[regexp, capture = 0] = other_string + +With Regexp argument +regexp+ given and +capture+ as zero, +searches for a matching substring in +self+; +updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: + + s = 'hello' + s[/l/] = 'L' # => "L" + [$`, $&, $'] # => ["he", "l", "lo"] + s[/eLlo/] = 'owdy' # => "owdy" + [$`, $&, $'] # => ["h", "eLlo", ""] + s[/eLlo/] = 'owdy' # Raises IndexError: regexp not matched. + [$`, $&, $'] # => [nil, nil, nil] + +With +capture+ as a positive integer +n+, +searches for the +n+th matched group: + + s = 'hello' + s[/(h)(e)(l+)(o)/] = 'foo' # => "foo" + [$`, $&, $'] # => ["", "hello", ""] + + s = 'hello' + s[/(h)(e)(l+)(o)/, 1] = 'foo' # => "foo" + s # => "fooello" + [$`, $&, $'] # => ["", "hello", ""] + + s = 'hello' + s[/(h)(e)(l+)(o)/, 2] = 'foo' # => "foo" + s # => "hfoollo" + [$`, $&, $'] # => ["", "hello", ""] + + s = 'hello' + s[/(h)(e)(l+)(o)/, 4] = 'foo' # => "foo" + s # => "hellfoo" + [$`, $&, $'] # => ["", "hello", ""] + + s = 'hello' + # => "hello" + s[/(h)(e)(l+)(o)/, 5] = 'foo # Raises IndexError: index 5 out of regexp. + + s = 'hello' + s[/nosuch/] = 'foo' # Raises IndexError: regexp not matched. + +Form self[substring] = other_string + +With string argument +substring+ given: + + s = 'hello' + s['l'] = 'foo' # => "foo" + s # => "hefoolo" + + s = 'hello' + s['ll'] = 'foo' # => "foo" + s # => "hefooo" + + s = 'тест' + s['ес'] = 'foo' # => "foo" + s # => "тfooт" + + s = 'こんにちは' + s['んにち'] = 'foo' # => "foo" + s # => "こfooは" + + s['nosuch'] = 'foo' # Raises IndexError: string not matched. + +Related: see {Modifying}[rdoc-ref:String@Modifying]. diff --git a/string.c b/string.c index 0ee0ab744856d7..f797dab6516f8d 100644 --- a/string.c +++ b/string.c @@ -5809,10 +5809,8 @@ rb_str_aref(VALUE str, VALUE indx) * self[regexp, capture = 0] -> new_string or nil * self[substring] -> new_string or nil * - * Returns the substring of +self+ specified by the arguments. - * See examples at {String Slices}[rdoc-ref:String@String+Slices]. + * :include: doc/string/aref.rdoc * - * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -6026,30 +6024,14 @@ rb_str_aset(VALUE str, VALUE indx, VALUE val) /* * call-seq: - * self[index] = new_string - * self[start, length] = new_string - * self[range] = new_string - * self[regexp, capture = 0] = new_string - * self[substring] = new_string - * - * Replaces all, some, or none of the contents of +self+; returns +new_string+. - * See {String Slices}[rdoc-ref:String@String+Slices]. + * self[index] = other_string -> new_string + * self[start, length] = other_string -> new_string + * self[range] = other_string -> new_string + * self[regexp, capture = 0] = other_string -> new_string + * self[substring] = other_string -> new_string * - * A few examples: - * - * s = 'foo' - * s[2] = 'rtune' # => "rtune" - * s # => "fortune" - * s[1, 5] = 'init' # => "init" - * s # => "finite" - * s[3..4] = 'al' # => "al" - * s # => "finale" - * s[/e$/] = 'ly' # => "ly" - * s # => "finally" - * s['lly'] = 'ncial' # => "ncial" - * s # => "financial" + * :include: doc/string/aset.rdoc * - * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -6100,18 +6082,20 @@ rb_str_insert(VALUE str, VALUE idx, VALUE str2) * slice!(regexp, capture = 0) -> new_string or nil * slice!(substring) -> new_string or nil * - * Removes and returns the substring of +self+ specified by the arguments. - * See {String Slices}[rdoc-ref:String@String+Slices]. + * Like String#[] (and its alias String#slice), except that: + * + * - Performs substitutions in +self+ (not in a copy of +self+). + * - Returns the removed substring if any modifications were made, +nil+ otherwise. * * A few examples: * - * string = "This is a string" - * string.slice!(2) #=> "i" - * string.slice!(3..6) #=> " is " - * string.slice!(/s.*t/) #=> "sa st" - * string.slice!("r") #=> "r" - * string #=> "Thing" + * s = 'hello' + * s.slice!('e') # => "e" + * s # => "hllo" + * s.slice!('e') # => nil + * s # => "hllo" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From d0d1246cd414655356e218e25a1c32666227e504 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 10 Oct 2025 14:48:49 -0700 Subject: [PATCH 0291/2435] sync_default_gems.rb: Minimize the number of refs fetched from the repository --- tool/sync_default_gems.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index f404b7ff5061de..b4bd761a729078 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -64,6 +64,10 @@ module SyncDefaultGems CLASSICAL_DEFAULT_BRANCH = "master" + # Allow synchronizing commits up to this FETCH_DEPTH. We've historically merged PRs + # with about 250 commits to ruby/ruby, so we use this depth for ruby/ruby in general. + FETCH_DEPTH = 500 + class << REPOSITORIES def [](gem) repo, branch = super(gem) @@ -662,7 +666,7 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil) `git remote add #{gem} https://github.com/#{repo}.git` end end - system(*%W"git fetch --no-tags #{gem}") + system(*%W"git fetch --no-tags --depth=#{FETCH_DEPTH} #{gem} #{default_branch}") commits = commits_in_ranges(gem, repo, default_branch, ranges) From 07b59eee6aa120537d7d72422327cc7b855e9400 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 8 Oct 2025 17:08:20 -0400 Subject: [PATCH 0292/2435] Fix memory leak when load_from_binary raises ibf_load_code will leak memory allocated for the code if an exception is raised. The following script reproduces the leak: bin = RubyVM::InstructionSequence.of(1.method(:abs)).to_binary 10.times do 100_000.times do RubyVM::InstructionSequence.load_from_binary(bin) rescue ArgumentError end puts `ps -o rss= -p #{$$}` end Before: 18004 23380 28756 34260 39892 45396 50772 55892 61012 66132 After: 12536 12920 13304 13688 14072 14456 14840 15352 15608 15864 --- compile.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compile.c b/compile.c index cb8554f0a17fd3..717ec0a2cae5ab 100644 --- a/compile.c +++ b/compile.c @@ -12926,6 +12926,9 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod struct rb_call_data *cd_entries = load_body->call_data; int ic_index = 0; + load_body->iseq_encoded = code; + load_body->iseq_size = 0; + iseq_bits_t * mark_offset_bits; iseq_bits_t tmp[1] = {0}; @@ -13057,7 +13060,6 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod } } - load_body->iseq_encoded = code; load_body->iseq_size = code_index; if (ISEQ_MBITS_BUFLEN(load_body->iseq_size) == 1) { From b868beea10096df8e54c8b1175659f491a0b03dd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Oct 2025 09:44:32 +0900 Subject: [PATCH 0293/2435] commit-email.rb: Suppres git signed commit signatures When setting `log.showSignature=true`, `git log` and `git show` include messages gpg verfied the commits, in addition to the message specified by `--pretty`. --- tool/commit-email.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index f738261dcca6cd..2381a74debde45 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -121,7 +121,7 @@ def build_diff(path, numstats) end def git_show(revision, format:) - git('show', "--pretty=#{format}", '--no-patch', revision).strip + git('show', '--no-show-signature', "--pretty=#{format}", '--no-patch', revision).strip end def git(*args) @@ -180,7 +180,7 @@ def main(repo_path, to, rest) args, options = parse(rest) infos = args.each_slice(3).flat_map do |oldrev, newrev, refname| - revisions = IO.popen(['git', 'log', '--reverse', '--pretty=%H', "#{oldrev}^..#{newrev}"], &:read).lines.map(&:strip) + revisions = IO.popen(['git', 'log', '--no-show-signature', '--reverse', '--pretty=%H', "#{oldrev}^..#{newrev}"], &:read).lines.map(&:strip) revisions[0..-2].zip(revisions[1..-1]).map do |old, new| GitInfoBuilder.new(repo_path).build(old, new, refname) end From fa883a4de89c40d152c28ee0de83ff05b40059df Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Oct 2025 09:44:52 +0900 Subject: [PATCH 0294/2435] test_commit_email.rb: Simply use sh `env` on macOS resets DYLD environment variables that are needed to run ruby configured with `--enable-load-relative`. Use simple commands instead, and `/bin/sh` that is specified in POSIX is more portable than `/usr/bin/env`. --- tool/test/test_commit_email.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index efd45022be0bab..b948987ed96411 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -20,12 +20,11 @@ def setup end @sendmail = File.join(Dir.mktmpdir, 'sendmail') - File.write(@sendmail, <<~SENDMAIL) - #!/usr/bin/env ruby - print #{STDIN_DELIMITER.dump} - puts STDIN.read + File.write(@sendmail, <<~SENDMAIL, mode: "wx", perm: 0755) + #!/bin/sh + echo #{STDIN_DELIMITER.chomp.dump} + exec cat SENDMAIL - FileUtils.chmod(0755, @sendmail) @commit_email = File.expand_path('../../tool/commit-email.rb', __dir__) end From e500265b099df21768c28890c77fade4b60da068 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Oct 2025 09:56:42 +0900 Subject: [PATCH 0295/2435] commit-email.rb: Suppress warnings * assigned but unused variable * literal string will be frozen in the future --- tool/commit-email.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index 2381a74debde45..c887f8783e9d30 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -211,7 +211,7 @@ def b_encode(str) end def make_body(info, viewer_uri:) - body = '' + body = +'' body << "#{info.author}\t#{format_time(info.date)}\n" body << "\n" body << " New Revision: #{info.revision}\n" @@ -276,7 +276,6 @@ def modified_dirs(info) end def changed_dirs_info(info, uri) - rev = info.revision (info.added_dirs.collect do |dir| " Added: #{dir}\n" end + info.deleted_dirs.collect do |dir| @@ -293,14 +292,11 @@ def diff_info(info, uri) values.collect do |type, value| case type when :added - command = 'cat' rev = "?revision=#{info.revision}&view=markup" when :modified, :property_changed - command = 'diff' prev_revision = (info.revision.is_a?(Integer) ? info.revision - 1 : "#{info.revision}^") rev = "?r1=#{info.revision}&r2=#{prev_revision}&diff_format=u" when :deleted, :copied - command = 'cat' rev = '' else raise "unknown diff type: #{value[:type]}" @@ -328,7 +324,7 @@ def make_header(to, from, info) end def make_subject(info) - subject = '' + subject = +'' subject << "#{info.revision}" subject << " (#{info.branch})" subject << ': ' From e8f0e1423b6c6bf2a02791d28ed149eb892a27be Mon Sep 17 00:00:00 2001 From: Bilka Date: Wed, 8 Oct 2025 14:18:11 +0200 Subject: [PATCH 0296/2435] [DOC] Fix typo in Regexp Optimization section --- doc/_regexp.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/_regexp.rdoc b/doc/_regexp.rdoc index b862c9a49e0594..76ac3a859e5b15 100644 --- a/doc/_regexp.rdoc +++ b/doc/_regexp.rdoc @@ -1251,7 +1251,7 @@ the potential vulnerability arising from this is the {regular expression denial- \Regexp matching can apply an optimization to prevent ReDoS attacks. When the optimization is applied, matching time increases linearly (not polynomially or exponentially) -in relation to the input size, and a ReDoS attach is not possible. +in relation to the input size, and a ReDoS attack is not possible. This optimization is applied if the pattern meets these criteria: From 10c0d7a839ef920ae77fbbb38d081795ecec9057 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 11 Oct 2025 18:41:29 +0100 Subject: [PATCH 0297/2435] ZJIT: Count unoptimized `Send` (#14801) * ZJIT: Count unoptimized `Send` This includes `Send` in `send fallback reasons` to guide future optimizations. * ZJIT: Create dedicated def_type counter for Send --- zjit.rb | 3 +- zjit/src/codegen.rs | 5 ++- zjit/src/hir.rs | 39 ++++++++++++++++++ zjit/src/stats.rs | 96 ++++++++++++++++++++++++++++++++------------- 4 files changed, 114 insertions(+), 29 deletions(-) diff --git a/zjit.rb b/zjit.rb index 87ff52f55a1c21..fdfe4ce9835a13 100644 --- a/zjit.rb +++ b/zjit.rb @@ -159,7 +159,8 @@ def stats_string print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time - print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 35791bc0d7ae2f..08df10c100a3d8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -15,7 +15,7 @@ use crate::invariants::{ }; use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; -use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; +use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP}; @@ -1617,6 +1617,9 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso gen_incr_counter_ptr(asm, send_fallback_counter_ptr_for_opcode(opcode)); } SendWithoutBlockNotOptimizedMethodType(method_type) => { + gen_incr_counter(asm, send_without_block_fallback_counter_for_method_type(method_type)); + } + SendNotOptimizedMethodType(method_type) => { gen_incr_counter(asm, send_fallback_counter_for_method_type(method_type)); } _ => {} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8837457afaadee..8b04143f840deb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -535,6 +535,9 @@ pub enum SendFallbackReason { SendWithoutBlockCfuncArrayVariadic, SendWithoutBlockNotOptimizedMethodType(MethodType), SendWithoutBlockDirectTooManyArgs, + SendPolymorphic, + SendNoProfiles, + SendNotOptimizedMethodType(MethodType), CCallWithFrameTooManyArgs, ObjToStringNotString, /// Initial fallback reason for every instruction, which should be mutated to @@ -2123,6 +2126,42 @@ impl Function { self.push_insn_id(block, insn_id); continue; } } + // This doesn't actually optimize Send yet, just replaces the fallback reason to be more precise. + // TODO: Optimize Send + Insn::Send { recv, cd, state, .. } => { + let frame_state = self.frame_state(state); + let klass = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { + // If we know the class statically, use it to fold the lookup at compile-time. + klass + } else { + let Some(recv_type) = self.profiled_type_of_at(recv, frame_state.insn_idx) else { + if get_option!(stats) { + match self.is_polymorphic_at(recv, frame_state.insn_idx) { + Some(true) => self.set_dynamic_send_reason(insn_id, SendPolymorphic), + // If the class isn't known statically, then it should not also be monomorphic + Some(false) => panic!("Should not have monomorphic profile at this point in this branch"), + None => self.set_dynamic_send_reason(insn_id, SendNoProfiles), + } + } + self.push_insn_id(block, insn_id); continue; + }; + recv_type.class() + }; + let ci = unsafe { get_call_data_ci(cd) }; // info about the call site + let mid = unsafe { vm_ci_mid(ci) }; + // Do method lookup + let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; + if cme.is_null() { + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::Null)); + self.push_insn_id(block, insn_id); continue; + } + // Load an overloaded cme if applicable. See vm_search_cc(). + // It allows you to use a faster ISEQ if possible. + cme = unsafe { rb_check_overloaded_cme(cme, ci) }; + let def_type = unsafe { get_cme_def_type(cme) }; + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::from(def_type))); + self.push_insn_id(block, insn_id); continue; + } Insn::GetConstantPath { ic, state, .. } => { let idlist: *const ID = unsafe { (*ic).segments }; let ice = unsafe { (*ic).entry }; diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 6898053dca789b..d902c69b795c4b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -148,6 +148,9 @@ make_counters! { send_fallback_send_without_block_cfunc_array_variadic, send_fallback_send_without_block_not_optimized_method_type, send_fallback_send_without_block_direct_too_many_args, + send_fallback_send_polymorphic, + send_fallback_send_no_profiles, + send_fallback_send_not_optimized_method_type, send_fallback_ccall_with_frame_too_many_args, send_fallback_obj_to_string_not_string, send_fallback_not_optimized_instruction, @@ -185,20 +188,35 @@ make_counters! { dynamic_getivar_count, dynamic_setivar_count, - // Method call def_type related to fallback to dynamic dispatch - unspecialized_def_type_iseq, - unspecialized_def_type_cfunc, - unspecialized_def_type_attrset, - unspecialized_def_type_ivar, - unspecialized_def_type_bmethod, - unspecialized_def_type_zsuper, - unspecialized_def_type_alias, - unspecialized_def_type_undef, - unspecialized_def_type_not_implemented, - unspecialized_def_type_optimized, - unspecialized_def_type_missing, - unspecialized_def_type_refined, - unspecialized_def_type_null, + // Method call def_type related to send without block fallback to dynamic dispatch + unspecialized_send_without_block_def_type_iseq, + unspecialized_send_without_block_def_type_cfunc, + unspecialized_send_without_block_def_type_attrset, + unspecialized_send_without_block_def_type_ivar, + unspecialized_send_without_block_def_type_bmethod, + unspecialized_send_without_block_def_type_zsuper, + unspecialized_send_without_block_def_type_alias, + unspecialized_send_without_block_def_type_undef, + unspecialized_send_without_block_def_type_not_implemented, + unspecialized_send_without_block_def_type_optimized, + unspecialized_send_without_block_def_type_missing, + unspecialized_send_without_block_def_type_refined, + unspecialized_send_without_block_def_type_null, + + // Method call def_type related to send fallback to dynamic dispatch + unspecialized_send_def_type_iseq, + unspecialized_send_def_type_cfunc, + unspecialized_send_def_type_attrset, + unspecialized_send_def_type_ivar, + unspecialized_send_def_type_bmethod, + unspecialized_send_def_type_zsuper, + unspecialized_send_def_type_alias, + unspecialized_send_def_type_undef, + unspecialized_send_def_type_not_implemented, + unspecialized_send_def_type_optimized, + unspecialized_send_def_type_missing, + unspecialized_send_def_type_refined, + unspecialized_send_def_type_null, // Writes to the VM frame vm_write_pc_count, @@ -320,30 +338,54 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, + SendPolymorphic => send_fallback_send_polymorphic, + SendNoProfiles => send_fallback_send_no_profiles, + SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, ObjToStringNotString => send_fallback_obj_to_string_not_string, NotOptimizedInstruction(_) => send_fallback_not_optimized_instruction, } } +pub fn send_without_block_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter { + use crate::hir::MethodType::*; + use crate::stats::Counter::*; + + match method_type { + Iseq => unspecialized_send_without_block_def_type_iseq, + Cfunc => unspecialized_send_without_block_def_type_cfunc, + Attrset => unspecialized_send_without_block_def_type_attrset, + Ivar => unspecialized_send_without_block_def_type_ivar, + Bmethod => unspecialized_send_without_block_def_type_bmethod, + Zsuper => unspecialized_send_without_block_def_type_zsuper, + Alias => unspecialized_send_without_block_def_type_alias, + Undefined => unspecialized_send_without_block_def_type_undef, + NotImplemented => unspecialized_send_without_block_def_type_not_implemented, + Optimized => unspecialized_send_without_block_def_type_optimized, + Missing => unspecialized_send_without_block_def_type_missing, + Refined => unspecialized_send_without_block_def_type_refined, + Null => unspecialized_send_without_block_def_type_null, + } +} + pub fn send_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter { use crate::hir::MethodType::*; use crate::stats::Counter::*; match method_type { - Iseq => unspecialized_def_type_iseq, - Cfunc => unspecialized_def_type_cfunc, - Attrset => unspecialized_def_type_attrset, - Ivar => unspecialized_def_type_ivar, - Bmethod => unspecialized_def_type_bmethod, - Zsuper => unspecialized_def_type_zsuper, - Alias => unspecialized_def_type_alias, - Undefined => unspecialized_def_type_undef, - NotImplemented => unspecialized_def_type_not_implemented, - Optimized => unspecialized_def_type_optimized, - Missing => unspecialized_def_type_missing, - Refined => unspecialized_def_type_refined, - Null => unspecialized_def_type_null, + Iseq => unspecialized_send_def_type_iseq, + Cfunc => unspecialized_send_def_type_cfunc, + Attrset => unspecialized_send_def_type_attrset, + Ivar => unspecialized_send_def_type_ivar, + Bmethod => unspecialized_send_def_type_bmethod, + Zsuper => unspecialized_send_def_type_zsuper, + Alias => unspecialized_send_def_type_alias, + Undefined => unspecialized_send_def_type_undef, + NotImplemented => unspecialized_send_def_type_not_implemented, + Optimized => unspecialized_send_def_type_optimized, + Missing => unspecialized_send_def_type_missing, + Refined => unspecialized_send_def_type_refined, + Null => unspecialized_send_def_type_null, } } From d036dc0a794d87309f912e7585749c681c2438f5 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 10 Oct 2025 15:06:45 -0400 Subject: [PATCH 0298/2435] For prism parser, do not update $_ from STDIN Fixes [Bug #21635] --- io.c | 10 ++++++++-- prism_compile.c | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/io.c b/io.c index caa364a70bbd9a..d412d14963bded 100644 --- a/io.c +++ b/io.c @@ -4307,11 +4307,17 @@ rb_io_gets(VALUE io) } VALUE -rb_io_gets_internal(VALUE io) +rb_io_gets_limit_internal(VALUE io, long limit) { rb_io_t *fptr; GetOpenFile(io, fptr); - return rb_io_getline_0(rb_default_rs, -1, FALSE, fptr); + return rb_io_getline_0(rb_default_rs, limit, FALSE, fptr); +} + +VALUE +rb_io_gets_internal(VALUE io) +{ + return rb_io_gets_limit_internal(io, -1); } /* diff --git a/prism_compile.c b/prism_compile.c index 66bee579c9b45a..86753c90cc17a9 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -11466,6 +11466,8 @@ pm_parse_stdin_eof(void *stream) return wrapped_stdin->eof_seen; } +VALUE rb_io_gets_limit_internal(VALUE io, long limit); + /** * An implementation of fgets that is suitable for use with Ruby IO objects. */ @@ -11476,7 +11478,7 @@ pm_parse_stdin_fgets(char *string, int size, void *stream) struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream; - VALUE line = rb_funcall(wrapped_stdin->rb_stdin, rb_intern("gets"), 1, INT2FIX(size - 1)); + VALUE line = rb_io_gets_limit_internal(wrapped_stdin->rb_stdin, size - 1); if (NIL_P(line)) { return NULL; } From 89dc79eeba9b450d2cc921b428fd4519f5e04690 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 10 Oct 2025 19:27:08 -0400 Subject: [PATCH 0299/2435] Ignore thread not suspended warning messages in LSAN When a process with multiple threads is forked, LSAN outputs warning messages to stderr like: ==276855==Running thread 276851 was not suspended. False leaks are possible. We should ignore messages like this in tests. --- tool/lib/core_assertions.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index fc7b9402843303..7e6dc8e6537bd6 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -328,6 +328,13 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"}) args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt) + + if Test::Sanitizers.lsan_enabled? + # LSAN may output messages like the following line into stderr. We should ignore it. + # ==276855==Running thread 276851 was not suspended. False leaks are possible. + # See https://github.com/google/sanitizers/issues/1479 + stderr.gsub!(/==\d+==Running thread \d+ was not suspended\. False leaks are possible\.\n/, "") + end ensure if res_c res_c.close From fa54a9c9e58fa810ccef9eef86dd7ba8f1763f2d Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Sun, 12 Oct 2025 00:35:54 +0200 Subject: [PATCH 0300/2435] [ruby/erb] html_escape: Avoid buffer allocation for strings with no escapable character (https://github.com/ruby/erb/pull/87) This change improves reduces allocations and makes `html_escape` ~35% faster in a benchmark with escaped strings taken from the `test_html_escape` test in `test/test_erb.rb`. - Perform buffer allocation on first instance of escapable character. - Instead of copying characters one at a time, copy unescaped segments using `memcpy`. https://github.com/ruby/erb/commit/aa482890fe --- ext/erb/escape/escape.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c index 2a5903c3b738c8..a46fe236c0f952 100644 --- a/ext/erb/escape/escape.c +++ b/ext/erb/escape/escape.c @@ -39,29 +39,44 @@ static VALUE optimized_escape_html(VALUE str) { VALUE vbuf; - char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); + char *buf = NULL; const char *cstr = RSTRING_PTR(str); const char *end = cstr + RSTRING_LEN(str); - char *dest = buf; + const char *segment_start = cstr; + char *dest = NULL; while (cstr < end) { const unsigned char c = *cstr++; uint8_t len = html_escape_table[c].len; if (len) { + uint16_t segment_len = cstr - segment_start - 1; + if (!buf) { + buf = ALLOCV_N(char, vbuf, escaped_length(str)); + dest = buf; + } + if (segment_len) { + memcpy(dest, segment_start, segment_len); + dest += segment_len; + } + segment_start = cstr; memcpy(dest, html_escape_table[c].str, len); dest += len; } - else { - *dest++ = c; + } + if (buf) { + uint16_t segment_len = cstr - segment_start; + if (segment_len) { + memcpy(dest, segment_start, segment_len); + dest += segment_len; } } VALUE escaped = str; - if (RSTRING_LEN(str) < (dest - buf)) { + if (buf) { escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); + ALLOCV_END(vbuf); } - ALLOCV_END(vbuf); return escaped; } From 02d8a001eed707f8cd130f5c644f5c6f4b176424 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 11 Oct 2025 15:37:22 -0700 Subject: [PATCH 0301/2435] [ruby/erb] Version 5.1.0 https://github.com/ruby/erb/commit/25fdde41d6 --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 521e77ce4aac5b..a5459464613aa9 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '5.0.3' + VERSION = '5.1.0' end From a6f92ddd12805b12dc3e7e418ebc7189b6b6f95c Mon Sep 17 00:00:00 2001 From: git Date: Sat, 11 Oct 2025 22:39:27 +0000 Subject: [PATCH 0302/2435] Update default gems list at 02d8a001eed707f8cd130f5c644f5c [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 17cb9ba982d613..c24b9070ff1fe2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,7 +182,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev -* erb 5.0.3 +* erb 5.1.0 * etc 1.4.6 * fcntl 1.3.0 * io-console 0.8.1 From 32b98d71e6f381fb78316ed663c579886a1eaaad Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sat, 11 Oct 2025 15:32:26 -0400 Subject: [PATCH 0303/2435] YJIT: Fix unused warning from `cargo test` --- yjit/src/stats.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index b63e1c3272e356..a53d23435b49a7 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -917,6 +917,7 @@ pub extern "C" fn rb_yjit_record_exit_stack(exit_pc: *const VALUE) // rb_vm_insn_addr2opcode won't work in cargo test --all-features // because it's a C function. Without insn call, this function is useless // so wrap the whole thing in a not test check. + let _ = exit_pc; #[cfg(not(test))] { // Get the opcode from the encoded insn handler at this PC From 21e81160e61d43457f01f4af5becb81d1f76474a Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sat, 11 Oct 2025 16:37:19 -0400 Subject: [PATCH 0304/2435] CI: Surface `rustdoc` warnings Soft fails like warnings from rustc. The `rustdoc` warnings tend to be dead links in the markup. --- .github/workflows/rust-warnings.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml index 98ddfead5c8522..3ab095e8999ba3 100644 --- a/.github/workflows/rust-warnings.yml +++ b/.github/workflows/rust-warnings.yml @@ -21,7 +21,7 @@ permissions: contents: read jobs: - make: + rust-warnings: env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} @@ -46,6 +46,15 @@ jobs: run: | set -eu cargo check --quiet --all-features --message-format=json \ - | jq -r 'select(.message.level == "warning" or .message.level == "error") | .message.rendered' \ + | jq -r 'select(.message.level | IN("warning", "error")) | .message.rendered' \ + | tee messages.txt + (exit "${PIPESTATUS[0]}") && ! grep --quiet '[^[:space:]]' messages.txt + + - name: "📜 `rustdoc` warnings" + shell: bash + run: | + set -eu + cargo doc --document-private-items --all --no-deps --message-format=json \ + | jq -r 'select(.message.level | IN("warning", "error")) | .message.rendered' \ | tee messages.txt (exit "${PIPESTATUS[0]}") && ! grep --quiet '[^[:space:]]' messages.txt From 6be2a5104df894079d127d2cdc19b21c4f174d85 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sat, 11 Oct 2025 15:48:43 -0400 Subject: [PATCH 0305/2435] YJIT: ZJIT: Fix rustdoc dead links --- yjit/src/virtualmem.rs | 2 +- zjit/src/codegen.rs | 2 +- zjit/src/gc.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yjit/src/virtualmem.rs b/yjit/src/virtualmem.rs index 58c9d9a7fd380a..9126cf300e307d 100644 --- a/yjit/src/virtualmem.rs +++ b/yjit/src/virtualmem.rs @@ -46,7 +46,7 @@ pub struct VirtualMemory { /// Mutable parts of [`VirtualMemory`]. pub struct VirtualMemoryMut { /// Number of bytes that have we have allocated physical memory for starting at - /// [Self::region_start]. + /// [VirtualMemory::region_start]. mapped_region_bytes: usize, /// Keep track of the address of the last written to page. diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 08df10c100a3d8..262a0361b4a74e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1242,7 +1242,7 @@ fn gen_new_array( new_array } -/// Compile array access (array[index]) +/// Compile array access (`array[index]`) fn gen_aref_fixnum( asm: &mut Assembler, array: Opnd, diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index 934e1e8dcad165..baa0926c515189 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -262,7 +262,7 @@ fn ranges_overlap(left: &Range, right: &Range) -> bool where T: Partial left.start < right.end && right.start < left.end } -/// Callback for marking GC objects inside [Invariants]. +/// Callback for marking GC objects inside [crate::invariants::Invariants]. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_root_mark() { gc_mark_raw_samples(); From 7cc3191ea1d8cb637e65cd6c2e9229de0e627643 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 Oct 2025 13:50:53 +0900 Subject: [PATCH 0306/2435] [ruby/erb] Fix integer overflow Fix https://github.com/ruby/erb/pull/87 https://github.com/ruby/erb/commit/75764f022b --- ext/erb/escape/escape.c | 4 ++-- test/erb/test_erb.rb | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c index a46fe236c0f952..9437e9694eda5f 100644 --- a/ext/erb/escape/escape.c +++ b/ext/erb/escape/escape.c @@ -49,7 +49,7 @@ optimized_escape_html(VALUE str) const unsigned char c = *cstr++; uint8_t len = html_escape_table[c].len; if (len) { - uint16_t segment_len = cstr - segment_start - 1; + size_t segment_len = cstr - segment_start - 1; if (!buf) { buf = ALLOCV_N(char, vbuf, escaped_length(str)); dest = buf; @@ -64,7 +64,7 @@ optimized_escape_html(VALUE str) } } if (buf) { - uint16_t segment_len = cstr - segment_start; + size_t segment_len = cstr - segment_start; if (segment_len) { memcpy(dest, segment_start, segment_len); dest += segment_len; diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb index 24f6cc5d8944e0..c0df690ccebe97 100644 --- a/test/erb/test_erb.rb +++ b/test/erb/test_erb.rb @@ -77,6 +77,9 @@ def test_html_escape assert_equal("", ERB::Util.html_escape(nil)) assert_equal("123", ERB::Util.html_escape(123)) + + assert_equal(65536+5, ERB::Util.html_escape("x"*65536 + "&").size) + assert_equal(65536+5, ERB::Util.html_escape("&" + "x"*65536).size) end def test_html_escape_to_s From 226312ecd45304698f5ac74f9413057d5e351ba2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 12 Oct 2025 00:57:56 -0700 Subject: [PATCH 0307/2435] [ruby/erb] Version 5.1.1 https://github.com/ruby/erb/commit/3dc0bb09bf --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index a5459464613aa9..ceca10731e4ce4 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '5.1.0' + VERSION = '5.1.1' end From c938c11f102afc95fd9807e46060a6f9b864f792 Mon Sep 17 00:00:00 2001 From: git Date: Sun, 12 Oct 2025 08:00:03 +0000 Subject: [PATCH 0308/2435] Update default gems list at 226312ecd45304698f5ac74f941305 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index c24b9070ff1fe2..c9507fe2d2dc46 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,7 +182,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev -* erb 5.1.0 +* erb 5.1.1 * etc 1.4.6 * fcntl 1.3.0 * io-console 0.8.1 From c78895b1d61e74d62df6795fac439586135dd9c9 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Mon, 13 Oct 2025 16:30:30 +0900 Subject: [PATCH 0309/2435] Add "Namespace detection information" section in bug reports * To show environments stack when the current namespace is unexpected or namespace detection is broken * It is displayed only when RUBY_BUGREPORT_NAMESPACE_ENV=1 is specified --- vm.c | 1 + vm_dump.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) diff --git a/vm.c b/vm.c index 06e85a1d1a48e2..042a1ac12c98ca 100644 --- a/vm.c +++ b/vm.c @@ -108,6 +108,7 @@ rb_vm_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame static const VALUE * VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp) { + // rb_vmdebug_namespace_env_dump_raw() simulates this function const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; diff --git a/vm_dump.c b/vm_dump.c index 460a0f7b8ccd82..0fcef846db8704 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -162,6 +162,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c } kprintf("s:%04"PRIdPTRDIFF" ", cfp->sp - ec->vm_stack); kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); + kprintf("l:%s ", VM_ENV_LOCAL_P(cfp->ep) ? "y" : "n"); if (ns) { kprintf("n:%04ld ", ns->ns_id % 10000); } @@ -220,6 +221,247 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c return false; } +static inline const rb_control_frame_t * +vmdebug_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, const VALUE * const ep) +{ + if (!ep) { + return NULL; + } + else { + const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ + + while (cfp < eocfp) { + if (cfp->ep == ep) { + return cfp; + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + + return NULL; + } +} + +static rb_callable_method_entry_t * +vmdebug_env_method_entry_unchecked(VALUE obj, int can_be_svar) +{ + if (obj == Qfalse) return NULL; + + switch (imemo_type(obj)) { + case imemo_ment: + return (rb_callable_method_entry_t *)obj; + case imemo_cref: + return NULL; + case imemo_svar: + if (can_be_svar) { + return vmdebug_env_method_entry_unchecked(((struct vm_svar *)obj)->cref_or_me, FALSE); + } + default: + return NULL; + } +} + +static const rb_callable_method_entry_t * +vmdebug_frame_method_entry_unchecked(const VALUE *ep) +{ + rb_callable_method_entry_t *me; + + while (!VM_ENV_LOCAL_P_UNCHECKED(ep)) { + if ((me = vmdebug_env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) return me; + ep = VM_ENV_PREV_EP_UNCHECKED(ep); + } + + return vmdebug_env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); +} + +static bool +namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +{ + ptrdiff_t pc = -1; + ptrdiff_t ep = env - ec->vm_stack; + char ep_in_heap = ' '; + char posbuf[MAX_POSBUF+1]; + int line = 0; + const char *magic, *iseq_name = "-"; + VALUE tmp; + const rb_iseq_t *iseq = NULL; + const rb_namespace_t *ns = NULL; + const rb_control_frame_t *cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, env); + const rb_callable_method_entry_t *me = vmdebug_frame_method_entry_unchecked(env); + + if (ep < 0 || (size_t)ep > ec->vm_stack_size) { + if (cfp) { + ep = (ptrdiff_t)cfp->ep; + ep_in_heap = 'p'; + } + } + + switch (VM_ENV_FLAGS_UNCHECKED(env, VM_FRAME_MAGIC_MASK)) { + case VM_FRAME_MAGIC_TOP: + magic = "TOP"; + ns = VM_ENV_NAMESPACE_UNCHECKED(env); + break; + case VM_FRAME_MAGIC_METHOD: + magic = "METHOD"; + if (me) { + ns = me->def->ns; + } + break; + case VM_FRAME_MAGIC_CLASS: + magic = "CLASS"; + ns = VM_ENV_NAMESPACE_UNCHECKED(env); + break; + case VM_FRAME_MAGIC_BLOCK: + magic = "BLOCK"; + break; + case VM_FRAME_MAGIC_CFUNC: + magic = "CFUNC"; + if (me) { + ns = me->def->ns; + } + break; + case VM_FRAME_MAGIC_IFUNC: + magic = "IFUNC"; + break; + case VM_FRAME_MAGIC_EVAL: + magic = "EVAL"; + break; + case VM_FRAME_MAGIC_RESCUE: + magic = "RESCUE"; + break; + case VM_FRAME_MAGIC_DUMMY: + magic = "DUMMY"; + break; + case 0: + magic = "------"; + break; + default: + magic = "(none)"; + break; + } + + if (cfp && cfp->iseq != 0) { +#define RUBY_VM_IFUNC_P(ptr) IMEMO_TYPE_P(ptr, imemo_ifunc) + if (RUBY_VM_IFUNC_P(cfp->iseq)) { + iseq_name = ""; + } + else if (SYMBOL_P((VALUE)cfp->iseq)) { + tmp = rb_sym2str((VALUE)cfp->iseq); + iseq_name = RSTRING_PTR(tmp); + snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); + line = -1; + } + else { + if (cfp->pc) { + iseq = cfp->iseq; + pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; + iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); + if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) { + line = rb_vm_get_sourceline(cfp); + } + if (line) { + snprintf(posbuf, MAX_POSBUF, "%s:%d", RSTRING_PTR(rb_iseq_path(iseq)), line); + } + } + else { + iseq_name = ""; + } + } + } + else if (me != NULL && IMEMO_TYPE_P(me, imemo_ment)) { + iseq_name = rb_id2name(me->def->original_id); + snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); + line = -1; + } + + if (cfp) { + kprintf("c:%04"PRIdPTRDIFF" ", + ((rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size) - cfp)); + } + else { + kprintf("c:---- "); + } + kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); + kprintf("l:%s ", VM_ENV_LOCAL_P(env) ? "y" : "n"); + if (ns) { + kprintf("n:%04ld ", ns->ns_id % 10000); + } + else { + kprintf("n:---- "); + } + kprintf("%-6s", magic); + if (line) { + kprintf(" %s", posbuf); + } + if (VM_ENV_FLAGS_UNCHECKED(env, VM_FRAME_FLAG_FINISH) != 0) { + kprintf(" [FINISH]"); + } + kprintf("\n"); + return true; + error: + return false; +} + +static bool +namespace_env_dump_unchecked(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +{ + if (env == NULL) { + kprintf("c:---- e:000000 l:- n:---- (none)\n"); + return true; + } + else { + return namespace_env_dump(ec, env, checkpoint_cfp, errout); + } + error: + return false; +} + +bool +rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp, FILE *errout) +{ + // See VM_EP_RUBY_LEP for the original logic + const VALUE *ep = current_cfp->ep; + const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ + const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; + + kprintf("-- Namespace detection information " + "-----------------------------------------\n"); + + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + + while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + } + while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { + if (!cfp) { + cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, ep); + } + if (!cfp) { + goto stop; + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + if (cfp >= eocfp) { + kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); + goto stop; + } + ep = cfp->ep; + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + if (!ep) { + goto stop; + } + } + checkpoint_cfp = cfp; + cfp = NULL; + } + stop: + kprintf("\n"); + return true; + + error: + return false; +} + bool rb_vmdebug_stack_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, FILE *errout) { @@ -1132,6 +1374,7 @@ rb_dump_machine_register(FILE *errout, const ucontext_t *ctx) bool rb_vm_bugreport(const void *ctx, FILE *errout) { + const char *ns_env = getenv("RUBY_BUGREPORT_NAMESPACE_ENV"); const char *cmd = getenv("RUBY_ON_BUG"); if (cmd) { char buf[0x100]; @@ -1175,6 +1418,9 @@ rb_vm_bugreport(const void *ctx, FILE *errout) if (vm && ec) { rb_vmdebug_stack_dump_raw(ec, ec->cfp, errout); + if (ns_env) { + rb_vmdebug_namespace_env_dump_raw(ec, ec->cfp, errout); + } rb_backtrace_print_as_bugreport(errout); kputs("\n"); // If we get here, hopefully things are intact enough that From f0a76f6295ce1ac2cbc7f3152949301afa95d42e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 13 Oct 2025 22:06:11 +0900 Subject: [PATCH 0310/2435] ruby_defint.m4: variable names must not contain spaces --- tool/m4/ruby_defint.m4 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/m4/ruby_defint.m4 b/tool/m4/ruby_defint.m4 index e9ed68e5b83abb..7f262a73fc62c2 100644 --- a/tool/m4/ruby_defint.m4 +++ b/tool/m4/ruby_defint.m4 @@ -17,7 +17,8 @@ typedef $1 t; int s = sizeof(t) == 42;])], ["${ac_cv_sizeof___int128@%:@*:}"], [ rb_cv_type_$1="m4_if([$3], [], [], [$3 ])__int128"], [ rb_cv_type_$1=no])])]) AS_IF([test "${rb_cv_type_$1}" != no], [ - type="${rb_cv_type_$1@%:@@%:@unsigned }" + type="${rb_cv_type_$1@%:@@%:@*signed }" + AS_IF([test "$type" = "long long"], [type=long_long]) AS_IF([test "$type" != yes && eval 'test -n "${ac_cv_sizeof_'$type'+set}"'], [ eval cond='"${ac_cv_sizeof_'$type'}"' AS_CASE([$cond], [*:*], [ From d11df4172ef203425fd7ee773b1e328b14da43c8 Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Mon, 13 Oct 2025 16:48:31 +0200 Subject: [PATCH 0311/2435] [ruby/erb] html_escape: refactor redundant if (https://github.com/ruby/erb/pull/88) https://github.com/ruby/erb/commit/c231ced3f4 --- ext/erb/escape/escape.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c index 9437e9694eda5f..1794fc30ebdce7 100644 --- a/ext/erb/escape/escape.c +++ b/ext/erb/escape/escape.c @@ -63,16 +63,13 @@ optimized_escape_html(VALUE str) dest += len; } } + VALUE escaped = str; if (buf) { size_t segment_len = cstr - segment_start; if (segment_len) { memcpy(dest, segment_start, segment_len); dest += segment_len; } - } - - VALUE escaped = str; - if (buf) { escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); ALLOCV_END(vbuf); From 79b268567512cdb802488107be1dcf843f371076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 13 Oct 2025 16:33:17 +0200 Subject: [PATCH 0312/2435] [DOC] Fix typos Inspired by 42ba82424d908c290a4a34ced8853f0a403b734b, I looked for other occurrences of "the the". --- error.c | 2 +- internal/io.h | 2 +- zjit/src/asm/arm64/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/error.c b/error.c index 2f24929d44ea91..abf50b696aefa6 100644 --- a/error.c +++ b/error.c @@ -1686,7 +1686,7 @@ check_order_keyword(VALUE opt) * - If the value of keyword +order+ is +:top+ (the default), * lists the error message and the innermost backtrace entry first. * - If the value of keyword +order+ is +:bottom+, - * lists the error message the the innermost entry last. + * lists the error message the innermost entry last. * * Example: * diff --git a/internal/io.h b/internal/io.h index e6a741ee71faee..b81774e0a715df 100644 --- a/internal/io.h +++ b/internal/io.h @@ -132,7 +132,7 @@ struct rb_io { struct rb_execution_context_struct *closing_ec; VALUE wakeup_mutex; - // The fork generation of the the blocking operations list. + // The fork generation of the blocking operations list. rb_serial_t fork_generation; }; diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index beb5a4b7bd61c7..ffb83cf16abd03 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -18,7 +18,7 @@ pub use arg::*; pub use opnd::*; /// The extend type for register operands in extended register instructions. -/// It's the reuslt size is determined by the the destination register and +/// It's the result size is determined by the destination register and /// the source size interpreted using the last letter. #[derive(Clone, Copy)] pub enum ExtendType { From 53ca9fbb4c9a307379b0647130564f2ef3ad07c7 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 13 Oct 2025 17:27:50 +0100 Subject: [PATCH 0313/2435] [DOC] Tweaks for String#rjust --- doc/string/rjust.rdoc | 2 ++ string.c | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/string/rjust.rdoc b/doc/string/rjust.rdoc index 24e7bf31595653..39e490b7ccce42 100644 --- a/doc/string/rjust.rdoc +++ b/doc/string/rjust.rdoc @@ -14,3 +14,5 @@ If +size+ is not greater than the size of +self+, returns a copy of +self+: 'hello'.rjust(5, 'ab') # => "hello" 'hello'.rjust(1, 'ab') # => "hello" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index f797dab6516f8d..a0369ecb5542b9 100644 --- a/string.c +++ b/string.c @@ -11181,8 +11181,6 @@ rb_str_ljust(int argc, VALUE *argv, VALUE str) * * :include: doc/string/rjust.rdoc * - * Related: String#ljust, String#center. - * */ static VALUE From 25821f3438fda08b54c7ff1702f0c87ffe6e7217 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 13 Oct 2025 17:33:03 +0100 Subject: [PATCH 0314/2435] [DOC] Tweaks for String#rjust --- doc/string/rjust.rdoc | 6 +++--- string.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/string/rjust.rdoc b/doc/string/rjust.rdoc index 39e490b7ccce42..864cfcce7a0ec0 100644 --- a/doc/string/rjust.rdoc +++ b/doc/string/rjust.rdoc @@ -1,7 +1,7 @@ Returns a right-justified copy of +self+. -If integer argument +size+ is greater than the size (in characters) of +self+, -returns a new string of length +size+ that is a copy of +self+, +If integer argument +width+ is greater than the size (in characters) of +self+, +returns a new string of length +width+ that is a copy of +self+, right justified and padded on the left with +pad_string+: 'hello'.rjust(10) # => " hello" @@ -10,7 +10,7 @@ right justified and padded on the left with +pad_string+: 'тест'.rjust(10) # => " тест" 'こんにちは'.rjust(10) # => " こんにちは" -If +size+ is not greater than the size of +self+, returns a copy of +self+: +If width <= self.size, returns a copy of +self+: 'hello'.rjust(5, 'ab') # => "hello" 'hello'.rjust(1, 'ab') # => "hello" diff --git a/string.c b/string.c index a0369ecb5542b9..fab1509bac0ead 100644 --- a/string.c +++ b/string.c @@ -11177,7 +11177,7 @@ rb_str_ljust(int argc, VALUE *argv, VALUE str) /* * call-seq: - * rjust(size, pad_string = ' ') -> new_string + * rjust(width, pad_string = ' ') -> new_string * * :include: doc/string/rjust.rdoc * From da3336c52b2884640a40adfc8d3a7f6032c4c259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 14 Oct 2025 02:42:03 +0200 Subject: [PATCH 0315/2435] [ruby/strscan] Fix typo (https://github.com/ruby/strscan/pull/164) https://github.com/ruby/strscan/commit/29ad49f89d --- doc/strscan/strscan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/strscan/strscan.md b/doc/strscan/strscan.md index aa7bd8ba847d82..c0bf5413623b07 100644 --- a/doc/strscan/strscan.md +++ b/doc/strscan/strscan.md @@ -204,7 +204,7 @@ put_situation(scanner) ## Target Substring -The target substring is the the part of the [stored string][1] +The target substring is the part of the [stored string][1] that extends from the current [byte position][2] to the end of the stored string; it is always either: From e94a2f691d67ad98be9036e76c765fcfa7d22552 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 14 Oct 2025 12:38:52 +0900 Subject: [PATCH 0316/2435] [Bug #21638] Mark ractor-local `$VERBOSE` and `$DEBUG` https://github.com/sampersand/blog/blob/master/the%20-s%20flag.md#the-segfault --- ractor.c | 2 ++ test/ruby/test_rubyoptions.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ractor.c b/ractor.c index 43a7f6c140e11d..8e7f7d6497fb44 100644 --- a/ractor.c +++ b/ractor.c @@ -216,6 +216,8 @@ ractor_mark(void *ptr) rb_gc_mark(r->r_stdin); rb_gc_mark(r->r_stdout); rb_gc_mark(r->r_stderr); + rb_gc_mark(r->verbose); + rb_gc_mark(r->debug); rb_hook_list_mark(&r->pub.hooks); if (r->threads.cnt > 0) { diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 8126cb3c268b37..527208cee5219d 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -530,6 +530,8 @@ def test_sflag assert_in_out_err(%w(- -#=foo), "#!ruby -s\n", [], /invalid name for global variable - -# \(NameError\)/) + + assert_in_out_err(['-s', '-e', 'GC.start; p $DEBUG', '--', '-DEBUG=x'], "", ['"x"']) end def test_option_missing_argument From e326e22eb83f420982dce32347929f538e764204 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Oct 2025 14:39:42 +0900 Subject: [PATCH 0317/2435] [rubygems/rubygems] Removed deprecated legacy windows platform support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/7d910dd94c Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/dsl.rb | 18 +-- lib/bundler/lockfile_parser.rb | 14 +- spec/bundler/bundler/dsl_spec.rb | 8 +- spec/bundler/bundler/lockfile_parser_spec.rb | 128 ------------------- 4 files changed, 13 insertions(+), 155 deletions(-) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 3bf5dbc1153cf7..d98dbd4759e922 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -411,7 +411,13 @@ def normalize_options(name, version, opts) next if VALID_PLATFORMS.include?(p) raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}" end - deprecate_legacy_windows_platforms(platforms) + + windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } + if windows_platforms.any? + windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") + removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead." + Bundler::SharedHelpers.feature_removed! removed_message + end # Save sources passed in a key if opts.key?("source") @@ -492,16 +498,6 @@ def normalize_source(source) end end - def deprecate_legacy_windows_platforms(platforms) - windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } - return if windows_platforms.empty? - - windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") - message = "Platform #{windows_platforms} is deprecated. Please use platform :windows instead." - removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - end - def check_path_source_safety return if @sources.global_path_source.nil? diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 9ab9d73ae26d3c..07b5bd75147cf9 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -141,18 +141,8 @@ def initialize(lockfile, strict: false) @pos.advance!(line) end - if !Bundler.frozen_bundle? && @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) - if @platforms.include?(Gem::Platform::X64_MINGW) - @platforms.delete(Gem::Platform::X64_MINGW_LEGACY) - SharedHelpers.major_deprecation(2, - "Found x64-mingw32 in lockfile, which is deprecated. Removing it. Support for x64-mingw32 will be removed in Bundler 4.0.", - removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") - else - @platforms[@platforms.index(Gem::Platform::X64_MINGW_LEGACY)] = Gem::Platform::X64_MINGW - SharedHelpers.major_deprecation(2, - "Found x64-mingw32 in lockfile, which is deprecated. Using x64-mingw-ucrt, the replacement for x64-mingw32 in modern rubies, instead. Support for x64-mingw32 will be removed in Bundler 4.0.", - removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") - end + if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) + SharedHelpers.feature_removed!("Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") end @most_specific_locked_platform = @platforms.min_by do |bundle_platform| diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index ac28aea4d7da0d..e88033e9554240 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -221,8 +221,8 @@ to raise_error(Bundler::GemfileError, /is not a valid platform/) end - it "raises a deprecation warning for legacy windows platforms" do - expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, /\APlatform :mswin, :x64_mingw is deprecated/, removed_message: /\APlatform :mswin, :x64_mingw has been removed/) + it "raises an error for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin, :x64_mingw has been removed/) subject.gem("foo", platforms: [:mswin, :jruby, :x64_mingw]) end @@ -291,8 +291,8 @@ end describe "#platforms" do - it "raises a deprecation warning for legacy windows platforms" do - expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, /\APlatform :mswin64, :mingw is deprecated/, removed_message: /\APlatform :mswin64, :mingw has been removed/) + it "raises an error for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin64, :mingw has been removed/) subject.platforms(:mswin64, :jruby, :mingw) do subject.gem("foo") end diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb index 54aa6a0bfe37e7..f38da2c9932183 100644 --- a/spec/bundler/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/bundler/lockfile_parser_spec.rb @@ -95,134 +95,6 @@ end end - describe "X64_MINGW_LEGACY platform handling" do - before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) } - - describe "when X64_MINGW_LEGACY is present alone" do - let(:lockfile_with_legacy_platform) { <<~L } - GEM - remote: https://rubygems.org/ - specs: - rake (10.3.2) - - PLATFORMS - ruby - x64-mingw32 - - DEPENDENCIES - rake - - BUNDLED WITH - 3.6.9 - L - - context "when bundle is not frozen" do - before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } - subject { described_class.new(lockfile_with_legacy_platform) } - - it "replaces X64_MINGW_LEGACY with X64_MINGW" do - allow(Bundler::SharedHelpers).to receive(:major_deprecation) - expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") - expect(subject.platforms.map(&:to_s)).not_to include("x64-mingw32") - end - - it "shows deprecation warning for replacement" do - expect(Bundler::SharedHelpers).to receive(:major_deprecation).with( - 2, - "Found x64-mingw32 in lockfile, which is deprecated. Using x64-mingw-ucrt, the replacement for x64-mingw32 in modern rubies, instead. Support for x64-mingw32 will be removed in Bundler 4.0.", - removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0." - ) - subject - end - end - - context "when bundle is frozen" do - before { allow(Bundler).to receive(:frozen_bundle?).and_return(true) } - subject { described_class.new(lockfile_with_legacy_platform) } - - it "preserves X64_MINGW_LEGACY platform without replacement" do - expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw32") - end - - it "does not show any deprecation warnings" do - expect(Bundler::SharedHelpers).not_to receive(:major_deprecation) - subject - end - end - end - - describe "when both X64_MINGW_LEGACY and X64_MINGW are present" do - let(:lockfile_with_both_platforms) { <<~L } - GEM - remote: https://rubygems.org/ - specs: - rake (10.3.2) - - PLATFORMS - ruby - x64-mingw32 - x64-mingw-ucrt - - DEPENDENCIES - rake - - BUNDLED WITH - 3.6.9 - L - - context "when bundle is not frozen" do - before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } - subject { described_class.new(lockfile_with_both_platforms) } - - it "removes X64_MINGW_LEGACY and keeps X64_MINGW" do - allow(Bundler::SharedHelpers).to receive(:major_deprecation) - expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") - expect(subject.platforms.map(&:to_s)).not_to include("x64-mingw32") - end - - it "shows deprecation warning for removing legacy platform" do - expect(Bundler::SharedHelpers).to receive(:major_deprecation).with( - 2, - "Found x64-mingw32 in lockfile, which is deprecated. Removing it. Support for x64-mingw32 will be removed in Bundler 4.0.", - removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0." - ) - subject - end - end - end - - describe "when no X64_MINGW_LEGACY platform is present" do - let(:lockfile_with_modern_platforms) { <<~L } - GEM - remote: https://rubygems.org/ - specs: - rake (10.3.2) - - PLATFORMS - ruby - x64-mingw-ucrt - - DEPENDENCIES - rake - - BUNDLED WITH - 3.6.9 - L - - before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } - subject { described_class.new(lockfile_with_modern_platforms) } - - it "preserves all modern platforms without changes" do - expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") - end - - it "does not show any deprecation warnings" do - expect(Bundler::SharedHelpers).not_to receive(:major_deprecation) - subject - end - end - end - describe "#initialize" do before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) } subject { described_class.new(lockfile_contents) } From f142d1b598d54b71a85f5ab952a96f9af14cf434 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Oct 2025 15:18:10 +0900 Subject: [PATCH 0318/2435] [rubygems/rubygems] Removed obsoleted examples for legacy windows platform https://github.com/rubygems/rubygems/commit/7b0da18764 --- spec/bundler/commands/cache_spec.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 1e90f01ce7f8c9..283719bedf53b7 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -207,17 +207,6 @@ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "prints an error when using legacy windows rubies" do - gemfile <<-D - source "https://gem.repo1" - gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] - D - - bundle "cache --all-platforms", raise_on_error: false - expect(err).to include("removed") - expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).not_to exist - end - it "does not attempt to install gems in without groups" do build_repo4 do build_gem "uninstallable", "2.0" do |s| From d0b89cab4ed7661cc0acea28ec04793f19ce3953 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Oct 2025 15:20:51 +0900 Subject: [PATCH 0319/2435] [rubygems/rubygems] Added example for legacy windows platform https://github.com/rubygems/rubygems/commit/90130c0648 --- spec/bundler/other/major_deprecation_spec.rb | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index f61dc1bc8859bc..d701c4008dbf4c 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -573,6 +573,38 @@ end end + context "bundle install with a lockfile including X64_MINGW_LEGACY platform" do + before do + gemfile <<~G + source "https://gem.repo1" + gem "rake" + G + + lockfile <<~L + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) + + PLATFORMS + ruby + x64-mingw32 + + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a helpful error" do + bundle "install", raise_on_error: false + + expect(err).to include("Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") + end + end + context "when Bundler.setup is run in a ruby script" do before do create_file "gems.rb", "source 'https://gem.repo1'" From 366e9c55f5b16c9f1dacccf5b54163e5ea42ebd2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Oct 2025 15:58:25 +0900 Subject: [PATCH 0320/2435] [rubygems/rubygems] Bump up to test version for 4.0.0.dev https://github.com/rubygems/rubygems/commit/9d70887185 --- spec/bundler/commands/update_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index f81c5578841dca..299285ba8b01ce 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -1571,12 +1571,12 @@ end it "does not claim to update to Bundler version to a wrong version when cached gems are present" do - pristine_system_gems "bundler-2.99.0" + pristine_system_gems "bundler-4.99.0" build_repo4 do build_gem "myrack", "3.0.9.1" - build_bundler "2.99.0" + build_bundler "4.99.0" end gemfile <<~G From 2002aa3ec295b4323cb10a7e80f057a1e61341f1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Oct 2025 18:12:59 +0900 Subject: [PATCH 0321/2435] [rubygems/rubygems] Removed legacy_check option from SpecSet#for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/376e4ec8c7 Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/spec_set.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 4ae03171dc5813..f9179e7a069334 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -11,16 +11,11 @@ def initialize(specs) @specs = specs end - def for(dependencies, platforms_or_legacy_check = [nil], legacy_platforms = [nil], skips: []) - platforms = if [true, false].include?(platforms_or_legacy_check) - Bundler::SharedHelpers.major_deprecation 2, + def for(dependencies, platforms = [nil], legacy_platforms = [nil], skips: []) + if [true, false].include?(platforms) + Bundler::SharedHelpers.feature_removed! \ "SpecSet#for received a `check` parameter, but that's no longer used and deprecated. " \ - "SpecSet#for always implicitly performs validation. Please remove this parameter", - print_caller_location: true - - legacy_platforms - else - platforms_or_legacy_check + "SpecSet#for always implicitly performs validation. Please remove this parameter" end materialize_dependencies(dependencies, platforms, skips: skips) From 34ee5cbf13d42d2fc48f48cbf787571d0c6e5729 Mon Sep 17 00:00:00 2001 From: Vincent Lin Date: Tue, 14 Oct 2025 12:05:18 +0100 Subject: [PATCH 0322/2435] [DOC] Fix minor typos in YJIT comments (#14829) [DOC] Fix typos in YJIT core --- yjit/src/core.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 2999f151bfb73e..0a14c44ae4d97e 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -2427,7 +2427,7 @@ impl<'a> JITState<'a> { // SAFETY: allocated with Box above unsafe { ptr::write(blockref, block) }; - // Block is initialized now. Note that MaybeUnint has the same layout as T. + // Block is initialized now. Note that MaybeUninit has the same layout as T. let blockref = NonNull::new(blockref as *mut Block).expect("no null from Box"); // Track all the assumptions the block makes as invariants @@ -3797,7 +3797,7 @@ pub fn gen_branch_stub_hit_trampoline(ocb: &mut OutlinedCb) -> Option { let mut asm = Assembler::new_without_iseq(); // For `branch_stub_hit(branch_ptr, target_idx, ec)`, - // `branch_ptr` and `target_idx` is different for each stub, + // `branch_ptr` and `target_idx` are different for each stub, // but the call and what's after is the same. This trampoline // is the unchanging part. // Since this trampoline is static, it allows code GC inside From 25a420351df113e02536e0ec98cf1671e437c772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 13 Oct 2025 16:26:11 +0200 Subject: [PATCH 0323/2435] [rubygems/rubygems] Fix typo https://github.com/rubygems/rubygems/commit/e4f1772d80 --- lib/rubygems/specification_record.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb index 195a35549670ed..d08410096facdd 100644 --- a/lib/rubygems/specification_record.rb +++ b/lib/rubygems/specification_record.rb @@ -73,7 +73,7 @@ def stubs_for_pattern(pattern, match_platform = true) end ## - # Adds +spec+ to the the record, keeping the collection properly sorted. + # Adds +spec+ to the record, keeping the collection properly sorted. def add_spec(spec) return if all.include? spec From 7e07a8d8f662edcf112094750f6e2abed8b84803 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 14 Oct 2025 21:30:30 +0900 Subject: [PATCH 0324/2435] Remove a debug method that is useless now --- namespace.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/namespace.c b/namespace.c index 6a8f4abc9d87bf..bf4f87f1de646f 100644 --- a/namespace.c +++ b/namespace.c @@ -349,20 +349,6 @@ rb_namespace_s_current(VALUE recv) return ns->ns_object; } -/* - * call-seq: - * Namespace.is_builtin?(klass) -> true or false - * - * Returns +true+ if +klass+ is only in a user namespace. - */ -static VALUE -rb_namespace_s_is_builtin_p(VALUE recv, VALUE klass) -{ - if (RCLASS_PRIME_CLASSEXT_READABLE_P(klass) && !RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)) - return Qtrue; - return Qfalse; -} - /* * call-seq: * load_path -> array @@ -1056,7 +1042,6 @@ Init_Namespace(void) rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_s_current, 0); - rb_define_singleton_method(rb_cNamespace, "is_builtin?", rb_namespace_s_is_builtin_p, 1); rb_define_method(rb_cNamespace, "load_path", rb_namespace_load_path, 0); rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1); From 9743b51806a8dc6843444e42f8e838831da99bcd Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 14 Oct 2025 21:42:55 +0900 Subject: [PATCH 0325/2435] Define main.to_s even in namespaces It just shows "main" just like the main object without namespace. All main objects in namespaces will show "main" and it is impossible to determine a main from main objects if it returns "main". But it's not a problem because we don't define anything on main objects usually. --- namespace.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/namespace.c b/namespace.c index bf4f87f1de646f..c8c947f50cd8ba 100644 --- a/namespace.c +++ b/namespace.c @@ -118,6 +118,12 @@ namespace_generate_id(void) return id; } +static VALUE +namespace_main_to_s(VALUE obj) +{ + return rb_str_new2("main"); +} + static void namespace_entry_initialize(rb_namespace_t *ns) { @@ -128,9 +134,8 @@ namespace_entry_initialize(rb_namespace_t *ns) ns->ns_id = 0; ns->top_self = rb_obj_alloc(rb_cObject); - // TODO: - // rb_define_singleton_method(rb_vm_top_self(), "to_s", main_to_s, 0); - // rb_define_alias(rb_singleton_class(rb_vm_top_self()), "inspect", "to_s"); + rb_define_singleton_method(ns->top_self, "to_s", namespace_main_to_s, 0); + rb_define_alias(rb_singleton_class(ns->top_self), "inspect", "to_s"); ns->load_path = rb_ary_dup(root->load_path); ns->expanded_load_path = rb_ary_dup(root->expanded_load_path); ns->load_path_snapshot = rb_ary_new(); From d60ee6fb7c3ce92963f52b16294c2be61549fd9f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 14 Oct 2025 22:02:41 +0900 Subject: [PATCH 0326/2435] Remove a comment - we cannot remove this method now probably --- class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class.c b/class.c index 7baaa5e715f044..84c8668b6be3a3 100644 --- a/class.c +++ b/class.c @@ -480,7 +480,7 @@ rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) } void -rb_class_remove_subclass_head(VALUE klass) // TODO: check this is still used and required +rb_class_remove_subclass_head(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); rb_class_classext_free_subclasses(ext, klass); From 29adf0bb74670e06c0aa49cbd49012c74edea895 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 14 Oct 2025 22:03:13 +0900 Subject: [PATCH 0327/2435] Split gvar space between root and main namespaces --- variable.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variable.c b/variable.c index bb3811a81c5425..c8565047a46e4e 100644 --- a/variable.c +++ b/variable.c @@ -999,7 +999,7 @@ rb_gvar_set_entry(struct rb_global_entry *entry, VALUE val) } #define USE_NAMESPACE_GVAR_TBL(ns,entry) \ - (NAMESPACE_OPTIONAL_P(ns) && \ + (NAMESPACE_USER_P(ns) && \ (!entry || !entry->var->namespace_ready || entry->var->setter != rb_gvar_readonly_setter)) VALUE @@ -1012,7 +1012,6 @@ rb_gvar_set(ID id, VALUE val) RB_VM_LOCKING() { entry = rb_global_entry(id); - // TODO: consider root/main namespaces if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); retval = val; From cad692de63d59ef365bce96e78f1baf137919590 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 14 Oct 2025 22:07:22 +0900 Subject: [PATCH 0328/2435] Remove useless comments Namespace frame exists, but it is used only for Namespace#eval now. --- vm_eval.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/vm_eval.c b/vm_eval.c index 81b6bed7257abf..71656f5a0f2ab1 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -2000,7 +2000,6 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li cref = vm_cref_dup(orig_cref); } vm_set_eval_stack(ec, iseq, cref, &block); - // TODO: set the namespace frame /* kick */ return vm_exec(ec); @@ -2023,8 +2022,6 @@ eval_string_with_scope(VALUE scope, VALUE src, VALUE file, int line) vm_bind_update_env(scope, bind, vm_make_env_object(ec, ec->cfp)); } - // TODO: set the namespace frame - /* kick */ return vm_exec(ec); } From 55e76b4cc969b0ff6ece433c13fae8084744acf4 Mon Sep 17 00:00:00 2001 From: Jason Garber Date: Tue, 14 Oct 2025 10:44:52 -0400 Subject: [PATCH 0329/2435] [ruby/erb] Add `changelog_uri` to spec metadata (https://github.com/ruby/erb/pull/89) This project's `NEWS.md` file appears to be the closest thing to a changelog file that I could find. The change here links to the file in the released version's branch. RubyGems.org will use this metadata to display a link to this file on the gem's release pages. https://github.com/ruby/erb/commit/85a4f10332 --- lib/erb/erb.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/erb/erb.gemspec b/lib/erb/erb.gemspec index 94edc686825419..3793e5d70fac87 100644 --- a/lib/erb/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |spec| spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md" spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } From de310176c25bfb82e972025c1ea388228bcea159 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 14 Oct 2025 11:13:05 -0400 Subject: [PATCH 0330/2435] ZJIT: Inline well-known C functions into HIR (#14679) Add the ability to create a Rust function that "open-codes" HIR implementations of specific well-known C functions, starting with `String#to_s`. It supports emitting multiple instructions into a temporary block, but does not support emitting *new blocks* (yet?). Fix https://github.com/Shopify/ruby/issues/792 --- zjit/src/cruby_methods.rs | 47 ++++++- zjit/src/hir.rs | 281 ++++++++++++++++++++++++++++---------- 2 files changed, 253 insertions(+), 75 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 7467cb50c85446..77fd22fcd02fbd 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -12,6 +12,7 @@ use crate::cruby::*; use std::collections::HashMap; use std::ffi::c_void; use crate::hir_type::{types, Type}; +use crate::hir; pub struct Annotations { cfuncs: HashMap<*mut c_void, FnProperties>, @@ -29,6 +30,7 @@ pub struct FnProperties { pub return_type: Type, /// Whether it's legal to remove the call if the result is unused pub elidable: bool, + pub inline: fn(&mut hir::Function, hir::BlockId, hir::InsnId, &[hir::InsnId], hir::InsnId) -> Option, } /// A safe default for un-annotated Ruby methods: we can't optimize them or their returned values. @@ -39,6 +41,7 @@ impl Default for FnProperties { leaf: false, return_type: types::BasicObject, elidable: false, + inline: no_inline, } } } @@ -152,9 +155,13 @@ pub fn init() -> Annotations { let builtin_funcs = &mut HashMap::new(); macro_rules! annotate { + ($module:ident, $method_name:literal, $inline:ident) => { + let props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: types::BasicObject, inline: $inline }; + annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); + }; ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { #[allow(unused_mut)] - let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type }; + let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: no_inline }; $( props.$properties = true; )* @@ -171,14 +178,15 @@ pub fn init() -> Annotations { no_gc: false, leaf: false, elidable: false, - return_type: $return_type + return_type: $return_type, + inline: no_inline, }; $(props.$properties = true;)+ annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); } } - annotate!(rb_mKernel, "itself", types::BasicObject, no_gc, leaf, elidable); + annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); @@ -188,12 +196,14 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); + annotate!(rb_cArray, "[]", inline_array_aref); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", types::NilClass, no_gc, leaf, elidable); + annotate!(rb_cString, "to_s", inline_string_to_s); annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); @@ -204,3 +214,34 @@ pub fn init() -> Annotations { builtin_funcs: std::mem::take(builtin_funcs), } } + +fn no_inline(_fun: &mut hir::Function, _block: hir::BlockId, _recv: hir::InsnId, _args: &[hir::InsnId], _state: hir::InsnId) -> Option { + None +} + +fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if args.len() == 0 && fun.likely_a(recv, types::StringExact, state) { + let recv = fun.coerce_to(block, recv, types::StringExact, state); + return Some(recv); + } + None +} + +fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if args.len() == 0 { + // No need to coerce the receiver; that is done by the SendWithoutBlock rewriting. + return Some(recv); + } + None +} + +fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if let &[index] = args { + if fun.likely_a(index, types::Fixnum, state) { + let index = fun.coerce_to(block, index, types::Fixnum, state); + let result = fun.push_insn(block, hir::Insn::ArrayArefFixnum { array: recv, index }); + return Some(result); + } + } + None +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8b04143f840deb..2c07c21ac9a007 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1361,7 +1361,7 @@ impl Function { } // Add an instruction to an SSA block - fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { + pub fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { let is_param = matches!(insn, Insn::Param { .. }); let id = self.new_insn(insn); if is_param { @@ -1401,6 +1401,13 @@ impl Function { id } + fn remove_block(&mut self, block_id: BlockId) { + if BlockId(self.blocks.len() - 1) != block_id { + panic!("Can only remove the last block"); + } + self.blocks.pop(); + } + /// Return a reference to the Block at the given index. pub fn block(&self, block_id: BlockId) -> &Block { &self.blocks[block_id.0] @@ -1882,6 +1889,23 @@ impl Function { None } + pub fn likely_a(&self, val: InsnId, ty: Type, state: InsnId) -> bool { + if self.type_of(val).is_subtype(ty) { + return true; + } + let frame_state = self.frame_state(state); + let iseq_insn_idx = frame_state.insn_idx as usize; + let Some(profiled_type) = self.profiled_type_of_at(val, iseq_insn_idx) else { + return false; + }; + Type::from_profiled_type(profiled_type).is_subtype(ty) + } + + pub fn coerce_to(&mut self, block: BlockId, val: InsnId, guard_type: Type, state: InsnId) -> InsnId { + if self.is_a(val, guard_type) { return val; } + self.push_insn(block, Insn::GuardType { val, guard_type, state }) + } + fn likely_is_fixnum(&self, val: InsnId, profiled_type: ProfiledType) -> bool { self.is_a(val, types::Fixnum) || profiled_type.is_fixnum() } @@ -1958,40 +1982,6 @@ impl Function { } } - fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId, state: InsnId) { - if !unsafe { rb_BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) } { - // If the basic operation is already redefined, we cannot optimize it. - self.push_insn_id(block, orig_insn_id); - return; - } - let self_type = self.type_of(self_val); - let idx_type = self.type_of(idx_val); - if self_type.is_subtype(types::ArrayExact) { - if let Some(array_obj) = self_type.ruby_object() { - if array_obj.is_frozen() { - if let Some(idx) = idx_type.fixnum_value() { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); - let val = unsafe { rb_yarv_ary_entry_internal(array_obj, idx) }; - let const_insn = self.push_insn(block, Insn::Const { val: Const::Value(val) }); - self.make_equal_to(orig_insn_id, const_insn); - return; - } - } - } - if self.type_of(idx_val).is_subtype(types::Fixnum) { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); - let fixnum_idx = self.push_insn(block, Insn::GuardType { val: idx_val, guard_type: types::Fixnum, state }); - let result = self.push_insn(block, Insn::ArrayArefFixnum { - array: self_val, - index: fixnum_idx, - }); - self.make_equal_to(orig_insn_id, result); - return; - } - } - self.push_insn_id(block, orig_insn_id); - } - /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. /// Also try and inline constant caches, specialize object allocations, and more. @@ -2031,8 +2021,6 @@ impl Function { self.try_rewrite_freeze(block, insn_id, recv, state), Insn::SendWithoutBlock { recv, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(minusat) && args.is_empty() => self.try_rewrite_uminus(block, insn_id, recv, state), - Insn::SendWithoutBlock { recv, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(aref) && args.len() == 1 => - self.try_rewrite_aref(block, insn_id, recv, args[0], state), Insn::SendWithoutBlock { mut recv, cd, args, state, .. } => { let frame_state = self.frame_state(state); let (klass, profiled_type) = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { @@ -2392,19 +2380,35 @@ impl Function { if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } + + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); + if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); + } + + // Try inlining the cfunc into HIR + let tmp_block = fun.new_block(u32::MAX); + if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { + // Copy contents of tmp_block to block + assert_ne!(block, tmp_block); + let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + fun.blocks[block.0].insns.extend(insns); + fun.make_equal_to(send_insn_id, replacement); + fun.remove_block(tmp_block); + return Ok(()); } + + // No inlining; emit a call let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); - - let props = ZJITState::get_method_annotations().get_cfunc_properties(method); - if props.is_none() && get_option!(stats) { - count_not_annotated_cfunc(fun, block, method); - } - let props = props.unwrap_or_default(); let return_type = props.return_type; let elidable = props.elidable; // Filter for a leaf and GC free function @@ -2427,9 +2431,6 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - if get_option!(stats) { - count_not_inlined_cfunc(fun, block, method); - } gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { @@ -2446,6 +2447,23 @@ impl Function { count_not_annotated_cfunc(fun, block, method); } let props = props.unwrap_or_default(); + + // Try inlining the cfunc into HIR + let tmp_block = fun.new_block(u32::MAX); + if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { + // Copy contents of tmp_block to block + assert_ne!(block, tmp_block); + let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + fun.blocks[block.0].insns.extend(insns); + fun.make_equal_to(send_insn_id, replacement); + fun.remove_block(tmp_block); + return Ok(()); + } + + // No inlining; emit a call + if get_option!(stats) { + count_not_inlined_cfunc(fun, block, method); + } let return_type = props.return_type; let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCallVariadic { @@ -2609,6 +2627,13 @@ impl Function { _ => None, }) } + Insn::ArrayArefFixnum { array, index } if self.type_of(array).ruby_object_known() + && self.type_of(index).ruby_object_known() => { + let array_obj = self.type_of(array).ruby_object().unwrap(); + let index = self.type_of(index).fixnum_value().unwrap(); + let val = unsafe { rb_yarv_ary_entry_internal(array_obj, index) }; + self.new_insn(Insn::Const { val: Const::Value(val) }) + } Insn::Test { val } if self.type_of(val).is_known_falsy() => { self.new_insn(Insn::Const { val: Const::CBool(false) }) } @@ -9183,7 +9208,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallVariadic []@0x1038, v26, v13 + v27:BasicObject = ArrayArefFixnum v26, v13 CheckInterrupts Return v27 "); @@ -9860,9 +9885,8 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) v22:Fixnum = GuardType v9, Fixnum - v23:BasicObject = CCall itself@0x1038, v22 CheckInterrupts - Return v23 + Return v22 "); } @@ -9884,9 +9908,8 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v22:BasicObject = CCall itself@0x1038, v11 CheckInterrupts - Return v22 + Return v11 "); } @@ -9913,7 +9936,6 @@ mod opt_tests { v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:BasicObject = CCall itself@0x1038, v14 PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -11528,9 +11550,8 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) CheckInterrupts PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v34:BasicObject = CCall itself@0x1038, v13 CheckInterrupts - Return v34 + Return v13 "); } @@ -11552,10 +11573,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:Fixnum[5] = Const Value(5) CheckInterrupts - Return v24 + Return v27 "); } @@ -11577,10 +11599,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-3] = Const Value(-3) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:Fixnum[4] = Const Value(4) CheckInterrupts - Return v24 + Return v27 "); } @@ -11602,10 +11625,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-10] = Const Value(-10) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v24 + Return v27 "); } @@ -11627,10 +11651,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v24 + Return v27 "); } @@ -11655,9 +11680,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - v17:BasicObject = SendWithoutBlock v12, :[], v13 + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 CheckInterrupts - Return v17 + Return v25 "); } @@ -12618,14 +12645,62 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:StringExact = CCallWithFrame to_s@0x1040, v12 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_string_literal_to_s() { + eval(r#" + def test = "foo".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_profiled_string_to_s() { + eval(r#" + def test(o) = o.to_s + test "foo" + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact CheckInterrupts Return v23 "); } #[test] - fn test_array_aref_fixnum() { + fn test_array_aref_fixnum_literal() { eval(" def test arr = [1, 2, 3] @@ -12647,8 +12722,70 @@ mod opt_tests { v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v15:ArrayExact = ArrayDup v13 v18:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v30:BasicObject = ArrayArefFixnum v15, v18 + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v31:BasicObject = ArrayArefFixnum v15, v18 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_array_aref_fixnum_profiled() { + eval(" + def test(arr, idx) + arr[idx] + end + test([1, 2, 3], 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v11, ArrayExact + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_array_aref_fixnum_array_subclass() { + eval(" + class C < Array; end + def test(arr, idx) + arr[idx] + end + test(C.new([1, 2, 3]), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 CheckInterrupts Return v30 "); From d1442727dd8dc6ae62d0addac2aba3bd8602430f Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 14 Oct 2025 13:36:50 -0400 Subject: [PATCH 0331/2435] ZJIT: Don't push Ruby frame for Thread#current (#14832) Fix https://github.com/Shopify/ruby/issues/795 --- zjit/src/cruby_methods.rs | 3 +++ zjit/src/hir.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 77fd22fcd02fbd..bc8f1d3b847501 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -165,6 +165,7 @@ pub fn init() -> Annotations { $( props.$properties = true; )* + #[allow(unused_unsafe)] annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); } } @@ -204,6 +205,8 @@ pub fn init() -> Annotations { annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", types::NilClass, no_gc, leaf, elidable); annotate!(rb_cString, "to_s", inline_string_to_s); + let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; + annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2c07c21ac9a007..a032d9ec8a30a7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12790,4 +12790,31 @@ mod opt_tests { Return v30 "); } + + #[test] + fn test_optimize_thread_current() { + eval(" + def test = Thread.current + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Thread) + v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + v25:BasicObject = CCall current@0x1048, v21 + CheckInterrupts + Return v25 + "); + } } From 8baf170e936525bbda4838db5bbfb138cf724229 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 14 Oct 2025 14:21:59 -0400 Subject: [PATCH 0332/2435] ZJIT: `mem::take` instead of `drain` then `collect` Gets rid of one transient vec copy/allocation. --- zjit/src/hir.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a032d9ec8a30a7..e32c15702eb36e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2398,7 +2398,7 @@ impl Function { if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { // Copy contents of tmp_block to block assert_ne!(block, tmp_block); - let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns); fun.blocks[block.0].insns.extend(insns); fun.make_equal_to(send_insn_id, replacement); fun.remove_block(tmp_block); @@ -2453,7 +2453,7 @@ impl Function { if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { // Copy contents of tmp_block to block assert_ne!(block, tmp_block); - let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns); fun.blocks[block.0].insns.extend(insns); fun.make_equal_to(send_insn_id, replacement); fun.remove_block(tmp_block); From d75207d0043c56034e68c306f6dfe5e7b4f09438 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 14 Oct 2025 15:09:53 -0400 Subject: [PATCH 0333/2435] ZJIT: Profile opt_ltlt and opt_aset (#14834) These bring `send_without_block_no_profiles` numbers down dramatically. On lobsters: Before: send_without_block_no_profiles: 3,466,375 After: send_without_block_no_profiles: 1,293,375 all stats before: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (70.4% of total 14,174,061): Hash#[]: 4,519,771 (31.9%) Kernel#is_a?: 1,030,757 ( 7.3%) Regexp#match?: 399,885 ( 2.8%) String#empty?: 353,775 ( 2.5%) Hash#key?: 349,125 ( 2.5%) Hash#[]=: 344,348 ( 2.4%) String#start_with?: 334,961 ( 2.4%) Kernel#respond_to?: 316,527 ( 2.2%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.7%) TrueClass#===: 235,770 ( 1.7%) FalseClass#===: 231,143 ( 1.6%) Array#include?: 211,383 ( 1.5%) Hash#fetch: 204,702 ( 1.4%) Kernel#block_given?: 181,793 ( 1.3%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.3%) Kernel#dup: 179,341 ( 1.3%) BasicObject#!=: 175,996 ( 1.2%) Class#new: 168,079 ( 1.2%) Kernel#kind_of?: 165,600 ( 1.2%) String#==: 157,734 ( 1.1%) Top-20 not annotated C methods (71.1% of total 14,336,035): Hash#[]: 4,519,781 (31.5%) Kernel#is_a?: 1,212,647 ( 8.5%) Regexp#match?: 399,885 ( 2.8%) String#empty?: 361,013 ( 2.5%) Hash#key?: 349,125 ( 2.4%) Hash#[]=: 344,348 ( 2.4%) String#start_with?: 334,961 ( 2.3%) Kernel#respond_to?: 316,527 ( 2.2%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.7%) TrueClass#===: 235,770 ( 1.6%) FalseClass#===: 231,143 ( 1.6%) Array#include?: 211,383 ( 1.5%) Hash#fetch: 204,702 ( 1.4%) Kernel#block_given?: 191,662 ( 1.3%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.3%) Kernel#dup: 179,348 ( 1.3%) BasicObject#!=: 176,180 ( 1.2%) Class#new: 168,079 ( 1.2%) Kernel#kind_of?: 165,634 ( 1.2%) String#==: 163,666 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,536,895): iseq: 2,281,897 (50.3%) bmethod: 985,679 (21.7%) optimized: 952,914 (21.0%) alias: 310,745 ( 6.8%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,123): invokesuper: 2,373,396 (55.3%) invokeblock: 811,891 (18.9%) sendforward: 505,449 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,403 ( 1.7%) opt_minus: 36,227 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 27,795,022): send_without_block_polymorphic: 9,505,835 (34.2%) send_no_profiles: 5,894,763 (21.2%) send_without_block_not_optimized_method_type: 4,536,895 (16.3%) not_optimized_instruction: 4,293,123 (15.4%) send_without_block_no_profiles: 3,466,407 (12.5%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,918 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,482): expandarray: 328,490 (47.6%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,901 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 48,651 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,752,391): register_spill_on_alloc: 3,457,680 (92.1%) register_spill_on_ccall: 176,348 ( 4.7%) exception_handler: 118,363 ( 3.2%) Top-14 side exit reasons (100.0% of total 10,852,021): compile_error: 3,752,391 (34.6%) guard_type_failure: 2,630,877 (24.2%) guard_shape_failure: 1,917,208 (17.7%) unhandled_yarv_insn: 690,482 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,784 ( 4.9%) unhandled_kwarg: 421,989 ( 3.9%) patchpoint: 369,799 ( 3.4%) unknown_newarray_send: 314,786 ( 2.9%) unhandled_splat: 122,062 ( 1.1%) unhandled_hir_insn: 76,394 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) obj_to_string_fallback: 566 ( 0.0%) interrupt: 468 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,989,407 dynamic_send_count: 27,795,022 (41.5%) optimized_send_count: 39,194,385 (58.5%) iseq_optimized_send_count: 18,060,194 (27.0%) inline_cfunc_optimized_send_count: 6,960,130 (10.4%) non_variadic_cfunc_optimized_send_count: 11,523,682 (17.2%) variadic_cfunc_optimized_send_count: 2,650,379 ( 4.0%) dynamic_getivar_count: 7,365,982 dynamic_setivar_count: 7,245,929 compiled_iseq_count: 4,795 failed_iseq_count: 449 compile_time: 846ms profile_time: 12ms gc_time: 9ms invalidation_time: 61ms vm_write_pc_count: 64,326,442 vm_write_sp_count: 62,982,524 vm_write_locals_count: 62,982,524 vm_write_stack_count: 62,982,524 vm_write_to_parent_iseq_local_count: 292,448 vm_read_from_parent_iseq_local_count: 6,471,353 code_region_bytes: 22,708,224 side_exit_count: 10,852,021 total_insn_count: 517,550,288 vm_insn_count: 162,946,459 zjit_insn_count: 354,603,829 ratio_in_zjit: 68.5% ``` all stats after: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (71.1% of total 15,575,343): Hash#[]: 4,519,778 (29.0%) Kernel#is_a?: 1,030,758 ( 6.6%) String#<<: 851,931 ( 5.5%) Hash#[]=: 742,938 ( 4.8%) Regexp#match?: 399,886 ( 2.6%) String#empty?: 353,775 ( 2.3%) Hash#key?: 349,127 ( 2.2%) String#start_with?: 334,961 ( 2.2%) Kernel#respond_to?: 316,529 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,380 ( 1.4%) Hash#fetch: 204,701 ( 1.3%) Kernel#block_given?: 181,792 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,341 ( 1.2%) BasicObject#!=: 175,997 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) Top-20 not annotated C methods (71.6% of total 15,737,486): Hash#[]: 4,519,788 (28.7%) Kernel#is_a?: 1,212,649 ( 7.7%) String#<<: 851,931 ( 5.4%) Hash#[]=: 743,117 ( 4.7%) Regexp#match?: 399,886 ( 2.5%) String#empty?: 361,013 ( 2.3%) Hash#key?: 349,127 ( 2.2%) String#start_with?: 334,961 ( 2.1%) Kernel#respond_to?: 316,529 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,380 ( 1.3%) Hash#fetch: 204,701 ( 1.3%) Kernel#block_given?: 191,661 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,348 ( 1.1%) BasicObject#!=: 176,181 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,650): iseq: 2,271,911 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,696 (21.0%) alias: 310,747 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,126): invokesuper: 2,373,395 (55.3%) invokeblock: 811,894 (18.9%) sendforward: 505,449 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,403 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,824,512): send_without_block_polymorphic: 9,721,725 (37.6%) send_no_profiles: 5,894,761 (22.8%) send_without_block_not_optimized_method_type: 4,523,650 (17.5%) not_optimized_instruction: 4,293,126 (16.6%) send_without_block_no_profiles: 1,293,404 ( 5.0%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,482): expandarray: 328,490 (47.6%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,901 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 48,651 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,752,504): register_spill_on_alloc: 3,457,793 (92.1%) register_spill_on_ccall: 176,348 ( 4.7%) exception_handler: 118,363 ( 3.2%) Top-14 side exit reasons (100.0% of total 10,860,754): compile_error: 3,752,504 (34.6%) guard_type_failure: 2,638,901 (24.3%) guard_shape_failure: 1,917,198 (17.7%) unhandled_yarv_insn: 690,482 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,785 ( 4.9%) unhandled_kwarg: 421,947 ( 3.9%) patchpoint: 370,447 ( 3.4%) unknown_newarray_send: 314,786 ( 2.9%) unhandled_splat: 122,065 ( 1.1%) unhandled_hir_insn: 76,395 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) obj_to_string_fallback: 566 ( 0.0%) interrupt: 463 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,945,926 dynamic_send_count: 25,824,512 (38.6%) optimized_send_count: 41,121,414 (61.4%) iseq_optimized_send_count: 18,587,430 (27.8%) inline_cfunc_optimized_send_count: 6,958,641 (10.4%) non_variadic_cfunc_optimized_send_count: 12,911,166 (19.3%) variadic_cfunc_optimized_send_count: 2,664,177 ( 4.0%) dynamic_getivar_count: 7,365,985 dynamic_setivar_count: 7,245,942 compiled_iseq_count: 4,794 failed_iseq_count: 450 compile_time: 852ms profile_time: 13ms gc_time: 11ms invalidation_time: 63ms vm_write_pc_count: 64,284,194 vm_write_sp_count: 62,940,427 vm_write_locals_count: 62,940,427 vm_write_stack_count: 62,940,427 vm_write_to_parent_iseq_local_count: 292,447 vm_read_from_parent_iseq_local_count: 6,470,931 code_region_bytes: 23,019,520 side_exit_count: 10,860,754 total_insn_count: 517,576,267 vm_insn_count: 163,188,187 zjit_insn_count: 354,388,080 ratio_in_zjit: 68.5% ``` --- insns.def | 2 ++ zjit/src/cruby_bindings.inc.rs | 14 ++++---- zjit/src/hir.rs | 59 ++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 2 ++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/insns.def b/insns.def index eef0d3f5dc1124..b895bffe222f37 100644 --- a/insns.def +++ b/insns.def @@ -1470,6 +1470,7 @@ opt_ltlt * string. Then what happens if that codepoint does not exist in the * string's encoding? Of course an exception. That's not a leaf. */ // attr bool leaf = false; /* has "invalid codepoint" exception */ +// attr bool zjit_profile = true; { val = vm_opt_ltlt(recv, obj); @@ -1537,6 +1538,7 @@ opt_aset /* This is another story than opt_aref. When vm_opt_aset() resorts * to rb_hash_aset(), which should call #hash for `obj`. */ // attr bool leaf = false; /* has rb_funcall() */ /* calls #hash */ +// attr bool zjit_profile = true; { val = vm_opt_aset(recv, obj, set); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index ea1bf68acc4173..6e3ae05194fa3b 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -694,12 +694,14 @@ pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 229; pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 230; pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 231; pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 232; -pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 233; -pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 236; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 237; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 239; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 240; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e32c15702eb36e..022451e8ab4c77 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12817,4 +12817,63 @@ mod opt_tests { Return v25 "); } + + #[test] + fn test_optimize_array_aset() { + eval(" + def test(arr) + arr[1] = 10 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[1] = Const Value(1) + v15:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v9, ArrayExact + v29:BasicObject = CCallVariadic []=@0x1038, v28, v14, v15 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_optimize_array_ltlt() { + eval(" + def test(arr) + arr << 1 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallWithFrame <<@0x1038, v26, v13 + CheckInterrupts + Return v27 + "); + } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 67f2fdc7403822..c2c35f687b37d4 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -74,6 +74,8 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_or => profile_operands(profiler, profile, 2), YARVINSN_opt_empty_p => profile_operands(profiler, profile, 1), YARVINSN_opt_aref => profile_operands(profiler, profile, 2), + YARVINSN_opt_ltlt => profile_operands(profiler, profile, 2), + YARVINSN_opt_aset => profile_operands(profiler, profile, 3), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), YARVINSN_objtostring => profile_operands(profiler, profile, 1), From de9298635dc7dd212c9d80db404f20855b1426af Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 14 Oct 2025 16:17:54 -0400 Subject: [PATCH 0334/2435] ZJIT: Profile opt_size, opt_length, opt_regexpmatch2 (#14837) These bring `send_without_block_no_profiles` numbers down more. On lobsters: Before: send_without_block_no_profiles: 1,293,375 After: send_without_block_no_profiles: 998,724 all stats before: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (71.1% of total 15,575,335): Hash#[]: 4,519,774 (29.0%) Kernel#is_a?: 1,030,758 ( 6.6%) String#<<: 851,929 ( 5.5%) Hash#[]=: 742,941 ( 4.8%) Regexp#match?: 399,889 ( 2.6%) String#empty?: 353,775 ( 2.3%) Hash#key?: 349,129 ( 2.2%) String#start_with?: 334,961 ( 2.2%) Kernel#respond_to?: 316,527 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,381 ( 1.4%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 181,792 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,340 ( 1.2%) BasicObject#!=: 175,997 ( 1.1%) Class#new: 168,078 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) Top-20 not annotated C methods (71.6% of total 15,737,478): Hash#[]: 4,519,784 (28.7%) Kernel#is_a?: 1,212,649 ( 7.7%) String#<<: 851,929 ( 5.4%) Hash#[]=: 743,120 ( 4.7%) Regexp#match?: 399,889 ( 2.5%) String#empty?: 361,013 ( 2.3%) Hash#key?: 349,129 ( 2.2%) String#start_with?: 334,961 ( 2.1%) Kernel#respond_to?: 316,527 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,381 ( 1.3%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 191,661 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,347 ( 1.1%) BasicObject#!=: 176,181 ( 1.1%) Class#new: 168,078 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,648): iseq: 2,271,904 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,702 (21.0%) alias: 310,746 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,096): invokesuper: 2,373,391 (55.3%) invokeblock: 811,872 (18.9%) sendforward: 505,448 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,403 ( 1.7%) opt_minus: 36,225 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,824,463): send_without_block_polymorphic: 9,721,727 (37.6%) send_no_profiles: 5,894,760 (22.8%) send_without_block_not_optimized_method_type: 4,523,648 (17.5%) not_optimized_instruction: 4,293,096 (16.6%) send_without_block_no_profiles: 1,293,386 ( 5.0%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,482): expandarray: 328,490 (47.6%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,901 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 48,651 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,752,502): register_spill_on_alloc: 3,457,791 (92.1%) register_spill_on_ccall: 176,348 ( 4.7%) exception_handler: 118,363 ( 3.2%) Top-14 side exit reasons (100.0% of total 10,860,787): compile_error: 3,752,502 (34.6%) guard_type_failure: 2,638,903 (24.3%) guard_shape_failure: 1,917,195 (17.7%) unhandled_yarv_insn: 690,482 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,787 ( 4.9%) unhandled_kwarg: 421,943 ( 3.9%) patchpoint: 370,449 ( 3.4%) unknown_newarray_send: 314,785 ( 2.9%) unhandled_splat: 122,060 ( 1.1%) unhandled_hir_insn: 76,396 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) obj_to_string_fallback: 566 ( 0.0%) interrupt: 504 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,945,801 dynamic_send_count: 25,824,463 (38.6%) optimized_send_count: 41,121,338 (61.4%) iseq_optimized_send_count: 18,587,368 (27.8%) inline_cfunc_optimized_send_count: 6,958,635 (10.4%) non_variadic_cfunc_optimized_send_count: 12,911,155 (19.3%) variadic_cfunc_optimized_send_count: 2,664,180 ( 4.0%) dynamic_getivar_count: 7,365,975 dynamic_setivar_count: 7,245,897 compiled_iseq_count: 4,794 failed_iseq_count: 450 compile_time: 760ms profile_time: 9ms gc_time: 8ms invalidation_time: 55ms vm_write_pc_count: 64,284,053 vm_write_sp_count: 62,940,297 vm_write_locals_count: 62,940,297 vm_write_stack_count: 62,940,297 vm_write_to_parent_iseq_local_count: 292,446 vm_read_from_parent_iseq_local_count: 6,470,923 code_region_bytes: 23,019,520 side_exit_count: 10,860,787 total_insn_count: 517,576,320 vm_insn_count: 163,188,910 zjit_insn_count: 354,387,410 ratio_in_zjit: 68.5% ``` all stats after: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (70.4% of total 15,740,856): Hash#[]: 4,519,792 (28.7%) Kernel#is_a?: 1,030,776 ( 6.5%) String#<<: 851,940 ( 5.4%) Hash#[]=: 742,914 ( 4.7%) Regexp#match?: 399,887 ( 2.5%) String#empty?: 353,775 ( 2.2%) Hash#key?: 349,139 ( 2.2%) String#start_with?: 334,961 ( 2.1%) Kernel#respond_to?: 316,529 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,381 ( 1.3%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 181,788 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,341 ( 1.1%) BasicObject#!=: 175,996 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) Top-20 not annotated C methods (70.9% of total 15,902,999): Hash#[]: 4,519,802 (28.4%) Kernel#is_a?: 1,212,667 ( 7.6%) String#<<: 851,940 ( 5.4%) Hash#[]=: 743,093 ( 4.7%) Regexp#match?: 399,887 ( 2.5%) String#empty?: 361,013 ( 2.3%) Hash#key?: 349,139 ( 2.2%) String#start_with?: 334,961 ( 2.1%) Kernel#respond_to?: 316,529 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.5%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,381 ( 1.3%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 191,657 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.1%) Kernel#dup: 179,348 ( 1.1%) BasicObject#!=: 176,180 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.0%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,637): iseq: 2,271,900 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,695 (21.0%) alias: 310,746 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,128): invokesuper: 2,373,401 (55.3%) invokeblock: 811,890 (18.9%) sendforward: 505,449 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,403 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,530,605): send_without_block_polymorphic: 9,722,499 (38.1%) send_no_profiles: 5,894,763 (23.1%) send_without_block_not_optimized_method_type: 4,523,637 (17.7%) not_optimized_instruction: 4,293,128 (16.8%) send_without_block_no_profiles: 998,732 ( 3.9%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,482): expandarray: 328,490 (47.6%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,901 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 48,651 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,752,500): register_spill_on_alloc: 3,457,792 (92.1%) register_spill_on_ccall: 176,348 ( 4.7%) exception_handler: 118,360 ( 3.2%) Top-14 side exit reasons (100.0% of total 10,860,797): compile_error: 3,752,500 (34.6%) guard_type_failure: 2,638,909 (24.3%) guard_shape_failure: 1,917,203 (17.7%) unhandled_yarv_insn: 690,482 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,784 ( 4.9%) unhandled_kwarg: 421,947 ( 3.9%) patchpoint: 370,474 ( 3.4%) unknown_newarray_send: 314,786 ( 2.9%) unhandled_splat: 122,067 ( 1.1%) unhandled_hir_insn: 76,395 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) obj_to_string_fallback: 566 ( 0.0%) interrupt: 469 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,945,326 dynamic_send_count: 25,530,605 (38.1%) optimized_send_count: 41,414,721 (61.9%) iseq_optimized_send_count: 18,587,439 (27.8%) inline_cfunc_optimized_send_count: 7,086,426 (10.6%) non_variadic_cfunc_optimized_send_count: 13,076,682 (19.5%) variadic_cfunc_optimized_send_count: 2,664,174 ( 4.0%) dynamic_getivar_count: 7,365,985 dynamic_setivar_count: 7,245,954 compiled_iseq_count: 4,794 failed_iseq_count: 450 compile_time: 748ms profile_time: 9ms gc_time: 8ms invalidation_time: 58ms vm_write_pc_count: 64,155,801 vm_write_sp_count: 62,812,041 vm_write_locals_count: 62,812,041 vm_write_stack_count: 62,812,041 vm_write_to_parent_iseq_local_count: 292,448 vm_read_from_parent_iseq_local_count: 6,470,939 code_region_bytes: 23,052,288 side_exit_count: 10,860,797 total_insn_count: 517,576,915 vm_insn_count: 163,192,099 zjit_insn_count: 354,384,816 ratio_in_zjit: 68.5% ``` --- insns.def | 3 ++ zjit/src/cruby_bindings.inc.rs | 9 ++-- zjit/src/hir.rs | 85 +++++++++++++++++++++++++++++++++- zjit/src/profile.rs | 3 ++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/insns.def b/insns.def index b895bffe222f37..69a8210d7d6f99 100644 --- a/insns.def +++ b/insns.def @@ -1553,6 +1553,7 @@ opt_length (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_length(recv, BOP_LENGTH); @@ -1567,6 +1568,7 @@ opt_size (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_length(recv, BOP_SIZE); @@ -1626,6 +1628,7 @@ opt_regexpmatch2 (VALUE obj2, VALUE obj1) (VALUE val) // attr bool leaf = false; /* match_at() has rb_thread_check_ints() */ +// attr bool zjit_profile = true; { val = vm_opt_regexpmatch2(obj2, obj1); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 6e3ae05194fa3b..56b569e064c0b0 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -699,9 +699,12 @@ pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 234; pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 235; pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 236; pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 237; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 238; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 239; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 242; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 243; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 022451e8ab4c77..33c034b819a7ec 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4957,7 +4957,7 @@ mod tests { } #[track_caller] - fn assert_contains_opcode(method: &str, opcode: u32) { + pub fn assert_contains_opcode(method: &str, opcode: u32) { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); @@ -7999,6 +7999,7 @@ mod opt_tests { use super::*; use crate::{hir_strings, options::*}; use insta::assert_snapshot; + use super::tests::assert_contains_opcode; #[track_caller] fn hir_string(method: &str) -> String { @@ -12876,4 +12877,86 @@ mod opt_tests { Return v27 "); } + + #[test] + fn test_optimize_array_length() { + eval(" + def test(arr) = arr.length + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_length); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + v26:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_size() { + eval(" + def test(arr) = arr.size + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_size); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + v26:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_regexpmatch2() { + eval(r#" + def test(s) = s =~ /a/ + test("foo") + "#); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v26:StringExact = GuardType v9, StringExact + v27:BasicObject = CCallWithFrame =~@0x1040, v26, v13 + CheckInterrupts + Return v27 + "); + } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index c2c35f687b37d4..e7db47142bcf65 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -78,7 +78,10 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_aset => profile_operands(profiler, profile, 3), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), + YARVINSN_opt_regexpmatch2 => profile_operands(profiler, profile, 2), YARVINSN_objtostring => profile_operands(profiler, profile, 1), + YARVINSN_opt_length => profile_operands(profiler, profile, 1), + YARVINSN_opt_size => profile_operands(profiler, profile, 1), YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr(); let argc = unsafe { vm_ci_argc((*cd).ci) }; From ed94e543515cad8624120c09500ff38fe1b56160 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 15 Oct 2025 04:36:47 +0800 Subject: [PATCH 0335/2435] ZJIT: Centralize the allocation of scratch registers (#14815) --- zjit/src/asm/mod.rs | 9 +- zjit/src/backend/arm64/mod.rs | 270 ++++++++++++++++++----------- zjit/src/backend/lir.rs | 91 +++++----- zjit/src/backend/x86_64/mod.rs | 301 ++++++++++++++++++++------------- zjit/src/codegen.rs | 20 +-- 5 files changed, 412 insertions(+), 279 deletions(-) diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index aeb429382d88c0..32dc633a2941ce 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -20,7 +20,6 @@ pub mod arm64; pub struct Label(pub usize); /// Reference to an ASM label -#[derive(Clone)] pub struct LabelRef { // Position in the code block where the label reference exists pos: usize, @@ -34,7 +33,7 @@ pub struct LabelRef { num_bytes: usize, /// The object that knows how to encode the branch instruction. - encode: fn(&mut CodeBlock, i64, i64) + encode: Box, } /// Block of memory into which instructions can be assembled @@ -223,11 +222,11 @@ impl CodeBlock { } // Add a label reference at the current write position - pub fn label_ref(&mut self, label: Label, num_bytes: usize, encode: fn(&mut CodeBlock, i64, i64)) { + pub fn label_ref(&mut self, label: Label, num_bytes: usize, encode: impl Fn(&mut CodeBlock, i64, i64) + 'static) { assert!(label.0 < self.label_addrs.len()); // Keep track of the reference - self.label_refs.push(LabelRef { pos: self.write_pos, label, num_bytes, encode }); + self.label_refs.push(LabelRef { pos: self.write_pos, label, num_bytes, encode: Box::new(encode) }); // Move past however many bytes the instruction takes up if self.write_pos + num_bytes < self.mem_size { @@ -251,7 +250,7 @@ impl CodeBlock { assert!(label_addr < self.mem_size); self.write_pos = ref_pos; - (label_ref.encode)(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64); + (label_ref.encode.as_ref())(self, (ref_pos + label_ref.num_bytes) as i64, label_addr as i64); // Assert that we've written the same number of bytes that we // expected to have written. diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 5ac62c059986d5..6750926b35daa1 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -113,8 +113,8 @@ fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { b(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); 1 } else { - let num_insns = emit_load_value(cb, Assembler::SCRATCH0, dst_addr as u64); - br(cb, Assembler::SCRATCH0); + let num_insns = emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr as u64); + br(cb, Assembler::EMIT0_OPND); num_insns + 1 }; @@ -181,7 +181,7 @@ fn emit_load_value(cb: &mut CodeBlock, rd: A64Opnd, value: u64) -> usize { /// List of registers that can be used for register allocation. /// This has the same number of registers for x86_64 and arm64. -/// SCRATCH0 and SCRATCH1 are excluded. +/// SCRATCH_OPND, EMIT0_OPND, and EMIT1_OPND are excluded. pub const ALLOC_REGS: &[Reg] = &[ X0_REG, X1_REG, @@ -193,17 +193,33 @@ pub const ALLOC_REGS: &[Reg] = &[ X12_REG, ]; -impl Assembler -{ - /// Special scratch registers for intermediate processing. - /// This register is call-clobbered (so we don't have to save it before using it). - /// Avoid using if you can since this is used to lower [Insn] internally and - /// so conflicts are possible. - pub const SCRATCH_REG: Reg = X16_REG; - const SCRATCH0_REG: Reg = Self::SCRATCH_REG; - const SCRATCH1_REG: Reg = X17_REG; - const SCRATCH0: A64Opnd = A64Opnd::Reg(Self::SCRATCH0_REG); - const SCRATCH1: A64Opnd = A64Opnd::Reg(Self::SCRATCH1_REG); +/// Special scratch register for intermediate processing. It should be used only by +/// [`Assembler::arm64_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. +const SCRATCH_OPND: Opnd = Opnd::Reg(X15_REG); + +impl Assembler { + /// Special registers for intermediate processing in arm64_emit. It should be used only by arm64_emit. + /// TODO: Remove the use of these registers by splitting instructions in arm64_split_with_scratch_reg. + const EMIT0_REG: Reg = X16_REG; + const EMIT1_REG: Reg = X17_REG; + const EMIT0_OPND: A64Opnd = A64Opnd::Reg(Self::EMIT0_REG); + const EMIT1_OPND: A64Opnd = A64Opnd::Reg(Self::EMIT1_REG); + + /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. + pub fn new_with_scratch_reg() -> (Self, Opnd) { + (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + } + + /// Return true if opnd contains a scratch reg + pub fn has_scratch_reg(opnd: Opnd) -> bool { + match opnd { + Opnd::Reg(_) => opnd == SCRATCH_OPND, + Opnd::Mem(Mem { base: MemBase::Reg(reg_no), .. }) => { + reg_no == SCRATCH_OPND.unwrap_reg().reg_no + } + _ => false, + } + } /// Get the list of registers from which we will allocate on this platform pub fn get_alloc_regs() -> Vec { @@ -372,7 +388,7 @@ impl Assembler let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len()); + let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); let asm = &mut asm_local; while let Some((index, mut insn)) = iterator.next() { @@ -555,14 +571,6 @@ impl Assembler } } }, - Insn::IncrCounter { mem, value } => { - let counter_addr = match mem { - Opnd::Mem(_) => asm.lea(*mem), - _ => *mem - }; - - asm.incr_counter(counter_addr, *value); - }, Insn::JmpOpnd(opnd) => { if let Opnd::Mem(_) = opnd { let opnd0 = split_load_operand(asm, *opnd); @@ -679,6 +687,58 @@ impl Assembler asm_local } + /// Split instructions using scratch registers. To maximize the use of the register pool for + /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions + /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this + /// splits them and uses scratch registers for it. + fn arm64_split_with_scratch_reg(mut self) -> Assembler { + let mut iterator = self.insns.into_iter().enumerate().peekable(); + let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); + + while let Some((_, mut insn)) = iterator.next() { + match &mut insn { + // For compile_side_exits, support splitting simple C arguments here + Insn::CCall { opnds, .. } if !opnds.is_empty() => { + for (i, opnd) in opnds.iter().enumerate() { + asm.load_into(C_ARG_OPNDS[i], *opnd); + } + *opnds = vec![]; + asm.push_insn(insn); + } + &mut Insn::Lea { opnd, out } => { + match (opnd, out) { + // Split here for compile_side_exits + (Opnd::Mem(_), Opnd::Mem(_)) => { + asm.lea_into(SCRATCH_OPND, opnd); + asm.store(out, SCRATCH_OPND); + } + _ => { + asm.push_insn(insn); + } + } + } + // Convert Opnd::const_ptr into Opnd::Mem. It's split here compile_side_exits. + &mut Insn::IncrCounter { mem, value } => { + assert!(matches!(mem, Opnd::UImm(_))); + asm.load_into(SCRATCH_OPND, mem); + asm.lea_into(SCRATCH_OPND, Opnd::mem(64, SCRATCH_OPND, 0)); + asm.incr_counter(SCRATCH_OPND, value); + } + // Resolve ParallelMov that couldn't be handled without a scratch register. + Insn::ParallelMov { moves } => { + for (reg, opnd) in Self::resolve_parallel_moves(moves, Some(SCRATCH_OPND)).unwrap() { + asm.load_into(Opnd::Reg(reg), opnd); + } + } + _ => { + asm.push_insn(insn); + } + } + } + + asm + } + /// Emit platform-specific machine code /// Returns a list of GC offsets. Can return failure to signal caller to retry. fn arm64_emit(&mut self, cb: &mut CodeBlock) -> Option> { @@ -739,8 +799,8 @@ impl Assembler // that if it doesn't match it will skip over the // instructions used for branching. bcond(cb, Condition::inverse(CONDITION), (load_insns + 2).into()); - emit_load_value(cb, Assembler::SCRATCH0, dst_addr as u64); - br(cb, Assembler::SCRATCH0); + emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr as u64); + br(cb, Assembler::EMIT0_OPND); // Here we'll return the number of instructions that it // took to write out the destination address + 1 for the @@ -806,8 +866,8 @@ impl Assembler } else { cbz(cb, reg, InstructionOffset::from_insns(load_insns + 2)); } - emit_load_value(cb, Assembler::SCRATCH0, dst_addr); - br(cb, Assembler::SCRATCH0); + emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr); + br(cb, Assembler::EMIT0_OPND); } */ } else { @@ -979,7 +1039,7 @@ impl Assembler (Some(Insn::JoMul(_)), _) | (Some(Insn::PosMarker(_)), Some(Insn::JoMul(_))) => { // Compute the high 64 bits - smulh(cb, Self::SCRATCH0, left.into(), right.into()); + smulh(cb, Self::EMIT0_OPND, left.into(), right.into()); // Compute the low 64 bits // This may clobber one of the input registers, @@ -988,11 +1048,11 @@ impl Assembler // Produce a register that is all zeros or all ones // Based on the sign bit of the 64-bit mul result - asr(cb, Self::SCRATCH1, out.into(), A64Opnd::UImm(63)); + asr(cb, Self::EMIT1_OPND, out.into(), A64Opnd::UImm(63)); // If the high 64-bits are not all zeros or all ones, // matching the sign bit, then we have an overflow - cmp(cb, Self::SCRATCH0, Self::SCRATCH1); + cmp(cb, Self::EMIT0_OPND, Self::EMIT1_OPND); // Insn::JoMul will emit_conditional_jump::<{Condition::NE}> } _ => { @@ -1021,77 +1081,57 @@ impl Assembler Insn::LShift { opnd, shift, out } => { lsl(cb, out.into(), opnd.into(), shift.into()); }, - store_insn @ Insn::Store { dest, src } => { - // With minor exceptions, as long as `dest` is a Mem, all forms of `src` are - // accepted. As a rule of thumb, avoid using Assembler::SCRATCH as a memory - // base register to gurantee things will work. + Insn::Store { dest, src } => { + // With minor exceptions, as long as `dest` is a Mem, all forms of `src` are accepted. let &Opnd::Mem(Mem { num_bits: dest_num_bits, base: MemBase::Reg(base_reg_no), disp }) = dest else { panic!("Unexpected Insn::Store destination in arm64_emit: {dest:?}"); }; - // This kind of tricky clobber can only happen for explicit use of SCRATCH_REG, - // so we panic to get the author to change their code. - #[track_caller] - fn assert_no_clobber(store_insn: &Insn, user_use: u8, backend_use: Reg) { - assert_ne!( - backend_use.reg_no, - user_use, - "Emitting {store_insn:?} would clobber {user_use:?}, in conflict with its semantics" - ); - } - - // Split src into SCRATCH0 if necessary + // Split src into EMIT0_OPND if necessary let src_reg: A64Reg = match src { Opnd::Reg(reg) => *reg, // Use zero register when possible Opnd::UImm(0) | Opnd::Imm(0) => XZR_REG, // Immediates &Opnd::Imm(imm) => { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH0_REG); - emit_load_value(cb, Self::SCRATCH0, imm as u64); - Self::SCRATCH0_REG + emit_load_value(cb, Self::EMIT0_OPND, imm as u64); + Self::EMIT0_REG } &Opnd::UImm(imm) => { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH0_REG); - emit_load_value(cb, Self::SCRATCH0, imm); - Self::SCRATCH0_REG + emit_load_value(cb, Self::EMIT0_OPND, imm); + Self::EMIT0_REG } &Opnd::Value(value) => { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH0_REG); - emit_load_gc_value(cb, &mut gc_offsets, Self::SCRATCH0, value); - Self::SCRATCH0_REG + emit_load_gc_value(cb, &mut gc_offsets, Self::EMIT0_OPND, value); + Self::EMIT0_REG } src_mem @ &Opnd::Mem(Mem { num_bits: src_num_bits, base: MemBase::Reg(src_base_reg_no), disp: src_disp }) => { - // For mem-to-mem store, load the source into SCRATCH0 - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH0_REG); + // For mem-to-mem store, load the source into EMIT0_OPND let src_mem = if mem_disp_fits_bits(src_disp) { src_mem.into() } else { - // Split the load address into SCRATCH0 first if necessary - assert_no_clobber(store_insn, src_base_reg_no, Self::SCRATCH0_REG); - load_effective_address(cb, Self::SCRATCH0, src_base_reg_no, src_disp); - A64Opnd::new_mem(dest_num_bits, Self::SCRATCH0, 0) + // Split the load address into EMIT0_OPND first if necessary + load_effective_address(cb, Self::EMIT0_OPND, src_base_reg_no, src_disp); + A64Opnd::new_mem(dest_num_bits, Self::EMIT0_OPND, 0) }; match src_num_bits { - 64 | 32 => ldur(cb, Self::SCRATCH0, src_mem), - 16 => ldurh(cb, Self::SCRATCH0, src_mem), - 8 => ldurb(cb, Self::SCRATCH0, src_mem), + 64 | 32 => ldur(cb, Self::EMIT0_OPND, src_mem), + 16 => ldurh(cb, Self::EMIT0_OPND, src_mem), + 8 => ldurb(cb, Self::EMIT0_OPND, src_mem), num_bits => panic!("unexpected num_bits: {num_bits}") }; - Self::SCRATCH0_REG + Self::EMIT0_REG } src @ (Opnd::Mem(_) | Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during arm64_emit: {src:?}") }; let src = A64Opnd::Reg(src_reg); - // Split dest into SCRATCH1 if necessary. + // Split dest into EMIT1_OPND if necessary. let dest = if mem_disp_fits_bits(disp) { dest.into() } else { - assert_no_clobber(store_insn, src_reg.reg_no, Self::SCRATCH1_REG); - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH1_REG); - load_effective_address(cb, Self::SCRATCH1, base_reg_no, disp); - A64Opnd::new_mem(dest_num_bits, Self::SCRATCH1, 0) + load_effective_address(cb, Self::EMIT1_OPND, base_reg_no, disp); + A64Opnd::new_mem(dest_num_bits, Self::EMIT1_OPND, 0) }; // This order may be surprising but it is correct. The way @@ -1169,10 +1209,10 @@ impl Assembler if let Target::Label(label_idx) = target { // Set output to the raw address of the label cb.label_ref(*label_idx, 4, |cb, end_addr, dst_addr| { - adr(cb, Self::SCRATCH0, A64Opnd::new_imm(dst_addr - (end_addr - 4))); + adr(cb, Self::EMIT0_OPND, A64Opnd::new_imm(dst_addr - (end_addr - 4))); }); - mov(cb, out.into(), Self::SCRATCH0); + mov(cb, out.into(), Self::EMIT0_OPND); } else { // Set output to the jump target's raw address let target_code = target.unwrap_code_ptr(); @@ -1197,15 +1237,15 @@ impl Assembler } // Push the flags/state register - mrs(cb, Self::SCRATCH0, SystemRegister::NZCV); - emit_push(cb, Self::SCRATCH0); + mrs(cb, Self::EMIT0_OPND, SystemRegister::NZCV); + emit_push(cb, Self::EMIT0_OPND); }, Insn::CPopAll => { let regs = Assembler::get_caller_save_regs(); // Pop the state/flags register - msr(cb, SystemRegister::NZCV, Self::SCRATCH0); - emit_pop(cb, Self::SCRATCH0); + msr(cb, SystemRegister::NZCV, Self::EMIT0_OPND); + emit_pop(cb, Self::EMIT0_OPND); for reg in regs.into_iter().rev() { emit_pop(cb, A64Opnd::Reg(reg)); @@ -1221,8 +1261,8 @@ impl Assembler if b_offset_fits_bits((dst_addr - src_addr) / 4) { bl(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); } else { - emit_load_value(cb, Self::SCRATCH0, dst_addr as u64); - blr(cb, Self::SCRATCH0); + emit_load_value(cb, Self::EMIT0_OPND, dst_addr as u64); + blr(cb, Self::EMIT0_OPND); } }, Insn::CRet { .. } => { @@ -1302,17 +1342,17 @@ impl Assembler let label = cb.new_label("incr_counter_loop".to_string()); cb.write_label(label); - ldaxr(cb, Self::SCRATCH0, mem.into()); - add(cb, Self::SCRATCH0, Self::SCRATCH0, value.into()); + ldaxr(cb, Self::EMIT0_OPND, mem.into()); + add(cb, Self::EMIT0_OPND, Self::EMIT0_OPND, value.into()); // The status register that gets used to track whether or // not the store was successful must be 32 bytes. Since we - // store the SCRATCH registers as their 64-bit versions, we + // store the EMIT registers as their 64-bit versions, we // need to rewrap it here. - let status = A64Opnd::Reg(Self::SCRATCH1.unwrap_reg().with_num_bits(32)); - stlxr(cb, status, Self::SCRATCH0, mem.into()); + let status = A64Opnd::Reg(Self::EMIT1_REG.with_num_bits(32)); + stlxr(cb, status, Self::EMIT0_OPND, mem.into()); - cmp(cb, Self::SCRATCH1, A64Opnd::new_uimm(0)); + cmp(cb, Self::EMIT1_OPND, A64Opnd::new_uimm(0)); emit_conditional_jump::<{Condition::NE}>(cb, Target::Label(label)); }, Insn::Breakpoint => { @@ -1363,9 +1403,16 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Result<(CodePtr, Vec), CompileError> { + // The backend is allowed to use scratch registers only if it has not accepted them so far. + let use_scratch_reg = !self.accept_scratch_reg; + let asm = self.arm64_split(); let mut asm = asm.alloc_regs(regs)?; + // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. asm.compile_side_exits(); + if use_scratch_reg { + asm = asm.arm64_split_with_scratch_reg(); + } // Create label instances in the code block for (idx, name) in asm.label_names.iter().enumerate() { @@ -1809,12 +1856,39 @@ mod tests { assert_snapshot!(cb.hexdump(), @"50000058030000140010000000000000b00200f8"); } + #[test] + fn test_store_with_valid_scratch_reg() { + let (mut asm, scratch_reg) = Assembler::new_with_scratch_reg(); + let mut cb = CodeBlock::new_dummy(); + asm.store(Opnd::mem(64, scratch_reg, 0), 0x83902.into()); + + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x16, #0x3902 + 0x4: movk x16, #8, lsl #16 + 0x8: stur x16, [x15] + "); + assert_snapshot!(cb.hexdump(), @"502087d21001a0f2f00100f8"); + } + + #[test] + #[should_panic] + fn test_store_with_invalid_scratch_reg() { + let (_, scratch_reg) = Assembler::new_with_scratch_reg(); + let (mut asm, mut cb) = setup_asm(); + // This would put the source into scratch_reg, messing up the destination + asm.store(Opnd::mem(64, scratch_reg, 0), 0x83902.into()); + + asm.compile_with_num_regs(&mut cb, 0); + } + #[test] #[should_panic] - fn test_store_unserviceable() { + fn test_load_into_with_invalid_scratch_reg() { + let (_, scratch_reg) = Assembler::new_with_scratch_reg(); let (mut asm, mut cb) = setup_asm(); - // This would put the source into SCRATCH_REG, messing up the destination - asm.store(Opnd::mem(64, SCRATCH_OPND, 0), 0x83902.into()); + // This would put the source into scratch_reg, messing up the destination + asm.load_into(scratch_reg, 0x83902.into()); asm.compile_with_num_regs(&mut cb, 0); } @@ -2300,13 +2374,13 @@ mod tests { asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x16, x0 + 0x0: mov x15, x0 0x4: mov x0, x1 - 0x8: mov x1, x16 + 0x8: mov x1, x15 0xc: mov x16, #0 0x10: blr x16 "); - assert_snapshot!(cb.hexdump(), @"f00300aae00301aae10310aa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae1030faa100080d200023fd6"); } #[test] @@ -2324,16 +2398,16 @@ mod tests { asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x16, x2 + 0x0: mov x15, x2 0x4: mov x2, x3 - 0x8: mov x3, x16 - 0xc: mov x16, x0 + 0x8: mov x3, x15 + 0xc: mov x15, x0 0x10: mov x0, x1 - 0x14: mov x1, x16 + 0x14: mov x1, x15 0x18: mov x16, #0 0x1c: blr x16 "); - assert_snapshot!(cb.hexdump(), @"f00302aae20303aae30310aaf00300aae00301aae10310aa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0302aae20303aae3030faaef0300aae00301aae1030faa100080d200023fd6"); } #[test] @@ -2350,13 +2424,13 @@ mod tests { asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x16, x0 + 0x0: mov x15, x0 0x4: mov x0, x1 0x8: mov x1, x2 - 0xc: mov x2, x16 + 0xc: mov x2, x15 0x10: mov x16, #0 0x14: blr x16 "); - assert_snapshot!(cb.hexdump(), @"f00300aae00301aae10302aae20310aa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae10302aae2030faa100080d200023fd6"); } } diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 76a53c66d6b652..aad5600f569767 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -17,7 +17,6 @@ pub use crate::backend::current::{ NATIVE_STACK_PTR, NATIVE_BASE_PTR, C_ARG_OPNDS, C_RET_REG, C_RET_OPND, }; -pub const SCRATCH_OPND: Opnd = Opnd::Reg(Assembler::SCRATCH_REG); pub static JIT_PRESERVED_REGS: &[Opnd] = &[CFP, SP, EC]; @@ -1173,6 +1172,10 @@ pub struct Assembler { /// Names of labels pub(super) label_names: Vec, + /// If true, `push_insn` is allowed to use scratch registers. + /// On `compile`, it also disables the backend's use of them. + pub(super) accept_scratch_reg: bool, + /// If Some, the next ccall should verify its leafness leaf_ccall_stack_size: Option } @@ -1181,12 +1184,12 @@ impl Assembler { /// Create an Assembler pub fn new() -> Self { - Self::new_with_label_names(Vec::default(), 0) + Self::new_with_label_names(Vec::default(), 0, false) } /// Create an Assembler with parameters that are populated by another Assembler instance. /// This API is used for copying an Assembler for the next compiler pass. - pub fn new_with_label_names(label_names: Vec, num_vregs: usize) -> Self { + pub fn new_with_label_names(label_names: Vec, num_vregs: usize, accept_scratch_reg: bool) -> Self { let mut live_ranges = Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY); live_ranges.resize(num_vregs, LiveRange { start: None, end: None }); @@ -1194,6 +1197,7 @@ impl Assembler insns: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), live_ranges, label_names, + accept_scratch_reg, leaf_ccall_stack_size: None, } } @@ -1255,6 +1259,14 @@ impl Assembler } } + // If this Assembler should not accept scratch registers, assert no use of them. + if !self.accept_scratch_reg { + let opnd_iter = insn.opnd_iter(); + for opnd in opnd_iter { + assert!(!Self::has_scratch_reg(*opnd), "should not use scratch register: {opnd:?}"); + } + } + self.insns.push(insn); } @@ -1268,9 +1280,9 @@ impl Assembler Target::Label(label) } - // Shuffle register moves, sometimes adding extra moves using SCRATCH_REG, + // Shuffle register moves, sometimes adding extra moves using scratch_reg, // so that they will not rewrite each other before they are used. - pub fn resolve_parallel_moves(old_moves: &[(Reg, Opnd)]) -> Vec<(Reg, Opnd)> { + pub fn resolve_parallel_moves(old_moves: &[(Reg, Opnd)], scratch_reg: Option) -> Option> { // Return the index of a move whose destination is not used as a source if any. fn find_safe_move(moves: &[(Reg, Opnd)]) -> Option { moves.iter().enumerate().find(|&(_, &(dest_reg, _))| { @@ -1289,19 +1301,21 @@ impl Assembler new_moves.push(old_moves.remove(index)); } - // No safe move. Load the source of one move into SCRATCH_REG, and - // then load SCRATCH_REG into the destination when it's safe. + // No safe move. Load the source of one move into scratch_reg, and + // then load scratch_reg into the destination when it's safe. if !old_moves.is_empty() { - // Make sure it's safe to use SCRATCH_REG - assert!(old_moves.iter().all(|&(_, opnd)| opnd != SCRATCH_OPND)); + // If scratch_reg is None, return None and leave it to *_split_with_scratch_regs to resolve it. + let scratch_reg = scratch_reg?.unwrap_reg(); + // Make sure it's safe to use scratch_reg + assert!(old_moves.iter().all(|&(_, opnd)| opnd != Opnd::Reg(scratch_reg))); - // Move SCRATCH <- opnd, and delay reg <- SCRATCH + // Move scratch_reg <- opnd, and delay reg <- scratch_reg let (reg, opnd) = old_moves.remove(0); - new_moves.push((Assembler::SCRATCH_REG, opnd)); - old_moves.push((reg, SCRATCH_OPND)); + new_moves.push((scratch_reg, opnd)); + old_moves.push((reg, Opnd::Reg(scratch_reg))); } } - new_moves + Some(new_moves) } /// Sets the out field on the various instructions that require allocated @@ -1345,7 +1359,7 @@ impl Assembler // live_ranges is indexed by original `index` given by the iterator. let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len()); + let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { @@ -1510,9 +1524,14 @@ impl Assembler let is_ccall = matches!(insn, Insn::CCall { .. }); match insn { Insn::ParallelMov { moves } => { - // Now that register allocation is done, it's ready to resolve parallel moves. - for (reg, opnd) in Self::resolve_parallel_moves(&moves) { - asm.load_into(Opnd::Reg(reg), opnd); + // For trampolines that use scratch registers, attempt to lower ParallelMov without scratch_reg. + if let Some(moves) = Self::resolve_parallel_moves(&moves, None) { + for (reg, opnd) in moves { + asm.load_into(Opnd::Reg(reg), opnd); + } + } else { + // If it needs a scratch_reg, leave it to *_split_with_scratch_regs to handle it. + asm.push_insn(Insn::ParallelMov { moves }); } } Insn::CCall { opnds, fptr, start_marker, end_marker, out } => { @@ -1586,7 +1605,7 @@ impl Assembler for (idx, target) in targets { // Compile a side exit. Note that this is past the split pass and alloc_regs(), - // so you can't use a VReg or an instruction that needs to be split. + // so you can't use an instruction that returns a VReg. if let Target::SideExit { pc, stack, locals, reason, label } = target { asm_comment!(self, "Exit: {reason}"); let side_exit_label = if let Some(label) = label { @@ -1609,35 +1628,24 @@ impl Assembler } asm_comment!(self, "save cfp->pc"); - self.load_into(SCRATCH_OPND, Opnd::const_ptr(pc)); - self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), SCRATCH_OPND); + self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(pc)); asm_comment!(self, "save cfp->sp"); - self.lea_into(SCRATCH_OPND, Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32)); - let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP); - self.store(cfp_sp, SCRATCH_OPND); + self.lea_into(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP), Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32)); // Using C_RET_OPND as an additional scratch register, which is no longer used if get_option!(stats) { asm_comment!(self, "increment a side exit counter"); - self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr(reason))); - self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); + self.incr_counter(Opnd::const_ptr(exit_counter_ptr(reason)), 1.into()); if let SideExitReason::UnhandledYARVInsn(opcode) = reason { asm_comment!(self, "increment an unhandled YARV insn counter"); - self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr_for_opcode(opcode))); - self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); + self.incr_counter(Opnd::const_ptr(exit_counter_ptr_for_opcode(opcode)), 1.into()); } } if get_option!(trace_side_exits) { - // Use `load_into` with `C_ARG_OPNDS` instead of `opnds` argument for ccall, since `compile_side_exits` - // is after the split pass, which would allow use of `opnds`. - self.load_into(C_ARG_OPNDS[0], Opnd::const_ptr(pc as *const u8)); - self.ccall( - rb_zjit_record_exit_stack as *const u8, - vec![] - ); + asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8)); } asm_comment!(self, "exit to the interpreter"); @@ -1823,19 +1831,6 @@ impl Assembler { self.push_insn(Insn::IncrCounter { mem, value }); } - /// incr_counter() but uses a specific register to split Insn::Lea - pub fn incr_counter_with_reg(&mut self, mem: Opnd, value: Opnd, reg: Opnd) { - assert!(matches!(reg, Opnd::Reg(_)), "incr_counter_with_reg should take a register, got: {reg:?}"); - let counter_opnd = if cfg!(target_arch = "aarch64") { // See arm64_split() - assert_ne!(reg, SCRATCH_OPND, "SCRATCH_REG should be reserved for IncrCounter"); - self.lea_into(reg, mem); - reg - } else { // x86_emit() expects Opnd::Mem - mem - }; - self.incr_counter(counter_opnd, value); - } - pub fn jbe(&mut self, target: Target) { self.push_insn(Insn::Jbe(target)); } @@ -1898,7 +1893,7 @@ impl Assembler { } pub fn lea_into(&mut self, out: Opnd, opnd: Opnd) { - assert!(matches!(out, Opnd::Reg(_)), "Destination of lea_into must be a register, got: {out:?}"); + assert!(matches!(out, Opnd::Reg(_) | Opnd::Mem(_)), "Destination of lea_into must be a register or memory, got: {out:?}"); self.push_insn(Insn::Lea { opnd, out }); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 2edd15380871e1..b6c4658463048a 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -83,7 +83,7 @@ impl From<&Opnd> for X86Opnd { /// List of registers that can be used for register allocation. /// This has the same number of registers for x86_64 and arm64. -/// SCRATCH_REG is excluded. +/// SCRATCH_OPND is excluded. pub const ALLOC_REGS: &[Reg] = &[ RDI_REG, RSI_REG, @@ -95,14 +95,26 @@ pub const ALLOC_REGS: &[Reg] = &[ RAX_REG, ]; -impl Assembler -{ - /// Special scratch registers for intermediate processing. - /// This register is call-clobbered (so we don't have to save it before using it). - /// Avoid using if you can since this is used to lower [Insn] internally and - /// so conflicts are possible. - pub const SCRATCH_REG: Reg = R11_REG; - const SCRATCH0: X86Opnd = X86Opnd::Reg(Assembler::SCRATCH_REG); +/// Special scratch register for intermediate processing. It should be used only by +/// [`Assembler::x86_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. +const SCRATCH_OPND: Opnd = Opnd::Reg(R11_REG); + +impl Assembler { + /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. + pub fn new_with_scratch_reg() -> (Self, Opnd) { + (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + } + + /// Return true if opnd contains a scratch reg + pub fn has_scratch_reg(opnd: Opnd) -> bool { + match opnd { + Opnd::Reg(_) => opnd == SCRATCH_OPND, + Opnd::Mem(Mem { base: MemBase::Reg(reg_no), .. }) => { + reg_no == SCRATCH_OPND.unwrap_reg().reg_no + } + _ => false, + } + } /// Get the list of registers from which we can allocate on this platform pub fn get_alloc_regs() -> Vec { @@ -127,7 +139,7 @@ impl Assembler { let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len()); + let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let is_load = matches!(insn, Insn::Load { .. } | Insn::LoadInto { .. }); @@ -374,36 +386,165 @@ impl Assembler asm } - /// Emit platform-specific machine code - pub fn x86_emit(&mut self, cb: &mut CodeBlock) -> Option> { + /// Split instructions using scratch registers. To maximize the use of the register pool + /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions + /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so + /// this splits them and uses scratch registers for it. + pub fn x86_split_with_scratch_reg(mut self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register - /// allocator. So we just use the SCRATCH0 register temporarily to hold + /// allocator. So we just use the SCRATCH_OPND register temporarily to hold /// the value before we immediately use it. - fn emit_64bit_immediate(cb: &mut CodeBlock, opnd: &Opnd) -> X86Opnd { + fn split_64bit_immediate(asm: &mut Assembler, opnd: Opnd) -> Opnd { match opnd { Opnd::Imm(value) => { // 32-bit values will be sign-extended - if imm_num_bits(*value) > 32 { - mov(cb, Assembler::SCRATCH0, opnd.into()); - Assembler::SCRATCH0 + if imm_num_bits(value) > 32 { + asm.mov(SCRATCH_OPND, opnd); + SCRATCH_OPND } else { - opnd.into() + opnd } }, Opnd::UImm(value) => { // 32-bit values will be sign-extended - if imm_num_bits(*value as i64) > 32 { - mov(cb, Assembler::SCRATCH0, opnd.into()); - Assembler::SCRATCH0 + if imm_num_bits(value as i64) > 32 { + asm.mov(SCRATCH_OPND, opnd); + SCRATCH_OPND } else { - imm_opnd(*value as i64) + Opnd::Imm(value as i64) } }, - _ => opnd.into() + _ => opnd } } + let mut iterator = self.insns.into_iter().enumerate().peekable(); + let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); + + while let Some((_, mut insn)) = iterator.next() { + match &mut insn { + Insn::Add { right, .. } | + Insn::Sub { right, .. } | + Insn::Mul { right, .. } | + Insn::And { right, .. } | + Insn::Or { right, .. } | + Insn::Xor { right, .. } | + Insn::Test { right, .. } => { + *right = split_64bit_immediate(&mut asm, *right); + asm.push_insn(insn); + } + Insn::Cmp { left, right } => { + let num_bits = match right { + Opnd::Imm(value) => Some(imm_num_bits(*value)), + Opnd::UImm(value) => Some(uimm_num_bits(*value)), + _ => None + }; + + // If the immediate is less than 64 bits (like 32, 16, 8), and the operand + // sizes match, then we can represent it as an immediate in the instruction + // without moving it to a register first. + // IOW, 64 bit immediates must always be moved to a register + // before comparisons, where other sizes may be encoded + // directly in the instruction. + let use_imm = num_bits.is_some() && left.num_bits() == num_bits && num_bits.unwrap() < 64; + if !use_imm { + *right = split_64bit_immediate(&mut asm, *right); + } + asm.push_insn(insn); + } + // For compile_side_exits, support splitting simple C arguments here + Insn::CCall { opnds, .. } if !opnds.is_empty() => { + for (i, opnd) in opnds.iter().enumerate() { + asm.load_into(C_ARG_OPNDS[i], *opnd); + } + *opnds = vec![]; + asm.push_insn(insn); + } + &mut Insn::Lea { opnd, out } => { + match (opnd, out) { + // Split here for compile_side_exits + (Opnd::Mem(_), Opnd::Mem(_)) => { + asm.lea_into(SCRATCH_OPND, opnd); + asm.store(out, SCRATCH_OPND); + } + _ => { + asm.push_insn(insn); + } + } + } + Insn::LeaJumpTarget { target, out } => { + if let Target::Label(_) = target { + asm.push_insn(Insn::LeaJumpTarget { out: SCRATCH_OPND, target: target.clone() }); + asm.mov(*out, SCRATCH_OPND); + } + } + // Convert Opnd::const_ptr into Opnd::Mem. This split is done here to give + // a register for compile_side_exits. + &mut Insn::IncrCounter { mem, value } => { + assert!(matches!(mem, Opnd::UImm(_))); + asm.load_into(SCRATCH_OPND, mem); + asm.incr_counter(Opnd::mem(64, SCRATCH_OPND, 0), value); + } + // Resolve ParallelMov that couldn't be handled without a scratch register. + Insn::ParallelMov { moves } => { + for (reg, opnd) in Self::resolve_parallel_moves(&moves, Some(SCRATCH_OPND)).unwrap() { + asm.load_into(Opnd::Reg(reg), opnd); + } + } + // Handle various operand combinations for spills on compile_side_exits. + &mut Insn::Store { dest, src } => { + let Opnd::Mem(Mem { num_bits, .. }) = dest else { + panic!("Unexpected Insn::Store destination in x86_split_with_scratch_reg: {dest:?}"); + }; + + let src = match src { + Opnd::Reg(_) => src, + Opnd::Mem(_) => { + asm.mov(SCRATCH_OPND, src); + SCRATCH_OPND + } + Opnd::Imm(imm) => { + // For 64 bit destinations, 32-bit values will be sign-extended + if num_bits == 64 && imm_num_bits(imm) > 32 { + asm.mov(SCRATCH_OPND, src); + SCRATCH_OPND + } else if uimm_num_bits(imm as u64) <= num_bits { + // If the bit string is short enough for the destination, use the unsigned representation. + // Note that 64-bit and negative values are ruled out. + Opnd::UImm(imm as u64) + } else { + src + } + } + Opnd::UImm(imm) => { + // For 64 bit destinations, 32-bit values will be sign-extended + if num_bits == 64 && imm_num_bits(imm as i64) > 32 { + asm.mov(SCRATCH_OPND, src); + SCRATCH_OPND + } else { + src.into() + } + } + Opnd::Value(_) => { + asm.load_into(SCRATCH_OPND, src); + SCRATCH_OPND + } + src @ (Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during x86_split_with_scratch_reg: {src:?}"), + }; + asm.store(dest, src); + } + _ => { + asm.push_insn(insn); + } + } + } + + asm + } + + /// Emit platform-specific machine code + pub fn x86_emit(&mut self, cb: &mut CodeBlock) -> Option> { fn emit_csel( cb: &mut CodeBlock, truthy: Opnd, @@ -506,33 +647,27 @@ impl Assembler } Insn::Add { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - add(cb, left.into(), opnd1); + add(cb, left.into(), right.into()); }, Insn::Sub { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - sub(cb, left.into(), opnd1); + sub(cb, left.into(), right.into()); }, Insn::Mul { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - imul(cb, left.into(), opnd1); + imul(cb, left.into(), right.into()); }, Insn::And { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - and(cb, left.into(), opnd1); + and(cb, left.into(), right.into()); }, Insn::Or { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - or(cb, left.into(), opnd1); + or(cb, left.into(), right.into()); }, Insn::Xor { left, right, .. } => { - let opnd1 = emit_64bit_immediate(cb, right); - xor(cb, left.into(), opnd1); + xor(cb, left.into(), right.into()); }, Insn::Not { opnd, .. } => { @@ -551,64 +686,6 @@ impl Assembler shr(cb, opnd.into(), shift.into()) }, - store_insn @ Insn::Store { dest, src } => { - let &Opnd::Mem(Mem { num_bits, base: MemBase::Reg(base_reg_no), disp: _ }) = dest else { - panic!("Unexpected Insn::Store destination in x64_emit: {dest:?}"); - }; - - // This kind of tricky clobber can only happen for explicit use of SCRATCH_REG, - // so we panic to get the author to change their code. - #[track_caller] - fn assert_no_clobber(store_insn: &Insn, user_use: u8, backend_use: Reg) { - assert_ne!( - backend_use.reg_no, - user_use, - "Emitting {store_insn:?} would clobber {user_use:?}, in conflict with its semantics" - ); - } - - let scratch = X86Opnd::Reg(Self::SCRATCH_REG); - let src = match src { - Opnd::Reg(_) => src.into(), - &Opnd::Mem(_) => { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH_REG); - mov(cb, scratch, src.into()); - scratch - } - &Opnd::Imm(imm) => { - // For 64 bit destinations, 32-bit values will be sign-extended - if num_bits == 64 && imm_num_bits(imm) > 32 { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH_REG); - mov(cb, scratch, src.into()); - scratch - } else if uimm_num_bits(imm as u64) <= num_bits { - // If the bit string is short enough for the destination, use the unsigned representation. - // Note that 64-bit and negative values are ruled out. - uimm_opnd(imm as u64) - } else { - src.into() - } - } - &Opnd::UImm(imm) => { - // For 64 bit destinations, 32-bit values will be sign-extended - if num_bits == 64 && imm_num_bits(imm as i64) > 32 { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH_REG); - mov(cb, scratch, src.into()); - scratch - } else { - src.into() - } - } - &Opnd::Value(value) => { - assert_no_clobber(store_insn, base_reg_no, Self::SCRATCH_REG); - emit_load_gc_value(cb, &mut gc_offsets, scratch, value); - scratch - } - src @ (Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during x86_emit: {src:?}") - }; - mov(cb, dest.into(), src); - } - // This assumes only load instructions can contain references to GC'd Value operands Insn::Load { opnd, out } | Insn::LoadInto { dest: out, opnd } => { @@ -626,6 +703,7 @@ impl Assembler Insn::ParallelMov { .. } => unreachable!("{insn:?} should have been lowered at alloc_regs()"), + Insn::Store { dest, src } | Insn::Mov { dest, src } => { mov(cb, dest.into(), src.into()); }, @@ -638,13 +716,12 @@ impl Assembler // Load address of jump target Insn::LeaJumpTarget { target, out } => { if let Target::Label(label) = target { + let out = *out; // Set output to the raw address of the label - cb.label_ref(*label, 7, |cb, src_addr, dst_addr| { + cb.label_ref(*label, 7, move |cb, src_addr, dst_addr| { let disp = dst_addr - src_addr; - lea(cb, Self::SCRATCH0, mem_opnd(8, RIP, disp.try_into().unwrap())); + lea(cb, out.into(), mem_opnd(8, RIP, disp.try_into().unwrap())); }); - - mov(cb, out.into(), Self::SCRATCH0); } else { // Set output to the jump target's raw address let target_code = target.unwrap_code_ptr(); @@ -700,30 +777,12 @@ impl Assembler // Compare Insn::Cmp { left, right } => { - let num_bits = match right { - Opnd::Imm(value) => Some(imm_num_bits(*value)), - Opnd::UImm(value) => Some(uimm_num_bits(*value)), - _ => None - }; - - // If the immediate is less than 64 bits (like 32, 16, 8), and the operand - // sizes match, then we can represent it as an immediate in the instruction - // without moving it to a register first. - // IOW, 64 bit immediates must always be moved to a register - // before comparisons, where other sizes may be encoded - // directly in the instruction. - if num_bits.is_some() && left.num_bits() == num_bits && num_bits.unwrap() < 64 { - cmp(cb, left.into(), right.into()); - } else { - let emitted = emit_64bit_immediate(cb, right); - cmp(cb, left.into(), emitted); - } + cmp(cb, left.into(), right.into()); } // Test and set flags Insn::Test { left, right } => { - let emitted = emit_64bit_immediate(cb, right); - test(cb, left.into(), emitted); + test(cb, left.into(), right.into()); } Insn::JmpOpnd(opnd) => { @@ -893,9 +952,16 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Result<(CodePtr, Vec), CompileError> { + // The backend is allowed to use scratch registers only if it has not accepted them so far. + let use_scratch_regs = !self.accept_scratch_reg; + let asm = self.x86_split(); let mut asm = asm.alloc_regs(regs)?; + // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. asm.compile_side_exits(); + if use_scratch_regs { + asm = asm.x86_split_with_scratch_reg(); + } // Create label instances in the code block for (idx, name) in asm.label_names.iter().enumerate() { @@ -1527,6 +1593,7 @@ mod tests { assert!(imitation_heap_value.heap_object_p()); asm.store(Opnd::mem(VALUE_BITS, SP, 0), imitation_heap_value.into()); + asm = asm.x86_split_with_scratch_reg(); let gc_offsets = asm.x86_emit(&mut cb).unwrap(); assert_eq!(1, gc_offsets.len(), "VALUE source operand should be reported as gc offset"); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 262a0361b4a74e..c5bdbcfe0a5a83 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -18,7 +18,7 @@ use crate::state::ZJITState; use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP}; +use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP}; use crate::hir::{iseq_to_hir, BlockId, BranchEdge, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId, SendFallbackReason}; use crate::hir_type::{types, Type}; @@ -1592,9 +1592,7 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, /// Generate code that records unoptimized C functions if --zjit-stats is enabled fn gen_incr_counter_ptr(asm: &mut Assembler, counter_ptr: *mut u64) { if get_option!(stats) { - let ptr_reg = asm.load(Opnd::const_ptr(counter_ptr as *const u8)); - let counter_opnd = Opnd::mem(64, ptr_reg, 0); - asm.incr_counter(counter_opnd, Opnd::UImm(1)); + asm.incr_counter(Opnd::const_ptr(counter_ptr as *const u8), Opnd::UImm(1)); } } @@ -1963,12 +1961,12 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result /// Compile a stub for an ISEQ called by SendWithoutBlockDirect fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result { - let mut asm = Assembler::new(); + let (mut asm, scratch_reg) = Assembler::new_with_scratch_reg(); asm_comment!(asm, "Stub: {}", iseq_get_location(iseq_call.iseq.get(), 0)); // Call function_stub_hit using the shared trampoline. See `gen_function_stub_hit_trampoline`. // Use load_into instead of mov, which is split on arm64, to avoid clobbering ALLOC_REGS. - asm.load_into(SCRATCH_OPND, Opnd::const_ptr(Rc::into_raw(iseq_call))); + asm.load_into(scratch_reg, Opnd::const_ptr(Rc::into_raw(iseq_call))); asm.jmp(ZJITState::get_function_stub_hit_trampoline().into()); asm.compile(cb).map(|(code_ptr, gc_offsets)| { @@ -1979,7 +1977,7 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result Result { - let mut asm = Assembler::new(); + let (mut asm, scratch_reg) = Assembler::new_with_scratch_reg(); asm_comment!(asm, "function_stub_hit trampoline"); // Maintain alignment for x86_64, and set up a frame for arm64 properly @@ -1992,8 +1990,8 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Result Result Date: Tue, 14 Oct 2025 18:16:07 -0400 Subject: [PATCH 0336/2435] YJIT: Use `mem::take` over `drain(..).collect()` --- yjit/src/backend/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/backend/ir.rs b/yjit/src/backend/ir.rs index 40df3ae4d5830b..8205d6de76bd2f 100644 --- a/yjit/src/backend/ir.rs +++ b/yjit/src/backend/ir.rs @@ -1602,7 +1602,7 @@ impl Assembler if c_args.len() > 0 { // Resolve C argument dependencies let c_args_len = c_args.len() as isize; - let moves = Self::reorder_reg_moves(&c_args.drain(..).collect()); + let moves = Self::reorder_reg_moves(&std::mem::take(&mut c_args)); shift_live_ranges(&mut shifted_live_ranges, asm.insns.len(), moves.len() as isize - c_args_len); // Push batched C arguments From df5d63cfa2af8902526d83775bec8192e29fcd1b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 14 Oct 2025 18:26:08 -0400 Subject: [PATCH 0337/2435] [DOC] Fix typo in String#partition --- doc/string/partition.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index ece034ee66225e..ee445bd21f32ba 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -29,7 +29,7 @@ If +pattern+ is a Regexp, performs the equivalent of self.match(pattern) ["hello", "", ""] If +pattern+ is not a Regexp, converts it to a string (if it is not already one), -then performs the equivalet of self.index(pattern) +then performs the equivalent of self.index(pattern) (and does _not_ set {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): 'hello'.partition('h') # => ["", "h", "ello"] From 8d438678023aa453717954bb519308b5c3eec0fe Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 14 Oct 2025 17:30:52 -0700 Subject: [PATCH 0338/2435] [rubygems/rubygems] remove some memoization I don't think these methods are hotspots, and since gem specifications are sometimes serialized to yaml / marshal, I think we should remove as many instance variables as possible https://github.com/rubygems/rubygems/commit/40490d918b --- lib/rubygems/specification.rb | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 1d351f8aff9746..ea7b58f20e23a9 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -488,8 +488,6 @@ def platform=(platform) end @platform = @new_platform.to_s - - invalidate_memoized_attributes end ## @@ -1620,14 +1618,14 @@ def build_info_file # spec's cached gem. def cache_dir - @cache_dir ||= File.join base_dir, "cache" + File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file - @cache_file ||= File.join cache_dir, "#{full_name}.gem" + File.join cache_dir, "#{full_name}.gem" end ## @@ -1903,10 +1901,6 @@ def for_cache spec end - def full_name - @full_name ||= super - end - ## # Work around old bundler versions removing my methods # Can be removed once RubyGems can no longer install Bundler 2.5 @@ -2044,17 +2038,6 @@ def base_dir end end - ## - # Expire memoized instance variables that can incorrectly generate, replace - # or miss files due changes in certain attributes used to compute them. - - def invalidate_memoized_attributes - @full_name = nil - @cache_file = nil - end - - private :invalidate_memoized_attributes - def inspect # :nodoc: if $DEBUG super @@ -2093,8 +2076,6 @@ def licenses def internal_init # :nodoc: super @bin_dir = nil - @cache_dir = nil - @cache_file = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @@ -2606,9 +2587,6 @@ def validate_permissions def version=(version) @version = Gem::Version.create(version) - return if @version.nil? - - invalidate_memoized_attributes end def stubbed? From 5bda42e4dec9106fb310c30178c1283ff92ebfa2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 14 Oct 2025 21:45:10 -0400 Subject: [PATCH 0339/2435] ZJIT: Include GC object dump when seeing dead objects Strictly more info than just the builtin_type from `assert_ne!`. Old: assertion `left != right` failed: ZJIT should only see live objects left: 0 right: 0 New: ZJIT saw a dead object. T_type=0, out-of-heap:0x0000000110d4bb40 Also, the new `VALUE::obj_info` is more flexible for print debugging than the dump_info() it replaces. It now allows you to use it as part of a `format!` string instead of always printing to stderr for you. --- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 32 ++++++++++++++++++++++++++------ zjit/src/cruby_bindings.inc.rs | 6 +++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index b40986c0f6e154..975a2d91570613 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -82,7 +82,7 @@ fn main() { .allowlist_type("ruby_rstring_flags") // This function prints info about a value and is useful for debugging - .allowlist_function("rb_obj_info_dump") + .allowlist_function("rb_raw_obj_info") .allowlist_function("ruby_init") .allowlist_function("ruby_init_stack") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1f514787f113d0..1a2dce03eda316 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -90,7 +90,7 @@ use std::convert::From; use std::ffi::{c_void, CString, CStr}; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::os::raw::{c_char, c_int, c_uint}; use std::panic::{catch_unwind, UnwindSafe}; @@ -400,10 +400,27 @@ pub enum ClassRelationship { NoRelation, } +/// A print adapator for debug info about a [VALUE]. Includes info +/// the GC knows about the handle. Example: `println!("{}", value.obj_info());`. +pub struct ObjInfoPrinter(VALUE); + +impl Display for ObjInfoPrinter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use std::mem::MaybeUninit; + const BUFFER_SIZE: usize = 0x100; + let mut buffer: MaybeUninit<[c_char; BUFFER_SIZE]> = MaybeUninit::uninit(); + let info = unsafe { + rb_raw_obj_info(buffer.as_mut_ptr().cast(), BUFFER_SIZE, self.0); + CStr::from_ptr(buffer.as_ptr().cast()).to_string_lossy() + }; + write!(f, "{info}") + } +} + impl VALUE { - /// Dump info about the value to the console similarly to rp(VALUE) - pub fn dump_info(self) { - unsafe { rb_obj_info_dump(self) } + /// Get a printer for raw debug info from `rb_obj_info()` about the value. + pub fn obj_info(self) -> ObjInfoPrinter { + ObjInfoPrinter(self) } /// Return whether the value is truthy or falsy in Ruby -- only nil and false are falsy. @@ -507,8 +524,11 @@ impl VALUE { pub fn class_of(self) -> VALUE { if !self.special_const_p() { let builtin_type = self.builtin_type(); - assert_ne!(builtin_type, RUBY_T_NONE, "ZJIT should only see live objects"); - assert_ne!(builtin_type, RUBY_T_MOVED, "ZJIT should only see live objects"); + assert!( + builtin_type != RUBY_T_NONE && builtin_type != RUBY_T_MOVED, + "ZJIT saw a dead object. T_type={builtin_type}, {}", + self.obj_info() + ); } unsafe { rb_yarv_class_of(self) } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 56b569e064c0b0..f18c0035ee0f67 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -839,7 +839,6 @@ unsafe extern "C" { pub fn rb_ivar_set(obj: VALUE, name: ID, val: VALUE) -> VALUE; pub fn rb_ivar_defined(obj: VALUE, name: ID) -> VALUE; pub fn rb_attr_get(obj: VALUE, name: ID) -> VALUE; - pub fn rb_obj_info_dump(obj: VALUE); pub fn rb_class_allocate_instance(klass: VALUE) -> VALUE; pub fn rb_obj_equal(obj1: VALUE, obj2: VALUE) -> VALUE; pub fn rb_reg_new_ary(ary: VALUE, options: ::std::os::raw::c_int) -> VALUE; @@ -872,6 +871,11 @@ unsafe extern "C" { cfp: *const rb_control_frame_t, ) -> *const rb_callable_method_entry_t; pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; + pub fn rb_raw_obj_info( + buff: *mut ::std::os::raw::c_char, + buff_size: usize, + obj: VALUE, + ) -> *const ::std::os::raw::c_char; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; From 26d1e6947e174b499aae9833d9a4c434fa9a5415 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 09:28:20 +0900 Subject: [PATCH 0340/2435] [rubygems/rubygems] Replaced Bundler::SharedHelpers.major_deprecation to feature_removed! or feature_deprecated! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/b1b963b34a Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/cli.rb | 3 +-- lib/bundler/cli/config.rb | 3 +-- lib/bundler/cli/update.rb | 2 +- lib/bundler/definition.rb | 2 +- lib/bundler/dsl.rb | 8 ++---- lib/bundler/shared_helpers.rb | 19 ++----------- spec/bundler/bundler/shared_helpers_spec.rb | 30 --------------------- 7 files changed, 8 insertions(+), 59 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 62225a352d71fa..9c7c1217fbf8bd 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -447,9 +447,8 @@ def cache D def exec(*args) if ARGV.include?("--no-keep-file-descriptors") - message = "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/exec" diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb index d963679085ebb7..6a77e4a65ea386 100644 --- a/lib/bundler/cli/config.rb +++ b/lib/bundler/cli/config.rb @@ -26,8 +26,7 @@ def base(name = nil, *value) end message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." - removed_message = "Using the `config` command without a subcommand [list, get, set, unset] has been removed. Use `bundle #{new_args.join(" ")}` instead." - SharedHelpers.major_deprecation 4, message, removed_message: removed_message + SharedHelpers.feature_deprecated! message Base.new(options, name, value, self).run end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 4b4ba3c64712be..9cc90acc58536a 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -26,7 +26,7 @@ def run if Bundler.settings[:update_requires_all_flag] raise InvalidOption, "To update everything, pass the `--all` flag." end - SharedHelpers.major_deprecation 4, "Pass --all to `bundle update` to update everything" + SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything" elsif !full_update && options[:all] raise InvalidOption, "Cannot specify --all along with specific options." end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index cc2394fda60f0a..6cae8964d8145a 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -372,7 +372,7 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or msg = "`Definition#lock` was passed a target file argument. #{suggestion}" - Bundler::SharedHelpers.major_deprecation 2, msg + Bundler::SharedHelpers.feature_removed! msg end write_lock(target_lockfile, preserve_unknown_sections) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index d98dbd4759e922..998a8134f8e12b 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -483,14 +483,10 @@ def validate_keys(command, opts, valid_keys) def normalize_source(source) case source when :gemcutter, :rubygems, :rubyforge - message = - "The source :#{source} is deprecated because HTTP requests are insecure.\n" \ - "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." removed_message = "The source :#{source} is disallowed because HTTP requests are insecure.\n" \ "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - "http://rubygems.org" + Bundler::SharedHelpers.feature_removed! removed_message when String source else @@ -509,7 +505,7 @@ def check_path_source_safety " gem 'rails'\n" \ " end\n\n" - SharedHelpers.major_deprecation(2, msg.strip) + SharedHelpers.feature_removed! msg.strip end def check_rubygems_source_safety diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 41b7128d36e18e..987a68afd75326 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -125,28 +125,13 @@ def filesystem_access(path, action = :write, &block) raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.") end - def major_deprecation(major_version, message, removed_message: nil, print_caller_location: false) - if print_caller_location - caller_location = caller_locations(2, 2).first - suffix = " (called at #{caller_location.path}:#{caller_location.lineno})" - message += suffix - end - - require_relative "../bundler" - - feature_flag = Bundler.feature_flag + def feature_deprecated!(message) + return unless prints_major_deprecations? - if feature_flag.removed_major?(major_version) - feature_removed!(removed_message || message) - end - - return unless feature_flag.deprecated_major?(major_version) && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end def feature_removed!(message) - require_relative "../bundler" - require_relative "errors" raise RemovedError, "[REMOVED] #{message}" end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 5b3a9c17a7e8d5..0aacb93c16e2a9 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -515,34 +515,4 @@ end end end - - describe "#major_deprecation" do - before { allow(Bundler).to receive(:feature_flag).and_return(Bundler::FeatureFlag.new(37)) } - before { allow(Bundler.ui).to receive(:warn) } - - it "prints and raises nothing below the deprecated major version" do - subject.major_deprecation(38, "Message") - subject.major_deprecation(39, "Message", removed_message: "Removal", print_caller_location: true) - expect(Bundler.ui).not_to have_received(:warn) - end - - it "prints but does not raise _at_ the deprecated major version" do - subject.major_deprecation(37, "Message") - subject.major_deprecation(37, "Message", removed_message: "Removal") - expect(Bundler.ui).to have_received(:warn).with("[DEPRECATED] Message").twice - - subject.major_deprecation(37, "Message", print_caller_location: true) - expect(Bundler.ui).to have_received(:warn). - with(a_string_matching(/^\[DEPRECATED\] Message \(called at .*:\d+\)$/)) - end - - it "raises the appropriate errors when _past_ the deprecated major version" do - expect { subject.major_deprecation(36, "Message") }. - to raise_error(Bundler::RemovedError, "[REMOVED] Message") - expect { subject.major_deprecation(36, "Message", removed_message: "Removal") }. - to raise_error(Bundler::RemovedError, "[REMOVED] Removal") - expect { subject.major_deprecation(35, "Message", removed_message: "Removal", print_caller_location: true) }. - to raise_error(Bundler::RemovedError, "[REMOVED] Removal") - end - end end From 1142abb1de10d8c47278d09a90d7c0cc713f59af Mon Sep 17 00:00:00 2001 From: Marino Bonetti Date: Wed, 15 Oct 2025 06:58:24 +0200 Subject: [PATCH 0341/2435] [DOC] Update making_changes_to_stdlibs.md mirror Example CSV is no more part of the standard lib, but the documentation was not updated (the example link was broken for the master branch) Selected ERB that has the dedicated directory, like CSV. --- doc/contributing/making_changes_to_stdlibs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/making_changes_to_stdlibs.md b/doc/contributing/making_changes_to_stdlibs.md index 2156a61e3948a3..2ceb2e60756714 100644 --- a/doc/contributing/making_changes_to_stdlibs.md +++ b/doc/contributing/making_changes_to_stdlibs.md @@ -4,7 +4,7 @@ Everything in the [lib](https://github.com/ruby/ruby/tree/master/lib) directory If you'd like to make contributions to standard libraries, do so in the standalone repositories, and the changes will be automatically mirrored into the Ruby repository. -For example, CSV lives in [a separate repository](https://github.com/ruby/csv) and is mirrored into [Ruby](https://github.com/ruby/ruby/tree/master/lib/csv). +For example, ERB lives in [a separate repository](https://github.com/ruby/erb) and is mirrored into [Ruby](https://github.com/ruby/ruby/tree/master/lib/erb). ## Maintainers From 8104c833ef47429ded66edf328fc7f9b57874cb4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 12:23:40 +0900 Subject: [PATCH 0342/2435] [rubygems/rubygems] Fixed wrong option message https://github.com/rubygems/rubygems/commit/15be905c44 --- lib/bundler/cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 9c7c1217fbf8bd..b8a8be82c18530 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -438,7 +438,7 @@ def cache map aliases_for("cache") desc "exec [OPTIONS]", "Run the command in context of the bundle" - method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is deprecated" + method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)." method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile" long_desc <<-D Exec runs a command, providing it access to the gems in the bundle. While using From fdc37df3d3bd8cb2c4b2fdbafa9e3a6d93d51ab4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 13:09:58 +0900 Subject: [PATCH 0343/2435] [rubygems/rubygems] Removed deprecated settings methods https://github.com/rubygems/rubygems/commit/89bcdfc941 --- lib/bundler/feature_flag.rb | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 8ec62fc1c9e638..dea8abedba393c 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -2,29 +2,6 @@ module Bundler class FeatureFlag - def self.settings_flag(flag, &default) - unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) - raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" - end - - settings_method("#{flag}?", flag, &default) - end - private_class_method :settings_flag - - def self.settings_option(key, &default) - settings_method(key, key, &default) - end - private_class_method :settings_option - - def self.settings_method(name, key, &default) - define_method(name) do - value = Bundler.settings[key] - value = instance_eval(&default) if value.nil? - value - end - end - private_class_method :settings_method - (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } def removed_major?(target_major_version) From d99a4295a8e2727023292ac78c058bdfc922c2a6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 13:22:30 +0900 Subject: [PATCH 0344/2435] [rubygems/rubygems] Restore an accidentally changes of cache_spec.rb https://github.com/rubygems/rubygems/commit/06508374aa --- spec/bundler/commands/cache_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 283719bedf53b7..1e90f01ce7f8c9 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -207,6 +207,17 @@ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end + it "prints an error when using legacy windows rubies" do + gemfile <<-D + source "https://gem.repo1" + gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] + D + + bundle "cache --all-platforms", raise_on_error: false + expect(err).to include("removed") + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).not_to exist + end + it "does not attempt to install gems in without groups" do build_repo4 do build_gem "uninstallable", "2.0" do |s| From 92cbd7ec3354e652d8fcc52ad772bc4699475469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:22:06 +0200 Subject: [PATCH 0345/2435] [rubygems/rubygems] Test current clean after bundle update behavior https://github.com/rubygems/rubygems/commit/c43e35c3ea --- spec/bundler/commands/clean_spec.rb | 43 ++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index f2cd8868935d7b..6b678d0aa5451b 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -220,7 +220,7 @@ def should_not_have_gems(*gems) expect(bundled_app("symlink-path/#{Bundler.ruby_scope}/bundler/gems/foo-#{revision[0..11]}")).to exist end - it "removes old git gems" do + it "removes old git gems on bundle update" do build_git "foo-bar", path: lib_path("foo-bar") revision = revision_for(lib_path("foo-bar")) @@ -383,7 +383,7 @@ def should_not_have_gems(*gems) expect(out).to include("myrack (1.0.0)").and include("thin (1.0)") end - it "automatically cleans when path has not been set", bundler: "5" do + it "does not clean on bundle update when path has not been set" do build_repo2 install_gemfile <<-G @@ -398,8 +398,43 @@ def should_not_have_gems(*gems) bundle "update", all: true - files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*")) - files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") } + files = Pathname.glob(default_bundle_path("*", "*")) + files.map! {|f| f.to_s.sub(default_bundle_path.to_s, "") } + expected_files = %W[ + /bin/bundle + /bin/bundler + /cache/bundler-#{Bundler::VERSION}.gem + /cache/foo-1.0.1.gem + /cache/foo-1.0.gem + /gems/bundler-#{Bundler::VERSION} + /gems/foo-1.0 + /gems/foo-1.0.1 + /specifications/bundler-#{Bundler::VERSION}.gemspec + /specifications/foo-1.0.1.gemspec + /specifications/foo-1.0.gemspec + ] + expected_files += ["/bin/bundle.bat", "/bin/bundler.bat"] if Gem.win_platform? + + expect(files.sort).to eq(expected_files.sort) + end + + it "will automatically clean on bundle update when path has not been set", bundler: "5" do + build_repo2 + + install_gemfile <<-G + source "https://gem.repo2" + + gem "foo" + G + + update_repo2 do + build_gem "foo", "1.0.1" + end + + bundle "update", all: true + + files = Pathname.glob(local_gem_path("*", "*")) + files.map! {|f| f.to_s.sub(local_gem_path.to_s, "") } expect(files.sort).to eq %w[ /cache/foo-1.0.1.gem /gems/foo-1.0.1 From a60b56c33d0eb40d8ad9731fe8611f401923a11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:22:13 +0200 Subject: [PATCH 0346/2435] [rubygems/rubygems] Use `default_cache_path` helper for brevity https://github.com/rubygems/rubygems/commit/29a12c3d46 --- spec/bundler/install/gemfile/git_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 216548cf27ccee..faf938383269ac 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -70,7 +70,7 @@ it "caches the git repo" do install_base_gemfile - expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1 + expect(Dir["#{default_cache_path}/git/foo-1.0-*"]).to have_attributes size: 1 end it "does not write to cache on bundler/setup" do From c3e6e65591e881fd8816de79d00d4be73a4edf3f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 13:39:32 +0900 Subject: [PATCH 0347/2435] [rubygems/rubygems] Removed duplicated examples with bundle install https://github.com/rubygems/rubygems/commit/59b909fa74 --- spec/bundler/commands/install_spec.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 65903b3e780024..8506005746a25b 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -327,21 +327,6 @@ end end - describe "doing bundle install foo" do - before do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - it "works" do - bundle "config set --local path vendor" - bundle "install" - expect(the_bundle).to include_gems "myrack 1.0" - end - end - it "gives useful errors if no global sources are set, and gems not installed locally, with and without a lockfile" do install_gemfile <<-G, raise_on_error: false gem "myrack" From 6c9acb533f747682df2f681070596c7bbaccde18 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 13:43:29 +0900 Subject: [PATCH 0348/2435] [rubygems/rubygems] Added example for global path with Gemfile https://github.com/rubygems/rubygems/commit/cd1493eec4 --- spec/bundler/other/major_deprecation_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index d701c4008dbf4c..1b04af76325795 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -605,6 +605,21 @@ end end + context "with a global path source" do + before do + build_lib "foo" + + install_gemfile <<-G, raise_on_error: false + path "#{lib_path("foo-1.0")}" + gem 'foo' + G + end + + it "shows an error" do + expect(err).to include("You can no longer specify a path source by itself") + end + end + context "when Bundler.setup is run in a ruby script" do before do create_file "gems.rb", "source 'https://gem.repo1'" From 51b2c5a4cd8d1a26f25767366e95391b9de203b5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 14:11:06 +0900 Subject: [PATCH 0349/2435] [rubygems/rubygems] Removed obsoleted option from bundle-exec manpages https://github.com/rubygems/rubygems/commit/6a3342541a --- lib/bundler/man/bundle-exec.1 | 5 +---- lib/bundler/man/bundle-exec.1.ronn | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 93788a850f463d..24c84889b5bf39 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" -\fBbundle exec\fR [\-\-keep\-file\-descriptors] [\-\-gemfile=GEMFILE] \fIcommand\fR +\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR .SH "DESCRIPTION" This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\. .P @@ -13,9 +13,6 @@ Essentially, if you would normally have run something like \fBrspec spec/my_spec Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\. .SH "OPTIONS" .TP -\fB\-\-keep\-file\-descriptors\fR -Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\. -.TP \fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. .SH "BUNDLE INSTALL \-\-BINSTUBS" diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn index 3d3f0eed7b89da..e51a66a0840c54 100644 --- a/lib/bundler/man/bundle-exec.1.ronn +++ b/lib/bundler/man/bundle-exec.1.ronn @@ -3,7 +3,7 @@ bundle-exec(1) -- Execute a command in the context of the bundle ## SYNOPSIS -`bundle exec` [--keep-file-descriptors] [--gemfile=GEMFILE] +`bundle exec` [--gemfile=GEMFILE] ## DESCRIPTION @@ -20,10 +20,6 @@ available on your shell's `$PATH`. ## OPTIONS -* `--keep-file-descriptors`: - Passes all file descriptors to the new processes. Default is true from - bundler version 2.2.26. Setting it to false is now deprecated. - * `--gemfile=GEMFILE`: Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. From dce202d6d653dfc1b2c64822fe53066c3c558a78 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 9 Oct 2025 17:23:43 +0200 Subject: [PATCH 0350/2435] [rubygems/rubygems] Add checksum of gems hosted on private servers: - ### Problem Running `bundle lock --add-checksums` doesn't add the checksum of gems hosted on server that don't implement the compact index API. This result in a lockfile which is unusable in production as some checksums will be missing and Bundler raising an error. Users can work around this problem by running: `BUNDLE_LOCKFILE_CHECKSUMS=true bundle install --force` But this means redownloading and installing all gems which isn't great and slow on large apps. ### Context Bundler uses the Compact Index API to get the checksum of gems, but most private gem servers don't implement the compact index API (such as cloudsmith or packagecloud). This results in a soft failure on bundler side, and bundler leaving out blank checksum for those gems. ### Solution For gems that are hosted on private servers that don't send back the checksum of the gem, I'd like to fallback to the `bundle install` mechanism, which don't rely on an external API but instead compute the checksum of the package installed on disk. This patch goes through the spec that didn't return a checksum, and compute one if the package exists on disk. This solution makes the `bundle lock --add-checksums` command actually usable in real world scenarios while keeping the `bundle lock` command fast enough. https://github.com/rubygems/rubygems/commit/8e9abb5472 --- lib/bundler/definition.rb | 13 ++++- spec/bundler/commands/lock_spec.rb | 91 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6cae8964d8145a..3c8c13b1303171 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -540,7 +540,18 @@ def add_checksums setup_domain!(add_checksums: true) - specs # force materialization to real specifications, so that checksums are fetched + # force materialization to real specifications, so that checksums are fetched + specs.each do |spec| + next unless spec.source.is_a?(Bundler::Source::Rubygems) + # Checksum was fetched from the compact index API. + next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec) + # The gem isn't installed, can't compute the checksum. + next unless spec.loaded_from + + package = Gem::Package.new(spec.source.cached_built_in_gem(spec)) + checksum = Checksum.from_gem_package(package) + spec.source.checksum_store.register(spec, checksum) + end end private diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index a7460ed695a456..36493e108a2cf4 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -2174,6 +2174,97 @@ L end + it "add checksums for gems installed on disk" do + build_repo4 do + build_gem "warning", "18.0.0" + end + + bundle "config lockfile_checksums false" + + simulate_platform "x86_64-linux" do + install_gemfile(<<-G, artifice: "endpoint") + source "https://gem.repo4" + + gem "warning" + G + + bundle "config --delete lockfile_checksums" + bundle("lock --add-checksums", artifice: "endpoint") + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "warning", "18.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + warning + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "doesn't add checksum for gems not installed on disk" do + lockfile(<<~L) + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile(<<~G) + source "https://gem.repo4" + + gem "warning" + G + + build_repo4 do + build_gem "warning", "18.0.0" + end + + FileUtils.rm_rf("#{gem_repo4}/gems") + + bundle("lock --add-checksums", artifice: "endpoint") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + CHECKSUMS + warning (18.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + context "when re-resolving to include prereleases" do before do build_repo4 do From bb4526b9b1c25f2e6435802321137d0d33216b76 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 15 Oct 2025 05:56:31 -0400 Subject: [PATCH 0351/2435] ZJIT: Add trace exit counter (#14831) --- zjit/src/backend/lir.rs | 22 ++++++++++++++++++---- zjit/src/options.rs | 32 +++++++++++++++++++++++--------- zjit/src/state.rs | 6 +++--- zjit/src/stats.rs | 25 ++++++++++++++++++++----- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index aad5600f569767..6efb3e1259e4e6 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -4,9 +4,9 @@ use std::mem::take; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::SideExitReason; -use crate::options::{debug, get_option}; +use crate::options::{debug, get_option, TraceExits}; use crate::cruby::VALUE; -use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError}; +use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; use crate::state::rb_zjit_record_exit_stack; @@ -1644,8 +1644,22 @@ impl Assembler } } - if get_option!(trace_side_exits) { - asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8)); + if get_option!(trace_side_exits).is_some() { + // Get the corresponding `Counter` for the current `SideExitReason`. + let side_exit_counter = side_exit_counter(reason); + + // Only record the exit if `trace_side_exits` is defined and the counter is either the one specified + let should_record_exit = get_option!(trace_side_exits) + .map(|trace| match trace { + TraceExits::All => true, + TraceExits::Counter(counter) if counter == side_exit_counter => true, + _ => false, + }) + .unwrap_or(false); + + if should_record_exit { + asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8)); + } } asm_comment!(self, "exit to the interpreter"); diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 4ef998acede36c..b7b20e63c4484d 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -3,6 +3,7 @@ use std::{ffi::{CStr, CString}, ptr::null}; use std::os::raw::{c_char, c_int, c_uint}; use crate::cruby::*; +use crate::stats::Counter; use std::collections::HashSet; /// Default --zjit-num-profiles @@ -72,7 +73,7 @@ pub struct Options { pub dump_disasm: bool, /// Trace and write side exit source maps to /tmp for stackprof. - pub trace_side_exits: bool, + pub trace_side_exits: Option, /// Frequency of tracing side exits. pub trace_side_exits_sample_interval: usize, @@ -102,7 +103,7 @@ impl Default for Options { dump_hir_graphviz: None, dump_lir: false, dump_disasm: false, - trace_side_exits: false, + trace_side_exits: None, trace_side_exits_sample_interval: 0, perf: false, allowed_iseqs: None, @@ -125,12 +126,20 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", "Log compiled ISEQs to the file. The file will be truncated."), - ("--zjit-trace-exits", - "Record Ruby source location when side-exiting."), - ("--zjit-trace-exits-sample-rate", + ("--zjit-trace-exits[=counter]", + "Record source on side-exit. `Counter` picks specific counter."), + ("--zjit-trace-exits-sample-rate=num", "Frequency at which to record side exits. Must be `usize`.") ]; +#[derive(Copy, Clone, Debug)] +pub enum TraceExits { + // Trace all exits + All, + // Trace exits for a specific `Counter` + Counter(Counter), +} + #[derive(Clone, Copy, Debug)] pub enum DumpHIR { // Dump High-level IR without Snapshot @@ -249,13 +258,18 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.print_stats = false; } - ("trace-exits", "") => { - options.trace_side_exits = true; + ("trace-exits", exits) => { + options.trace_side_exits = match exits { + "" => Some(TraceExits::All), + name => Counter::get(name).map(TraceExits::Counter), + } } ("trace-exits-sample-rate", sample_interval) => { - // Even if `trace_side_exits` is already set, set it. - options.trace_side_exits = true; + // If not already set, then set it to `TraceExits::All` by default. + if options.trace_side_exits.is_none() { + options.trace_side_exits = Some(TraceExits::All); + } // `sample_interval ` must provide a string that can be validly parsed to a `usize`. options.trace_side_exits_sample_interval = sample_interval.parse::().ok()?; } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 1b766d5bc4b5aa..c0e9e0b77ca909 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -82,7 +82,7 @@ impl ZJITState { let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap(); let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap(); - let exit_locations = if get_option!(trace_side_exits) { + let exit_locations = if get_option!(trace_side_exits).is_some() { Some(SideExitLocations::default()) } else { None @@ -369,7 +369,7 @@ fn try_increment_existing_stack( /// Record a backtrace with ZJIT side exits #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return; } @@ -425,7 +425,7 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { /// Mark `raw_samples` so they can be used by rb_zjit_add_frame. pub fn gc_mark_raw_samples() { // Return if ZJIT is not enabled - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return; } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index d902c69b795c4b..6faa328a1ca736 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -57,6 +57,17 @@ macro_rules! make_counters { $( Counter::$counter_name => stringify!($counter_name), )+ } } + + pub fn get(name: &str) -> Option { + match name { + $( stringify!($default_counter_name) => Some(Counter::$default_counter_name), )+ + $( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+ + $( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+ + $( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+ + $( stringify!($counter_name) => Some(Counter::$counter_name), )+ + _ => None, + } + } } /// Map a counter to a pointer @@ -298,11 +309,11 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { } } -pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { +pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { use crate::hir::SideExitReason::*; use crate::hir::CallType::*; use crate::stats::Counter::*; - let counter = match reason { + match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, UnhandledCallType(Tailcall) => exit_unhandled_tailcall, UnhandledCallType(Splat) => exit_unhandled_splat, @@ -324,7 +335,11 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { StackOverflow => exit_stackoverflow, BlockParamProxyModified => exit_block_param_proxy_modified, BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc, - }; + } +} + +pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { + let counter = side_exit_counter(reason); counter_ptr(counter) } @@ -563,7 +578,7 @@ pub struct SideExitLocations { #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { // Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set. - if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits) { + if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits.is_some()) { Qtrue } else { Qfalse @@ -574,7 +589,7 @@ pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: /// into raw, lines, and frames hash for RubyVM::YJIT.exit_locations. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return Qnil; } From 63a58c7943603b5774e0d55dadf6035b6235a72a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 15 Oct 2025 10:24:10 -0400 Subject: [PATCH 0352/2435] ZJIT: Don't const-fold Array#[] on non-frozen array (#14841) Accidentally added in https://github.com/ruby/ruby/pull/14679 --- zjit/src/hir.rs | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 33c034b819a7ec..2d1eb6425a5181 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2630,9 +2630,13 @@ impl Function { Insn::ArrayArefFixnum { array, index } if self.type_of(array).ruby_object_known() && self.type_of(index).ruby_object_known() => { let array_obj = self.type_of(array).ruby_object().unwrap(); - let index = self.type_of(index).fixnum_value().unwrap(); - let val = unsafe { rb_yarv_ary_entry_internal(array_obj, index) }; - self.new_insn(Insn::Const { val: Const::Value(val) }) + if array_obj.is_frozen() { + let index = self.type_of(index).fixnum_value().unwrap(); + let val = unsafe { rb_yarv_ary_entry_internal(array_obj, index) }; + self.new_insn(Insn::Const { val: Const::Value(val) }) + } else { + insn_id + } } Insn::Test { val } if self.type_of(val).is_known_falsy() => { self.new_insn(Insn::Const { val: Const::CBool(false) }) @@ -11556,6 +11560,37 @@ mod opt_tests { "); } + #[test] + fn test_dont_eliminate_load_from_non_frozen_array() { + eval(r##" + S = [4,5,6] + def test = S[0] + test + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, S) + v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Array@0x1010) + v28:BasicObject = ArrayArefFixnum v24, v12 + CheckInterrupts + Return v28 + "); + // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we + // actually do the load at run-time. + } + #[test] fn test_eliminate_load_from_frozen_array_in_bounds() { eval(r##" From 31a1a39ace8971f7ac8e1d192c4e61ae89108422 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 15 Oct 2025 13:27:30 -0400 Subject: [PATCH 0353/2435] ZJIT: Never yield to the GC while compiling This fixes a reliable "ZJIT saw a dead object" repro on my machine, and should fix the flaky ones on CI. The code for disabling the GC is the same as the code in newobj_of(). See: https://github.com/ruby/ruby/actions/runs/18511676257/job/52753782036 --- zjit/bindgen/src/main.rs | 2 ++ zjit/src/cruby.rs | 15 ++++++++++++++- zjit/src/cruby_bindings.inc.rs | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 975a2d91570613..77d482db4e9d97 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -130,6 +130,8 @@ fn main() { .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") .allowlist_function("rb_class_get_superclass") + .allowlist_function("rb_gc_disable_no_rest") + .allowlist_function("rb_gc_enable") .allowlist_function("rb_gc_mark") .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1a2dce03eda316..4eb1d3a17c836e 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -890,6 +890,14 @@ where let mut recursive_lock_level: c_uint = 0; unsafe { rb_jit_vm_lock_then_barrier(&mut recursive_lock_level, file, line) }; + // Ensure GC is off while we have the VM lock because: + // 1. We create many transient Rust collections that hold VALUEs during compilation. + // It's extremely tricky to properly marked and reference update these, not to + // mention the overhead and ergonomics issues. + // 2. If we yield to the GC while compiling, it re-enters our mark and update functions. + // This breaks `&mut` exclusivity since mark functions derive fresh `&mut` from statics + // while there is a stack frame below it that has an overlapping `&mut`. That's UB. + let gc_disabled_pre_call = unsafe { rb_gc_disable_no_rest() }.test(); let ret = match catch_unwind(func) { Ok(result) => result, @@ -909,7 +917,12 @@ where } }; - unsafe { rb_jit_vm_unlock(&mut recursive_lock_level, file, line) }; + unsafe { + if !gc_disabled_pre_call { + rb_gc_enable(); + } + rb_jit_vm_unlock(&mut recursive_lock_level, file, line); + }; ret } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index f18c0035ee0f67..6a6263ab15110a 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -745,6 +745,7 @@ unsafe extern "C" { pub fn rb_gc_mark(obj: VALUE); pub fn rb_gc_mark_movable(obj: VALUE); pub fn rb_gc_location(obj: VALUE) -> VALUE; + pub fn rb_gc_enable() -> VALUE; pub fn rb_gc_writebarrier(old: VALUE, young: VALUE); pub fn rb_class_get_superclass(klass: VALUE) -> VALUE; pub static mut rb_cObject: VALUE; @@ -876,6 +877,7 @@ unsafe extern "C" { buff_size: usize, obj: VALUE, ) -> *const ::std::os::raw::c_char; + pub fn rb_gc_disable_no_rest() -> VALUE; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; From 27ff586152321a43cc678f65e5f489a2c0f1e9af Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Fri, 10 Oct 2025 14:47:46 -0400 Subject: [PATCH 0354/2435] We can't grab the VM Lock in free functions This is due to the way MMTK frees objects, which is on another native thread. Due to this, there's no `ec` so we can't grab the VM Lock. This was causing issues in release builds of MMTK on CI like: ``` /home/runner/work/ruby/ruby/build/ruby(sigsegv+0x46) [0x557905117ef6] ../src/signal.c:948 /lib/x86_64-linux-gnu/libc.so.6(0x7f555f845330) [0x7f555f845330] /home/runner/work/ruby/ruby/build/ruby(rb_ec_thread_ptr+0x0) [0x5579051d59d5] ../src/vm_core.h:2087 /home/runner/work/ruby/ruby/build/ruby(rb_ec_ractor_ptr) ../src/vm_core.h:2036 /home/runner/work/ruby/ruby/build/ruby(rb_current_execution_context) ../src/vm_core.h:2105 /home/runner/work/ruby/ruby/build/ruby(rb_current_ractor_raw) ../src/vm_core.h:2104 /home/runner/work/ruby/ruby/build/ruby(rb_current_ractor) ../src/vm_core.h:2112 /home/runner/work/ruby/ruby/build/ruby(rb_current_ractor) ../src/vm_core.h:2110 /home/runner/work/ruby/ruby/build/ruby(vm_locked) ../src/vm_sync.c:15 /home/runner/work/ruby/ruby/build/ruby(rb_vm_lock_enter_body) ../src/vm_sync.c:141 /home/runner/work/ruby/ruby/build/ruby(rb_vm_lock_enter+0xa) [0x557905390a5a] ../src/vm_sync.h:76 /home/runner/work/ruby/ruby/build/ruby(fiber_pool_stack_release) ../src/cont.c:777 /home/runner/work/ruby/ruby/build/ruby(fiber_stack_release+0xe) [0x557905392075] ../src/cont.c:919 /home/runner/work/ruby/ruby/build/ruby(cont_free) ../src/cont.c:1087 /home/runner/work/ruby/ruby/build/ruby(fiber_free) ../src/cont.c:1180 ``` This would have ran into an assertion error in a debug build but we don't run debug builds of MMTK on Github's CI. Co-authored-by: john.hawthorn@shopify.com --- cont.c | 69 +++++++++++++++++++++++++++++++------------------------ vm.c | 3 +++ vm_core.h | 2 ++ 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/cont.c b/cont.c index 0d7245ed9c20f1..f885cdb1095032 100644 --- a/cont.c +++ b/cont.c @@ -774,44 +774,40 @@ static void fiber_pool_stack_release(struct fiber_pool_stack * stack) { struct fiber_pool * pool = stack->pool; - RB_VM_LOCK_ENTER(); - { - struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size); + struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size); - if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used); + if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used); - // Copy the stack details into the vacancy area: - vacancy->stack = *stack; - // After this point, be careful about updating/using state in stack, since it's copied to the vacancy area. + // Copy the stack details into the vacancy area: + vacancy->stack = *stack; + // After this point, be careful about updating/using state in stack, since it's copied to the vacancy area. - // Reset the stack pointers and reserve space for the vacancy data: - fiber_pool_vacancy_reset(vacancy); + // Reset the stack pointers and reserve space for the vacancy data: + fiber_pool_vacancy_reset(vacancy); - // Push the vacancy into the vancancies list: - pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies); - pool->used -= 1; + // Push the vacancy into the vancancies list: + pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies); + pool->used -= 1; #ifdef FIBER_POOL_ALLOCATION_FREE - struct fiber_pool_allocation * allocation = stack->allocation; + struct fiber_pool_allocation * allocation = stack->allocation; - allocation->used -= 1; + allocation->used -= 1; - // Release address space and/or dirty memory: - if (allocation->used == 0) { - fiber_pool_allocation_free(allocation); - } - else if (stack->pool->free_stacks) { - fiber_pool_stack_free(&vacancy->stack); - } + // Release address space and/or dirty memory: + if (allocation->used == 0) { + fiber_pool_allocation_free(allocation); + } + else if (stack->pool->free_stacks) { + fiber_pool_stack_free(&vacancy->stack); + } #else - // This is entirely optional, but clears the dirty flag from the stack - // memory, so it won't get swapped to disk when there is memory pressure: - if (stack->pool->free_stacks) { - fiber_pool_stack_free(&vacancy->stack); - } -#endif + // This is entirely optional, but clears the dirty flag from the stack + // memory, so it won't get swapped to disk when there is memory pressure: + if (stack->pool->free_stacks) { + fiber_pool_stack_free(&vacancy->stack); } - RB_VM_LOCK_LEAVE(); +#endif } static inline void @@ -924,6 +920,17 @@ fiber_stack_release(rb_fiber_t * fiber) rb_ec_clear_vm_stack(ec); } +static void +fiber_stack_release_locked(rb_fiber_t *fiber) +{ + if (!ruby_vm_during_cleanup) { + // We can't try to acquire the VM lock here because MMTK calls free in its own native thread which has no ec. + // This assertion will fail on MMTK but we currently don't have CI for debug releases of MMTK, so we can assert for now. + ASSERT_vm_locking_with_barrier(); + } + fiber_stack_release(fiber); +} + static const char * fiber_status_name(enum fiber_status s) { @@ -1084,7 +1091,7 @@ cont_free(void *ptr) else { rb_fiber_t *fiber = (rb_fiber_t*)cont; coroutine_destroy(&fiber->context); - fiber_stack_release(fiber); + fiber_stack_release_locked(fiber); } RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr); @@ -2741,7 +2748,9 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi // We cannot free the stack until the pthread is joined: #ifndef COROUTINE_PTHREAD_CONTEXT if (resuming_fiber && FIBER_TERMINATED_P(fiber)) { - fiber_stack_release(fiber); + RB_VM_LOCKING() { + fiber_stack_release(fiber); + } } #endif diff --git a/vm.c b/vm.c index 042a1ac12c98ca..3281fad0181916 100644 --- a/vm.c +++ b/vm.c @@ -59,6 +59,8 @@ int ruby_assert_critical_section_entered = 0; static void *native_main_thread_stack_top; +bool ruby_vm_during_cleanup = false; + VALUE rb_str_concat_literals(size_t, const VALUE*); VALUE vm_exec(rb_execution_context_t *); @@ -3287,6 +3289,7 @@ int ruby_vm_destruct(rb_vm_t *vm) { RUBY_FREE_ENTER("vm"); + ruby_vm_during_cleanup = true; if (vm) { rb_thread_t *th = vm->ractor.main_thread; diff --git a/vm_core.h b/vm_core.h index 5068e200575c18..e8e6a6a3a6b3f9 100644 --- a/vm_core.h +++ b/vm_core.h @@ -823,6 +823,8 @@ typedef struct rb_vm_struct { } default_params; } rb_vm_t; +extern bool ruby_vm_during_cleanup; + /* default values */ #define RUBY_VM_SIZE_ALIGN 4096 From 6a94632d4a92c17f97acdc2856a36a187092efcd Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 15 Oct 2025 14:00:44 -0400 Subject: [PATCH 0355/2435] ZJIT: Add HashAref to HIR and inline Hash#[] to HashAref (#14838) Fixes https://github.com/Shopify/ruby/issues/793 ## Testing on `liquid-render`:
Before patch: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (96.8% of total 20,222,783): Kernel#respond_to?: 9,725,886 (48.1%) Hash#key?: 4,589,528 (22.7%) Set#include?: 1,493,789 ( 7.4%) String#===: 616,183 ( 3.0%) Hash#[]: 453,675 ( 2.2%) String#<<: 386,831 ( 1.9%) Integer#<<: 319,768 ( 1.6%) Kernel#is_a?: 312,176 ( 1.5%) Integer#/: 238,502 ( 1.2%) Kernel#format: 238,502 ( 1.2%) Array#<<: 220,724 ( 1.1%) Class#last_match: 179,182 ( 0.9%) Hash#[]=: 167,728 ( 0.8%) CGI::EscapeExt#escapeHTML: 106,471 ( 0.5%) Array#shift: 98,030 ( 0.5%) Array#unshift: 90,851 ( 0.4%) String#=~: 90,637 ( 0.4%) String#start_with?: 88,122 ( 0.4%) Regexp#===: 85,648 ( 0.4%) String#empty?: 80,950 ( 0.4%) Top-20 not annotated C methods (97.0% of total 20,268,253): Kernel#respond_to?: 9,725,886 (48.0%) Hash#key?: 4,589,528 (22.6%) Set#include?: 1,493,789 ( 7.4%) String#===: 616,183 ( 3.0%) Hash#[]: 453,675 ( 2.2%) Kernel#is_a?: 397,366 ( 2.0%) String#<<: 386,831 ( 1.9%) Integer#<<: 319,768 ( 1.6%) Integer#/: 238,502 ( 1.2%) Kernel#format: 238,502 ( 1.2%) Array#<<: 220,724 ( 1.1%) Class#last_match: 179,182 ( 0.9%) Hash#[]=: 167,728 ( 0.8%) CGI::EscapeExt#escapeHTML: 106,471 ( 0.5%) Array#shift: 98,030 ( 0.5%) Array#unshift: 90,851 ( 0.4%) String#=~: 90,637 ( 0.4%) String#start_with?: 88,122 ( 0.4%) Regexp#===: 85,648 ( 0.4%) String#empty?: 80,950 ( 0.4%) Top-2 not optimized method types for send (100.0% of total 1,180): iseq: 602 (51.0%) cfunc: 578 (49.0%) Top-3 not optimized method types for send_without_block (100.0% of total 4,896,785): iseq: 4,669,764 (95.4%) optimized: 227,001 ( 4.6%) alias: 20 ( 0.0%) Top-9 not optimized instructions (100.0% of total 1,255,287): invokeblock: 430,174 (34.3%) opt_neq: 319,471 (25.5%) opt_and: 319,471 (25.5%) opt_eq: 127,926 (10.2%) opt_le: 31,238 ( 2.5%) invokesuper: 23,409 ( 1.9%) opt_minus: 2,934 ( 0.2%) opt_send_without_block: 562 ( 0.0%) opt_or: 102 ( 0.0%) Top-7 send fallback reasons (100.0% of total 17,930,659): send_no_profiles: 6,145,096 (34.3%) send_without_block_polymorphic: 5,459,600 (30.4%) send_without_block_not_optimized_method_type: 4,896,785 (27.3%) not_optimized_instruction: 1,255,287 ( 7.0%) send_without_block_no_profiles: 170,037 ( 0.9%) obj_to_string_not_string: 2,674 ( 0.0%) send_not_optimized_method_type: 1,180 ( 0.0%) Top-3 unhandled YARV insns (100.0% of total 157,831): getclassvariable: 157,694 (99.9%) once: 121 ( 0.1%) getconstant: 16 ( 0.0%) Top-2 compile error reasons (100.0% of total 8,905,991): register_spill_on_alloc: 8,905,891 (100.0%) register_spill_on_ccall: 100 ( 0.0%) Top-9 side exit reasons (100.0% of total 26,549,652): compile_error: 8,905,991 (33.5%) guard_shape_failure: 6,590,116 (24.8%) guard_type_failure: 4,882,217 (18.4%) unhandled_splat: 4,150,547 (15.6%) unhandled_kwarg: 1,827,728 ( 6.9%) unhandled_yarv_insn: 157,831 ( 0.6%) unhandled_hir_insn: 34,072 ( 0.1%) patchpoint: 1,100 ( 0.0%) block_param_proxy_not_iseq_or_ifunc: 50 ( 0.0%) send_count: 72,944,863 dynamic_send_count: 17,930,659 (24.6%) optimized_send_count: 55,014,204 (75.4%) iseq_optimized_send_count: 26,520,888 (36.4%) inline_cfunc_optimized_send_count: 8,270,533 (11.3%) non_variadic_cfunc_optimized_send_count: 9,344,065 (12.8%) variadic_cfunc_optimized_send_count: 10,878,718 (14.9%) dynamic_getivar_count: 2,171,396 dynamic_setivar_count: 1,737,553 compiled_iseq_count: 383 failed_iseq_count: 46 compile_time: 820ms profile_time: 4ms gc_time: 22ms invalidation_time: 0ms vm_write_pc_count: 71,973,068 vm_write_sp_count: 71,544,492 vm_write_locals_count: 71,544,492 vm_write_stack_count: 71,544,492 vm_write_to_parent_iseq_local_count: 1,070,897 vm_read_from_parent_iseq_local_count: 27,449,010 code_region_bytes: 2,113,536 side_exit_count: 26,549,652 total_insn_count: 908,528,764 vm_insn_count: 484,633,128 zjit_insn_count: 423,895,636 ratio_in_zjit: 46.7% ```
after patch: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (97.2% of total 19,769,108): Kernel#respond_to?: 9,725,886 (49.2%) Hash#key?: 4,589,528 (23.2%) Set#include?: 1,493,789 ( 7.6%) String#===: 616,183 ( 3.1%) String#<<: 386,831 ( 2.0%) Integer#<<: 319,768 ( 1.6%) Kernel#is_a?: 312,176 ( 1.6%) Integer#/: 238,502 ( 1.2%) Kernel#format: 238,502 ( 1.2%) Array#<<: 220,724 ( 1.1%) Class#last_match: 179,182 ( 0.9%) Hash#[]=: 167,728 ( 0.8%) CGI::EscapeExt#escapeHTML: 106,471 ( 0.5%) Array#shift: 98,030 ( 0.5%) Array#unshift: 90,851 ( 0.5%) String#=~: 90,637 ( 0.5%) String#start_with?: 88,122 ( 0.4%) Regexp#===: 85,648 ( 0.4%) String#empty?: 80,950 ( 0.4%) Array#push: 78,615 ( 0.4%) Top-20 not annotated C methods (97.4% of total 19,814,578): Kernel#respond_to?: 9,725,886 (49.1%) Hash#key?: 4,589,528 (23.2%) Set#include?: 1,493,789 ( 7.5%) String#===: 616,183 ( 3.1%) Kernel#is_a?: 397,366 ( 2.0%) String#<<: 386,831 ( 2.0%) Integer#<<: 319,768 ( 1.6%) Integer#/: 238,502 ( 1.2%) Kernel#format: 238,502 ( 1.2%) Array#<<: 220,724 ( 1.1%) Class#last_match: 179,182 ( 0.9%) Hash#[]=: 167,728 ( 0.8%) CGI::EscapeExt#escapeHTML: 106,471 ( 0.5%) Array#shift: 98,030 ( 0.5%) Array#unshift: 90,851 ( 0.5%) String#=~: 90,637 ( 0.5%) String#start_with?: 88,122 ( 0.4%) Regexp#===: 85,648 ( 0.4%) String#empty?: 80,950 ( 0.4%) Array#push: 78,615 ( 0.4%) Top-2 not optimized method types for send (100.0% of total 1,180): iseq: 602 (51.0%) cfunc: 578 (49.0%) Top-3 not optimized method types for send_without_block (100.0% of total 4,896,785): iseq: 4,669,764 (95.4%) optimized: 227,001 ( 4.6%) alias: 20 ( 0.0%) Top-9 not optimized instructions (100.0% of total 1,255,287): invokeblock: 430,174 (34.3%) opt_neq: 319,471 (25.5%) opt_and: 319,471 (25.5%) opt_eq: 127,926 (10.2%) opt_le: 31,238 ( 2.5%) invokesuper: 23,409 ( 1.9%) opt_minus: 2,934 ( 0.2%) opt_send_without_block: 562 ( 0.0%) opt_or: 102 ( 0.0%) Top-7 send fallback reasons (100.0% of total 17,930,659): send_no_profiles: 6,145,096 (34.3%) send_without_block_polymorphic: 5,459,600 (30.4%) send_without_block_not_optimized_method_type: 4,896,785 (27.3%) not_optimized_instruction: 1,255,287 ( 7.0%) send_without_block_no_profiles: 170,037 ( 0.9%) obj_to_string_not_string: 2,674 ( 0.0%) send_not_optimized_method_type: 1,180 ( 0.0%) Top-3 unhandled YARV insns (100.0% of total 157,831): getclassvariable: 157,694 (99.9%) once: 121 ( 0.1%) getconstant: 16 ( 0.0%) Top-2 compile error reasons (100.0% of total 8,905,991): register_spill_on_alloc: 8,905,891 (100.0%) register_spill_on_ccall: 100 ( 0.0%) Top-9 side exit reasons (100.0% of total 26,549,652): compile_error: 8,905,991 (33.5%) guard_shape_failure: 6,590,116 (24.8%) guard_type_failure: 4,882,217 (18.4%) unhandled_splat: 4,150,547 (15.6%) unhandled_kwarg: 1,827,728 ( 6.9%) unhandled_yarv_insn: 157,831 ( 0.6%) unhandled_hir_insn: 34,072 ( 0.1%) patchpoint: 1,100 ( 0.0%) block_param_proxy_not_iseq_or_ifunc: 50 ( 0.0%) send_count: 72,491,188 dynamic_send_count: 17,930,659 (24.7%) optimized_send_count: 54,560,529 (75.3%) iseq_optimized_send_count: 26,520,888 (36.6%) inline_cfunc_optimized_send_count: 8,270,533 (11.4%) non_variadic_cfunc_optimized_send_count: 8,890,390 (12.3%) variadic_cfunc_optimized_send_count: 10,878,718 (15.0%) dynamic_getivar_count: 2,171,396 dynamic_setivar_count: 1,737,553 compiled_iseq_count: 383 failed_iseq_count: 46 compile_time: 808ms profile_time: 4ms gc_time: 21ms invalidation_time: 0ms vm_write_pc_count: 71,973,068 vm_write_sp_count: 71,544,492 vm_write_locals_count: 71,544,492 vm_write_stack_count: 71,544,492 vm_write_to_parent_iseq_local_count: 1,070,897 vm_read_from_parent_iseq_local_count: 27,449,010 code_region_bytes: 2,097,152 side_exit_count: 26,549,652 total_insn_count: 908,528,764 vm_insn_count: 484,633,128 zjit_insn_count: 423,895,636 ratio_in_zjit: 46.7% ```
## Testing on `lobsters`:
Before patch: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (71.0% of total 28,729,305): Hash#[]: 8,490,837 (29.6%) Kernel#is_a?: 1,861,955 ( 6.5%) String#<<: 1,773,932 ( 6.2%) Hash#[]=: 1,159,328 ( 4.0%) Regexp#match?: 775,654 ( 2.7%) String#empty?: 724,503 ( 2.5%) Hash#key?: 691,233 ( 2.4%) Kernel#respond_to?: 608,714 ( 2.1%) TrueClass#===: 451,557 ( 1.6%) FalseClass#===: 442,907 ( 1.5%) Array#include?: 429,408 ( 1.5%) ActiveSupport::OrderedOptions#_get: 377,468 ( 1.3%) String#start_with?: 373,685 ( 1.3%) ObjectSpace::WeakKeyMap#[]: 356,664 ( 1.2%) Kernel#kind_of?: 349,451 ( 1.2%) Kernel#dup: 328,120 ( 1.1%) Class#new: 310,590 ( 1.1%) Kernel#block_given?: 307,113 ( 1.1%) String#==: 290,654 ( 1.0%) Hash#fetch: 290,533 ( 1.0%) Top-20 not annotated C methods (71.7% of total 29,033,802): Hash#[]: 8,490,847 (29.2%) Kernel#is_a?: 2,231,950 ( 7.7%) String#<<: 1,773,932 ( 6.1%) Hash#[]=: 1,159,507 ( 4.0%) Regexp#match?: 775,654 ( 2.7%) String#empty?: 739,580 ( 2.5%) Hash#key?: 691,233 ( 2.4%) Kernel#respond_to?: 608,714 ( 2.1%) TrueClass#===: 451,557 ( 1.6%) FalseClass#===: 442,907 ( 1.5%) Array#include?: 429,408 ( 1.5%) ActiveSupport::OrderedOptions#_get: 377,468 ( 1.3%) String#start_with?: 373,685 ( 1.3%) ObjectSpace::WeakKeyMap#[]: 356,664 ( 1.2%) Kernel#kind_of?: 349,486 ( 1.2%) Kernel#dup: 328,127 ( 1.1%) Kernel#block_given?: 327,655 ( 1.1%) Class#new: 310,590 ( 1.1%) String#==: 296,624 ( 1.0%) Hash#fetch: 290,533 ( 1.0%) Top-2 not optimized method types for send (100.0% of total 96,231): cfunc: 75,873 (78.8%) iseq: 20,358 (21.2%) Top-6 not optimized method types for send_without_block (100.0% of total 8,044,793): iseq: 4,034,262 (50.1%) bmethod: 1,757,537 (21.8%) optimized: 1,647,169 (20.5%) alias: 596,446 ( 7.4%) null: 8,161 ( 0.1%) cfunc: 1,218 ( 0.0%) Top-13 not optimized instructions (100.0% of total 7,507,191): invokesuper: 4,343,829 (57.9%) invokeblock: 1,323,655 (17.6%) sendforward: 842,491 (11.2%) opt_eq: 722,952 ( 9.6%) opt_plus: 145,599 ( 1.9%) opt_minus: 52,269 ( 0.7%) opt_send_without_block: 39,595 ( 0.5%) opt_neq: 15,048 ( 0.2%) opt_mult: 13,826 ( 0.2%) opt_or: 7,452 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 45,075,567): send_without_block_polymorphic: 17,072,731 (37.9%) send_no_profiles: 10,490,735 (23.3%) send_without_block_not_optimized_method_type: 8,044,793 (17.8%) not_optimized_instruction: 7,507,191 (16.7%) send_without_block_no_profiles: 1,816,853 ( 4.0%) send_not_optimized_method_type: 96,231 ( 0.2%) send_without_block_cfunc_array_variadic: 31,156 ( 0.1%) obj_to_string_not_string: 15,303 ( 0.0%) send_without_block_direct_too_many_args: 574 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 1,279,306): expandarray: 660,222 (51.6%) checkkeyword: 316,124 (24.7%) getclassvariable: 119,678 ( 9.4%) getblockparam: 88,485 ( 6.9%) invokesuperforward: 78,843 ( 6.2%) opt_duparray_send: 14,149 ( 1.1%) getconstant: 1,496 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 6,508,618): register_spill_on_alloc: 6,162,701 (94.7%) register_spill_on_ccall: 345,917 ( 5.3%) Top-14 side exit reasons (100.0% of total 19,988,958): compile_error: 6,508,618 (32.6%) guard_type_failure: 5,255,050 (26.3%) guard_shape_failure: 3,698,481 (18.5%) unhandled_yarv_insn: 1,279,306 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 990,585 ( 5.0%) unhandled_kwarg: 801,146 ( 4.0%) unknown_newarray_send: 539,110 ( 2.7%) patchpoint: 496,826 ( 2.5%) unhandled_splat: 242,104 ( 1.2%) unhandled_hir_insn: 147,346 ( 0.7%) block_param_proxy_modified: 29,122 ( 0.1%) interrupt: 1,072 ( 0.0%) obj_to_string_fallback: 170 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 118,969,379 dynamic_send_count: 45,075,567 (37.9%) optimized_send_count: 73,893,812 (62.1%) iseq_optimized_send_count: 32,439,432 (27.3%) inline_cfunc_optimized_send_count: 12,725,075 (10.7%) non_variadic_cfunc_optimized_send_count: 24,121,279 (20.3%) variadic_cfunc_optimized_send_count: 4,608,026 ( 3.9%) dynamic_getivar_count: 13,002,365 dynamic_setivar_count: 12,402,229 compiled_iseq_count: 4,817 failed_iseq_count: 466 compile_time: 8,961ms profile_time: 68ms gc_time: 41ms invalidation_time: 288ms vm_write_pc_count: 113,940,194 vm_write_sp_count: 111,595,088 vm_write_locals_count: 111,595,088 vm_write_stack_count: 111,595,088 vm_write_to_parent_iseq_local_count: 514,997 vm_read_from_parent_iseq_local_count: 11,288,600 code_region_bytes: 22,970,368 side_exit_count: 19,988,958 total_insn_count: 928,321,939 vm_insn_count: 297,374,855 zjit_insn_count: 630,947,084 ratio_in_zjit: 68.0% ```
after patch: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (60.9% of total 19,827,919): Kernel#is_a?: 1,827,297 ( 9.2%) String#<<: 1,764,393 ( 8.9%) Hash#[]=: 1,159,637 ( 5.8%) Regexp#match?: 775,625 ( 3.9%) String#empty?: 723,469 ( 3.6%) Hash#key?: 691,214 ( 3.5%) Kernel#respond_to?: 602,389 ( 3.0%) TrueClass#===: 447,671 ( 2.3%) FalseClass#===: 439,274 ( 2.2%) Array#include?: 425,491 ( 2.1%) Hash#fetch: 382,294 ( 1.9%) String#start_with?: 373,684 ( 1.9%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) Kernel#kind_of?: 340,341 ( 1.7%) Kernel#dup: 328,108 ( 1.7%) Class#new: 309,571 ( 1.6%) Kernel#block_given?: 307,098 ( 1.5%) String#==: 286,539 ( 1.4%) BasicObject#!=: 284,640 ( 1.4%) String#length: 256,345 ( 1.3%) Top-20 not annotated C methods (62.1% of total 20,127,933): Kernel#is_a?: 2,205,849 (11.0%) String#<<: 1,764,393 ( 8.8%) Hash#[]=: 1,159,816 ( 5.8%) Regexp#match?: 775,625 ( 3.9%) String#empty?: 738,546 ( 3.7%) Hash#key?: 691,214 ( 3.4%) Kernel#respond_to?: 602,389 ( 3.0%) TrueClass#===: 447,671 ( 2.2%) FalseClass#===: 439,274 ( 2.2%) Array#include?: 425,491 ( 2.1%) Hash#fetch: 382,294 ( 1.9%) String#start_with?: 373,684 ( 1.9%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) Kernel#kind_of?: 340,375 ( 1.7%) Kernel#dup: 328,115 ( 1.6%) Kernel#block_given?: 327,640 ( 1.6%) Class#new: 309,571 ( 1.5%) String#==: 292,509 ( 1.5%) BasicObject#!=: 284,824 ( 1.4%) String#length: 256,345 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 113,430): cfunc: 75,863 (66.9%) iseq: 37,567 (33.1%) Top-6 not optimized method types for send_without_block (100.0% of total 8,005,732): iseq: 4,007,647 (50.1%) bmethod: 1,750,263 (21.9%) optimized: 1,647,088 (20.6%) alias: 591,356 ( 7.4%) null: 8,161 ( 0.1%) cfunc: 1,217 ( 0.0%) Top-13 not optimized instructions (100.0% of total 7,569,803): invokesuper: 4,320,589 (57.1%) invokeblock: 1,321,548 (17.5%) sendforward: 841,452 (11.1%) opt_eq: 811,601 (10.7%) opt_plus: 142,565 ( 1.9%) opt_minus: 52,268 ( 0.7%) opt_send_without_block: 42,982 ( 0.6%) opt_neq: 15,047 ( 0.2%) opt_mult: 13,824 ( 0.2%) opt_or: 7,452 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 45,409,745): send_without_block_polymorphic: 17,360,049 (38.2%) send_no_profiles: 10,502,130 (23.1%) send_without_block_not_optimized_method_type: 8,005,732 (17.6%) not_optimized_instruction: 7,569,803 (16.7%) send_without_block_no_profiles: 1,811,570 ( 4.0%) send_not_optimized_method_type: 113,430 ( 0.2%) send_without_block_cfunc_array_variadic: 31,154 ( 0.1%) obj_to_string_not_string: 15,303 ( 0.0%) send_without_block_direct_too_many_args: 574 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 1,241,241): expandarray: 622,183 (50.1%) checkkeyword: 316,113 (25.5%) getclassvariable: 119,668 ( 9.6%) getblockparam: 88,481 ( 7.1%) invokesuperforward: 78,842 ( 6.4%) opt_duparray_send: 14,149 ( 1.1%) getconstant: 1,496 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 6,521,426): register_spill_on_alloc: 6,175,519 (94.7%) register_spill_on_ccall: 345,907 ( 5.3%) Top-14 side exit reasons (100.0% of total 19,869,193): compile_error: 6,521,426 (32.8%) guard_type_failure: 5,167,727 (26.0%) guard_shape_failure: 3,708,529 (18.7%) unhandled_yarv_insn: 1,241,241 ( 6.2%) block_param_proxy_not_iseq_or_ifunc: 990,130 ( 5.0%) unhandled_kwarg: 800,104 ( 4.0%) unknown_newarray_send: 539,105 ( 2.7%) patchpoint: 494,790 ( 2.5%) unhandled_splat: 229,423 ( 1.2%) unhandled_hir_insn: 147,342 ( 0.7%) block_param_proxy_modified: 28,111 ( 0.1%) interrupt: 1,073 ( 0.0%) obj_to_string_fallback: 170 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 109,972,903 dynamic_send_count: 45,409,745 (41.3%) optimized_send_count: 64,563,158 (58.7%) iseq_optimized_send_count: 32,205,906 (29.3%) inline_cfunc_optimized_send_count: 12,529,333 (11.4%) non_variadic_cfunc_optimized_send_count: 15,123,197 (13.8%) variadic_cfunc_optimized_send_count: 4,704,722 ( 4.3%) dynamic_getivar_count: 12,973,226 dynamic_setivar_count: 12,381,984 compiled_iseq_count: 4,816 failed_iseq_count: 467 compile_time: 8,116ms profile_time: 59ms gc_time: 35ms invalidation_time: 289ms vm_write_pc_count: 113,616,123 vm_write_sp_count: 111,273,109 vm_write_locals_count: 111,273,109 vm_write_stack_count: 111,273,109 vm_write_to_parent_iseq_local_count: 516,816 vm_read_from_parent_iseq_local_count: 11,255,225 code_region_bytes: 22,872,064 side_exit_count: 19,869,193 total_insn_count: 924,733,475 vm_insn_count: 296,183,588 zjit_insn_count: 628,549,887 ratio_in_zjit: 68.0% ```
--- zjit/src/codegen.rs | 6 ++ zjit/src/cruby_methods.rs | 9 +++ zjit/src/hir.rs | 130 +++++++++++++++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index c5bdbcfe0a5a83..0bf0205b5351d2 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -431,6 +431,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)), &Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))), &Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) }, + &Insn::HashAref { hash, key, state } => { gen_hash_aref(jit, asm, opnd!(hash), opnd!(key), &function.frame_state(state)) }, &Insn::ArrayPush { array, val, state } => { no_output!(gen_array_push(asm, opnd!(array), opnd!(val), &function.frame_state(state))) }, &Insn::ToNewArray { val, state } => { gen_to_new_array(jit, asm, opnd!(val), &function.frame_state(state)) }, &Insn::ToArray { val, state } => { gen_to_array(jit, asm, opnd!(val), &function.frame_state(state)) }, @@ -860,6 +861,11 @@ fn gen_hash_dup(asm: &mut Assembler, val: Opnd, state: &FrameState) -> lir::Opnd asm_ccall!(asm, rb_hash_resurrect, val) } +fn gen_hash_aref(jit: &mut JITState, asm: &mut Assembler, hash: Opnd, key: Opnd, state: &FrameState) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + asm_ccall!(asm, rb_hash_aref, hash, key) +} + fn gen_array_push(asm: &mut Assembler, array: Opnd, val: Opnd, state: &FrameState) { gen_prepare_leaf_call_with_gc(asm, state); asm_ccall!(asm, rb_ary_push, array, val); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index bc8f1d3b847501..27e6b151bf5020 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -198,6 +198,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); + annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); @@ -248,3 +249,11 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In } None } + +fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if let &[key] = args { + let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); + return Some(result); + } + None +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2d1eb6425a5181..4ad20b9c1d8295 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -582,6 +582,7 @@ pub enum Insn { ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + HashAref { hash: InsnId, key: InsnId, state: InsnId }, HashDup { val: InsnId, state: InsnId }, /// Allocate an instance of the `val` object without calling `#initialize` on it. @@ -923,6 +924,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") } Insn::HashDup { val, .. } => { write!(f, "HashDup {val}") } + Insn::HashAref { hash, key, .. } => { write!(f, "HashAref {hash}, {key}")} Insn::ObjectAlloc { val, .. } => { write!(f, "ObjectAlloc {val}") } &Insn::ObjectAllocClass { class, .. } => { let class_name = get_class_name(class); @@ -1580,6 +1582,7 @@ impl Function { &InvokeBuiltin { bf, ref args, state, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, return_type }, &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, &HashDup { val, state } => HashDup { val: find!(val), state }, + &HashAref { hash, key, state } => HashAref { hash: find!(hash), key: find!(key), state }, &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, @@ -1680,6 +1683,7 @@ impl Function { Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, Insn::ArrayArefFixnum { .. } => types::BasicObject, + Insn::HashAref { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, Insn::HashDup { .. } => types::HashExact, Insn::NewRange { .. } => types::RangeExact, @@ -2775,6 +2779,11 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::HashAref { hash, key, state } => { + worklist.push_back(hash); + worklist.push_back(key); + worklist.push_back(state); + } &Insn::Send { recv, ref args, state, .. } | &Insn::SendForward { recv, ref args, state, .. } | &Insn::SendWithoutBlock { recv, ref args, state, .. } @@ -9242,7 +9251,7 @@ mod opt_tests { PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) v26:HashExact = GuardType v9, HashExact - v27:BasicObject = CCallWithFrame []@0x1038, v26, v13 + v27:BasicObject = HashAref v26, v13 CheckInterrupts Return v27 "); @@ -12827,6 +12836,125 @@ mod opt_tests { "); } + #[test] + fn test_hash_aref_literal() { + eval(" + def test + arr = {1 => 3} + arr[1] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:HashExact = HashDup v13 + v18:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Hash@0x1008) + v31:BasicObject = HashAref v15, v18 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_hash_aref_profiled() { + eval(" + def test(hash, key) + hash[key] + end + test({1 => 3}, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v11, HashExact + v29:BasicObject = HashAref v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_hash_aref_subclass() { + eval(" + class C < Hash; end + def test(hash, key) + hash[key] + end + test(C.new({0 => 3}), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:BasicObject = HashAref v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_does_not_fold_hash_aref_with_frozen_hash() { + eval(" + H = {a: 0}.freeze + def test = H[:a] + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, H) + v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) + PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) + PatchPoint NoSingletonClass(Hash@0x1018) + v28:BasicObject = HashAref v24, v12 + CheckInterrupts + Return v28 + "); + } + #[test] fn test_optimize_thread_current() { eval(" From 829b1884c0c917ca47988abc2c626c47c5748197 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 15 Oct 2025 13:24:56 -0400 Subject: [PATCH 0356/2435] [ruby/prism] explicitly cast shifted constant to unsigned to avoid undefined behavior https://github.com/ruby/prism/commit/0b2710a6c9 --- prism/prism.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index cc1896ee3486ed..d6ee50b301da8d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8648,7 +8648,7 @@ static const uint32_t context_terminators[] = { static inline bool context_terminator(pm_context_t context, pm_token_t *token) { - return token->type < 32 && (context_terminators[context] & (1 << token->type)); + return token->type < 32 && (context_terminators[context] & (1U << token->type)); } /** From b052d706a53e764786093d6d1cade5a9adebbb7b Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 15 Oct 2025 13:26:01 -0400 Subject: [PATCH 0357/2435] [ruby/prism] explicitly cast constants in initializers as well https://github.com/ruby/prism/commit/e7db2b06ab --- prism/prism.c | 106 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index d6ee50b301da8d..0ebcae62f9e822 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8591,59 +8591,59 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) { static const uint32_t context_terminators[] = { [PM_CONTEXT_NONE] = 0, - [PM_CONTEXT_BEGIN] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BEGIN_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BEGIN_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BEGIN_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BLOCK_BRACES] = (1 << PM_TOKEN_BRACE_RIGHT), - [PM_CONTEXT_BLOCK_KEYWORDS] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_BLOCK_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BLOCK_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_BLOCK_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_CASE_WHEN] = (1 << PM_TOKEN_KEYWORD_WHEN) | (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_ELSE), - [PM_CONTEXT_CASE_IN] = (1 << PM_TOKEN_KEYWORD_IN) | (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_ELSE), - [PM_CONTEXT_CLASS] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_CLASS_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_CLASS_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_CLASS_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_DEF] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_DEF_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_DEF_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_DEF_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_DEF_PARAMS] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_DEFINED] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_DEFAULT_PARAMS] = (1 << PM_TOKEN_COMMA) | (1 << PM_TOKEN_PARENTHESIS_RIGHT), - [PM_CONTEXT_ELSE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_ELSIF] = (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_ELSIF) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_EMBEXPR] = (1 << PM_TOKEN_EMBEXPR_END), - [PM_CONTEXT_FOR] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_FOR_INDEX] = (1 << PM_TOKEN_KEYWORD_IN), - [PM_CONTEXT_IF] = (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_ELSIF) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_LAMBDA_BRACES] = (1 << PM_TOKEN_BRACE_RIGHT), - [PM_CONTEXT_LAMBDA_DO_END] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_LAMBDA_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_LAMBDA_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_LAMBDA_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_LOOP_PREDICATE] = (1 << PM_TOKEN_KEYWORD_DO) | (1 << PM_TOKEN_KEYWORD_THEN), - [PM_CONTEXT_MAIN] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_MODULE] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_MODULE_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_MODULE_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_MODULE_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_MULTI_TARGET] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_PARENS] = (1 << PM_TOKEN_PARENTHESIS_RIGHT), - [PM_CONTEXT_POSTEXE] = (1 << PM_TOKEN_BRACE_RIGHT), - [PM_CONTEXT_PREDICATE] = (1 << PM_TOKEN_KEYWORD_THEN) | (1 << PM_TOKEN_NEWLINE) | (1 << PM_TOKEN_SEMICOLON), - [PM_CONTEXT_PREEXE] = (1 << PM_TOKEN_BRACE_RIGHT), - [PM_CONTEXT_RESCUE_MODIFIER] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_SCLASS] = (1 << PM_TOKEN_KEYWORD_END) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ENSURE), - [PM_CONTEXT_SCLASS_ENSURE] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_SCLASS_ELSE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_SCLASS_RESCUE] = (1 << PM_TOKEN_KEYWORD_ENSURE) | (1 << PM_TOKEN_KEYWORD_RESCUE) | (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_TERNARY] = (1 << PM_TOKEN_EOF), - [PM_CONTEXT_UNLESS] = (1 << PM_TOKEN_KEYWORD_ELSE) | (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_UNTIL] = (1 << PM_TOKEN_KEYWORD_END), - [PM_CONTEXT_WHILE] = (1 << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BEGIN] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BEGIN_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BEGIN_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BEGIN_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BLOCK_BRACES] = (1U << PM_TOKEN_BRACE_RIGHT), + [PM_CONTEXT_BLOCK_KEYWORDS] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_BLOCK_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BLOCK_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_BLOCK_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_CASE_WHEN] = (1U << PM_TOKEN_KEYWORD_WHEN) | (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_ELSE), + [PM_CONTEXT_CASE_IN] = (1U << PM_TOKEN_KEYWORD_IN) | (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_ELSE), + [PM_CONTEXT_CLASS] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_CLASS_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_CLASS_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_CLASS_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_DEF] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_DEF_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_DEF_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_DEF_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_DEF_PARAMS] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_DEFINED] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_DEFAULT_PARAMS] = (1U << PM_TOKEN_COMMA) | (1U << PM_TOKEN_PARENTHESIS_RIGHT), + [PM_CONTEXT_ELSE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_ELSIF] = (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_ELSIF) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_EMBEXPR] = (1U << PM_TOKEN_EMBEXPR_END), + [PM_CONTEXT_FOR] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_FOR_INDEX] = (1U << PM_TOKEN_KEYWORD_IN), + [PM_CONTEXT_IF] = (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_ELSIF) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_LAMBDA_BRACES] = (1U << PM_TOKEN_BRACE_RIGHT), + [PM_CONTEXT_LAMBDA_DO_END] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_LAMBDA_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_LAMBDA_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_LAMBDA_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_LOOP_PREDICATE] = (1U << PM_TOKEN_KEYWORD_DO) | (1U << PM_TOKEN_KEYWORD_THEN), + [PM_CONTEXT_MAIN] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_MODULE] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_MODULE_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_MODULE_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_MODULE_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_MULTI_TARGET] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_PARENS] = (1U << PM_TOKEN_PARENTHESIS_RIGHT), + [PM_CONTEXT_POSTEXE] = (1U << PM_TOKEN_BRACE_RIGHT), + [PM_CONTEXT_PREDICATE] = (1U << PM_TOKEN_KEYWORD_THEN) | (1U << PM_TOKEN_NEWLINE) | (1U << PM_TOKEN_SEMICOLON), + [PM_CONTEXT_PREEXE] = (1U << PM_TOKEN_BRACE_RIGHT), + [PM_CONTEXT_RESCUE_MODIFIER] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_SCLASS] = (1U << PM_TOKEN_KEYWORD_END) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ENSURE), + [PM_CONTEXT_SCLASS_ENSURE] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_SCLASS_ELSE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_SCLASS_RESCUE] = (1U << PM_TOKEN_KEYWORD_ENSURE) | (1U << PM_TOKEN_KEYWORD_RESCUE) | (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_TERNARY] = (1U << PM_TOKEN_EOF), + [PM_CONTEXT_UNLESS] = (1U << PM_TOKEN_KEYWORD_ELSE) | (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_UNTIL] = (1U << PM_TOKEN_KEYWORD_END), + [PM_CONTEXT_WHILE] = (1U << PM_TOKEN_KEYWORD_END), }; static inline bool From 4c426e98a89015de0ccbd52f3ceb92aa71d31bb4 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 15 Oct 2025 13:53:57 -0400 Subject: [PATCH 0358/2435] ZJIT: Use rb_gc_disable() over rb_gc_disable_no_rest() no_rest() trips an assert inside the GC when we allocate with the GC disabled this way: (gc_continue) ../src/gc/default/default.c:2029 (newobj_cache_miss+0x128) [0x105040048] ../src/gc/default/default.c:2370 (rb_gc_impl_new_obj+0x7c) [0x105036374] ../src/gc/default/default.c:2482 (newobj_of) ../src/gc.c:995 (rb_method_entry_alloc+0x40) [0x1051e6c64] ../src/vm_method.c:1102 (rb_method_entry_complement_defined_class) ../src/vm_method.c:1180 (prepare_callable_method_entry+0x14c) [0x1051e87b8] ../src/vm_method.c:1728 (callable_method_entry_or_negative+0x1e8) [0x1051e809c] ../src/vm_method.c:1874 It's tries to continue the GC because it was out of space. Looks like it's not safe to allocate new objects after using rb_gc_disable_no_rest(); existing usages use it for malloc calls. --- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 2 +- zjit/src/cruby_bindings.inc.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 77d482db4e9d97..43fec090149ef8 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -130,7 +130,7 @@ fn main() { .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") .allowlist_function("rb_class_get_superclass") - .allowlist_function("rb_gc_disable_no_rest") + .allowlist_function("rb_gc_disable") .allowlist_function("rb_gc_enable") .allowlist_function("rb_gc_mark") .allowlist_function("rb_gc_mark_movable") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 4eb1d3a17c836e..dca4d9180556e8 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -897,7 +897,7 @@ where // 2. If we yield to the GC while compiling, it re-enters our mark and update functions. // This breaks `&mut` exclusivity since mark functions derive fresh `&mut` from statics // while there is a stack frame below it that has an overlapping `&mut`. That's UB. - let gc_disabled_pre_call = unsafe { rb_gc_disable_no_rest() }.test(); + let gc_disabled_pre_call = unsafe { rb_gc_disable() }.test(); let ret = match catch_unwind(func) { Ok(result) => result, diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 6a6263ab15110a..17cda12a0b6204 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -746,6 +746,7 @@ unsafe extern "C" { pub fn rb_gc_mark_movable(obj: VALUE); pub fn rb_gc_location(obj: VALUE) -> VALUE; pub fn rb_gc_enable() -> VALUE; + pub fn rb_gc_disable() -> VALUE; pub fn rb_gc_writebarrier(old: VALUE, young: VALUE); pub fn rb_class_get_superclass(klass: VALUE) -> VALUE; pub static mut rb_cObject: VALUE; @@ -877,7 +878,6 @@ unsafe extern "C" { buff_size: usize, obj: VALUE, ) -> *const ::std::os::raw::c_char; - pub fn rb_gc_disable_no_rest() -> VALUE; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; From d272a81f42414d1576dbb44128fcbea74a6cf1d2 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 15 Oct 2025 14:39:46 -0400 Subject: [PATCH 0359/2435] ZJIT: Rewrite arm64_split_with_scratch_reg for clarity * The while loop pattern can be rewritten to be more idiomatic, which also allows the iterator to no longer be mutable. --- zjit/src/backend/arm64/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 6750926b35daa1..74b5210f0fb0a6 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -692,10 +692,10 @@ impl Assembler { /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this /// splits them and uses scratch registers for it. fn arm64_split_with_scratch_reg(mut self) -> Assembler { - let mut iterator = self.insns.into_iter().enumerate().peekable(); + let iterator = self.insns.into_iter().enumerate().peekable(); let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); - while let Some((_, mut insn)) = iterator.next() { + for (_, mut insn) in iterator { match &mut insn { // For compile_side_exits, support splitting simple C arguments here Insn::CCall { opnds, .. } if !opnds.is_empty() => { From 5a9fac6939af7be2991a5fe16df5fcba8f24eab9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 14 Oct 2025 20:49:59 -0400 Subject: [PATCH 0360/2435] Fix assert_equal order in test_namespace.rb The expected value is the first parameter and the actual value is the second in assert_equal. --- test/ruby/test_namespace.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index af308ab15c25e3..5661a98ca2b5f1 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -552,7 +552,7 @@ def test_prelude_gems_and_loaded_features # No additional warnings except for experimental warnings assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS - assert_equal error.size, 2 + assert_equal 2, error.size assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' @@ -574,7 +574,7 @@ def test_prelude_gems_and_loaded_features_with_disable_gems end; assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS - assert_equal error.size, 2 + assert_equal 2, error.size refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' From 9e4a75696303812d23366d57e4381166b1f88bb1 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 14 Oct 2025 23:59:39 -0700 Subject: [PATCH 0361/2435] Use BUILTIN_TYPE in gc_mark_check_t_none --- gc/default/default.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index af386a9793ae1d..7c10cc33063b0c 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4377,7 +4377,7 @@ gc_grey(rb_objspace_t *objspace, VALUE obj) static inline void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj) { - if (RB_UNLIKELY(RB_TYPE_P(obj, T_NONE))) { + if (RB_UNLIKELY(BUILTIN_TYPE(obj) == T_NONE)) { enum {info_size = 256}; char obj_info_buf[info_size]; rb_raw_obj_info(obj_info_buf, info_size, obj); From 45c016866c24a244d286a2db0babab1ff6867ba0 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 22 Jul 2025 09:40:20 -0700 Subject: [PATCH 0362/2435] Use explicit memory orders in concurrent_set The atomic load/store operations here should mostly be using release/acquire semantics. This may lead to better performance than what we had under the default seq_cst. On x86 this may make the atomic store of hash faster, as it can avoid xchg. On ARM the loads may be faster (depending on target CPU for the compiler). Reference for comparison of atomic operations https://godbolt.org/z/6EdaMa5rG --- concurrent_set.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/concurrent_set.c b/concurrent_set.c index 87279384eb8893..dab7ef2180b605 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -1,7 +1,6 @@ #include "internal.h" #include "internal/gc.h" #include "internal/concurrent_set.h" -#include "ruby_atomic.h" #include "ruby/atomic.h" #include "vm_sync.h" @@ -109,7 +108,7 @@ static void concurrent_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) { + if (rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE) != old_set_obj) { return; } @@ -117,7 +116,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) // 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; + int expected_size = rbimpl_atomic_load(&old_set->size, RBIMPL_ATOMIC_RELAXED) - old_set->deleted_entries; struct concurrent_set_entry *old_entries = old_set->entries; int old_capacity = old_set->capacity; @@ -135,13 +134,13 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) for (int i = 0; i < old_capacity; i++) { struct concurrent_set_entry *entry = &old_entries[i]; - VALUE key = RUBY_ATOMIC_VALUE_EXCHANGE(entry->key, CONCURRENT_SET_MOVED); + VALUE key = rbimpl_atomic_value_exchange(&entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(key != CONCURRENT_SET_MOVED); if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue; if (!RB_SPECIAL_CONST_P(key) && rb_objspace_garbage_object_p(key)) continue; - VALUE hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); + VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); if (hash == 0) { // Either in-progress insert or extremely unlikely 0 hash. // Re-calculate the hash. @@ -174,7 +173,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) } } - RUBY_ATOMIC_VALUE_SET(*set_obj_ptr, new_set_obj); + rbimpl_atomic_value_store(set_obj_ptr, new_set_obj, RBIMPL_ATOMIC_RELEASE); RB_GC_GUARD(old_set_obj); } @@ -196,7 +195,7 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) VALUE hash = 0; retry: - set_obj = RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr); + set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); @@ -212,7 +211,7 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; - VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key); + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { case CONCURRENT_SET_EMPTY: @@ -225,13 +224,13 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) goto retry; default: { - VALUE curr_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); + VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); if (curr_hash != 0 && curr_hash != hash) break; if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && 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, CONCURRENT_SET_DELETED); + rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); break; } @@ -259,7 +258,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) VALUE hash = 0; retry: - set_obj = RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr); + set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); @@ -275,7 +274,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; - VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key); + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { case CONCURRENT_SET_EMPTY: { @@ -286,7 +285,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) inserting = true; } - rb_atomic_t prev_size = RUBY_ATOMIC_FETCH_ADD(set->size, 1); + rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED); if (UNLIKELY(prev_size > set->capacity / 2)) { concurrent_set_try_resize(set_obj, set_obj_ptr); @@ -294,16 +293,16 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) goto retry; } - curr_key = RUBY_ATOMIC_VALUE_CAS(entry->key, CONCURRENT_SET_EMPTY, key); + curr_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); if (curr_key == CONCURRENT_SET_EMPTY) { - RUBY_ATOMIC_VALUE_SET(entry->hash, hash); + rbimpl_atomic_value_store(&entry->hash, hash, RBIMPL_ATOMIC_RELAXED); RB_GC_GUARD(set_obj); return key; } else { // Entry was not inserted. - RUBY_ATOMIC_DEC(set->size); + rbimpl_atomic_sub(&set->size, 1, RBIMPL_ATOMIC_RELAXED); // Another thread won the race, try again at the same location. continue; @@ -317,13 +316,13 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) goto retry; default: { - VALUE curr_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); + VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); if (curr_hash != 0 && curr_hash != hash) break; if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && 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, CONCURRENT_SET_DELETED); + rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); break; } @@ -363,7 +362,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; - VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key); + VALUE curr_key = entry->key; switch (curr_key) { case CONCURRENT_SET_EMPTY: From 7a474e1fbd6198e2bffff9bc4f94bfa376162d59 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 15 Oct 2025 22:01:00 -0400 Subject: [PATCH 0363/2435] ZJIT: Inline String#getbyte (#14842) --- zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 6 ++++ zjit/src/cruby.rs | 1 + zjit/src/cruby_methods.rs | 13 +++++++ zjit/src/hir.rs | 71 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 43fec090149ef8..b54e9404fdb10e 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -73,6 +73,7 @@ fn main() { .allowlist_function("rb_utf8_str_new") .allowlist_function("rb_str_buf_append") .allowlist_function("rb_str_dup") + .allowlist_function("rb_str_getbyte") .allowlist_type("ruby_preserved_encindex") .allowlist_function("rb_class2name") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0bf0205b5351d2..049501dd1518fb 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -363,6 +363,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // If it happens we abort the compilation for now Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state), Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)), + &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), @@ -2109,6 +2110,11 @@ fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec result } +fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) -> Opnd { + // TODO(max): Open-code rb_str_getbyte to avoid a call + asm_ccall!(asm, rb_str_getbyte, string, index) +} + /// Generate a JIT entry that just increments exit_compilation_failure and exits fn gen_compile_error_counter(cb: &mut CodeBlock, compile_error: &CompileError) -> Result { let mut asm = Assembler::new(); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index dca4d9180556e8..5f4eac1db5ed9e 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -132,6 +132,7 @@ unsafe extern "C" { pub fn rb_hash_empty_p(hash: VALUE) -> VALUE; pub fn rb_yjit_str_concat_codepoint(str: VALUE, codepoint: VALUE); pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; + pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 27e6b151bf5020..0da7b07ad47c2e 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -190,6 +190,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); + annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); @@ -257,3 +258,15 @@ fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins } None } + +fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[index] = args else { return None; }; + if fun.likely_a(index, types::Fixnum, state) { + // String#getbyte with a Fixnum is leaf and nogc; otherwise it may run arbitrary Ruby code + // when converting the index to a C integer. + let index = fun.coerce_to(block, index, types::Fixnum, state); + let result = fun.push_insn(block, hir::Insn::StringGetbyteFixnum { string: recv, index }); + return Some(result); + } + None +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4ad20b9c1d8295..f7df493d4a0593 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -557,6 +557,8 @@ pub enum Insn { StringCopy { val: InsnId, chilled: bool, state: InsnId }, StringIntern { val: InsnId, state: InsnId }, StringConcat { strings: Vec, state: InsnId }, + /// Call rb_str_getbyte with known-Fixnum index + StringGetbyteFixnum { string: InsnId, index: InsnId }, /// Combine count stack values into a regexp ToRegexp { opt: usize, values: Vec, state: InsnId }, @@ -860,6 +862,7 @@ impl Insn { // but we don't have type information here in `impl Insn`. See rb_range_new(). Insn::NewRange { .. } => true, Insn::NewRangeFixnum { .. } => false, + Insn::StringGetbyteFixnum { .. } => false, _ => true, } } @@ -941,6 +944,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) } + Insn::StringGetbyteFixnum { string, index, .. } => { + write!(f, "StringGetbyteFixnum {string}, {index}") + } Insn::ToRegexp { values, opt, .. } => { write!(f, "ToRegexp")?; let mut prefix = " "; @@ -1498,6 +1504,7 @@ impl Function { &StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state }, &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, &StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) }, + &StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) }, &ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state }, &Test { val } => Test { val: find!(val) }, &IsNil { val } => IsNil { val: find!(val) }, @@ -1679,6 +1686,7 @@ impl Function { Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, + Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass), Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, @@ -2712,6 +2720,10 @@ impl Function { worklist.extend(strings); worklist.push_back(state); } + &Insn::StringGetbyteFixnum { string, index } => { + worklist.push_back(string); + worklist.push_back(index); + } &Insn::ToRegexp { ref values, state, .. } => { worklist.extend(values); worklist.push_back(state); @@ -13122,4 +13134,63 @@ mod opt_tests { Return v27 "); } + + #[test] + fn test_optimize_string_getbyte_fixnum() { + eval(r#" + def test(s, i) = s.getbyte(i) + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:Fixnum = GuardType v12, Fixnum + v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_elide_string_getbyte_fixnum() { + eval(r#" + def test(s, i) + s.getbyte(i) + 5 + end + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v29:StringExact = GuardType v11, StringExact + v30:Fixnum = GuardType v12, Fixnum + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } } From 1d95d75c3f8821309356131beea837ff158dffc1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 15 Oct 2025 23:40:45 -0400 Subject: [PATCH 0364/2435] ZJIT: Profile opt_succ and inline Integer#succ for Fixnum (#14846) This is only really called a lot in the benchmark harness, as far as I can tell. --- insns.def | 1 + zjit/src/cruby_bindings.inc.rs | 7 ++-- zjit/src/cruby_methods.rs | 12 ++++++ zjit/src/hir.rs | 76 ++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 1 + 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/insns.def b/insns.def index 69a8210d7d6f99..8225d1cceaf97e 100644 --- a/insns.def +++ b/insns.def @@ -1598,6 +1598,7 @@ opt_succ (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_succ(recv); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 17cda12a0b6204..ffaafed5ff33c2 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -702,9 +702,10 @@ pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 237; pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 238; pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 239; pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 240; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 241; -pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 242; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 243; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 244; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0da7b07ad47c2e..11496f7b9874a0 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -206,6 +206,7 @@ pub fn init() -> Annotations { annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", types::NilClass, no_gc, leaf, elidable); + annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cString, "to_s", inline_string_to_s); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); @@ -270,3 +271,14 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir } None } + +fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + if fun.likely_a(recv, types::Fixnum, state) { + let left = fun.coerce_to(block, recv, types::Fixnum, state); + let right = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(VALUE::fixnum_from_usize(1)) }); + let result = fun.push_insn(block, hir::Insn::FixnumAdd { left, right, state }); + return Some(result); + } + None +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index f7df493d4a0593..a87a416e9e5408 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13193,4 +13193,80 @@ mod opt_tests { Return v20 "); } + + #[test] + fn test_inline_integer_succ_with_fixnum() { + eval(" + def test(x) = x.succ + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum[1] = Const Value(1) + v26:Fixnum = FixnumAdd v24, v25 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_bignum() { + eval(" + def test(x) = x.succ + test(4 << 70) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Integer = GuardType v9, Integer + v25:BasicObject = CCallWithFrame succ@0x1038, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_args() { + eval(" + def test = 4.succ 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[4] = Const Value(4) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = SendWithoutBlock v10, :succ, v11 + CheckInterrupts + Return v13 + "); + } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e7db47142bcf65..e935ec9731f383 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -82,6 +82,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_objtostring => profile_operands(profiler, profile, 1), YARVINSN_opt_length => profile_operands(profiler, profile, 1), YARVINSN_opt_size => profile_operands(profiler, profile, 1), + YARVINSN_opt_succ => profile_operands(profiler, profile, 1), YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr(); let argc = unsafe { vm_ci_argc((*cd).ci) }; From f5d3b6e6261569a7205b637052d1fec45ae4620b Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 15 Oct 2025 08:46:32 +0200 Subject: [PATCH 0365/2435] [ruby/prism] Add support for `Prism.parse(foo, version: "current")` The docs currently say to use `Prism.parse(foo, version: RUBY_VERSION)` for this. By specifying "current" instead, we can have prism raise a more specifc error. Note: Does not use `ruby_version` from `ruby/version.h` because writing a test for that is not really possible. `RUBY_VERSION` is nicely stubbable for both the c-ext and FFI backend. https://github.com/ruby/prism/commit/9c5cd205cf --- lib/prism.rb | 21 +++++++++++++++++++++ lib/prism/ffi.rb | 10 ++++++++-- prism/extension.c | 13 +++++++++++-- test/prism/api/parse_test.rb | 30 ++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/lib/prism.rb b/lib/prism.rb index dceba4b1f5c0a8..2cbe196b57e2b4 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -37,6 +37,27 @@ module Prism private_constant :LexCompat private_constant :LexRipper + # Raised when requested to parse as the currently running Ruby version but Prism has no support for it. + class CurrentVersionError < ArgumentError + # Initialize a new exception for the given ruby version string. + def initialize(version) + message = +"invalid version: Requested to parse as `version: 'current'`; " + gem_version = + begin + Gem::Version.new(version) + rescue ArgumentError + end + + if gem_version && gem_version < Gem::Version.new("3.3.0") + message << " #{version} is below the minimum supported syntax." + else + message << " #{version} is unknown. Please update the `prism` gem." + end + + super(message) + end + end + # :call-seq: # Prism::lex_compat(source, **options) -> LexCompat::Result # diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 5ae177055f0108..a3bf5786bd67b7 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -423,7 +423,9 @@ def dump_options_command_line(options) # Return the value that should be dumped for the version option. def dump_options_version(version) - case version + current = version == "current" + + case current ? RUBY_VERSION : version when nil, "latest" 0 # Handled in pm_parser_init when /\A3\.3(\.\d+)?\z/ @@ -433,7 +435,11 @@ def dump_options_version(version) when /\A3\.5(\.\d+)?\z/ 3 else - raise ArgumentError, "invalid version: #{version}" + if current + raise CurrentVersionError, RUBY_VERSION + else + raise ArgumentError, "invalid version: #{version}" + end end end diff --git a/prism/extension.c b/prism/extension.c index 83415d0c29923e..0c9d04225da1fe 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -25,6 +25,7 @@ VALUE rb_cPrismLexResult; VALUE rb_cPrismParseLexResult; VALUE rb_cPrismStringQuery; VALUE rb_cPrismScope; +VALUE rb_cPrismCurrentVersionError; VALUE rb_cPrismDebugEncoding; @@ -199,7 +200,13 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { if (!NIL_P(value)) { const char *version = check_string(value); - if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { + if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) { + VALUE current_ruby_value = rb_const_get(rb_cObject, rb_intern("RUBY_VERSION")); + const char *current_version = RSTRING_PTR(current_ruby_value); + if (!pm_options_version_set(options, current_version, 3)) { + rb_exc_raise(rb_exc_new_str(rb_cPrismCurrentVersionError, current_ruby_value)); + } + } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); } } @@ -888,7 +895,7 @@ parse_input(pm_string_t *input, const pm_options_t *options) { * version of Ruby syntax (which you can trigger with `nil` or * `"latest"`). You may also restrict the syntax to a specific version of * Ruby, e.g., with `"3.3.0"`. To parse with the same syntax version that - * the current Ruby is running use `version: RUBY_VERSION`. Raises + * the current Ruby is running use `version: "current"`. Raises * ArgumentError if the version is not currently supported by Prism. */ static VALUE @@ -1364,6 +1371,8 @@ Init_prism(void) { rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject); rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject); + rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError")); + // Intern all of the IDs eagerly that we support so that we don't have to do // it every time we parse. rb_id_option_command_line = rb_intern_const("command_line"); diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index bbce8a8fadb80c..67a252c5895541 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -140,6 +140,25 @@ def test_version end end + def test_version_current + if RUBY_VERSION >= "3.3" + assert Prism.parse_success?("1 + 1", version: "current") + else + assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") } + end + + version = RUBY_VERSION.split(".").tap { |segments| segments[0] = segments[0].succ }.join(".") + stub_ruby_version(version) do + error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") } + assert_includes error.message, "unknown" + end + + stub_ruby_version("2.7.0") do + error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") } + assert_includes error.message, "minimum" + end + end + def test_scopes assert_kind_of Prism::CallNode, Prism.parse_statement("foo") assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]]) @@ -167,5 +186,16 @@ def find_source_file_node(program) queue.concat(node.compact_child_nodes) end end + + def stub_ruby_version(version) + old_version = RUBY_VERSION + + Object.send(:remove_const, :RUBY_VERSION) + Object.const_set(:RUBY_VERSION, version) + yield + ensure + Object.send(:remove_const, :RUBY_VERSION) + Object.const_set(:RUBY_VERSION, old_version) + end end end From aa2d3cd5133e6a5deb24fa047e5a66c2b65873eb Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 08:33:29 -0400 Subject: [PATCH 0366/2435] [ruby/prism] Bump to v1.6.0 https://github.com/ruby/prism/commit/b72fcc6183 --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 4 ++-- prism/version.h | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 8f6075ad9643d4..168f8211ff384a 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.5.2" + spec.version = "1.6.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index 1f15b775ff0266..70017a4ae39ca2 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.5.2" +#define EXPECTED_PRISM_VERSION "1.6.0" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 271631b5ace817..526be67431f0f0 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,11 +10,11 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 5 + MINOR_VERSION = 6 # The patch version of prism that we are expecting to find in the serialized # strings. - PATCH_VERSION = 2 + PATCH_VERSION = 0 # Deserialize the dumped output from a request to parse or parse_file. # diff --git a/prism/version.h b/prism/version.h index c0d82dc8e2b2f4..f202b0f4d72c3a 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,16 +14,16 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 5 +#define PRISM_VERSION_MINOR 6 /** * The patch version of the Prism library as an int. */ -#define PRISM_VERSION_PATCH 2 +#define PRISM_VERSION_PATCH 0 /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.5.2" +#define PRISM_VERSION "1.6.0" #endif From ea4c3169c1629eddae57ab218cf50974404a76ad Mon Sep 17 00:00:00 2001 From: git Date: Thu, 16 Oct 2025 12:45:23 +0000 Subject: [PATCH 0367/2435] Update default gems list at aa2d3cd5133e6a5deb24fa047e5a66 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index c9507fe2d2dc46..bf4820b86f000f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -192,7 +192,7 @@ The following default gems are updated. * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 -* prism 1.5.2 +* prism 1.6.0 * psych 5.2.6 * resolv 0.6.2 * stringio 3.1.8.dev From 99929d6f2b8433d5d2573054f08070e8a4c6bac3 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 09:20:03 -0400 Subject: [PATCH 0368/2435] [ruby/prism] Do not rely on Gem being loaded https://github.com/ruby/prism/commit/2466940e49 --- lib/prism.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/prism.rb b/lib/prism.rb index 2cbe196b57e2b4..bae5427e79dee3 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -42,13 +42,12 @@ class CurrentVersionError < ArgumentError # Initialize a new exception for the given ruby version string. def initialize(version) message = +"invalid version: Requested to parse as `version: 'current'`; " - gem_version = - begin - Gem::Version.new(version) - rescue ArgumentError + segments = + if version.match?(/\A\d+\.\d+.\d+\z/) + version.split(".").map(&:to_i) end - if gem_version && gem_version < Gem::Version.new("3.3.0") + if segments && (segments[0] < 3) || (segments[0] == 3 && segments[1] < 3) message << " #{version} is below the minimum supported syntax." else message << " #{version} is unknown. Please update the `prism` gem." From 6652d5072fa4888443d43db0a4026524b56925bf Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 09:59:47 -0400 Subject: [PATCH 0369/2435] [ruby/prism] Create a new string for the current version error https://github.com/ruby/prism/commit/ebf4425d49 --- prism/extension.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/prism/extension.c b/prism/extension.c index 0c9d04225da1fe..71c2d91b98d0f0 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -201,10 +201,9 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { const char *version = check_string(value); if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) { - VALUE current_ruby_value = rb_const_get(rb_cObject, rb_intern("RUBY_VERSION")); - const char *current_version = RSTRING_PTR(current_ruby_value); + const char *current_version = RSTRING_PTR(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION"))); if (!pm_options_version_set(options, current_version, 3)) { - rb_exc_raise(rb_exc_new_str(rb_cPrismCurrentVersionError, current_ruby_value)); + rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, current_version)); } } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); From cea3307fe61992f576dd665ae1066e8073b53dab Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 10:10:02 -0400 Subject: [PATCH 0370/2435] [ruby/prism] Do not stub Ruby version https://github.com/ruby/prism/commit/44c4306247 --- test/prism/api/parse_test.rb | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 67a252c5895541..96aec7c838457a 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -148,15 +148,8 @@ def test_version_current end version = RUBY_VERSION.split(".").tap { |segments| segments[0] = segments[0].succ }.join(".") - stub_ruby_version(version) do - error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") } - assert_includes error.message, "unknown" - end - - stub_ruby_version("2.7.0") do - error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") } - assert_includes error.message, "minimum" - end + assert_includes CurrentVersionError.new(version).message, "unknown" + assert_includes CurrentVersionError.new("2.7").message, "minimum" end def test_scopes @@ -186,16 +179,5 @@ def find_source_file_node(program) queue.concat(node.compact_child_nodes) end end - - def stub_ruby_version(version) - old_version = RUBY_VERSION - - Object.send(:remove_const, :RUBY_VERSION) - Object.const_set(:RUBY_VERSION, version) - yield - ensure - Object.send(:remove_const, :RUBY_VERSION) - Object.const_set(:RUBY_VERSION, old_version) - end end end From a8a8f1c20e3f10614de7b6bc262d910678b4f526 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 10:29:11 -0400 Subject: [PATCH 0371/2435] [ruby/prism] Handle RUBY_VERSION being nil https://github.com/ruby/prism/commit/dda0dc81df --- lib/prism.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism.rb b/lib/prism.rb index bae5427e79dee3..f6ad0c1fd10155 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -47,7 +47,7 @@ def initialize(version) version.split(".").map(&:to_i) end - if segments && (segments[0] < 3) || (segments[0] == 3 && segments[1] < 3) + if segments && ((segments[0] < 3) || (segments[0] == 3 && segments[1] < 3)) message << " #{version} is below the minimum supported syntax." else message << " #{version} is unknown. Please update the `prism` gem." From 68f45a6da7bcea9126e04653a5ab47ddcb3b3126 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 16 Oct 2025 10:37:42 -0400 Subject: [PATCH 0372/2435] [ruby/prism] Do not rely on RUBY_VERSION being consistent https://github.com/ruby/prism/commit/34428946db --- test/prism/api/parse_test.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 96aec7c838457a..1f885fa4935302 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -146,10 +146,6 @@ def test_version_current else assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") } end - - version = RUBY_VERSION.split(".").tap { |segments| segments[0] = segments[0].succ }.join(".") - assert_includes CurrentVersionError.new(version).message, "unknown" - assert_includes CurrentVersionError.new("2.7").message, "minimum" end def test_scopes From f925f1ae7b18c6314fe59d19cd56107de91c3b26 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 16 Oct 2025 11:34:07 -0400 Subject: [PATCH 0373/2435] ZJIT: Inline BasicObject#initialize (#14856) It just returns nil. --- zjit/src/cruby_methods.rs | 8 +++++++- zjit/src/hir.rs | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 11496f7b9874a0..ea9f1beffce3f8 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -205,7 +205,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cBasicObject, "initialize", types::NilClass, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cString, "to_s", inline_string_to_s); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; @@ -282,3 +282,9 @@ fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir:: } None } + +fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); + Some(result) +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a87a416e9e5408..633e4d64f7b024 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -10626,7 +10626,7 @@ mod opt_tests { v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(C@0x1008) - v47:NilClass = CCall initialize@0x1070, v43 + v47:NilClass = Const Value(nil) CheckInterrupts CheckInterrupts Return v43 @@ -10694,7 +10694,7 @@ mod opt_tests { v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Object@0x1008) - v47:NilClass = CCall initialize@0x1070, v43 + v47:NilClass = Const Value(nil) CheckInterrupts CheckInterrupts Return v43 @@ -10725,7 +10725,7 @@ mod opt_tests { v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(BasicObject@0x1008) - v47:NilClass = CCall initialize@0x1070, v43 + v47:NilClass = Const Value(nil) CheckInterrupts CheckInterrupts Return v43 From 190a3cd4759e62166004639bbe3b1f6a134b1f3b Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 16 Oct 2025 13:35:33 -0400 Subject: [PATCH 0374/2435] ZJIT: [DOC] Recommend cargo-binstall for ZJIT tool installation (GH-14859) --- doc/zjit.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index d0d3e361913039..f3db94448d4513 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -32,11 +32,17 @@ Note that tests link against CRuby, so directly calling `cargo test`, or `cargo First, ensure you have `cargo` installed. If you do not already have it, you can use [rustup.rs](https://rustup.rs/). +Also install cargo-binstall with: + +```bash +cargo install cargo-binstall +``` + Make sure to add `--enable-zjit=dev` when you run `configure`, then install the following tools: ```bash -cargo install cargo-nextest -cargo install cargo-insta +cargo binstall --secure cargo-nextest +cargo binstall --secure cargo-insta ``` `cargo-insta` is used for updating snapshots. `cargo-nextest` runs each test in its own process, which is valuable since CRuby only supports booting once per process, and most APIs are not thread safe. From 037b6e24ea89e15c9b24e427862ba43a916401ee Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 16 Oct 2025 15:16:47 -0400 Subject: [PATCH 0375/2435] ZJIT: Break out patchpoint exit reasons (#14858) We have a lot of patchpoint exits on some applications and this helps pin down why. --- zjit/src/stats.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 6faa328a1ca736..843806e5be5e89 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -141,7 +141,13 @@ make_counters! { exit_guard_type_not_failure, exit_guard_bit_equals_failure, exit_guard_shape_failure, - exit_patchpoint, + exit_patchpoint_bop_redefined, + exit_patchpoint_method_redefined, + exit_patchpoint_stable_constant_names, + exit_patchpoint_no_tracepoint, + exit_patchpoint_no_ep_escape, + exit_patchpoint_single_ractor_mode, + exit_patchpoint_no_singleton_class, exit_callee_side_exit, exit_obj_to_string_fallback, exit_interrupt, @@ -312,6 +318,7 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { use crate::hir::SideExitReason::*; use crate::hir::CallType::*; + use crate::hir::Invariant; use crate::stats::Counter::*; match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, @@ -328,13 +335,26 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardTypeNot(_) => exit_guard_type_not_failure, GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, - PatchPoint(_) => exit_patchpoint, CalleeSideExit => exit_callee_side_exit, ObjToStringFallback => exit_obj_to_string_fallback, Interrupt => exit_interrupt, StackOverflow => exit_stackoverflow, BlockParamProxyModified => exit_block_param_proxy_modified, BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc, + PatchPoint(Invariant::BOPRedefined { .. }) + => exit_patchpoint_bop_redefined, + PatchPoint(Invariant::MethodRedefined { .. }) + => exit_patchpoint_method_redefined, + PatchPoint(Invariant::StableConstantNames { .. }) + => exit_patchpoint_stable_constant_names, + PatchPoint(Invariant::NoTracePoint) + => exit_patchpoint_no_tracepoint, + PatchPoint(Invariant::NoEPEscape(_)) + => exit_patchpoint_no_ep_escape, + PatchPoint(Invariant::SingleRactorMode) + => exit_patchpoint_single_ractor_mode, + PatchPoint(Invariant::NoSingletonClass { .. }) + => exit_patchpoint_no_singleton_class, } } From 9598b4449d50eb7d262a3711c43345cb987834f6 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 16 Oct 2025 12:57:00 -0400 Subject: [PATCH 0376/2435] ZJIT: Fix singleton class qualified method names in stats Now methods on singleton classes (for example, `new`) get split up into `String*#new`, `Array*#new`, ... (where the `*` indicates a singleton class) instead of all looking like `Class#new`. before: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (59.8% of total 10,506,888): String#<<: 987,752 ( 9.4%) Kernel#is_a?: 755,223 ( 7.2%) Hash#[]=: 700,802 ( 6.7%) Regexp#match?: 400,129 ( 3.8%) String#empty?: 353,775 ( 3.4%) String#start_with?: 334,961 ( 3.2%) Hash#key?: 331,080 ( 3.2%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%) TrueClass#===: 235,771 ( 2.2%) FalseClass#===: 231,144 ( 2.2%) Array#include?: 213,362 ( 2.0%) Kernel#respond_to?: 198,730 ( 1.9%) Kernel#dup: 178,920 ( 1.7%) Kernel#block_given?: 178,767 ( 1.7%) BasicObject#!=: 170,602 ( 1.6%) Class#new: 168,079 ( 1.6%) Kernel#kind_of?: 165,600 ( 1.6%) String#==: 158,036 ( 1.5%) Module#clock_gettime: 144,992 ( 1.4%) NilClass#===: 137,833 ( 1.3%) ``` after: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (59.8% of total 10,506,906): String#<<: 987,752 ( 9.4%) Kernel#is_a?: 755,237 ( 7.2%) Hash#[]=: 700,802 ( 6.7%) Regexp#match?: 400,129 ( 3.8%) String#empty?: 353,775 ( 3.4%) String#start_with?: 334,961 ( 3.2%) Hash#key?: 331,080 ( 3.2%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%) TrueClass#===: 235,771 ( 2.2%) FalseClass#===: 231,144 ( 2.2%) Array#include?: 213,362 ( 2.0%) Kernel#respond_to?: 198,730 ( 1.9%) Kernel#dup: 178,920 ( 1.7%) Kernel#block_given?: 178,767 ( 1.7%) BasicObject#!=: 170,602 ( 1.6%) String*#new: 166,696 ( 1.6%) Kernel#kind_of?: 165,600 ( 1.6%) String#==: 158,039 ( 1.5%) Process*#clock_gettime: 144,992 ( 1.4%) NilClass#===: 137,833 ( 1.3%) ``` --- zjit/src/hir.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 633e4d64f7b024..ca403cf36c2b7c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2506,12 +2506,22 @@ impl Function { Err(()) } + fn qualified_method_name(class: VALUE, method_id: ID) -> String { + let method_name = method_id.contents_lossy(); + // rb_zjit_singleton_class_p also checks if it's a class + if unsafe { rb_zjit_singleton_class_p(class) } { + let class_name = get_class_name(unsafe { rb_class_attached_object(class) }); + format!("{}.{}", class_name, method_name) + } else { + let class_name = get_class_name(class); + format!("{}#{}", class_name, method_name) + } + } + fn count_not_inlined_cfunc(fun: &mut Function, block: BlockId, cme: *const rb_callable_method_entry_t) { let owner = unsafe { (*cme).owner }; let called_id = unsafe { (*cme).called_id }; - let class_name = get_class_name(owner); - let method_name = called_id.contents_lossy(); - let qualified_method_name = format!("{}#{}", class_name, method_name); + let qualified_method_name = qualified_method_name(owner, called_id); let not_inlined_cfunc_counter_pointers = ZJITState::get_not_inlined_cfunc_counter_pointers(); let counter_ptr = not_inlined_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); let counter_ptr = &mut **counter_ptr as *mut u64; @@ -2522,9 +2532,7 @@ impl Function { fn count_not_annotated_cfunc(fun: &mut Function, block: BlockId, cme: *const rb_callable_method_entry_t) { let owner = unsafe { (*cme).owner }; let called_id = unsafe { (*cme).called_id }; - let class_name = get_class_name(owner); - let method_name = called_id.contents_lossy(); - let qualified_method_name = format!("{}#{}", class_name, method_name); + let qualified_method_name = qualified_method_name(owner, called_id); let not_annotated_cfunc_counter_pointers = ZJITState::get_not_annotated_cfunc_counter_pointers(); let counter_ptr = not_annotated_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); let counter_ptr = &mut **counter_ptr as *mut u64; From 3fa848460098d1e77919f5280ff3dfd7687e243b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 16 Oct 2025 13:06:03 -0400 Subject: [PATCH 0377/2435] ZJIT: Add to counters when FnProperties.inline inlining succeeds This counts methods that can be folded away to nothing *in addition* to the already-counted `CCall`. --- zjit/src/codegen.rs | 1 - zjit/src/hir.rs | 152 ++++++++++++++++++++++++++++++-------------- 2 files changed, 103 insertions(+), 50 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 049501dd1518fb..42501de2423888 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -711,7 +711,6 @@ fn gen_ccall_with_frame(jit: &mut JITState, asm: &mut Assembler, cfunc: *const u /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, args: Vec) -> lir::Opnd { - gen_incr_counter(asm, Counter::inline_cfunc_optimized_send_count); asm.ccall(cfunc, args) } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ca403cf36c2b7c..48011bd0883e5b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2412,6 +2412,7 @@ impl Function { assert_ne!(block, tmp_block); let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns); fun.blocks[block.0].insns.extend(insns); + fun.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); fun.make_equal_to(send_insn_id, replacement); fun.remove_block(tmp_block); return Ok(()); @@ -2425,6 +2426,7 @@ impl Function { let elidable = props.elidable; // Filter for a leaf and GC free function if props.leaf && props.no_gc { + fun.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { @@ -2467,6 +2469,7 @@ impl Function { assert_ne!(block, tmp_block); let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns); fun.blocks[block.0].insns.extend(insns); + fun.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); fun.make_equal_to(send_insn_id, replacement); fun.remove_block(tmp_block); return Ok(()); @@ -9243,6 +9246,7 @@ mod opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v26:ArrayExact = GuardType v9, ArrayExact v27:BasicObject = ArrayArefFixnum v26, v13 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v27 "); @@ -9272,6 +9276,7 @@ mod opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v26:HashExact = GuardType v9, HashExact v27:BasicObject = HashAref v26, v13 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v27 "); @@ -9919,6 +9924,7 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) v22:Fixnum = GuardType v9, Fixnum + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v22 "); @@ -9942,6 +9948,7 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v11 "); @@ -9970,6 +9977,7 @@ mod opt_tests { v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -10004,7 +10012,8 @@ mod opt_tests { v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) - v33:StringExact|NilClass = CCall name@0x1048, v29 + IncrCounter inline_cfunc_optimized_send_count + v34:StringExact|NilClass = CCall name@0x1048, v29 PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -10035,7 +10044,8 @@ mod opt_tests { v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:Fixnum = CCall length@0x1038, v14 + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v14 v21:Fixnum[5] = Const Value(5) CheckInterrupts Return v21 @@ -10178,7 +10188,8 @@ mod opt_tests { v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:Fixnum = CCall size@0x1038, v14 + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v14 v21:Fixnum[5] = Const Value(5) CheckInterrupts Return v21 @@ -10505,9 +10516,10 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:Fixnum = CCall bytesize@0x1040, v12 + IncrCounter inline_cfunc_optimized_send_count + v24:Fixnum = CCall bytesize@0x1040, v12 CheckInterrupts - Return v23 + Return v24 "); } @@ -10635,6 +10647,7 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(C@0x1008) v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts Return v43 @@ -10703,6 +10716,7 @@ mod opt_tests { PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Object@0x1008) v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts Return v43 @@ -10734,6 +10748,7 @@ mod opt_tests { PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(BasicObject@0x1008) v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts Return v43 @@ -10914,9 +10929,10 @@ mod opt_tests { v17:ArrayExact = NewArray v11, v12 PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:Fixnum = CCall length@0x1038, v17 + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v17 CheckInterrupts - Return v30 + Return v31 "); } @@ -10940,9 +10956,10 @@ mod opt_tests { v17:ArrayExact = NewArray v11, v12 PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:Fixnum = CCall size@0x1038, v17 + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v17 CheckInterrupts - Return v30 + Return v31 "); } @@ -11584,6 +11601,7 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) CheckInterrupts PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v13 "); @@ -11613,6 +11631,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Array@0x1010) v28:BasicObject = ArrayArefFixnum v24, v12 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 "); @@ -11640,9 +11659,10 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v27:Fixnum[5] = Const Value(5) + v28:Fixnum[5] = Const Value(5) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v28 "); } @@ -11666,9 +11686,10 @@ mod opt_tests { v13:Fixnum[-3] = Const Value(-3) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v27:Fixnum[4] = Const Value(4) + v28:Fixnum[4] = Const Value(4) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v28 "); } @@ -11692,9 +11713,10 @@ mod opt_tests { v13:Fixnum[-10] = Const Value(-10) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v27:NilClass = Const Value(nil) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v28 "); } @@ -11718,9 +11740,10 @@ mod opt_tests { v13:Fixnum[10] = Const Value(10) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v27:NilClass = Const Value(nil) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v28 "); } @@ -11847,9 +11870,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:NilClass = Const Value(nil) PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v22:TrueClass = CCall nil?@0x1038, v10 + IncrCounter inline_cfunc_optimized_send_count + v23:TrueClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v22 + Return v23 "); } @@ -11873,6 +11897,7 @@ mod opt_tests { bb2(v6:BasicObject): v10:NilClass = Const Value(nil) PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count v17:Fixnum[1] = Const Value(1) CheckInterrupts Return v17 @@ -11896,9 +11921,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v22:FalseClass = CCall nil?@0x1038, v10 + IncrCounter inline_cfunc_optimized_send_count + v23:FalseClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v22 + Return v23 "); } @@ -11922,6 +11948,7 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count v17:Fixnum[2] = Const Value(2) CheckInterrupts Return v17 @@ -11948,9 +11975,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) v24:NilClass = GuardType v9, NilClass - v25:TrueClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:TrueClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -11974,9 +12002,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) v24:FalseClass = GuardType v9, FalseClass - v25:FalseClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -12000,9 +12029,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) v24:TrueClass = GuardType v9, TrueClass - v25:FalseClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -12026,9 +12056,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) v24:StaticSymbol = GuardType v9, StaticSymbol - v25:FalseClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -12052,9 +12083,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) v24:Fixnum = GuardType v9, Fixnum - v25:FalseClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -12078,9 +12110,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) v24:Flonum = GuardType v9, Flonum - v25:FalseClass = CCall nil?@0x1038, v24 + IncrCounter inline_cfunc_optimized_send_count + v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v25 + Return v26 "); } @@ -12105,9 +12138,10 @@ mod opt_tests { PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v25:StringExact = GuardType v9, StringExact - v26:FalseClass = CCall nil?@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:FalseClass = CCall nil?@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -12132,9 +12166,10 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v25:ArrayExact = GuardType v9, ArrayExact - v26:BoolExact = CCall !@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall !@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -12159,9 +12194,10 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v25:ArrayExact = GuardType v9, ArrayExact - v26:BoolExact = CCall empty?@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -12186,9 +12222,10 @@ mod opt_tests { PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) v25:HashExact = GuardType v9, HashExact - v26:BoolExact = CCall empty?@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -12215,9 +12252,10 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v29:BoolExact = CCall ==@0x1038, v28, v12 + IncrCounter inline_cfunc_optimized_send_count + v30:BoolExact = CCall ==@0x1038, v28, v12 CheckInterrupts - Return v29 + Return v30 "); } @@ -12710,6 +12748,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v12 "); @@ -12734,6 +12773,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v12 "); @@ -12759,6 +12799,7 @@ mod opt_tests { PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v23 "); @@ -12790,6 +12831,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) v31:BasicObject = ArrayArefFixnum v15, v18 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v31 "); @@ -12820,6 +12862,7 @@ mod opt_tests { v28:ArrayExact = GuardType v11, ArrayExact v29:Fixnum = GuardType v12, Fixnum v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v30 "); @@ -12851,6 +12894,7 @@ mod opt_tests { v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v29:Fixnum = GuardType v12, Fixnum v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v30 "); @@ -12882,6 +12926,7 @@ mod opt_tests { PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Hash@0x1008) v31:BasicObject = HashAref v15, v18 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v31 "); @@ -12911,6 +12956,7 @@ mod opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v28:HashExact = GuardType v11, HashExact v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v29 "); @@ -12941,6 +12987,7 @@ mod opt_tests { PatchPoint NoSingletonClass(C@0x1000) v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v29 "); @@ -12970,6 +13017,7 @@ mod opt_tests { PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) PatchPoint NoSingletonClass(Hash@0x1018) v28:BasicObject = HashAref v24, v12 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 "); @@ -12996,9 +13044,10 @@ mod opt_tests { v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) - v25:BasicObject = CCall current@0x1048, v21 + IncrCounter inline_cfunc_optimized_send_count + v26:BasicObject = CCall current@0x1048, v21 CheckInterrupts - Return v25 + Return v26 "); } @@ -13082,9 +13131,10 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v25:ArrayExact = GuardType v9, ArrayExact - v26:Fixnum = CCall length@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -13109,9 +13159,10 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v25:ArrayExact = GuardType v9, ArrayExact - v26:Fixnum = CCall size@0x1038, v25 + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 CheckInterrupts - Return v26 + Return v27 "); } @@ -13166,6 +13217,7 @@ mod opt_tests { v26:StringExact = GuardType v11, StringExact v27:Fixnum = GuardType v12, Fixnum v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 "); @@ -13196,6 +13248,7 @@ mod opt_tests { PatchPoint NoSingletonClass(String@0x1000) v29:StringExact = GuardType v11, StringExact v30:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count v20:Fixnum[5] = Const Value(5) CheckInterrupts Return v20 @@ -13224,6 +13277,7 @@ mod opt_tests { v24:Fixnum = GuardType v9, Fixnum v25:Fixnum[1] = Const Value(1) v26:Fixnum = FixnumAdd v24, v25 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v26 "); From 9a80258b23c76a40070668dbebab8dd6f0361b92 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 15 Oct 2025 19:30:31 -0400 Subject: [PATCH 0378/2435] Fix crash when freeing namespaces remove_class_from_subclasses calls st_insert, which mallocs. Malloc is not allowed in GC. This commit replaces the st_insert with an st_update since we know that ns_id exists in the st_table. The following script reproduces the crash: require "tempfile" Tempfile.create do |file| ns = Namespace.new ns.require(file) end --- class.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/class.c b/class.c index 84c8668b6be3a3..469cc5e54fef86 100644 --- a/class.c +++ b/class.c @@ -496,6 +496,13 @@ class_get_subclasses_for_ns(struct st_table *tbl, VALUE ns_id) return NULL; } +static int +remove_class_from_subclasses_replace_first_entry(st_data_t *key, st_data_t *value, st_data_t arg, int existing) +{ + *value = arg; + return ST_CONTINUE; +} + static void remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) { @@ -516,7 +523,7 @@ remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) if (first_entry) { if (next) { - st_insert(tbl, ns_id, (st_data_t)next); + st_update(tbl, ns_id, remove_class_from_subclasses_replace_first_entry, (st_data_t)next); } else { // no subclass entries in this ns From 4f51f6243eb75395dcc31407fd76cc1b2b356c65 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 16 Oct 2025 14:05:17 -0700 Subject: [PATCH 0379/2435] [rubygems/rubygems] Restrict what schemes are acceptable in the remote fetcher The remote fetcher only works with certain schemes (`http`, `https`, `s3`, and `file`). It's possible for other schemes to show up in this code and it can cause bugs. Before this patch, doing `gem install path:///hello` would result in an infinite loop because this function would do `send "fetch_path"`, calling itself forever. Now we see an exception. I think we should validate gem names earlier, but it's really best practice to restrict the possible strings passed to `send`. https://github.com/rubygems/rubygems/commit/54e2781b73 --- lib/rubygems/remote_fetcher.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 6ed0842963e6f0..01788a6a5f17d5 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -245,11 +245,14 @@ def fetch_http(uri, last_modified = nil, head = false, depth = 0) def fetch_path(uri, mtime = nil, head = false) uri = Gem::Uri.new uri - unless uri.scheme - raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" - end - - data = send "fetch_#{uri.scheme}", uri, mtime, head + method = { + "http" => "fetch_http", + "https" => "fetch_http", + "s3" => "fetch_s3", + "file" => "fetch_file", + }.fetch(uri.scheme) { raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" } + + data = send method, uri, mtime, head if data && !head && uri.to_s.end_with?(".gz") begin From 9664191e19892b30bc7db349aa7cd71de1549983 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 17 Oct 2025 10:24:15 +0900 Subject: [PATCH 0380/2435] Use ruby/setup-ruby v1.265.0 --- .github/workflows/annocheck.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 0aa21706160dbc..70e6398b044d33 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -72,7 +72,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 4537157b830885..7cc357728fc50b 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index e8b37d616143b8..28a9f9ec252885 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -40,7 +40,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 96b1afa05fc48b..2a99f18e2de6cb 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -61,7 +61,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 04be9f309d1c33..7e850e44576c84 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -58,7 +58,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 81b242bbca52ae..bfbe8296845fd5 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -46,7 +46,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 86e951bcb77e46..0dfba09bc39556 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -69,7 +69,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 011351161aea07..fcb062a4c1d5c0 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -98,7 +98,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 38f628243ccff4..9c0f2fe5722bba 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index bf12f80c0eae1a..d9855ee7410b03 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -127,7 +127,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 02afda470aec47..ec92cd5342588c 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -106,7 +106,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@a9bfc2ecf3dd40734a9418f89a7e9d484c32b990 # v1.248.0 + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: ruby-version: '3.1' bundler: none From ff6bd7fb4ef54b6963b5a09f2e09e3dd0d9d7156 Mon Sep 17 00:00:00 2001 From: ishikawa999 Date: Sat, 27 Apr 2024 16:24:39 +0900 Subject: [PATCH 0381/2435] [ruby/ipaddr] Fix InvalidAddressError message https://github.com/ruby/ipaddr/commit/c96dbadee3 --- lib/ipaddr.rb | 4 ++-- test/test_ipaddr.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 1ad7980e9e6480..fca75deff95ebf 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -721,8 +721,8 @@ def in_addr(addr) octets = addr.split('.') end octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}" - (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" + (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}" + (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}" i << 8 | n } end diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 64927a1444d7cb..005927cd054cd9 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -620,4 +620,16 @@ def test_hash assert_equal(true, s.include?(a5)) assert_equal(true, s.include?(a6)) end + + def test_raises_invalid_address_error_with_error_message + e = assert_raise(IPAddr::InvalidAddressError) do + IPAddr.new('192.168.0.1000') + end + assert_equal('invalid address: 192.168.0.1000', e.message) + + e = assert_raise(IPAddr::InvalidAddressError) do + IPAddr.new('192.168.01.100') + end + assert_equal('zero-filled number in IPv4 address is ambiguous: 192.168.01.100', e.message) + end end From b2d4dc9c46719b1a67bd24a4b4cf444c90621a78 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 16 Oct 2025 10:41:40 +0900 Subject: [PATCH 0382/2435] win32: Support more `clockid_t` Add `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID`. --- include/ruby/win32.h | 15 ++++++----- win32/win32.c | 61 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 31dc13e93251fb..be3a1cce964fb1 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -126,12 +126,6 @@ typedef unsigned int uintptr_t; #define O_SHARE_DELETE 0x20000000 /* for rb_w32_open(), rb_w32_wopen() */ typedef int clockid_t; -#if defined(__MINGW32__) -/* I don't know why but these return some strange values. */ -#undef CLOCK_PROCESS_CPUTIME_ID -#undef CLOCK_THREAD_CPUTIME_ID -#undef CLOCK_REALTIME_COARSE -#endif /* defined in win32/win32.c for old versions */ #if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETTIME) @@ -148,6 +142,15 @@ typedef int clockid_t; #ifndef CLOCK_MONOTONIC # define CLOCK_MONOTONIC 1 #endif +#ifndef CLOCK_PROCESS_CPUTIME_ID +# define CLOCK_PROCESS_CPUTIME_ID 2 +#endif +#ifndef CLOCK_THREAD_CPUTIME_ID +# define CLOCK_THREAD_CPUTIME_ID 3 +#endif +#ifndef CLOCK_REALTIME_COARSE +# define CLOCK_REALTIME_COARSE 4 +#endif #undef utime #undef lseek diff --git a/win32/win32.c b/win32/win32.c index 1a5308337a05d3..9bdc9f82769e7e 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4762,19 +4762,39 @@ gettimeofday(struct timeval *tv, struct timezone *tz) } #ifdef NEED_CLOCK_GETTIME +/* License: Ruby's */ +static FILETIME +filetimes_plus(FILETIME t1, FILETIME t2) +{ + ULARGE_INTEGER i1 = {.u = {.LowPart = t1.dwLowDateTime, .HighPart = t1.dwHighDateTime}}; + ULARGE_INTEGER i2 = {.u = {.LowPart = t2.dwLowDateTime, .HighPart = t2.dwHighDateTime}}; + ULARGE_INTEGER i = {.QuadPart = i1.QuadPart + i2.QuadPart}; + return (FILETIME){.dwLowDateTime = i.LowPart, .dwHighDateTime = i.HighPart}; +} + +static void +filetime_to_timespec(FILETIME ft, struct timespec *sp) +{ + long subsec; + sp->tv_sec = filetime_split(&ft, &subsec); + sp->tv_nsec = subsec * 100; +} + +/* License: Ruby's */ +static const secs_in_ns = 1000000000; + /* License: Ruby's */ int clock_gettime(clockid_t clock_id, struct timespec *sp) { switch (clock_id) { case CLOCK_REALTIME: + case CLOCK_REALTIME_COARSE: { FILETIME ft; - long subsec; GetSystemTimePreciseAsFileTime(&ft); - sp->tv_sec = filetime_split(&ft, &subsec); - sp->tv_nsec = subsec * 100; + filetime_to_timespec(ft, sp); return 0; } case CLOCK_MONOTONIC: @@ -4790,10 +4810,28 @@ clock_gettime(clockid_t clock_id, struct timespec *sp) return -1; } sp->tv_sec = count.QuadPart / freq.QuadPart; - if (freq.QuadPart < 1000000000) - sp->tv_nsec = (count.QuadPart % freq.QuadPart) * 1000000000 / freq.QuadPart; + if (freq.QuadPart < secs_in_ns) + sp->tv_nsec = (count.QuadPart % freq.QuadPart) * secs_in_ns / freq.QuadPart; else - sp->tv_nsec = (long)((count.QuadPart % freq.QuadPart) * (1000000000.0 / freq.QuadPart)); + sp->tv_nsec = (long)((count.QuadPart % freq.QuadPart) * ((double)secs_in_ns / freq.QuadPart)); + return 0; + } + case CLOCK_PROCESS_CPUTIME_ID: + case CLOCK_THREAD_CPUTIME_ID: + { + FILETIME ct, et, kt, ut; + BOOL ok; + if (clock_id == CLOCK_PROCESS_CPUTIME_ID) { + ok = GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut); + } + else { + ok = GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut); + } + if (!ok) { + errno = map_errno(GetLastError()); + return -1; + } + filetime_to_timespec(filetimes_plus(kt, ut), sp); return 0; } default: @@ -4810,6 +4848,7 @@ clock_getres(clockid_t clock_id, struct timespec *sp) { switch (clock_id) { case CLOCK_REALTIME: + case CLOCK_REALTIME_COARSE: { sp->tv_sec = 0; sp->tv_nsec = 1000; @@ -4823,7 +4862,15 @@ clock_getres(clockid_t clock_id, struct timespec *sp) return -1; } sp->tv_sec = 0; - sp->tv_nsec = (long)(1000000000.0 / freq.QuadPart); + sp->tv_nsec = (long)((double)secs_in_ns / freq.QuadPart); + return 0; + } + case CLOCK_PROCESS_CPUTIME_ID: + case CLOCK_THREAD_CPUTIME_ID: + { + const int frames_in_sec = 60; + sp->tv_sec = 0; + sp->tv_nsec = (long)(secs_in_ns / frames_in_sec); return 0; } default: From 5a23716c4f10700e9378f66702cd6797475ec30e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 17 Oct 2025 12:01:43 +0900 Subject: [PATCH 0383/2435] Skip low precision clocks to measure performances --- tool/lib/core_assertions.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 7e6dc8e6537bd6..1288b8c9aae132 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -838,6 +838,9 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block) rescue # Constants may be defined but not implemented, e.g., mingw. else + unless Process.clock_getres(clk) < 1.0e-03 + next # needs msec precision + end PERFORMANCE_CLOCK = clk end end From 2dc23c1ad84ee756ff678412636ae255e25d9176 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 17 Oct 2025 15:08:45 +0900 Subject: [PATCH 0384/2435] win32: Fix missing type --- win32/win32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/win32.c b/win32/win32.c index 9bdc9f82769e7e..9095b1a251eab7 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4781,7 +4781,7 @@ filetime_to_timespec(FILETIME ft, struct timespec *sp) } /* License: Ruby's */ -static const secs_in_ns = 1000000000; +static const long secs_in_ns = 1000000000; /* License: Ruby's */ int From 3dc620166bd3c914722c04120ec0b127294d66a7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 13 Oct 2025 12:53:15 +0900 Subject: [PATCH 0385/2435] win32: Install the same packages as vcpkg even on msys --- win32/install-msys-packages.cmd | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 win32/install-msys-packages.cmd diff --git a/win32/install-msys-packages.cmd b/win32/install-msys-packages.cmd new file mode 100755 index 00000000000000..d3adbaf5956651 --- /dev/null +++ b/win32/install-msys-packages.cmd @@ -0,0 +1,29 @@ +::- Install msys packages for rubygems +::- The dependencies are taken from vcpkg.json to share the common info. + +@setlocal EnableExtensions DisableDelayedExpansion || exit /b - 1 +@set PROMPT=$h$e[96m$g$e[39m$s +@set script=%0 +@call set "srcdir=%%script:\win32\%~nx0=%%" + +@if not defined MINGW_PACKAGE_PREFIX ( + ::- Enable msys environment by ridk (from RubyInstaller-DevKit) + where ridk >nul 2>&1 || ( + (echo MINGW_PACKAGE_PREFIX is not set, you have to enable development environment.) 1>&2 + exit /b 1 + ) + call ridk enable %* + echo: +) else if not "%1" == "" ( + ::- Switch msys environment by ridk (from RubyInstaller-DevKit) + call ridk enable %* + echo: +) + +@set pkgs= +@( + for /f %%I in ('powershell -c "(ConvertFrom-Json $input).dependencies"') do @( + call set "pkgs=%%pkgs%% %%MINGW_PACKAGE_PREFIX%%-%%%%I" + ) +) < "%srcdir%\vcpkg.json" +pacman -S --needed --noconfirm %pkgs:~1% From 89961f8581e0b6c84dd24b01c3e1c0f39bd1fdb6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 13 Oct 2025 22:05:40 +0900 Subject: [PATCH 0386/2435] configure.ac: Update caches for functions defined in win32.c --- configure.ac | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b035e1688a8461..981271fc0e290f 100644 --- a/configure.ac +++ b/configure.ac @@ -1236,6 +1236,11 @@ main() ac_cv_header_sys_time_h=no ac_cv_header_sys_times_h=no ac_cv_header_sys_socket_h=no + ac_cv_func_chown=yes + ac_cv_func_getegid=yes + ac_cv_func_geteuid=yes + ac_cv_func_getgid=yes + ac_cv_func_getuid=yes ac_cv_func_execv=yes ac_cv_func_lstat=yes ac_cv_func_times=yes @@ -1248,6 +1253,8 @@ main() ac_cv_func_readlink=yes ac_cv_func_shutdown=yes ac_cv_func_symlink=yes + ac_cv_func_clock_getres=yes + ac_cv_func_clock_gettime=yes ac_cv_lib_crypt_crypt=no ac_cv_func_getpgrp_void=no ac_cv_func_memcmp_working=yes @@ -1264,8 +1271,8 @@ main() AS_IF([test "$target_cpu" = x64], [ ac_cv_func___builtin_setjmp=yes ac_cv_func_round=no + ac_cv_func_tgamma=no ]) - ac_cv_func_tgamma=no AC_CHECK_TYPE([NET_LUID], [], [], [@%:@include @%:@include From 1546362fd10cc6cf441bbbdaf6bb7c48439e0cd4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 15 Oct 2025 23:12:44 +0900 Subject: [PATCH 0387/2435] win32: Prefix `clock_getclock` and `clock_getres` Get rid of conflict with inline versions provided in time.h. --- include/ruby/win32.h | 17 ++++++++--------- win32/win32.c | 4 ---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index be3a1cce964fb1..69e92ed9ffa1ba 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -127,15 +127,14 @@ typedef unsigned int uintptr_t; typedef int clockid_t; -/* defined in win32/win32.c for old versions */ -#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETTIME) -# define HAVE_CLOCK_GETTIME 1 -# define NEED_CLOCK_GETTIME 1 -#endif -#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETRES) -# define HAVE_CLOCK_GETRES 1 -# define NEED_CLOCK_GETRES 1 -#endif +/* + * Since we use our versions in win32/win32.c, not to depend on yet + * another DLL, prefix our versions not to conflict with inline + * versions provided in time.h. + */ +#define clock_gettime rb_w32_clock_gettime +#define clock_getres rb_w32_clock_getres + #ifndef CLOCK_REALTIME # define CLOCK_REALTIME 0 #endif diff --git a/win32/win32.c b/win32/win32.c index 9095b1a251eab7..c13c4e17320268 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4761,7 +4761,6 @@ gettimeofday(struct timeval *tv, struct timezone *tz) return 0; } -#ifdef NEED_CLOCK_GETTIME /* License: Ruby's */ static FILETIME filetimes_plus(FILETIME t1, FILETIME t2) @@ -4839,9 +4838,7 @@ clock_gettime(clockid_t clock_id, struct timespec *sp) return -1; } } -#endif -#ifdef NEED_CLOCK_GETRES /* License: Ruby's */ int clock_getres(clockid_t clock_id, struct timespec *sp) @@ -4878,7 +4875,6 @@ clock_getres(clockid_t clock_id, struct timespec *sp) return -1; } } -#endif /* License: Ruby's */ static char * From 485f079dc59d0bdc76a12292e46b616346de29de Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 16 Oct 2025 10:43:13 +0900 Subject: [PATCH 0388/2435] win32: OBJCOPY is not needed on Windows in favor of def file --- configure.ac | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.ac b/configure.ac index 981271fc0e290f..48f0b31f6a41d5 100644 --- a/configure.ac +++ b/configure.ac @@ -272,6 +272,8 @@ AS_CASE(["${build_os}"], ]) AS_CASE(["${target_os}"], [cygwin*|msys*|mingw*|darwin*], [ + ac_ct_OBJCOPY=":" + ac_cv_prog_OBJCOPY=":" ac_cv_prog_ac_ct_OBJCOPY=":" ]) From 87593f2c0fcf317cf8fcafb2e9ad3742c4d8f76b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 16 Oct 2025 21:49:44 +0900 Subject: [PATCH 0389/2435] Disable shorten-64-to-32 warning on IL32LLP64 Disable the shorten-64-to-32 warning for now, because it currently generates a lot of warnings on platforms where `sizeof(void*)` is larger than `sizeof(long)`. TODO: Replace `long` with `ptrdiff_t` or something in the all sources. --- configure.ac | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/configure.ac b/configure.ac index 48f0b31f6a41d5..339ee3b2f2e66e 100644 --- a/configure.ac +++ b/configure.ac @@ -4304,6 +4304,24 @@ AS_IF([test "${ARCH_FLAG}"], [ CXXFLAGS=`echo "$CXXFLAGS" | sed "s| *$archflagpat"'||'` LDFLAGS=`echo "$LDFLAGS" | sed "s| *$archflagpat"'||'` ]) +AS_CASE([" $rb_cv_warnflags "], [*" -Wshorten-64-to-32 "*|*" -Werror=shorten-64-to-32 "*], [ + voidp_ll= + AS_CASE([$ac_cv_sizeof_voidp], + [SIZEOF_LONG_LONG], [voidp_ll=true], + [@<:@0-9@:>@*], [ + AS_IF([test $ac_cv_sizeof_voidp -gt $ac_cv_sizeof_long], [voidp_ll=true]) + ]) + AS_IF([test "$voidp_ll"], [ + # Disable the shorten-64-to-32 warning for now, because it currently + # generates a lot of warnings on platforms where `sizeof(void*)` is + # larger than `sizeof(long)`. + # + # TODO: Replace `long` with `ptrdiff_t` or something in the all sources. + rb_cv_warnflags=`echo "$rb_cv_warnflags" | + sed -e 's/ -W\(shorten-64-to-32 \)/ -Wno-\1/' \ + -e 's/ -Werror=\(shorten-64-to-32 \)/ -Wno-\1/'` + ]) +]) rb_cv_warnflags=`echo "$rb_cv_warnflags" | sed 's/^ *//;s/ *$//'` warnflags="$rb_cv_warnflags" AC_SUBST(cppflags)dnl From c2860bff88cc4ba692273a5bd2393fa03b78db9a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 16 Oct 2025 22:15:59 +0900 Subject: [PATCH 0390/2435] CI: Try mingw arm64 --- .github/workflows/mingw.yml | 48 +++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 4a71f1753011a6..61ee5209592d1b 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -30,16 +30,30 @@ permissions: # jobs: make: - runs-on: windows-2022 + runs-on: windows-${{ matrix.os }} name: ${{ github.workflow }} (${{ matrix.msystem }}) env: MSYSTEM: ${{ matrix.msystem }} - MSYS2_ARCH: x86_64 - CHOST: 'x86_64-w64-mingw32' - CFLAGS: '-march=x86-64 -mtune=generic -O3 -pipe' - CXXFLAGS: '-march=x86-64 -mtune=generic -O3 -pipe' + MSYS2_ARCH: >- + ${{ + contains(matrix.msystem, 'arm64') && 'aarch64' || + contains(matrix.msystem, '64') && 'x86_64' || 'i686' + }} + MINGW_PACKAGE_PREFIX: >- + mingw-w${{ + endsWith(matrix.msystem, '64') && '64' || '32' + }}-${{ + startsWith(matrix.msystem, 'clang') && 'clang' || + startsWith(matrix.msystem, 'ucrt') && 'ucrt' || + 'mingw' + }}-${{ + contains(matrix.msystem, 'arm64') && 'aarch64' || + endsWith(matrix.msystem, '64') && 'x86_64' || 'i686' + }} + CFLAGS: '-mtune=generic -O3 -pipe' + CXXFLAGS: '-mtune=generic -O3 -pipe' CPPFLAGS: '-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048' LDFLAGS: '-pipe' GITPULLOPTIONS: --no-tags origin ${{ github.ref }} @@ -50,8 +64,12 @@ jobs: # To mitigate flakiness of MinGW CI, we test only one runtime that newer MSYS2 uses. # Ruby 3.2 is the first Windows Ruby to use OpenSSL 3.x - msystem: 'UCRT64' + os: 2022 test_task: 'check' test-all-opts: '--name=!/TestObjSpace#test_reachable_objects_during_iteration/' + - msystem: 'CLANGARM64' + os: 11-arm + test_task: 'test' fail-fast: false if: >- @@ -66,25 +84,26 @@ jobs: - uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2 # v2.29.0 id: msys2 with: - msystem: UCRT64 + msystem: ${{ matrix.msystem }} update: true install: >- git make ruby autoconf - mingw-w64-ucrt-x86_64-gcc - mingw-w64-ucrt-x86_64-ragel - mingw-w64-ucrt-x86_64-openssl - mingw-w64-ucrt-x86_64-libyaml - mingw-w64-ucrt-x86_64-libffi + ${{ env.MINGW_PACKAGE_PREFIX }}-gcc + ${{ env.MINGW_PACKAGE_PREFIX }}-ragel + ${{ env.MINGW_PACKAGE_PREFIX }}-openssl + ${{ env.MINGW_PACKAGE_PREFIX }}-libyaml + ${{ env.MINGW_PACKAGE_PREFIX }}-libffi - name: Set up env id: setup-env working-directory: run: | $msys2 = ${env:MSYS2_LOCATION} - echo $msys2\usr\bin $msys2\ucrt64\bin | + $msystem = ${env:MSYSTEM}.ToLower() + echo $msys2\usr\bin $msys2\$msystem\bin | Tee-Object ${env:GITHUB_PATH} -Append -Encoding utf-8 # Use the fast device for the temporary directory. @@ -96,6 +115,7 @@ jobs: shell: pwsh # cmd.exe does not strip spaces before `|`. env: MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }} + MSYSTEM: ${{ matrix.msystem }} - name: Remove Strawberry Perl pkg-config working-directory: @@ -140,7 +160,7 @@ jobs: I libssl-3-x64.dll group Packages - pacman -Qs mingw-w64-ucrt-x86_64-* | /bin/sed -n "s,local/mingw-w64-ucrt-x86_64-,,p" + pacman -Qs $MINGW_PACKAGE_PREFIX-* | /bin/sed -n "s,local/$MINGW_PACKAGE_PREFIX-,,p" endgroup [ ${#failed[@]} -eq 0 ] @@ -164,6 +184,8 @@ jobs: ../src/configure --disable-install-doc --prefix=/. --build=$CHOST --host=$CHOST --target=$CHOST shell: sh + env: + CHOST: ${{ env.MSYS2_ARCH }}-w64-mingw32 - name: make all timeout-minutes: 30 From 5ced99dddb384762a79c9aa07de130e460479f06 Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Thu, 5 Jun 2025 23:30:22 -0700 Subject: [PATCH 0391/2435] [rubygems/rubygems] :Revamp CmakeBuilder to fix the issues described in #8572. Specifically: * Correctly pass command line arguments to CMake * Call CMake twice - once to configure a project and a second time to build (which is the standard way to use CMake). This fixes the previously incorrect assumption that CMake generates a Make file. * Update the tests to specify a CMake minimum version of 3.26 (which is already two years old). 3.26 is a bit arbritary but it aligns with Rice, and updates from the ancient 3.5 version being used (which CMake generates a warning message saying stop using it!) * Update the CMake call to use CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY to tell CMake to copy compiled binaries to the a Gem's lib directory. Note the updated builder took inspiration from the Cargo Builder, meaning you first create an instance of CmakeBuilder versus just calling class methods. https://github.com/rubygems/rubygems/commit/9e248d4679 --- lib/rubygems/ext/builder.rb | 2 +- lib/rubygems/ext/cmake_builder.rb | 103 ++++++++++++++++++-- test/rubygems/test_gem_ext_cmake_builder.rb | 95 ++++++++++++++---- 3 files changed, 175 insertions(+), 25 deletions(-) diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index b47996d0920bb3..600a6a5ff675ac 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -169,7 +169,7 @@ def builder_for(extension) # :nodoc: @ran_rake = true Gem::Ext::RakeBuilder when /CMakeLists.txt/ then - Gem::Ext::CmakeBuilder + Gem::Ext::CmakeBuilder.new when /Cargo.toml/ then Gem::Ext::CargoBuilder.new else diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index c7bfbb8a57ad27..2915568b39d0c1 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -1,21 +1,110 @@ # frozen_string_literal: true +# This builder creates extensions defined using CMake. Its is invoked if a Gem's spec file +# sets the `extension` property to a string that contains `CMakeLists.txt`. +# +# In general, CMake projects are built in two steps: +# +# * configure +# * build +# +# The builder follow this convention. First it runs a configuration step and then it runs a build step. +# +# CMake projects can be quite configurable - it is likely you will want to specify options when +# installing a gem. To pass options to CMake specify them after `--` in the gem install command. For example: +# +# gem install -- --preset +# +# Note that options are ONLY sent to the configure step - it is not currently possible to specify +# options for the build step. If this becomes and issue then the CMake builder can be updated to +# support build options. +# +# Useful options to know are: +# +# -G to specify a generator (-G Ninja is recommended) +# -D to set a CMake variable (for example -DCMAKE_BUILD_TYPE=Release) +# --preset to use a preset +# +# If the Gem author provides presets, via CMakePresets.json file, you will likely want to use one of them. +# If not, you may wish to specify a generator. Ninja is recommended because it can build projects in parallel +# and thus much faster than building them serially like Make does. + class Gem::Ext::CmakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, + attr_accessor :runner, :profile + def initialize + @runner = self.class.method(:run) + @profile = :release + end + + def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end - unless File.exist?(File.join(cmake_dir, "Makefile")) - require_relative "../command" - cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args] + # Figure the build dir + build_dir = File.join(cmake_dir, "build") - run cmd, results, class_name, cmake_dir - end + # Check if the gem defined presets + check_presets(cmake_dir, args, results) + + # Configure + configure(cmake_dir, build_dir, dest_path, args, results) - make dest_path, results, cmake_dir, target_rbconfig: target_rbconfig + # Compile + compile(cmake_dir, build_dir, args, results) results end + + def configure(cmake_dir, build_dir, install_dir, args, results) + cmd = ["cmake", + cmake_dir, + "-B", + build_dir, + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=#{install_dir}", # Windows + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=#{install_dir}", # Not Windows + *Gem::Command.build_args, + *args] + + runner.call(cmd, results, "cmake_configure", cmake_dir) + end + + def compile(cmake_dir, build_dir, args, results) + cmd = ["cmake", + "--build", + build_dir.to_s, + "--config", + @profile.to_s] + + runner.call(cmd, results, "cmake_compile", cmake_dir) + end + + private + + def check_presets(cmake_dir, args, results) + # Return if the user specified a preset + return unless args.grep(/--preset/i).empty? + + cmd = ["cmake", + "--list-presets"] + + presets = Array.new + begin + runner.call(cmd, presets, "cmake_presets", cmake_dir) + + # Remove the first two lines of the array which is the current_directory and the command + # that was run + presets = presets[2..].join + results << <<~EOS + The gem author provided a list of presets that can be used to build the gem. To use a preset specify it on the command line: + + gem install -- --preset + + #{presets} + EOS + rescue Gem::InstallError + # Do nothing, CMakePresets.json was not included in the Gem + end + end end diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb index b4cf8a8443c99b..e2bdedc710546d 100644 --- a/test/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/rubygems/test_gem_ext_cmake_builder.rb @@ -29,7 +29,7 @@ def setup def test_self_build File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists| cmakelists.write <<-EO_CMAKE -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.26) project(self_build NONE) install (FILES test.txt DESTINATION bin) EO_CMAKE @@ -39,46 +39,107 @@ def test_self_build output = [] - Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext + builder = Gem::Ext::CmakeBuilder.new + builder.build nil, @dest_path, output, [], @dest_path, @ext output = output.join "\n" - assert_match(/^cmake \. -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output) + assert_match(/^current directory: #{Regexp.escape @ext}/, output) + assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output) + assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output) + assert_match(/#{Regexp.escape @ext}/, output) + end + + def test_self_build_presets + File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists| + cmakelists.write <<-EO_CMAKE +cmake_minimum_required(VERSION 3.26) +project(self_build NONE) +install (FILES test.txt DESTINATION bin) + EO_CMAKE + end + + File.open File.join(@ext, "CMakePresets.json"), "w" do |presets| + presets.write <<-EO_CMAKE +{ + "version": 6, + "configurePresets": [ + { + "name": "debug", + "displayName": "Debug", + "generator": "Ninja", + "binaryDir": "build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release", + "generator": "Ninja", + "binaryDir": "build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] +} + EO_CMAKE + end + + FileUtils.touch File.join(@ext, "test.txt") + + output = [] + + builder = Gem::Ext::CmakeBuilder.new + builder.build nil, @dest_path, output, [], @dest_path, @ext + + output = output.join "\n" + + assert_match(/The gem author provided a list of presets that can be used to build the gem./, output) + assert_match(/Available configure presets/, output) + assert_match(/\"debug\" - Debug/, output) + assert_match(/\"release\" - Release/, output) + assert_match(/^current directory: #{Regexp.escape @ext}/, output) + assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output) + assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output) assert_match(/#{Regexp.escape @ext}/, output) - assert_contains_make_command "", output - assert_contains_make_command "install", output - assert_match(/test\.txt/, output) end def test_self_build_fail output = [] + builder = Gem::Ext::CmakeBuilder.new error = assert_raise Gem::InstallError do - Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext + builder.build nil, @dest_path, output, [], @dest_path, @ext end - output = output.join "\n" + assert_match "cmake_configure failed", error.message shell_error_msg = /(CMake Error: .*)/ - - assert_match "cmake failed", error.message - - assert_match(/^cmake . -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output) + output = output.join "\n" assert_match(/#{shell_error_msg}/, output) + assert_match(/CMake Error: The source directory .* does not appear to contain CMakeLists.txt./, output) end def test_self_build_has_makefile - File.open File.join(@ext, "Makefile"), "w" do |makefile| - makefile.puts "all:\n\t@echo ok\ninstall:\n\t@echo ok" + File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists| + cmakelists.write <<-EO_CMAKE +cmake_minimum_required(VERSION 3.26) +project(self_build NONE) +install (FILES test.txt DESTINATION bin) + EO_CMAKE end output = [] - Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext + builder = Gem::Ext::CmakeBuilder.new + builder.build nil, @dest_path, output, [], @dest_path, @ext output = output.join "\n" - assert_contains_make_command "", output - assert_contains_make_command "install", output + # The default generator will create a Makefile in the build directory + makefile = File.join(@ext, "build", "Makefile") + assert(File.exist?(makefile)) end end From 943b5f66f7b2e90c349b1751b489b9881f6c26bc Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 9 Oct 2025 15:52:04 +0100 Subject: [PATCH 0392/2435] CI: Launchable: Fix errors at actions/setup-python on ppc64le/s390x The following errors happened at the actions/setup-python step. https://github.com/ruby/ruby/actions/runs/18229870239 > The version '3.x' with architecture 's390x' was not found for Ubuntu 24.04. > The list of all available versions can be found here: https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json > The version '3.x' with architecture 'ppc64' was not found for Ubuntu 24.04. > The list of all available versions can be found here: https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json After skipping the actions/setup-python step, the following errors also happened at the actions/setup-java step. https://github.com/ruby/ruby/actions/runs/18355975425?pr=14721 > make-ibm (check, ubuntu-24.04-ppc64le) > Could not find satisfied version for SemVer '17'. > make-ibm (check, ubuntu-24.04-s390x) > The process '/usr/bin/bash' failed with exit code 1 > make-ibm (check, ubuntu-24.04-s390x) > Process completed with exit code 127. To fix the errors, I started using the Java distribution semeru (IBM Semeru Runtime Open Edition) on the ppc64le/s390x cases. You can see the following page for the details of the Java distribution semeru. https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions https://github.com/actions/setup-java/blob/ead9eaa3cfe0b0fc2fa749519ae09c3d4f4080b0/src/distributions/semeru/installer.ts#L20-L27 --- .github/actions/launchable/setup/action.yml | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 54f1abd97a35a0..16af8fc3fd74d6 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -92,14 +92,38 @@ runs: uses: actions/setup-python@871daa956ca9ea99f3c3e30acb424b7960676734 # v5.0.0 with: python-version: "3.x" - if: steps.enable-launchable.outputs.enable-launchable + if: >- + ${{ steps.enable-launchable.outputs.enable-launchable + && !endsWith(inputs.os, 'ppc64le') && !endsWith(inputs.os, 's390x') }} - name: Set up Java uses: actions/setup-java@7a445ee88d4e23b52c33fdc7601e40278616c7f8 # v4.0.0 with: distribution: 'temurin' java-version: '17' - if: steps.enable-launchable.outputs.enable-launchable + if: >- + ${{ steps.enable-launchable.outputs.enable-launchable + && !endsWith(inputs.os, 'ppc64le') && !endsWith(inputs.os, 's390x') }} + + - name: Set up Java ppc64le + uses: actions/setup-java@7a445ee88d4e23b52c33fdc7601e40278616c7f8 # v4.0.0 + with: + distribution: 'semeru' + architecture: 'ppc64le' + java-version: '17' + if: >- + ${{ steps.enable-launchable.outputs.enable-launchable + && endsWith(inputs.os, 'ppc64le') }} + + - name: Set up Java s390x + uses: actions/setup-java@7a445ee88d4e23b52c33fdc7601e40278616c7f8 # v4.0.0 + with: + distribution: 'semeru' + architecture: 's390x' + java-version: '17' + if: >- + ${{ steps.enable-launchable.outputs.enable-launchable + && endsWith(inputs.os, 's390x') }} - name: Set global vars id: global @@ -142,7 +166,13 @@ runs: # Since updated PATH variable will be available in only subsequent actions, we need to add the path beforehand. # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path run: echo "$(python -msite --user-base)/bin" >> $GITHUB_PATH - if: steps.enable-launchable.outputs.enable-launchable && startsWith(inputs.os, 'macos') + if: >- + ${{ + steps.enable-launchable.outputs.enable-launchable + && (startsWith(inputs.os, 'macos') + || endsWith(inputs.os, 'ppc64le') + || endsWith(inputs.os, 's390x')) + }} - name: Set up Launchable id: setup-launchable From 837947ac7fb52c0492eb148e3cb17946aefaadc4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 17 Oct 2025 17:34:59 +0900 Subject: [PATCH 0393/2435] Compatibility with test-unit-ruby-core --- tool/lib/core_assertions.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 1288b8c9aae132..fbf2c1a8bb3174 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -74,7 +74,19 @@ def message msg = nil, ending = nil, &default module CoreAssertions require_relative 'envutil' require 'pp' - require '-test-/sanitizers' + begin + require '-test-/sanitizers' + rescue LoadError + # in test-unit-ruby-core gem + def sanitizers + nil + end + else + def sanitizers + Test::Sanitizers + end + end + module_function :sanitizers nil.pretty_inspect @@ -159,7 +171,7 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # ASAN has the same problem - its shadow memory greatly increases memory usage # (plus asan has better ways to detect memory leaks than this assertion) - pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if Test::Sanitizers.asan_enabled? + pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if sanitizers&.asan_enabled? require_relative 'memory_status' raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status) @@ -329,7 +341,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt) - if Test::Sanitizers.lsan_enabled? + if sanitizers&.lsan_enabled? # LSAN may output messages like the following line into stderr. We should ignore it. # ==276855==Running thread 276851 was not suspended. False leaks are possible. # See https://github.com/google/sanitizers/issues/1479 From fb72e188ef2401c5399d855c198783a256a524c0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 17 Oct 2025 21:26:35 +0900 Subject: [PATCH 0394/2435] Update repository urls for rubygems and bundler --- doc/maintainers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index 7d217a166534a9..353909fe152baa 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -52,7 +52,7 @@ have commit right, others don't. * Eric Hodel ([drbrain]) * Hiroshi SHIBATA ([hsbt]) -* https://github.com/rubygems/rubygems +* https://github.com/ruby/rubygems #### lib/unicode_normalize.rb, lib/unicode_normalize/* @@ -104,7 +104,7 @@ have commit right, others don't. #### lib/bundler.rb, lib/bundler/* * Hiroshi SHIBATA ([hsbt]) -* https://github.com/rubygems/rubygems +* https://github.com/ruby/rubygems * https://rubygems.org/gems/bundler #### lib/cgi/escape.rb From 5298e979547a5859e84a70da88df0d7e4d308877 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 17 Oct 2025 20:00:55 +0900 Subject: [PATCH 0395/2435] [rubygems/rubygems] Postpone to remove legacy mingw platform https://github.com/rubygems/rubygems/commit/9b3a5a8ae9 --- lib/bundler/lockfile_parser.rb | 2 +- spec/bundler/other/major_deprecation_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 07b5bd75147cf9..ac0ce1ef3d0aaf 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -142,7 +142,7 @@ def initialize(lockfile, strict: false) end if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) - SharedHelpers.feature_removed!("Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") + SharedHelpers.feature_deprecated!("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") end @most_specific_locked_platform = @platforms.min_by do |bundle_platform| diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 1b04af76325795..947800be22bad1 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -601,7 +601,7 @@ it "raises a helpful error" do bundle "install", raise_on_error: false - expect(err).to include("Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") + expect(err).to include("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") end end From 0e5cb74a0016b3facd3055fefc439ea6472981fd Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 17 Oct 2025 13:26:03 -0400 Subject: [PATCH 0396/2435] ZJIT: Don't push frame for String#empty? (#14836) lobsters before: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (71.9% of total 15,247,103): Hash#[]: 4,516,006 (29.6%) Class#current: 1,154,273 ( 7.6%) Kernel#is_a?: 1,027,952 ( 6.7%) Regexp#match?: 398,256 ( 2.6%) String#empty?: 353,775 ( 2.3%) Hash#key?: 349,154 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,339 ( 1.4%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 181,789 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,336 ( 1.2%) BasicObject#!=: 174,429 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) Top-20 not annotated C methods (72.5% of total 15,409,355): Hash#[]: 4,516,016 (29.3%) Kernel#is_a?: 1,209,970 ( 7.9%) Class#current: 1,154,273 ( 7.5%) Regexp#match?: 398,256 ( 2.6%) String#empty?: 361,013 ( 2.3%) Hash#key?: 349,154 ( 2.3%) Hash#[]=: 344,347 ( 2.2%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,339 ( 1.4%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 191,658 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,343 ( 1.2%) BasicObject#!=: 174,613 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 71,083): cfunc: 47,637 (67.0%) iseq: 23,446 (33.0%) Top-6 not optimized method types for send_without_block (100.0% of total 4,482,446): iseq: 2,227,443 (49.7%) bmethod: 985,679 (22.0%) optimized: 952,914 (21.3%) alias: 310,750 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,264,922): invokesuper: 2,346,296 (55.0%) invokeblock: 809,163 (19.0%) sendforward: 505,446 (11.9%) opt_eq: 454,244 (10.7%) opt_plus: 74,059 ( 1.7%) opt_minus: 36,227 ( 0.8%) opt_send_without_block: 21,396 ( 0.5%) opt_neq: 7,247 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,617 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 27,366,538): send_without_block_polymorphic: 9,222,828 (33.7%) send_no_profiles: 5,892,897 (21.5%) send_without_block_not_optimized_method_type: 4,482,446 (16.4%) not_optimized_instruction: 4,264,922 (15.6%) send_without_block_no_profiles: 3,407,046 (12.4%) send_not_optimized_method_type: 71,083 ( 0.3%) send_without_block_cfunc_array_variadic: 15,135 ( 0.1%) obj_to_string_not_string: 9,919 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 688,292): expandarray: 328,369 (47.7%) checkkeyword: 190,697 (27.7%) getclassvariable: 59,286 ( 8.6%) getblockparam: 48,651 ( 7.1%) invokesuperforward: 48,162 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,675,808): register_spill_on_alloc: 3,459,950 (94.1%) register_spill_on_ccall: 215,858 ( 5.9%) Top-14 side exit reasons (100.0% of total 10,732,532): compile_error: 3,675,808 (34.2%) guard_type_failure: 2,616,693 (24.4%) guard_shape_failure: 1,902,102 (17.7%) unhandled_yarv_insn: 688,292 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 534,943 ( 5.0%) unhandled_kwarg: 421,996 ( 3.9%) patchpoint: 359,831 ( 3.4%) unknown_newarray_send: 314,665 ( 2.9%) unhandled_splat: 121,910 ( 1.1%) unhandled_hir_insn: 76,393 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) interrupt: 528 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,343,482 dynamic_send_count: 27,366,538 (41.2%) optimized_send_count: 38,976,944 (58.8%) iseq_optimized_send_count: 17,935,768 (27.0%) inline_cfunc_optimized_send_count: 5,794,073 ( 8.7%) non_variadic_cfunc_optimized_send_count: 12,588,582 (19.0%) variadic_cfunc_optimized_send_count: 2,658,521 ( 4.0%) dynamic_getivar_count: 7,321,990 dynamic_setivar_count: 7,231,183 compiled_iseq_count: 4,770 failed_iseq_count: 468 compile_time: 7,466ms profile_time: 52ms gc_time: 33ms invalidation_time: 116ms vm_write_pc_count: 64,768,186 vm_write_sp_count: 63,445,066 vm_write_locals_count: 63,445,066 vm_write_stack_count: 63,445,066 vm_write_to_parent_iseq_local_count: 292,445 vm_read_from_parent_iseq_local_count: 6,461,354 code_region_bytes: 22,446,080 side_exit_count: 10,732,532 total_insn_count: 515,600,654 vm_insn_count: 163,640,874 zjit_insn_count: 351,959,780 ratio_in_zjit: 68.3% ``` lobsters after: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (72.3% of total 14,893,304): Hash#[]: 4,515,997 (30.3%) Class#current: 1,154,273 ( 7.8%) Kernel#is_a?: 1,027,957 ( 6.9%) Regexp#match?: 398,259 ( 2.7%) Hash#key?: 349,149 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.3%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.6%) FalseClass#===: 231,144 ( 1.6%) Array#include?: 211,333 ( 1.4%) Hash#fetch: 204,703 ( 1.4%) Kernel#block_given?: 181,781 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,337 ( 1.2%) BasicObject#!=: 174,429 ( 1.2%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) String#==: 154,751 ( 1.0%) Top-20 not annotated C methods (72.9% of total 15,048,318): Hash#[]: 4,516,007 (30.0%) Kernel#is_a?: 1,209,975 ( 8.0%) Class#current: 1,154,273 ( 7.7%) Regexp#match?: 398,259 ( 2.6%) Hash#key?: 349,149 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.6%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,333 ( 1.4%) Hash#fetch: 204,703 ( 1.4%) Kernel#block_given?: 191,650 ( 1.3%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,344 ( 1.2%) BasicObject#!=: 174,613 ( 1.2%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) String#==: 160,682 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 71,084): cfunc: 47,638 (67.0%) iseq: 23,446 (33.0%) Top-6 not optimized method types for send_without_block (100.0% of total 4,482,444): iseq: 2,227,440 (49.7%) bmethod: 985,679 (22.0%) optimized: 952,916 (21.3%) alias: 310,749 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,264,913): invokesuper: 2,346,301 (55.0%) invokeblock: 809,153 (19.0%) sendforward: 505,445 (11.9%) opt_eq: 454,244 (10.7%) opt_plus: 74,056 ( 1.7%) opt_minus: 36,227 ( 0.8%) opt_send_without_block: 21,396 ( 0.5%) opt_neq: 7,247 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,617 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 27,366,491): send_without_block_polymorphic: 9,222,820 (33.7%) send_no_profiles: 5,892,885 (21.5%) send_without_block_not_optimized_method_type: 4,482,444 (16.4%) not_optimized_instruction: 4,264,913 (15.6%) send_without_block_no_profiles: 3,407,030 (12.4%) send_not_optimized_method_type: 71,084 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,919 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 688,291): expandarray: 328,368 (47.7%) checkkeyword: 190,697 (27.7%) getclassvariable: 59,286 ( 8.6%) getblockparam: 48,651 ( 7.1%) invokesuperforward: 48,162 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,675,807): register_spill_on_alloc: 3,459,949 (94.1%) register_spill_on_ccall: 215,858 ( 5.9%) Top-14 side exit reasons (100.0% of total 10,732,546): compile_error: 3,675,807 (34.2%) guard_type_failure: 2,616,699 (24.4%) guard_shape_failure: 1,902,100 (17.7%) unhandled_yarv_insn: 688,291 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 534,950 ( 5.0%) unhandled_kwarg: 421,993 ( 3.9%) patchpoint: 359,837 ( 3.4%) unknown_newarray_send: 314,667 ( 2.9%) unhandled_splat: 121,913 ( 1.1%) unhandled_hir_insn: 76,393 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) interrupt: 525 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,343,388 dynamic_send_count: 27,366,491 (41.2%) optimized_send_count: 38,976,897 (58.8%) iseq_optimized_send_count: 17,935,730 (27.0%) inline_cfunc_optimized_send_count: 6,147,863 ( 9.3%) non_variadic_cfunc_optimized_send_count: 12,234,780 (18.4%) variadic_cfunc_optimized_send_count: 2,658,524 ( 4.0%) dynamic_getivar_count: 7,321,987 dynamic_setivar_count: 7,231,160 compiled_iseq_count: 4,770 failed_iseq_count: 468 compile_time: 7,223ms profile_time: 51ms gc_time: 32ms invalidation_time: 107ms vm_write_pc_count: 64,414,293 vm_write_sp_count: 63,091,183 vm_write_locals_count: 63,091,183 vm_write_stack_count: 63,091,183 vm_write_to_parent_iseq_local_count: 292,443 vm_read_from_parent_iseq_local_count: 6,461,326 code_region_bytes: 22,446,080 side_exit_count: 10,732,546 total_insn_count: 515,600,823 vm_insn_count: 163,641,263 zjit_insn_count: 351,959,560 ratio_in_zjit: 68.3% ``` --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index ea9f1beffce3f8..f6c439ad985783 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -191,6 +191,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); + annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 48011bd0883e5b..f0ada408e36793 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13255,6 +13255,65 @@ mod opt_tests { "); } + #[test] + fn test_specialize_string_empty() { + eval(r#" + def test(s) + s.empty? + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_string_empty() { + eval(r#" + def test(s) + s.empty? + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } + #[test] fn test_inline_integer_succ_with_fixnum() { eval(" From 0594646c0b6c94cb25f86445582623c9c98ea900 Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 17 Oct 2025 15:33:11 -0400 Subject: [PATCH 0397/2435] ZJIT: Don't push frame for Hash#size (#14871) `Hash#size` was not in "Top-20 not annotated C methods" on lobsters so our before / after benchmarks are not very helpful for this change.
Before
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (60.9% of total 10,963,289): Kernel#is_a?: 1,047,725 ( 9.6%) String#<<: 861,497 ( 7.9%) Hash#[]=: 740,725 ( 6.8%) Regexp#match?: 398,297 ( 3.6%) String#empty?: 354,809 ( 3.2%) Hash#key?: 349,173 ( 3.2%) String#start_with?: 337,387 ( 3.1%) Kernel#respond_to?: 321,134 ( 2.9%) TrueClass#===: 239,657 ( 2.2%) ObjectSpace::WeakKeyMap#[]: 238,988 ( 2.2%) FalseClass#===: 234,777 ( 2.1%) Array#include?: 213,229 ( 1.9%) Kernel#block_given?: 181,801 ( 1.7%) Kernel#dup: 179,349 ( 1.6%) Kernel#kind_of?: 174,710 ( 1.6%) BasicObject#!=: 174,448 ( 1.6%) String.new: 167,716 ( 1.5%) Hash#fetch: 160,704 ( 1.5%) String#==: 158,858 ( 1.4%) Process.clock_gettime: 145,002 ( 1.3%) Top-20 not annotated C methods (61.8% of total 11,128,431): Kernel#is_a?: 1,226,218 (11.0%) String#<<: 861,497 ( 7.7%) Hash#[]=: 740,904 ( 6.7%) Regexp#match?: 398,297 ( 3.6%) String#empty?: 362,047 ( 3.3%) Hash#key?: 349,173 ( 3.1%) String#start_with?: 337,387 ( 3.0%) Kernel#respond_to?: 321,134 ( 2.9%) TrueClass#===: 239,657 ( 2.2%) ObjectSpace::WeakKeyMap#[]: 238,988 ( 2.1%) FalseClass#===: 234,777 ( 2.1%) Array#include?: 213,229 ( 1.9%) Kernel#block_given?: 191,670 ( 1.7%) Kernel#dup: 179,356 ( 1.6%) Kernel#kind_of?: 174,745 ( 1.6%) BasicObject#!=: 174,632 ( 1.6%) String.new: 167,716 ( 1.5%) String#==: 164,789 ( 1.5%) Hash#fetch: 160,704 ( 1.4%) Process.clock_gettime: 145,002 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 62,854): cfunc: 47,647 (75.8%) iseq: 15,207 (24.2%) Top-6 not optimized method types for send_without_block (100.0% of total 4,497,956): iseq: 2,236,049 (49.7%) bmethod: 993,299 (22.1%) optimized: 949,781 (21.1%) alias: 313,166 ( 7.0%) null: 5,106 ( 0.1%) cfunc: 555 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,255,830): invokesuper: 2,371,027 (55.7%) invokeblock: 811,314 (19.1%) sendforward: 506,486 (11.9%) opt_eq: 415,294 ( 9.8%) opt_plus: 77,090 ( 1.8%) opt_minus: 36,228 ( 0.9%) opt_send_without_block: 20,297 ( 0.5%) opt_neq: 7,248 ( 0.2%) opt_mult: 6,754 ( 0.2%) opt_or: 3,617 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 24,945,472): send_without_block_polymorphic: 9,308,731 (37.3%) send_no_profiles: 5,907,934 (23.7%) send_without_block_not_optimized_method_type: 4,497,956 (18.0%) not_optimized_instruction: 4,255,830 (17.1%) send_without_block_no_profiles: 887,000 ( 3.6%) send_not_optimized_method_type: 62,854 ( 0.3%) send_without_block_cfunc_array_variadic: 15,138 ( 0.1%) obj_to_string_not_string: 9,767 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 707,558): expandarray: 347,142 (49.1%) checkkeyword: 190,708 (27.0%) getclassvariable: 59,296 ( 8.4%) getblockparam: 49,122 ( 6.9%) invokesuperforward: 48,163 ( 6.8%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,649,990): register_spill_on_alloc: 3,428,507 (93.9%) register_spill_on_ccall: 221,483 ( 6.1%) Top-17 side exit reasons (100.0% of total 10,833,336): compile_error: 3,649,990 (33.7%) guard_type_failure: 2,681,177 (24.7%) guard_shape_failure: 1,897,864 (17.5%) unhandled_yarv_insn: 707,558 ( 6.5%) block_param_proxy_not_iseq_or_ifunc: 536,761 ( 5.0%) unhandled_kwarg: 456,394 ( 4.2%) unknown_newarray_send: 314,671 ( 2.9%) patchpoint_stable_constant_names: 229,825 ( 2.1%) unhandled_splat: 129,577 ( 1.2%) patchpoint_no_singleton_class: 108,465 ( 1.0%) unhandled_hir_insn: 76,401 ( 0.7%) patchpoint_method_redefined: 20,493 ( 0.2%) block_param_proxy_modified: 20,204 ( 0.2%) patchpoint_no_ep_escape: 3,765 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 13 ( 0.0%) send_count: 67,968,616 dynamic_send_count: 24,945,472 (36.7%) optimized_send_count: 43,023,144 (63.3%) iseq_optimized_send_count: 18,621,234 (27.4%) inline_cfunc_optimized_send_count: 13,438,621 (19.8%) non_variadic_cfunc_optimized_send_count: 8,333,523 (12.3%) variadic_cfunc_optimized_send_count: 2,629,766 ( 3.9%) dynamic_getivar_count: 7,351,238 dynamic_setivar_count: 7,267,701 compiled_iseq_count: 4,772 failed_iseq_count: 465 compile_time: 7,006ms profile_time: 52ms gc_time: 46ms invalidation_time: 123ms vm_write_pc_count: 63,668,147 vm_write_sp_count: 62,343,075 vm_write_locals_count: 62,343,075 vm_write_stack_count: 62,343,075 vm_write_to_parent_iseq_local_count: 292,130 vm_read_from_parent_iseq_local_count: 6,623,223 code_region_bytes: 22,724,608 side_exit_count: 10,833,336 total_insn_count: 519,162,657 vm_insn_count: 164,942,584 zjit_insn_count: 354,220,073 ratio_in_zjit: 68.2% ```
After
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (61.1% of total 10,915,774): Kernel#is_a?: 1,027,957 ( 9.4%) String#<<: 851,954 ( 7.8%) Hash#[]=: 740,863 ( 6.8%) Regexp#match?: 398,265 ( 3.6%) String#empty?: 353,775 ( 3.2%) Hash#key?: 349,161 ( 3.2%) String#start_with?: 337,386 ( 3.1%) Kernel#respond_to?: 316,003 ( 2.9%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.2%) TrueClass#===: 235,771 ( 2.2%) FalseClass#===: 231,144 ( 2.1%) Array#include?: 211,340 ( 1.9%) Hash#fetch: 204,703 ( 1.9%) Kernel#block_given?: 181,791 ( 1.7%) Kernel#dup: 179,337 ( 1.6%) BasicObject#!=: 174,430 ( 1.6%) String.new: 166,696 ( 1.5%) Kernel#kind_of?: 165,600 ( 1.5%) String#==: 154,751 ( 1.4%) Process.clock_gettime: 144,992 ( 1.3%) Top-20 not annotated C methods (62.0% of total 11,078,184): Kernel#is_a?: 1,209,975 (10.9%) String#<<: 851,954 ( 7.7%) Hash#[]=: 741,042 ( 6.7%) Regexp#match?: 398,265 ( 3.6%) String#empty?: 361,013 ( 3.3%) Hash#key?: 349,161 ( 3.2%) String#start_with?: 337,386 ( 3.0%) Kernel#respond_to?: 316,003 ( 2.9%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.2%) TrueClass#===: 235,771 ( 2.1%) FalseClass#===: 231,144 ( 2.1%) Array#include?: 211,340 ( 1.9%) Hash#fetch: 204,703 ( 1.8%) Kernel#block_given?: 191,660 ( 1.7%) Kernel#dup: 179,344 ( 1.6%) BasicObject#!=: 174,614 ( 1.6%) String.new: 166,696 ( 1.5%) Kernel#kind_of?: 165,634 ( 1.5%) String#==: 160,682 ( 1.5%) Process.clock_gettime: 144,992 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 71,084): cfunc: 47,638 (67.0%) iseq: 23,446 (33.0%) Top-6 not optimized method types for send_without_block (100.0% of total 4,469,252): iseq: 2,217,500 (49.6%) bmethod: 985,636 (22.1%) optimized: 949,705 (21.2%) alias: 310,751 ( 7.0%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,264,988): invokesuper: 2,346,307 (55.0%) invokeblock: 809,211 (19.0%) sendforward: 505,452 (11.9%) opt_eq: 454,244 (10.7%) opt_plus: 74,059 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,396 ( 0.5%) opt_neq: 7,247 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,617 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,044,791): send_without_block_polymorphic: 9,439,021 (37.7%) send_no_profiles: 5,892,924 (23.5%) send_without_block_not_optimized_method_type: 4,469,252 (17.8%) not_optimized_instruction: 4,264,988 (17.0%) send_without_block_no_profiles: 882,357 ( 3.5%) send_not_optimized_method_type: 71,084 ( 0.3%) send_without_block_cfunc_array_variadic: 15,136 ( 0.1%) obj_to_string_not_string: 9,767 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 688,760): expandarray: 328,369 (47.7%) checkkeyword: 190,697 (27.7%) getclassvariable: 59,286 ( 8.6%) getblockparam: 49,119 ( 7.1%) invokesuperforward: 48,162 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,642,051): register_spill_on_alloc: 3,420,578 (93.9%) register_spill_on_ccall: 221,473 ( 6.1%) Top-17 side exit reasons (100.0% of total 10,740,844): compile_error: 3,642,051 (33.9%) guard_type_failure: 2,624,731 (24.4%) guard_shape_failure: 1,902,123 (17.7%) unhandled_yarv_insn: 688,760 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 534,951 ( 5.0%) unhandled_kwarg: 455,354 ( 4.2%) unknown_newarray_send: 314,667 ( 2.9%) patchpoint_stable_constant_names: 227,790 ( 2.1%) unhandled_splat: 121,916 ( 1.1%) patchpoint_no_singleton_class: 108,465 ( 1.0%) unhandled_hir_insn: 76,397 ( 0.7%) patchpoint_method_redefined: 20,487 ( 0.2%) block_param_proxy_modified: 19,193 ( 0.2%) patchpoint_no_ep_escape: 3,765 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 16 ( 0.0%) send_count: 67,576,368 dynamic_send_count: 25,044,791 (37.1%) optimized_send_count: 42,531,577 (62.9%) iseq_optimized_send_count: 18,461,332 (27.3%) inline_cfunc_optimized_send_count: 13,154,471 (19.5%) non_variadic_cfunc_optimized_send_count: 8,243,438 (12.2%) variadic_cfunc_optimized_send_count: 2,672,336 ( 4.0%) dynamic_getivar_count: 7,322,001 dynamic_setivar_count: 7,230,445 compiled_iseq_count: 4,771 failed_iseq_count: 466 compile_time: 7,134ms profile_time: 52ms gc_time: 46ms invalidation_time: 123ms vm_write_pc_count: 63,337,758 vm_write_sp_count: 62,014,782 vm_write_locals_count: 62,014,782 vm_write_stack_count: 62,014,782 vm_write_to_parent_iseq_local_count: 292,458 vm_read_from_parent_iseq_local_count: 6,589,698 code_region_bytes: 22,724,608 side_exit_count: 10,740,844 total_insn_count: 515,656,824 vm_insn_count: 163,676,059 zjit_insn_count: 351,980,765 ratio_in_zjit: 68.3% ```
--- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 57 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index f6c439ad985783..0609d37b1b5854 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -201,6 +201,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); annotate!(rb_cHash, "[]", inline_hash_aref); + annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index f0ada408e36793..eca0e3a598865b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13390,4 +13390,61 @@ mod opt_tests { Return v13 "); } + + #[test] + fn test_specialize_hash_size() { + eval(" + def test(hash) = hash.size + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_hash_size() { + eval(" + def test(hash) + hash.size + 5 + end + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } } From 23287c45806cac060ed63e87176d1a87968b9267 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 17 Oct 2025 15:48:54 -0400 Subject: [PATCH 0398/2435] ZJIT: Mark commonly-edited files as merge=union (#14865) This helps the default merge driver make reasonable decisions (and therefore avoid conflicts) when multiple people are e.g. adding tests to src/hir.rs at the same time. --- zjit/.gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 zjit/.gitattributes diff --git a/zjit/.gitattributes b/zjit/.gitattributes new file mode 100644 index 00000000000000..2750c4c62683ca --- /dev/null +++ b/zjit/.gitattributes @@ -0,0 +1,3 @@ +src/hir.rs merge=union +src/cruby_methods.rs merge=union +src/codegen.rs merge=union From 1c119f02456ff7dd3a4025456f8de5e50be88ef3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 17 Oct 2025 16:29:03 -0400 Subject: [PATCH 0399/2435] Revert "ZJIT: Mark commonly-edited files as merge=union (#14865)" This reverts commit 23287c45806cac060ed63e87176d1a87968b9267. This looks like a mixed bag... --- zjit/.gitattributes | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 zjit/.gitattributes diff --git a/zjit/.gitattributes b/zjit/.gitattributes deleted file mode 100644 index 2750c4c62683ca..00000000000000 --- a/zjit/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -src/hir.rs merge=union -src/cruby_methods.rs merge=union -src/codegen.rs merge=union From cb55043383cbf39ac1df1d227836080a3d7cef33 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 16 Oct 2025 17:11:30 -0400 Subject: [PATCH 0400/2435] Set method table owned by iclass in rb_class_duplicate_classext We duplicate the method table in rb_class_duplicate_classext, so we should set RCLASSEXT_ICLASS_IS_ORIGIN and unset RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL to signal that the iclass owns the method table and it should be freed. --- class.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/class.c b/class.c index 469cc5e54fef86..c8bf624140d726 100644 --- a/class.c +++ b/class.c @@ -305,6 +305,8 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(orig); RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass); + RCLASSEXT_ICLASS_IS_ORIGIN(ext) = true; + RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext) = false; if (orig->fields_obj) { RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj)); From a0bf6d349856dfca22798a49c5b4e05162edaf3c Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 17 Oct 2025 18:37:22 -0400 Subject: [PATCH 0401/2435] ZJIT: Add inlining for Kernel#respond_to? (#14873) lobsters before:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (61.1% of total 10,568,718): Kernel#is_a?: 1,030,925 ( 9.8%) String#<<: 851,954 ( 8.1%) Hash#[]=: 742,942 ( 7.0%) Regexp#match?: 399,898 ( 3.8%) Hash#key?: 349,146 ( 3.3%) String#start_with?: 334,963 ( 3.2%) Kernel#respond_to?: 316,528 ( 3.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%) TrueClass#===: 235,771 ( 2.2%) FalseClass#===: 231,144 ( 2.2%) Array#include?: 211,386 ( 2.0%) Hash#fetch: 204,702 ( 1.9%) Kernel#block_given?: 181,796 ( 1.7%) Kernel#dup: 179,341 ( 1.7%) BasicObject#!=: 175,997 ( 1.7%) String.new: 166,696 ( 1.6%) Kernel#kind_of?: 165,600 ( 1.6%) String#==: 157,746 ( 1.5%) Process.clock_gettime: 144,992 ( 1.4%) Array#any?: 138,310 ( 1.3%) Top-20 not annotated C methods (62.1% of total 10,723,613): Kernel#is_a?: 1,212,816 (11.3%) String#<<: 851,954 ( 7.9%) Hash#[]=: 743,121 ( 6.9%) Regexp#match?: 399,898 ( 3.7%) Hash#key?: 349,146 ( 3.3%) String#start_with?: 334,963 ( 3.1%) Kernel#respond_to?: 316,528 ( 3.0%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.2%) TrueClass#===: 235,771 ( 2.2%) FalseClass#===: 231,144 ( 2.2%) Array#include?: 211,386 ( 2.0%) Hash#fetch: 204,702 ( 1.9%) Kernel#block_given?: 191,665 ( 1.8%) Kernel#dup: 179,348 ( 1.7%) BasicObject#!=: 176,181 ( 1.6%) String.new: 166,696 ( 1.6%) Kernel#kind_of?: 165,634 ( 1.5%) String#==: 163,678 ( 1.5%) Process.clock_gettime: 144,992 ( 1.4%) Array#any?: 138,310 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 72,324): cfunc: 48,057 (66.4%) iseq: 24,267 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,699): iseq: 2,271,952 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,704 (21.0%) alias: 310,747 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,340): invokesuper: 2,373,561 (55.3%) invokeblock: 811,934 (18.9%) sendforward: 505,452 (11.8%) opt_eq: 451,756 (10.5%) opt_plus: 74,406 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,481,476): send_without_block_polymorphic: 9,722,801 (38.2%) send_no_profiles: 5,894,799 (23.1%) send_without_block_not_optimized_method_type: 4,523,699 (17.8%) not_optimized_instruction: 4,293,340 (16.8%) send_without_block_no_profiles: 948,985 ( 3.7%) send_not_optimized_method_type: 72,324 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,957): expandarray: 328,491 (47.5%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,907 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 49,119 ( 7.1%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,718,841): register_spill_on_alloc: 3,418,472 (91.9%) register_spill_on_ccall: 182,023 ( 4.9%) exception_handler: 118,346 ( 3.2%) Top-17 side exit reasons (100.0% of total 10,861,013): compile_error: 3,718,841 (34.2%) guard_type_failure: 2,638,940 (24.3%) guard_shape_failure: 1,917,541 (17.7%) unhandled_yarv_insn: 690,957 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,789 ( 4.9%) unhandled_kwarg: 455,351 ( 4.2%) unknown_newarray_send: 314,786 ( 2.9%) patchpoint_stable_constant_names: 235,507 ( 2.2%) unhandled_splat: 122,071 ( 1.1%) patchpoint_no_singleton_class: 109,668 ( 1.0%) unhandled_hir_insn: 76,397 ( 0.7%) patchpoint_method_redefined: 21,598 ( 0.2%) block_param_proxy_modified: 19,193 ( 0.2%) patchpoint_no_ep_escape: 3,765 ( 0.0%) obj_to_string_fallback: 568 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 19 ( 0.0%) send_count: 68,205,150 dynamic_send_count: 25,481,476 (37.4%) optimized_send_count: 42,723,674 (62.6%) iseq_optimized_send_count: 18,588,101 (27.3%) inline_cfunc_optimized_send_count: 13,566,855 (19.9%) non_variadic_cfunc_optimized_send_count: 7,904,518 (11.6%) variadic_cfunc_optimized_send_count: 2,664,200 ( 3.9%) dynamic_getivar_count: 7,366,650 dynamic_setivar_count: 7,245,122 compiled_iseq_count: 4,796 failed_iseq_count: 447 compile_time: 778ms profile_time: 9ms gc_time: 11ms invalidation_time: 77ms vm_write_pc_count: 63,636,742 vm_write_sp_count: 62,292,946 vm_write_locals_count: 62,292,946 vm_write_stack_count: 62,292,946 vm_write_to_parent_iseq_local_count: 292,458 vm_read_from_parent_iseq_local_count: 6,600,017 code_region_bytes: 22,970,368 side_exit_count: 10,861,013 total_insn_count: 517,633,620 vm_insn_count: 162,995,567 zjit_insn_count: 354,638,053 ratio_in_zjit: 68.5% ```
lobsters after:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (61.1% of total 10,239,008): Kernel#is_a?: 1,030,914 (10.1%) String#<<: 851,954 ( 8.3%) Hash#[]=: 742,942 ( 7.3%) Regexp#match?: 376,144 ( 3.7%) Hash#key?: 349,147 ( 3.4%) String#start_with?: 334,963 ( 3.3%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%) TrueClass#===: 235,771 ( 2.3%) FalseClass#===: 231,144 ( 2.3%) Array#include?: 211,386 ( 2.1%) Hash#fetch: 204,702 ( 2.0%) Kernel#block_given?: 181,797 ( 1.8%) Kernel#dup: 179,341 ( 1.8%) BasicObject#!=: 175,997 ( 1.7%) String.new: 166,696 ( 1.6%) Kernel#kind_of?: 165,600 ( 1.6%) String#==: 157,751 ( 1.5%) Process.clock_gettime: 144,992 ( 1.4%) Array#any?: 138,311 ( 1.4%) Set#include?: 134,362 ( 1.3%) Top-20 not annotated C methods (62.2% of total 10,372,753): Kernel#is_a?: 1,212,805 (11.7%) String#<<: 851,954 ( 8.2%) Hash#[]=: 743,121 ( 7.2%) Regexp#match?: 376,144 ( 3.6%) Hash#key?: 349,147 ( 3.4%) String#start_with?: 334,963 ( 3.2%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%) TrueClass#===: 235,771 ( 2.3%) FalseClass#===: 231,144 ( 2.2%) Array#include?: 211,386 ( 2.0%) Hash#fetch: 204,702 ( 2.0%) Kernel#block_given?: 191,666 ( 1.8%) Kernel#dup: 179,348 ( 1.7%) BasicObject#!=: 176,181 ( 1.7%) String.new: 166,696 ( 1.6%) Kernel#kind_of?: 165,634 ( 1.6%) String#==: 163,683 ( 1.6%) Process.clock_gettime: 144,992 ( 1.4%) Array#any?: 138,311 ( 1.3%) Integer#<=>: 135,056 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 72,324): cfunc: 48,057 (66.4%) iseq: 24,267 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,699): iseq: 2,271,952 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,704 (21.0%) alias: 310,747 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,339): invokesuper: 2,373,561 (55.3%) invokeblock: 811,933 (18.9%) sendforward: 505,452 (11.8%) opt_eq: 451,756 (10.5%) opt_plus: 74,406 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,457,719): send_without_block_polymorphic: 9,699,046 (38.1%) send_no_profiles: 5,894,798 (23.2%) send_without_block_not_optimized_method_type: 4,523,699 (17.8%) not_optimized_instruction: 4,293,339 (16.9%) send_without_block_no_profiles: 948,985 ( 3.7%) send_not_optimized_method_type: 72,324 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,957): expandarray: 328,491 (47.5%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,907 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 49,119 ( 7.1%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,706,981): register_spill_on_alloc: 3,406,595 (91.9%) register_spill_on_ccall: 182,023 ( 4.9%) exception_handler: 118,363 ( 3.2%) Top-17 side exit reasons (100.0% of total 10,837,266): compile_error: 3,706,981 (34.2%) guard_type_failure: 2,638,921 (24.4%) guard_shape_failure: 1,917,552 (17.7%) unhandled_yarv_insn: 690,957 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,789 ( 4.9%) unhandled_kwarg: 455,351 ( 4.2%) unknown_newarray_send: 314,786 ( 2.9%) patchpoint_stable_constant_names: 223,630 ( 2.1%) unhandled_splat: 122,071 ( 1.1%) patchpoint_no_singleton_class: 109,668 ( 1.0%) unhandled_hir_insn: 76,397 ( 0.7%) patchpoint_method_redefined: 21,598 ( 0.2%) block_param_proxy_modified: 19,193 ( 0.2%) patchpoint_no_ep_escape: 3,765 ( 0.0%) obj_to_string_fallback: 568 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 17 ( 0.0%) send_count: 68,157,710 dynamic_send_count: 25,457,719 (37.4%) optimized_send_count: 42,699,991 (62.6%) iseq_optimized_send_count: 18,588,067 (27.3%) inline_cfunc_optimized_send_count: 13,872,916 (20.4%) non_variadic_cfunc_optimized_send_count: 7,904,566 (11.6%) variadic_cfunc_optimized_send_count: 2,334,442 ( 3.4%) dynamic_getivar_count: 7,342,896 dynamic_setivar_count: 7,245,126 compiled_iseq_count: 4,796 failed_iseq_count: 447 compile_time: 791ms profile_time: 9ms gc_time: 9ms invalidation_time: 68ms vm_write_pc_count: 63,283,243 vm_write_sp_count: 61,939,447 vm_write_locals_count: 61,939,447 vm_write_stack_count: 61,939,447 vm_write_to_parent_iseq_local_count: 292,458 vm_read_from_parent_iseq_local_count: 6,576,263 code_region_bytes: 22,872,064 side_exit_count: 10,837,266 total_insn_count: 517,075,555 vm_insn_count: 162,674,783 zjit_insn_count: 354,400,772 ratio_in_zjit: 68.5% ```
--------- Co-authored-by: Max Bernstein --- zjit/src/cruby_methods.rs | 112 +++++++++++++ zjit/src/hir.rs | 341 +++++++++++++++++++++++++++++++++++++- 2 files changed, 452 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0609d37b1b5854..40fb0cbe442dab 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -205,6 +205,7 @@ pub fn init() -> Annotations { annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); + annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); @@ -290,3 +291,114 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); Some(result) } + +fn inline_kernel_respond_to_p( + fun: &mut hir::Function, + block: hir::BlockId, + recv: hir::InsnId, + args: &[hir::InsnId], + state: hir::InsnId, +) -> Option { + // Parse arguments: respond_to?(method_name, allow_priv = false) + let (method_name, allow_priv) = match *args { + [method_name] => (method_name, false), + [method_name, arg] => match fun.type_of(arg) { + t if t.is_known_truthy() => (method_name, true), + t if t.is_known_falsy() => (method_name, false), + // Unknown type; bail out + _ => return None, + }, + // Unknown args; bail out + _ => return None, + }; + + // Method name must be a static symbol + let method_name = fun.type_of(method_name).ruby_object()?; + if !method_name.static_sym_p() { + return None; + } + + // The receiver must have a known class to call `respond_to?` on + // TODO: This is technically overly strict. This would also work if all of the + // observed objects at this point agree on `respond_to?` and we can add many patchpoints. + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + + // Get the method ID and its corresponding callable method entry + let mid = unsafe { rb_sym2id(method_name) }; + let target_cme = unsafe { rb_callable_method_entry_or_negative(recv_class, mid) }; + assert!( + !target_cme.is_null(), + "Should never be null, as in that case we will be returned a \"negative CME\"" + ); + + let cme_def_type = unsafe { get_cme_def_type(target_cme) }; + + // Cannot inline a refined method, since their refinement depends on lexical scope + if cme_def_type == VM_METHOD_TYPE_REFINED { + return None; + } + + let visibility = match cme_def_type { + VM_METHOD_TYPE_UNDEF => METHOD_VISI_UNDEF, + _ => unsafe { METHOD_ENTRY_VISI(target_cme) }, + }; + + let result = match (visibility, allow_priv) { + // Method undefined; check `respond_to_missing?` + (METHOD_VISI_UNDEF, _) => { + let respond_to_missing = ID!(respond_to_missing); + if unsafe { rb_method_basic_definition_p(recv_class, respond_to_missing) } == 0 { + return None; // Custom definition of respond_to_missing?, so cannot inline + } + let respond_to_missing_cme = + unsafe { rb_callable_method_entry(recv_class, respond_to_missing) }; + // Protect against redefinition of `respond_to_missing?` + fun.push_insn( + block, + hir::Insn::PatchPoint { + invariant: hir::Invariant::NoTracePoint, + state, + }, + ); + fun.push_insn( + block, + hir::Insn::PatchPoint { + invariant: hir::Invariant::MethodRedefined { + klass: recv_class, + method: respond_to_missing, + cme: respond_to_missing_cme, + }, + state, + }, + ); + Qfalse + } + // Private method with allow priv=false, so `respond_to?` returns false + (METHOD_VISI_PRIVATE, false) => Qfalse, + // Public method or allow_priv=true: check if implemented + (METHOD_VISI_PUBLIC, _) | (_, true) => { + if cme_def_type == VM_METHOD_TYPE_NOTIMPLEMENTED { + // C method with rb_f_notimplement(). `respond_to?` returns false + // without consulting `respond_to_missing?`. See also: rb_add_method_cfunc() + Qfalse + } else { + Qtrue + } + } + (_, _) => return None, // not public and include_all not known, can't compile + }; + fun.push_insn(block, hir::Insn::PatchPoint { invariant: hir::Invariant::NoTracePoint, state }); + fun.push_insn(block, hir::Insn::PatchPoint { + invariant: hir::Invariant::MethodRedefined { + klass: recv_class, + method: mid, + cme: target_cme + }, state + }); + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, hir::Insn::PatchPoint { + invariant: hir::Invariant::NoSingletonClass { klass: recv_class }, state + }); + } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) })) +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index eca0e3a598865b..bccd27fc39dcbe 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1649,7 +1649,7 @@ impl Function { } /// Check if the type of `insn` is a subtype of `ty`. - fn is_a(&self, insn: InsnId, ty: Type) -> bool { + pub fn is_a(&self, insn: InsnId, ty: Type) -> bool { self.type_of(insn).is_subtype(ty) } @@ -2453,6 +2453,7 @@ impl Function { if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); @@ -13447,4 +13448,342 @@ mod opt_tests { Return v19 "); } + + #[test] + fn test_optimize_respond_to_p_true() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_no_method() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_default_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, false) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:FalseClass = Const Value(false) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_true_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, true) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:TrueClass = Const Value(true) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_truthy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, 4) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_missing() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_do_not_optimize_redefined_respond_to_missing() { + eval(r#" + class C + def respond_to_missing?(method, include_private = false) + true + end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 + CheckInterrupts + Return v25 + "); + } } From 9b2216954a34934fd855deb642a4369fc009c68a Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 17 Oct 2025 19:40:58 -0500 Subject: [PATCH 0402/2435] [DOC] Tweaks for String#rindex --- doc/string/index.rdoc | 2 +- doc/string/rindex.rdoc | 52 +++++++++++++++++++++++++++++++++++++++++ string.c | 53 ++---------------------------------------- 3 files changed, 55 insertions(+), 52 deletions(-) create mode 100644 doc/string/rindex.rdoc diff --git a/doc/string/index.rdoc b/doc/string/index.rdoc index cc34bc68e6b6bd..6045fac0f6fb5a 100644 --- a/doc/string/index.rdoc +++ b/doc/string/index.rdoc @@ -11,7 +11,7 @@ returns the index of the first matching substring in +self+: 'тест'.index('с') # => 2 # Characters, not bytes. 'こんにちは'.index('ち') # => 3 -When +pattern is a Regexp, returns the index of the first match in +self+: +When +pattern+ is a Regexp, returns the index of the first match in +self+: 'foo'.index(/o./) # => 1 'foo'.index(/.o/) # => 0 diff --git a/doc/string/rindex.rdoc b/doc/string/rindex.rdoc new file mode 100644 index 00000000000000..8a1cc0106f59fb --- /dev/null +++ b/doc/string/rindex.rdoc @@ -0,0 +1,52 @@ +Returns the integer position of the _last_ substring that matches the given argument +pattern+, +or +nil+ if none found. + +When +pattern+ is a string, returns the index of the last matching substring in self: + + 'foo'.rindex('f') # => 0 + 'foo'.rindex('o') # => 2 + 'foo'.rindex('oo' # => 1 + 'foo'.rindex('ooo') # => nil + 'тест'.rindex('т') # => 3 + 'こんにちは'.rindex('ち') # => 3 + +When +pattern+ is a Regexp, returns the index of the last match in self: + + 'foo'.rindex(/f/) # => 0 + 'foo'.rindex(/o/) # => 2 + 'foo'.rindex(/oo/) # => 1 + 'foo'.rindex(/ooo/) # => nil + +When +offset+ is non-negative, it specifies the maximum starting position in the +string to end the search: + + 'foo'.rindex('o', 0) # => nil + 'foo'.rindex('o', 1) # => 1 + 'foo'.rindex('o', 2) # => 2 + 'foo'.rindex('o', 3) # => 2 + +With negative integer argument +offset+, +selects the search position by counting backward from the end of +self+: + + 'foo'.rindex('o', -1) # => 2 + 'foo'.rindex('o', -2) # => 1 + 'foo'.rindex('o', -3) # => nil + 'foo'.rindex('o', -4) # => nil + +The last match means starting at the possible last position, not +the last of longest matches: + + 'foo'.rindex(/o+/) # => 2 + $~ # => # + +To get the last longest match, combine with negative lookbehind: + + 'foo'.rindex(/(? 1 + $~ # => # + +Or String#index with negative lookforward. + + 'foo'.index(/o+(?!.*o)/) # => 1 + $~ # => # + +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/string.c b/string.c index fab1509bac0ead..47de66eca6f3df 100644 --- a/string.c +++ b/string.c @@ -4770,59 +4770,10 @@ rb_str_rindex(VALUE str, VALUE sub, long pos) /* * call-seq: - * rindex(substring, offset = self.length) -> integer or nil - * rindex(regexp, offset = self.length) -> integer or nil + * rindex(pattern, offset = self.length) -> integer or nil * - * Returns the Integer index of the _last_ occurrence of the given +substring+, - * or +nil+ if none found: + * :include:doc/string/rindex.rdoc * - * 'foo'.rindex('f') # => 0 - * 'foo'.rindex('o') # => 2 - * 'foo'.rindex('oo') # => 1 - * 'foo'.rindex('ooo') # => nil - * - * Returns the Integer index of the _last_ match for the given Regexp +regexp+, - * or +nil+ if none found: - * - * 'foo'.rindex(/f/) # => 0 - * 'foo'.rindex(/o/) # => 2 - * 'foo'.rindex(/oo/) # => 1 - * 'foo'.rindex(/ooo/) # => nil - * - * The _last_ match means starting at the possible last position, not - * the last of longest matches. - * - * 'foo'.rindex(/o+/) # => 2 - * $~ #=> # - * - * To get the last longest match, needs to combine with negative - * lookbehind. - * - * 'foo'.rindex(/(? 1 - * $~ #=> # - * - * Or String#index with negative lookforward. - * - * 'foo'.index(/o+(?!.*o)/) # => 1 - * $~ #=> # - * - * Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the - * string to _end_ the search: - * - * 'foo'.rindex('o', 0) # => nil - * 'foo'.rindex('o', 1) # => 1 - * 'foo'.rindex('o', 2) # => 2 - * 'foo'.rindex('o', 3) # => 2 - * - * If +offset+ is a negative Integer, the maximum starting position in the - * string to _end_ the search is the sum of the string's length and +offset+: - * - * 'foo'.rindex('o', -1) # => 2 - * 'foo'.rindex('o', -2) # => 1 - * 'foo'.rindex('o', -3) # => nil - * 'foo'.rindex('o', -4) # => nil - * - * Related: String#index. */ static VALUE From 7989a2ff46e0dc8dc26b1571215768801fa04463 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 17 Oct 2025 17:28:55 -0400 Subject: [PATCH 0403/2435] Preallocate capacity for id table in rb_singleton_class_clone_and_attach We know the exact capacity for the constant table created in rb_singleton_class_clone_and_attach so we can preallocate it. --- class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class.c b/class.c index c8bf624140d726..77f2fba51647bc 100644 --- a/class.c +++ b/class.c @@ -1193,7 +1193,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) if (RCLASS_CONST_TBL(klass)) { struct clone_const_arg arg; struct rb_id_table *table; - arg.tbl = table = rb_id_table_create(0); + arg.tbl = table = rb_id_table_create(rb_id_table_size(RCLASS_CONST_TBL(klass))); arg.klass = clone; rb_id_table_foreach(RCLASS_CONST_TBL(klass), clone_const_i, &arg); RCLASS_SET_CONST_TBL(clone, table, false); From db357848950d54128d6505621037872e7575697a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 17 Oct 2025 16:15:42 +0900 Subject: [PATCH 0404/2435] [ruby/zlib] Initialize const member ``` /github/workspace/src/ext/zlib/zlib.c:2608:25: warning: default initialization of an object of type 'struct read_raw_arg' with const member leaves the object uninitialized [-Wdefault-const-init-field-unsafe] 2608 | struct read_raw_arg ra; | ^ /github/workspace/src/ext/zlib/zlib.c:2450:14: note: member 'argv' declared 'const' here 2450 | const VALUE argv[2]; /* for rb_funcallv */ | ^ ``` https://github.com/ruby/zlib/commit/dfa1fcbd37 --- ext/zlib/zlib.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 0b9c4d65ee8694..640c28122c48ac 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -2444,17 +2444,16 @@ struct gzfile { #define GZFILE_READ_SIZE 2048 +enum { read_raw_arg_len, read_raw_arg_buf, read_raw_arg__count}; struct read_raw_arg { VALUE io; - union { - const VALUE argv[2]; /* for rb_funcallv */ - struct { - VALUE len; - VALUE buf; - } in; - } as; + const VALUE argv[read_raw_arg__count]; /* for rb_funcallv */ }; +#define read_raw_arg_argc(ra) \ + ((int)read_raw_arg__count - NIL_P((ra)->argv[read_raw_arg__count - 1])) +#define read_raw_arg_init(io, len, buf) { io, { len, buf } } + static void gzfile_mark(void *p) { @@ -2580,9 +2579,9 @@ gzfile_read_raw_partial(VALUE arg) { struct read_raw_arg *ra = (struct read_raw_arg *)arg; VALUE str; - int argc = NIL_P(ra->as.argv[1]) ? 1 : 2; + int argc = read_raw_arg_argc(ra); - str = rb_funcallv(ra->io, id_readpartial, argc, ra->as.argv); + str = rb_funcallv(ra->io, id_readpartial, argc, ra->argv); Check_Type(str, T_STRING); return str; } @@ -2593,8 +2592,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _) struct read_raw_arg *ra = (struct read_raw_arg *)arg; VALUE str = Qnil; if (rb_obj_is_kind_of(rb_errinfo(), rb_eNoMethodError)) { - int argc = NIL_P(ra->as.argv[1]) ? 1 : 2; - str = rb_funcallv(ra->io, id_read, argc, ra->as.argv); + int argc = read_raw_arg_argc(ra); + str = rb_funcallv(ra->io, id_read, argc, ra->argv); if (!NIL_P(str)) { Check_Type(str, T_STRING); } @@ -2605,11 +2604,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _) static VALUE gzfile_read_raw(struct gzfile *gz, VALUE outbuf) { - struct read_raw_arg ra; - - ra.io = gz->io; - ra.as.in.len = INT2FIX(GZFILE_READ_SIZE); - ra.as.in.buf = outbuf; + struct read_raw_arg ra = + read_raw_arg_init(gz->io, INT2FIX(GZFILE_READ_SIZE), outbuf); return rb_rescue2(gzfile_read_raw_partial, (VALUE)&ra, gzfile_read_raw_rescue, (VALUE)&ra, From d7f412e685aee3138213734ad81ffd5fe0e4be8c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 17 Oct 2025 20:45:04 -0400 Subject: [PATCH 0405/2435] Add rb_root_namespace_data_type --- namespace.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index c8c947f50cd8ba..bd805ae0d38ac6 100644 --- a/namespace.c +++ b/namespace.c @@ -238,6 +238,17 @@ const rb_data_type_t rb_namespace_data_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers }; +const rb_data_type_t rb_root_namespace_data_type = { + "Namespace::Root", + { + rb_namespace_entry_mark, + namespace_entry_free, + namespace_entry_memsize, + rb_namespace_gc_update_references, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers +}; + VALUE rb_namespace_entry_alloc(VALUE klass) { @@ -708,7 +719,7 @@ initialize_root_namespace(void) root->ns_id = namespace_generate_id(); root->ns_object = root_namespace; - entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_namespace_data_type, root); + entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_root_namespace_data_type, root); rb_ivar_set(root_namespace, id_namespace_entry, entry); } else { From eb4a6f0cda35e9f7dc926d5cf66efdfaf3136ac3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 17 Oct 2025 21:41:12 -0400 Subject: [PATCH 0406/2435] Fix memory leak of TypedData data in Namespace --- namespace.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/namespace.c b/namespace.c index bd805ae0d38ac6..e860de20ef56eb 100644 --- a/namespace.c +++ b/namespace.c @@ -208,7 +208,7 @@ free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg) } static void -namespace_entry_free(void *ptr) +namespace_root_free(void *ptr) { rb_namespace_t *ns = (rb_namespace_t *)ptr; if (ns->loading_table) { @@ -218,6 +218,13 @@ namespace_entry_free(void *ptr) } } +static void +namespace_entry_free(void *ptr) +{ + namespace_root_free(ptr); + xfree(ptr); +} + static size_t namespace_entry_memsize(const void *ptr) { @@ -242,7 +249,7 @@ const rb_data_type_t rb_root_namespace_data_type = { "Namespace::Root", { rb_namespace_entry_mark, - namespace_entry_free, + namespace_root_free, namespace_entry_memsize, rb_namespace_gc_update_references, }, From ddd1aeaa648d7fe608d73004f3bfb2f11108c5d3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 17 Oct 2025 22:23:26 -0400 Subject: [PATCH 0407/2435] Free loaded_features_index in namespace --- namespace.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/namespace.c b/namespace.c index e860de20ef56eb..86a5ecf1544907 100644 --- a/namespace.c +++ b/namespace.c @@ -216,6 +216,10 @@ namespace_root_free(void *ptr) st_free_table(ns->loading_table); ns->loading_table = 0; } + + if (ns->loaded_features_index) { + st_free_table(ns->loaded_features_index); + } } static void From 4e6d78b8f4f9493a6ea15cc4bd0c03099008931d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 13 Aug 2025 13:14:21 +0900 Subject: [PATCH 0408/2435] [ruby/English] [DOC] Enclose English in quotes https://github.com/ruby/English/commit/70b46b58cc --- lib/English.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/English.rb b/lib/English.rb index 03fe7219911de4..1553c0bd10e277 100644 --- a/lib/English.rb +++ b/lib/English.rb @@ -9,7 +9,7 @@ # "waterbuffalo" =~ /buff/ # print $', $$, "\n" # -# With English: +# With 'English': # # require "English" # From 56afc0a0cef4c3e5a05839b12ea387f434ef49df Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 13 Aug 2025 13:20:21 +0900 Subject: [PATCH 0409/2435] [ruby/English] [DOC] Markup variables in the full list as code https://github.com/ruby/English/commit/5e60c1068a --- lib/English.rb | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/English.rb b/lib/English.rb index 1553c0bd10e277..bf7896dcd6fa24 100644 --- a/lib/English.rb +++ b/lib/English.rb @@ -20,30 +20,30 @@ # Below is a full list of descriptive aliases and their associated global # variable: # -# $ERROR_INFO:: $! -# $ERROR_POSITION:: $@ -# $FS:: $; -# $FIELD_SEPARATOR:: $; -# $OFS:: $, -# $OUTPUT_FIELD_SEPARATOR:: $, -# $RS:: $/ -# $INPUT_RECORD_SEPARATOR:: $/ -# $ORS:: $\ -# $OUTPUT_RECORD_SEPARATOR:: $\ -# $INPUT_LINE_NUMBER:: $. -# $NR:: $. -# $LAST_READ_LINE:: $_ -# $DEFAULT_OUTPUT:: $> -# $DEFAULT_INPUT:: $< -# $PID:: $$ -# $PROCESS_ID:: $$ -# $CHILD_STATUS:: $? -# $LAST_MATCH_INFO:: $~ -# $ARGV:: $* -# $MATCH:: $& -# $PREMATCH:: $` -# $POSTMATCH:: $' -# $LAST_PAREN_MATCH:: $+ +# $ERROR_INFO:: $! +# $ERROR_POSITION:: $@ +# $FS:: $; +# $FIELD_SEPARATOR:: $; +# $OFS:: $, +# $OUTPUT_FIELD_SEPARATOR:: $, +# $RS:: $/ +# $INPUT_RECORD_SEPARATOR:: $/ +# $ORS:: $\ +# $OUTPUT_RECORD_SEPARATOR:: $\ +# $NR:: $. +# $INPUT_LINE_NUMBER:: $. +# $LAST_READ_LINE:: $_ +# $DEFAULT_OUTPUT:: $> +# $DEFAULT_INPUT:: $< +# $PID:: $$ +# $PROCESS_ID:: $$ +# $CHILD_STATUS:: $? +# $LAST_MATCH_INFO:: $~ +# $ARGV:: $* +# $MATCH:: $& +# $PREMATCH:: $` +# $POSTMATCH:: $' +# $LAST_PAREN_MATCH:: $+ # module English end if false From 8edb40f6e82994059c25917a193bf317897ec4d0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 19 Oct 2025 19:18:28 +0900 Subject: [PATCH 0410/2435] Change upstream repository of rubygems --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index b4bd761a729078..55e8492851d363 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -43,7 +43,7 @@ module SyncDefaultGems prism: ["ruby/prism", "main"], psych: 'ruby/psych', resolv: "ruby/resolv", - rubygems: 'rubygems/rubygems', + rubygems: 'ruby/rubygems', securerandom: "ruby/securerandom", shellwords: "ruby/shellwords", singleton: "ruby/singleton", From 2f20dc5dc5806cf1a836420e0216b814d0c6252a Mon Sep 17 00:00:00 2001 From: TaoufikMejri Date: Sun, 12 Oct 2025 19:55:07 +0100 Subject: [PATCH 0411/2435] [DOC] Improve loop code example documentation --- kernel.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel.rb b/kernel.rb index 5d596c889258c9..dc5cea1515df9a 100644 --- a/kernel.rb +++ b/kernel.rb @@ -141,6 +141,7 @@ def then # loop do # print "Input: " # line = gets + # # break if q, Q is entered or EOF signal (Ctrl-D on Unix, Ctrl-Z on windows) is sent # break if !line or line =~ /^q/i # # ... # end From 957c832db137e67289e93dfd9fd9e915b1f2fc87 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 18 Oct 2025 14:32:25 -0400 Subject: [PATCH 0412/2435] Fix memory leak in rb_const_remove when using namespace We need to free the rb_const_entry_t we remove from the RCLASS_WRITABLE_CONST_TBL otherwise it will leak memory. --- variable.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/variable.c b/variable.c index c8565047a46e4e..5fc98fb0879024 100644 --- a/variable.c +++ b/variable.c @@ -3533,7 +3533,8 @@ rb_const_remove(VALUE mod, ID id) rb_check_frozen(mod); ce = rb_const_lookup(mod, id); - if (!ce || !rb_id_table_delete(RCLASS_WRITABLE_CONST_TBL(mod), id)) { + + if (!ce) { if (rb_const_defined_at(mod, id)) { rb_name_err_raise("cannot remove %2$s::%1$s", mod, ID2SYM(id)); } @@ -3541,6 +3542,14 @@ rb_const_remove(VALUE mod, ID id) undefined_constant(mod, ID2SYM(id)); } + VALUE writable_ce = 0; + if (rb_id_table_lookup(RCLASS_WRITABLE_CONST_TBL(mod), id, &writable_ce)) { + rb_id_table_delete(RCLASS_WRITABLE_CONST_TBL(mod), id); + if ((rb_const_entry_t *)writable_ce != ce) { + xfree((rb_const_entry_t *)writable_ce); + } + } + rb_const_warn_if_deprecated(ce, mod, id); rb_clear_constant_cache_for_id(id); From 4353187d0812e55c5663299eca16b962000fccd9 Mon Sep 17 00:00:00 2001 From: "Daisuke Fujimura (fd0)" Date: Fri, 17 Oct 2025 22:16:42 +0900 Subject: [PATCH 0413/2435] Fix extension file permissions on Cygwin in namespace feature --- namespace.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/namespace.c b/namespace.c index 86a5ecf1544907..d4a990cb386e08 100644 --- a/namespace.c +++ b/namespace.c @@ -510,6 +510,10 @@ copy_ext_file_error(char *message, size_t size, int copy_retvalue, char *src_pat snprintf(message, size, "failed to read the extension path: %s", src_path); case 4: snprintf(message, size, "failed to write the extension path: %s", dst_path); + case 5: + snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path); + case 6: + snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path); default: rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue); } @@ -585,6 +589,19 @@ copy_ext_file(char *src_path, char *dst_path) } fclose(src); fclose(dst); +#if defined(__CYGWIN__) + // On Cygwin, CopyFile-like operations may strip executable bits. + // Explicitly match destination file permissions to source. + if (retvalue == 0) { + struct stat st; + if (stat(src_path, &st) != 0) { + retvalue = 5; + } + else if (chmod(dst_path, st.st_mode & 0777) != 0) { + retvalue = 6; + } + } +#endif return retvalue; #endif } From 7587e92910e7604a4c66f2b804bfa2076339c6ff Mon Sep 17 00:00:00 2001 From: viralpraxis Date: Sun, 19 Oct 2025 22:55:45 +0400 Subject: [PATCH 0414/2435] [Bug #21644] compile.c: fix `newrange` INSN peephole optimization for chilled string ref: https://bugs.ruby-lang.org/issues/21644 ```shell $ ruby -v -e '("a" || "b").."c"' ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux] -e:1: warning: possibly useless use of .. in void context -e:1: [BUG] Stack consistency error (sp: 7, bp: 6) ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux] -- Control frame information ----------------------------------------------- c:0002 p:0013 s:0007 e:000005 EVAL -e:1 [FINISH] c:0001 p:0000 s:0003 E:001920 DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- -e:1:in '
' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- ruby/3.4.7/lib/libruby.so.3.4(rb_print_backtrace+0x8) [0x78aa9573c882] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:823 ruby/3.4.7/lib/libruby.so.3.4(rb_vm_bugreport) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:1155 ruby/3.4.7/lib/libruby.so.3.4(rb_bug_without_die_internal+0x6b) [0x78aa9544c62f] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1097 ruby/3.4.7/lib/libruby.so.3.4(rb_bug) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1115 ruby/3.4.7/lib/libruby.so.3.4(vm_stack_consistency_error+0x1f) [0x78aa9544f091] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:6523 ruby/3.4.7/lib/libruby.so.3.4(vm_get_cref) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:1134 ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1630 ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1627 ruby/3.4.7/lib/libruby.so.3.4(vm_exec_core) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:253 ruby/3.4.7/lib/libruby.so.3.4(vm_exec_loop+0xa) [0x78aa95724959] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2622 ruby/3.4.7/lib/libruby.so.3.4(rb_vm_exec) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2598 ruby/3.4.7/lib/libruby.so.3.4(rb_ec_exec_node+0xa5) [0x78aa95525695] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:281 ruby/3.4.7/lib/libruby.so.3.4(ruby_run_node+0x83) [0x78aa95529333] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:319 ruby/3.4.7/bin/ruby(rb_main+0x21) [0x59d86f5e0186] ./main.c:43 ruby/3.4.7/bin/ruby(main) ./main.c:68 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_call_main+0x7a) [0x78aa9502a1ca] ../sysdeps/nptl/libc_start_call_main.h:58 /lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x78aa9502a28b] ../csu/libc-start.c:360 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main_impl) ../csu/libc-start.c:347 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main) (null):0 [0x59d86f5e01d5] ``` The optimization in question: https://github.com/ruby/ruby/blob/957c832db137e67289e93dfd9fd9e915b1f2fc87/compile.c\#L3453-L3480 Before entering the `newrange` optimization, the iseq looks like this: ``` == disasm: #@:1 (1,0)-(1,17)> 0000 putchilledstring "a" ( 1)[Li] 0002 dup 0003 branchif 8 0005 pop 0006 putchilledstring "b" 0008 putchilledstring "c" 0010 newrange 0 0012 leave ``` So the optimization constructs a new range using the wrong operands (`"b"` and `"c"` instead of `"a"` and `"c"`). I tried to fix this by checking whether the two previous instructions are labeled. --- compile.c | 22 +++++++++++++++++++++- test/ruby/test_range.rb | 1 + 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/compile.c b/compile.c index 717ec0a2cae5ab..27ed42f1f574d4 100644 --- a/compile.c +++ b/compile.c @@ -3222,6 +3222,25 @@ is_frozen_putstring(INSN *insn, VALUE *op) return 0; } +static int +insn_has_label_before(LINK_ELEMENT *elem) +{ + LINK_ELEMENT *prev = elem->prev; + while (prev) { + if (prev->type == ISEQ_ELEMENT_LABEL) { + LABEL *label = (LABEL *)prev; + if (label->refcnt > 0) { + return 1; + } + } + else if (prev->type == ISEQ_ELEMENT_INSN) { + break; + } + prev = prev->prev; + } + return 0; +} + static int optimize_checktype(rb_iseq_t *iseq, INSN *iobj) { @@ -3467,7 +3486,8 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal if ((end = (INSN *)get_prev_insn(range)) != 0 && is_frozen_putstring(end, &str_end) && (beg = (INSN *)get_prev_insn(end)) != 0 && - is_frozen_putstring(beg, &str_beg)) { + is_frozen_putstring(beg, &str_beg) && + !(insn_has_label_before(&beg->link) || insn_has_label_before(&end->link))) { int excl = FIX2INT(OPERAND_AT(range, 0)); VALUE lit_range = rb_range_new(str_beg, str_end, excl); diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index f875c0ab40c5e4..cdf6a0cea59f2f 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -36,6 +36,7 @@ def test_range_string assert_equal(["a"], ("a" ... "b").to_a) assert_equal(["a", "b"], ("a" .. "b").to_a) assert_equal([*"a".."z", "aa"], ("a"..).take(27)) + assert_equal([*"a".."z"], eval("('a' || 'b')..'z'").to_a) end def test_range_numeric_string From 22ceaf278f66db8e30c0b20aef34750a6de4f3e5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 20 Oct 2025 10:23:20 +0900 Subject: [PATCH 0415/2435] [ruby/rubygems] Now ruby/rubygems is the canonical repository url https://github.com/ruby/rubygems/commit/c637007e91 --- lib/bundler/cli/issue.rb | 4 ++-- lib/bundler/friendly_errors.rb | 4 ++-- lib/bundler/source/git/git_proxy.rb | 2 +- lib/rubygems.rb | 6 +++--- lib/rubygems/ext/cargo_builder.rb | 2 +- spec/bundler/bundler/bundler_spec.rb | 6 +++--- spec/bundler/bundler/friendly_errors_spec.rb | 8 ++++---- .../bundler/source/git/git_proxy_spec.rb | 12 ++++++------ spec/bundler/commands/exec_spec.rb | 18 +++++++++--------- spec/bundler/lock/lockfile_spec.rb | 2 +- test/rubygems/test_bundled_ca.rb | 2 +- .../rubygems/test_gem_commands_info_command.rb | 2 +- test/rubygems/test_gem_ext_cmake_builder.rb | 2 +- test/rubygems/test_gem_ext_rake_builder.rb | 2 +- 14 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index fbe9184d121f50..cbfb7da2d8723f 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -10,7 +10,7 @@ def run be sure to check out these resources: 1. Check out our troubleshooting guide for quick fixes to common issues: - https://github.com/rubygems/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md + https://github.com/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md 2. Instructions for common Bundler uses can be found on the documentation site: https://bundler.io/ @@ -22,7 +22,7 @@ def run still aren't working the way you expect them to, please let us know so that we can diagnose and help fix the problem you're having, by filling in the new issue form located at - https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, + https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, and copy and pasting the information below. EOS diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index 8a5ab2e0255cb5..5e8eaee6bbc4b1 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -104,12 +104,12 @@ def issues_url(exception) message = message.split("-").first if exception.is_a?(Errno) require "cgi/escape" require "cgi/util" unless defined?(CGI::EscapeExt) - "https://github.com/rubygems/rubygems/search?q=" \ + "https://github.com/ruby/rubygems/search?q=" \ "#{CGI.escape(message)}&type=Issues" end def new_issue_url - "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" + "https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" end end diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index f613377cb20213..b6f8460400f841 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -16,7 +16,7 @@ class GitNotAllowedError < GitError def initialize(command) msg = String.new msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, " - msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " + msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" super msg end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index b523544e198fa0..db1da659f9d5a8 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -37,7 +37,7 @@ module Gem # Further RubyGems documentation can be found at: # # * {RubyGems Guides}[https://guides.rubygems.org] -# * {RubyGems API}[https://www.rubydoc.info/github/rubygems/rubygems] (also available from +# * {RubyGems API}[https://www.rubydoc.info/github/ruby/rubygems] (also available from # gem server) # # == RubyGems Plugins @@ -69,7 +69,7 @@ module Gem # == Bugs # # You can submit bugs to the -# {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues] +# {RubyGems bug tracker}[https://github.com/ruby/rubygems/issues] # on GitHub # # == Credits @@ -105,7 +105,7 @@ module Gem # # == License # -# See {LICENSE.txt}[https://github.com/rubygems/rubygems/blob/master/LICENSE.txt] for permissions. +# See {LICENSE.txt}[https://github.com/ruby/rubygems/blob/master/LICENSE.txt] for permissions. # # Thanks! # diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index e58d0bb75ccb9d..6bf3b405ad7ce4 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -159,7 +159,7 @@ def platform_specific_rustc_args(dest_dir, flags = []) def linker_args cc_flag = self.class.shellsplit(makefile_config("CC")) # Avoid to ccache like tool from Rust build - # see https://github.com/rubygems/rubygems/pull/8521#issuecomment-2689854359 + # see https://github.com/ruby/rubygems/pull/8521#issuecomment-2689854359 # ex. CC="ccache gcc" or CC="sccache clang --any --args" cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-") linker = cc_flag.shift diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb index 4db8c00e5227ea..bddcbdaef39c25 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -52,10 +52,10 @@ s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" s.email = ["team@bundler.io"] s.homepage = "https://bundler.io" - s.metadata = { "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", - "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + s.metadata = { "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", - "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler" } + "source_code_uri" => "https://github.com/ruby/rubygems/tree/master/bundler" } s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new([">= 2.6.0"]) s.required_rubygems_version = Gem::Requirement.new([">= 3.0.1"]) diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index d6a9d4813dbacc..426e3c856d8fb5 100644 --- a/spec/bundler/bundler/friendly_errors_spec.rb +++ b/spec/bundler/bundler/friendly_errors_spec.rb @@ -197,7 +197,7 @@ it "generates a search URL for the exception message" do exception = Exception.new("Exception message") - expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=Exception+message&type=Issues") + expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/rubygems/search?q=Exception+message&type=Issues") end it "generates a search URL for only the first line of a multi-line exception message" do @@ -206,7 +206,7 @@ Second line of the exception message END - expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=First+line+of+the+exception+message&type=Issues") + expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/rubygems/search?q=First+line+of+the+exception+message&type=Issues") end it "generates the url without colons" do @@ -215,7 +215,7 @@ END issues_url = Bundler::FriendlyErrors.issues_url(exception) expect(issues_url).not_to include("%3A") - expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues") + expect(issues_url).to eq("https://github.com/ruby/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues") end it "removes information after - for Errono::EACCES" do @@ -225,7 +225,7 @@ allow(exception).to receive(:is_a?).with(Errno).and_return(true) issues_url = Bundler::FriendlyErrors.issues_url(exception) expect(issues_url).not_to include("/Users/foo/bar") - expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues") + expect(issues_url).to eq("https://github.com/ruby/rubygems/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues") end end end diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb index 492eee64444b1c..b2b7ab5c54009f 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do let(:path) { Pathname("path") } - let(:uri) { "https://github.com/rubygems/rubygems.git" } + let(:uri) { "https://github.com/ruby/rubygems.git" } let(:ref) { nil } let(:branch) { nil } let(:tag) { nil } @@ -64,7 +64,7 @@ it "adds username and password to URI" do Bundler.settings.temporary(uri => "u:p") do allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") - expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end @@ -72,13 +72,13 @@ it "adds username and password to URI for host" do Bundler.settings.temporary("github.com" => "u:p") do allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") - expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end it "does not add username and password to mismatched URI" do - Bundler.settings.temporary("https://u:p@github.com/rubygems/rubygems-mismatch.git" => "u:p") do + Bundler.settings.temporary("https://u:p@github.com/ruby/rubygems-mismatch.git" => "u:p") do allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) subject.checkout @@ -87,7 +87,7 @@ it "keeps original userinfo" do Bundler.settings.temporary("github.com" => "u:p") do - original = "https://orig:info@github.com/rubygems/rubygems.git" + original = "https://orig:info@github.com/ruby/rubygems.git" git_proxy = described_class.new(Pathname("path"), original, options) allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result]) @@ -199,7 +199,7 @@ end context "URI is HTTP" do - let(:uri) { "http://github.com/rubygems/rubygems.git" } + let(:uri) { "http://github.com/ruby/rubygems.git" } let(:without_depth_arguments) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--single-branch"] } let(:fail_clone_result) { double(Process::Status, success?: false) } diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index af8bfc71f19419..03f1d839c76c9a 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -95,7 +95,7 @@ end it "respects custom process title when loading through ruby" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility = <<~'RUBY' Process.setproctitle("1-2-3-4-5-6-7") @@ -120,7 +120,7 @@ end it "handles --keep-file-descriptors" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? require "tempfile" @@ -153,7 +153,7 @@ end it "can run a command named --verbose" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" File.open(bundled_app("--verbose"), "w") do |f| @@ -805,7 +805,7 @@ def bin_path(a,b,c) end it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? subject expect(exitstatus).to eq(exit_code) @@ -1042,7 +1042,7 @@ def bin_path(a,b,c) RUBY it "receives the signal" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? bundle("exec #{path}") do |_, o, thr| o.gets # Consumes 'Started' and ensures that thread has started @@ -1065,7 +1065,7 @@ def bin_path(a,b,c) RUBY it "makes sure no unexpected signals are restored to DEFAULT" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? test_signals.each do |n| Signal.trap(n, "IGNORE") @@ -1082,7 +1082,7 @@ def bin_path(a,b,c) context "nested bundle exec" do context "when bundle in a local path" do before do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? gemfile <<-G source "https://gem.repo1" @@ -1106,7 +1106,7 @@ def bin_path(a,b,c) context "when Kernel.require uses extra monkeypatches" do before do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"" end @@ -1211,7 +1211,7 @@ def require(path) it "only leaves the default gem in the stdlib available" do default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION" - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"" # must happen before installing the broken system gem diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 02e53454d89a99..d56f7834eb4fdb 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -982,7 +982,7 @@ update_repo2 do # Capistrano did this (at least until version 2.5.10) # RubyGems 2.2 doesn't allow the specifying of a dependency twice - # See https://github.com/rubygems/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f + # See https://github.com/ruby/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f build_gem "double_deps", "1.0", skip_validation: true do |s| s.add_dependency "net-ssh", ">= 1.0.0" s.add_dependency "net-ssh" diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb index a737185681ee25..cc8fa884cacdf9 100644 --- a/test/rubygems/test_bundled_ca.rb +++ b/test/rubygems/test_bundled_ca.rb @@ -12,7 +12,7 @@ # = Testing Bundled CA # -# The tested hosts are explained in detail here: https://github.com/rubygems/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9 +# The tested hosts are explained in detail here: https://github.com/ruby/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9 # class TestGemBundledCA < Gem::TestCase diff --git a/test/rubygems/test_gem_commands_info_command.rb b/test/rubygems/test_gem_commands_info_command.rb index f020d380d29630..dab7cfb836b799 100644 --- a/test/rubygems/test_gem_commands_info_command.rb +++ b/test/rubygems/test_gem_commands_info_command.rb @@ -13,7 +13,7 @@ def setup def gem(name, version = "1.0") spec = quick_gem name do |gem| gem.summary = "test gem" - gem.homepage = "https://github.com/rubygems/rubygems" + gem.homepage = "https://github.com/ruby/rubygems" gem.files = %W[lib/#{name}.rb Rakefile] gem.authors = ["Colby", "Jack"] gem.license = "MIT" diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb index e2bdedc710546d..b9b57084d4fd0d 100644 --- a/test/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/rubygems/test_gem_ext_cmake_builder.rb @@ -7,7 +7,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase def setup super - # Details: https://github.com/rubygems/rubygems/issues/1270#issuecomment-177368340 + # Details: https://github.com/ruby/rubygems/issues/1270#issuecomment-177368340 pend "CmakeBuilder doesn't work on Windows." if Gem.win_platform? require "open3" diff --git a/test/rubygems/test_gem_ext_rake_builder.rb b/test/rubygems/test_gem_ext_rake_builder.rb index bd72c1aa08c561..68ad15b044f0a2 100644 --- a/test/rubygems/test_gem_ext_rake_builder.rb +++ b/test/rubygems/test_gem_ext_rake_builder.rb @@ -29,7 +29,7 @@ def test_class_build end end - # https://github.com/rubygems/rubygems/pull/1819 + # https://github.com/ruby/rubygems/pull/1819 # # It should not fail with a non-empty args list either def test_class_build_with_args From b6f1c4edee9e3e1c5e81229225170a06b921b6f2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 20 Oct 2025 14:01:14 +0900 Subject: [PATCH 0416/2435] [ruby/rubygems] Use ruby/rubygems instead of rubygems/rubygems at document, tool and configurations https://github.com/ruby/rubygems/commit/749b498822 --- lib/bundler/bundler.gemspec | 6 +++--- lib/bundler/man/bundle-config.1 | 2 +- lib/bundler/man/bundle-config.1.ronn | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 16ca4a022ce998..49319e81b4f08c 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -23,10 +23,10 @@ Gem::Specification.new do |s| s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" s.metadata = { - "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", - "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", - "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler", + "source_code_uri" => "https://github.com/ruby/rubygems/tree/master/bundler", } s.required_ruby_version = ">= 3.2.0" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 29e830a3b0e03b..b44891f6e92374 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -312,7 +312,7 @@ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" For gems with a git source with HTTP(S) URL you can specify credentials like so: .IP "" 4 .nf -bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password +bundle config set \-\-global https://github\.com/ruby/rubygems\.git username:password .fi .IP "" 0 .P diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 62fce8fa919aa0..281ab2da0cd124 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -360,7 +360,7 @@ Or you can set the credentials as an environment variable like this: For gems with a git source with HTTP(S) URL you can specify credentials like so: - bundle config set --global https://github.com/rubygems/rubygems.git username:password + bundle config set --global https://github.com/ruby/rubygems.git username:password Or you can set the credentials as an environment variable like so: From 6eb75f6c36288cdd7522f27ad18608d13031f2b8 Mon Sep 17 00:00:00 2001 From: Mat Sadler Date: Fri, 17 Oct 2025 19:49:12 -0700 Subject: [PATCH 0417/2435] [ruby/rubygems] update magnus version in rust extension gem template https://github.com/ruby/rubygems/commit/1ba8eb4ab3 --- lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt index 0ebce0e4a0c6e7..c0dc63fbfa3abd 100644 --- a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt +++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -12,4 +12,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -magnus = { version = "0.6.2" } +magnus = { version = "0.8.2" } From fba349e65883b7b9541433f7716e41d27c7e8a69 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 20 Oct 2025 10:55:52 -0400 Subject: [PATCH 0418/2435] ZJIT: Implement expandarray (#14847) Only support the simple case: no splat or rest. lobsters before:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (60.5% of total 11,039,954): Kernel#is_a?: 1,030,769 ( 9.3%) String#<<: 851,954 ( 7.7%) Hash#[]=: 742,941 ( 6.7%) Regexp#match?: 399,894 ( 3.6%) String#empty?: 353,775 ( 3.2%) Hash#key?: 349,147 ( 3.2%) String#start_with?: 334,961 ( 3.0%) Kernel#respond_to?: 316,528 ( 2.9%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.2%) TrueClass#===: 235,771 ( 2.1%) FalseClass#===: 231,144 ( 2.1%) Array#include?: 211,385 ( 1.9%) Hash#fetch: 204,702 ( 1.9%) Kernel#block_given?: 181,797 ( 1.6%) Kernel#dup: 179,341 ( 1.6%) BasicObject#!=: 175,997 ( 1.6%) Class#new: 168,079 ( 1.5%) Kernel#kind_of?: 165,600 ( 1.5%) String#==: 157,735 ( 1.4%) Module#clock_gettime: 144,992 ( 1.3%) Top-20 not annotated C methods (61.4% of total 11,202,087): Kernel#is_a?: 1,212,660 (10.8%) String#<<: 851,954 ( 7.6%) Hash#[]=: 743,120 ( 6.6%) Regexp#match?: 399,894 ( 3.6%) String#empty?: 361,013 ( 3.2%) Hash#key?: 349,147 ( 3.1%) String#start_with?: 334,961 ( 3.0%) Kernel#respond_to?: 316,528 ( 2.8%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.1%) TrueClass#===: 235,771 ( 2.1%) FalseClass#===: 231,144 ( 2.1%) Array#include?: 211,385 ( 1.9%) Hash#fetch: 204,702 ( 1.8%) Kernel#block_given?: 191,666 ( 1.7%) Kernel#dup: 179,348 ( 1.6%) BasicObject#!=: 176,181 ( 1.6%) Class#new: 168,079 ( 1.5%) Kernel#kind_of?: 165,634 ( 1.5%) String#==: 163,667 ( 1.5%) Module#clock_gettime: 144,992 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,523,682): iseq: 2,271,936 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,703 (21.0%) alias: 310,747 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,293,171): invokesuper: 2,373,404 (55.3%) invokeblock: 811,926 (18.9%) sendforward: 505,452 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,404 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,530,724): send_without_block_polymorphic: 9,722,491 (38.1%) send_no_profiles: 5,894,788 (23.1%) send_without_block_not_optimized_method_type: 4,523,682 (17.7%) not_optimized_instruction: 4,293,171 (16.8%) send_without_block_no_profiles: 998,746 ( 3.9%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 690,950): expandarray: 328,490 (47.5%) checkkeyword: 190,694 (27.6%) getclassvariable: 59,901 ( 8.7%) invokesuperforward: 49,503 ( 7.2%) getblockparam: 49,119 ( 7.1%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 952 ( 0.1%) checkmatch: 290 ( 0.0%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,718,636): register_spill_on_alloc: 3,418,255 (91.9%) register_spill_on_ccall: 182,018 ( 4.9%) exception_handler: 118,363 ( 3.2%) Top-14 side exit reasons (100.0% of total 10,860,385): compile_error: 3,718,636 (34.2%) guard_type_failure: 2,638,926 (24.3%) guard_shape_failure: 1,917,209 (17.7%) unhandled_yarv_insn: 690,950 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 535,789 ( 4.9%) unhandled_kwarg: 455,347 ( 4.2%) patchpoint: 370,476 ( 3.4%) unknown_newarray_send: 314,786 ( 2.9%) unhandled_splat: 122,071 ( 1.1%) unhandled_hir_insn: 76,397 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) obj_to_string_fallback: 566 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 17 ( 0.0%) send_count: 62,244,604 dynamic_send_count: 25,530,724 (41.0%) optimized_send_count: 36,713,880 (59.0%) iseq_optimized_send_count: 18,587,512 (29.9%) inline_cfunc_optimized_send_count: 7,086,414 (11.4%) non_variadic_cfunc_optimized_send_count: 8,375,754 (13.5%) variadic_cfunc_optimized_send_count: 2,664,200 ( 4.3%) dynamic_getivar_count: 7,365,995 dynamic_setivar_count: 7,245,005 compiled_iseq_count: 4,796 failed_iseq_count: 447 compile_time: 814ms profile_time: 9ms gc_time: 9ms invalidation_time: 72ms vm_write_pc_count: 64,156,223 vm_write_sp_count: 62,812,449 vm_write_locals_count: 62,812,449 vm_write_stack_count: 62,812,449 vm_write_to_parent_iseq_local_count: 292,458 vm_read_from_parent_iseq_local_count: 6,599,701 code_region_bytes: 22,953,984 side_exit_count: 10,860,385 total_insn_count: 517,606,340 vm_insn_count: 162,979,530 zjit_insn_count: 354,626,810 ratio_in_zjit: 68.5% ```
lobsters after:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (59.9% of total 11,291,815): Kernel#is_a?: 1,046,269 ( 9.3%) String#<<: 851,954 ( 7.5%) Hash#[]=: 743,274 ( 6.6%) Regexp#match?: 399,894 ( 3.5%) String#empty?: 353,775 ( 3.1%) Hash#key?: 349,147 ( 3.1%) String#start_with?: 334,961 ( 3.0%) Kernel#respond_to?: 316,502 ( 2.8%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.1%) TrueClass#===: 235,771 ( 2.1%) FalseClass#===: 231,144 ( 2.0%) String#sub!: 219,579 ( 1.9%) Array#include?: 211,385 ( 1.9%) Hash#fetch: 204,702 ( 1.8%) Kernel#block_given?: 181,797 ( 1.6%) Kernel#dup: 179,341 ( 1.6%) BasicObject#!=: 175,997 ( 1.6%) Class#new: 168,079 ( 1.5%) Kernel#kind_of?: 165,600 ( 1.5%) String#==: 157,742 ( 1.4%) Top-20 not annotated C methods (60.9% of total 11,466,928): Kernel#is_a?: 1,239,923 (10.8%) String#<<: 851,954 ( 7.4%) Hash#[]=: 743,453 ( 6.5%) Regexp#match?: 399,894 ( 3.5%) String#empty?: 361,013 ( 3.1%) Hash#key?: 349,147 ( 3.0%) String#start_with?: 334,961 ( 2.9%) Kernel#respond_to?: 316,502 ( 2.8%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.1%) TrueClass#===: 235,771 ( 2.1%) FalseClass#===: 231,144 ( 2.0%) String#sub!: 219,579 ( 1.9%) Array#include?: 211,385 ( 1.8%) Hash#fetch: 204,702 ( 1.8%) Kernel#block_given?: 191,666 ( 1.7%) Kernel#dup: 179,348 ( 1.6%) BasicObject#!=: 176,181 ( 1.5%) Class#new: 168,079 ( 1.5%) Kernel#kind_of?: 165,634 ( 1.4%) String#==: 163,674 ( 1.4%) Top-2 not optimized method types for send (100.0% of total 72,318): cfunc: 48,055 (66.4%) iseq: 24,263 (33.6%) Top-6 not optimized method types for send_without_block (100.0% of total 4,524,016): iseq: 2,272,269 (50.2%) bmethod: 985,636 (21.8%) optimized: 949,704 (21.0%) alias: 310,747 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,294,241): invokesuper: 2,375,446 (55.3%) invokeblock: 810,955 (18.9%) sendforward: 505,451 (11.8%) opt_eq: 451,754 (10.5%) opt_plus: 74,404 ( 1.7%) opt_minus: 36,228 ( 0.8%) opt_send_without_block: 21,792 ( 0.5%) opt_neq: 7,231 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,753 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 25,534,542): send_without_block_polymorphic: 9,723,469 (38.1%) send_no_profiles: 5,896,023 (23.1%) send_without_block_not_optimized_method_type: 4,524,016 (17.7%) not_optimized_instruction: 4,294,241 (16.8%) send_without_block_no_profiles: 998,947 ( 3.9%) send_not_optimized_method_type: 72,318 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,765 ( 0.0%) send_without_block_direct_too_many_args: 629 ( 0.0%) Top-8 unhandled YARV insns (100.0% of total 362,460): checkkeyword: 190,694 (52.6%) getclassvariable: 59,901 (16.5%) invokesuperforward: 49,503 (13.7%) getblockparam: 49,119 (13.6%) opt_duparray_send: 11,978 ( 3.3%) getconstant: 952 ( 0.3%) checkmatch: 290 ( 0.1%) once: 23 ( 0.0%) Top-3 compile error reasons (100.0% of total 3,798,744): register_spill_on_alloc: 3,495,669 (92.0%) register_spill_on_ccall: 184,712 ( 4.9%) exception_handler: 118,363 ( 3.1%) Top-15 side exit reasons (100.0% of total 10,637,319): compile_error: 3,798,744 (35.7%) guard_type_failure: 2,655,504 (25.0%) guard_shape_failure: 1,917,217 (18.0%) block_param_proxy_not_iseq_or_ifunc: 535,789 ( 5.0%) unhandled_kwarg: 455,492 ( 4.3%) patchpoint: 370,478 ( 3.5%) unhandled_yarv_insn: 362,460 ( 3.4%) unknown_newarray_send: 314,786 ( 3.0%) unhandled_splat: 122,071 ( 1.1%) unhandled_hir_insn: 83,066 ( 0.8%) block_param_proxy_modified: 19,193 ( 0.2%) guard_int_equals_failure: 1,914 ( 0.0%) obj_to_string_fallback: 566 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 17 ( 0.0%) send_count: 62,495,067 dynamic_send_count: 25,534,542 (40.9%) optimized_send_count: 36,960,525 (59.1%) iseq_optimized_send_count: 18,582,072 (29.7%) inline_cfunc_optimized_send_count: 7,086,638 (11.3%) non_variadic_cfunc_optimized_send_count: 8,392,657 (13.4%) variadic_cfunc_optimized_send_count: 2,899,158 ( 4.6%) dynamic_getivar_count: 7,365,994 dynamic_setivar_count: 7,248,500 compiled_iseq_count: 4,780 failed_iseq_count: 463 compile_time: 816ms profile_time: 9ms gc_time: 11ms invalidation_time: 70ms vm_write_pc_count: 64,363,541 vm_write_sp_count: 63,022,221 vm_write_locals_count: 63,022,221 vm_write_stack_count: 63,022,221 vm_write_to_parent_iseq_local_count: 292,458 vm_read_from_parent_iseq_local_count: 6,850,977 code_region_bytes: 23,019,520 side_exit_count: 10,637,319 total_insn_count: 517,303,190 vm_insn_count: 160,562,103 zjit_insn_count: 356,741,087 ratio_in_zjit: 69.0% ```
railsbench before:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (66.1% of total 25,524,934): Hash#[]=: 1,700,237 ( 6.7%) String#getbyte: 1,572,123 ( 6.2%) String#<<: 1,494,022 ( 5.9%) Kernel#is_a?: 1,429,930 ( 5.6%) String#empty?: 1,370,323 ( 5.4%) Regexp#match?: 1,235,067 ( 4.8%) Kernel#respond_to?: 1,198,251 ( 4.7%) Hash#key?: 1,087,406 ( 4.3%) String#setbyte: 810,022 ( 3.2%) Integer#^: 766,624 ( 3.0%) Kernel#block_given?: 603,613 ( 2.4%) String#==: 590,409 ( 2.3%) Class#new: 506,216 ( 2.0%) Hash#delete: 455,288 ( 1.8%) BasicObject#!=: 428,771 ( 1.7%) Hash#fetch: 408,621 ( 1.6%) String#ascii_only?: 373,915 ( 1.5%) ObjectSpace::WeakKeyMap#[]: 287,957 ( 1.1%) NilClass#===: 277,244 ( 1.1%) Kernel#Array: 269,590 ( 1.1%) Top-20 not annotated C methods (66.8% of total 25,392,654): Hash#[]=: 1,700,416 ( 6.7%) String#getbyte: 1,572,123 ( 6.2%) Kernel#is_a?: 1,515,672 ( 6.0%) String#<<: 1,494,022 ( 5.9%) String#empty?: 1,370,478 ( 5.4%) Regexp#match?: 1,235,067 ( 4.9%) Kernel#respond_to?: 1,198,251 ( 4.7%) Hash#key?: 1,087,406 ( 4.3%) String#setbyte: 810,022 ( 3.2%) Integer#^: 766,624 ( 3.0%) Kernel#block_given?: 603,613 ( 2.4%) String#==: 601,115 ( 2.4%) Class#new: 506,216 ( 2.0%) Hash#delete: 455,288 ( 1.8%) BasicObject#!=: 428,876 ( 1.7%) Hash#fetch: 408,621 ( 1.6%) String#ascii_only?: 373,915 ( 1.5%) ObjectSpace::WeakKeyMap#[]: 287,957 ( 1.1%) NilClass#===: 277,244 ( 1.1%) Kernel#Array: 269,590 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 186,159): iseq: 112,747 (60.6%) cfunc: 73,412 (39.4%) Top-6 not optimized method types for send_without_block (100.0% of total 8,142,248): iseq: 3,464,671 (42.6%) optimized: 2,632,884 (32.3%) bmethod: 1,290,701 (15.9%) alias: 706,020 ( 8.7%) null: 47,942 ( 0.6%) cfunc: 30 ( 0.0%) Top-11 not optimized instructions (100.0% of total 8,394,873): invokesuper: 5,602,274 (66.7%) invokeblock: 1,764,936 (21.0%) sendforward: 551,832 ( 6.6%) opt_eq: 441,959 ( 5.3%) opt_plus: 31,635 ( 0.4%) opt_send_without_block: 1,163 ( 0.0%) opt_lt: 372 ( 0.0%) opt_mult: 251 ( 0.0%) opt_ge: 193 ( 0.0%) opt_neq: 149 ( 0.0%) opt_or: 109 ( 0.0%) Top-8 send fallback reasons (100.0% of total 40,748,753): send_without_block_polymorphic: 12,933,923 (31.7%) send_no_profiles: 9,033,636 (22.2%) not_optimized_instruction: 8,394,873 (20.6%) send_without_block_not_optimized_method_type: 8,142,248 (20.0%) send_without_block_no_profiles: 1,839,228 ( 4.5%) send_without_block_cfunc_array_variadic: 215,046 ( 0.5%) send_not_optimized_method_type: 186,159 ( 0.5%) obj_to_string_not_string: 3,640 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 1,604,456): getclassvariable: 458,136 (28.6%) getblockparam: 455,921 (28.4%) checkkeyword: 265,425 (16.5%) invokesuperforward: 239,383 (14.9%) expandarray: 137,305 ( 8.6%) getconstant: 48,100 ( 3.0%) checkmatch: 149 ( 0.0%) once: 23 ( 0.0%) opt_duparray_send: 14 ( 0.0%) Top-3 compile error reasons (100.0% of total 5,570,130): register_spill_on_alloc: 4,994,130 (89.7%) exception_handler: 356,784 ( 6.4%) register_spill_on_ccall: 219,216 ( 3.9%) Top-13 side exit reasons (100.0% of total 12,412,181): compile_error: 5,570,130 (44.9%) unhandled_yarv_insn: 1,604,456 (12.9%) guard_shape_failure: 1,462,872 (11.8%) guard_type_failure: 845,891 ( 6.8%) block_param_proxy_not_iseq_or_ifunc: 765,968 ( 6.2%) unhandled_kwarg: 658,341 ( 5.3%) patchpoint: 504,437 ( 4.1%) unhandled_splat: 446,990 ( 3.6%) unknown_newarray_send: 332,740 ( 2.7%) unhandled_hir_insn: 160,205 ( 1.3%) block_param_proxy_modified: 59,589 ( 0.5%) obj_to_string_fallback: 553 ( 0.0%) interrupt: 9 ( 0.0%) send_count: 119,067,587 dynamic_send_count: 40,748,753 (34.2%) optimized_send_count: 78,318,834 (65.8%) iseq_optimized_send_count: 39,936,542 (33.5%) inline_cfunc_optimized_send_count: 12,857,358 (10.8%) non_variadic_cfunc_optimized_send_count: 19,722,584 (16.6%) variadic_cfunc_optimized_send_count: 5,802,350 ( 4.9%) dynamic_getivar_count: 10,980,323 dynamic_setivar_count: 12,962,726 compiled_iseq_count: 2,531 failed_iseq_count: 245 compile_time: 414ms profile_time: 21ms gc_time: 33ms invalidation_time: 5ms vm_write_pc_count: 129,093,714 vm_write_sp_count: 126,023,084 vm_write_locals_count: 126,023,084 vm_write_stack_count: 126,023,084 vm_write_to_parent_iseq_local_count: 385,461 vm_read_from_parent_iseq_local_count: 11,266,484 code_region_bytes: 12,156,928 side_exit_count: 12,412,181 total_insn_count: 866,780,158 vm_insn_count: 216,821,134 zjit_insn_count: 649,959,024 ratio_in_zjit: 75.0% ```
railsbench after:
``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (66.0% of total 25,597,895): Hash#[]=: 1,724,042 ( 6.7%) String#getbyte: 1,572,123 ( 6.1%) String#<<: 1,494,022 ( 5.8%) Kernel#is_a?: 1,429,946 ( 5.6%) String#empty?: 1,370,323 ( 5.4%) Regexp#match?: 1,235,067 ( 4.8%) Kernel#respond_to?: 1,198,251 ( 4.7%) Hash#key?: 1,087,406 ( 4.2%) String#setbyte: 810,022 ( 3.2%) Integer#^: 766,624 ( 3.0%) Kernel#block_given?: 603,613 ( 2.4%) String#==: 590,699 ( 2.3%) Class#new: 506,216 ( 2.0%) Hash#delete: 455,288 ( 1.8%) BasicObject#!=: 428,771 ( 1.7%) Hash#fetch: 408,621 ( 1.6%) String#ascii_only?: 373,915 ( 1.5%) ObjectSpace::WeakKeyMap#[]: 287,957 ( 1.1%) NilClass#===: 277,244 ( 1.1%) Kernel#Array: 269,590 ( 1.1%) Top-20 not annotated C methods (66.7% of total 25,465,615): Hash#[]=: 1,724,221 ( 6.8%) String#getbyte: 1,572,123 ( 6.2%) Kernel#is_a?: 1,515,688 ( 6.0%) String#<<: 1,494,022 ( 5.9%) String#empty?: 1,370,478 ( 5.4%) Regexp#match?: 1,235,067 ( 4.8%) Kernel#respond_to?: 1,198,251 ( 4.7%) Hash#key?: 1,087,406 ( 4.3%) String#setbyte: 810,022 ( 3.2%) Integer#^: 766,624 ( 3.0%) Kernel#block_given?: 603,613 ( 2.4%) String#==: 601,405 ( 2.4%) Class#new: 506,216 ( 2.0%) Hash#delete: 455,288 ( 1.8%) BasicObject#!=: 428,876 ( 1.7%) Hash#fetch: 408,621 ( 1.6%) String#ascii_only?: 373,915 ( 1.5%) ObjectSpace::WeakKeyMap#[]: 287,957 ( 1.1%) NilClass#===: 277,244 ( 1.1%) Kernel#Array: 269,590 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 186,159): iseq: 112,747 (60.6%) cfunc: 73,412 (39.4%) Top-6 not optimized method types for send_without_block (100.0% of total 8,142,248): iseq: 3,464,671 (42.6%) optimized: 2,632,884 (32.3%) bmethod: 1,290,701 (15.9%) alias: 706,020 ( 8.7%) null: 47,942 ( 0.6%) cfunc: 30 ( 0.0%) Top-11 not optimized instructions (100.0% of total 8,442,456): invokesuper: 5,649,857 (66.9%) invokeblock: 1,764,936 (20.9%) sendforward: 551,832 ( 6.5%) opt_eq: 441,959 ( 5.2%) opt_plus: 31,635 ( 0.4%) opt_send_without_block: 1,163 ( 0.0%) opt_lt: 372 ( 0.0%) opt_mult: 251 ( 0.0%) opt_ge: 193 ( 0.0%) opt_neq: 149 ( 0.0%) opt_or: 109 ( 0.0%) Top-8 send fallback reasons (100.0% of total 40,796,314): send_without_block_polymorphic: 12,933,921 (31.7%) send_no_profiles: 9,033,616 (22.1%) not_optimized_instruction: 8,442,456 (20.7%) send_without_block_not_optimized_method_type: 8,142,248 (20.0%) send_without_block_no_profiles: 1,839,228 ( 4.5%) send_without_block_cfunc_array_variadic: 215,046 ( 0.5%) send_not_optimized_method_type: 186,159 ( 0.5%) obj_to_string_not_string: 3,640 ( 0.0%) Top-8 unhandled YARV insns (100.0% of total 1,467,151): getclassvariable: 458,136 (31.2%) getblockparam: 455,921 (31.1%) checkkeyword: 265,425 (18.1%) invokesuperforward: 239,383 (16.3%) getconstant: 48,100 ( 3.3%) checkmatch: 149 ( 0.0%) once: 23 ( 0.0%) opt_duparray_send: 14 ( 0.0%) Top-3 compile error reasons (100.0% of total 5,825,923): register_spill_on_alloc: 5,225,940 (89.7%) exception_handler: 356,784 ( 6.1%) register_spill_on_ccall: 243,199 ( 4.2%) Top-13 side exit reasons (100.0% of total 12,530,763): compile_error: 5,825,923 (46.5%) unhandled_yarv_insn: 1,467,151 (11.7%) guard_shape_failure: 1,462,876 (11.7%) guard_type_failure: 845,913 ( 6.8%) block_param_proxy_not_iseq_or_ifunc: 765,968 ( 6.1%) unhandled_kwarg: 658,341 ( 5.3%) patchpoint: 504,437 ( 4.0%) unhandled_splat: 446,990 ( 3.6%) unknown_newarray_send: 332,740 ( 2.7%) unhandled_hir_insn: 160,273 ( 1.3%) block_param_proxy_modified: 59,589 ( 0.5%) obj_to_string_fallback: 553 ( 0.0%) interrupt: 9 ( 0.0%) send_count: 119,163,569 dynamic_send_count: 40,796,314 (34.2%) optimized_send_count: 78,367,255 (65.8%) iseq_optimized_send_count: 39,911,967 (33.5%) inline_cfunc_optimized_send_count: 12,857,393 (10.8%) non_variadic_cfunc_optimized_send_count: 19,770,401 (16.6%) variadic_cfunc_optimized_send_count: 5,827,494 ( 4.9%) dynamic_getivar_count: 10,980,323 dynamic_setivar_count: 12,986,381 compiled_iseq_count: 2,523 failed_iseq_count: 252 compile_time: 420ms profile_time: 21ms gc_time: 30ms invalidation_time: 4ms vm_write_pc_count: 128,973,665 vm_write_sp_count: 125,926,968 vm_write_locals_count: 125,926,968 vm_write_stack_count: 125,926,968 vm_write_to_parent_iseq_local_count: 385,752 vm_read_from_parent_iseq_local_count: 11,267,766 code_region_bytes: 12,189,696 side_exit_count: 12,530,763 total_insn_count: 866,667,490 vm_insn_count: 217,813,201 zjit_insn_count: 648,854,289 ratio_in_zjit: 74.9% ```
--- jit.c | 6 ++ test/ruby/test_zjit.rb | 30 ++++++++ yjit.c | 6 -- yjit/bindgen/src/main.rs | 2 +- yjit/src/codegen.rs | 4 +- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 14 +++- zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 137 +++++++++++++++++++++++++++++++-- zjit/src/hir_type/mod.rs | 17 ++++ zjit/src/stats.rs | 1 + 12 files changed, 204 insertions(+), 17 deletions(-) diff --git a/jit.c b/jit.c index f233a2f01f1c8e..0b491f0481d875 100644 --- a/jit.c +++ b/jit.c @@ -450,6 +450,12 @@ rb_yarv_ary_entry_internal(VALUE ary, long offset) return rb_ary_entry_internal(ary, offset); } +long +rb_jit_array_len(VALUE a) +{ + return rb_array_len(a); +} + void rb_set_cfp_pc(struct rb_control_frame_struct *cfp, const VALUE *pc) { diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index faf717096a4c77..b0c717bc24b435 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1714,6 +1714,36 @@ def test = RUBY_COPYRIGHT }, call_threshold: 1, insns: [:opt_getconstant_path] end + def test_expandarray_no_splat + assert_compiles '[3, 4]', %q{ + def test(o) + a, b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat + assert_compiles '[3, [4]]', %q{ + def test(o) + a, *b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat_post + assert_compiles '[3, [4], 5]', %q{ + def test(o) + a, *b, c = o + [a, b, c] + end + test [3, 4, 5] + }, call_threshold: 1, insns: [:expandarray] + end + def test_getconstant_path_autoload # A constant-referencing expression can run arbitrary code through Kernel#autoload. Dir.mktmpdir('autoload') do |tmpdir| diff --git a/yjit.c b/yjit.c index 57b09d73b0bceb..598fe5716704d0 100644 --- a/yjit.c +++ b/yjit.c @@ -69,12 +69,6 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); // The "_yjit_" part is for trying to be informative. We might want different // suffixes for symbols meant for Rust and symbols meant for broader CRuby. -long -rb_yjit_array_len(VALUE a) -{ - return rb_array_len(a); -} - # define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) // For a given raw_sample (frame), set the hash with the caller's diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 29b17346cd90ae..0d4d57e0695941 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -381,7 +381,7 @@ fn main() { .allowlist_function("rb_METHOD_ENTRY_VISI") .allowlist_function("rb_RCLASS_ORIGIN") .allowlist_function("rb_method_basic_definition_p") - .allowlist_function("rb_yjit_array_len") + .allowlist_function("rb_jit_array_len") .allowlist_function("rb_obj_class") .allowlist_function("rb_obj_is_proc") .allowlist_function("rb_vm_base_ptr") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 25a8545e859172..bf758a4f62bd21 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2305,7 +2305,7 @@ fn gen_expandarray( } // Get the compile-time array length - let comptime_len = unsafe { rb_yjit_array_len(comptime_recv) as u32 }; + let comptime_len = unsafe { rb_jit_array_len(comptime_recv) as u32 }; // Move the array from the stack and check that it's an array. guard_object_is_array( @@ -7603,7 +7603,7 @@ fn gen_send_iseq( gen_counter_incr(jit, asm, Counter::send_iseq_splat_not_array); return None; } else { - unsafe { rb_yjit_array_len(array) as u32} + unsafe { rb_jit_array_len(array) as u32} }; // Arity check accounting for size of the splat. When callee has rest parameters, we insert diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 1e34440460edd1..0a14a699284268 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1113,7 +1113,6 @@ extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); - pub fn rb_yjit_array_len(a: VALUE) -> ::std::os::raw::c_long; pub fn rb_yjit_exit_locations_dict( yjit_raw_samples: *mut VALUE, yjit_line_samples: *mut ::std::os::raw::c_int, @@ -1250,6 +1249,7 @@ extern "C" { pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int; pub fn rb_assert_cme_handle(handle: VALUE); pub fn rb_yarv_ary_entry_internal(ary: VALUE, offset: ::std::os::raw::c_long) -> VALUE; + pub fn rb_jit_array_len(a: VALUE) -> ::std::os::raw::c_long; pub fn rb_set_cfp_pc(cfp: *mut rb_control_frame_struct, pc: *const VALUE); pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE); pub fn rb_jit_shape_too_complex_p(shape_id: shape_id_t) -> bool; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index b54e9404fdb10e..f13b61acf04609 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -272,6 +272,7 @@ fn main() { .allowlist_function("rb_jit_mark_executable") .allowlist_function("rb_jit_mark_unused") .allowlist_function("rb_jit_get_page_size") + .allowlist_function("rb_jit_array_len") .allowlist_function("rb_zjit_iseq_builtin_attrs") .allowlist_function("rb_zjit_iseq_inspect") .allowlist_function("rb_zjit_iseq_insn_set") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 42501de2423888..ea4a7ecc7c8c21 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -356,6 +356,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), + Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), &Insn::ObjectAllocClass { class, state } => gen_object_alloc_class(asm, class, &function.frame_state(state)), Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)), @@ -1258,6 +1259,10 @@ fn gen_aref_fixnum( asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) } +fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { + asm_ccall!(asm, rb_jit_array_len, array) +} + /// Compile a new hash instruction fn gen_new_hash( jit: &mut JITState, @@ -1589,8 +1594,13 @@ fn gen_guard_type_not(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, g } /// Compile an identity check with a side exit -fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, expected: VALUE, state: &FrameState) -> lir::Opnd { - asm.cmp(val, Opnd::Value(expected)); +fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, expected: crate::hir::Const, state: &FrameState) -> lir::Opnd { + let expected_opnd: Opnd = match expected { + crate::hir::Const::Value(v) => { Opnd::Value(v) } + crate::hir::Const::CInt64(v) => { v.into() } + _ => panic!("gen_guard_bit_equals: unexpected hir::Const {:?}", expected), + }; + asm.cmp(val, expected_opnd); asm.jnz(side_exit(jit, state, GuardBitEquals(expected))); val } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index ffaafed5ff33c2..c67e229a8009e7 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1045,6 +1045,7 @@ unsafe extern "C" { pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int; pub fn rb_assert_cme_handle(handle: VALUE); pub fn rb_yarv_ary_entry_internal(ary: VALUE, offset: ::std::os::raw::c_long) -> VALUE; + pub fn rb_jit_array_len(a: VALUE) -> ::std::os::raw::c_long; pub fn rb_set_cfp_pc(cfp: *mut rb_control_frame_struct, pc: *const VALUE); pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE); pub fn rb_jit_shape_too_complex_p(shape_id: shape_id_t) -> bool; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bccd27fc39dcbe..73b7cca8d08cdd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -270,7 +270,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum Const { Value(VALUE), CBool(bool), @@ -460,7 +460,7 @@ pub enum SideExitReason { GuardType(Type), GuardTypeNot(Type), GuardShape(ShapeId), - GuardBitEquals(VALUE), + GuardBitEquals(Const), PatchPoint(Invariant), CalleeSideExit, ObjToStringFallback, @@ -583,6 +583,8 @@ pub enum Insn { /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + /// Return the length of the array as a C `long` ([`types::CInt64`]) + ArrayLength { array: InsnId }, HashAref { hash: InsnId, key: InsnId, state: InsnId }, HashDup { val: InsnId, state: InsnId }, @@ -768,8 +770,8 @@ pub enum Insn { /// Side-exit if val doesn't have the expected type. GuardType { val: InsnId, guard_type: Type, state: InsnId }, GuardTypeNot { val: InsnId, guard_type: Type, state: InsnId }, - /// Side-exit if val is not the expected VALUE. - GuardBitEquals { val: InsnId, expected: VALUE, state: InsnId }, + /// Side-exit if val is not the expected Const. + GuardBitEquals { val: InsnId, expected: Const, state: InsnId }, /// Side-exit if val doesn't have the expected shape. GuardShape { val: InsnId, shape: ShapeId, state: InsnId }, /// Side-exit if the block param has been modified or the block handler for the frame @@ -899,6 +901,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayArefFixnum { array, index, .. } => { write!(f, "ArrayArefFixnum {array}, {index}") } + Insn::ArrayLength { array } => { + write!(f, "ArrayLength {array}") + } Insn::NewHash { elements, .. } => { write!(f, "NewHash")?; let mut prefix = " "; @@ -1604,6 +1609,7 @@ impl Function { &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, + &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, @@ -1691,6 +1697,7 @@ impl Function { Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, Insn::ArrayArefFixnum { .. } => types::BasicObject, + Insn::ArrayLength { .. } => types::CInt64, Insn::HashAref { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, Insn::HashDup { .. } => types::HashExact, @@ -1703,7 +1710,7 @@ impl Function { &Insn::CCallVariadic { return_type, .. } => return_type, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), Insn::GuardTypeNot { .. } => types::BasicObject, - Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_value(*expected)), + Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), Insn::FixnumAdd { .. } => types::Fixnum, Insn::FixnumSub { .. } => types::Fixnum, @@ -2803,6 +2810,9 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::ArrayLength { array } => { + worklist.push_back(array); + } &Insn::HashAref { hash, key, state } => { worklist.push_back(hash); worklist.push_back(key); @@ -4428,6 +4438,31 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { state.stack_push(result); } } + YARVINSN_expandarray => { + let num = get_arg(pc, 0).as_u64(); + let flag = get_arg(pc, 1).as_u64(); + if flag != 0 { + // We don't (yet) handle 0x01 (rest args), 0x02 (post args), or 0x04 + // (reverse?) + // + // Unhandled opcode; side-exit into the interpreter + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode) }); + break; // End the block + } + let val = state.stack_pop()?; + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let array = fun.push_insn(block, Insn::GuardType { val, guard_type: types::ArrayExact, state: exit_id, }); + let length = fun.push_insn(block, Insn::ArrayLength { array }); + fun.push_insn(block, Insn::GuardBitEquals { val: length, expected: Const::CInt64(num as i64), state: exit_id }); + for i in (0..num).rev() { + // TODO(max): Add a short-cut path for long indices into an array where the + // index is known to be in-bounds + let index = fun.push_insn(block, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(i.try_into().unwrap())) }); + let element = fun.push_insn(block, Insn::ArrayArefFixnum { array, index }); + state.stack_push(element); + } + } _ => { // Unhandled opcode; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); @@ -7914,6 +7949,98 @@ mod tests { Return v17 "); } + + #[test] + fn test_expandarray_no_splat() { + eval(r#" + def test(o) + a, b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + v20:ArrayExact = GuardType v13, ArrayExact + v21:CInt64 = ArrayLength v20 + v22:CInt64[2] = GuardBitEquals v21, CInt64(2) + v23:Fixnum[1] = Const Value(1) + v24:BasicObject = ArrayArefFixnum v20, v23 + v25:Fixnum[0] = Const Value(0) + v26:BasicObject = ArrayArefFixnum v20, v25 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_expandarray_splat() { + eval(r#" + def test(o) + a, *b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } + + #[test] + fn test_expandarray_splat_post() { + eval(r#" + def test(o) + a, *b, c = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject): + EntryPoint JIT(0) + v10:NilClass = Const Value(nil) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:NilClass, v17:NilClass, v18:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } } #[cfg(test)] diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index ffde7e458d3b28..f24161657e8ec7 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -238,6 +238,23 @@ impl Type { } } + pub fn from_const(val: Const) -> Type { + match val { + Const::Value(v) => Self::from_value(v), + Const::CBool(v) => Self::from_cbool(v), + Const::CInt8(v) => Self::from_cint(types::CInt8, v as i64), + Const::CInt16(v) => Self::from_cint(types::CInt16, v as i64), + Const::CInt32(v) => Self::from_cint(types::CInt32, v as i64), + Const::CInt64(v) => Self::from_cint(types::CInt64, v as i64), + Const::CUInt8(v) => Self::from_cint(types::CUInt8, v as i64), + Const::CUInt16(v) => Self::from_cint(types::CUInt16, v as i64), + Const::CUInt32(v) => Self::from_cint(types::CUInt32, v as i64), + Const::CUInt64(v) => Self::from_cint(types::CUInt64, v as i64), + Const::CPtr(v) => Self::from_cptr(v), + Const::CDouble(v) => Self::from_double(v), + } + } + pub fn from_profiled_type(val: ProfiledType) -> Type { if val.is_fixnum() { types::Fixnum } else if val.is_flonum() { types::Flonum } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 843806e5be5e89..50f6e61f5c242e 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -140,6 +140,7 @@ make_counters! { exit_guard_type_failure, exit_guard_type_not_failure, exit_guard_bit_equals_failure, + exit_guard_int_equals_failure, exit_guard_shape_failure, exit_patchpoint_bop_redefined, exit_patchpoint_method_redefined, From 33f1af6779a22ab84633b43d42dcf273a7e3bbe9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 20 Oct 2025 12:22:53 -0400 Subject: [PATCH 0419/2435] ZJIT: Remove idx from hir::Insn::Param (#14872) It turns out that we don't use it anywhere. --- zjit/src/codegen.rs | 6 +++--- zjit/src/hir.rs | 21 +++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index ea4a7ecc7c8c21..1f04e61dbc9757 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -264,9 +264,9 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul asm.write_label(label); // Compile all parameters - for &insn_id in block.params() { + for (idx, &insn_id) in block.params().enumerate() { match function.find(insn_id) { - Insn::Param { idx } => { + Insn::Param => { jit.opnds[insn_id.0] = Some(gen_param(&mut asm, idx)); }, insn => unreachable!("Non-param insn found in block.params: {insn:?}"), @@ -367,7 +367,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), - Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), + Insn::Param => unreachable!("block.insns should not have Insn::Param"), Insn::Snapshot { .. } => return Ok(()), // we don't need to do anything for this instruction at the moment Insn::Jump(branch) => no_output!(gen_jump(jit, asm, branch)), Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 73b7cca8d08cdd..370ed568579e0c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -552,7 +552,7 @@ pub enum SendFallbackReason { pub enum Insn { Const { val: Const }, /// SSA block parameter. Also used for function parameters in the function's entry block. - Param { idx: usize }, + Param, StringCopy { val: InsnId, chilled: bool, state: InsnId }, StringIntern { val: InsnId, state: InsnId }, @@ -888,7 +888,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self.inner { Insn::Const { val } => { write!(f, "Const {}", val.print(self.ptr_map)) } - Insn::Param { idx } => { write!(f, "Param {idx}") } + Insn::Param => { write!(f, "Param") } Insn::NewArray { elements, .. } => { write!(f, "NewArray")?; let mut prefix = " "; @@ -3661,18 +3661,15 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } // Load basic block params first - let self_param = fun.push_insn(block, Insn::Param { idx: SELF_PARAM_IDX }); + let self_param = fun.push_insn(block, Insn::Param); let mut state = { let mut result = FrameState::new(iseq); - let mut idx = 1; let local_size = if insn_idx == 0 { num_locals(iseq) } else { incoming_state.locals.len() }; for _ in 0..local_size { - result.locals.push(fun.push_insn(block, Insn::Param { idx })); - idx += 1; + result.locals.push(fun.push_insn(block, Insn::Param)); } for _ in incoming_state.stack { - result.stack.push(fun.push_insn(block, Insn::Param { idx })); - idx += 1; + result.stack.push(fun.push_insn(block, Insn::Param)); } result }; @@ -4581,11 +4578,11 @@ fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId) -> (Ins let iseq = fun.iseq; let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); - let self_param = fun.push_insn(jit_entry_block, Insn::Param { idx: SELF_PARAM_IDX }); + let self_param = fun.push_insn(jit_entry_block, Insn::Param); let mut entry_state = FrameState::new(iseq); for local_idx in 0..num_locals(iseq) { if local_idx < param_size { - entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Param { idx: local_idx + 1 })); // +1 for self + entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Param)); } else { entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Const { val: Const::Value(Qnil) })); } @@ -4935,7 +4932,7 @@ mod infer_tests { function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } }); let v1 = function.push_insn(entry, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(4)) }); function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![v1] })); - let param = function.push_insn(exit, Insn::Param { idx: 0 }); + let param = function.push_insn(exit, Insn::Param); crate::cruby::with_rubyvm(|| { function.infer_types(); }); @@ -4954,7 +4951,7 @@ mod infer_tests { function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } }); let v1 = function.push_insn(entry, Insn::Const { val: Const::Value(Qfalse) }); function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![v1] })); - let param = function.push_insn(exit, Insn::Param { idx: 0 }); + let param = function.push_insn(exit, Insn::Param); crate::cruby::with_rubyvm(|| { function.infer_types(); assert_bit_equal(function.type_of(param), types::TrueClass.union(types::FalseClass)); From e047cea2804c987c32450279a9b8fd78655cfa9d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 20 Oct 2025 21:10:25 +0100 Subject: [PATCH 0420/2435] ZJIT: Optimize send with block into CCallWithFrame (#14863) Since `Send` has a block iseq, I updated `CCallWithFrame` to take an optional `blockiseq` as well, and then generate `CCallWithFrame` for `Send` when the condition is right. ## Stats `liquid-render` Benchmark | Metric | Before | After | Change | |----------------------|--------------------|--------------------|--------------------- | | send_no_profiles | 3,209,418 (34.1%) | 4,119 (0.1%) | -3,205,299 (-99.9%) | | dynamic_send_count | 9,410,758 (23.1%) | 6,459,678 (15.9%) | -2,951,080 (-31.4%) | | optimized_send_count | 31,269,388 (76.9%) | 34,220,474 (84.1%) | +2,951,086 (+9.4%) | `lobsters` Benchmark | Metric | Before | After | Change | |----------------------|------------|------------|---------------------| | send_no_profiles | 10,769,052 | 2,902,865 | -7,866,187 (-73.0%) | | dynamic_send_count | 45,673,185 | 42,880,160 | -2,793,025 (-6.1%) | | optimized_send_count | 75,142,407 | 78,378,514 | +3,236,107 (+4.3%) | ### `liquid-render` Before
``` Average of last 22, non-warmup iters: 262ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (96.9% of total 10,370,809): Kernel#respond_to?: 5,069,204 (48.9%) Hash#key?: 2,394,488 (23.1%) Set#include?: 778,429 ( 7.5%) String#===: 326,134 ( 3.1%) String#<<: 203,231 ( 2.0%) Integer#<<: 166,768 ( 1.6%) Kernel#is_a?: 164,272 ( 1.6%) Kernel#format: 124,262 ( 1.2%) Integer#/: 124,262 ( 1.2%) Array#<<: 115,325 ( 1.1%) Regexp.last_match: 94,862 ( 0.9%) Hash#[]=: 88,485 ( 0.9%) String#start_with?: 55,933 ( 0.5%) CGI::EscapeExt#escapeHTML: 55,471 ( 0.5%) Array#shift: 55,298 ( 0.5%) Regexp#===: 48,928 ( 0.5%) String#=~: 48,477 ( 0.5%) Array#unshift: 47,331 ( 0.5%) String#empty?: 42,870 ( 0.4%) Array#push: 41,215 ( 0.4%) Top-20 not annotated C methods (97.1% of total 10,394,421): Kernel#respond_to?: 5,069,204 (48.8%) Hash#key?: 2,394,488 (23.0%) Set#include?: 778,429 ( 7.5%) String#===: 326,134 ( 3.1%) Kernel#is_a?: 208,664 ( 2.0%) String#<<: 203,231 ( 2.0%) Integer#<<: 166,768 ( 1.6%) Integer#/: 124,262 ( 1.2%) Kernel#format: 124,262 ( 1.2%) Array#<<: 115,325 ( 1.1%) Regexp.last_match: 94,862 ( 0.9%) Hash#[]=: 88,485 ( 0.9%) String#start_with?: 55,933 ( 0.5%) CGI::EscapeExt#escapeHTML: 55,471 ( 0.5%) Array#shift: 55,298 ( 0.5%) Regexp#===: 48,928 ( 0.5%) String#=~: 48,477 ( 0.5%) Array#unshift: 47,331 ( 0.5%) String#empty?: 42,870 ( 0.4%) Array#push: 41,215 ( 0.4%) Top-2 not optimized method types for send (100.0% of total 2,382): cfunc: 1,196 (50.2%) iseq: 1,186 (49.8%) Top-4 not optimized method types for send_without_block (100.0% of total 2,561,006): iseq: 2,442,091 (95.4%) optimized: 118,882 ( 4.6%) alias: 20 ( 0.0%) null: 13 ( 0.0%) Top-9 not optimized instructions (100.0% of total 685,128): invokeblock: 227,376 (33.2%) opt_neq: 166,471 (24.3%) opt_and: 166,471 (24.3%) opt_eq: 66,721 ( 9.7%) invokesuper: 39,363 ( 5.7%) opt_le: 16,278 ( 2.4%) opt_minus: 1,574 ( 0.2%) opt_send_without_block: 772 ( 0.1%) opt_or: 102 ( 0.0%) Top-8 send fallback reasons (100.0% of total 9,410,758): send_no_profiles: 3,209,418 (34.1%) send_without_block_polymorphic: 2,858,558 (30.4%) send_without_block_not_optimized_method_type: 2,561,006 (27.2%) not_optimized_instruction: 685,128 ( 7.3%) send_without_block_no_profiles: 91,913 ( 1.0%) send_not_optimized_method_type: 2,382 ( 0.0%) obj_to_string_not_string: 2,352 ( 0.0%) send_without_block_cfunc_array_variadic: 1 ( 0.0%) Top-3 unhandled YARV insns (100.0% of total 83,682): getclassvariable: 83,431 (99.7%) once: 137 ( 0.2%) getconstant: 114 ( 0.1%) Top-3 compile error reasons (100.0% of total 5,431,910): register_spill_on_alloc: 4,665,393 (85.9%) exception_handler: 766,347 (14.1%) register_spill_on_ccall: 170 ( 0.0%) Top-11 side exit reasons (100.0% of total 14,635,508): compile_error: 5,431,910 (37.1%) guard_shape_failure: 3,436,341 (23.5%) guard_type_failure: 2,545,791 (17.4%) unhandled_splat: 2,162,907 (14.8%) unhandled_kwarg: 952,568 ( 6.5%) unhandled_yarv_insn: 83,682 ( 0.6%) unhandled_hir_insn: 19,112 ( 0.1%) patchpoint_stable_constant_names: 1,608 ( 0.0%) obj_to_string_fallback: 902 ( 0.0%) patchpoint_method_redefined: 599 ( 0.0%) block_param_proxy_not_iseq_or_ifunc: 88 ( 0.0%) send_count: 40,680,153 dynamic_send_count: 9,410,758 (23.1%) optimized_send_count: 31,269,395 (76.9%) iseq_optimized_send_count: 13,886,902 (34.1%) inline_cfunc_optimized_send_count: 7,011,684 (17.2%) non_variadic_cfunc_optimized_send_count: 4,670,333 (11.5%) variadic_cfunc_optimized_send_count: 5,700,476 (14.0%) dynamic_getivar_count: 1,144,613 dynamic_setivar_count: 950,830 compiled_iseq_count: 402 failed_iseq_count: 48 compile_time: 976ms profile_time: 3,223ms gc_time: 22ms invalidation_time: 0ms vm_write_pc_count: 37,744,491 vm_write_sp_count: 37,511,865 vm_write_locals_count: 37,511,865 vm_write_stack_count: 37,511,865 vm_write_to_parent_iseq_local_count: 558,177 vm_read_from_parent_iseq_local_count: 14,317,032 code_region_bytes: 2,211,840 side_exit_count: 14,635,508 total_insn_count: 476,097,972 vm_insn_count: 253,795,154 zjit_insn_count: 222,302,818 ratio_in_zjit: 46.7% ```
### `liquid-render` After
``` Average of last 21, non-warmup iters: 272ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (96.8% of total 10,093,966): Kernel#respond_to?: 4,932,224 (48.9%) Hash#key?: 2,329,928 (23.1%) Set#include?: 757,389 ( 7.5%) String#===: 317,494 ( 3.1%) String#<<: 197,831 ( 2.0%) Integer#<<: 162,268 ( 1.6%) Kernel#is_a?: 159,892 ( 1.6%) Kernel#format: 120,902 ( 1.2%) Integer#/: 120,902 ( 1.2%) Array#<<: 112,225 ( 1.1%) Regexp.last_match: 92,382 ( 0.9%) Hash#[]=: 86,145 ( 0.9%) String#start_with?: 54,953 ( 0.5%) Array#shift: 54,038 ( 0.5%) CGI::EscapeExt#escapeHTML: 53,971 ( 0.5%) Regexp#===: 47,848 ( 0.5%) String#=~: 47,237 ( 0.5%) Array#unshift: 46,051 ( 0.5%) String#empty?: 41,750 ( 0.4%) Array#push: 40,115 ( 0.4%) Top-20 not annotated C methods (97.1% of total 10,116,938): Kernel#respond_to?: 4,932,224 (48.8%) Hash#key?: 2,329,928 (23.0%) Set#include?: 757,389 ( 7.5%) String#===: 317,494 ( 3.1%) Kernel#is_a?: 203,084 ( 2.0%) String#<<: 197,831 ( 2.0%) Integer#<<: 162,268 ( 1.6%) Kernel#format: 120,902 ( 1.2%) Integer#/: 120,902 ( 1.2%) Array#<<: 112,225 ( 1.1%) Regexp.last_match: 92,382 ( 0.9%) Hash#[]=: 86,145 ( 0.9%) String#start_with?: 54,953 ( 0.5%) Array#shift: 54,038 ( 0.5%) CGI::EscapeExt#escapeHTML: 53,971 ( 0.5%) Regexp#===: 47,848 ( 0.5%) String#=~: 47,237 ( 0.5%) Array#unshift: 46,051 ( 0.5%) String#empty?: 41,750 ( 0.4%) Array#push: 40,115 ( 0.4%) Top-2 not optimized method types for send (100.0% of total 182,938): iseq: 178,414 (97.5%) cfunc: 4,524 ( 2.5%) Top-4 not optimized method types for send_without_block (100.0% of total 2,492,246): iseq: 2,376,511 (95.4%) optimized: 115,702 ( 4.6%) alias: 20 ( 0.0%) null: 13 ( 0.0%) Top-9 not optimized instructions (100.0% of total 667,727): invokeblock: 221,375 (33.2%) opt_neq: 161,971 (24.3%) opt_and: 161,971 (24.3%) opt_eq: 64,921 ( 9.7%) invokesuper: 39,243 ( 5.9%) opt_le: 15,838 ( 2.4%) opt_minus: 1,534 ( 0.2%) opt_send_without_block: 772 ( 0.1%) opt_or: 102 ( 0.0%) Top-9 send fallback reasons (100.0% of total 6,287,956): send_without_block_polymorphic: 2,782,058 (44.2%) send_without_block_not_optimized_method_type: 2,492,246 (39.6%) not_optimized_instruction: 667,727 (10.6%) send_not_optimized_method_type: 182,938 ( 2.9%) send_without_block_no_profiles: 89,613 ( 1.4%) send_polymorphic: 66,962 ( 1.1%) send_no_profiles: 4,059 ( 0.1%) obj_to_string_not_string: 2,352 ( 0.0%) send_without_block_cfunc_array_variadic: 1 ( 0.0%) Top-3 unhandled YARV insns (100.0% of total 81,482): getclassvariable: 81,231 (99.7%) once: 137 ( 0.2%) getconstant: 114 ( 0.1%) Top-3 compile error reasons (100.0% of total 5,286,310): register_spill_on_alloc: 4,540,413 (85.9%) exception_handler: 745,727 (14.1%) register_spill_on_ccall: 170 ( 0.0%) Top-12 side exit reasons (100.0% of total 14,244,881): compile_error: 5,286,310 (37.1%) guard_shape_failure: 3,346,873 (23.5%) guard_type_failure: 2,477,071 (17.4%) unhandled_splat: 2,104,447 (14.8%) unhandled_kwarg: 926,828 ( 6.5%) unhandled_yarv_insn: 81,482 ( 0.6%) unhandled_hir_insn: 18,672 ( 0.1%) patchpoint_stable_constant_names: 1,608 ( 0.0%) obj_to_string_fallback: 902 ( 0.0%) patchpoint_method_redefined: 599 ( 0.0%) block_param_proxy_not_iseq_or_ifunc: 88 ( 0.0%) interrupt: 1 ( 0.0%) send_count: 39,591,410 dynamic_send_count: 6,287,956 (15.9%) optimized_send_count: 33,303,454 (84.1%) iseq_optimized_send_count: 13,514,283 (34.1%) inline_cfunc_optimized_send_count: 6,823,745 (17.2%) non_variadic_cfunc_optimized_send_count: 7,417,432 (18.7%) variadic_cfunc_optimized_send_count: 5,547,994 (14.0%) dynamic_getivar_count: 1,110,647 dynamic_setivar_count: 927,309 compiled_iseq_count: 403 failed_iseq_count: 48 compile_time: 968ms profile_time: 3,547ms gc_time: 22ms invalidation_time: 0ms vm_write_pc_count: 36,735,108 vm_write_sp_count: 36,508,262 vm_write_locals_count: 36,508,262 vm_write_stack_count: 36,508,262 vm_write_to_parent_iseq_local_count: 543,097 vm_read_from_parent_iseq_local_count: 13,930,672 code_region_bytes: 2,228,224 side_exit_count: 14,244,881 total_insn_count: 463,357,969 vm_insn_count: 247,003,727 zjit_insn_count: 216,354,242 ratio_in_zjit: 46.7% ```
### `lobsters` Before
``` Average of last 10, non-warmup iters: 898ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (61.3% of total 19,495,906): String#<<: 1,764,437 ( 9.1%) Kernel#is_a?: 1,615,120 ( 8.3%) Hash#[]=: 1,159,455 ( 5.9%) Regexp#match?: 777,496 ( 4.0%) String#empty?: 722,953 ( 3.7%) Hash#key?: 685,258 ( 3.5%) Kernel#respond_to?: 602,017 ( 3.1%) TrueClass#===: 447,671 ( 2.3%) FalseClass#===: 439,276 ( 2.3%) Array#include?: 426,758 ( 2.2%) Kernel#block_given?: 405,271 ( 2.1%) Hash#fetch: 382,302 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) String#start_with?: 353,793 ( 1.8%) Kernel#kind_of?: 340,341 ( 1.7%) Kernel#dup: 328,162 ( 1.7%) String.new: 306,667 ( 1.6%) String#==: 287,549 ( 1.5%) BasicObject#!=: 284,642 ( 1.5%) String#length: 256,070 ( 1.3%) Top-20 not annotated C methods (62.4% of total 19,796,172): Kernel#is_a?: 1,993,676 (10.1%) String#<<: 1,764,437 ( 8.9%) Hash#[]=: 1,159,634 ( 5.9%) Regexp#match?: 777,496 ( 3.9%) String#empty?: 738,030 ( 3.7%) Hash#key?: 685,258 ( 3.5%) Kernel#respond_to?: 602,017 ( 3.0%) TrueClass#===: 447,671 ( 2.3%) FalseClass#===: 439,276 ( 2.2%) Array#include?: 426,758 ( 2.2%) Kernel#block_given?: 425,813 ( 2.2%) Hash#fetch: 382,302 ( 1.9%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) String#start_with?: 353,793 ( 1.8%) Kernel#kind_of?: 340,375 ( 1.7%) Kernel#dup: 328,169 ( 1.7%) String.new: 306,667 ( 1.5%) String#==: 293,520 ( 1.5%) BasicObject#!=: 284,825 ( 1.4%) String#length: 256,070 ( 1.3%) Top-2 not optimized method types for send (100.0% of total 115,007): cfunc: 76,172 (66.2%) iseq: 38,835 (33.8%) Top-6 not optimized method types for send_without_block (100.0% of total 8,003,641): iseq: 3,999,211 (50.0%) bmethod: 1,750,271 (21.9%) optimized: 1,653,426 (20.7%) alias: 591,342 ( 7.4%) null: 8,174 ( 0.1%) cfunc: 1,217 ( 0.0%) Top-13 not optimized instructions (100.0% of total 7,590,826): invokesuper: 4,335,446 (57.1%) invokeblock: 1,329,215 (17.5%) sendforward: 841,463 (11.1%) opt_eq: 810,614 (10.7%) opt_plus: 141,773 ( 1.9%) opt_minus: 52,270 ( 0.7%) opt_send_without_block: 43,248 ( 0.6%) opt_neq: 15,047 ( 0.2%) opt_mult: 13,824 ( 0.2%) opt_or: 7,451 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-9 send fallback reasons (100.0% of total 45,673,212): send_without_block_polymorphic: 17,390,335 (38.1%) send_no_profiles: 10,769,053 (23.6%) send_without_block_not_optimized_method_type: 8,003,641 (17.5%) not_optimized_instruction: 7,590,826 (16.6%) send_without_block_no_profiles: 1,757,109 ( 3.8%) send_not_optimized_method_type: 115,007 ( 0.3%) send_without_block_cfunc_array_variadic: 31,149 ( 0.1%) obj_to_string_not_string: 15,518 ( 0.0%) send_without_block_direct_too_many_args: 574 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 1,242,228): expandarray: 622,203 (50.1%) checkkeyword: 316,111 (25.4%) getclassvariable: 120,540 ( 9.7%) getblockparam: 88,480 ( 7.1%) invokesuperforward: 78,842 ( 6.3%) opt_duparray_send: 14,149 ( 1.1%) getconstant: 1,588 ( 0.1%) checkmatch: 288 ( 0.0%) once: 27 ( 0.0%) Top-3 compile error reasons (100.0% of total 6,769,693): register_spill_on_alloc: 6,188,305 (91.4%) register_spill_on_ccall: 347,108 ( 5.1%) exception_handler: 234,280 ( 3.5%) Top-17 side exit reasons (100.0% of total 20,142,827): compile_error: 6,769,693 (33.6%) guard_type_failure: 5,169,050 (25.7%) guard_shape_failure: 3,726,362 (18.5%) unhandled_yarv_insn: 1,242,228 ( 6.2%) block_param_proxy_not_iseq_or_ifunc: 984,480 ( 4.9%) unhandled_kwarg: 800,154 ( 4.0%) unknown_newarray_send: 539,317 ( 2.7%) patchpoint_stable_constant_names: 340,283 ( 1.7%) unhandled_splat: 229,440 ( 1.1%) unhandled_hir_insn: 147,351 ( 0.7%) patchpoint_no_singleton_class: 128,856 ( 0.6%) patchpoint_method_redefined: 32,718 ( 0.2%) block_param_proxy_modified: 25,274 ( 0.1%) patchpoint_no_ep_escape: 7,559 ( 0.0%) obj_to_string_fallback: 24 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 16 ( 0.0%) send_count: 120,815,640 dynamic_send_count: 45,673,212 (37.8%) optimized_send_count: 75,142,428 (62.2%) iseq_optimized_send_count: 32,188,039 (26.6%) inline_cfunc_optimized_send_count: 23,458,483 (19.4%) non_variadic_cfunc_optimized_send_count: 14,809,797 (12.3%) variadic_cfunc_optimized_send_count: 4,686,109 ( 3.9%) dynamic_getivar_count: 13,023,437 dynamic_setivar_count: 12,311,158 compiled_iseq_count: 4,806 failed_iseq_count: 466 compile_time: 8,943ms profile_time: 99ms gc_time: 45ms invalidation_time: 239ms vm_write_pc_count: 113,652,291 vm_write_sp_count: 111,209,623 vm_write_locals_count: 111,209,623 vm_write_stack_count: 111,209,623 vm_write_to_parent_iseq_local_count: 516,800 vm_read_from_parent_iseq_local_count: 11,225,587 code_region_bytes: 22,609,920 side_exit_count: 20,142,827 total_insn_count: 926,088,942 vm_insn_count: 297,636,255 zjit_insn_count: 628,452,687 ratio_in_zjit: 67.9% ```
### `lobsters` After
``` Average of last 10, non-warmup iters: 919ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (61.3% of total 19,495,868): String#<<: 1,764,437 ( 9.1%) Kernel#is_a?: 1,615,110 ( 8.3%) Hash#[]=: 1,159,455 ( 5.9%) Regexp#match?: 777,496 ( 4.0%) String#empty?: 722,953 ( 3.7%) Hash#key?: 685,258 ( 3.5%) Kernel#respond_to?: 602,016 ( 3.1%) TrueClass#===: 447,671 ( 2.3%) FalseClass#===: 439,276 ( 2.3%) Array#include?: 426,758 ( 2.2%) Kernel#block_given?: 405,271 ( 2.1%) Hash#fetch: 382,302 ( 2.0%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) String#start_with?: 353,793 ( 1.8%) Kernel#kind_of?: 340,341 ( 1.7%) Kernel#dup: 328,162 ( 1.7%) String.new: 306,667 ( 1.6%) String#==: 287,545 ( 1.5%) BasicObject#!=: 284,642 ( 1.5%) String#length: 256,070 ( 1.3%) Top-20 not annotated C methods (62.4% of total 19,796,134): Kernel#is_a?: 1,993,666 (10.1%) String#<<: 1,764,437 ( 8.9%) Hash#[]=: 1,159,634 ( 5.9%) Regexp#match?: 777,496 ( 3.9%) String#empty?: 738,030 ( 3.7%) Hash#key?: 685,258 ( 3.5%) Kernel#respond_to?: 602,016 ( 3.0%) TrueClass#===: 447,671 ( 2.3%) FalseClass#===: 439,276 ( 2.2%) Array#include?: 426,758 ( 2.2%) Kernel#block_given?: 425,813 ( 2.2%) Hash#fetch: 382,302 ( 1.9%) ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%) String#start_with?: 353,793 ( 1.8%) Kernel#kind_of?: 340,375 ( 1.7%) Kernel#dup: 328,169 ( 1.7%) String.new: 306,667 ( 1.5%) String#==: 293,516 ( 1.5%) BasicObject#!=: 284,825 ( 1.4%) String#length: 256,070 ( 1.3%) Top-4 not optimized method types for send (100.0% of total 4,749,678): iseq: 2,563,391 (54.0%) cfunc: 2,064,888 (43.5%) alias: 118,577 ( 2.5%) null: 2,822 ( 0.1%) Top-6 not optimized method types for send_without_block (100.0% of total 8,003,641): iseq: 3,999,211 (50.0%) bmethod: 1,750,271 (21.9%) optimized: 1,653,426 (20.7%) alias: 591,342 ( 7.4%) null: 8,174 ( 0.1%) cfunc: 1,217 ( 0.0%) Top-13 not optimized instructions (100.0% of total 7,590,818): invokesuper: 4,335,442 (57.1%) invokeblock: 1,329,215 (17.5%) sendforward: 841,463 (11.1%) opt_eq: 810,610 (10.7%) opt_plus: 141,773 ( 1.9%) opt_minus: 52,270 ( 0.7%) opt_send_without_block: 43,248 ( 0.6%) opt_neq: 15,047 ( 0.2%) opt_mult: 13,824 ( 0.2%) opt_or: 7,451 ( 0.1%) opt_lt: 348 ( 0.0%) opt_ge: 91 ( 0.0%) opt_gt: 36 ( 0.0%) Top-10 send fallback reasons (100.0% of total 43,152,037): send_without_block_polymorphic: 17,390,322 (40.3%) send_without_block_not_optimized_method_type: 8,003,641 (18.5%) not_optimized_instruction: 7,590,818 (17.6%) send_not_optimized_method_type: 4,749,678 (11.0%) send_no_profiles: 2,893,666 ( 6.7%) send_without_block_no_profiles: 1,757,109 ( 4.1%) send_polymorphic: 719,562 ( 1.7%) send_without_block_cfunc_array_variadic: 31,149 ( 0.1%) obj_to_string_not_string: 15,518 ( 0.0%) send_without_block_direct_too_many_args: 574 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 1,242,215): expandarray: 622,203 (50.1%) checkkeyword: 316,111 (25.4%) getclassvariable: 120,540 ( 9.7%) getblockparam: 88,467 ( 7.1%) invokesuperforward: 78,842 ( 6.3%) opt_duparray_send: 14,149 ( 1.1%) getconstant: 1,588 ( 0.1%) checkmatch: 288 ( 0.0%) once: 27 ( 0.0%) Top-3 compile error reasons (100.0% of total 6,769,688): register_spill_on_alloc: 6,188,305 (91.4%) register_spill_on_ccall: 347,108 ( 5.1%) exception_handler: 234,275 ( 3.5%) Top-17 side exit reasons (100.0% of total 20,144,372): compile_error: 6,769,688 (33.6%) guard_type_failure: 5,169,204 (25.7%) guard_shape_failure: 3,726,374 (18.5%) unhandled_yarv_insn: 1,242,215 ( 6.2%) block_param_proxy_not_iseq_or_ifunc: 984,480 ( 4.9%) unhandled_kwarg: 800,154 ( 4.0%) unknown_newarray_send: 539,317 ( 2.7%) patchpoint_stable_constant_names: 340,283 ( 1.7%) unhandled_splat: 229,440 ( 1.1%) unhandled_hir_insn: 147,351 ( 0.7%) patchpoint_no_singleton_class: 130,252 ( 0.6%) patchpoint_method_redefined: 32,716 ( 0.2%) block_param_proxy_modified: 25,274 ( 0.1%) patchpoint_no_ep_escape: 7,559 ( 0.0%) obj_to_string_fallback: 24 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) interrupt: 19 ( 0.0%) send_count: 120,812,030 dynamic_send_count: 43,152,037 (35.7%) optimized_send_count: 77,659,993 (64.3%) iseq_optimized_send_count: 32,187,900 (26.6%) inline_cfunc_optimized_send_count: 23,458,491 (19.4%) non_variadic_cfunc_optimized_send_count: 17,327,499 (14.3%) variadic_cfunc_optimized_send_count: 4,686,103 ( 3.9%) dynamic_getivar_count: 13,023,424 dynamic_setivar_count: 12,310,991 compiled_iseq_count: 4,806 failed_iseq_count: 466 compile_time: 9,012ms profile_time: 104ms gc_time: 44ms invalidation_time: 239ms vm_write_pc_count: 113,648,665 vm_write_sp_count: 111,205,997 vm_write_locals_count: 111,205,997 vm_write_stack_count: 111,205,997 vm_write_to_parent_iseq_local_count: 516,800 vm_read_from_parent_iseq_local_count: 11,225,587 code_region_bytes: 23,052,288 side_exit_count: 20,144,372 total_insn_count: 926,090,214 vm_insn_count: 297,647,811 zjit_insn_count: 628,442,403 ratio_in_zjit: 67.9% ```
--- insns.def | 1 + zjit/src/codegen.rs | 43 +++++- zjit/src/cruby_bindings.inc.rs | 53 +++---- zjit/src/hir.rs | 259 +++++++++++++++++++++++++++++++-- zjit/src/profile.rs | 2 +- 5 files changed, 315 insertions(+), 43 deletions(-) diff --git a/insns.def b/insns.def index 8225d1cceaf97e..ce358da28575ed 100644 --- a/insns.def +++ b/insns.def @@ -846,6 +846,7 @@ send (CALL_DATA cd, ISEQ blockiseq) (...) (VALUE val) +// attr bool zjit_profile = true; // attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci); // attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci); { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1f04e61dbc9757..87e0ed907a044d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -411,7 +411,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), - Insn::CCallWithFrame { cfunc, args, cme, state, .. } => gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, &function.frame_state(*state)), + Insn::CCallWithFrame { cfunc, args, cme, state, blockiseq, .. } => + gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } @@ -673,20 +674,36 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian } /// Generate code for a C function call that pushes a frame -fn gen_ccall_with_frame(jit: &mut JITState, asm: &mut Assembler, cfunc: *const u8, args: Vec, cme: *const rb_callable_method_entry_t, state: &FrameState) -> lir::Opnd { +fn gen_ccall_with_frame( + jit: &mut JITState, + asm: &mut Assembler, + cfunc: *const u8, + args: Vec, + cme: *const rb_callable_method_entry_t, + blockiseq: Option, + state: &FrameState, +) -> lir::Opnd { gen_incr_counter(asm, Counter::non_variadic_cfunc_optimized_send_count); - gen_prepare_non_leaf_call(jit, asm, state); + let caller_stack_size = state.stack_size() - args.len(); + + // Can't use gen_prepare_non_leaf_call() because we need to adjust the SP + // to account for the receiver and arguments (and block arguments if any) + gen_prepare_call_with_gc(asm, state, false); + gen_save_sp(asm, caller_stack_size); + gen_spill_stack(jit, asm, state); + gen_spill_locals(jit, asm, state); gen_push_frame(asm, args.len(), state, ControlFrame { recv: args[0], iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, + block_iseq: blockiseq, }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -738,6 +755,7 @@ fn gen_ccall_variadic( iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, + block_iseq: None, }); asm_comment!(asm, "switch to new SP register"); @@ -1130,6 +1148,7 @@ fn gen_send_without_block_direct( iseq: Some(iseq), cme, frame_type: VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, + block_iseq: None, }); asm_comment!(asm, "switch to new SP register"); @@ -1719,6 +1738,7 @@ struct ControlFrame { iseq: Option, cme: *const rb_callable_method_entry_t, frame_type: u32, + block_iseq: Option, } /// Compile an interpreter frame @@ -1735,9 +1755,20 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C }; let ep_offset = state.stack().len() as i32 + local_size - argc as i32 + VM_ENV_DATA_SIZE as i32 - 1; asm.store(Opnd::mem(64, SP, (ep_offset - 2) * SIZEOF_VALUE_I32), VALUE::from(frame.cme).into()); + + let block_handler_opnd = if let Some(block_iseq) = frame.block_iseq { + // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). + // VM_CFP_TO_CAPTURED_BLOCK does &cfp->self, rb_captured_block->code.iseq aliases + // with cfp->block_code. + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); + let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); + asm.or(cfp_self_addr, Opnd::Imm(1)) + } else { + VM_BLOCK_HANDLER_NONE.into() + }; + // ep[-1]: block_handler or prev EP - // block_handler is not supported for now - asm.store(Opnd::mem(64, SP, (ep_offset - 1) * SIZEOF_VALUE_I32), VM_BLOCK_HANDLER_NONE.into()); + asm.store(Opnd::mem(64, SP, (ep_offset - 1) * SIZEOF_VALUE_I32), block_handler_opnd); // ep[0]: ENV_FLAGS asm.store(Opnd::mem(64, SP, ep_offset * SIZEOF_VALUE_I32), frame.frame_type.into()); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index c67e229a8009e7..af604661b299b3 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -680,32 +680,33 @@ pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 215; pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 216; pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 217; pub const YARVINSN_zjit_getinstancevariable: ruby_vminsn_type = 218; -pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 219; -pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 220; -pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 221; -pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 222; -pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 223; -pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 224; -pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 225; -pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 226; -pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 227; -pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 228; -pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 229; -pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 230; -pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 231; -pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 232; -pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 233; -pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 236; -pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 237; -pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 238; -pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 239; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 240; -pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 241; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 242; -pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 243; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 244; +pub const YARVINSN_zjit_send: ruby_vminsn_type = 219; +pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 220; +pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 221; +pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 222; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 223; +pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 224; +pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 225; +pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 226; +pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 227; +pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 228; +pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 229; +pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 230; +pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 231; +pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 232; +pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 244; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 245; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 370ed568579e0c..1f77f38dc8872b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -668,6 +668,7 @@ pub enum Insn { state: InsnId, return_type: Type, elidable: bool, + blockiseq: Option, }, /// Call a variadic C function with signature: func(int argc, VALUE *argv, VALUE recv) @@ -1063,11 +1064,14 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) }, - Insn::CCallWithFrame { cfunc, args, name, .. } => { + Insn::CCallWithFrame { cfunc, args, name, blockiseq, .. } => { write!(f, "CCallWithFrame {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } + if let Some(blockiseq) = blockiseq { + write!(f, ", block={:p}", self.ptr_map.map_ptr(blockiseq))?; + } Ok(()) }, Insn::CCallVariadic { cfunc, recv, args, name, .. } => { @@ -1598,7 +1602,17 @@ impl Function { &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, - &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type, elidable }, + &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable, blockiseq } => CCallWithFrame { + cd, + cfunc, + args: find_vec!(args), + cme, + name, + state: find!(state), + return_type, + elidable, + blockiseq, + }, &CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable } => CCallVariadic { cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable }, @@ -2134,7 +2148,7 @@ impl Function { } } // This doesn't actually optimize Send yet, just replaces the fallback reason to be more precise. - // TODO: Optimize Send + // The actual optimization is done in reduce_send_to_ccall. Insn::Send { recv, cd, state, .. } => { let frame_state = self.frame_state(state); let klass = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { @@ -2338,8 +2352,111 @@ impl Function { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); } - // Try to reduce one SendWithoutBlock to a CCall - fn reduce_to_ccall( + // Try to reduce a Send insn to a CCallWithFrame + fn reduce_send_to_ccall( + fun: &mut Function, + block: BlockId, + self_type: Type, + send: Insn, + send_insn_id: InsnId, + ) -> Result<(), ()> { + let Insn::Send { mut recv, cd, blockiseq, mut args, state, .. } = send else { + return Err(()); + }; + + let call_info = unsafe { (*cd).ci }; + let argc = unsafe { vm_ci_argc(call_info) }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + + // If we have info about the class of the receiver + let (recv_class, profiled_type) = if let Some(class) = self_type.runtime_exact_ruby_class() { + (class, None) + } else { + let iseq_insn_idx = fun.frame_state(state).insn_idx; + let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) }; + (recv_type.class(), Some(recv_type)) + }; + + // Do method lookup + let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; + if method.is_null() { + return Err(()); + } + + // Filter for C methods + let def_type = unsafe { get_cme_def_type(method) }; + if def_type != VM_METHOD_TYPE_CFUNC { + return Err(()); + } + + // Find the `argc` (arity) of the C method, which describes the parameters it expects + let cfunc = unsafe { get_cme_def_body_cfunc(method) }; + let cfunc_argc = unsafe { get_mct_argc(cfunc) }; + match cfunc_argc { + 0.. => { + // (self, arg0, arg1, ..., argc) form + // + // Bail on argc mismatch + if argc != cfunc_argc as u32 { + return Err(()); + } + + let ci_flags = unsafe { vm_ci_flag(call_info) }; + + // When seeing &block argument, fall back to dynamic dispatch for now + // TODO: Support block forwarding + if ci_flags & VM_CALL_ARGS_BLOCKARG != 0 { + return Err(()); + } + + // Commit to the replacement. Put PatchPoint. + gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); + } + + if let Some(profiled_type) = profiled_type { + // Guard receiver class + recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); + } + + let blockiseq = if blockiseq.is_null() { None } else { Some(blockiseq) }; + + // Emit a call + let cfunc = unsafe { get_mct_func(cfunc) }.cast(); + let mut cfunc_args = vec![recv]; + cfunc_args.append(&mut args); + + let ccall = fun.push_insn(block, Insn::CCallWithFrame { + cd, + cfunc, + args: cfunc_args, + cme: method, + name: method_id, + state, + return_type: types::BasicObject, + elidable: false, + blockiseq, + }); + fun.make_equal_to(send_insn_id, ccall); + return Ok(()); + } + // Variadic method + -1 => { + // func(int argc, VALUE *argv, VALUE recv) + return Err(()); + } + -2 => { + // (self, args_ruby_array) + return Err(()); + } + _ => unreachable!("unknown cfunc kind: argc={argc}") + } + } + + // Try to reduce a SendWithoutBlock insn to a CCall/CCallWithFrame + fn reduce_send_without_block_to_ccall( fun: &mut Function, block: BlockId, self_type: Type, @@ -2440,7 +2557,17 @@ impl Function { if get_option!(stats) { count_not_inlined_cfunc(fun, block, method); } - let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state, return_type, elidable }); + let ccall = fun.push_insn(block, Insn::CCallWithFrame { + cd, + cfunc, + args: cfunc_args, + cme: method, + name: method_id, + state, + return_type, + elidable, + blockiseq: None, + }); fun.make_equal_to(send_insn_id, ccall); } @@ -2555,11 +2682,21 @@ impl Function { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); assert!(self.blocks[block.0].insns.is_empty()); for insn_id in old_insns { - if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) { - let recv_type = self.type_of(recv); - if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() { - continue; + let send = self.find(insn_id); + match send { + send @ Insn::SendWithoutBlock { recv, .. } => { + let recv_type = self.type_of(recv); + if reduce_send_without_block_to_ccall(self, block, recv_type, send, insn_id).is_ok() { + continue; + } + } + send @ Insn::Send { recv, .. } => { + let recv_type = self.type_of(recv); + if reduce_send_to_ccall(self, block, recv_type, send, insn_id).is_ok() { + continue; + } } + _ => {} } self.push_insn_id(block, insn_id); } @@ -12583,6 +12720,108 @@ mod opt_tests { "); } + #[test] + fn test_optimize_send_with_block() { + eval(r#" + def test = [1, 2, 3].map { |x| x * 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v23:BasicObject = CCallWithFrame map@0x1040, v12, block=0x1048 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_do_not_optimize_send_variadic_with_block() { + eval(r#" + def test = [1, 2, 3].index { |x| x == 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + v14:BasicObject = Send v12, 0x1008, :index + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_do_not_optimize_send_with_block_forwarding() { + eval(r#" + def test(&block) = [].map(&block) + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = NewArray + GuardBlockParamProxy l0 + v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v19:BasicObject = Send v14, 0x1008, :map, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_do_not_optimize_send_to_iseq_method_with_block() { + eval(r#" + def foo + yield 1 + end + + def test = foo {} + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :foo + CheckInterrupts + Return v11 + "); + } + #[test] fn test_inline_attr_reader_constant() { eval(" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e935ec9731f383..a6c837df5a48ff 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -83,7 +83,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_length => profile_operands(profiler, profile, 1), YARVINSN_opt_size => profile_operands(profiler, profile, 1), YARVINSN_opt_succ => profile_operands(profiler, profile, 1), - YARVINSN_opt_send_without_block => { + YARVINSN_opt_send_without_block | YARVINSN_send => { let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr(); let argc = unsafe { vm_ci_argc((*cd).ci) }; // Profile all the arguments and self (+1). From 6e9f7974df5608dd74b5a107658ac944ec05f8d0 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Sep 2025 16:30:41 -0400 Subject: [PATCH 0421/2435] [DOC] Create doc/contributing/concurrency_guide.md This guide is for those that want to contribute to ruby but don't understand where they need to use locks or other concurrency mechanisms. It teaches them how to use these locks safely and what is prohibited in certain circumstances. --- doc/contributing/concurrency_guide.md | 154 ++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 doc/contributing/concurrency_guide.md diff --git a/doc/contributing/concurrency_guide.md b/doc/contributing/concurrency_guide.md new file mode 100644 index 00000000000000..1fb58f7203ad8f --- /dev/null +++ b/doc/contributing/concurrency_guide.md @@ -0,0 +1,154 @@ +# Concurrency Guide + +This is a guide to thinking about concurrency in the cruby source code, whether that's contributing to Ruby +by writing C or by contributing to one of the JITs. This does not touch on native extensions, only the core +language. It will go over: + +* What needs synchronizing? +* How to use the VM lock, and what you can and can't do when you've acquired this lock. +* What you can and can't do when you've acquired other native locks. +* The difference between the VM lock and the GVL. +* What a VM barrier is and when to use it. +* The lock ordering of some important locks. +* How ruby interrupt handling works. +* The timer thread and what it's responsible for. + +## What needs synchronizing? + +Before ractors, only one ruby thread could run at once. That didn't mean you could forget about concurrency issues, though. The timer thread +is a native thread that interacts with other ruby threads and changes some VM internals, so if these changes can be done in parallel by both the timer +thread and a ruby thread, they need to be synchronized. + +When you add ractors to the mix, it gets more complicated. However, ractors allow you to forget about synchronization for non-shareable objects because +they aren't used across ractors. Only one ruby thread can touch the object at once. For shareable objects, they are deeply frozen so there isn't any +mutation on the objects themselves. However, something like reading/writing constants across ractors does need to be synchronized. In this case, ruby threads need to +see a consistent view of the VM. If publishing the update takes 2 steps or even two separate instructions, like in this case, synchronization is required. + +Most synchronization is to protect VM internals. These internals include structures for the thread scheduler on each ractor, the global ractor scheduler, the +coordination between ruby threads and ractors, global tables (for `fstrings`, encodings, symbols and global vars), etc. Anything that can be mutated by a ractor +that can also be read or mutated by another ractor at the same time requires proper synchronization. + +## The VM Lock + +There's only one VM lock and it is for critical sections that can only be entered by one ractor at a time. +Without ractors, the VM lock is useless. It does not stop all ractors from running, as ractors can run +without trying to acquire this lock. If you're updating global (shared) data between ractors and aren't using +atomics, you need to use a lock and this is a convenient one to use. Unlike other locks, you can allocate ruby-managed +memory with it held. When you take the VM lock, there are things you can and can't do during your critical section: + +You can (as long as no other locks are also held before the VM lock): + +* Create ruby objects, call `ruby_xmalloc`, etc. + +You can't: + +* Context switch to another ruby thread or ractor. This is important, as many things can cause ruby-level context switches including: + + * Calling any ruby method through, for example, `rb_funcall`. If you execute ruby code, a context switch could happen. + This also applies to ruby methods defined in C, as they can be redefined in Ruby. Things that call ruby methods such as + `rb_obj_respond_to` are also disallowed. + + * Calling `rb_raise`. This will call `initialize` on the new exception object. With the VM lock + held, nothing you call should be able to raise an exception. `NoMemoryError` is allowed, however. + + * Calling `rb_nogvl` or a ruby-level mechanism that can context switch like `rb_mutex_lock`. + + * Enter any blocking operation managed by ruby. This will context switch to another ruby thread using `rb_nogvl` or + something equivalent. A blocking operation is one that blocks the thread's progress, such as `sleep` or `IO#read`. + +Internally, the VM lock is the `vm->ractor.sync.lock`. + +You need to be on a ruby thread to take the VM lock. You also can't take it inside any functions that could be called during sweeping, as MMTK sweeps +on another thread and you need a valid `ec` to grab the lock. For this same reason (among others), you can't take it from the timer thread either. + +## Other Locks + +All native locks that aren't the VM lock share a more strict set of rules for what's allowed during the critical section. By native locks, we mean +anything that uses `rb_native_mutex_lock`. Some important locks include the `interrupt_lock`, the ractor scheduling lock (protects global scheduling data structures), +the thread scheduling lock (local to each ractor, protects per-ractor scheduling data structures) and the ractor lock (local to each ractor, protects ractor data structures). + +When you acquire one of these locks, + +You can: + +* Allocate memory though non-ruby allocation such as raw `malloc` or the standard library. But be careful, some functions like `strdup` use +ruby allocation through the use of macros! + +* Use `ccan` lists, as they don't allocate. + +* Do the usual things like set variables or struct fields, manipulate linked lists, signal condition variables etc. + +You can't: + +* Allocate ruby-managed memory. This includes creating ruby objects or using `ruby_xmalloc` or `st_insert`. The reason this +is disallowed is if that allocation causes a GC, then all other ruby threads must join a VM barrier as soon as possible +(when they next check interrupts or acquire the VM lock). This is so that no other ractors are running during GC. If a ruby thread +is waiting (blocked) on this same native lock, it can't join the barrier and a deadlock occurs because the barrier will never finish. + +* Raise exceptions. You also can't use `EC_JUMP_TAG` if it jumps out of the critical section. + +* Context switch. See the `VM Lock` section for more info. + +## Difference Between VM Lock and GVL + +The VM Lock is a particular lock in the source code. There is only one VM Lock. The GVL, on the other hand, is more of a combination of locks. +It is "acquired" when a ruby thread is about to run or is running. Since many ruby threads can run at the same time if they're in different ractors, +there are many GVLs (1 per `SNT` + 1 for the main ractor). It can no longer be thought of as a "Global VM Lock" like it once was before ractors. + +## VM Barriers + +Sometimes, taking the VM Lock isn't enough and you need a guarantee that all ractors have stopped. This happens when running `GC`, for instance. +To get a barrier, you take the VM Lock and call `rb_vm_barrier()`. For the duration that the VM lock is held, no other ractors will be running. It's not used +often as taking a barrier slows ractor performance down considerably, but it's useful to know about and is sometimes the only solution. + +## Lock Orderings + +It's a good idea to not hold more than 2 locks at once on the same thread. Locking multiple locks can introduce deadlocks, so do it with care. When locking +multiple locks at once, follow an ordering that is consistent across the program, otherwise you can introduce deadlocks. Here are the orderings of some important locks: + +* VM lock before ractor_sched_lock +* thread_sched_lock before ractor_sched_lock +* interrupt_lock before timer_th.waiting_lock +* timer_th.waiting_lock before ractor_sched_lock + +These orderings are subject to change, so check the source if you're not sure. On top of this: + +* During each `ubf` (unblock) function, the VM lock can be taken around it in some circumstances. This happens during VM shutdown, for example. +See the "Interrupt Handling" section for more details. + +## Ruby Interrupt Handling + +When the VM runs ruby code, ruby's threads intermittently check ruby-level interrupts. These software interrupts +are for various things in ruby and they can be set by other ruby threads or the timer thread. + +* Ruby threads check when they should give up their timeslice. The native thread switches to another ruby thread when their time is up. +* The timer thread sends a "trap" interrupt to the main thread if any ruby-level signal handlers are pending. +* Ruby threads can have other ruby threads run tasks for them by sending them an interrupt. For instance, ractors send +the main thread an interrupt when they need to `require` a file so that it's done on the main thread. They wait for the +main thread's result. +* During VM shutdown, a "terminate" interrupt is sent to all ractor main threads top stop them asap. +* When calling `Thread#raise`, the caller sends an interrupt to that thread telling it which exception to raise. +* Unlocking a mutex sends the next waiter (if any) an interrupt telling it to grab the lock. +* Signalling or broadcasting on a condition variable tells the waiter(s) to wake up. + +This isn't a complete list. + +When sending an interrupt to a ruby thread, the ruby thread can be blocked. For example, it could be in the middle of a `TCPSocket#read` call. If so, +the receiving thread's `ubf` (unblock function) gets called from the thread (ruby thread or timer thread) that sent the interrupt. +Each ruby thread has a `ubf` that is set when it enters a blocking operation and is unset after returning from it. By default, this `ubf` function sends a +`SIGVTALRM` to the receiving thread to try to unblock it from the kernel so it can check its interrupts. There are other `ubfs` that +aren't associated with a syscall, such as when calling `Ractor#join` or `sleep`. All `ubfs` are called with the `interrupt_lock` held, +so take that into account when using locks inside `ubfs`. + +Remember, `ubfs` can be called from the timer thread so you cannot assume an `ec` inside them. The `ec` (execution context) is only set on ruby threads. + +## The Timer Thread + +The timer thread has a few functions. They are: + +* Send interrupts to ruby threads that have run for their whole timeslice. +* Wake up M:N ruby threads (threads in non-main ractors) blocked on IO or after a specified timeout. This +uses `kqueue` or `epoll`, depending on the OS, to receive IO events on behalf of the threads. +* Continue calling the `SIGVTARLM` signal if a thread is still blocked on a syscall after the first `ubf` call. +* Signal native threads (`SNT`) waiting on a ractor if there are ractors waiting in the global run queue. +* Create more `SNT`s if some are blocked, like on IO or on `Ractor#join`. From 17368234bfd0b37c58a4b694d764d42e2cad8880 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 20 Oct 2025 17:30:48 -0400 Subject: [PATCH 0422/2435] ZJIT: Implement codegen for FixnumMod (#14857) This is mostly to see what happens to the loops-times benchmark. --- depend | 2 ++ jit.c | 7 +++++++ yjit.c | 6 ------ yjit/bindgen/src/main.rs | 2 +- yjit/src/cruby.rs | 2 +- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/src/codegen.rs | 9 ++++++++- zjit/src/cruby.rs | 2 ++ zjit/src/hir.rs | 1 + zjit/src/stats.rs | 2 ++ 10 files changed, 25 insertions(+), 10 deletions(-) diff --git a/depend b/depend index 63e73a56390056..1f9f0c31eba695 100644 --- a/depend +++ b/depend @@ -7382,8 +7382,10 @@ jit.$(OBJEXT): $(CCAN_DIR)/str/str.h jit.$(OBJEXT): $(hdrdir)/ruby/ruby.h jit.$(OBJEXT): $(top_srcdir)/internal/array.h jit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +jit.$(OBJEXT): $(top_srcdir)/internal/bits.h jit.$(OBJEXT): $(top_srcdir)/internal/class.h jit.$(OBJEXT): $(top_srcdir)/internal/compilers.h +jit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h jit.$(OBJEXT): $(top_srcdir)/internal/gc.h jit.$(OBJEXT): $(top_srcdir)/internal/imemo.h jit.$(OBJEXT): $(top_srcdir)/internal/namespace.h diff --git a/jit.c b/jit.c index 0b491f0481d875..b7cb05d1c34efd 100644 --- a/jit.c +++ b/jit.c @@ -14,6 +14,7 @@ #include "iseq.h" #include "internal/gc.h" #include "vm_sync.h" +#include "internal/fixnum.h" // Field offsets for the RObject struct enum robject_offsets { @@ -720,3 +721,9 @@ rb_jit_icache_invalidate(void *start, void *end) #error No instruction cache clear available with this compiler on Aarch64! #endif } + +VALUE +rb_jit_fix_mod_fix(VALUE recv, VALUE obj) +{ + return rb_fix_mod_fix(recv, obj); +} diff --git a/yjit.c b/yjit.c index 598fe5716704d0..d0ab367b1c7bb1 100644 --- a/yjit.c +++ b/yjit.c @@ -332,12 +332,6 @@ rb_yjit_fix_div_fix(VALUE recv, VALUE obj) return rb_fix_div_fix(recv, obj); } -VALUE -rb_yjit_fix_mod_fix(VALUE recv, VALUE obj) -{ - return rb_fix_mod_fix(recv, obj); -} - // Return non-zero when `obj` is an array and its last item is a // `ruby2_keywords` hash. We don't support this kind of splat. size_t diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 0d4d57e0695941..2b4f48d73ec4bd 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -367,7 +367,7 @@ fn main() { .allowlist_function("rb_yarv_ary_entry_internal") .allowlist_function("rb_yjit_ruby2_keywords_splat_p") .allowlist_function("rb_yjit_fix_div_fix") - .allowlist_function("rb_yjit_fix_mod_fix") + .allowlist_function("rb_jit_fix_mod_fix") .allowlist_function("rb_FL_TEST") .allowlist_function("rb_FL_TEST_RAW") .allowlist_function("rb_RB_TYPE_P") diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 36baecd5358031..0d9e3b74dad874 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -199,7 +199,7 @@ pub use rb_get_call_data_ci as get_call_data_ci; pub use rb_yarv_str_eql_internal as rb_str_eql_internal; pub use rb_yarv_ary_entry_internal as rb_ary_entry_internal; pub use rb_yjit_fix_div_fix as rb_fix_div_fix; -pub use rb_yjit_fix_mod_fix as rb_fix_mod_fix; +pub use rb_jit_fix_mod_fix as rb_fix_mod_fix; pub use rb_FL_TEST as FL_TEST; pub use rb_FL_TEST_RAW as FL_TEST_RAW; pub use rb_RB_TYPE_P as RB_TYPE_P; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0a14a699284268..74661e7ade9bf8 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1142,7 +1142,6 @@ extern "C" { pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE; pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; pub fn rb_yjit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; - pub fn rb_yjit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yjit_ruby2_keywords_splat_p(obj: VALUE) -> usize; pub fn rb_yjit_splat_varg_checks( sp: *mut VALUE, @@ -1275,4 +1274,5 @@ extern "C" { start: *mut ::std::os::raw::c_void, end: *mut ::std::os::raw::c_void, ); + pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 87e0ed907a044d..f7b335f1bfce89 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -398,6 +398,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumGe { left, right } => gen_fixnum_ge(asm, opnd!(left), opnd!(right)), Insn::FixnumAnd { left, right } => gen_fixnum_and(asm, opnd!(left), opnd!(right)), Insn::FixnumOr { left, right } => gen_fixnum_or(asm, opnd!(left), opnd!(right)), + &Insn::FixnumMod { left, right, state } => gen_fixnum_mod(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), @@ -447,7 +448,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } - | &Insn::FixnumMod { state, .. } | &Insn::Throw { state, .. } => return Err(state), }; @@ -1460,6 +1460,13 @@ fn gen_fixnum_or(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir: asm.or(left, right) } +fn gen_fixnum_mod(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { + // Check for left % 0, which raises ZeroDivisionError + asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); + asm.je(side_exit(jit, state, FixnumModByZero)); + asm_ccall!(asm, rb_fix_mod_fix, left, right) +} + // Compile val == nil fn gen_isnil(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { asm.cmp(val, Qnil.into()); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 5f4eac1db5ed9e..a84e408861fc54 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -134,6 +134,7 @@ unsafe extern "C" { pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; + pub fn rb_jit_fix_mod_fix(x: VALUE, y: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE; pub fn rb_vm_concat_to_array(ary1: VALUE, ary2st: VALUE) -> VALUE; @@ -219,6 +220,7 @@ pub use rb_vm_ci_kwarg as vm_ci_kwarg; pub use rb_METHOD_ENTRY_VISI as METHOD_ENTRY_VISI; pub use rb_RCLASS_ORIGIN as RCLASS_ORIGIN; pub use rb_vm_get_special_object as vm_get_special_object; +pub use rb_jit_fix_mod_fix as rb_fix_mod_fix; /// Helper so we can get a Rust string for insn_name() pub fn insn_name(opcode: usize) -> String { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1f77f38dc8872b..7083a082fba1a8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -468,6 +468,7 @@ pub enum SideExitReason { BlockParamProxyModified, BlockParamProxyNotIseqOrIfunc, StackOverflow, + FixnumModByZero, } #[derive(Debug, Clone, Copy)] diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 50f6e61f5c242e..33f29fb3aaed22 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -137,6 +137,7 @@ make_counters! { exit_fixnum_add_overflow, exit_fixnum_sub_overflow, exit_fixnum_mult_overflow, + exit_fixnum_mod_by_zero, exit_guard_type_failure, exit_guard_type_not_failure, exit_guard_bit_equals_failure, @@ -332,6 +333,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { FixnumAddOverflow => exit_fixnum_add_overflow, FixnumSubOverflow => exit_fixnum_sub_overflow, FixnumMultOverflow => exit_fixnum_mult_overflow, + FixnumModByZero => exit_fixnum_mod_by_zero, GuardType(_) => exit_guard_type_failure, GuardTypeNot(_) => exit_guard_type_not_failure, GuardBitEquals(_) => exit_guard_bit_equals_failure, From e930bd3eae441df07ded74d97dd76c0d700fbdb3 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 19 Oct 2025 21:44:05 +0100 Subject: [PATCH 0423/2435] [DOC] Tweaks for String#rstrip! --- string.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 47de66eca6f3df..d24435ba590ae4 100644 --- a/string.c +++ b/string.c @@ -10409,10 +10409,12 @@ rstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) * call-seq: * rstrip! -> self or nil * - * Like String#rstrip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * Like String#rstrip, except that: * - * Related: String#lstrip!, String#strip!. + * - Performs stripping in +self+ (not in a copy of +self+). + * - Returns +self+ if any characters are stripped, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From 3b87e76cc6a2ab7f6e625953b0954edfb9e5c647 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 19 Oct 2025 21:20:29 +0100 Subject: [PATCH 0424/2435] [DOC] Tweaks for String#rpartition --- doc/string/rpartition.rdoc | 57 +++++++++++++++++++++++++++----------- string.c | 2 +- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/doc/string/rpartition.rdoc b/doc/string/rpartition.rdoc index d24106fb9fc09b..56eb9ddb95cab3 100644 --- a/doc/string/rpartition.rdoc +++ b/doc/string/rpartition.rdoc @@ -1,24 +1,49 @@ Returns a 3-element array of substrings of +self+. -Matches a pattern against +self+, scanning backwards from the end. -The pattern is: +Searches +self+ for a match of +pattern+, seeking the _last_ match. -- +string_or_regexp+ itself, if it is a Regexp. -- Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string. +If +pattern+ is not matched, returns the array: -If the pattern is matched, returns pre-match, last-match, post-match: + ["", "", self.dup] - 'hello'.rpartition('l') # => ["hel", "l", "o"] - 'hello'.rpartition('ll') # => ["he", "ll", "o"] - 'hello'.rpartition('h') # => ["", "h", "ello"] - 'hello'.rpartition('o') # => ["hell", "o", ""] - 'hello'.rpartition(/l+/) # => ["hel", "l", "o"] - 'hello'.rpartition('') # => ["hello", "", ""] - 'тест'.rpartition('т') # => ["тес", "т", ""] - 'こんにちは'.rpartition('に') # => ["こん", "に", "ちは"] +If +pattern+ is matched, returns the array: -If the pattern is not matched, returns two empty strings and a copy of +self+: + [pre_match, last_match, post_match] - 'hello'.rpartition('x') # => ["", "", "hello"] +where: -Related: String#partition, String#split. +- +last_match+ is the last-found matching substring. +- +pre_match+ and +post_match+ are the preceding and following substrings. + +The pattern used is: + +- +pattern+ itself, if it is a Regexp. +- Regexp.quote(pattern), if +pattern+ is a string. + +Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. + +If +pattern+ is a Regexp, searches for the last matching substring +(also setting {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): + + 'hello'.rpartition(/l/) # => ["hel", "l", "o"] + 'hello'.rpartition(/ll/) # => ["he", "ll", "o"] + 'hello'.rpartition(/h/) # => ["", "h", "ello"] + 'hello'.rpartition(/o/) # => ["hell", "o", ""] + 'hello'.rpartition(//) # => ["hello", "", ""] + 'hello'.rpartition(/x/) # => ["", "", "hello"] + 'тест'.rpartition(/т/) # => ["тес", "т", ""] + 'こんにちは'.rpartition(/に/) # => ["こん", "に", "ちは"] + +If +pattern+ is not a Regexp, converts it to a string (if it is not already one), +then searches for the last matching substring +(and does _not_ set {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): + + 'hello'.rpartition('l') # => ["hel", "l", "o"] + 'hello'.rpartition('ll') # => ["he", "ll", "o"] + 'hello'.rpartition('h') # => ["", "h", "ello"] + 'hello'.rpartition('o') # => ["hell", "o", ""] + 'hello'.rpartition('') # => ["hello", "", ""] + 'тест'.rpartition('т') # => ["тес", "т", ""] + 'こんにちは'.rpartition('に') # => ["こん", "に", "ちは"] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/string.c b/string.c index d24435ba590ae4..65b9d407f7b914 100644 --- a/string.c +++ b/string.c @@ -11196,7 +11196,7 @@ rb_str_partition(VALUE str, VALUE sep) /* * call-seq: - * rpartition(sep) -> [head, match, tail] + * rpartition(pattern) -> [pre_match, last_match, post_match] * * :include: doc/string/rpartition.rdoc * From caff9b9065750ec74e4fc6b3d222e7ab780fb1e0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Oct 2025 15:44:03 -0500 Subject: [PATCH 0425/2435] [DOC] Tweaks for String#rstrip (#14881) --- string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index 65b9d407f7b914..b7b89eb6dd78e7 100644 --- a/string.c +++ b/string.c @@ -10443,7 +10443,7 @@ rb_str_rstrip_bang(VALUE str) * call-seq: * rstrip -> new_string * - * Returns a copy of the receiver with trailing whitespace removed; + * Returns a copy of +self+ with trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " @@ -10451,7 +10451,7 @@ rb_str_rstrip_bang(VALUE str) * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.rstrip # => "\u0000\t\n\v\f\r abc" * - * Related: String#lstrip, String#strip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From 42c040978157d7964b2d162f9b20805e010e3a99 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Oct 2025 15:44:41 -0500 Subject: [PATCH 0426/2435] [DOC] Tweaks for String#scan (#14884) --- doc/string/scan.rdoc | 36 ++++++++++++++++++++++++++++++++++++ string.c | 36 +++--------------------------------- 2 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 doc/string/scan.rdoc diff --git a/doc/string/scan.rdoc b/doc/string/scan.rdoc new file mode 100644 index 00000000000000..cbede5280f5c49 --- /dev/null +++ b/doc/string/scan.rdoc @@ -0,0 +1,36 @@ +Matches a pattern against +self+: + +- If +pattern+ is a Regexp, the pattern used is +pattern+ itself. +- If +pattern+ is a string, the pattern used is Regexp.quote(pattern). + +Generates a collection of matching results +and updates {regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: + +- If the pattern contains no groups, each result is a matched substring. +- If the pattern contains groups, each result is an array + containing a matched substring for each group. + +With no block given, returns an array of the results: + + 'cruel world'.scan(/\w+/) # => ["cruel", "world"] + 'cruel world'.scan(/.../) # => ["cru", "el ", "wor"] + 'cruel world'.scan(/(...)/) # => [["cru"], ["el "], ["wor"]] + 'cruel world'.scan(/(..)(..)/) # => [["cr", "ue"], ["l ", "wo"]] + 'тест'.scan(/../) # => ["те", "ст"] + 'こんにちは'.scan(/../) # => ["こん", "にち"] + 'abracadabra'.scan('ab') # => ["ab", "ab"] + 'abracadabra'.scan('nosuch') # => [] + +With a block given, calls the block with each result; returns +self+: + + 'cruel world'.scan(/\w+/) {|w| p w } + # => "cruel" + # => "world" + 'cruel world'.scan(/(.)(.)/) {|x, y| p [x, y] } + # => ["c", "r"] + # => ["u", "e"] + # => ["l", " "] + # => ["w", "o"] + # => ["r", "l"] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/string.c b/string.c index b7b89eb6dd78e7..8788c0ca67f803 100644 --- a/string.c +++ b/string.c @@ -10596,40 +10596,10 @@ scan_once(VALUE str, VALUE pat, long *start, int set_backref_str) /* * call-seq: - * scan(string_or_regexp) -> array - * scan(string_or_regexp) {|matches| ... } -> self + * scan(pattern) -> array_of_results + * scan(pattern) {|result| ... } -> self * - * Matches a pattern against +self+; the pattern is: - * - * - +string_or_regexp+ itself, if it is a Regexp. - * - Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string. - * - * Iterates through +self+, generating a collection of matching results: - * - * - If the pattern contains no groups, each result is the - * matched string, $&. - * - If the pattern contains groups, each result is an array - * containing one entry per group. - * - * With no block given, returns an array of the results: - * - * s = 'cruel world' - * s.scan(/\w+/) # => ["cruel", "world"] - * s.scan(/.../) # => ["cru", "el ", "wor"] - * s.scan(/(...)/) # => [["cru"], ["el "], ["wor"]] - * s.scan(/(..)(..)/) # => [["cr", "ue"], ["l ", "wo"]] - * - * With a block given, calls the block with each result; returns +self+: - * - * s.scan(/\w+/) {|w| print "<<#{w}>> " } - * print "\n" - * s.scan(/(.)(.)/) {|x,y| print y, x } - * print "\n" - * - * Output: - * - * <> <> - * rceu lowlr + * :include: doc/string/scan.rdoc * */ From 862b42a52cd59ca406db40cf47e98fa107c97fd1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Oct 2025 15:46:40 -0500 Subject: [PATCH 0427/2435] [DOC] Tweaks for String#scrub! (#14893) --- string.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/string.c b/string.c index 8788c0ca67f803..344b38d255fde5 100644 --- a/string.c +++ b/string.c @@ -11927,11 +11927,15 @@ str_scrub(int argc, VALUE *argv, VALUE str) /* * call-seq: - * scrub! -> self - * scrub!(replacement_string = default_replacement) -> self - * scrub!{|bytes| ... } -> self + * scrub!(replacement_string = default_replacement_string) -> self + * scrub!{|sequence| ... } -> self * - * Like String#scrub, except that any replacements are made in +self+. + * Like String#scrub, except that: + * + * - Any replacements are made in +self+. + * - Returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * */ static VALUE From cb52809ca124cb627293a666e8cbb44e031eeec1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Oct 2025 15:48:11 -0500 Subject: [PATCH 0428/2435] [DOC] Tweaks for String#scrub (#14892) --- doc/string/scrub.rdoc | 27 ++++++++++++--------------- string.c | 4 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/doc/string/scrub.rdoc b/doc/string/scrub.rdoc index 1a5b1c79d07e22..5ace376cdbec85 100644 --- a/doc/string/scrub.rdoc +++ b/doc/string/scrub.rdoc @@ -1,25 +1,22 @@ Returns a copy of +self+ with each invalid byte sequence replaced by the given +replacement_string+. -With no block given and no argument, replaces each invalid sequence -with the default replacement string -("�" for a Unicode encoding, '?' otherwise): +With no block given, replaces each invalid sequence +with the given +default_replacement_string+ +(by default, "�" for a Unicode encoding, '?' otherwise): - s = "foo\x81\x81bar" - s.scrub # => "foo��bar" + "foo\x81\x81bar"scrub # => "foo��bar" + "foo\x81\x81bar".force_encoding('US-ASCII').scrub # => "foo??bar" + "foo\x81\x81bar".scrub('xyzzy') # => "fooxyzzyxyzzybar" -With no block given and argument +replacement_string+ given, -replaces each invalid sequence with that string: +With a block given, calls the block with each invalid sequence, +and replaces that sequence with the return value of the block: - "foo\x81\x81bar".scrub('xyzzy') # => "fooxyzzyxyzzybar" + "foo\x81\x81bar".scrub {|sequence| p sequence; 'XYZZY' } # => "fooXYZZYXYZZYbar" -With a block given, replaces each invalid sequence with the value -of the block: - - "foo\x81\x81bar".scrub {|bytes| p bytes; 'XYZZY' } - # => "fooXYZZYXYZZYbar" - -Output: +Output : "\x81" "\x81" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 344b38d255fde5..8f9d19fb54aad5 100644 --- a/string.c +++ b/string.c @@ -11911,8 +11911,8 @@ enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl, int cr) /* * call-seq: - * scrub(replacement_string = default_replacement) -> new_string - * scrub{|bytes| ... } -> new_string + * scrub(replacement_string = default_replacement_string) -> new_string + * scrub{|sequence| ... } -> new_string * * :include: doc/string/scrub.rdoc * From 193b299b8d1b09de9de695f6fd88314ccfbf884b Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 21 Oct 2025 19:16:00 +0100 Subject: [PATCH 0429/2435] =?UTF-8?q?[DOC]=20=C2=94Tweaks=20for=20String#s?= =?UTF-8?q?etbyte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- string.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/string.c b/string.c index 8f9d19fb54aad5..92541622633211 100644 --- a/string.c +++ b/string.c @@ -6667,13 +6667,14 @@ rb_str_getbyte(VALUE str, VALUE index) * call-seq: * setbyte(index, integer) -> integer * - * Sets the byte at zero-based +index+ to +integer+; returns +integer+: + * Sets the byte at zero-based offset +index+ to the value of the given +integer+; + * returns +integer+: * - * s = 'abcde' # => "abcde" - * s.setbyte(0, 98) # => 98 - * s # => "bbcde" + * s = 'xyzzy' + * s.setbyte(2, 129) # => 129 + * s # => "xy\x81zy" * - * Related: String#getbyte. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE rb_str_setbyte(VALUE str, VALUE index, VALUE value) From 35c2230734e65e8ab55aa718ca6ea02ca9622984 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sun, 19 Oct 2025 15:11:39 -0400 Subject: [PATCH 0430/2435] ZJIT: Fix binding to `INVALID_SHAPE_ID` under `-std=c99 -pedantic` ``` /src/jit.c:19:5: error: ISO C restricts enumerator values to range of 'int' (4294967295 is too large) [-Werror,-Wpedantic] 19 | RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID, | ^ ~~~~~~~~~~~~~~~~ ``` --- jit.c | 5 +++++ zjit.c | 4 ---- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 2 +- zjit/src/cruby_bindings.inc.rs | 3 +-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/jit.c b/jit.c index b7cb05d1c34efd..db3fe097efd5c5 100644 --- a/jit.c +++ b/jit.c @@ -22,6 +22,11 @@ enum robject_offsets { ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary), }; +// Manually bound in rust since this is out-of-range of `int`, +// so this can't be in a `enum`, and we avoid `static const` +// to avoid allocating storage for the constant. +const shape_id_t rb_invalid_shape_id = INVALID_SHAPE_ID; + unsigned int rb_iseq_encoded_size(const rb_iseq_t *iseq) { diff --git a/zjit.c b/zjit.c index e17abc1b37ff29..fac087a605f4d1 100644 --- a/zjit.c +++ b/zjit.c @@ -235,10 +235,6 @@ rb_zjit_print_exception(void) rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0)); } -enum zjit_exported_constants { - RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID, -}; - bool rb_zjit_singleton_class_p(VALUE klass) { diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index f13b61acf04609..60dcb7a69c5eca 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -100,6 +100,7 @@ fn main() { .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") + .allowlist_var("rb_invalid_shape_id") .allowlist_var("SHAPE_ID_NUM_BITS") .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") @@ -295,7 +296,6 @@ fn main() { .allowlist_function("rb_zjit_insn_leaf") .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") - .allowlist_type("zjit_exported_constants") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index a84e408861fc54..09a221b5027f68 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -273,7 +273,7 @@ pub type IseqPtr = *const rb_iseq_t; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct ShapeId(pub u32); -pub const INVALID_SHAPE_ID: ShapeId = ShapeId(RB_INVALID_SHAPE_ID); +pub const INVALID_SHAPE_ID: ShapeId = ShapeId(rb_invalid_shape_id); impl ShapeId { pub fn is_valid(self) -> bool { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index af604661b299b3..6f2ad37d3bab2e 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -730,11 +730,10 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const RB_INVALID_SHAPE_ID: zjit_exported_constants = 4294967295; -pub type zjit_exported_constants = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: robject_offsets = 16; pub const ROBJECT_OFFSET_AS_ARY: robject_offsets = 16; pub type robject_offsets = u32; +pub const rb_invalid_shape_id: shape_id_t = 4294967295; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; unsafe extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); From bb7f3d17edd29ee8ab5504866c5047fd73a78e64 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 20 Oct 2025 12:12:31 -0400 Subject: [PATCH 0431/2435] YJIT: ZJIT: Extract common bindings to jit.c and remove unnamed enums. The type name bindgen picks for anonymous enums creates desync issues on the bindgen CI checks. --- jit.c | 22 +- shape.h | 6 +- yjit.c | 20 -- yjit/bindgen/src/main.rs | 6 +- yjit/src/codegen.rs | 2 +- yjit/src/cruby_bindings.inc.rs | 17 +- zjit/bindgen/src/main.rs | 5 +- zjit/src/cruby_bindings.inc.rs | 372 ++++++++++++++++++++++++++++++++- 8 files changed, 406 insertions(+), 44 deletions(-) diff --git a/jit.c b/jit.c index db3fe097efd5c5..2ff38c28e2d6d3 100644 --- a/jit.c +++ b/jit.c @@ -16,10 +16,13 @@ #include "vm_sync.h" #include "internal/fixnum.h" -// Field offsets for the RObject struct -enum robject_offsets { +enum jit_bindgen_constants { + // Field offsets for the RObject struct ROBJECT_OFFSET_AS_HEAP_FIELDS = offsetof(struct RObject, as.heap.fields), ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary), + + // Field offsets for the RString struct + RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len) }; // Manually bound in rust since this is out-of-range of `int`, @@ -162,6 +165,21 @@ rb_get_def_original_id(const rb_method_definition_t *def) return def->original_id; } +VALUE +rb_get_def_bmethod_proc(rb_method_definition_t *def) +{ + RUBY_ASSERT(def->type == VM_METHOD_TYPE_BMETHOD); + return def->body.bmethod.proc; +} + +rb_proc_t * +rb_jit_get_proc_ptr(VALUE procv) +{ + rb_proc_t *proc; + GetProcPtr(procv, proc); + return proc; +} + int rb_get_mct_argc(const rb_method_cfunc_t *mct) { diff --git a/shape.h b/shape.h index fdc2b3ddd6ff80..a20da1baa55668 100644 --- a/shape.h +++ b/shape.h @@ -47,9 +47,9 @@ enum shape_id_fl_type { #undef RBIMPL_SHAPE_ID_FL }; -// This masks allows to check if a shape_id contains any ivar. -// It rely on ROOT_SHAPE_WITH_OBJ_ID==1. -enum { +// This mask allows to check if a shape_id contains any ivar. +// It relies on ROOT_SHAPE_WITH_OBJ_ID==1. +enum shape_id_mask { SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), }; diff --git a/yjit.c b/yjit.c index d0ab367b1c7bb1..807aec9e391172 100644 --- a/yjit.c +++ b/yjit.c @@ -38,11 +38,6 @@ #include -// Field offsets for the RString struct -enum rstring_offsets { - RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len) -}; - // We need size_t to have a known size to simplify code generation and FFI. // TODO(alan): check this in configure.ac to fail fast on 32 bit platforms. STATIC_ASSERT(64b_size_t, SIZE_MAX == UINT64_MAX); @@ -234,14 +229,6 @@ rb_iseq_set_yjit_payload(const rb_iseq_t *iseq, void *payload) iseq->body->yjit_payload = payload; } -rb_proc_t * -rb_yjit_get_proc_ptr(VALUE procv) -{ - rb_proc_t *proc; - GetProcPtr(procv, proc); - return proc; -} - // This is defined only as a named struct inside rb_iseq_constant_body. // By giving it a separate typedef, we make it nameable by rust-bindgen. // Bindgen's temp/anon name isn't guaranteed stable. @@ -249,13 +236,6 @@ typedef struct rb_iseq_param_keyword rb_seq_param_keyword_struct; ID rb_get_symbol_id(VALUE namep); -VALUE -rb_get_def_bmethod_proc(rb_method_definition_t *def) -{ - RUBY_ASSERT(def->type == VM_METHOD_TYPE_BMETHOD); - return def->body.bmethod.proc; -} - VALUE rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler) { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 2b4f48d73ec4bd..df287e1bf84a2e 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -91,7 +91,7 @@ fn main() { .allowlist_function("rb_yjit_shape_capacity") .allowlist_function("rb_yjit_shape_index") .allowlist_var("SHAPE_ID_NUM_BITS") - .allowlist_var("SHAPE_ID_HAS_IVAR_MASK") + .allowlist_type("shape_id_mask") .allowlist_function("rb_funcall") .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") @@ -265,7 +265,7 @@ fn main() { .allowlist_function("rb_RSTRING_PTR") .allowlist_function("rb_RSTRING_LEN") .allowlist_function("rb_ENCODING_GET") - .allowlist_function("rb_yjit_get_proc_ptr") + .allowlist_function("rb_jit_get_proc_ptr") .allowlist_function("rb_yjit_exit_locations_dict") .allowlist_function("rb_jit_icache_invalidate") .allowlist_function("rb_optimized_call") @@ -280,7 +280,7 @@ fn main() { .allowlist_function("rb_jit_vm_lock_then_barrier") .allowlist_function("rb_jit_vm_unlock") .allowlist_function("rb_jit_for_each_iseq") - .allowlist_type("robject_offsets") + .allowlist_type("jit_bindgen_constants") .allowlist_function("rb_vm_barrier") // Not sure why it's picking these up, but don't. diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index bf758a4f62bd21..231655826109ab 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7367,7 +7367,7 @@ fn gen_send_bmethod( ) -> Option { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; - let proc = unsafe { rb_yjit_get_proc_ptr(procv) }; + let proc = unsafe { rb_jit_get_proc_ptr(procv) }; let proc_block = unsafe { &(*proc).block }; if proc_block.type_ != block_type_iseq { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 74661e7ade9bf8..272586a79f3fb5 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -634,8 +634,8 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; pub type attr_index_t = u16; pub type shape_id_t = u32; -pub const SHAPE_ID_HAS_IVAR_MASK: _bindgen_ty_37 = 134742014; -pub type _bindgen_ty_37 = u32; +pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 134742014; +pub type shape_id_mask = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { pub index: u32, @@ -941,12 +941,11 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const RUBY_OFFSET_RSTRING_LEN: rstring_offsets = 16; -pub type rstring_offsets = u32; pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; -pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: robject_offsets = 16; -pub const ROBJECT_OFFSET_AS_ARY: robject_offsets = 16; -pub type robject_offsets = u32; +pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; +pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub type jit_bindgen_constants = u32; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); @@ -1122,9 +1121,7 @@ extern "C" { pub fn rb_full_cfunc_return(ec: *mut rb_execution_context_t, return_value: VALUE); pub fn rb_iseq_get_yjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; pub fn rb_iseq_set_yjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void); - pub fn rb_yjit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; pub fn rb_get_symbol_id(namep: VALUE) -> ID; - pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; pub fn rb_optimized_call( recv: *mut VALUE, ec: *mut rb_execution_context_t, @@ -1200,6 +1197,8 @@ extern "C" { ) -> *mut rb_method_cfunc_t; pub fn rb_get_def_method_serial(def: *const rb_method_definition_t) -> usize; pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; + pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; + pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 60dcb7a69c5eca..92f7a10e56f97c 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -294,8 +294,7 @@ fn main() { .allowlist_function("rb_zjit_singleton_class_p") .allowlist_function("rb_zjit_defined_ivar") .allowlist_function("rb_zjit_insn_leaf") - .allowlist_type("robject_offsets") - .allowlist_type("rstring_offsets") + .allowlist_type("jit_bindgen_constants") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") @@ -303,7 +302,6 @@ fn main() { .allowlist_function("rb_jit_vm_unlock") .allowlist_function("rb_jit_for_each_iseq") .allowlist_function("rb_iseq_reset_jit_func") - .allowlist_type("robject_offsets") .allowlist_function("rb_vm_barrier") // Not sure why it's picking these up, but don't. @@ -367,6 +365,7 @@ fn main() { .allowlist_function("rb_get_mct_func") .allowlist_function("rb_get_def_iseq_ptr") .allowlist_function("rb_get_def_bmethod_proc") + .allowlist_function("rb_jit_get_proc_ptr") .allowlist_function("rb_iseq_encoded_size") .allowlist_function("rb_get_iseq_body_total_calls") .allowlist_function("rb_get_iseq_body_local_iseq") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 6f2ad37d3bab2e..c9e5bc8fd1ebcb 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1,5 +1,142 @@ /* automatically generated by rust-bindgen 0.71.1 */ +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct __BindgenBitfieldUnit { + storage: Storage, +} +impl __BindgenBitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} +impl __BindgenBitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + fn extract_bit(byte: u8, index: usize) -> bool { + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + #[inline] + pub fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + Self::extract_bit(byte, index) + } + #[inline] + pub unsafe fn raw_get_bit(this: *const Self, index: usize) -> bool { + debug_assert!(index / 8 < core::mem::size_of::()); + let byte_index = index / 8; + let byte = *(core::ptr::addr_of!((*this).storage) as *const u8).offset(byte_index as isize); + Self::extract_bit(byte, index) + } + #[inline] + fn change_bit(byte: u8, index: usize, val: bool) -> u8 { + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { + byte | mask + } else { + byte & !mask + } + } + #[inline] + pub fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + *byte = Self::change_bit(*byte, index, val); + } + #[inline] + pub unsafe fn raw_set_bit(this: *mut Self, index: usize, val: bool) { + debug_assert!(index / 8 < core::mem::size_of::()); + let byte_index = index / 8; + let byte = + (core::ptr::addr_of_mut!((*this).storage) as *mut u8).offset(byte_index as isize); + *byte = Self::change_bit(*byte, index, val); + } + #[inline] + pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if self.get_bit(i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + #[inline] + pub unsafe fn raw_get(this: *const Self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < core::mem::size_of::()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= core::mem::size_of::()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if Self::raw_get_bit(this, i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + #[inline] + pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + self.set_bit(index + bit_offset, val_bit_is_set); + } + } + #[inline] + pub unsafe fn raw_set(this: *mut Self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < core::mem::size_of::()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= core::mem::size_of::()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + Self::raw_set_bit(this, index + bit_offset, val_bit_is_set); + } + } +} #[repr(C)] #[derive(Default)] pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); @@ -30,6 +167,49 @@ impl ::std::fmt::Debug for __IncompleteArrayField { fmt.write_str("__IncompleteArrayField") } } +#[repr(C)] +pub struct __BindgenUnionField(::std::marker::PhantomData); +impl __BindgenUnionField { + #[inline] + pub const fn new() -> Self { + __BindgenUnionField(::std::marker::PhantomData) + } + #[inline] + pub unsafe fn as_ref(&self) -> &T { + ::std::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut(&mut self) -> &mut T { + ::std::mem::transmute(self) + } +} +impl ::std::default::Default for __BindgenUnionField { + #[inline] + fn default() -> Self { + Self::new() + } +} +impl ::std::clone::Clone for __BindgenUnionField { + #[inline] + fn clone(&self) -> Self { + *self + } +} +impl ::std::marker::Copy for __BindgenUnionField {} +impl ::std::fmt::Debug for __BindgenUnionField { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fmt.write_str("__BindgenUnionField") + } +} +impl ::std::hash::Hash for __BindgenUnionField { + fn hash(&self, _state: &mut H) {} +} +impl ::std::cmp::PartialEq for __BindgenUnionField { + fn eq(&self, _other: &__BindgenUnionField) -> bool { + true + } +} +impl ::std::cmp::Eq for __BindgenUnionField {} pub const ONIG_OPTION_IGNORECASE: u32 = 1; pub const ONIG_OPTION_EXTEND: u32 = 2; pub const ONIG_OPTION_MULTILINE: u32 = 4; @@ -163,6 +343,16 @@ pub type ruby_rmodule_flags = u32; pub const ROBJECT_HEAP: ruby_robject_flags = 65536; pub type ruby_robject_flags = u32; pub type rb_event_flag_t = u32; +pub type rb_block_call_func = ::std::option::Option< + unsafe extern "C" fn( + yielded_arg: VALUE, + callback_arg: VALUE, + argc: ::std::os::raw::c_int, + argv: *const VALUE, + blockarg: VALUE, + ) -> VALUE, +>; +pub type rb_block_call_func_t = rb_block_call_func; pub const RUBY_ENCODING_INLINE_MAX: ruby_encoding_consts = 127; pub const RUBY_ENCODING_SHIFT: ruby_encoding_consts = 22; pub const RUBY_ENCODING_MASK: ruby_encoding_consts = 532676608; @@ -233,6 +423,20 @@ pub const imemo_callcache: imemo_type = 11; pub const imemo_constcache: imemo_type = 12; pub const imemo_fields: imemo_type = 13; pub type imemo_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vm_ifunc_argc { + pub min: ::std::os::raw::c_int, + pub max: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct vm_ifunc { + pub flags: VALUE, + pub svar_lep: *mut VALUE, + pub func: rb_block_call_func_t, + pub data: *const ::std::os::raw::c_void, + pub argc: vm_ifunc_argc, +} pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0; pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1; pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2; @@ -354,7 +558,166 @@ pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { pub table: *const ID, pub default_values: *mut VALUE, } +#[repr(C)] +pub struct rb_captured_block { + pub self_: VALUE, + pub ep: *const VALUE, + pub code: rb_captured_block__bindgen_ty_1, +} +#[repr(C)] +pub struct rb_captured_block__bindgen_ty_1 { + pub iseq: __BindgenUnionField<*const rb_iseq_t>, + pub ifunc: __BindgenUnionField<*const vm_ifunc>, + pub val: __BindgenUnionField, + pub bindgen_union_field: u64, +} +pub const block_type_iseq: rb_block_type = 0; +pub const block_type_ifunc: rb_block_type = 1; +pub const block_type_symbol: rb_block_type = 2; +pub const block_type_proc: rb_block_type = 3; +pub type rb_block_type = u32; +#[repr(C)] +pub struct rb_block { + pub as_: rb_block__bindgen_ty_1, + pub type_: rb_block_type, +} +#[repr(C)] +pub struct rb_block__bindgen_ty_1 { + pub captured: __BindgenUnionField, + pub symbol: __BindgenUnionField, + pub proc_: __BindgenUnionField, + pub bindgen_union_field: [u64; 3usize], +} pub type rb_control_frame_t = rb_control_frame_struct; +#[repr(C)] +pub struct rb_proc_t { + pub block: rb_block, + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, + pub __bindgen_padding_0: [u8; 7usize], +} +impl rb_proc_t { + #[inline] + pub fn is_from_method(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u32) } + } + #[inline] + pub fn set_is_from_method(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn is_from_method_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 0usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_is_from_method_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 0usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn is_lambda(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u32) } + } + #[inline] + pub fn set_is_lambda(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(1usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn is_lambda_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 1usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_is_lambda_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 1usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn is_isolated(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u32) } + } + #[inline] + pub fn set_is_isolated(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(2usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn is_isolated_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 2usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_is_isolated_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 2usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn new_bitfield_1( + is_from_method: ::std::os::raw::c_uint, + is_lambda: ::std::os::raw::c_uint, + is_isolated: ::std::os::raw::c_uint, + ) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 1u8, { + let is_from_method: u32 = unsafe { ::std::mem::transmute(is_from_method) }; + is_from_method as u64 + }); + __bindgen_bitfield_unit.set(1usize, 1u8, { + let is_lambda: u32 = unsafe { ::std::mem::transmute(is_lambda) }; + is_lambda as u64 + }); + __bindgen_bitfield_unit.set(2usize, 1u8, { + let is_isolated: u32 = unsafe { ::std::mem::transmute(is_isolated) }; + is_isolated as u64 + }); + __bindgen_bitfield_unit + } +} pub const VM_CHECKMATCH_TYPE_WHEN: vm_check_match_type = 1; pub const VM_CHECKMATCH_TYPE_CASE: vm_check_match_type = 2; pub const VM_CHECKMATCH_TYPE_RESCUE: vm_check_match_type = 3; @@ -730,9 +1093,10 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: robject_offsets = 16; -pub const ROBJECT_OFFSET_AS_ARY: robject_offsets = 16; -pub type robject_offsets = u32; +pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; +pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub type jit_bindgen_constants = u32; pub const rb_invalid_shape_id: shape_id_t = 4294967295; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; unsafe extern "C" { @@ -997,6 +1361,8 @@ unsafe extern "C" { ) -> *mut rb_method_cfunc_t; pub fn rb_get_def_method_serial(def: *const rb_method_definition_t) -> usize; pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; + pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; + pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; From b14fac9440ab87d447824a1c8bce2921b3cea076 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 17 Oct 2025 22:35:28 -0400 Subject: [PATCH 0432/2435] ZJIT: Issue `SendWithoutBlockDirect` to `VM_METHOD_TYPE_BMETHOD` This helps ZJIT optimize ~300,000 more sends in ruby-bench's lobsters Top-6 not optimized method types for send_without_block Before After iseq: 713,899 (48.0%) iseq: 725,668 (62.4%) optimized: 359,864 (24.2%) optimized: 359,940 (31.0%) bmethod: 339,040 (22.8%) alias: 73,541 ( 6.3%) alias: 73,392 ( 4.9%) null: 2,521 ( 0.2%) null: 2,521 ( 0.2%) bmethod: 979 ( 0.1%) cfunc: 4 ( 0.0%) cfunc: 4 ( 0.0%) --- zjit/src/codegen.rs | 61 +++++++++++------- zjit/src/cruby.rs | 5 ++ zjit/src/hir.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 3 + 4 files changed, 200 insertions(+), 22 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f7b335f1bfce89..40187ba03c2733 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -694,12 +694,23 @@ fn gen_ccall_with_frame( gen_spill_stack(jit, asm, state); gen_spill_locals(jit, asm, state); + let block_handler_specval = if let Some(block_iseq) = blockiseq { + // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). + // VM_CFP_TO_CAPTURED_BLOCK then turns &cfp->self into a block handler. + // rb_captured_block->code.iseq aliases with cfp->block_code. + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); + let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); + asm.or(cfp_self_addr, Opnd::Imm(1)) + } else { + VM_BLOCK_HANDLER_NONE.into() + }; + gen_push_frame(asm, args.len(), state, ControlFrame { recv: args[0], iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, - block_iseq: blockiseq, + specval: block_handler_specval, }); asm_comment!(asm, "switch to new SP register"); @@ -755,7 +766,7 @@ fn gen_ccall_variadic( iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, - block_iseq: None, + specval: VM_BLOCK_HANDLER_NONE.into(), }); asm_comment!(asm, "switch to new SP register"); @@ -1141,14 +1152,28 @@ fn gen_send_without_block_direct( gen_spill_locals(jit, asm, state); gen_spill_stack(jit, asm, state); + let (frame_type, specval) = if VM_METHOD_TYPE_BMETHOD == unsafe { get_cme_def_type(cme) } { + // Extract EP from the Proc instance + let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; + let proc = unsafe { rb_jit_get_proc_ptr(procv) }; + let proc_block = unsafe { &(*proc).block }; + let capture = unsafe { proc_block.as_.captured.as_ref() }; + let bmethod_frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA; + // Tag the captured EP like VM_GUARDED_PREV_EP() in vm_call_iseq_bmethod() + let bmethod_specval = (capture.ep.addr() | 1).into(); + (bmethod_frame_type, bmethod_specval) + } else { + (VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, VM_BLOCK_HANDLER_NONE.into()) + }; + // Set up the new frame // TODO: Lazily materialize caller frames on side exits or when needed gen_push_frame(asm, args.len(), state, ControlFrame { recv, iseq: Some(iseq), cme, - frame_type: VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, - block_iseq: None, + frame_type, + specval, }); asm_comment!(asm, "switch to new SP register"); @@ -1745,7 +1770,9 @@ struct ControlFrame { iseq: Option, cme: *const rb_callable_method_entry_t, frame_type: u32, - block_iseq: Option, + /// The [`VM_ENV_DATA_INDEX_SPECVAL`] slot of the frame. + /// For the type of frames we push, block handler or the parent EP. + specval: lir::Opnd, } /// Compile an interpreter frame @@ -1761,21 +1788,10 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C 0 }; let ep_offset = state.stack().len() as i32 + local_size - argc as i32 + VM_ENV_DATA_SIZE as i32 - 1; + // ep[-2]: CME asm.store(Opnd::mem(64, SP, (ep_offset - 2) * SIZEOF_VALUE_I32), VALUE::from(frame.cme).into()); - - let block_handler_opnd = if let Some(block_iseq) = frame.block_iseq { - // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). - // VM_CFP_TO_CAPTURED_BLOCK does &cfp->self, rb_captured_block->code.iseq aliases - // with cfp->block_code. - asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); - let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); - asm.or(cfp_self_addr, Opnd::Imm(1)) - } else { - VM_BLOCK_HANDLER_NONE.into() - }; - - // ep[-1]: block_handler or prev EP - asm.store(Opnd::mem(64, SP, (ep_offset - 1) * SIZEOF_VALUE_I32), block_handler_opnd); + // ep[-1]: specval + asm.store(Opnd::mem(64, SP, (ep_offset - 1) * SIZEOF_VALUE_I32), frame.specval); // ep[0]: ENV_FLAGS asm.store(Opnd::mem(64, SP, ep_offset * SIZEOF_VALUE_I32), frame.frame_type.into()); @@ -1998,9 +2014,10 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result })?; // We currently don't support JIT-to-JIT calls for ISEQs with optional arguments. - // So we only need to use jit_entry_ptrs[0] for now. TODO: Support optional arguments. - assert_eq!(1, jit_entry_ptrs.len()); - let jit_entry_ptr = jit_entry_ptrs[0]; + // So we only need to use jit_entry_ptrs[0] for now. TODO(Shopify/ruby#817): Support optional arguments. + let Some(&jit_entry_ptr) = jit_entry_ptrs.get(0) else { + return Err(CompileError::JitToJitOptional) + }; // Update the stub to call the code pointer let code_addr = jit_entry_ptr.raw_ptr(cb); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 09a221b5027f68..05d543cb33e09a 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -450,6 +450,11 @@ impl VALUE { !self.special_const_p() } + /// Shareability between ractors. `RB_OBJ_SHAREABLE_P()`. + pub fn shareable_p(self) -> bool { + (self.builtin_flags() & RUBY_FL_SHAREABLE as usize) != 0 + } + /// Return true if the value is a Ruby Fixnum (immediate-size integer) pub fn fixnum_p(self) -> bool { let VALUE(cval) = self; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7083a082fba1a8..de7e3aaec100fd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2101,6 +2101,45 @@ impl Function { if klass.instance_can_have_singleton_class() { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); } + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state }); + self.make_equal_to(insn_id, send_direct); + } else if def_type == VM_METHOD_TYPE_BMETHOD { + let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; + let proc = unsafe { rb_jit_get_proc_ptr(procv) }; + let proc_block = unsafe { &(*proc).block }; + // Target ISEQ bmethods. Can't handle for example, `define_method(:foo, &:foo)` + // which makes a `block_type_symbol` bmethod. + if proc_block.type_ != block_type_iseq { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); + self.push_insn_id(block, insn_id); continue; + } + let capture = unsafe { proc_block.as_.captured.as_ref() }; + let iseq = unsafe { *capture.code.iseq.as_ref() }; + + if !can_direct_send(iseq) { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); + self.push_insn_id(block, insn_id); continue; + } + // Can't pass a block to a block for now + if (unsafe { rb_vm_ci_flag(ci) } & VM_CALL_ARGS_BLOCKARG) != 0 { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); + self.push_insn_id(block, insn_id); continue; + } + + // Patch points: + // Check for "defined with an un-shareable Proc in a different Ractor" + if !procv.shareable_p() { + // TODO(alan): Turn this into a ractor belonging guard to work better in multi ractor mode. + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); + } + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if klass.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); + } + if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } @@ -12116,6 +12155,120 @@ mod opt_tests { "); } + #[test] + fn test_bmethod_send_direct() { + eval(" + define_method(:zero) { :b } + define_method(:one) { |arg| arg } + + def test = one(zero) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:BasicObject = SendWithoutBlockDirect v22, :zero (0x1038) + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:BasicObject = SendWithoutBlockDirect v27, :one (0x1038), v23 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_symbol_block_bmethod() { + eval(" + define_method(:identity, &:itself) + def test = identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[100] = Const Value(100) + v12:BasicObject = SendWithoutBlock v6, :identity, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_call_bmethod_with_block() { + eval(" + define_method(:bmethod) { :b } + def test = (bmethod {}) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :bmethod + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_call_shareable_bmethod() { + eval(" + class Foo + class << self + define_method(:identity, &(Ractor.make_shareable ->(val){val})) + end + end + def test = Foo.identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Foo) + v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[100] = Const Value(100) + PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + v25:BasicObject = SendWithoutBlockDirect v22, :identity (0x1048), v12 + CheckInterrupts + Return v25 + "); + } + #[test] fn test_nil_nil_specialized_to_ccall() { eval(" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 33f29fb3aaed22..913a72fa5646ff 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -187,6 +187,7 @@ make_counters! { compile_error_iseq_stack_too_large, compile_error_exception_handler, compile_error_out_of_memory, + compile_error_jit_to_jit_optional, compile_error_register_spill_on_ccall, compile_error_register_spill_on_alloc, compile_error_parse_stack_underflow, @@ -286,6 +287,7 @@ pub enum CompileError { RegisterSpillOnAlloc, RegisterSpillOnCCall, ParseError(ParseError), + JitToJitOptional, } /// Return a raw pointer to the exit counter for a given CompileError @@ -300,6 +302,7 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { OutOfMemory => compile_error_out_of_memory, RegisterSpillOnAlloc => compile_error_register_spill_on_alloc, RegisterSpillOnCCall => compile_error_register_spill_on_ccall, + JitToJitOptional => compile_error_jit_to_jit_optional, ParseError(parse_error) => match parse_error { StackUnderflow(_) => compile_error_parse_stack_underflow, MalformedIseq(_) => compile_error_parse_malformed_iseq, From 89472d89111cb342f238e9a6f73336297dbb1087 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 21 Oct 2025 16:05:04 +0200 Subject: [PATCH 0433/2435] ZJIT: Inline Fixnum#^ * Handled in cruby_methods.rs because there is no basic operation for Fixnum#^. --- test/ruby/test_zjit.rb | 22 ++++++ zjit/src/codegen.rs | 8 ++ zjit/src/cruby.rs | 1 + zjit/src/cruby_methods.rs | 12 +++ zjit/src/hir.rs | 162 +++++++++++++++++++++++++++++++++++++- 5 files changed, 204 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index b0c717bc24b435..4db18a2291e52f 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -829,6 +829,28 @@ def test(a, b) = a | b }, call_threshold: 2, insns: [:opt_or] end + def test_fixnum_xor + assert_compiles '[6, -8, 3]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(-5, 3), + test(1, 2) + ] + }, call_threshold: 2 + end + + def test_fixnum_xor_side_exit + assert_compiles '[6, 6, true]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(5, 3), + test(true, false) + ] + }, call_threshold: 2 + end + def test_fixnum_mul assert_compiles '12', %q{ C = 3 diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 40187ba03c2733..5ce9f24cd25540 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -398,6 +398,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumGe { left, right } => gen_fixnum_ge(asm, opnd!(left), opnd!(right)), Insn::FixnumAnd { left, right } => gen_fixnum_and(asm, opnd!(left), opnd!(right)), Insn::FixnumOr { left, right } => gen_fixnum_or(asm, opnd!(left), opnd!(right)), + Insn::FixnumXor { left, right } => gen_fixnum_xor(asm, opnd!(left), opnd!(right)), &Insn::FixnumMod { left, right, state } => gen_fixnum_mod(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), @@ -1485,6 +1486,13 @@ fn gen_fixnum_or(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir: asm.or(left, right) } +/// Compile Fixnum ^ Fixnum +fn gen_fixnum_xor(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { + // XOR and then re-tag the resulting fixnum + let out_val = asm.xor(left, right); + asm.add(out_val, Opnd::UImm(1)) +} + fn gen_fixnum_mod(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { // Check for left % 0, which raises ZeroDivisionError asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 05d543cb33e09a..645891496edbae 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1359,6 +1359,7 @@ pub(crate) mod ids { name: ge content: b">=" name: and content: b"&" name: or content: b"|" + name: xor content: b"^" name: freeze name: minusat content: b"-@" name: aref content: b"[]" diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 40fb0cbe442dab..656ccab7817c91 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -210,6 +210,7 @@ pub fn init() -> Annotations { annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); + annotate!(rb_cInteger, "^", inline_integer_xor); annotate!(rb_cString, "to_s", inline_string_to_s); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); @@ -286,6 +287,17 @@ fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir:: None } +fn inline_integer_xor(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[right] = args else { return None; }; + if fun.likely_a(recv, types::Fixnum, state) && fun.likely_a(right, types::Fixnum, state) { + let left = fun.coerce_to(block, recv, types::Fixnum, state); + let right = fun.coerce_to(block, right, types::Fixnum, state); + let result = fun.push_insn(block, hir::Insn::FixnumXor { left, right }); + return Some(result); + } + None +} + fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index de7e3aaec100fd..1da61928e6d366 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -750,7 +750,7 @@ pub enum Insn { /// Non-local control flow. See the throw YARV instruction Throw { throw_state: u32, val: InsnId, state: InsnId }, - /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, | + /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^ FixnumAdd { left: InsnId, right: InsnId, state: InsnId }, FixnumSub { left: InsnId, right: InsnId, state: InsnId }, FixnumMult { left: InsnId, right: InsnId, state: InsnId }, @@ -764,6 +764,7 @@ pub enum Insn { FixnumGe { left: InsnId, right: InsnId }, FixnumAnd { left: InsnId, right: InsnId }, FixnumOr { left: InsnId, right: InsnId }, + FixnumXor { left: InsnId, right: InsnId }, // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined ObjToString { val: InsnId, cd: *const rb_call_data, state: InsnId }, @@ -853,6 +854,7 @@ impl Insn { Insn::FixnumGe { .. } => false, Insn::FixnumAnd { .. } => false, Insn::FixnumOr { .. } => false, + Insn::FixnumXor { .. } => false, Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::LoadPC => false, @@ -1051,6 +1053,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumGe { left, right, .. } => { write!(f, "FixnumGe {left}, {right}") }, Insn::FixnumAnd { left, right, .. } => { write!(f, "FixnumAnd {left}, {right}") }, Insn::FixnumOr { left, right, .. } => { write!(f, "FixnumOr {left}, {right}") }, + Insn::FixnumXor { left, right, .. } => { write!(f, "FixnumXor {left}, {right}") }, Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardTypeNot { val, guard_type, .. } => { write!(f, "GuardTypeNot {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, @@ -1541,6 +1544,7 @@ impl Function { &FixnumLe { left, right } => FixnumLe { left: find!(left), right: find!(right) }, &FixnumAnd { left, right } => FixnumAnd { left: find!(left), right: find!(right) }, &FixnumOr { left, right } => FixnumOr { left: find!(left), right: find!(right) }, + &FixnumXor { left, right } => FixnumXor { left: find!(left), right: find!(right) }, &ObjToString { val, cd, state } => ObjToString { val: find!(val), cd, @@ -1740,6 +1744,7 @@ impl Function { Insn::FixnumGe { .. } => types::BoolExact, Insn::FixnumAnd { .. } => types::Fixnum, Insn::FixnumOr { .. } => types::Fixnum, + Insn::FixnumXor { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, Insn::SendWithoutBlock { .. } => types::BasicObject, Insn::SendWithoutBlockDirect { .. } => types::BasicObject, @@ -2967,6 +2972,7 @@ impl Function { | &Insn::FixnumNeq { left, right } | &Insn::FixnumAnd { left, right } | &Insn::FixnumOr { left, right } + | &Insn::FixnumXor { left, right } | &Insn::IsBitEqual { left, right } => { worklist.push_back(left); @@ -13909,6 +13915,160 @@ mod opt_tests { "); } + #[test] + fn test_inline_integer_xor_with_fixnum() { + eval(" + def test(x, y) = x ^ y + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:Fixnum = GuardType v12, Fixnum + v27:Fixnum = FixnumXor v25, v26 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_integer_xor() { + eval(r#" + def test(x, y) + x ^ y + 42 + end + test(1, 2) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v28:Fixnum = GuardType v11, Fixnum + v29:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count + v20:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_bignum_or_boolean() { + eval(" + def test(x, y) = x ^ y + test(4 << 70, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Integer = GuardType v11, Integer + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(1, 4 << 70) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(true, 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_args() { + eval(" + def test(x, y) = x.^() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:BasicObject = SendWithoutBlock v11, :^ + CheckInterrupts + Return v17 + "); + } + #[test] fn test_specialize_hash_size() { eval(" From 0cc4819f2461c8080a2e50b9ab7cbb16798c39ce Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 21 Oct 2025 22:56:24 +0200 Subject: [PATCH 0434/2435] Improve test_fixnum_{and,or}* by checking all results and using more interesting inputs --- test/ruby/test_zjit.rb | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 4db18a2291e52f..e151a022d1bc61 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -794,38 +794,46 @@ def test(x, y) = x | y end def test_fixnum_and - assert_compiles '1', %q{ + assert_compiles '[1, 2, 4]', %q{ def test(a, b) = a & b - test(2, 2) - test(2, 2) - test(5, 3) + [ + test(5, 3), + test(0b011, 0b110), + test(-0b011, 0b110) + ] }, call_threshold: 2, insns: [:opt_and] end def test_fixnum_and_side_exit - assert_compiles 'false', %q{ + assert_compiles '[2, 2, false]', %q{ def test(a, b) = a & b - test(2, 2) - test(2, 2) - test(true, false) + [ + test(2, 2), + test(0b011, 0b110), + test(true, false) + ] }, call_threshold: 2, insns: [:opt_and] end def test_fixnum_or - assert_compiles '3', %q{ + assert_compiles '[7, 3, -3]', %q{ def test(a, b) = a | b - test(5, 3) - test(5, 3) - test(1, 2) + [ + test(5, 3), + test(1, 2), + test(1, -4) + ] }, call_threshold: 2, insns: [:opt_or] end def test_fixnum_or_side_exit - assert_compiles 'true', %q{ + assert_compiles '[3, 2, true]', %q{ def test(a, b) = a | b - test(2, 2) - test(2, 2) - test(true, false) + [ + test(1, 2), + test(2, 2), + test(true, false) + ] }, call_threshold: 2, insns: [:opt_or] end From cd42096f5a8a15573ccb1e8bcd83872877907541 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 19 Oct 2025 11:16:02 -0400 Subject: [PATCH 0435/2435] Move rb_class_classext_free to class.c --- class.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ gc.c | 40 ++--------------------------------- internal/class.h | 3 +++ 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/class.c b/class.c index 77f2fba51647bc..2b78d73378e14e 100644 --- a/class.c +++ b/class.c @@ -79,6 +79,60 @@ #define METACLASS_OF(k) RBASIC(k)->klass #define SET_METACLASS_OF(k, cls) RBASIC_SET_CLASS(k, cls) +static enum rb_id_table_iterator_result +cvar_table_free_i(VALUE value, void *ctx) +{ + xfree((void *)value); + return ID_TABLE_CONTINUE; +} + +void +rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) +{ + struct rb_id_table *tbl; + + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + + if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) { + rb_free_const_table(tbl); + } + + if ((tbl = RCLASSEXT_CVC_TBL(ext)) != NULL) { + rb_id_table_foreach_values(tbl, cvar_table_free_i, NULL); + rb_id_table_free(tbl); + } + + rb_class_classext_free_subclasses(ext, klass); + + if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { + RUBY_ASSERT(is_prime); // superclasses should only be used on prime + xfree(RCLASSEXT_SUPERCLASSES(ext)); + } + + if (!is_prime) { // the prime classext will be freed with RClass + xfree(ext); + } +} + +void +rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) +{ + if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { + /* Method table is not shared for origin iclasses of classes */ + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + } + + if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { + rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); + } + + rb_class_classext_free_subclasses(ext, klass); + + if (!is_prime) { // the prime classext will be freed with RClass + xfree(ext); + } +} + RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; struct duplicate_id_tbl_data { diff --git a/gc.c b/gc.c index 42625e10046b59..897447c808e3d1 100644 --- a/gc.c +++ b/gc.c @@ -1160,13 +1160,6 @@ rb_objspace_data_type_name(VALUE obj) } } -static enum rb_id_table_iterator_result -cvar_table_free_i(VALUE value, void *ctx) -{ - xfree((void *)value); - return ID_TABLE_CONTINUE; -} - static void io_fptr_finalize(void *fptr) { @@ -1233,26 +1226,9 @@ struct classext_foreach_args { static void classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) { - struct rb_id_table *tbl; struct classext_foreach_args *args = (struct classext_foreach_args *)arg; - rb_id_table_free(RCLASSEXT_M_TBL(ext)); - - if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) { - rb_free_const_table(tbl); - } - if ((tbl = RCLASSEXT_CVC_TBL(ext)) != NULL) { - rb_id_table_foreach_values(tbl, cvar_table_free_i, NULL); - rb_id_table_free(tbl); - } - rb_class_classext_free_subclasses(ext, args->klass); - if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { - RUBY_ASSERT(is_prime); // superclasses should only be used on prime - xfree(RCLASSEXT_SUPERCLASSES(ext)); - } - if (!is_prime) { // the prime classext will be freed with RClass - xfree(ext); - } + rb_class_classext_free(args->klass, ext, is_prime); } static void @@ -1260,19 +1236,7 @@ classext_iclass_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *a { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; - if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { - /* Method table is not shared for origin iclasses of classes */ - rb_id_table_free(RCLASSEXT_M_TBL(ext)); - } - if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { - rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); - } - - rb_class_classext_free_subclasses(ext, args->klass); - - if (!is_prime) { // the prime classext will be freed with RClass - xfree(ext); - } + rb_iclass_classext_free(args->klass, ext, is_prime); } bool diff --git a/internal/class.h b/internal/class.h index f5c5142b452d78..9fc6d243e65b5a 100644 --- a/internal/class.h +++ b/internal/class.h @@ -317,6 +317,9 @@ RCLASS_SET_CLASSEXT_TBL(VALUE klass, st_table *tbl) rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns); void rb_class_ensure_writable(VALUE obj); +void rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); +void rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); + static inline int RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext) { From 4a23b6a89fbc7626e31d4cd72b1eccbd0b570009 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 20 Oct 2025 17:00:31 -0400 Subject: [PATCH 0436/2435] Fix memory leak in RCLASS_SET_NAMESPACE_CLASSEXT The st_insert in RCLASS_SET_NAMESPACE_CLASSEXT may overwrite an existing rb_classext_t, causing it to leak memory. This commit changes it to use st_update to free the existing one before overwriting it. --- class.c | 35 +++++++++++++++++++++++++++++++++++ internal/class.h | 10 +++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/class.c b/class.c index 2b78d73378e14e..74dcbe5fa7b99a 100644 --- a/class.c +++ b/class.c @@ -133,6 +133,41 @@ rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) } } +struct rb_class_set_namespace_classext_args { + VALUE obj; + rb_classext_t *ext; +}; + +static int +rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) +{ + struct rb_class_set_namespace_classext_args *args = (struct rb_class_set_namespace_classext_args *)a; + + if (existing) { + if (BUILTIN_TYPE(args->obj) == T_ICLASS) { + rb_iclass_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + } + else { + rb_class_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + } + } + + *val_ptr = (st_data_t)args->ext; + + return ST_CONTINUE; +} + +void +rb_class_set_namespace_classext(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext) +{ + struct rb_class_set_namespace_classext_args args = { + .obj = obj, + .ext = ext, + }; + + st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)ns->ns_object, rb_class_set_namespace_classext_update, (st_data_t)&args); +} + RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; struct duplicate_id_tbl_data { diff --git a/internal/class.h b/internal/class.h index 9fc6d243e65b5a..a791672cadcacf 100644 --- a/internal/class.h +++ b/internal/class.h @@ -317,8 +317,7 @@ RCLASS_SET_CLASSEXT_TBL(VALUE klass, st_table *tbl) rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns); void rb_class_ensure_writable(VALUE obj); -void rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); -void rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); +void rb_class_set_namespace_classext(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext); static inline int RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext) @@ -335,7 +334,9 @@ RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t if (rb_st_table_size(tbl) == 0) { first_set = 1; } - rb_st_insert(tbl, (st_data_t)ns->ns_object, (st_data_t)ext); + + rb_class_set_namespace_classext(obj, ns, ext); + return first_set; } @@ -518,6 +519,9 @@ void rb_undef_methods_from(VALUE klass, VALUE super); VALUE rb_class_inherited(VALUE, VALUE); VALUE rb_keyword_error_new(const char *, VALUE); +void rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); +void rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); + RUBY_SYMBOL_EXPORT_BEGIN /* for objspace */ From a2a107c2d7e32d31f4e6bb17466b3ddfdbe315a3 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 21 Oct 2025 17:00:19 -0400 Subject: [PATCH 0437/2435] ZJIT: Handle when SetIvar raises FrozenError --- zjit/src/codegen.rs | 6 ++++-- zjit/src/hir.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5ce9f24cd25540..402c500c8b17b9 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -424,7 +424,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp), &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), - Insn::SetIvar { self_val, id, val, state: _ } => no_output!(gen_setivar(asm, opnd!(self_val), *id, opnd!(val))), + Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)), @@ -803,8 +803,10 @@ fn gen_getivar(asm: &mut Assembler, recv: Opnd, id: ID) -> Opnd { } /// Emit an uncached instance variable store -fn gen_setivar(asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd) { +fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd, state: &FrameState) { gen_incr_counter(asm, Counter::dynamic_setivar_count); + // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. + gen_prepare_non_leaf_call(jit, asm, state); asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1da61928e6d366..68ed867e4ddd4d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -7263,6 +7263,33 @@ mod tests { "); } + #[test] + fn test_set_ivar_rescue_frozen() { + let result = eval(" + class Foo + attr_accessor :bar + def initialize + @bar = 1 + freeze + end + end + + def test(foo) + begin + foo.bar = 2 + rescue FrozenError + end + end + + foo = Foo.new + test(foo) + test(foo) + + foo.bar + "); + assert_eq!(VALUE::fixnum_from_usize(1), result); + } + #[test] fn test_setglobal() { eval(" From f46ebed2ff66a88e82556f08054932b817bbcfad Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Oct 2025 16:49:10 -0400 Subject: [PATCH 0438/2435] Fix memory leak of darray in loaded_features_index --- depend | 1 + namespace.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/depend b/depend index 1f9f0c31eba695..fa61de77a00e62 100644 --- a/depend +++ b/depend @@ -9302,6 +9302,7 @@ namespace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h namespace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h namespace.$(OBJEXT): {$(VPATH)}config.h namespace.$(OBJEXT): {$(VPATH)}constant.h +namespace.$(OBJEXT): {$(VPATH)}darray.h namespace.$(OBJEXT): {$(VPATH)}debug_counter.h namespace.$(OBJEXT): {$(VPATH)}defines.h namespace.$(OBJEXT): {$(VPATH)}encoding.h diff --git a/namespace.c b/namespace.c index d4a990cb386e08..b85cbf57157924 100644 --- a/namespace.c +++ b/namespace.c @@ -16,6 +16,7 @@ #include "ruby/internal/globals.h" #include "ruby/util.h" #include "vm_core.h" +#include "darray.h" #include @@ -207,6 +208,15 @@ free_loading_table_entry(st_data_t key, st_data_t value, st_data_t arg) return ST_DELETE; } +static int +free_loaded_feature_index_i(st_data_t key, st_data_t value, st_data_t arg) +{ + if (!FIXNUM_P(value)) { + rb_darray_free((void *)value); + } + return ST_CONTINUE; +} + static void namespace_root_free(void *ptr) { @@ -218,6 +228,7 @@ namespace_root_free(void *ptr) } if (ns->loaded_features_index) { + st_foreach(ns->loaded_features_index, free_loaded_feature_index_i, 0); st_free_table(ns->loaded_features_index); } } From 66c12bd5194396fab66338a87ca8f2d89f1d66d0 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 22 Oct 2025 06:53:46 +0000 Subject: [PATCH 0439/2435] Update bundled gems list as of 2025-10-22 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index bf4820b86f000f..4939962a4049c9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -159,7 +159,7 @@ The following bundled gems are promoted from default gems. * ostruct 0.6.3 * pstore 0.2.0 -* benchmark 0.4.1 +* benchmark 0.5.0 * logger 1.7.0 * rdoc 6.15.0 * win32ole 1.9.2 diff --git a/gems/bundled_gems b/gems/bundled_gems index 9f49934794f930..64c8b2df5e196f 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -37,7 +37,7 @@ csv 3.3.5 https://github.com/ruby/csv repl_type_completor 0.1.11 https://github.com/ruby/repl_type_completor 25108aa8d69ddaba0b5da3feff1c0035371524b2 ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore -benchmark 0.4.1 https://github.com/ruby/benchmark +benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger rdoc 6.15.0 https://github.com/ruby/rdoc ac2a6fbf62b584a8325a665a9e7b368388bc7df6 win32ole 1.9.2 https://github.com/ruby/win32ole From 6047eada20d39bbe80976c31277ec7916118f78a Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:19:56 +0200 Subject: [PATCH 0440/2435] [ruby/prism] Make error and snapshot tests multi-version aware This one has been on my mind for a while now. Currently, there are only tests against the latest syntax version. This changes the snapshot structure as follows: * Snapshots at their current location are tested against all syntax versions * Snapshots inside a version folder like "3.3" are tested against all versions starting from that version * Snapshots inside a version folder like "3.3-4.2" are tested against all versions in the given range. This makes sure that as new syntax is added, older versions still work as expected. I also added a few tests for now valid syntax that should be invalid in older versions (and the other way around as well) These tests run really fast. So even though it does 3x the work for these, I am still able to run the whole test suite in just 11 seconds. https://github.com/ruby/prism/commit/5191b1aa68 --- prism/prism.c | 2 +- .../errors/3.3-3.3/circular_parameters.txt | 12 ++++ test/prism/errors/3.3-3.4/leading_logical.txt | 34 ++++++++++ .../errors/3.3-3.4/private_endless_method.txt | 3 + .../block_args_in_array_assignment.txt | 0 .../dont_allow_return_inside_sclass_body.txt | 0 .../{ => 3.4}/it_with_ordinary_parameter.txt | 0 .../keyword_args_in_array_assignment.txt | 0 test/prism/errors_test.rb | 65 +++---------------- .../block_args_in_array_assignment.txt | 1 + test/prism/fixtures/{ => 3.3-3.3}/it.txt | 0 .../{ => 3.3-3.3}/it_indirect_writes.txt | 0 .../{ => 3.3-3.3}/it_read_and_assignment.txt | 0 .../3.3-3.3/it_with_ordinary_parameter.txt | 1 + .../keyword_args_in_array_assignment.txt | 1 + .../fixtures/3.3-3.3/return_in_sclass.txt | 1 + .../fixtures/3.4/circular_parameters.txt | 4 ++ test/prism/fixtures/3.4/it.txt | 5 ++ .../prism/fixtures/3.4/it_indirect_writes.txt | 23 +++++++ .../fixtures/3.4/it_read_and_assignment.txt | 1 + .../endless_methods_command_call.txt | 0 .../fixtures/{ => 3.5}/leading_logical.txt | 0 test/prism/fixtures_test.rb | 8 ++- test/prism/lex_test.rb | 14 ++-- test/prism/locals_test.rb | 4 +- test/prism/ruby/parser_test.rb | 13 ++-- test/prism/ruby/ripper_test.rb | 12 +++- test/prism/ruby/ruby_parser_test.rb | 12 +++- test/prism/snippets_test.rb | 12 ++-- test/prism/test_helper.rb | 47 +++++++++++++- 30 files changed, 191 insertions(+), 84 deletions(-) create mode 100644 test/prism/errors/3.3-3.3/circular_parameters.txt create mode 100644 test/prism/errors/3.3-3.4/leading_logical.txt create mode 100644 test/prism/errors/3.3-3.4/private_endless_method.txt rename test/prism/errors/{ => 3.4}/block_args_in_array_assignment.txt (100%) rename test/prism/errors/{ => 3.4}/dont_allow_return_inside_sclass_body.txt (100%) rename test/prism/errors/{ => 3.4}/it_with_ordinary_parameter.txt (100%) rename test/prism/errors/{ => 3.4}/keyword_args_in_array_assignment.txt (100%) create mode 100644 test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt rename test/prism/fixtures/{ => 3.3-3.3}/it.txt (100%) rename test/prism/fixtures/{ => 3.3-3.3}/it_indirect_writes.txt (100%) rename test/prism/fixtures/{ => 3.3-3.3}/it_read_and_assignment.txt (100%) create mode 100644 test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt create mode 100644 test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt create mode 100644 test/prism/fixtures/3.3-3.3/return_in_sclass.txt create mode 100644 test/prism/fixtures/3.4/circular_parameters.txt create mode 100644 test/prism/fixtures/3.4/it.txt create mode 100644 test/prism/fixtures/3.4/it_indirect_writes.txt create mode 100644 test/prism/fixtures/3.4/it_read_and_assignment.txt rename test/prism/fixtures/{ => 3.5}/endless_methods_command_call.txt (100%) rename test/prism/fixtures/{ => 3.5}/leading_logical.txt (100%) diff --git a/prism/prism.c b/prism/prism.c index 0ebcae62f9e822..95e7d0905040b1 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -15764,7 +15764,7 @@ parse_return(pm_parser_t *parser, pm_node_t *node) { break; } } - if (in_sclass) { + if (in_sclass && parser->version >= PM_OPTIONS_VERSION_CRUBY_3_4) { pm_parser_err_node(parser, node, PM_ERR_RETURN_INVALID); } } diff --git a/test/prism/errors/3.3-3.3/circular_parameters.txt b/test/prism/errors/3.3-3.3/circular_parameters.txt new file mode 100644 index 00000000000000..ef9642b075aae9 --- /dev/null +++ b/test/prism/errors/3.3-3.3/circular_parameters.txt @@ -0,0 +1,12 @@ +def foo(bar = bar) = 42 + ^~~ circular argument reference - bar + +def foo(bar: bar) = 42 + ^~~ circular argument reference - bar + +proc { |foo = foo| } + ^~~ circular argument reference - foo + +proc { |foo: foo| } + ^~~ circular argument reference - foo + diff --git a/test/prism/errors/3.3-3.4/leading_logical.txt b/test/prism/errors/3.3-3.4/leading_logical.txt new file mode 100644 index 00000000000000..2a702e281d5efc --- /dev/null +++ b/test/prism/errors/3.3-3.4/leading_logical.txt @@ -0,0 +1,34 @@ +1 +&& 2 +^~ unexpected '&&', ignoring it +&& 3 +^~ unexpected '&&', ignoring it + +1 +|| 2 +^ unexpected '|', ignoring it + ^ unexpected '|', ignoring it +|| 3 +^ unexpected '|', ignoring it + ^ unexpected '|', ignoring it + +1 +and 2 +^~~ unexpected 'and', ignoring it +and 3 +^~~ unexpected 'and', ignoring it + +1 +or 2 +^~ unexpected 'or', ignoring it +or 3 +^~ unexpected 'or', ignoring it + +1 +and foo +^~~ unexpected 'and', ignoring it + +2 +or foo +^~ unexpected 'or', ignoring it + diff --git a/test/prism/errors/3.3-3.4/private_endless_method.txt b/test/prism/errors/3.3-3.4/private_endless_method.txt new file mode 100644 index 00000000000000..8aae5e0cd39035 --- /dev/null +++ b/test/prism/errors/3.3-3.4/private_endless_method.txt @@ -0,0 +1,3 @@ +private def foo = puts "Hello" + ^ unexpected string literal, expecting end-of-input + diff --git a/test/prism/errors/block_args_in_array_assignment.txt b/test/prism/errors/3.4/block_args_in_array_assignment.txt similarity index 100% rename from test/prism/errors/block_args_in_array_assignment.txt rename to test/prism/errors/3.4/block_args_in_array_assignment.txt diff --git a/test/prism/errors/dont_allow_return_inside_sclass_body.txt b/test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt similarity index 100% rename from test/prism/errors/dont_allow_return_inside_sclass_body.txt rename to test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt diff --git a/test/prism/errors/it_with_ordinary_parameter.txt b/test/prism/errors/3.4/it_with_ordinary_parameter.txt similarity index 100% rename from test/prism/errors/it_with_ordinary_parameter.txt rename to test/prism/errors/3.4/it_with_ordinary_parameter.txt diff --git a/test/prism/errors/keyword_args_in_array_assignment.txt b/test/prism/errors/3.4/keyword_args_in_array_assignment.txt similarity index 100% rename from test/prism/errors/keyword_args_in_array_assignment.txt rename to test/prism/errors/3.4/keyword_args_in_array_assignment.txt diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 9dd4aea72865d9..9abed9265212d0 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -1,41 +1,19 @@ # frozen_string_literal: true +return if RUBY_VERSION < "3.3.0" + require_relative "test_helper" module Prism class ErrorsTest < TestCase base = File.expand_path("errors", __dir__) - filepaths = Dir["*.txt", base: base] - - if RUBY_VERSION < "3.0" - filepaths -= [ - "cannot_assign_to_a_reserved_numbered_parameter.txt", - "writing_numbered_parameter.txt", - "targeting_numbered_parameter.txt", - "defining_numbered_parameter.txt", - "defining_numbered_parameter_2.txt", - "numbered_parameters_in_block_arguments.txt", - "numbered_and_write.txt", - "numbered_or_write.txt", - "numbered_operator_write.txt" - ] - end - - if RUBY_VERSION < "3.4" - filepaths -= [ - "it_with_ordinary_parameter.txt", - "block_args_in_array_assignment.txt", - "keyword_args_in_array_assignment.txt" - ] - end - - if RUBY_VERSION < "3.4" || RUBY_RELEASE_DATE < "2024-07-24" - filepaths -= ["dont_allow_return_inside_sclass_body.txt"] - end + filepaths = Dir["**/*.txt", base: base] filepaths.each do |filepath| - define_method(:"test_#{File.basename(filepath, ".txt")}") do - assert_errors(File.join(base, filepath)) + ruby_versions_for(filepath).each do |version| + define_method(:"test_#{version}_#{File.basename(filepath, ".txt")}") do + assert_errors(File.join(base, filepath), version) + end end end @@ -86,38 +64,15 @@ def test_invalid_message_name assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name end - def test_circular_parameters - source = <<~RUBY - def foo(bar = bar) = 42 - def foo(bar: bar) = 42 - proc { |foo = foo| } - proc { |foo: foo| } - RUBY - - source.each_line do |line| - assert_predicate Prism.parse(line, version: "3.3.0"), :failure? - assert_predicate Prism.parse(line), :success? - end - end - - def test_private_endless_method - source = <<~RUBY - private def foo = puts "Hello" - RUBY - - assert_predicate Prism.parse(source, version: "3.4"), :failure? - assert_predicate Prism.parse(source), :success? - end - private - def assert_errors(filepath) + def assert_errors(filepath, version) expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8) source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "") - refute_valid_syntax(source) + refute_valid_syntax(source) if current_major_minor == version - result = Prism.parse(source) + result = Prism.parse(source, version: version) errors = result.errors refute_empty errors, "Expected errors in #{filepath}" diff --git a/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt new file mode 100644 index 00000000000000..6d6b052681b5da --- /dev/null +++ b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt @@ -0,0 +1 @@ +matrix[5, &block] = 8 diff --git a/test/prism/fixtures/it.txt b/test/prism/fixtures/3.3-3.3/it.txt similarity index 100% rename from test/prism/fixtures/it.txt rename to test/prism/fixtures/3.3-3.3/it.txt diff --git a/test/prism/fixtures/it_indirect_writes.txt b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt similarity index 100% rename from test/prism/fixtures/it_indirect_writes.txt rename to test/prism/fixtures/3.3-3.3/it_indirect_writes.txt diff --git a/test/prism/fixtures/it_read_and_assignment.txt b/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt similarity index 100% rename from test/prism/fixtures/it_read_and_assignment.txt rename to test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt diff --git a/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt new file mode 100644 index 00000000000000..178b641e6b94ec --- /dev/null +++ b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt @@ -0,0 +1 @@ +proc { || it } diff --git a/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt new file mode 100644 index 00000000000000..88016c2afe8251 --- /dev/null +++ b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt @@ -0,0 +1 @@ +matrix[5, axis: :y] = 8 diff --git a/test/prism/fixtures/3.3-3.3/return_in_sclass.txt b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt new file mode 100644 index 00000000000000..f1fde5771afab2 --- /dev/null +++ b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt @@ -0,0 +1 @@ +class << A; return; end diff --git a/test/prism/fixtures/3.4/circular_parameters.txt b/test/prism/fixtures/3.4/circular_parameters.txt new file mode 100644 index 00000000000000..11537023ada61a --- /dev/null +++ b/test/prism/fixtures/3.4/circular_parameters.txt @@ -0,0 +1,4 @@ +def foo(bar = bar) = 42 +def foo(bar: bar) = 42 +proc { |foo = foo| } +proc { |foo: foo| } diff --git a/test/prism/fixtures/3.4/it.txt b/test/prism/fixtures/3.4/it.txt new file mode 100644 index 00000000000000..5410b01e711a3a --- /dev/null +++ b/test/prism/fixtures/3.4/it.txt @@ -0,0 +1,5 @@ +x do + it +end + +-> { it } diff --git a/test/prism/fixtures/3.4/it_indirect_writes.txt b/test/prism/fixtures/3.4/it_indirect_writes.txt new file mode 100644 index 00000000000000..bb87e9483e2a75 --- /dev/null +++ b/test/prism/fixtures/3.4/it_indirect_writes.txt @@ -0,0 +1,23 @@ +tap { it += 1 } + +tap { it ||= 1 } + +tap { it &&= 1 } + +tap { it; it += 1 } + +tap { it; it ||= 1 } + +tap { it; it &&= 1 } + +tap { it += 1; it } + +tap { it ||= 1; it } + +tap { it &&= 1; it } + +tap { it; it += 1; it } + +tap { it; it ||= 1; it } + +tap { it; it &&= 1; it } diff --git a/test/prism/fixtures/3.4/it_read_and_assignment.txt b/test/prism/fixtures/3.4/it_read_and_assignment.txt new file mode 100644 index 00000000000000..2cceeb2a548710 --- /dev/null +++ b/test/prism/fixtures/3.4/it_read_and_assignment.txt @@ -0,0 +1 @@ +42.tap { p it; it = it; p it } diff --git a/test/prism/fixtures/endless_methods_command_call.txt b/test/prism/fixtures/3.5/endless_methods_command_call.txt similarity index 100% rename from test/prism/fixtures/endless_methods_command_call.txt rename to test/prism/fixtures/3.5/endless_methods_command_call.txt diff --git a/test/prism/fixtures/leading_logical.txt b/test/prism/fixtures/3.5/leading_logical.txt similarity index 100% rename from test/prism/fixtures/leading_logical.txt rename to test/prism/fixtures/3.5/leading_logical.txt diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index ddb6ffb40c6632..9d2acfdc1b142e 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -24,9 +24,13 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + if RUBY_VERSION < "3.4.0" + except << "3.4/circular_parameters.txt" + end + # Leaving these out until they are supported by parse.y. - except << "leading_logical.txt" - except << "endless_methods_command_call.txt" + except << "3.5/leading_logical.txt" + except << "3.5/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 3a0da1a2d87d67..9682bf8a322c21 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -43,16 +43,16 @@ class LexTest < TestCase end # https://bugs.ruby-lang.org/issues/20925 - except << "leading_logical.txt" + except << "3.5/leading_logical.txt" # https://bugs.ruby-lang.org/issues/17398#note-12 - except << "endless_methods_command_call.txt" + except << "3.5/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" - Fixture.each(except: except) do |fixture| - define_method(fixture.test_name) { assert_lex(fixture) } + Fixture.each_with_version(except: except) do |fixture, version| + define_method(fixture.test_name(version)) { assert_lex(fixture, version) } end def test_lex_file @@ -97,10 +97,12 @@ def test_parse_lex_file private - def assert_lex(fixture) + def assert_lex(fixture, version) + return unless current_major_minor == version + source = fixture.read - result = Prism.lex_compat(source) + result = Prism.lex_compat(source, version: version) assert_equal [], result.errors Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)| diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 9a3224e8ef8843..d5def0d18f964c 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -32,8 +32,8 @@ class LocalsTest < TestCase "whitequark/ruby_bug_10653.txt", # Leaving these out until they are supported by parse.y. - "leading_logical.txt", - "endless_methods_command_call.txt", + "3.5/leading_logical.txt", + "3.5/endless_methods_command_call.txt", "command_method_call_2.txt" ] diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 10b5fca5eaefef..016fda91f03819 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -65,11 +65,14 @@ class ParserTest < TestCase # 1.. && 2 "ranges.txt", + # https://bugs.ruby-lang.org/issues/20478 + "3.4/circular_parameters.txt", + # Cannot yet handling leading logical operators. - "leading_logical.txt", + "3.5/leading_logical.txt", # Ruby >= 3.5 specific syntax - "endless_methods_command_call.txt", + "3.5/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", @@ -165,9 +168,9 @@ def test_non_prism_builder_class_deprecated if RUBY_VERSION >= "3.3" def test_current_parser_for_current_ruby - major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments + major, minor = current_major_minor.split(".") # Let's just hope there never is a Ruby 3.10 or similar - expected = major * 10 + minor + expected = major.to_i * 10 + minor.to_i assert_equal(expected, Translation::ParserCurrent.new.version) end end @@ -189,7 +192,7 @@ def test_invalid_syntax end def test_it_block_parameter_syntax - it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt") + it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/3.4/it.txt") buffer = Parser::Source::Buffer.new(it_fixture_path) buffer.source = it_fixture_path.read diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 4916ec8d9de752..12c854aea660be 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -9,7 +9,7 @@ class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ # Not yet supported. - "leading_logical.txt", + "3.5/leading_logical.txt", # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", @@ -31,8 +31,16 @@ class RipperTest < TestCase # Ripper fails to understand some structures that span across heredocs. "spanning_heredoc.txt", + "3.3-3.3/block_args_in_array_assignment.txt", + "3.3-3.3/it_with_ordinary_parameter.txt", + "3.3-3.3/keyword_args_in_array_assignment.txt", + "3.3-3.3/return_in_sclass.txt", + + # https://bugs.ruby-lang.org/issues/20478 + "3.4/circular_parameters.txt", + # https://bugs.ruby-lang.org/issues/17398#note-12 - "endless_methods_command_call.txt", + "3.5/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index ec55e41967c6a0..7640ddaf1ca6c0 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -39,7 +39,6 @@ class RubyParserTest < TestCase "dos_endings.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", - "leading_logical.txt", "method_calls.txt", "methods.txt", "multi_write.txt", @@ -77,8 +76,15 @@ class RubyParserTest < TestCase "whitequark/ruby_bug_19281.txt", "whitequark/slash_newline_in_heredocs.txt", - # Ruby >= 3.5 specific syntax - "endless_methods_command_call.txt", + "3.3-3.3/block_args_in_array_assignment.txt", + "3.3-3.3/it_with_ordinary_parameter.txt", + "3.3-3.3/keyword_args_in_array_assignment.txt", + "3.3-3.3/return_in_sclass.txt", + + "3.4/circular_parameters.txt", + + "3.5/endless_methods_command_call.txt", + "3.5/leading_logical.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/snippets_test.rb b/test/prism/snippets_test.rb index 66802c5dc3a1be..3160442cc07653 100644 --- a/test/prism/snippets_test.rb +++ b/test/prism/snippets_test.rb @@ -18,24 +18,24 @@ class SnippetsTest < TestCase "whitequark/multiple_pattern_matches.txt" ] - Fixture.each(except: except) do |fixture| - define_method(fixture.test_name) { assert_snippets(fixture) } + Fixture.each_with_version(except: except) do |fixture, version| + define_method(fixture.test_name(version)) { assert_snippets(fixture, version) } end private # We test every snippet (separated by \n\n) in isolation to ensure the # parser does not try to read bytes further than the end of each snippet. - def assert_snippets(fixture) + def assert_snippets(fixture, version) fixture.read.split(/(?<=\S)\n\n(?=\S)/).each do |snippet| snippet = snippet.rstrip - result = Prism.parse(snippet, filepath: fixture.path) + result = Prism.parse(snippet, filepath: fixture.path, version: version) assert result.success? if !ENV["PRISM_BUILD_MINIMAL"] - dumped = Prism.dump(snippet, filepath: fixture.path) - assert_equal_nodes(result.value, Prism.load(snippet, dumped).value) + dumped = Prism.dump(snippet, filepath: fixture.path, version: version) + assert_equal_nodes(result.value, Prism.load(snippet, dumped, version: version).value) end end end diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index 0be9d1e7da4f36..84871722c9b6ad 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -58,8 +58,12 @@ def snapshot_path File.join(File.expand_path("../..", __dir__), "snapshots", path) end - def test_name - :"test_#{path}" + def test_name(version = nil) + if version + :"test_#{version}_#{path}" + else + :"test_#{path}" + end end def self.each(except: [], &block) @@ -68,6 +72,14 @@ def self.each(except: [], &block) paths.each { |path| yield Fixture.new(path) } end + def self.each_with_version(except: [], &block) + each(except: except) do |fixture| + TestCase.ruby_versions_for(fixture.path).each do |version| + yield fixture, version + end + end + end + def self.custom_base_path? ENV.key?("FIXTURE_BASE") end @@ -217,6 +229,37 @@ def self.windows? RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i) end + # All versions that prism can parse + SYNTAX_VERSIONS = %w[3.3 3.4 3.5] + + # Returns an array of ruby versions that a given filepath should test against: + # test.txt # => all available versions + # 3.4/test.txt # => versions since 3.4 (inclusive) + # 3.4-4.2/test.txt # => verisions since 3.4 (inclusive) up to 4.2 (inclusive) + def self.ruby_versions_for(filepath) + return [ENV['SYNTAX_VERSION']] if ENV['SYNTAX_VERSION'] + + parts = filepath.split("/") + return SYNTAX_VERSIONS if parts.size == 1 + + version_start, version_stop = parts[0].split("-") + if version_stop + SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..SYNTAX_VERSIONS.index(version_stop)] + else + SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..] + end + end + + def current_major_minor + RUBY_VERSION.split(".")[0, 2].join(".") + end + + if RUBY_VERSION >= "3.3.0" + def test_all_syntax_versions_present + assert_include(SYNTAX_VERSIONS, current_major_minor) + end + end + private if RUBY_ENGINE == "ruby" && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism From 839b1fa54f9addd1418541e03fd8396b8ad992c9 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 22 Oct 2025 12:07:26 -0400 Subject: [PATCH 0441/2435] ZJIT: Specialize String#<< to StringAppend (#14861) Fixes https://github.com/Shopify/ruby/issues/805 --- zjit/src/codegen.rs | 6 ++ zjit/src/cruby_methods.rs | 15 +++++ zjit/src/hir.rs | 127 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 402c500c8b17b9..50a7295bbe2672 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -365,6 +365,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state), Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)), &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), + Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), Insn::Param => unreachable!("block.insns should not have Insn::Param"), @@ -2189,6 +2190,11 @@ fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) -> asm_ccall!(asm, rb_str_getbyte, string, index) } +fn gen_string_append(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: Opnd, state: &FrameState) -> Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + asm_ccall!(asm, rb_str_buf_append, string, val) +} + /// Generate a JIT entry that just increments exit_compilation_failure and exits fn gen_compile_error_counter(cb: &mut CodeBlock, compile_error: &CompileError) -> Result { let mut asm = Assembler::new(); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 656ccab7817c91..9d3f5a756b4a47 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -192,6 +192,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); @@ -276,6 +277,20 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir None } +fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + // Inline only StringExact << String, which matches original type check from + // `vm_opt_ltlt`, which checks `RB_TYPE_P(obj, T_STRING)`. + if fun.likely_a(recv, types::StringExact, state) && fun.likely_a(other, types::String, state) { + let recv = fun.coerce_to(block, recv, types::StringExact, state); + let other = fun.coerce_to(block, other, types::String, state); + let _ = fun.push_insn(block, hir::Insn::StringAppend { recv, other, state }); + Some(recv) + } else { + None + } +} + fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if !args.is_empty() { return None; } if fun.likely_a(recv, types::Fixnum, state) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 68ed867e4ddd4d..fbe99d40d3e4dc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -560,6 +560,7 @@ pub enum Insn { StringConcat { strings: Vec, state: InsnId }, /// Call rb_str_getbyte with known-Fixnum index StringGetbyteFixnum { string: InsnId, index: InsnId }, + StringAppend { recv: InsnId, other: InsnId, state: InsnId }, /// Combine count stack values into a regexp ToRegexp { opt: usize, values: Vec, state: InsnId }, @@ -956,6 +957,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::StringGetbyteFixnum { string, index, .. } => { write!(f, "StringGetbyteFixnum {string}, {index}") } + Insn::StringAppend { recv, other, .. } => { + write!(f, "StringAppend {recv}, {other}") + } Insn::ToRegexp { values, opt, .. } => { write!(f, "ToRegexp")?; let mut prefix = " "; @@ -1518,6 +1522,7 @@ impl Function { &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, &StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) }, &StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) }, + &StringAppend { recv, other, state } => StringAppend { recv: find!(recv), other: find!(other), state: find!(state) }, &ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state }, &Test { val } => Test { val: find!(val) }, &IsNil { val } => IsNil { val: find!(val) }, @@ -1712,6 +1717,7 @@ impl Function { Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass), + Insn::StringAppend { .. } => types::StringExact, Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, @@ -2925,6 +2931,11 @@ impl Function { worklist.push_back(string); worklist.push_back(index); } + &Insn::StringAppend { recv, other, state } => { + worklist.push_back(recv); + worklist.push_back(other); + worklist.push_back(state); + } &Insn::ToRegexp { ref values, state, .. } => { worklist.extend(values); worklist.push_back(state); @@ -13919,6 +13930,122 @@ mod opt_tests { "); } + #[test] + fn test_optimize_string_append() { + eval(r#" + def test(x, y) = x << y + test("iron", "fish") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + // TODO: This should be inlined just as in the interpreter + #[test] + fn test_optimize_string_append_non_string() { + eval(r#" + def test(x, y) = x << y + test("iron", 4) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + // TODO: Should be optimized, but is waiting on String#== inlining + #[test] + fn test_optimize_string_append_string_subclass() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test("iron", MyString.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_do_not_optimize_string_subclass_append_string() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test(MyString.new, "iron") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyString@0x1000) + v28:HeapObject[class_exact:MyString] = GuardType v11, HeapObject[class_exact:MyString] + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + #[test] fn test_dont_inline_integer_succ_with_args() { eval(" From 619110b1dbdb9faca177974be4a3ef72f00ad96d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 21 Oct 2025 18:45:43 -0400 Subject: [PATCH 0442/2435] ZJIT: A64: Fix Lea with large displacement and overlapping register Previously, when the output register and the base register are the same in `out = Lea(Mem(out, disp))`, we did out = disp out = out + out Which wasn't the desired `out = out + disp`. Fixes a SEGV with `--zjit-call-threshold=2` in `bootstraptest/test_yjit.rb`. --- zjit/src/backend/arm64/mod.rs | 45 +++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 74b5210f0fb0a6..dadf5d36474a65 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -878,7 +878,9 @@ impl Assembler { /// Do the address calculation of `out_reg = base_reg + disp` fn load_effective_address(cb: &mut CodeBlock, out: A64Opnd, base_reg_no: u8, disp: i32) { let base_reg = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: base_reg_no }); - assert_ne!(31, out.unwrap_reg().reg_no, "Lea sp, [sp, #imm] not always encodable. Use add/sub instead."); + let out_reg_no = out.unwrap_reg().reg_no; + assert_ne!(31, out_reg_no, "Lea sp, [sp, #imm] not always encodable. Use add/sub instead."); + assert_ne!(base_reg_no, out_reg_no, "large displacement need a scratch register"); if ShiftedImmediate::try_from(disp.unsigned_abs() as u64).is_ok() { // Use ADD/SUB if the displacement fits @@ -1203,7 +1205,27 @@ impl Assembler { let &Opnd::Mem(Mem { num_bits: _, base: MemBase::Reg(base_reg_no), disp }) = opnd else { panic!("Unexpected Insn::Lea operand in arm64_emit: {opnd:?}"); }; - load_effective_address(cb, out.into(), base_reg_no, disp); + let out_reg_no = out.unwrap_reg().reg_no; + assert_ne!(31, out_reg_no, "Lea sp, [sp, #imm] not always encodable. Use add/sub instead."); + + let out = A64Opnd::from(out); + let base_reg = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: base_reg_no }); + if ShiftedImmediate::try_from(disp.unsigned_abs() as u64).is_ok() { + // Use ADD/SUB if the displacement fits + add(cb, out, base_reg, A64Opnd::new_imm(disp.into())); + } else { + // Use a scratch reg for `out += displacement` + let disp_reg = if out_reg_no == base_reg_no { + Self::EMIT0_OPND + } else { + out + }; + // Use add_extended() to interpret reg_no=31 as sp + // since the base register is never the zero register. + // Careful! Only the first two operands can refer to sp. + emit_load_value(cb, disp_reg, disp as u64); + add_extended(cb, out, base_reg, disp_reg); + } } Insn::LeaJumpTarget { out, target, .. } => { if let Target::Label(label_idx) = target { @@ -1806,6 +1828,25 @@ mod tests { assert_snapshot!(cb.hexdump(), @"e07b40b2e063208b000180d22000a0f2e063208b000083d2e063208be0230891e02308d1e0ff8292e063208b00ff9fd2c0ffbff2e0ffdff2e0fffff2e063208be08361b2e063208b"); } + #[test] + fn test_load_larg_disp_mem() { + let (mut asm, mut cb) = setup_asm(); + + let extended_ivars = asm.load(Opnd::mem(64, NATIVE_STACK_PTR, 0)); + let result = asm.load(Opnd::mem(VALUE_BITS, extended_ivars, 1000 * SIZEOF_VALUE_I32)); + asm.store(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, 0), result); + + asm.compile_with_num_regs(&mut cb, 1); + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldur x0, [sp] + 0x4: mov x16, #0x1f40 + 0x8: add x0, x0, x16, uxtx + 0xc: ldur x0, [x0] + 0x10: stur x0, [sp] + "); + assert_snapshot!(cb.hexdump(), @"e00340f810e883d20060308b000040f8e00300f8"); + } + #[test] fn test_store() { let (mut asm, mut cb) = setup_asm(); From 4c60fc48b1e88327521ebe0646843d6427dcfe17 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:57:33 +0200 Subject: [PATCH 0443/2435] [ruby/prism] Test against parse.y https://github.com/ruby/prism/commit/17a6a19bbae5c8b438a94816ed67c3852547d859 broke ruby/ruby CI because some tests are only run against parse.y This will catch that in the future. https://github.com/ruby/prism/commit/98e1cd5c04 --- test/prism/fixtures_test.rb | 6 ++++++ test/prism/locals_test.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 9d2acfdc1b142e..0f0577c10d5735 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -28,6 +28,12 @@ class FixturesTest < TestCase except << "3.4/circular_parameters.txt" end + # Valid only on Ruby 3.3 + except << "3.3-3.3/block_args_in_array_assignment.txt" + except << "3.3-3.3/it_with_ordinary_parameter.txt" + except << "3.3-3.3/keyword_args_in_array_assignment.txt" + except << "3.3-3.3/return_in_sclass.txt" + # Leaving these out until they are supported by parse.y. except << "3.5/leading_logical.txt" except << "3.5/endless_methods_command_call.txt" diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index d5def0d18f964c..439625b750a6f1 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -31,6 +31,12 @@ class LocalsTest < TestCase # CRuby is eliminating dead code. "whitequark/ruby_bug_10653.txt", + # Valid only on Ruby 3.3 + "3.3-3.3/block_args_in_array_assignment.txt", + "3.3-3.3/it_with_ordinary_parameter.txt", + "3.3-3.3/keyword_args_in_array_assignment.txt", + "3.3-3.3/return_in_sclass.txt", + # Leaving these out until they are supported by parse.y. "3.5/leading_logical.txt", "3.5/endless_methods_command_call.txt", From 87fdd6d53b51f2d5eb944c7f97aebf728b1ab439 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 10:56:02 -0700 Subject: [PATCH 0444/2435] ZJIT: Support make in zjit_bisect.rb (#14584) Find ZJIT options in RUN_OPTS/SPECOPTS and put new ones from the bisection script there too. --- .github/auto_request_review.yml | 1 + tool/zjit_bisect.rb | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 51e0e4db973ca7..814150c90e1da9 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -11,6 +11,7 @@ files: 'doc/zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit] 'defs/jit.mk': [team:jit] + 'tool/zjit_bisect.rb': [team:jit] # Skip github workflow files because the team don't necessarily need to review dependabot updates for GitHub Actions. It's noisy in notifications, and they're auto-merged anyway. options: ignore_draft: true diff --git a/tool/zjit_bisect.rb b/tool/zjit_bisect.rb index 175bbe5feb34e2..997c572f513019 100755 --- a/tool/zjit_bisect.rb +++ b/tool/zjit_bisect.rb @@ -72,7 +72,31 @@ def run_bisect(command, items) bisect_impl(command, [], items) end +def add_zjit_options cmd + if RUBY == "make" + # Automatically detect that we're running a make command instead of a Ruby + # one. Pass the bisection options via RUN_OPTS/SPECOPTS instead. + zjit_opts = cmd.select { |arg| arg.start_with?("--zjit") } + run_opts_index = cmd.find_index { |arg| arg.start_with?("RUN_OPTS=") } + specopts_index = cmd.find_index { |arg| arg.start_with?("SPECOPTS=") } + if run_opts_index + run_opts = Shellwords.split(cmd[run_opts_index].delete_prefix("RUN_OPTS=")) + run_opts.concat(zjit_opts) + cmd[run_opts_index] = "RUN_OPTS=#{run_opts.shelljoin}" + elsif specopts_index + specopts = Shellwords.split(cmd[specopts_index].delete_prefix("SPECOPTS=")) + specopts.concat(zjit_opts) + cmd[specopts_index] = "SPECOPTS=#{specopts.shelljoin}" + else + raise "Expected RUN_OPTS or SPECOPTS to be present in make command" + end + cmd = cmd - zjit_opts + end + cmd +end + def run_ruby *cmd + cmd = add_zjit_options(cmd) pid = Process.spawn(*cmd, { in: :close, out: [File::NULL, File::RDWR], @@ -128,7 +152,7 @@ def run_with_jit_list(ruby, options, jit_list) file.puts(result) end puts "Run:" -command = [RUBY, "--zjit-allowed-iseqs=jitlist.txt", *OPTIONS].shelljoin -puts command +jitlist_path = File.expand_path("jitlist.txt") +puts add_zjit_options([RUBY, "--zjit-allowed-iseqs=#{jitlist_path}", *OPTIONS]).shelljoin puts "Reduced JIT list (available in jitlist.txt):" puts result From f09e74ce2b2794571531c708ed684b47a74a2ce9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 22 Oct 2025 11:14:39 -0700 Subject: [PATCH 0445/2435] ZJIT: Fix some dead code in the backend (#14897) --- zjit/src/backend/arm64/mod.rs | 7 ++-- zjit/src/backend/mod.rs | 3 ++ zjit/src/backend/tests.rs | 62 +++++++++++++---------------------- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index dadf5d36474a65..a6a5fc59582610 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -835,10 +835,8 @@ impl Assembler { } /// Emit a CBZ or CBNZ which branches when a register is zero or non-zero - fn emit_cmp_zero_jump(_cb: &mut CodeBlock, _reg: A64Opnd, _branch_if_zero: bool, target: Target) { - if let Target::Label(_) = target { - unimplemented!("this should be re-implemented with Label for side exits"); - /* + fn emit_cmp_zero_jump(cb: &mut CodeBlock, reg: A64Opnd, branch_if_zero: bool, target: Target) { + if let Target::CodePtr(dst_ptr) = target { let dst_addr = dst_ptr.as_offset(); let src_addr = cb.get_write_ptr().as_offset(); @@ -869,7 +867,6 @@ impl Assembler { emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr); br(cb, Assembler::EMIT0_OPND); } - */ } else { unreachable!("We should only generate Joz/Jonz with side-exit targets"); } diff --git a/zjit/src/backend/mod.rs b/zjit/src/backend/mod.rs index ee861f5bd3562a..635acbf60c1009 100644 --- a/zjit/src/backend/mod.rs +++ b/zjit/src/backend/mod.rs @@ -12,4 +12,7 @@ pub use x86_64 as current; #[cfg(target_arch = "aarch64")] pub use arm64 as current; +#[cfg(test)] +mod tests; + pub mod lir; diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index aca775edd66cb5..1b72777212cb23 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -1,19 +1,21 @@ #![cfg(test)] use crate::asm::CodeBlock; -use crate::backend::*; +use crate::backend::lir::*; use crate::cruby::*; -use crate::utils::c_callable; +use crate::codegen::c_callable; +use crate::options::rb_zjit_prepare_options; #[test] fn test_add() { - let mut asm = Assembler::new(0); + let mut asm = Assembler::new(); let out = asm.add(SP, Opnd::UImm(1)); let _ = asm.add(out, Opnd::UImm(2)); } #[test] fn test_alloc_regs() { - let mut asm = Assembler::new(0); + rb_zjit_prepare_options(); // for asm.alloc_regs + let mut asm = Assembler::new(); // Get the first output that we're going to reuse later. let out1 = asm.add(EC, Opnd::UImm(1)); @@ -36,7 +38,7 @@ fn test_alloc_regs() { let _ = asm.add(out3, Opnd::UImm(6)); // Here we're going to allocate the registers. - let result = asm.alloc_regs(Assembler::get_alloc_regs()); + let result = asm.alloc_regs(Assembler::get_alloc_regs()).unwrap(); // Now we're going to verify that the out field has been appropriately // updated for each of the instructions that needs it. @@ -62,8 +64,8 @@ fn test_alloc_regs() { fn setup_asm() -> (Assembler, CodeBlock) { return ( - Assembler::new(0), - CodeBlock::new_dummy(1024) + Assembler::new(), + CodeBlock::new_dummy() ); } @@ -86,6 +88,7 @@ fn test_compile() fn test_mov_mem2mem() { let (mut asm, mut cb) = setup_asm(); + rb_zjit_prepare_options(); // for asm_comment asm_comment!(asm, "check that comments work too"); asm.mov(Opnd::mem(64, SP, 0), Opnd::mem(64, SP, 8)); @@ -163,11 +166,10 @@ fn test_base_insn_out() ); // Load the pointer into a register - let ptr_reg = asm.load(Opnd::const_ptr(4351776248 as *const u8)); - let counter_opnd = Opnd::mem(64, ptr_reg, 0); + let ptr_opnd = Opnd::const_ptr(4351776248 as *const u8); // Increment and store the updated value - asm.incr_counter(counter_opnd, 1.into()); + asm.incr_counter(ptr_opnd, 1.into()); asm.compile_with_num_regs(&mut cb, 2); } @@ -180,6 +182,7 @@ fn test_c_call() } let (mut asm, mut cb) = setup_asm(); + rb_zjit_prepare_options(); // for asm.compile let ret_val = asm.ccall( dummy_c_fun as *const u8, @@ -189,17 +192,17 @@ fn test_c_call() // Make sure that the call's return value is usable asm.mov(Opnd::mem(64, SP, 0), ret_val); - asm.compile_with_num_regs(&mut cb, 1); + asm.compile(&mut cb).unwrap(); } #[test] fn test_alloc_ccall_regs() { - let mut asm = Assembler::new(0); + let mut asm = Assembler::new(); let out1 = asm.ccall(0 as *const u8, vec![]); let out2 = asm.ccall(0 as *const u8, vec![out1]); asm.mov(EC, out2); - let mut cb = CodeBlock::new_dummy(1024); - asm.compile_with_regs(&mut cb, None, Assembler::get_alloc_regs()); + let mut cb = CodeBlock::new_dummy(); + asm.compile_with_regs(&mut cb, Assembler::get_alloc_regs()).unwrap(); } #[test] @@ -220,7 +223,7 @@ fn test_jcc_label() let label = asm.new_label("foo"); asm.cmp(EC, EC); - asm.je(label); + asm.je(label.clone()); asm.write_label(label); asm.compile_with_num_regs(&mut cb, 1); @@ -281,31 +284,11 @@ fn test_bake_string() { asm.compile_with_num_regs(&mut cb, 0); } -#[test] -fn test_draining_iterator() { - let mut asm = Assembler::new(0); - - let _ = asm.load(Opnd::None); - asm.store(Opnd::None, Opnd::None); - let _ = asm.add(Opnd::None, Opnd::None); - - let mut iter = asm.into_draining_iter(); - - while let Some((index, insn)) = iter.next_unmapped() { - match index { - 0 => assert!(matches!(insn, Insn::Load { .. })), - 1 => assert!(matches!(insn, Insn::Store { .. })), - 2 => assert!(matches!(insn, Insn::Add { .. })), - _ => panic!("Unexpected instruction index"), - }; - } -} - #[test] fn test_cmp_8_bit() { let (mut asm, mut cb) = setup_asm(); let reg = Assembler::get_alloc_regs()[0]; - asm.cmp(Opnd::Reg(reg).with_num_bits(8).unwrap(), Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); + asm.cmp(Opnd::Reg(reg).with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); asm.compile_with_num_regs(&mut cb, 1); } @@ -314,7 +297,8 @@ fn test_cmp_8_bit() { fn test_no_pos_marker_callback_when_compile_fails() { // When compilation fails (e.g. when out of memory), the code written out is malformed. // We don't want to invoke the pos_marker callbacks with positions of malformed code. - let mut asm = Assembler::new(0); + let mut asm = Assembler::new(); + rb_zjit_prepare_options(); // for asm.compile // Markers around code to exhaust memory limit let fail_if_called = |_code_ptr, _cb: &_| panic!("pos_marker callback should not be called"); @@ -324,6 +308,6 @@ fn test_no_pos_marker_callback_when_compile_fails() { asm.store(Opnd::mem(64, SP, 8), sum); asm.pos_marker(fail_if_called); - let cb = &mut CodeBlock::new_dummy(8); - assert!(asm.compile(cb, None).is_none(), "should fail due to tiny size limit"); + let cb = &mut CodeBlock::new_dummy_sized(8); + assert!(asm.compile(cb).is_err(), "should fail due to tiny size limit"); } From ceed406958349ccd3d29d86ab5b4af9aaf4616e0 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 11:19:08 -0700 Subject: [PATCH 0446/2435] ZJIT: Inline simple SendWithoutBlockDirect (#14888) Copy the YJIT simple inliner except for the kwargs bit. It works great! --- zjit.rb | 1 + zjit/src/hir.rs | 441 +++++++++++++++++++++++++++++++++++++++++++--- zjit/src/stats.rs | 1 + 3 files changed, 421 insertions(+), 22 deletions(-) diff --git a/zjit.rb b/zjit.rb index fdfe4ce9835a13..88c572849c7529 100644 --- a/zjit.rb +++ b/zjit.rb @@ -176,6 +176,7 @@ def stats_string :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, + :inline_iseq_optimized_send_count, :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, ], buf:, stats:, right_align: true, base: :send_count) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index fbe99d40d3e4dc..7def0b090eb602 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1358,6 +1358,72 @@ pub struct Function { profiles: Option, } +/// The kind of a value an ISEQ returns +enum IseqReturn { + Value(VALUE), + LocalVariable(u32), + Receiver, +} + +unsafe extern "C" { + fn rb_simple_iseq_p(iseq: IseqPtr) -> bool; +} + +/// Return the ISEQ's return value if it consists of one simple instruction and leave. +fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: u32) -> Option { + // Expect only two instructions and one possible operand + // NOTE: If an ISEQ has an optional keyword parameter with a default value that requires + // computation, the ISEQ will always have more than two instructions and won't be inlined. + let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; + if !(2..=3).contains(&iseq_size) { + return None; + } + + // Get the first two instructions + let first_insn = iseq_opcode_at_idx(iseq, 0); + let second_insn = iseq_opcode_at_idx(iseq, insn_len(first_insn as usize)); + + // Extract the return value if known + if second_insn != YARVINSN_leave { + return None; + } + match first_insn { + YARVINSN_getlocal_WC_0 => { + // Accept only cases where only positional arguments are used by both the callee and the caller. + // Keyword arguments may be specified by the callee or the caller but not used. + if captured_opnd.is_some() + // Equivalent to `VM_CALL_ARGS_SIMPLE - VM_CALL_KWARG - has_block_iseq` + || ci_flags & ( + VM_CALL_ARGS_SPLAT + | VM_CALL_KW_SPLAT + | VM_CALL_ARGS_BLOCKARG + | VM_CALL_FORWARDING + ) != 0 + { + return None; + } + + let ep_offset = unsafe { *rb_iseq_pc_at_idx(iseq, 1) }.as_u32(); + let local_idx = ep_offset_to_local_idx(iseq, ep_offset); + + if unsafe { rb_simple_iseq_p(iseq) } { + return Some(IseqReturn::LocalVariable(local_idx.try_into().unwrap())); + } + + // TODO(max): Support only_kwparam case where the local_idx is a positional parameter + + return None; + } + YARVINSN_putnil => Some(IseqReturn::Value(Qnil)), + YARVINSN_putobject => Some(IseqReturn::Value(unsafe { *rb_iseq_pc_at_idx(iseq, 1) })), + YARVINSN_putobject_INT2FIX_0_ => Some(IseqReturn::Value(VALUE::fixnum_from_usize(0))), + YARVINSN_putobject_INT2FIX_1_ => Some(IseqReturn::Value(VALUE::fixnum_from_usize(1))), + // We don't support invokeblock for now. Such ISEQs are likely not used by blocks anyway. + YARVINSN_putself if captured_opnd.is_none() => Some(IseqReturn::Receiver), + _ => None, + } +} + impl Function { fn new(iseq: *const rb_iseq_t) -> Function { Function { @@ -2343,6 +2409,46 @@ impl Function { self.infer_types(); } + fn inline(&mut self) { + for block in self.rpo() { + let old_insns = std::mem::take(&mut self.blocks[block.0].insns); + assert!(self.blocks[block.0].insns.is_empty()); + for insn_id in old_insns { + match self.find(insn_id) { + // Reject block ISEQs to avoid autosplat and other block parameter complications. + Insn::SendWithoutBlockDirect { recv, iseq, cd, args, .. } => { + let call_info = unsafe { (*cd).ci }; + let ci_flags = unsafe { vm_ci_flag(call_info) }; + // .send call is not currently supported for builtins + if ci_flags & VM_CALL_OPT_SEND != 0 { + self.push_insn_id(block, insn_id); continue; + } + let Some(value) = iseq_get_return_value(iseq, None, ci_flags) else { + self.push_insn_id(block, insn_id); continue; + }; + match value { + IseqReturn::LocalVariable(idx) => { + self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); + self.make_equal_to(insn_id, args[idx as usize]); + } + IseqReturn::Value(value) => { + self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); + let replacement = self.push_insn(block, Insn::Const { val: Const::Value(value) }); + self.make_equal_to(insn_id, replacement); + } + IseqReturn::Receiver => { + self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); + self.make_equal_to(insn_id, recv); + } + } + } + _ => { self.push_insn_id(block, insn_id); } + } + } + } + self.infer_types(); + } + fn optimize_getivar(&mut self) { for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); @@ -3208,6 +3314,8 @@ impl Function { // Function is assumed to have types inferred already self.type_specialize(); #[cfg(debug_assertions)] self.assert_validates(); + self.inline(); + #[cfg(debug_assertions)] self.assert_validates(); self.optimize_getivar(); #[cfg(debug_assertions)] self.assert_validates(); self.optimize_c_calls(); @@ -8980,15 +9088,14 @@ mod opt_tests { #[test] fn test_optimize_top_level_call_into_send_direct() { eval(" - def foo - end + def foo = [] def test foo end test; test "); assert_snapshot!(hir_string("test"), @r" - fn test@:5: + fn test@:4: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -9036,8 +9143,7 @@ mod opt_tests { #[test] fn test_optimize_private_top_level_call() { eval(" - def foo - end + def foo = [] private :foo def test foo @@ -9045,7 +9151,7 @@ mod opt_tests { test; test "); assert_snapshot!(hir_string("test"), @r" - fn test@:6: + fn test@:5: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -9094,15 +9200,14 @@ mod opt_tests { #[test] fn test_optimize_top_level_call_with_args_into_send_direct() { eval(" - def foo a, b - end + def foo(a, b) = [] def test foo 1, 2 end test; test "); assert_snapshot!(hir_string("test"), @r" - fn test@:5: + fn test@:4: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -9125,10 +9230,8 @@ mod opt_tests { #[test] fn test_optimize_top_level_sends_into_send_direct() { eval(" - def foo - end - def bar - end + def foo = [] + def bar = [] def test foo bar @@ -9136,7 +9239,7 @@ mod opt_tests { test; test "); assert_snapshot!(hir_string("test"), @r" - fn test@:7: + fn test@:5: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -10656,9 +10759,7 @@ mod opt_tests { fn test_send_direct_to_instance_method() { eval(" class C - def foo - 3 - end + def foo = [] end def test(c) = c.foo @@ -10668,7 +10769,7 @@ mod opt_tests { "); assert_snapshot!(hir_string("test"), @r" - fn test@:8: + fn test@:6: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -12097,7 +12198,7 @@ mod opt_tests { fn test_dont_optimize_array_aref_if_redefined() { eval(r##" class Array - def [](index); end + def [](index) = [] end def test = [4,5,6].freeze[10] "##); @@ -12126,7 +12227,7 @@ mod opt_tests { fn test_dont_optimize_array_max_if_redefined() { eval(r##" class Array - def max = 10 + def max = [] end def test = [4,5,6].max "##); @@ -12797,9 +12898,10 @@ mod opt_tests { PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Object@0x1000) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) CheckInterrupts - Return v20 + Return v22 "); } @@ -14617,4 +14719,299 @@ mod opt_tests { Return v25 "); } + + #[test] + fn test_inline_send_without_block_direct_putself() { + eval(r#" + def callee = self + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_string() { + eval(r#" + # frozen_string_literal: true + def callee = "abc" + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putnil() { + eval(r#" + def callee = nil + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_true() { + eval(r#" + def callee = true + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:TrueClass = Const Value(true) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_false() { + eval(r#" + def callee = false + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:FalseClass = Const Value(false) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_zero() { + eval(r#" + def callee = 0 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_one() { + eval(r#" + def callee = 1 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_parameter() { + eval(r#" + def callee(x) = x + def test = callee 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_inline_send_without_block_direct_last_parameter() { + eval(r#" + def callee(x, y, z) = z + def test = callee 1, 2, 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + v12:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_symbol_to_sym() { + eval(r#" + def test(o) = o.to_sym + test :foo + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_sym@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_integer_to_i() { + eval(r#" + def test(o) = o.to_i + test 5 + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, to_i@0x1008, cme:0x1010) + v21:Fixnum = GuardType v9, Fixnum + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 913a72fa5646ff..4dd87d269ad4e7 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -179,6 +179,7 @@ make_counters! { optimized_send { iseq_optimized_send_count, inline_cfunc_optimized_send_count, + inline_iseq_optimized_send_count, non_variadic_cfunc_optimized_send_count, variadic_cfunc_optimized_send_count, } From 6fdcd08eb5c08415ee5ca0352b1673cfe769d4d8 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 11:22:53 -0700 Subject: [PATCH 0447/2435] ZJIT: Fix land race --- zjit/src/hir.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7def0b090eb602..e8a366ca6c45f2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12323,14 +12323,15 @@ mod opt_tests { PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Object@0x1000) v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :zero (0x1038) + IncrCounter inline_iseq_optimized_send_count + v30:StaticSymbol[:b] = Const Value(VALUE(0x1038)) PatchPoint SingleRactorMode PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Object@0x1000) v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v28:BasicObject = SendWithoutBlockDirect v27, :one (0x1038), v23 + IncrCounter inline_iseq_optimized_send_count CheckInterrupts - Return v28 + Return v30 "); } @@ -12408,9 +12409,9 @@ mod opt_tests { v12:Fixnum[100] = Const Value(100) PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) - v25:BasicObject = SendWithoutBlockDirect v22, :identity (0x1048), v12 + IncrCounter inline_iseq_optimized_send_count CheckInterrupts - Return v25 + Return v12 "); } From 18e8e446acd32ed8948dfa0df90042e8d597b826 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 22 Oct 2025 14:53:12 -0400 Subject: [PATCH 0448/2435] ZJIT: Revert removal of empty samples from zjit trace exits (#14905) ZJIT: Revert 9a75c05 --- zjit.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/zjit.rb b/zjit.rb index 88c572849c7529..e5fdb3fb484a86 100644 --- a/zjit.rb +++ b/zjit.rb @@ -103,9 +103,6 @@ def exit_locations # These values are mandatory to include for stackprof, but we don't use them. results[:missed_samples] = 0 results[:gc_samples] = 0 - - results[:frames].reject! { |k, v| v[:samples] == 0 } - results end From d792de29bc942605b598db028a66379eaf0a0d34 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 20 Oct 2025 20:46:01 -0400 Subject: [PATCH 0449/2435] ZJIT: Fix Type::from_class for subclasses of built-in types --- zjit/src/codegen.rs | 2 +- zjit/src/hir.rs | 14 +++---- zjit/src/hir_type/gen_hir_type.rb | 30 ++++++++++---- zjit/src/hir_type/hir_type.inc.rs | 19 ++++++++- zjit/src/hir_type/mod.rs | 69 +++++++++++++++++++++++++------ 5 files changed, 104 insertions(+), 30 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 50a7295bbe2672..1fd9d5c1bf60a2 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1611,7 +1611,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard let tag = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64)); asm.cmp(tag, Opnd::UImm(RUBY_T_STRING as u64)); asm.jne(side); - } else if guard_type.bit_equal(types::HeapObject) { + } else if guard_type.bit_equal(types::HeapBasicObject) { let side_exit = side_exit(jit, state, GuardType(guard_type)); asm.cmp(val, Opnd::Value(Qfalse)); asm.je(side_exit.clone()); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e8a366ca6c45f2..6b873401ca7dc5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1794,7 +1794,7 @@ impl Function { Insn::HashDup { .. } => types::HashExact, Insn::NewRange { .. } => types::RangeExact, Insn::NewRangeFixnum { .. } => types::RangeExact, - Insn::ObjectAlloc { .. } => types::HeapObject, + Insn::ObjectAlloc { .. } => types::HeapBasicObject, Insn::ObjectAllocClass { class, .. } => Type::from_class(*class), &Insn::CCallWithFrame { return_type, .. } => return_type, Insn::CCall { return_type, .. } => *return_type, @@ -2474,7 +2474,7 @@ impl Function { // too-complex shapes can't use index access self.push_insn_id(block, insn_id); continue; } - let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapObject, state }); + let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); let mut ivar_index: u16 = 0; let replacement = if ! unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { @@ -7089,7 +7089,7 @@ mod tests { v12:NilClass = Const Value(nil) v14:CBool = IsMethodCFunc v11, :new IfFalse v14, bb3(v6, v12, v11) - v16:HeapObject = ObjectAlloc v11 + v16:HeapBasicObject = ObjectAlloc v11 v18:BasicObject = SendWithoutBlock v16, :initialize CheckInterrupts Jump bb4(v6, v16, v18) @@ -10608,7 +10608,7 @@ mod opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MY_MODULE) - v19:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) CheckInterrupts Return v19 "); @@ -11283,7 +11283,7 @@ mod opt_tests { v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) - v16:HeapObject = ObjectAlloc v40 + v16:HeapBasicObject = ObjectAlloc v40 PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Set@0x1008) v46:SetExact = GuardType v16, SetExact @@ -13556,7 +13556,7 @@ mod opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] v29:Fixnum = GuardType v12, Fixnum v30:BasicObject = ArrayArefFixnum v28, v29 IncrCounter inline_cfunc_optimized_send_count @@ -13650,7 +13650,7 @@ mod opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] v29:BasicObject = HashAref v28, v12 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 1ab6adf2eb16f1..653efa6f8f2544 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -58,20 +58,26 @@ def to_graphviz type $subclass = [basic_object_subclass.name, object_subclass.name] $builtin_exact = [basic_object_exact.name, object_exact.name] -$c_names = { +$exact_c_names = { "ObjectExact" => "rb_cObject", "BasicObjectExact" => "rb_cBasicObject", } +$inexact_c_names = { + "Object" => "rb_cObject", + "BasicObject" => "rb_cBasicObject", +} + # Define a new type that can be subclassed (most of them). # If c_name is given, mark the rb_cXYZ object as equivalent to this exact type. def base_type name, c_name: nil type = $object.subtype name exact = type.subtype(name+"Exact") + subclass = type.subtype(name+"Subclass") if c_name - $c_names[exact.name] = c_name + $exact_c_names[exact.name] = c_name + $inexact_c_names[subclass.name] = c_name end - subclass = type.subtype(name+"Subclass") $builtin_exact << exact.name $subclass << subclass.name [type, exact] @@ -81,7 +87,7 @@ def base_type name, c_name: nil # If c_name is given, mark the rb_cXYZ object as equivalent to this type. def final_type name, base: $object, c_name: nil if c_name - $c_names[name] = c_name + $exact_c_names[name] = c_name end type = base.subtype name $builtin_exact << type.name @@ -171,8 +177,10 @@ def add_union name, type_names add_union "Subclass", $subclass add_union "BoolExact", [true_exact.name, false_exact.name] add_union "Immediate", [fixnum.name, flonum.name, static_sym.name, nil_exact.name, true_exact.name, false_exact.name, undef_.name] -$bits["HeapObject"] = ["BasicObject & !Immediate"] -$numeric_bits["HeapObject"] = $numeric_bits["BasicObject"] & ~$numeric_bits["Immediate"] +$bits["HeapBasicObject"] = ["BasicObject & !Immediate"] +$numeric_bits["HeapBasicObject"] = $numeric_bits["BasicObject"] & ~$numeric_bits["Immediate"] +$bits["HeapObject"] = ["Object & !Immediate"] +$numeric_bits["HeapObject"] = $numeric_bits["Object"] & ~$numeric_bits["Immediate"] # ===== Finished generating the DAG; write Rust code ===== @@ -198,8 +206,14 @@ def add_union name, type_names $bits.keys.sort.map {|type_name| puts " pub const #{type_name}: Type = Type::from_bits(bits::#{type_name});" } -puts " pub const ExactBitsAndClass: [(u64, *const VALUE); #{$c_names.size}] = [" -$c_names.each {|type_name, c_name| +puts " pub const ExactBitsAndClass: [(u64, *const VALUE); #{$exact_c_names.size}] = [" +$exact_c_names.each {|type_name, c_name| + puts " (bits::#{type_name}, &raw const crate::cruby::#{c_name})," +} +puts " ];" +$inexact_c_names = $inexact_c_names.to_a.sort_by {|name, _| $bits[name]}.to_h +puts " pub const InexactBitsAndClass: [(u64, *const VALUE); #{$inexact_c_names.size}] = [" +$inexact_c_names.each {|type_name, c_name| puts " (bits::#{type_name}, &raw const crate::cruby::#{c_name})," } puts " ];" diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index c392735742d386..bad45a737644fb 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -37,8 +37,9 @@ mod bits { pub const Hash: u64 = HashExact | HashSubclass; pub const HashExact: u64 = 1u64 << 23; pub const HashSubclass: u64 = 1u64 << 24; + pub const HeapBasicObject: u64 = BasicObject & !Immediate; pub const HeapFloat: u64 = 1u64 << 25; - pub const HeapObject: u64 = BasicObject & !Immediate; + pub const HeapObject: u64 = Object & !Immediate; pub const Immediate: u64 = FalseClass | Fixnum | Flonum | NilClass | StaticSymbol | TrueClass | Undef; pub const Integer: u64 = Bignum | Fixnum; pub const Module: u64 = Class | ModuleExact | ModuleSubclass; @@ -69,7 +70,7 @@ mod bits { pub const Symbol: u64 = DynamicSymbol | StaticSymbol; pub const TrueClass: u64 = 1u64 << 42; pub const Undef: u64 = 1u64 << 43; - pub const AllBitPatterns: [(&'static str, u64); 69] = [ + pub const AllBitPatterns: [(&'static str, u64); 70] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -79,6 +80,7 @@ mod bits { ("BuiltinExact", BuiltinExact), ("BoolExact", BoolExact), ("TrueClass", TrueClass), + ("HeapBasicObject", HeapBasicObject), ("HeapObject", HeapObject), ("String", String), ("Subclass", Subclass), @@ -181,6 +183,7 @@ pub mod types { pub const Hash: Type = Type::from_bits(bits::Hash); pub const HashExact: Type = Type::from_bits(bits::HashExact); pub const HashSubclass: Type = Type::from_bits(bits::HashSubclass); + pub const HeapBasicObject: Type = Type::from_bits(bits::HeapBasicObject); pub const HeapFloat: Type = Type::from_bits(bits::HeapFloat); pub const HeapObject: Type = Type::from_bits(bits::HeapObject); pub const Immediate: Type = Type::from_bits(bits::Immediate); @@ -232,4 +235,16 @@ pub mod types { (bits::TrueClass, &raw const crate::cruby::rb_cTrueClass), (bits::FalseClass, &raw const crate::cruby::rb_cFalseClass), ]; + pub const InexactBitsAndClass: [(u64, *const VALUE); 10] = [ + (bits::ArraySubclass, &raw const crate::cruby::rb_cArray), + (bits::HashSubclass, &raw const crate::cruby::rb_cHash), + (bits::ModuleSubclass, &raw const crate::cruby::rb_cModule), + (bits::NumericSubclass, &raw const crate::cruby::rb_cNumeric), + (bits::RangeSubclass, &raw const crate::cruby::rb_cRange), + (bits::RegexpSubclass, &raw const crate::cruby::rb_cRegexp), + (bits::SetSubclass, &raw const crate::cruby::rb_cSet), + (bits::StringSubclass, &raw const crate::cruby::rb_cString), + (bits::Object, &raw const crate::cruby::rb_cObject), + (bits::BasicObject, &raw const crate::cruby::rb_cBasicObject), + ]; } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index f24161657e8ec7..91d98a6eedc874 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -185,6 +185,14 @@ impl Type { .map(|&(bits, _)| bits) } + fn bits_from_subclass(class: VALUE) -> Option { + types::InexactBitsAndClass + .iter() + .find(|&(_, class_object)| class.is_subclass_of(unsafe { **class_object }) == ClassRelationship::Subclass) + // Can't be an immediate if it's a subclass. + .map(|&(bits, _)| bits & !bits::Immediate) + } + fn from_heap_object(val: VALUE) -> Type { assert!(!val.special_const_p(), "val should be a heap object"); let bits = @@ -199,12 +207,11 @@ impl Type { else if val.class_of() == unsafe { rb_cInteger } { bits::Bignum } else if val.class_of() == unsafe { rb_cFloat } { bits::HeapFloat } else if val.class_of() == unsafe { rb_cSymbol } { bits::DynamicSymbol } + else if let Some(bits) = Self::bits_from_exact_class(val.class_of()) { bits } + else if let Some(bits) = Self::bits_from_subclass(val.class_of()) { bits } else { - Self::bits_from_exact_class(val.class_of()).unwrap_or({ - // We don't have a specific built-in bit pattern for this class, so generalize - // as HeapObject with object specialization. - bits::HeapObject - }) + unreachable!("Class {} is not a subclass of BasicObject! Don't know what to do.", + get_class_name(val.class_of())) }; let spec = Specialization::Object(val); Type { bits, spec } @@ -266,14 +273,14 @@ impl Type { } pub fn from_class(class: VALUE) -> Type { - match Self::bits_from_exact_class(class) { - Some(bits) => Type::from_bits(bits), - None => { - // We don't have a specific built-in bit pattern for this class, so generalize - // as HeapObject with object specialization. - Type { bits: bits::HeapObject, spec: Specialization::TypeExact(class) } - } + if let Some(bits) = Self::bits_from_exact_class(class) { + return Type::from_bits(bits); + } + if let Some(bits) = Self::bits_from_subclass(class) { + return Type { bits, spec: Specialization::TypeExact(class) } } + unreachable!("Class {} is not a subclass of BasicObject! Don't know what to do.", + get_class_name(class)) } /// Private. Only for creating type globals. @@ -613,6 +620,32 @@ mod tests { assert_not_subtype(types::HeapFloat, types::Immediate); } + #[test] + fn heap_basic_object() { + assert_not_subtype(Type::fixnum(123), types::HeapBasicObject); + assert_not_subtype(types::Fixnum, types::HeapBasicObject); + assert_subtype(types::Bignum, types::HeapBasicObject); + assert_not_subtype(types::Integer, types::HeapBasicObject); + assert_not_subtype(types::NilClass, types::HeapBasicObject); + assert_not_subtype(types::TrueClass, types::HeapBasicObject); + assert_not_subtype(types::FalseClass, types::HeapBasicObject); + assert_not_subtype(types::StaticSymbol, types::HeapBasicObject); + assert_subtype(types::DynamicSymbol, types::HeapBasicObject); + assert_not_subtype(types::Flonum, types::HeapBasicObject); + assert_subtype(types::HeapFloat, types::HeapBasicObject); + assert_not_subtype(types::BasicObject, types::HeapBasicObject); + assert_not_subtype(types::Object, types::HeapBasicObject); + assert_not_subtype(types::Immediate, types::HeapBasicObject); + assert_not_subtype(types::HeapBasicObject, types::Immediate); + crate::cruby::with_rubyvm(|| { + let left = Type::from_value(rust_str_to_ruby("hello")); + let right = Type::from_value(rust_str_to_ruby("world")); + assert_subtype(left, types::HeapBasicObject); + assert_subtype(right, types::HeapBasicObject); + assert_subtype(left.union(right), types::HeapBasicObject); + }); + } + #[test] fn heap_object() { assert_not_subtype(Type::fixnum(123), types::HeapObject); @@ -845,6 +878,18 @@ mod tests { }); } + #[test] + fn string_subclass_is_string_subtype() { + use crate::cruby::{rb_callable_method_entry, ID}; + crate::cruby::with_rubyvm(|| { + assert_subtype(types::StringExact, types::String); + assert_subtype(Type::from_class(unsafe { rb_cString }), types::String); + assert_subtype(Type::from_class(unsafe { rb_cString }), types::StringExact); + let c_class = define_class("C", unsafe { rb_cString }); + assert_subtype(Type::from_class(c_class), types::String); + }); + } + #[test] fn union_specialized_with_no_relation_returns_unspecialized() { crate::cruby::with_rubyvm(|| { From 9ad902e55c610e66114f528f77f7895295a242de Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 20 Oct 2025 11:45:59 -0400 Subject: [PATCH 0450/2435] ZJIT: Inline String#==, String#=== --- depend | 1 + jit.c | 9 ++ yjit.c | 8 -- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/src/cruby.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/cruby_methods.rs | 21 ++++ zjit/src/hir.rs | 191 ++++++++++++++++++++++++++++++++- 8 files changed, 221 insertions(+), 13 deletions(-) diff --git a/depend b/depend index fa61de77a00e62..5ed27d04e0c38d 100644 --- a/depend +++ b/depend @@ -7393,6 +7393,7 @@ jit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h jit.$(OBJEXT): $(top_srcdir)/internal/serial.h jit.$(OBJEXT): $(top_srcdir)/internal/set_table.h jit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +jit.$(OBJEXT): $(top_srcdir)/internal/string.h jit.$(OBJEXT): $(top_srcdir)/internal/variable.h jit.$(OBJEXT): $(top_srcdir)/internal/vm.h jit.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/jit.c b/jit.c index 2ff38c28e2d6d3..3111dcc3e372c9 100644 --- a/jit.c +++ b/jit.c @@ -15,6 +15,7 @@ #include "internal/gc.h" #include "vm_sync.h" #include "internal/fixnum.h" +#include "internal/string.h" enum jit_bindgen_constants { // Field offsets for the RObject struct @@ -750,3 +751,11 @@ rb_jit_fix_mod_fix(VALUE recv, VALUE obj) { return rb_fix_mod_fix(recv, obj); } + +// YJIT/ZJIT need this function to never allocate and never raise +VALUE +rb_yarv_str_eql_internal(VALUE str1, VALUE str2) +{ + // We wrap this since it's static inline + return rb_str_eql_internal(str1, str2); +} diff --git a/yjit.c b/yjit.c index 807aec9e391172..3793b0f1ac4857 100644 --- a/yjit.c +++ b/yjit.c @@ -283,14 +283,6 @@ rb_yjit_str_simple_append(VALUE str1, VALUE str2) extern VALUE *rb_vm_base_ptr(struct rb_control_frame_struct *cfp); -// YJIT needs this function to never allocate and never raise -VALUE -rb_yarv_str_eql_internal(VALUE str1, VALUE str2) -{ - // We wrap this since it's static inline - return rb_str_eql_internal(str1, str2); -} - VALUE rb_str_neq_internal(VALUE str1, VALUE str2) { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 272586a79f3fb5..6542e5ef09d4de 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1134,7 +1134,6 @@ extern "C" { pub fn rb_yjit_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function; pub fn rb_yjit_str_simple_append(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; - pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_str_neq_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE; pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; @@ -1274,4 +1273,5 @@ extern "C" { end: *mut ::std::os::raw::c_void, ); pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; + pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 645891496edbae..41e0e847aa4e37 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1342,6 +1342,7 @@ pub(crate) mod ids { name: NULL content: b"" name: respond_to_missing content: b"respond_to_missing?" name: eq content: b"==" + name: string_eq content: b"String#==" name: include_p content: b"include?" name: to_ary name: to_s diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index c9e5bc8fd1ebcb..d9bd2d33c080ed 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1437,4 +1437,5 @@ unsafe extern "C" { start: *mut ::std::os::raw::c_void, end: *mut ::std::os::raw::c_void, ); + pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 9d3f5a756b4a47..eabddce7395474 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -193,6 +193,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); + annotate!(rb_cString, "==", inline_string_eq); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); @@ -291,6 +292,26 @@ fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir: } } +fn inline_string_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + if fun.likely_a(recv, types::String, state) && fun.likely_a(other, types::String, state) { + let recv = fun.coerce_to(block, recv, types::String, state); + let other = fun.coerce_to(block, other, types::String, state); + let return_type = types::BoolExact; + let elidable = true; + // TODO(max): Make StringEqual its own opcode so that we can later constant-fold StringEqual(a, a) => true + let result = fun.push_insn(block, hir::Insn::CCall { + cfunc: rb_yarv_str_eql_internal as *const u8, + args: vec![recv, other], + name: ID!(string_eq), + return_type, + elidable, + }); + return Some(result); + } + None +} + fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if !args.is_empty() { return None; } if fun.likely_a(recv, types::Fixnum, state) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6b873401ca7dc5..91484ca97020f0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -14090,7 +14090,6 @@ mod opt_tests { "); } - // TODO: Should be optimized, but is waiting on String#== inlining #[test] fn test_optimize_string_append_string_subclass() { eval(r#" @@ -14114,9 +14113,11 @@ mod opt_tests { PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v28:StringExact = GuardType v11, StringExact - v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v29 + Return v28 "); } @@ -14142,7 +14143,7 @@ mod opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(MyString@0x1000) - v28:HeapObject[class_exact:MyString] = GuardType v11, HeapObject[class_exact:MyString] + v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 CheckInterrupts Return v29 @@ -15015,4 +15016,186 @@ mod opt_tests { Return v21 "); } + + #[test] + fn test_optimize_stringexact_eq_stringexact() { + eval(r#" + def test(l, r) = l == r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_string_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_stringexact() { + eval(r#" + def test(l, r) = l === r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_string_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } } From fa3a6f5cde5f6cf0b972b845194cb556a36bfd74 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 22 Oct 2025 22:38:03 +0200 Subject: [PATCH 0451/2435] Add docs for --zjit-debug --- doc/zjit.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/zjit.md b/doc/zjit.md index f3db94448d4513..a45128adbdd438 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -162,6 +162,16 @@ A file called `zjit_exits_{pid}.dump` will be created in the same directory as ` stackprof path/to/zjit_exits_{pid}.dump ``` +### Printing ZJIT Errors + +`--zjit-debug` prints ZJIT compilation errors and other diagnostics: + +```bash +./miniruby --zjit-debug script.rb +``` + +As you might guess from the name, this option is intended mostly for ZJIT developers. + ## Useful dev commands To view YARV output for code snippets: From 3861918fd3fc31a7c04a6b44d3ee9bfcf67c75ba Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 22 Oct 2025 17:16:52 -0400 Subject: [PATCH 0452/2435] ZJIT: Handle invalid Counter on --zjit-trace-exits=counter (#14911) --- zjit/src/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/options.rs b/zjit/src/options.rs index b7b20e63c4484d..f4a52e1ccdc6bd 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -261,7 +261,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { ("trace-exits", exits) => { options.trace_side_exits = match exits { "" => Some(TraceExits::All), - name => Counter::get(name).map(TraceExits::Counter), + name => Some(Counter::get(name).map(TraceExits::Counter)?), } } From fee863b4df374d014a8b49ec1fa5ae5e31b2e310 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 22 Oct 2025 16:48:27 -0400 Subject: [PATCH 0453/2435] YJIT: Buffer writes to the perf map --- yjit/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 231655826109ab..2eb66a298eea90 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -438,7 +438,7 @@ impl<'a> JITState<'a> { fn flush_perf_symbols(&self, cb: &CodeBlock) { assert_eq!(0, self.perf_stack.len()); let path = format!("/tmp/perf-{}.map", std::process::id()); - let mut f = std::fs::File::options().create(true).append(true).open(path).unwrap(); + let mut f = std::io::BufWriter::new(std::fs::File::options().create(true).append(true).open(path).unwrap()); for sym in self.perf_map.borrow().iter() { if let (start, Some(end), name) = sym { // In case the code straddles two pages, part of it belongs to the symbol. From 71e3ef3d9ca9b3382ceb1986578386590c548f66 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 22 Oct 2025 16:48:43 -0400 Subject: [PATCH 0454/2435] ZJIT: Buffer writes to the perf map --- zjit/src/codegen.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1fd9d5c1bf60a2..f0ac9b5c7bc249 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -156,10 +156,11 @@ fn gen_iseq_call(cb: &mut CodeBlock, caller_iseq: IseqPtr, iseq_call: &IseqCallR fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) { use std::io::Write; let perf_map = format!("/tmp/perf-{}.map", std::process::id()); - let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(&perf_map) else { + let Ok(file) = std::fs::OpenOptions::new().create(true).append(true).open(&perf_map) else { debug!("Failed to open perf map file: {perf_map}"); return; }; + let mut file = std::io::BufWriter::new(file); let Ok(_) = writeln!(file, "{:#x} {:#x} zjit::{}", start_ptr, code_size, iseq_name) else { debug!("Failed to write {iseq_name} to perf map file: {perf_map}"); return; From f9338a95afbde65f33c7d8af0d3dc361b727ed4c Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Wed, 22 Oct 2025 21:32:03 +0100 Subject: [PATCH 0455/2435] [DOC] Tweaks for String#squeeze --- doc/string/squeeze.rdoc | 33 +++++++++++++++++++++++++++++++++ string.c | 11 +---------- 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 doc/string/squeeze.rdoc diff --git a/doc/string/squeeze.rdoc b/doc/string/squeeze.rdoc new file mode 100644 index 00000000000000..1a38c08b3274a9 --- /dev/null +++ b/doc/string/squeeze.rdoc @@ -0,0 +1,33 @@ +Returns a copy of +self+ with each tuple (doubling, tripling, etc.) of specified characters +"squeezed" down to a single character. + +The tuples to be squeezed are specified by arguments +selectors+, +each of which is a string; +see {Character Selectors}[rdoc-ref:character_selectors.rdoc@Character+Selectors]. + +A single argument may be a single character: + + 'Noooooo!'.squeeze('o') # => "No!" + 'foo bar baz'.squeeze(' ') # => "foo bar baz" + 'Mississippi'.squeeze('s') # => "Misisippi" + 'Mississippi'.squeeze('p') # => "Mississipi" + 'Mississippi'.squeeze('x') # => "Mississippi" # Unused selector character is ignored. + 'бессонница'.squeeze('с') # => "бесонница" + 'бессонница'.squeeze('н') # => "бессоница" + +A single argument may be a string of characters: + + 'Mississippi'.squeeze('sp') # => "Misisipi" + 'Mississippi'.squeeze('ps') # => "Misisipi" # Order doesn't matter. + 'Mississippi'.squeeze('nonsense') # => "Misisippi" # Unused selector characters are ignored. + +A single argument may be a range of characters: + + 'Mississippi'.squeeze('a-p') # => "Mississipi" + 'Mississippi'.squeeze('q-z') # => "Misisippi" + 'Mississippi'.squeeze('a-z') # => "Misisipi" + +Multiple arguments are allowed; +see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 92541622633211..f907798f856057 100644 --- a/string.c +++ b/string.c @@ -8971,16 +8971,7 @@ rb_str_squeeze_bang(int argc, VALUE *argv, VALUE str) * call-seq: * squeeze(*selectors) -> new_string * - * Returns a copy of +self+ with characters specified by +selectors+ "squeezed" - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * "Squeezed" means that each multiple-character run of a selected character - * is squeezed down to a single character; - * with no arguments given, squeezes all characters: - * - * "yellow moon".squeeze #=> "yelow mon" - * " now is the".squeeze(" ") #=> " now is the" - * "putters shoot balls".squeeze("m-z") #=> "puters shot balls" + * :include: doc/string/squeeze.rdoc * */ From d4ea1686b5f7989c241511bed4760dc384ff7b54 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Wed, 22 Oct 2025 20:12:49 +0100 Subject: [PATCH 0456/2435] [DOC] Tweaks for String#split --- doc/string/split.rdoc | 134 ++++++++++++++++++++++-------------------- string.c | 2 +- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/doc/string/split.rdoc b/doc/string/split.rdoc index 131c14b83fcbda..9e61bc5bab3751 100644 --- a/doc/string/split.rdoc +++ b/doc/string/split.rdoc @@ -1,99 +1,103 @@ -Returns an array of substrings of +self+ -that are the result of splitting +self+ +Creates an array of substrings by splitting +self+ at each occurrence of the given field separator +field_sep+. -When +field_sep+ is $;: +With no arguments given, +splits using the field separator $;, +whose default value is +nil+. -- If $; is +nil+ (its default value), - the split occurs just as if +field_sep+ were given as a space character - (see below). +With no block given, returns the array of substrings: -- If $; is a string, - the split occurs just as if +field_sep+ were given as that string - (see below). + 'abracadabra'.split('a') # => ["", "br", "c", "d", "br"] -When +field_sep+ is ' ' and +limit+ is +0+ (its default value), -the split occurs at each sequence of whitespace: +When +field_sep+ is +nil+ or ' ' (a single space), +splits at each sequence of whitespace: - 'abc def ghi'.split(' ') # => ["abc", "def", "ghi"] - "abc \n\tdef\t\n ghi".split(' ') # => ["abc", "def", "ghi"] - 'abc def ghi'.split(' ') # => ["abc", "def", "ghi"] + 'foo bar baz'.split(nil) # => ["foo", "bar", "baz"] + 'foo bar baz'.split(' ') # => ["foo", "bar", "baz"] + "foo \n\tbar\t\n baz".split(' ') # => ["foo", "bar", "baz"] + 'foo bar baz'.split(' ') # => ["foo", "bar", "baz"] ''.split(' ') # => [] -When +field_sep+ is a string different from ' ' -and +limit+ is +0+, -the split occurs at each occurrence of +field_sep+; -trailing empty substrings are not returned: +When +field_sep+ is an empty string, +splits at every character: - 'abracadabra'.split('ab') # => ["", "racad", "ra"] - 'aaabcdaaa'.split('a') # => ["", "", "", "bcd"] - ''.split('a') # => [] - '3.14159'.split('1') # => ["3.", "4", "59"] - '!@#$%^$&*($)_+'.split('$') # => ["!@#", "%^", "&*(", ")_+"] - 'тест'.split('т') # => ["", "ес"] - 'こんにちは'.split('に') # => ["こん", "ちは"] + 'abracadabra'.split('') # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"] + ''.split('') # => [] + 'тест'.split('') # => ["т", "е", "с", "т"] + 'こんにちは'.split('') # => ["こ", "ん", "に", "ち", "は"] -When +field_sep+ is a Regexp and +limit+ is +0+, -the split occurs at each occurrence of a match; -trailing empty substrings are not returned: +When +field_sep+ is a non-empty string and different from ' ' (a single space), +uses that string as the separator: + + 'abracadabra'.split('a') # => ["", "br", "c", "d", "br"] + 'abracadabra'.split('ab') # => ["", "racad", "ra"] + ''.split('a') # => [] + 'тест'.split('т') # => ["", "ес"] + 'こんにちは'.split('に') # => ["こん", "ちは"] + +When +field_sep+ is a Regexp, +splits at each occurrence of a matching substring: 'abracadabra'.split(/ab/) # => ["", "racad", "ra"] - 'aaabcdaaa'.split(/a/) # => ["", "", "", "bcd"] - 'aaabcdaaa'.split(//) # => ["a", "a", "a", "b", "c", "d", "a", "a", "a"] '1 + 1 == 2'.split(/\W+/) # => ["1", "1", "2"] + 'abracadabra'.split(//) # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"] -If the \Regexp contains groups, their matches are also included +If the \Regexp contains groups, their matches are included in the returned array: '1:2:3'.split(/(:)()()/, 2) # => ["1", ":", "", "", "2:3"] -As seen above, if +limit+ is +0+, -trailing empty substrings are not returned: +Argument +limit+ sets a limit on the size of the returned array; +it also determines whether trailing empty strings are included in the returned array. - 'aaabcdaaa'.split('a') # => ["", "", "", "bcd"] +When +limit+ is zero, +there is no limit on the size of the array, +but trailing empty strings are omitted: -If +limit+ is positive integer +n+, no more than n - 1- -splits occur, so that at most +n+ substrings are returned, -and trailing empty substrings are included: + 'abracadabra'.split('', 0) # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"] + 'abracadabra'.split('a', 0) # => ["", "br", "c", "d", "br"] # Empty string after last 'a' omitted. - 'aaabcdaaa'.split('a', 1) # => ["aaabcdaaa"] - 'aaabcdaaa'.split('a', 2) # => ["", "aabcdaaa"] - 'aaabcdaaa'.split('a', 5) # => ["", "", "", "bcd", "aa"] - 'aaabcdaaa'.split('a', 7) # => ["", "", "", "bcd", "", "", ""] - 'aaabcdaaa'.split('a', 8) # => ["", "", "", "bcd", "", "", ""] +When +limit+ is a positive integer, +there is a limit on the size of the array (no more than n - 1 splits occur), +and trailing empty strings are included: -Note that if +field_sep+ is a \Regexp containing groups, -their matches are in the returned array, but do not count toward the limit. + 'abracadabra'.split('', 3) # => ["a", "b", "racadabra"] + 'abracadabra'.split('a', 3) # => ["", "br", "cadabra"] + 'abracadabra'.split('', 30) # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a", ""] + 'abracadabra'.split('a', 30) # => ["", "br", "c", "d", "br", ""] + 'abracadabra'.split('', 1) # => ["abracadabra"] + 'abracadabra'.split('a', 1) # => ["abracadabra"] -If +limit+ is negative, it behaves the same as if +limit+ was zero, -meaning that there is no limit, -and trailing empty substrings are included: +When +limit+ is negative, +there is no limit on the size of the array, +and trailing empty strings are omitted: - 'aaabcdaaa'.split('a', -1) # => ["", "", "", "bcd", "", "", ""] + 'abracadabra'.split('', -1) # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a", ""] + 'abracadabra'.split('a', -1) # => ["", "br", "c", "d", "br", ""] If a block is given, it is called with each substring and returns +self+: - 'abc def ghi'.split(' ') {|substring| p substring } + 'foo bar baz'.split(' ') {|substring| p substring } + +Output : + + "foo" + "bar" + "baz" -Output: +Note that the above example is functionally equivalent to: - "abc" - "def" - "ghi" - => "abc def ghi" + 'foo bar baz'.split(' ').each {|substring| p substring } -Note that the above example is functionally the same as calling +#each+ after -+#split+ and giving the same block. However, the above example has better -performance because it avoids the creation of an intermediate array. Also, -note the different return values. +Output : - 'abc def ghi'.split(' ').each {|substring| p substring } + "foo" + "bar" + "baz" -Output: +But the latter: - "abc" - "def" - "ghi" - => ["abc", "def", "ghi"] +- Has poorer performance because it creates an intermediate array. +- Returns an array (instead of +self+). -Related: String#partition, String#rpartition. +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/string.c b/string.c index f907798f856057..1236057ad177ed 100644 --- a/string.c +++ b/string.c @@ -9192,7 +9192,7 @@ literal_split_pattern(VALUE spat, split_type_t default_type) /* * call-seq: - * split(field_sep = $;, limit = 0) -> array + * split(field_sep = $;, limit = 0) -> array_of_substrings * split(field_sep = $;, limit = 0) {|substring| ... } -> self * * :include: doc/string/split.rdoc From a763e6dd484951759b1b6cb7022b99bdf192895d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 15:40:52 -0700 Subject: [PATCH 0457/2435] ZJIT: Disable not-annotated cfuncs in --zjit-stats (#14915) It's mostly a duplicate of not-inlined-cfuncs right now. --- zjit.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index e5fdb3fb484a86..8b44330b36043a 100644 --- a/zjit.rb +++ b/zjit.rb @@ -153,7 +153,8 @@ def stats_string # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) + # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs + # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) From ae767b6ca859bfc9b18e964494052c7e2e5a41df Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 16:26:37 -0700 Subject: [PATCH 0458/2435] ZJIT: Inline Kernel#block_given? (#14914) Fix https://github.com/Shopify/ruby/issues/832 --- test/ruby/test_zjit.rb | 21 ++++++++ zjit/src/codegen.rs | 16 ++++++ zjit/src/cruby_methods.rs | 8 +++ zjit/src/hir.rs | 106 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 148 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index e151a022d1bc61..44f010d0561002 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1962,6 +1962,27 @@ def test }, call_threshold: 2 end + def test_block_given_p + assert_compiles "false", "block_given?" + assert_compiles '[false, false, true]', %q{ + def test = block_given? + [test, test, test{}] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_block_given_p_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + assert_compiles '[false, false, true]', %q{ + def test + yield_self { yield_self { block_given? } } + end + + [test, test, test{}] + }, call_threshold: 2 + end + def test_invokeblock_without_block_after_jit_call assert_compiles '"no block given (yield)"', %q{ def test(*arr, &b) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f0ac9b5c7bc249..848249d7749bb2 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -449,6 +449,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadSelf => gen_load_self(), &Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index), &Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index), + &Insn::IsBlockGiven => gen_is_block_given(jit, asm), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } @@ -525,6 +526,8 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, // `yield` goes to the block handler stowed in the "local" iseq which is // the current iseq or a parent. Only the "method" iseq type can be passed a // block handler. (e.g. `yield` in the top level script is a syntax error.) + // + // Similar to gen_is_block_given let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) }; if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD { let lep = gen_get_lep(jit, asm); @@ -550,6 +553,19 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, } } +/// Similar to gen_defined for DEFINED_YIELD +fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd { + let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) }; + if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD { + let lep = gen_get_lep(jit, asm); + let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)); + asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into()); + asm.csel_e(Qfalse.into(), Qtrue.into()) + } else { + Qfalse.into() + } +} + /// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs. /// We generate this instruction with level=0 only when the local variable is on the heap, so we /// can't optimize the level=0 case using the SP register. diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index eabddce7395474..ee10eaa681c7e4 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -188,6 +188,7 @@ pub fn init() -> Annotations { } annotate!(rb_mKernel, "itself", inline_kernel_itself); + annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); @@ -247,6 +248,13 @@ fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hi None } +fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[] = args else { return None; }; + // TODO(max): In local iseq types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. + let result = fun.push_insn(block, hir::Insn::IsBlockGiven); + return Some(result); +} + fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if let &[index] = args { if fun.likely_a(index, types::Fixnum, state) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 91484ca97020f0..489ea83a44731a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -610,8 +610,12 @@ pub enum Insn { IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId }, /// Return C `true` if left == right IsBitEqual { left: InsnId, right: InsnId }, + // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, + /// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with + /// `DEFINED_YIELD` + IsBlockGiven, /// Get a global variable named `id` GetGlobal { id: ID, state: InsnId }, @@ -870,6 +874,7 @@ impl Insn { Insn::NewRange { .. } => true, Insn::NewRangeFixnum { .. } => false, Insn::StringGetbyteFixnum { .. } => false, + Insn::IsBlockGiven => false, _ => true, } } @@ -1065,6 +1070,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, + Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => { write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { @@ -1562,6 +1568,7 @@ impl Function { result@(Const {..} | Param {..} | GetConstantPath {..} + | IsBlockGiven | PatchPoint {..} | PutSpecialObject {..} | GetGlobal {..} @@ -1828,6 +1835,7 @@ impl Function { Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, + Insn::IsBlockGiven { .. } => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, @@ -3009,6 +3017,7 @@ impl Function { | &Insn::LoadSelf | &Insn::GetLocal { .. } | &Insn::PutSpecialObject { .. } + | &Insn::IsBlockGiven | &Insn::IncrCounter(_) | &Insn::IncrCounterPtr { .. } => {} @@ -8492,13 +8501,23 @@ mod opt_tests { use super::tests::assert_contains_opcode; #[track_caller] - fn hir_string(method: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + fn hir_string_function(function: &Function) -> String { + format!("{}", FunctionPrinter::without_snapshot(function)) + } + + #[track_caller] + fn hir_string_proc(proc: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; let mut function = iseq_to_hir(iseq).unwrap(); function.optimize(); function.validate().unwrap(); - format!("{}", FunctionPrinter::without_snapshot(&function)) + hir_string_function(&function) + } + + #[track_caller] + fn hir_string(method: &str) -> String { + hir_string_proc(&format!("{}.method(:{})", "self", method)) } #[test] @@ -10671,6 +10690,87 @@ mod opt_tests { "); } + #[test] + fn test_inline_kernel_block_given_p() { + eval(" + def test = block_given? + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_kernel_block_given_p_in_block() { + eval(" + TEST = proc { block_given? } + TEST.call + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_elide_kernel_block_given_p() { + eval(" + def test + block_given? + 5 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_cfunc_optimized_send_count + v14:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v14 + "); + } + #[test] fn const_send_direct_integer() { eval(" From dfcb79ca63336b614392e49e1d903deca26aa3e4 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 22 Oct 2025 19:35:24 -0400 Subject: [PATCH 0459/2435] ZJIT: Fix unused warnings in `make zjit-test` [ci skip] --- zjit/src/hir_type/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 91d98a6eedc874..7e6da62fd0ff44 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -880,7 +880,6 @@ mod tests { #[test] fn string_subclass_is_string_subtype() { - use crate::cruby::{rb_callable_method_entry, ID}; crate::cruby::with_rubyvm(|| { assert_subtype(types::StringExact, types::String); assert_subtype(Type::from_class(unsafe { rb_cString }), types::String); From da4bd3b3df9f26b7941808b2e291a6d6494e6c2f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Oct 2025 20:27:36 -0400 Subject: [PATCH 0460/2435] Fix memory leak when RUBYOPT is invalid When RUBYOPT is invalid, it raises an error which causes moreswitches to leak memory. It can be seen when building with LSAN enabled: $ RUBY_FREE_AT_EXIT=1 RUBYOPT=f ruby ruby: invalid option -f (-h will show valid options) (RuntimeError) Direct leak of 16 byte(s) in 1 object(s) allocated from: #0 0x618cef8efa23 in malloc (miniruby+0x64a23) #1 0x618cefa0e8d8 in rb_gc_impl_malloc gc/default/default.c:8182:5 #2 0x618cef9f7f01 in ruby_xmalloc2_body gc.c:5182:12 #3 0x618cef9f7eac in ruby_xmalloc2 gc.c:5176:34 #4 0x618cefb547b2 in moreswitches ruby.c:919:18 #5 0x618cefb526fe in process_options ruby.c:2350:9 #6 0x618cefb524ac in ruby_process_options ruby.c:3202:12 #7 0x618cef9dc11f in ruby_options eval.c:119:16 #8 0x618cef8f2fb5 in rb_main main.c:42:26 #9 0x618cef8f2f59 in main main.c:62:12 --- ruby.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ruby.c b/ruby.c index 05a9fd4191d8bb..0f5e6d60f7f22b 100644 --- a/ruby.c +++ b/ruby.c @@ -916,7 +916,9 @@ moreswitches(const char *s, ruby_cmdline_options_t *opt, int envopt) argc = RSTRING_LEN(argary) / sizeof(ap); ap = 0; rb_str_cat(argary, (char *)&ap, sizeof(ap)); - argv = ptr = ALLOC_N(char *, argc); + + VALUE ptr_obj; + argv = ptr = RB_ALLOCV_N(char *, ptr_obj, argc); MEMMOVE(argv, RSTRING_PTR(argary), char *, argc); while ((i = proc_options(argc, argv, opt, envopt)) > 1 && envopt && (argc -= i) > 0) { @@ -948,7 +950,8 @@ moreswitches(const char *s, ruby_cmdline_options_t *opt, int envopt) opt->crash_report = crash_report; } - ruby_xfree(ptr); + RB_ALLOCV_END(ptr_obj); + /* get rid of GC */ rb_str_resize(argary, 0); rb_str_resize(argstr, 0); From fa5481bc063fb54bd2735c6253224349419399e7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 16:08:05 -0700 Subject: [PATCH 0461/2435] ZJIT: Fetch Primitive.attr!(leaf) for InvokeBuiltin Fix https://github.com/Shopify/ruby/issues/670 --- jit.c | 6 ++++ yjit.c | 6 ---- yjit/bindgen/src/main.rs | 2 +- yjit/src/codegen.rs | 4 +-- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 5 +++ zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 63 +++++++++++++++++++++++++++++++--- 9 files changed, 76 insertions(+), 15 deletions(-) diff --git a/jit.c b/jit.c index 3111dcc3e372c9..43c932e5a00ffa 100644 --- a/jit.c +++ b/jit.c @@ -181,6 +181,12 @@ rb_jit_get_proc_ptr(VALUE procv) return proc; } +unsigned int +rb_jit_iseq_builtin_attrs(const rb_iseq_t *iseq) +{ + return iseq->body->builtin_attrs; +} + int rb_get_mct_argc(const rb_method_cfunc_t *mct) { diff --git a/yjit.c b/yjit.c index 3793b0f1ac4857..4b78cfbae25a25 100644 --- a/yjit.c +++ b/yjit.c @@ -244,12 +244,6 @@ rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv return rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, block_handler); } -unsigned int -rb_yjit_iseq_builtin_attrs(const rb_iseq_t *iseq) -{ - return iseq->body->builtin_attrs; -} - // If true, the iseq has only opt_invokebuiltin_delegate(_leave) and leave insns. static bool invokebuiltin_delegate_leave_p(const rb_iseq_t *iseq) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index df287e1bf84a2e..100abbb33fc8cb 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -249,7 +249,7 @@ fn main() { .allowlist_function("rb_jit_mark_executable") .allowlist_function("rb_jit_mark_unused") .allowlist_function("rb_jit_get_page_size") - .allowlist_function("rb_yjit_iseq_builtin_attrs") + .allowlist_function("rb_jit_iseq_builtin_attrs") .allowlist_function("rb_yjit_iseq_inspect") .allowlist_function("rb_yjit_builtin_function") .allowlist_function("rb_set_cfp_(pc|sp)") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 2eb66a298eea90..3f6f1bb46e31ec 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7694,7 +7694,7 @@ fn gen_send_iseq( gen_counter_incr(jit, asm, Counter::num_send_iseq); // Shortcut for special `Primitive.attr! :leaf` builtins - let builtin_attrs = unsafe { rb_yjit_iseq_builtin_attrs(iseq) }; + let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let builtin_func_raw = unsafe { rb_yjit_builtin_function(iseq) }; let builtin_func = if builtin_func_raw.is_null() { None } else { Some(builtin_func_raw) }; let opt_send_call = flags & VM_CALL_OPT_SEND != 0; // .send call is not currently supported for builtins @@ -9635,7 +9635,7 @@ fn gen_invokeblock_specialized( // If the current ISEQ is annotated to be inlined but it's not being inlined here, // generate a dynamic dispatch to avoid making this yield megamorphic. - if unsafe { rb_yjit_iseq_builtin_attrs(jit.iseq) } & BUILTIN_ATTR_INLINE_BLOCK != 0 && !asm.ctx.inline() { + if unsafe { rb_jit_iseq_builtin_attrs(jit.iseq) } & BUILTIN_ATTR_INLINE_BLOCK != 0 && !asm.ctx.inline() { gen_counter_incr(jit, asm, Counter::invokeblock_iseq_not_inlined); return None; } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 6542e5ef09d4de..f6e4e0e22f4d22 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1130,7 +1130,6 @@ extern "C" { kw_splat: ::std::os::raw::c_int, block_handler: VALUE, ) -> VALUE; - pub fn rb_yjit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_yjit_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function; pub fn rb_yjit_str_simple_append(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; @@ -1198,6 +1197,7 @@ extern "C" { pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; + pub fn rb_jit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 92f7a10e56f97c..75dbd46794abe4 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -274,7 +274,7 @@ fn main() { .allowlist_function("rb_jit_mark_unused") .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_jit_array_len") - .allowlist_function("rb_zjit_iseq_builtin_attrs") + .allowlist_function("rb_jit_iseq_builtin_attrs") .allowlist_function("rb_zjit_iseq_inspect") .allowlist_function("rb_zjit_iseq_insn_set") .allowlist_function("rb_zjit_local_id") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 41e0e847aa4e37..d4e4079b5c3c9b 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1175,6 +1175,11 @@ pub mod test_utils { get_proc_iseq(&format!("{}.method(:{})", recv, name)) } + /// Get IseqPtr for a specified instance method + pub fn get_instance_method_iseq(recv: &str, name: &str) -> *const rb_iseq_t { + get_proc_iseq(&format!("{}.instance_method(:{})", recv, name)) + } + /// Get IseqPtr for a specified Proc object pub fn get_proc_iseq(obj: &str) -> *const rb_iseq_t { let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({obj})")); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index d9bd2d33c080ed..f7e6cdde9419b3 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1363,6 +1363,7 @@ unsafe extern "C" { pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; + pub fn rb_jit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 489ea83a44731a..834a33d23c33d2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -745,6 +745,7 @@ pub enum Insn { bf: rb_builtin_function, args: Vec, state: InsnId, + leaf: bool, return_type: Option, // None for unannotated builtins }, @@ -1039,8 +1040,10 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } - Insn::InvokeBuiltin { bf, args, .. } => { - write!(f, "InvokeBuiltin {}", unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?; + Insn::InvokeBuiltin { bf, args, leaf, .. } => { + write!(f, "InvokeBuiltin{} {}", + if *leaf { " leaf" } else { "" }, + unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?; for arg in args { write!(f, ", {arg}")?; } @@ -1678,7 +1681,7 @@ impl Function { state, reason, }, - &InvokeBuiltin { bf, ref args, state, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, return_type }, + &InvokeBuiltin { bf, ref args, state, leaf, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, leaf, return_type }, &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, &HashDup { val, state } => HashDup { val: find!(val), state }, &HashAref { hash, key, state } => HashAref { hash: find!(hash), key: find!(key), state }, @@ -4671,10 +4674,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { .get_builtin_properties(&bf) .map(|props| props.return_type); + let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; + let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; + let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id, + leaf, return_type, }); state.stack_push(insn_id); @@ -4697,10 +4704,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { .get_builtin_properties(&bf) .map(|props| props.return_type); + let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; + let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; + let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id, + leaf, return_type, }); state.stack_push(insn_id); @@ -7928,7 +7939,7 @@ mod tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:Class = InvokeBuiltin _bi20, v6 + v11:Class = InvokeBuiltin leaf _bi20, v6 Jump bb3(v6, v11) bb3(v13:BasicObject, v14:Class): CheckInterrupts @@ -8026,6 +8037,50 @@ mod tests { "); } + #[test] + fn test_invoke_leaf_builtin_symbol_name() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn name@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBuiltin leaf _bi28, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invoke_leaf_builtin_symbol_to_s() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn to_s@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBuiltin leaf _bi12, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + #[test] fn dupn() { eval(" From c3404d736fa4fe7e330f3fbbeb4103f9206254d0 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 22 Oct 2025 16:13:57 -0700 Subject: [PATCH 0462/2435] ZJIT: Use InvokeBuiltin leaf attribute in codegen --- zjit/src/codegen.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 848249d7749bb2..c00bdb474ecbeb 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -386,7 +386,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), - Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args)), + Insn::InvokeBuiltin { bf, leaf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, *leaf, opnds!(args)), &Insn::EntryPoint { jit_entry_idx } => no_output!(gen_entry_point(jit, asm, jit_entry_idx)), Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), @@ -640,13 +640,17 @@ fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_in asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) } -fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: Vec) -> lir::Opnd { +fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, leaf: bool, args: Vec) -> lir::Opnd { assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", unsafe { std::ffi::CStr::from_ptr(bf.name).to_str().unwrap() }, bf.argc); - // Anything can happen inside builtin functions - gen_prepare_non_leaf_call(jit, asm, state); + if leaf { + gen_prepare_leaf_call_with_gc(asm, state); + } else { + // Anything can happen inside builtin functions + gen_prepare_non_leaf_call(jit, asm, state); + } let mut cargs = vec![EC]; cargs.extend(args); From 36e7db00c90a4046d9e569664bd23edb14ece1c4 Mon Sep 17 00:00:00 2001 From: niku <10890+niku@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:45:20 +0900 Subject: [PATCH 0463/2435] [DOC] Tweaks for TCPSocket.new --- ext/socket/tcpsocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 22c9f28ab71703..300a426eda8471 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -12,7 +12,7 @@ /* * call-seq: - * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, fast_fallback: true) + * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, open_timeout: nil, fast_fallback: true) * * Opens a TCP connection to +remote_host+ on +remote_port+. If +local_host+ * and +local_port+ are specified, then those parameters are used on the local From 8b014b1bbf6464c0edfb5af97b08c69b6ebff9b4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 23 Oct 2025 07:54:45 +0900 Subject: [PATCH 0464/2435] [ruby/rubygems] Bump up vendored uri to 1.0.4 https://github.com/ruby/rubygems/commit/bc77ec0bf2 --- lib/bundler/vendor/uri/lib/uri/generic.rb | 29 ++++++++++++++++------ lib/bundler/vendor/uri/lib/uri/version.rb | 2 +- lib/rubygems/vendor/uri/lib/uri/generic.rb | 29 ++++++++++++++++------ lib/rubygems/vendor/uri/lib/uri/version.rb | 2 +- tool/bundler/vendor_gems.rb | 2 +- tool/bundler/vendor_gems.rb.lock | 6 ++--- 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb index 6abb171d14254b..a27874748e5312 100644 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ b/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -186,18 +186,18 @@ def initialize(scheme, if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil) user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil) # See also Bundler::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ def password @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Bundler::URI decoding. def decoded_user Bundler::URI.decode_uri_component(@user) if @user @@ -615,6 +621,13 @@ def set_host(v) end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ def set_host(v) def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -729,6 +743,7 @@ def set_port(v) def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -1121,7 +1136,7 @@ def merge(oth) base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1134,9 +1149,7 @@ def merge(oth) # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index d4996a12e2929b..a3ec8b9b0b478b 100644 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ b/lib/bundler/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Bundler::URI # :stopdoc: - VERSION_CODE = '010003'.freeze + VERSION_CODE = '010004'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/lib/rubygems/vendor/uri/lib/uri/generic.rb b/lib/rubygems/vendor/uri/lib/uri/generic.rb index 2eabe2b4e338db..99b33b3d4f1136 100644 --- a/lib/rubygems/vendor/uri/lib/uri/generic.rb +++ b/lib/rubygems/vendor/uri/lib/uri/generic.rb @@ -186,18 +186,18 @@ def initialize(scheme, if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil) user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil) # See also Gem::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ def password @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Gem::URI decoding. def decoded_user Gem::URI.decode_uri_component(@user) if @user @@ -615,6 +621,13 @@ def set_host(v) end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ def set_host(v) def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -729,6 +743,7 @@ def set_port(v) def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -1121,7 +1136,7 @@ def merge(oth) base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1134,9 +1149,7 @@ def merge(oth) # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) diff --git a/lib/rubygems/vendor/uri/lib/uri/version.rb b/lib/rubygems/vendor/uri/lib/uri/version.rb index c2f617ce25008e..d3dd421aaa3923 100644 --- a/lib/rubygems/vendor/uri/lib/uri/version.rb +++ b/lib/rubygems/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Gem::URI # :stopdoc: - VERSION_CODE = '010003'.freeze + VERSION_CODE = '010004'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index b3e06d3f096047..72546faf31632e 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -14,4 +14,4 @@ gem "timeout", "0.4.3" gem "thor", "1.4.0" gem "tsort", "0.2.0" -gem "uri", "1.0.3" +gem "uri", "1.0.4" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index d911764a472649..b1a2351e949e4d 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -40,7 +40,7 @@ GEM thor (1.4.0) timeout (0.4.3) tsort (0.2.0) - uri (1.0.3) + uri (1.0.4) PLATFORMS java @@ -64,7 +64,7 @@ DEPENDENCIES thor (= 1.4.0) timeout (= 0.4.3) tsort (= 0.2.0) - uri (= 1.0.3) + uri (= 1.0.4) CHECKSUMS connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 @@ -80,7 +80,7 @@ CHECKSUMS thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f - uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 + uri (1.0.4) sha256=34485d137c079f8753a0ca1d883841a7ba2e5fae556e3c30c2aab0dde616344b BUNDLED WITH 4.0.0.dev From 4368e6c42effc16904e35f753fc2002f0bba375a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 23 Oct 2025 08:22:43 +0900 Subject: [PATCH 0465/2435] [ruby/rubygems] Removed credential assertion from stdout https://github.com/ruby/rubygems/commit/3946be008c --- test/rubygems/test_gem_request.rb | 2 +- test/rubygems/test_gem_uri.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb index 244f9d90feec46..cd0a416e79c02d 100644 --- a/test/rubygems/test_gem_request.rb +++ b/test/rubygems/test_gem_request.rb @@ -248,7 +248,7 @@ def test_fetch_basic_oauth_encoded auth_header = conn.payload["Authorization"] assert_equal "Basic #{base64_encode64("{DEScede}pass:x-oauth-basic")}".strip, auth_header - assert_includes @ui.output, "GET https://REDACTED:x-oauth-basic@example.rubygems/specs.#{Gem.marshal_version}" + assert_includes @ui.output, "GET https://REDACTED@example.rubygems/specs.#{Gem.marshal_version}" end def test_fetch_head diff --git a/test/rubygems/test_gem_uri.rb b/test/rubygems/test_gem_uri.rb index 1253ebc6de4a45..ce633c99b63b0a 100644 --- a/test/rubygems/test_gem_uri.rb +++ b/test/rubygems/test_gem_uri.rb @@ -21,7 +21,7 @@ def test_redacted_with_token end def test_redacted_with_user_x_oauth_basic - assert_equal "https://REDACTED:x-oauth-basic@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s + assert_equal "https://REDACTED@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s end def test_redacted_without_credential From f762e50bcbfca76b3f710bede6a29d82d308b897 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 22 Oct 2025 19:51:40 -0500 Subject: [PATCH 0466/2435] [ruby/stringio] [DOC] Tweaks for StringIO.open (https://github.com/ruby/stringio/pull/146) https://github.com/ruby/stringio/commit/141c6c0edf --- ext/stringio/stringio.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 0493c8cd50856b..ce7398bb836287 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -370,23 +370,20 @@ strio_finalize(VALUE self) /* * call-seq: - * StringIO.open(string = '', mode = 'r+') {|strio| ... } + * StringIO.open(string = '', mode = 'r+') -> new_stringio + * StringIO.open(string = '', mode = 'r+') {|strio| ... } -> object * - * Note that +mode+ defaults to 'r' if +string+ is frozen. - * - * Creates a new \StringIO instance formed from +string+ and +mode+; - * see {Access Modes}[rdoc-ref:File@Access+Modes]. + * Creates new \StringIO instance by calling StringIO.new(string, mode). * - * With no block, returns the new instance: + * With no block given, returns the new instance: * * strio = StringIO.open # => # * - * With a block, calls the block with the new instance + * With a block given, calls the block with the new instance * and returns the block's value; - * closes the instance on block exit. + * closes the instance on block exit: * - * StringIO.open {|strio| p strio } - * # => # + * StringIO.open('foo') {|strio| strio.string.upcase } # => "FOO" * * Related: StringIO.new. */ From da214cf3a9611ca00d3dd204a97b3c22ba90d2d1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 22 Oct 2025 19:59:47 -0500 Subject: [PATCH 0467/2435] [DOC] Tweaks for StringIO#binmode (#147) --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index ce7398bb836287..d37dee59ff963c 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -712,7 +712,7 @@ strio_set_lineno(VALUE self, VALUE lineno) * binmode -> self * * Sets the data mode in +self+ to binary mode; - * see {Data Mode}[rdoc-ref:File@Data+Mode]. + * see {Data Mode}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Data+Mode]. * */ static VALUE From 271be0a2258061be486e24dc61994a3f4155d669 Mon Sep 17 00:00:00 2001 From: Daniel Colson Date: Wed, 22 Oct 2025 23:01:26 -0400 Subject: [PATCH 0468/2435] ZJIT: Implement classvar get and set (#14918) https://github.com/Shopify/ruby/issues/649 Class vars are a bit more involved than ivars, since we need to get the class from the cref, so this calls out to `rb_vm_getclassvariable` and `rb_vm_setclassvariable` like YJIT. --- test/ruby/test_zjit.rb | 22 +++++++++++ zjit/src/codegen.rs | 16 ++++++++ zjit/src/hir.rs | 88 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 44f010d0561002..13c78170177a20 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1664,6 +1664,28 @@ def test() = @foo = 1 } end + def test_getclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x + end + + Foo.class_variable_set(:@@x, 42) + Foo.test() + } + end + + def test_setclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x = 42 + end + + Foo.test() + Foo.class_variable_get(:@@x) + } + end + def test_attr_reader assert_compiles '[4, 4]', %q{ class C diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index c00bdb474ecbeb..029e144303f999 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -426,6 +426,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp), &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), + Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)), + Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), @@ -832,6 +834,20 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); } +fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + let iseq = asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ)); + asm_ccall!(asm, rb_vm_getclassvariable, iseq, CFP, id.0.into(), Opnd::const_ptr(ic)) +} + +fn gen_setclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) { + gen_prepare_non_leaf_call(jit, asm, state); + + let iseq = asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ)); + asm_ccall!(asm, rb_vm_setclassvariable, iseq, CFP, id.0.into(), val, Opnd::const_ptr(ic)); +} + /// Look up global variables fn gen_getglobal(jit: &mut JITState, asm: &mut Assembler, id: ID, state: &FrameState) -> Opnd { // `Warning` module's method `warn` can be called when reading certain global variables diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 834a33d23c33d2..9f422c0146cce9 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -648,6 +648,11 @@ pub enum Insn { GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId }, GetSpecialNumber { nth: u64, state: InsnId }, + /// Get a class variable `id` + GetClassVar { id: ID, ic: *const iseq_inline_cvar_cache_entry, state: InsnId }, + /// Set a class variable `id` to `val` + SetClassVar { id: ID, val: InsnId, ic: *const iseq_inline_cvar_cache_entry, state: InsnId }, + /// Own a FrameState so that instructions can look up their dominating FrameState when /// generating deopt side-exits and frame reconstruction metadata. Does not directly generate /// any code. @@ -811,7 +816,7 @@ impl Insn { match self { Insn::Jump(_) | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. } - | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } + | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false, @@ -1130,6 +1135,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::SetLocal { val, level, ep_offset } => write!(f, "SetLocal l{level}, EP@{ep_offset}, {val}"), Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"), Insn::GetSpecialNumber { nth, .. } => write!(f, "GetSpecialNumber {nth}"), + Insn::GetClassVar { id, .. } => write!(f, "GetClassVar :{}", id.contents_lossy()), + Insn::SetClassVar { id, val, .. } => write!(f, "SetClassVar :{}, {val}", id.contents_lossy()), Insn::ToArray { val, .. } => write!(f, "ToArray {val}"), Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"), Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"), @@ -1716,6 +1723,8 @@ impl Function { &LoadIvarEmbedded { self_val, id, index } => LoadIvarEmbedded { self_val: find!(self_val), id, index }, &LoadIvarExtended { self_val, id, index } => LoadIvarExtended { self_val: find!(self_val), id, index }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, + &GetClassVar { id, ic, state } => GetClassVar { id, ic, state }, + &SetClassVar { id, val, ic, state } => SetClassVar { id, val: find!(val), ic, state }, &SetLocal { val, ep_offset, level } => SetLocal { val: find!(val), ep_offset, level }, &GetSpecialSymbol { symbol_type, state } => GetSpecialSymbol { symbol_type, state }, &GetSpecialNumber { nth, state } => GetSpecialNumber { nth, state }, @@ -1765,7 +1774,7 @@ impl Function { Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::EntryPoint { .. } | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } - | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } + | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } => panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), @@ -1848,6 +1857,7 @@ impl Function { Insn::LoadIvarExtended { .. } => types::BasicObject, Insn::GetSpecialSymbol { .. } => types::BasicObject, Insn::GetSpecialNumber { .. } => types::BasicObject, + Insn::GetClassVar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, Insn::ObjToString { .. } => types::BasicObject, @@ -3156,6 +3166,13 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + &Insn::GetClassVar { state, .. } => { + worklist.push_back(state); + } + &Insn::SetClassVar { val, state, .. } => { + worklist.push_back(val); + worklist.push_back(state); + } &Insn::ArrayPush { array, val, state } => { worklist.push_back(array); worklist.push_back(val); @@ -4639,6 +4656,20 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let val = state.stack_pop()?; fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, val, state: exit_id }); } + YARVINSN_getclassvariable => { + let id = ID(get_arg(pc, 0).as_u64()); + let ic = get_arg(pc, 1).as_ptr(); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let result = fun.push_insn(block, Insn::GetClassVar { id, ic, state: exit_id }); + state.stack_push(result); + } + YARVINSN_setclassvariable => { + let id = ID(get_arg(pc, 0).as_u64()); + let ic = get_arg(pc, 1).as_ptr(); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let val = state.stack_pop()?; + fun.push_insn(block, Insn::SetClassVar { id, val, ic, state: exit_id }); + } YARVINSN_opt_reverse => { // Reverse the order of the top N stack items. let n = get_arg(pc, 0).as_usize(); @@ -7429,6 +7460,59 @@ mod tests { assert_eq!(VALUE::fixnum_from_usize(1), result); } + #[test] + fn test_getclassvariable() { + eval(" + class Foo + def self.test = @@foo + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetClassVar :@@foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_setclassvariable() { + eval(" + class Foo + def self.test = @@foo = 42 + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[42] = Const Value(42) + SetClassVar :@@foo, v10 + CheckInterrupts + Return v10 + "); + } + #[test] fn test_setglobal() { eval(" From 45907b1b00d09ce2c40f5073ff540d8b63217d96 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 24 Sep 2025 02:34:45 +0900 Subject: [PATCH 0469/2435] add SET_SHAREABLE macros * `RB_OBJ_SET_SHAREABLE(obj)` makes obj shareable. All of reachable objects from `obj` should be shareable. * `RB_OBJ_SET_FROZEN_SHAREABLE(obj)` same as above but freeze `obj` before making it shareable. Also `rb_gc_verify_shareable(obj)` is introduced to check the `obj` does not violate shareable rule (an shareable object only refers shareable objects) strictly. The rule has some exceptions (some shareable objects can refer to unshareable objects, such as a Ractor object (which is a shareable object) can refer to the Ractor local objects. To handle such case, `check_shareable` flag is also introduced. `STRICT_VERIFY_SHAREABLE` macro is also introduced to verify the strict shareable rule at `SET_SHAREABLE`. --- gc/default/default.c | 62 +++++++++++++++++++++++++++++++++++++++++++ include/ruby/ractor.h | 14 ++++++++++ internal/gc.h | 3 +++ ractor.c | 42 ++++++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 7c10cc33063b0c..7cceb9911bfcc7 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -491,6 +491,7 @@ typedef struct rb_objspace { unsigned int during_minor_gc : 1; unsigned int during_incremental_marking : 1; unsigned int measure_gc : 1; + unsigned int check_shareable : 1; } flags; rb_event_flag_t hook_events; @@ -1455,6 +1456,19 @@ RVALUE_WHITE_P(rb_objspace_t *objspace, VALUE obj) return !RVALUE_MARKED(objspace, obj); } +bool +rb_gc_impl_checking_shareable(void *objspace_ptr) +{ + rb_objspace_t *objspace = objspace_ptr; + return objspace->flags.check_shareable; +} + +bool +rb_gc_checking_shareable(void) +{ + return rb_gc_impl_checking_shareable(rb_gc_get_objspace()); +} + bool rb_gc_impl_gc_enabled_p(void *objspace_ptr) { @@ -4962,6 +4976,52 @@ check_children_i(const VALUE child, void *ptr) } } +static void +check_shareable_i(const VALUE child, void *ptr) +{ + struct verify_internal_consistency_struct *data = (struct verify_internal_consistency_struct *)ptr; + + if (!RB_OBJ_SHAREABLE_P(child)) { + fprintf(stderr, "(a) "); + rp(data->parent); + fprintf(stderr, "(b) "); + rp(child); + fprintf(stderr, "check_shareable_i: shareable (a) -> unshareable (b)\n"); + + data->err_count++; + rb_bug("!! violate shareable constraint !!"); + } +} + +static void +gc_verify_shareable(rb_objspace_t *objspace, VALUE obj, void *data) +{ + // while objspace->flags.check_shareable is true, + // other Ractors should not run the GC, until the flag is not local. + // TODO: remove VM locking if the flag is Ractor local + RB_VM_LOCKING() { + objspace->flags.check_shareable = true; + rb_objspace_reachable_objects_from(obj, check_shareable_i, (void *)data); + objspace->flags.check_shareable = false; + } +} + +// TODO: only one level (non-recursive) +void +rb_gc_verify_shareable(VALUE obj) +{ + rb_objspace_t *objspace = rb_gc_get_objspace(); + struct verify_internal_consistency_struct data = { + .parent = obj, + .err_count = 0, + }; + gc_verify_shareable(objspace, obj, &data); + + if (data.err_count > 0) { + rb_bug("rb_gc_verify_shareable"); + } +} + static int verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, struct verify_internal_consistency_struct *data) @@ -6645,6 +6705,7 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ gc_enter_count(event); if (RB_UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0"); if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace); + GC_ASSERT(!objspace->flags.check_shareable); during_gc = TRUE; RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace)); @@ -6658,6 +6719,7 @@ static inline void gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev) { GC_ASSERT(during_gc != 0); + GC_ASSERT(!objspace->flags.check_shareable); rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_EXIT); diff --git a/include/ruby/ractor.h b/include/ruby/ractor.h index 7811616f6d8a84..85222bbe115860 100644 --- a/include/ruby/ractor.h +++ b/include/ruby/ractor.h @@ -261,4 +261,18 @@ rb_ractor_shareable_p(VALUE obj) } } +// TODO: optimize on interpreter core +#ifndef RB_OBJ_SET_SHAREABLE +VALUE rb_obj_set_shareable(VALUE obj); // ractor.c +#define RB_OBJ_SET_SHAREABLE(obj) rb_obj_set_shareable(obj) +#endif + +static inline VALUE +RB_OBJ_SET_FROZEN_SHAREABLE(VALUE obj) +{ + RB_OBJ_FREEZE(obj); + RB_OBJ_SET_SHAREABLE(obj); + return obj; +} + #endif /* RUBY_RACTOR_H */ diff --git a/internal/gc.h b/internal/gc.h index f0dc04fc58a954..7357bef732dcf4 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -351,4 +351,7 @@ ruby_sized_realloc_n(void *ptr, size_t new_count, size_t element_size, size_t ol #define ruby_sized_xrealloc ruby_sized_xrealloc_inlined #define ruby_sized_xrealloc2 ruby_sized_xrealloc2_inlined #define ruby_sized_xfree ruby_sized_xfree_inlined + +void rb_gc_verify_shareable(VALUE); + #endif /* INTERNAL_GC_H */ diff --git a/ractor.c b/ractor.c index 8e7f7d6497fb44..d21e29cffb93b7 100644 --- a/ractor.c +++ b/ractor.c @@ -1113,6 +1113,44 @@ rb_ractor_hooks(rb_ractor_t *cr) return &cr->pub.hooks; } +static void +rb_obj_set_shareable_no_assert(VALUE obj) +{ + FL_SET_RAW(obj, FL_SHAREABLE); + + if (rb_obj_exivar_p(obj)) { + VALUE fields = rb_obj_fields_no_ractor_check(obj); + if (imemo_type_p(fields, imemo_fields)) { + // no recursive mark + FL_SET_RAW(fields, FL_SHAREABLE); + } + } +} + +#ifndef STRICT_VERIFY_SHAREABLE +#define STRICT_VERIFY_SHAREABLE 0 +#endif + +bool +rb_ractor_verify_shareable(VALUE obj) +{ +#if STRICT_VERIFY_SHAREABLE + rb_gc_verify_shareable(obj); +#endif + return true; +} + +VALUE +rb_obj_set_shareable(VALUE obj) +{ + RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); + + rb_obj_set_shareable_no_assert(obj); + RUBY_ASSERT(rb_ractor_verify_shareable(obj)); + + return obj; +} + /// traverse function // 2: stop search @@ -1239,6 +1277,8 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) case T_ARRAY: { + rb_ary_cancel_sharing(obj); + for (int i = 0; i < RARRAY_LENINT(obj); i++) { VALUE e = rb_ary_entry(obj, i); if (obj_traverse_i(e, data)) return 1; @@ -1422,7 +1462,7 @@ make_shareable_check_shareable(VALUE obj) static enum obj_traverse_iterator_result mark_shareable(VALUE obj) { - FL_SET_RAW(obj, RUBY_FL_SHAREABLE); + rb_obj_set_shareable_no_assert(obj); return traverse_cont; } From bc00c4468e0054ca896d2b83d3020180915f64cf Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 25 Sep 2025 05:50:05 +0900 Subject: [PATCH 0470/2435] use `SET_SHAREABLE` to adopt strict shareable rule. * (basically) shareable objects only refer shareable objects * (exception) shareable objects can refere unshareable objects but should not leak reference to unshareable objects to Ruby world --- array.c | 17 +++++--- class.c | 2 +- compile.c | 99 +++++++++++++++++++++++++++++++++----------- depend | 4 ++ encoding.c | 4 +- gc.c | 18 ++++++-- gc/default/default.c | 12 +++++- hash.c | 4 +- id_table.c | 1 + imemo.c | 80 +++++++++++++++++++++-------------- internal/class.h | 4 +- internal/gc.h | 1 + internal/imemo.h | 11 ++--- iseq.c | 27 ++++++++---- iseq.h | 2 +- prism_compile.c | 48 ++++++++++++++------- ractor.c | 41 +++++++++++------- re.c | 7 +++- string.c | 26 ++++++++++-- symbol.c | 4 +- variable.c | 16 ++++--- vm.c | 34 +++++++++++---- vm_callinfo.h | 2 +- vm_insnhelper.c | 17 ++++++-- vm_method.c | 8 ++-- 25 files changed, 347 insertions(+), 142 deletions(-) diff --git a/array.c b/array.c index 378da150ee3e3e..12f45e2cbb421a 100644 --- a/array.c +++ b/array.c @@ -29,6 +29,7 @@ #include "ruby/st.h" #include "ruby/thread.h" #include "ruby/util.h" +#include "ruby/ractor.h" #include "vm_core.h" #include "builtin.h" @@ -107,10 +108,12 @@ should_be_T_ARRAY(VALUE ary) } while (0) #define FL_UNSET_SHARED(ary) FL_UNSET((ary), RARRAY_SHARED_FLAG) +#define ARY_SET_PTR_FORCE(ary, p) \ + RARRAY(ary)->as.heap.ptr = (p); #define ARY_SET_PTR(ary, p) do { \ RUBY_ASSERT(!ARY_EMBED_P(ary)); \ RUBY_ASSERT(!OBJ_FROZEN(ary)); \ - RARRAY(ary)->as.heap.ptr = (p); \ + ARY_SET_PTR_FORCE(ary, p); \ } while (0) #define ARY_SET_EMBED_LEN(ary, n) do { \ long tmp_n = (n); \ @@ -148,11 +151,13 @@ should_be_T_ARRAY(VALUE ary) #define ARY_CAPA(ary) (ARY_EMBED_P(ary) ? ary_embed_capa(ary) : \ ARY_SHARED_ROOT_P(ary) ? RARRAY_LEN(ary) : ARY_HEAP_CAPA(ary)) +#define ARY_SET_CAPA_FORCE(ary, n) \ + RARRAY(ary)->as.heap.aux.capa = (n); #define ARY_SET_CAPA(ary, n) do { \ RUBY_ASSERT(!ARY_EMBED_P(ary)); \ RUBY_ASSERT(!ARY_SHARED_P(ary)); \ RUBY_ASSERT(!OBJ_FROZEN(ary)); \ - RARRAY(ary)->as.heap.aux.capa = (n); \ + ARY_SET_CAPA_FORCE(ary, n); \ } while (0) #define ARY_SHARED_ROOT_OCCUPIED(ary) (!OBJ_FROZEN(ary) && ARY_SHARED_ROOT_REFCNT(ary) == 1) @@ -560,8 +565,8 @@ rb_ary_cancel_sharing(VALUE ary) VALUE *ptr = ary_heap_alloc_buffer(len); MEMCPY(ptr, ARY_HEAP_PTR(ary), VALUE, len); rb_ary_unshare(ary); - ARY_SET_CAPA(ary, len); - ARY_SET_PTR(ary, ptr); + ARY_SET_CAPA_FORCE(ary, len); + ARY_SET_PTR_FORCE(ary, ptr); } rb_gc_writebarrier_remember(ary); @@ -4729,6 +4734,8 @@ rb_ary_replace(VALUE copy, VALUE orig) ARY_SET_PTR(copy, ARY_HEAP_PTR(orig)); ARY_SET_LEN(copy, ARY_HEAP_LEN(orig)); rb_ary_set_shared(copy, shared_root); + + RUBY_ASSERT(RB_OBJ_SHAREABLE_P(copy) ? RB_OBJ_SHAREABLE_P(shared_root) : 1); } ary_verify(copy); return copy; @@ -8883,7 +8890,7 @@ Init_Array(void) rb_define_method(rb_cArray, "deconstruct", rb_ary_deconstruct, 0); - rb_cArray_empty_frozen = rb_ary_freeze(rb_ary_new()); + rb_cArray_empty_frozen = RB_OBJ_SET_SHAREABLE(rb_ary_freeze(rb_ary_new())); rb_vm_register_global_object(rb_cArray_empty_frozen); } diff --git a/class.c b/class.c index 74dcbe5fa7b99a..4dddf08c67af38 100644 --- a/class.c +++ b/class.c @@ -775,7 +775,7 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) RUBY_ASSERT(type == T_CLASS || type == T_ICLASS || type == T_MODULE); - VALUE flags = type; + VALUE flags = type | FL_SHAREABLE; if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; if (namespaceable) flags |= RCLASS_NAMESPACEABLE; diff --git a/compile.c b/compile.c index 27ed42f1f574d4..87c4d15ccfb925 100644 --- a/compile.c +++ b/compile.c @@ -838,9 +838,9 @@ get_string_value(const NODE *node) { switch (nd_type(node)) { case NODE_STR: - return rb_node_str_string_val(node); + return RB_OBJ_SET_SHAREABLE(rb_node_str_string_val(node)); case NODE_FILE: - return rb_node_file_path_val(node); + return RB_OBJ_SET_SHAREABLE(rb_node_file_path_val(node)); default: rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); } @@ -1400,6 +1400,9 @@ static void iseq_insn_each_object_write_barrier(VALUE * obj, VALUE iseq) { RB_OBJ_WRITTEN(iseq, Qundef, *obj); + RUBY_ASSERT(SPECIAL_CONST_P(*obj) || + RBASIC_CLASS(*obj) == 0 || // hidden + RB_OBJ_SHAREABLE_P(*obj)); } static INSN * @@ -2063,6 +2066,7 @@ iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, for (i = 0; i < RARRAY_LEN(default_values); i++) { VALUE dv = RARRAY_AREF(default_values, i); if (dv == complex_mark) dv = Qundef; + if (!SPECIAL_CONST_P(dv)) rb_ractor_make_shareable(dv); RB_OBJ_WRITE(iseq, &dvs[i], dv); } @@ -2749,6 +2753,7 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) rb_hash_rehash(map); freeze_hide_obj(map); + rb_ractor_make_shareable(map); generated_iseq[code_index + 1 + j] = map; ISEQ_MBITS_SET(mark_offset_bits, code_index + 1 + j); RB_OBJ_WRITTEN(iseq, Qundef, map); @@ -3489,7 +3494,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal is_frozen_putstring(beg, &str_beg) && !(insn_has_label_before(&beg->link) || insn_has_label_before(&end->link))) { int excl = FIX2INT(OPERAND_AT(range, 0)); - VALUE lit_range = rb_range_new(str_beg, str_end, excl); + VALUE lit_range = RB_OBJ_SET_SHAREABLE(rb_range_new(str_beg, str_end, excl)); ELEM_REMOVE(&beg->link); ELEM_REMOVE(&end->link); @@ -3556,6 +3561,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { VALUE hash = iobj->operands[0]; rb_obj_reveal(hash, rb_cHash); + RB_OBJ_SET_SHAREABLE(hash); insn_replace_with_operands(iseq, iobj, BIN(opt_hash_freeze), 2, hash, (VALUE)ci); ELEM_REMOVE(next); @@ -3929,6 +3935,9 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal rb_set_errinfo(errinfo); COMPILE_ERROR(iseq, line, "%" PRIsVALUE, message); } + else { + RB_OBJ_SET_SHAREABLE(re); + } RB_OBJ_WRITE(iseq, &OPERAND_AT(iobj, 0), re); ELEM_REMOVE(iobj->link.next); } @@ -4170,7 +4179,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal unsigned int flags = vm_ci_flag(ci); if ((flags & set_flags) == set_flags && !(flags & unset_flags)) { ((INSN*)niobj)->insn_id = BIN(putobject); - RB_OBJ_WRITE(iseq, &OPERAND_AT(niobj, 0), rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0)))); + RB_OBJ_WRITE(iseq, &OPERAND_AT(niobj, 0), RB_OBJ_SET_SHAREABLE(rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0))))); const struct rb_callinfo *nci = vm_ci_new(vm_ci_mid(ci), flags & ~VM_CALL_KW_SPLAT_MUT, vm_ci_argc(ci), vm_ci_kwarg(ci)); @@ -4725,6 +4734,7 @@ compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) if (!RNODE_DSTR(node)->nd_next) { VALUE lit = rb_node_dstr_string_val(node); ADD_INSN1(ret, node, putstring, lit); + RB_OBJ_SET_SHAREABLE(lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } else { @@ -4744,6 +4754,7 @@ compile_dregx(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i if (!popped) { VALUE src = rb_node_dregx_string_val(node); VALUE match = rb_reg_compile(src, cflag, NULL, 0); + RB_OBJ_SET_SHAREABLE(match); ADD_INSN1(ret, node, putobject, match); RB_OBJ_WRITTEN(iseq, Qundef, match); } @@ -5088,13 +5099,21 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) { switch (nd_type(node)) { case NODE_INTEGER: - return rb_node_integer_literal_val(node); + { + VALUE lit = rb_node_integer_literal_val(node); + if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit); + return lit; + } case NODE_FLOAT: - return rb_node_float_literal_val(node); + { + VALUE lit = rb_node_float_literal_val(node); + if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit); + return lit; + } case NODE_RATIONAL: - return rb_node_rational_literal_val(node); + return rb_ractor_make_shareable(rb_node_rational_literal_val(node)); case NODE_IMAGINARY: - return rb_node_imaginary_literal_val(node); + return rb_ractor_make_shareable(rb_node_imaginary_literal_val(node)); case NODE_NIL: return Qnil; case NODE_TRUE: @@ -5104,7 +5123,7 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) case NODE_SYM: return rb_node_sym_string_val(node); case NODE_REGX: - return rb_node_regx_string_val(node); + return RB_OBJ_SET_SHAREABLE(rb_node_regx_string_val(node)); case NODE_LINE: return rb_node_line_lineno_val(node); case NODE_ENCODING: @@ -5113,7 +5132,9 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) case NODE_STR: if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { VALUE lit = get_string_value(node); - return rb_str_with_debug_created_info(lit, rb_iseq_path(iseq), (int)nd_line(node)); + VALUE str = rb_str_with_debug_created_info(lit, rb_iseq_path(iseq), (int)nd_line(node)); + RB_OBJ_SET_SHAREABLE(str); + return str; } else { return get_string_value(node); @@ -5211,7 +5232,7 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int pop /* Create a hidden array */ for (; count; count--, node = RNODE_LIST(node)->nd_next) rb_ary_push(ary, static_literal_value(RNODE_LIST(node)->nd_head, iseq)); - OBJ_FREEZE(ary); + RB_OBJ_SET_FROZEN_SHAREABLE(ary); /* Emit optimized code */ FLUSH_CHUNK; @@ -5223,6 +5244,7 @@ compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int pop ADD_INSN1(ret, line_node, putobject, ary); ADD_INSN(ret, line_node, concattoarray); } + RB_OBJ_SET_SHAREABLE(ary); RB_OBJ_WRITTEN(iseq, Qundef, ary); } } @@ -5349,13 +5371,14 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int meth for (; count; count--, node = RNODE_LIST(RNODE_LIST(node)->nd_next)->nd_next) { VALUE elem[2]; elem[0] = static_literal_value(RNODE_LIST(node)->nd_head, iseq); + if (!RB_SPECIAL_CONST_P(elem[0])) RB_OBJ_SET_FROZEN_SHAREABLE(elem[0]); elem[1] = static_literal_value(RNODE_LIST(RNODE_LIST(node)->nd_next)->nd_head, iseq); + if (!RB_SPECIAL_CONST_P(elem[1])) RB_OBJ_SET_FROZEN_SHAREABLE(elem[1]); rb_ary_cat(ary, elem, 2); } VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2); rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary), hash); - hash = rb_obj_hide(hash); - OBJ_FREEZE(hash); + hash = RB_OBJ_SET_FROZEN_SHAREABLE(rb_obj_hide(hash)); /* Emit optimized code */ FLUSH_CHUNK(); @@ -6022,10 +6045,12 @@ collect_const_segments(rb_iseq_t *iseq, const NODE *node) switch (nd_type(node)) { case NODE_CONST: rb_ary_unshift(arr, ID2SYM(RNODE_CONST(node)->nd_vid)); + RB_OBJ_SET_SHAREABLE(arr); return arr; case NODE_COLON3: rb_ary_unshift(arr, ID2SYM(RNODE_COLON3(node)->nd_mid)); rb_ary_unshift(arr, ID2SYM(idNULL)); + RB_OBJ_SET_SHAREABLE(arr); return arr; case NODE_COLON2: rb_ary_unshift(arr, ID2SYM(RNODE_COLON2(node)->nd_mid)); @@ -7122,6 +7147,7 @@ compile_case(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_nod if (only_special_literals && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { ADD_INSN(ret, orig_node, dup); + rb_obj_hide(literals); ADD_INSN2(ret, orig_node, opt_case_dispatch, literals, elselabel); RB_OBJ_WRITTEN(iseq, Qundef, literals); LABEL_REF(elselabel); @@ -7657,6 +7683,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN(ret, line_node, putnil); } else { + RB_OBJ_SET_FROZEN_SHAREABLE(keys); ADD_INSN1(ret, line_node, duparray, keys); RB_OBJ_WRITTEN(iseq, Qundef, rb_obj_hide(keys)); } @@ -7694,7 +7721,8 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN(ret, line_node, dup); ADD_INSNL(ret, line_node, branchif, match_succeeded); - ADD_INSN1(ret, line_node, putobject, rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, key))); // (4) + VALUE str = rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, key)); + ADD_INSN1(ret, line_node, putobject, RB_OBJ_SET_SHAREABLE(str)); // (4) ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 2 /* (3), (4) */)); ADD_INSN1(ret, line_node, putobject, Qtrue); // (5) ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 3 /* (3), (4), (5) */)); @@ -10163,9 +10191,13 @@ compile_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i INIT_ANCHOR(val); switch ((int)type) { case NODE_MATCH: - ADD_INSN1(recv, node, putobject, rb_node_regx_string_val(node)); - ADD_INSN2(val, node, getspecial, INT2FIX(0), - INT2FIX(0)); + { + VALUE re = rb_node_regx_string_val(node); + RB_OBJ_SET_FROZEN_SHAREABLE(re); + ADD_INSN1(recv, node, putobject, re); + ADD_INSN2(val, node, getspecial, INT2FIX(0), + INT2FIX(0)); + } break; case NODE_MATCH2: CHECK(COMPILE(recv, "receiver", RNODE_MATCH2(node)->nd_recv)); @@ -10242,6 +10274,7 @@ compile_colon3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { ISEQ_BODY(iseq)->ic_size++; VALUE segments = rb_ary_new_from_args(2, ID2SYM(idNULL), ID2SYM(RNODE_COLON3(node)->nd_mid)); + RB_OBJ_SET_FROZEN_SHAREABLE(segments); ADD_INSN1(ret, node, opt_getconstant_path, segments); RB_OBJ_WRITTEN(iseq, Qundef, segments); } @@ -10269,6 +10302,7 @@ compile_dots(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in VALUE bv = optimized_range_item(b); VALUE ev = optimized_range_item(e); VALUE val = rb_range_new(bv, ev, excl); + rb_ractor_make_shareable(rb_obj_freeze(val)); ADD_INSN1(ret, node, putobject, val); RB_OBJ_WRITTEN(iseq, Qundef, val); } @@ -11080,6 +11114,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { body->ic_size++; VALUE segments = rb_ary_new_from_args(1, ID2SYM(RNODE_CONST(node)->nd_vid)); + RB_OBJ_SET_FROZEN_SHAREABLE(segments); ADD_INSN1(ret, node, opt_getconstant_path, segments); RB_OBJ_WRITTEN(iseq, Qundef, segments); } @@ -11145,6 +11180,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_INTEGER:{ VALUE lit = rb_node_integer_literal_val(node); + if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit); debugp_param("integer", lit); if (!popped) { ADD_INSN1(ret, node, putobject, lit); @@ -11154,6 +11190,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_FLOAT:{ VALUE lit = rb_node_float_literal_val(node); + if (!SPECIAL_CONST_P(lit)) RB_OBJ_SET_SHAREABLE(lit); debugp_param("float", lit); if (!popped) { ADD_INSN1(ret, node, putobject, lit); @@ -11163,6 +11200,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_RATIONAL:{ VALUE lit = rb_node_rational_literal_val(node); + rb_ractor_make_shareable(lit); debugp_param("rational", lit); if (!popped) { ADD_INSN1(ret, node, putobject, lit); @@ -11172,6 +11210,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_IMAGINARY:{ VALUE lit = rb_node_imaginary_literal_val(node); + rb_ractor_make_shareable(lit); debugp_param("imaginary", lit); if (!popped) { ADD_INSN1(ret, node, putobject, lit); @@ -11188,6 +11227,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no if ((option->debug_frozen_string_literal || RTEST(ruby_debug)) && option->frozen_string_literal != ISEQ_FROZEN_STRING_LITERAL_DISABLED) { lit = rb_str_with_debug_created_info(lit, rb_iseq_path(iseq), line); + RB_OBJ_SET_SHAREABLE(lit); } switch (option->frozen_string_literal) { case ISEQ_FROZEN_STRING_LITERAL_UNSET: @@ -11242,6 +11282,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no case NODE_REGX:{ if (!popped) { VALUE lit = rb_node_regx_string_val(node); + RB_OBJ_SET_SHAREABLE(lit); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -12105,6 +12146,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, rb_hash_aset(map, key, (VALUE)label | 1); } RB_GC_GUARD(op); + RB_OBJ_SET_SHAREABLE(rb_obj_hide(map)); // allow mutation while compiling argv[j] = map; RB_OBJ_WRITTEN(iseq, Qundef, map); } @@ -12992,7 +13034,7 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod v = rb_hash_dup(v); // hash dumped as frozen RHASH_TBL_RAW(v)->type = &cdhash_type; rb_hash_rehash(v); // hash function changed - freeze_hide_obj(v); + RB_OBJ_SET_SHAREABLE(freeze_hide_obj(v)); // Overwrite the existing hash in the object list. This // is to keep the object alive during load time. @@ -14126,7 +14168,9 @@ ibf_load_object_float(const struct ibf_load *load, const struct ibf_object_heade double d; /* Avoid unaligned VFP load on ARMv7; IBF payload may be unaligned (C99 6.3.2.3 p7). */ memcpy(&d, IBF_OBJBODY(double, offset), sizeof(d)); - return DBL2NUM(d); + VALUE f = DBL2NUM(d); + if (!FLONUM_P(f)) RB_OBJ_SET_SHAREABLE(f); + return f; } static void @@ -14197,7 +14241,7 @@ ibf_load_object_regexp(const struct ibf_load *load, const struct ibf_object_head VALUE reg = rb_reg_compile(srcstr, (int)regexp.option, NULL, 0); if (header->internal) rb_obj_hide(reg); - if (header->frozen) rb_obj_freeze(reg); + if (header->frozen) RB_OBJ_SET_SHAREABLE(rb_obj_freeze(reg)); return reg; } @@ -14228,7 +14272,10 @@ ibf_load_object_array(const struct ibf_load *load, const struct ibf_object_heade rb_ary_push(ary, ibf_load_object(load, index)); } - if (header->frozen) rb_ary_freeze(ary); + if (header->frozen) { + rb_ary_freeze(ary); + rb_ractor_make_shareable(ary); // TODO: check elements + } return ary; } @@ -14273,7 +14320,9 @@ ibf_load_object_hash(const struct ibf_load *load, const struct ibf_object_header rb_hash_rehash(obj); if (header->internal) rb_obj_hide(obj); - if (header->frozen) rb_obj_freeze(obj); + if (header->frozen) { + RB_OBJ_SET_FROZEN_SHAREABLE(obj); + } return obj; } @@ -14309,7 +14358,7 @@ ibf_load_object_struct(const struct ibf_load *load, const struct ibf_object_head VALUE end = ibf_load_object(load, range->end); VALUE obj = rb_range_new(beg, end, range->excl); if (header->internal) rb_obj_hide(obj); - if (header->frozen) rb_obj_freeze(obj); + if (header->frozen) RB_OBJ_SET_FROZEN_SHAREABLE(obj); return obj; } @@ -14337,7 +14386,7 @@ ibf_load_object_bignum(const struct ibf_load *load, const struct ibf_object_head big_unpack_flags | (sign == 0 ? INTEGER_PACK_NEGATIVE : 0)); if (header->internal) rb_obj_hide(obj); - if (header->frozen) rb_obj_freeze(obj); + if (header->frozen) RB_OBJ_SET_FROZEN_SHAREABLE(obj); return obj; } @@ -14398,7 +14447,7 @@ ibf_load_object_complex_rational(const struct ibf_load *load, const struct ibf_o rb_complex_new(a, b) : rb_rational_new(a, b); if (header->internal) rb_obj_hide(obj); - if (header->frozen) rb_obj_freeze(obj); + if (header->frozen) rb_ractor_make_shareable(rb_obj_freeze(obj)); return obj; } diff --git a/depend b/depend index 5ed27d04e0c38d..b56378ebce14c8 100644 --- a/depend +++ b/depend @@ -263,6 +263,7 @@ array.$(OBJEXT): {$(VPATH)}onigmo.h array.$(OBJEXT): {$(VPATH)}oniguruma.h array.$(OBJEXT): {$(VPATH)}probes.dmyh array.$(OBJEXT): {$(VPATH)}probes.h +array.$(OBJEXT): {$(VPATH)}ractor.h array.$(OBJEXT): {$(VPATH)}ruby_assert.h array.$(OBJEXT): {$(VPATH)}ruby_atomic.h array.$(OBJEXT): {$(VPATH)}rubyparser.h @@ -4385,6 +4386,7 @@ encoding.$(OBJEXT): {$(VPATH)}missing.h encoding.$(OBJEXT): {$(VPATH)}node.h encoding.$(OBJEXT): {$(VPATH)}onigmo.h encoding.$(OBJEXT): {$(VPATH)}oniguruma.h +encoding.$(OBJEXT): {$(VPATH)}ractor.h encoding.$(OBJEXT): {$(VPATH)}regenc.h encoding.$(OBJEXT): {$(VPATH)}ruby_assert.h encoding.$(OBJEXT): {$(VPATH)}ruby_atomic.h @@ -16794,6 +16796,7 @@ string.$(OBJEXT): {$(VPATH)}onigmo.h string.$(OBJEXT): {$(VPATH)}oniguruma.h string.$(OBJEXT): {$(VPATH)}probes.dmyh string.$(OBJEXT): {$(VPATH)}probes.h +string.$(OBJEXT): {$(VPATH)}ractor.h string.$(OBJEXT): {$(VPATH)}re.h string.$(OBJEXT): {$(VPATH)}regex.h string.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -17265,6 +17268,7 @@ symbol.$(OBJEXT): {$(VPATH)}onigmo.h symbol.$(OBJEXT): {$(VPATH)}oniguruma.h symbol.$(OBJEXT): {$(VPATH)}probes.dmyh symbol.$(OBJEXT): {$(VPATH)}probes.h +symbol.$(OBJEXT): {$(VPATH)}ractor.h symbol.$(OBJEXT): {$(VPATH)}ruby_assert.h symbol.$(OBJEXT): {$(VPATH)}ruby_atomic.h symbol.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/encoding.c b/encoding.c index da434cda1a4ff0..3d5c1d777283fe 100644 --- a/encoding.c +++ b/encoding.c @@ -27,6 +27,7 @@ #include "ruby/atomic.h" #include "ruby/encoding.h" #include "ruby/util.h" +#include "ruby/ractor.h" #include "ruby_assert.h" #include "vm_sync.h" #include "ruby_atomic.h" @@ -135,8 +136,7 @@ static VALUE enc_new(rb_encoding *encoding) { VALUE enc = TypedData_Wrap_Struct(rb_cEncoding, &encoding_data_type, (void *)encoding); - rb_obj_freeze(enc); - FL_SET_RAW(enc, RUBY_FL_SHAREABLE); + RB_OBJ_SET_FROZEN_SHAREABLE(enc); return enc; } diff --git a/gc.c b/gc.c index 897447c808e3d1..231f5fb38853fe 100644 --- a/gc.c +++ b/gc.c @@ -2804,13 +2804,17 @@ mark_m_tbl(void *objspace, struct rb_id_table *tbl) } } +bool rb_gc_impl_checking_shareable(void *objspace_ptr); // in defaut/deafult.c + static enum rb_id_table_iterator_result mark_const_entry_i(VALUE value, void *objspace) { const rb_const_entry_t *ce = (const rb_const_entry_t *)value; - gc_mark_internal(ce->value); - gc_mark_internal(ce->file); + if (!rb_gc_impl_checking_shareable(objspace)) { + gc_mark_internal(ce->value); + gc_mark_internal(ce->file); // TODO: ce->file should be shareable? + } return ID_TABLE_CONTINUE; } @@ -3071,7 +3075,12 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *a gc_mark_internal(RCLASSEXT_SUPER(ext)); } mark_m_tbl(objspace, RCLASSEXT_M_TBL(ext)); - gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext)); + + if (!rb_gc_impl_checking_shareable(objspace)) { + // unshareable + gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext)); + } + if (!RCLASSEXT_SHARED_CONST_TBL(ext) && RCLASSEXT_CONST_TBL(ext)) { mark_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext)); } @@ -3137,7 +3146,8 @@ rb_gc_mark_children(void *objspace, VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_CLASS: - if (FL_TEST_RAW(obj, FL_SINGLETON)) { + if (FL_TEST_RAW(obj, FL_SINGLETON) && + !rb_gc_impl_checking_shareable(objspace)) { gc_mark_internal(RCLASS_ATTACHED_OBJECT(obj)); } // Continue to the shared T_CLASS/T_MODULE diff --git a/gc/default/default.c b/gc/default/default.c index 7cceb9911bfcc7..b1c3bbf1a6b5a9 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -5053,6 +5053,10 @@ verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, rb_objspace_reachable_objects_from(obj, check_generation_i, (void *)data); } + if (!is_marking(objspace) && RB_OBJ_SHAREABLE_P(obj)) { + gc_verify_shareable(objspace, obj, data); + } + if (is_incremental_marking(objspace)) { if (RVALUE_BLACK_P(objspace, obj)) { /* reachable objects from black objects should be black or grey objects */ @@ -8975,6 +8979,12 @@ gc_profile_disable(VALUE _) return Qnil; } +void +rb_gc_verify_internal_consistency(void) +{ + gc_verify_internal_consistency(rb_gc_get_objspace()); +} + /* * call-seq: * GC.verify_internal_consistency -> nil @@ -8988,7 +8998,7 @@ gc_profile_disable(VALUE _) static VALUE gc_verify_internal_consistency_m(VALUE dummy) { - gc_verify_internal_consistency(rb_gc_get_objspace()); + rb_gc_verify_internal_consistency(); return Qnil; } diff --git a/hash.c b/hash.c index 7b523ba23561ef..603cab76db33d9 100644 --- a/hash.c +++ b/hash.c @@ -7474,6 +7474,7 @@ Init_Hash(void) rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash", rb_hash_s_ruby2_keywords_hash, 1); rb_cHash_empty_frozen = rb_hash_freeze(rb_hash_new()); + RB_OBJ_SET_SHAREABLE(rb_cHash_empty_frozen); rb_vm_register_global_object(rb_cHash_empty_frozen); /* Document-class: ENV @@ -7643,8 +7644,7 @@ Init_Hash(void) origenviron = environ; envtbl = TypedData_Wrap_Struct(rb_cObject, &env_data_type, NULL); rb_extend_object(envtbl, rb_mEnumerable); - FL_SET_RAW(envtbl, RUBY_FL_SHAREABLE); - + RB_OBJ_SET_SHAREABLE(envtbl); rb_define_singleton_method(envtbl, "[]", rb_f_getenv, 1); rb_define_singleton_method(envtbl, "fetch", env_fetch, -1); diff --git a/id_table.c b/id_table.c index b70587319182ce..eb8477237ea622 100644 --- a/id_table.c +++ b/id_table.c @@ -373,6 +373,7 @@ rb_managed_id_table_create(const rb_data_type_t *type, size_t capa) { struct rb_id_table *tbl; VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, type, tbl); + RB_OBJ_SET_SHAREABLE(obj); rb_id_table_init(tbl, capa); return obj; } diff --git a/imemo.c b/imemo.c index 2dd05b29f9df3b..d83c690ba5ae81 100644 --- a/imemo.c +++ b/imemo.c @@ -40,9 +40,9 @@ rb_imemo_name(enum imemo_type type) * ========================================================================= */ VALUE -rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) +rb_imemo_new(enum imemo_type type, VALUE v0, size_t size, bool is_shareable) { - VALUE flags = T_IMEMO | FL_WB_PROTECTED | (type << FL_USHIFT); + VALUE flags = T_IMEMO | FL_WB_PROTECTED | (type << FL_USHIFT) | (is_shareable ? FL_SHAREABLE : 0); NEWOBJ_OF(obj, void, v0, flags, size, 0); return (VALUE)obj; @@ -98,16 +98,16 @@ rb_free_tmp_buffer(volatile VALUE *store) } static VALUE -imemo_fields_new(VALUE owner, size_t capa) +imemo_fields_new(VALUE owner, size_t capa, bool shareable) { size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE); if (rb_gc_size_allocatable_p(embedded_size)) { - VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size); + VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size, shareable); RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields)); return fields; } else { - VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields)); + VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.external.ptr = ALLOC_N(VALUE, capa); FL_SET_RAW(fields, OBJ_FIELD_HEAP); return fields; @@ -115,24 +115,24 @@ imemo_fields_new(VALUE owner, size_t capa) } VALUE -rb_imemo_fields_new(VALUE owner, size_t capa) +rb_imemo_fields_new(VALUE owner, size_t capa, bool shareable) { - return imemo_fields_new(owner, capa); + return imemo_fields_new(owner, capa, shareable); } static VALUE -imemo_fields_new_complex(VALUE owner, size_t capa) +imemo_fields_new_complex(VALUE owner, size_t capa, bool shareable) { - VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields)); + VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa); FL_SET_RAW(fields, OBJ_FIELD_HEAP); return fields; } VALUE -rb_imemo_fields_new_complex(VALUE owner, size_t capa) +rb_imemo_fields_new_complex(VALUE owner, size_t capa, bool shareable) { - return imemo_fields_new_complex(owner, capa); + return imemo_fields_new_complex(owner, capa, shareable); } static int @@ -151,9 +151,9 @@ imemo_fields_complex_wb_i(st_data_t key, st_data_t value, st_data_t arg) } VALUE -rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl) +rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl, bool shareable) { - VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields)); + VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable); IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl; FL_SET_RAW(fields, OBJ_FIELD_HEAP); st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields); @@ -170,7 +170,7 @@ rb_imemo_fields_clone(VALUE fields_obj) st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj); st_table *dest_table = xcalloc(1, sizeof(st_table)); - clone = rb_imemo_fields_new_complex_tbl(rb_imemo_fields_owner(fields_obj), dest_table); + clone = rb_imemo_fields_new_complex_tbl(rb_imemo_fields_owner(fields_obj), dest_table, false /* TODO: check */); st_replace(dest_table, src_table); RBASIC_SET_SHAPE_ID(clone, shape_id); @@ -178,7 +178,7 @@ rb_imemo_fields_clone(VALUE fields_obj) st_foreach(dest_table, imemo_fields_complex_wb_i, (st_data_t)clone); } else { - clone = imemo_fields_new(rb_imemo_fields_owner(fields_obj), RSHAPE_CAPACITY(shape_id)); + clone = imemo_fields_new(rb_imemo_fields_owner(fields_obj), RSHAPE_CAPACITY(shape_id), false /* TODO: check */); RBASIC_SET_SHAPE_ID(clone, shape_id); VALUE *fields = rb_imemo_fields_ptr(clone); attr_index_t fields_count = RSHAPE_LEN(shape_id); @@ -303,7 +303,9 @@ mark_and_move_method_entry(rb_method_entry_t *ment, bool reference_updating) rb_gc_mark_and_move(&def->body.attr.location); break; case VM_METHOD_TYPE_BMETHOD: - rb_gc_mark_and_move(&def->body.bmethod.proc); + if (!rb_gc_checking_shareable()) { + rb_gc_mark_and_move(&def->body.bmethod.proc); + } if (def->body.bmethod.hooks) { rb_hook_list_mark_and_move(def->body.bmethod.hooks); } @@ -386,16 +388,27 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) case imemo_constcache: { struct iseq_inline_constant_cache_entry *ice = (struct iseq_inline_constant_cache_entry *)obj; - rb_gc_mark_and_move(&ice->value); + if ((ice->flags & IMEMO_CONST_CACHE_SHAREABLE) || + !rb_gc_checking_shareable()) { + rb_gc_mark_and_move(&ice->value); + } break; } case imemo_cref: { rb_cref_t *cref = (rb_cref_t *)obj; - rb_gc_mark_and_move(&cref->klass_or_self); + if (!rb_gc_checking_shareable()) { + // cref->klass_or_self can be unshareable, but no way to access it from other ractors + rb_gc_mark_and_move(&cref->klass_or_self); + } + rb_gc_mark_and_move_ptr(&cref->next); - rb_gc_mark_and_move(&cref->refinements); + + // TODO: Ractor and refeinements are not resolved yet + if (!rb_gc_checking_shareable()) { + rb_gc_mark_and_move(&cref->refinements); + } break; } @@ -481,20 +494,25 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) case imemo_fields: { rb_gc_mark_and_move((VALUE *)&RBASIC(obj)->klass); - if (rb_shape_obj_too_complex_p(obj)) { - st_table *tbl = rb_imemo_fields_complex_tbl(obj); - if (reference_updating) { - rb_gc_ref_update_table_values_only(tbl); + if (!rb_gc_checking_shareable()) { + // imemo_fields can refer unshareable objects + // even if the imemo_fields is shareable. + + if (rb_shape_obj_too_complex_p(obj)) { + st_table *tbl = rb_imemo_fields_complex_tbl(obj); + if (reference_updating) { + rb_gc_ref_update_table_values_only(tbl); + } + else { + rb_mark_tbl_no_pin(tbl); + } } else { - rb_mark_tbl_no_pin(tbl); - } - } - else { - VALUE *fields = rb_imemo_fields_ptr(obj); - attr_index_t len = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); - for (attr_index_t i = 0; i < len; i++) { - rb_gc_mark_and_move(&fields[i]); + VALUE *fields = rb_imemo_fields_ptr(obj); + attr_index_t len = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + for (attr_index_t i = 0; i < len; i++) { + rb_gc_mark_and_move(&fields[i]); + } } } break; diff --git a/internal/class.h b/internal/class.h index a791672cadcacf..5d843e58da3923 100644 --- a/internal/class.h +++ b/internal/class.h @@ -555,7 +555,7 @@ RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj) RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj); if (!ext->fields_obj) { - RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(obj, 1)); + RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(obj, 1, true)); } return ext->fields_obj; } @@ -762,6 +762,7 @@ RCLASS_SET_CLASSPATH(VALUE klass, VALUE classpath, bool permanent) rb_classext_t *ext = RCLASS_EXT_READABLE(klass); assert(BUILTIN_TYPE(klass) == T_CLASS || BUILTIN_TYPE(klass) == T_MODULE); assert(classpath == 0 || BUILTIN_TYPE(classpath) == T_STRING); + assert(FL_TEST_RAW(classpath, RUBY_FL_SHAREABLE)); RB_OBJ_WRITE(klass, &(RCLASSEXT_CLASSPATH(ext)), classpath); RCLASSEXT_PERMANENT_CLASSPATH(ext) = permanent; @@ -773,6 +774,7 @@ RCLASS_WRITE_CLASSPATH(VALUE klass, VALUE classpath, bool permanent) rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); assert(BUILTIN_TYPE(klass) == T_CLASS || BUILTIN_TYPE(klass) == T_MODULE); assert(classpath == 0 || BUILTIN_TYPE(classpath) == T_STRING); + assert(!RB_FL_ABLE(classpath) || FL_TEST_RAW(classpath, RUBY_FL_SHAREABLE)); RB_OBJ_WRITE(klass, &(RCLASSEXT_CLASSPATH(ext)), classpath); RCLASSEXT_PERMANENT_CLASSPATH(ext) = permanent; diff --git a/internal/gc.h b/internal/gc.h index 7357bef732dcf4..ec408d7fac53b9 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -353,5 +353,6 @@ ruby_sized_realloc_n(void *ptr, size_t new_count, size_t element_size, size_t ol #define ruby_sized_xfree ruby_sized_xfree_inlined void rb_gc_verify_shareable(VALUE); +bool rb_gc_checking_shareable(void); #endif /* INTERNAL_GC_H */ diff --git a/internal/imemo.h b/internal/imemo.h index 3b91ef4b818f93..f8bda26f0b50f9 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -114,7 +114,8 @@ struct MEMO { } u3; }; -#define IMEMO_NEW(T, type, v0) ((T *)rb_imemo_new((type), (v0), sizeof(T))) +#define IMEMO_NEW(T, type, v0) ((T *)rb_imemo_new((type), (v0), sizeof(T), false)) +#define SHAREABLE_IMEMO_NEW(T, type, v0) ((T *)rb_imemo_new((type), (v0), sizeof(T), true)) /* ment is in method.h */ @@ -131,7 +132,7 @@ struct MEMO { #ifndef RUBY_RUBYPARSER_H typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif -VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size); +VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size, bool is_shareable); VALUE rb_imemo_tmpbuf_new(void); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); @@ -270,9 +271,9 @@ STATIC_ASSERT(imemo_fields_embed_offset, offsetof(struct RObject, as.heap.fields #define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields) -VALUE rb_imemo_fields_new(VALUE owner, size_t capa); -VALUE rb_imemo_fields_new_complex(VALUE owner, size_t capa); -VALUE rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl); +VALUE rb_imemo_fields_new(VALUE owner, size_t capa, bool shareable); +VALUE rb_imemo_fields_new_complex(VALUE owner, size_t capa, bool shareable); +VALUE rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl, bool shareable); VALUE rb_imemo_fields_clone(VALUE fields_obj); void rb_imemo_fields_clear(VALUE fields_obj); diff --git a/iseq.c b/iseq.c index ae30d60ced9e3f..daa684713f1bb8 100644 --- a/iseq.c +++ b/iseq.c @@ -425,13 +425,15 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) rb_gc_mark_and_move(&iseq->aux.loader.obj); } else if (FL_TEST_RAW((VALUE)iseq, ISEQ_USE_COMPILE_DATA)) { - const struct iseq_compile_data *const compile_data = ISEQ_COMPILE_DATA(iseq); + if (!rb_gc_checking_shareable()) { + const struct iseq_compile_data *const compile_data = ISEQ_COMPILE_DATA(iseq); - rb_iseq_mark_and_move_insn_storage(compile_data->insn.storage_head); - rb_iseq_mark_and_move_each_compile_data_value(iseq, reference_updating ? ISEQ_ORIGINAL_ISEQ(iseq) : NULL); + rb_iseq_mark_and_move_insn_storage(compile_data->insn.storage_head); + rb_iseq_mark_and_move_each_compile_data_value(iseq, reference_updating ? ISEQ_ORIGINAL_ISEQ(iseq) : NULL); - rb_gc_mark_and_move((VALUE *)&compile_data->err_info); - rb_gc_mark_and_move((VALUE *)&compile_data->catch_table_ary); + rb_gc_mark_and_move((VALUE *)&compile_data->err_info); + rb_gc_mark_and_move((VALUE *)&compile_data->catch_table_ary); + } } else { /* executable */ @@ -544,9 +546,14 @@ rb_iseq_pathobj_new(VALUE path, VALUE realpath) pathobj = rb_fstring(path); } else { - if (!NIL_P(realpath)) realpath = rb_fstring(realpath); - pathobj = rb_ary_new_from_args(2, rb_fstring(path), realpath); + if (!NIL_P(realpath)) { + realpath = rb_fstring(realpath); + } + VALUE fpath = rb_fstring(path); + + pathobj = rb_ary_new_from_args(2, fpath, realpath); rb_ary_freeze(pathobj); + RB_OBJ_SET_SHAREABLE(pathobj); } return pathobj; } @@ -565,6 +572,11 @@ rb_iseq_alloc_with_dummy_path(VALUE fname) rb_iseq_t *dummy_iseq = iseq_alloc(); ISEQ_BODY(dummy_iseq)->type = ISEQ_TYPE_TOP; + + if (!RB_OBJ_SHAREABLE_P(fname)) { + RB_OBJ_SET_FROZEN_SHAREABLE(fname); + } + RB_OBJ_WRITE(dummy_iseq, &ISEQ_BODY(dummy_iseq)->location.pathobj, fname); RB_OBJ_WRITE(dummy_iseq, &ISEQ_BODY(dummy_iseq)->location.label, fname); @@ -1568,6 +1580,7 @@ iseqw_new(const rb_iseq_t *iseq) RB_OBJ_WRITE(obj, ptr, iseq); /* cache a wrapper object */ + RB_OBJ_SET_FROZEN_SHAREABLE((VALUE)obj); RB_OBJ_WRITE((VALUE)iseq, &iseq->wrapper, obj); return obj; diff --git a/iseq.h b/iseq.h index a8ad8ef9b064b8..86063d8be28136 100644 --- a/iseq.h +++ b/iseq.h @@ -175,7 +175,7 @@ ISEQ_COMPILE_DATA_CLEAR(rb_iseq_t *iseq) static inline rb_iseq_t * iseq_imemo_alloc(void) { - rb_iseq_t *iseq = IMEMO_NEW(rb_iseq_t, imemo_iseq, 0); + rb_iseq_t *iseq = SHAREABLE_IMEMO_NEW(rb_iseq_t, imemo_iseq, 0); // Clear out the whole iseq except for the flags. memset((char *)iseq + sizeof(VALUE), 0, sizeof(rb_iseq_t) - sizeof(VALUE)); diff --git a/prism_compile.c b/prism_compile.c index 86753c90cc17a9..6b4e32f629a6a3 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -201,6 +201,10 @@ parse_integer_value(const pm_integer_t *integer) result = rb_funcall(result, rb_intern("-@"), 0); } + if (!SPECIAL_CONST_P(result)) { + RB_OBJ_SET_SHAREABLE(result); // bignum + } + return result; } @@ -219,7 +223,11 @@ parse_integer(const pm_integer_node_t *node) static VALUE parse_float(const pm_float_node_t *node) { - return DBL2NUM(node->value); + VALUE val = DBL2NUM(node->value); + if (!FLONUM_P(val)) { + RB_OBJ_SET_SHAREABLE(val); + } + return val; } /** @@ -233,7 +241,8 @@ parse_rational(const pm_rational_node_t *node) { VALUE numerator = parse_integer_value(&node->numerator); VALUE denominator = parse_integer_value(&node->denominator); - return rb_rational_new(numerator, denominator); + + return rb_ractor_make_shareable(rb_rational_new(numerator, denominator)); } /** @@ -263,7 +272,7 @@ parse_imaginary(const pm_imaginary_node_t *node) rb_bug("Unexpected numeric type on imaginary number %s\n", pm_node_type_to_str(PM_NODE_TYPE(node->numeric))); } - return rb_complex_raw(INT2FIX(0), imaginary_part); + return RB_OBJ_SET_SHAREABLE(rb_complex_raw(INT2FIX(0), imaginary_part)); } static inline VALUE @@ -315,7 +324,7 @@ parse_static_literal_string(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { int line_number = pm_node_line_number(scope_node->parser, node); - value = rb_str_with_debug_created_info(value, rb_iseq_path(iseq), line_number); + value = rb_ractor_make_shareable(rb_str_with_debug_created_info(value, rb_iseq_path(iseq), line_number)); } return value; @@ -531,8 +540,7 @@ parse_regexp(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t return Qnil; } - rb_obj_freeze(regexp); - return regexp; + return RB_OBJ_SET_SHAREABLE(rb_obj_freeze(regexp)); } static inline VALUE @@ -542,6 +550,7 @@ parse_regexp_literal(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const p if (regexp_encoding == NULL) regexp_encoding = scope_node->encoding; VALUE string = rb_enc_str_new((const char *) pm_string_source(unescaped), pm_string_length(unescaped), regexp_encoding); + RB_OBJ_SET_SHAREABLE(string); return parse_regexp(iseq, scope_node, node, string); } @@ -724,7 +733,9 @@ static VALUE pm_static_literal_string(rb_iseq_t *iseq, VALUE string, int line_number) { if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { - return rb_str_with_debug_created_info(string, rb_iseq_path(iseq), line_number); + VALUE str = rb_str_with_debug_created_info(string, rb_iseq_path(iseq), line_number); + RB_OBJ_SET_SHAREABLE(str); + return str; } else { return rb_fstring(string); @@ -753,7 +764,7 @@ pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, const pm_scope_n rb_ary_push(value, pm_static_literal_value(iseq, elements->nodes[index], scope_node)); } - OBJ_FREEZE(value); + RB_OBJ_SET_FROZEN_SHAREABLE(value); return value; } case PM_FALSE_NODE: @@ -776,7 +787,7 @@ pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, const pm_scope_n rb_hash_bulk_insert(RARRAY_LEN(array), RARRAY_CONST_PTR(array), value); value = rb_obj_hide(value); - OBJ_FREEZE(value); + RB_OBJ_SET_FROZEN_SHAREABLE(value); return value; } case PM_IMAGINARY_NODE: @@ -1445,7 +1456,7 @@ pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2); rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary), hash); hash = rb_obj_hide(hash); - OBJ_FREEZE(hash); + RB_OBJ_SET_FROZEN_SHAREABLE(hash); // Emit optimized code. FLUSH_CHUNK; @@ -2860,8 +2871,10 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t PUSH_INSN(ret, location, putnil); } else { + rb_obj_hide(keys); + RB_OBJ_SET_FROZEN_SHAREABLE(keys); PUSH_INSN1(ret, location, duparray, keys); - RB_OBJ_WRITTEN(iseq, Qundef, rb_obj_hide(keys)); + RB_OBJ_WRITTEN(iseq, Qundef, keys); } PUSH_SEND(ret, location, rb_intern("deconstruct_keys"), INT2FIX(1)); @@ -2897,6 +2910,7 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t { VALUE operand = rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, symbol)); + RB_OBJ_SET_SHAREABLE(operand); PUSH_INSN1(ret, location, putobject, operand); } @@ -5545,6 +5559,7 @@ pm_compile_constant_read(rb_iseq_t *iseq, VALUE name, const pm_location_t *name_ if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { ISEQ_BODY(iseq)->ic_size++; VALUE segments = rb_ary_new_from_args(1, name); + RB_OBJ_SET_SHAREABLE(segments); PUSH_INSN1(ret, location, opt_getconstant_path, segments); } else { @@ -5758,6 +5773,9 @@ pm_compile_shareable_constant_value(rb_iseq_t *iseq, const pm_node_t *node, cons if (shareability & PM_SHAREABLE_CONSTANT_NODE_FLAGS_LITERAL) { PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); PUSH_SEQ(ret, value_seq); + if (!RB_OBJ_SHAREABLE_P(path)) { + RB_OBJ_SET_SHAREABLE(path); + } PUSH_INSN1(ret, location, putobject, path); PUSH_SEND_WITH_FLAG(ret, location, rb_intern("ensure_shareable"), INT2FIX(2), INT2FIX(VM_CALL_ARGS_SIMPLE)); } @@ -7108,6 +7126,7 @@ pm_compile_array_node(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_list if (!popped) { if (elements->size) { VALUE value = pm_static_literal_value(iseq, node, scope_node); + RB_OBJ_SET_FROZEN_SHAREABLE(value); PUSH_INSN1(ret, *location, duparray, value); } else { @@ -7238,7 +7257,7 @@ pm_compile_array_node(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_list rb_ary_push(tmp_array, pm_static_literal_value(iseq, elements->nodes[index++], scope_node)); index--; // about to be incremented by for loop - OBJ_FREEZE(tmp_array); + RB_OBJ_SET_FROZEN_SHAREABLE(tmp_array); // Emit the optimized code. FLUSH_CHUNK; @@ -7465,7 +7484,6 @@ static VALUE pm_compile_case_node_dispatch(rb_iseq_t *iseq, VALUE dispatch, const pm_node_t *node, LABEL *label, const pm_scope_node_t *scope_node) { VALUE key = Qundef; - switch (PM_NODE_TYPE(node)) { case PM_FLOAT_NODE: { key = pm_static_literal_value(iseq, node, scope_node); @@ -7498,7 +7516,6 @@ pm_compile_case_node_dispatch(rb_iseq_t *iseq, VALUE dispatch, const pm_node_t * if (NIL_P(rb_hash_lookup(dispatch, key))) { rb_hash_aset(dispatch, key, ((VALUE) label) | 1); } - return dispatch; } @@ -7726,6 +7743,7 @@ pm_compile_case_node(rb_iseq_t *iseq, const pm_case_node_t *cast, const pm_node_ // optimization. if (dispatch != Qundef) { PUSH_INSN(ret, location, dup); + RB_OBJ_SET_SHAREABLE(dispatch); // it is special that the hash is shareable but not frozen, because compile.c modify them. This Hahs instance is not accessible so it is safe to leave it. PUSH_INSN2(ret, location, opt_case_dispatch, dispatch, else_label); LABEL_REF(else_label); } @@ -8953,6 +8971,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache && ((parts = pm_constant_path_parts(node, scope_node)) != Qnil)) { ISEQ_BODY(iseq)->ic_size++; + RB_OBJ_SET_SHAREABLE(parts); PUSH_INSN1(ret, location, opt_getconstant_path, parts); } else { @@ -10068,6 +10087,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, exclude_end ); + RB_OBJ_SET_SHAREABLE(val); PUSH_INSN1(ret, location, putobject, val); } } diff --git a/ractor.c b/ractor.c index d21e29cffb93b7..68ef0a87ac4cc7 100644 --- a/ractor.c +++ b/ractor.c @@ -207,28 +207,34 @@ static void ractor_mark(void *ptr) { rb_ractor_t *r = (rb_ractor_t *)ptr; + bool checking_shareable = rb_gc_checking_shareable(); // mark received messages ractor_sync_mark(r); rb_gc_mark(r->loc); rb_gc_mark(r->name); - rb_gc_mark(r->r_stdin); - rb_gc_mark(r->r_stdout); - rb_gc_mark(r->r_stderr); - rb_gc_mark(r->verbose); - rb_gc_mark(r->debug); - rb_hook_list_mark(&r->pub.hooks); - - if (r->threads.cnt > 0) { - rb_thread_t *th = 0; - ccan_list_for_each(&r->threads.set, th, lt_node) { - VM_ASSERT(th != NULL); - rb_gc_mark(th->self); + + if (!checking_shareable) { + // may unshareable objects + rb_gc_mark(r->r_stdin); + rb_gc_mark(r->r_stdout); + rb_gc_mark(r->r_stderr); + rb_gc_mark(r->verbose); + rb_gc_mark(r->debug); + + rb_hook_list_mark(&r->pub.hooks); + + if (r->threads.cnt > 0) { + rb_thread_t *th = 0; + ccan_list_for_each(&r->threads.set, th, lt_node) { + VM_ASSERT(th != NULL); + rb_gc_mark(th->self); + } } - } - ractor_local_storage_mark(r); + ractor_local_storage_mark(r); + } } static void @@ -493,8 +499,9 @@ ractor_init(rb_ractor_t *r, VALUE name, VALUE loc) } name = rb_str_new_frozen(name); } - r->name = name; + if (!SPECIAL_CONST_P(loc)) RB_OBJ_SET_SHAREABLE(loc); r->loc = loc; + r->name = name; } void @@ -1462,6 +1469,10 @@ make_shareable_check_shareable(VALUE obj) static enum obj_traverse_iterator_result mark_shareable(VALUE obj) { + if (RB_TYPE_P(obj, T_STRING)) { + rb_str_make_independent(obj); + } + rb_obj_set_shareable_no_assert(obj); return traverse_cont; } diff --git a/re.c b/re.c index 13d7f0ef9e5fc7..e4d30d2939596e 100644 --- a/re.c +++ b/re.c @@ -3369,10 +3369,13 @@ static void reg_set_source(VALUE reg, VALUE str, rb_encoding *enc) { rb_encoding *regenc = rb_enc_get(reg); + if (regenc != enc) { - str = rb_enc_associate(rb_str_dup(str), enc = regenc); + VALUE dup = rb_str_dup(str); + str = rb_enc_associate(dup, enc = regenc); } - RB_OBJ_WRITE(reg, &RREGEXP(reg)->src, rb_fstring(str)); + str = rb_fstring(str); + RB_OBJ_WRITE(reg, &RREGEXP(reg)->src, str); } static int diff --git a/string.c b/string.c index 1236057ad177ed..1de87071272938 100644 --- a/string.c +++ b/string.c @@ -45,6 +45,7 @@ #include "ruby/re.h" #include "ruby/thread.h" #include "ruby/util.h" +#include "ruby/ractor.h" #include "ruby_assert.h" #include "shape.h" #include "vm_sync.h" @@ -537,7 +538,10 @@ fstring_concurrent_set_create(VALUE str, void *data) ENC_CODERANGE_SET(str, coderange); RBASIC(str)->flags |= RSTRING_FSTR; - + if (!RB_OBJ_SHAREABLE_P(str)) { + RB_OBJ_SET_SHAREABLE(str); + } + RUBY_ASSERT((rb_gc_verify_shareable(str), 1)); RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(str)); RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); @@ -583,6 +587,8 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash) RUBY_ASSERT(!rb_objspace_garbage_object_p(result)); RUBY_ASSERT(RB_TYPE_P(result, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(result)); + RUBY_ASSERT(RB_OBJ_SHAREABLE_P(result)); + RUBY_ASSERT((rb_gc_verify_shareable(result), 1)); RUBY_ASSERT(!FL_TEST_RAW(result, STR_FAKESTR)); RUBY_ASSERT(RBASIC_CLASS(result) == rb_cString); @@ -1555,6 +1561,10 @@ rb_str_tmp_frozen_no_embed_acquire(VALUE orig) RBASIC(str)->flags |= RBASIC(orig)->flags & STR_NOFREE; RBASIC(orig)->flags &= ~STR_NOFREE; STR_SET_SHARED(orig, str); + if (RB_OBJ_SHAREABLE_P(orig)) { + RB_OBJ_SET_SHAREABLE(str); + RUBY_ASSERT((rb_gc_verify_shareable(str), 1)); + } } RSTRING(str)->len = RSTRING(orig)->len; @@ -1604,6 +1614,7 @@ heap_str_make_shared(VALUE klass, VALUE orig) { RUBY_ASSERT(!STR_EMBED_P(orig)); RUBY_ASSERT(!STR_SHARED_P(orig)); + RUBY_ASSERT(!RB_OBJ_SHAREABLE_P(orig)); VALUE str = str_alloc_heap(klass); STR_SET_LEN(str, RSTRING_LEN(orig)); @@ -1613,7 +1624,7 @@ heap_str_make_shared(VALUE klass, VALUE orig) RBASIC(orig)->flags &= ~STR_NOFREE; STR_SET_SHARED(orig, str); if (klass == 0) - FL_UNSET_RAW(str, STR_BORROWED); + FL_UNSET_RAW(str, STR_BORROWED); return str; } @@ -1663,7 +1674,12 @@ str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding) TERM_FILL(RSTRING_END(str), TERM_LEN(orig)); } else { - str = heap_str_make_shared(klass, orig); + if (RB_OBJ_SHAREABLE_P(orig)) { + str = str_new(klass, RSTRING_PTR(orig), RSTRING_LEN(orig)); + } + else { + str = heap_str_make_shared(klass, orig); + } } } @@ -12676,7 +12692,9 @@ rb_enc_literal_str(const char *ptr, long len, rb_encoding *enc) } struct RString fake_str = {RBASIC_INIT}; - return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, true); + VALUE str = register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, true); + RUBY_ASSERT(RB_OBJ_SHAREABLE_P(str) && (rb_gc_verify_shareable(str), 1)); + return str; } VALUE diff --git a/symbol.c b/symbol.c index a498f742aa1600..11602ee33b7c5d 100644 --- a/symbol.c +++ b/symbol.c @@ -19,6 +19,7 @@ #include "internal/vm.h" #include "probes.h" #include "ruby/encoding.h" +#include "ruby/ractor.h" #include "ruby/st.h" #include "symbol.h" #include "vm_sync.h" @@ -200,7 +201,6 @@ dup_string_for_create(VALUE str) OBJ_FREEZE(str); str = rb_fstring(str); - return str; } @@ -255,8 +255,8 @@ sym_set_create(VALUE sym, void *data) rb_encoding *enc = rb_enc_get(str); rb_enc_set_index((VALUE)obj, rb_enc_to_index(enc)); - OBJ_FREEZE((VALUE)obj); RB_OBJ_WRITE((VALUE)obj, &obj->fstr, str); + RB_OBJ_SET_FROZEN_SHAREABLE((VALUE)obj); int id = rb_str_symname_type(str, IDSET_ATTRSET_FOR_INTERN); if (id < 0) id = ID_INTERNAL; diff --git a/variable.c b/variable.c index 5fc98fb0879024..bab423e95029f7 100644 --- a/variable.c +++ b/variable.c @@ -321,6 +321,7 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) } name = rb_str_new_frozen(name); + RB_OBJ_SET_SHAREABLE(name); // Set the temporary classpath to the given name: RB_VM_LOCKING() { @@ -432,6 +433,7 @@ rb_set_class_path_string(VALUE klass, VALUE under, VALUE name) str = build_const_pathname(str, name); } + RB_OBJ_SET_SHAREABLE(str); RCLASS_SET_CLASSPATH(klass, str, permanent); } @@ -1552,7 +1554,7 @@ obj_transition_too_complex(VALUE obj, st_table *table) break; default: { - VALUE fields_obj = rb_imemo_fields_new_complex_tbl(obj, table); + VALUE fields_obj = rb_imemo_fields_new_complex_tbl(obj, table, RB_OBJ_SHAREABLE_P(obj)); RBASIC_SET_SHAPE_ID(fields_obj, shape_id); rb_obj_replace_fields(obj, fields_obj); } @@ -1731,7 +1733,7 @@ static VALUE imemo_fields_complex_from_obj(VALUE owner, VALUE source_fields_obj, shape_id_t shape_id) { attr_index_t len = source_fields_obj ? RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)) : 0; - VALUE fields_obj = rb_imemo_fields_new_complex(owner, len + 1); + VALUE fields_obj = rb_imemo_fields_new_complex(owner, len + 1, RB_OBJ_SHAREABLE_P(owner)); rb_field_foreach(source_fields_obj, imemo_fields_complex_from_obj_i, (st_data_t)fields_obj, false); RBASIC_SET_SHAPE_ID(fields_obj, shape_id); @@ -1742,7 +1744,7 @@ imemo_fields_complex_from_obj(VALUE owner, VALUE source_fields_obj, shape_id_t s static VALUE imemo_fields_copy_capa(VALUE owner, VALUE source_fields_obj, attr_index_t new_size) { - VALUE fields_obj = rb_imemo_fields_new(owner, new_size); + VALUE fields_obj = rb_imemo_fields_new(owner, new_size, RB_OBJ_SHAREABLE_P(owner)); if (source_fields_obj) { attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)); VALUE *fields = rb_imemo_fields_ptr(fields_obj); @@ -2227,7 +2229,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) return; } - new_fields_obj = rb_imemo_fields_new(dest, RSHAPE_CAPACITY(dest_shape_id)); + new_fields_obj = rb_imemo_fields_new(dest, RSHAPE_CAPACITY(dest_shape_id), RB_OBJ_SHAREABLE_P(dest)); VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id); @@ -3797,6 +3799,7 @@ static void set_namespace_path(VALUE named_namespace, VALUE namespace_path) { struct rb_id_table *const_table = RCLASS_CONST_TBL(named_namespace); + RB_OBJ_SET_SHAREABLE(namespace_path); RB_VM_LOCKING() { RCLASS_WRITE_CLASSPATH(named_namespace, namespace_path, true); @@ -3875,7 +3878,8 @@ const_set(VALUE klass, ID id, VALUE val) set_namespace_path(val, build_const_path(parental_path, id)); } else if (!parental_path_permanent && NIL_P(val_path)) { - RCLASS_SET_CLASSPATH(val, build_const_path(parental_path, id), false); + VALUE path = build_const_path(parental_path, id); + RCLASS_SET_CLASSPATH(val, path, false); } } } @@ -4488,7 +4492,7 @@ static attr_index_t class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj, bool *new_ivar_out) { const VALUE original_fields_obj = fields_obj; - fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1); + fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1, true); shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); shape_id_t next_shape_id = current_shape_id; // for too_complex diff --git a/vm.c b/vm.c index 3281fad0181916..c11ac9f7a10510 100644 --- a/vm.c +++ b/vm.c @@ -311,7 +311,7 @@ vm_cref_new0(VALUE klass, rb_method_visibility_t visi, int module_func, rb_cref_ VM_ASSERT(singleton || klass); - rb_cref_t *cref = IMEMO_NEW(rb_cref_t, imemo_cref, refinements); + rb_cref_t *cref = SHAREABLE_IMEMO_NEW(rb_cref_t, imemo_cref, refinements); cref->klass_or_self = klass; cref->next = use_prev_prev ? CREF_NEXT(prev_cref) : prev_cref; *((rb_scope_visibility_t *)&cref->scope_visi) = scope_visi; @@ -1311,7 +1311,7 @@ rb_proc_dup(VALUE self) break; } - if (RB_OBJ_SHAREABLE_P(self)) FL_SET_RAW(procval, RUBY_FL_SHAREABLE); + if (RB_OBJ_SHAREABLE_P(self)) RB_OBJ_SET_SHAREABLE(procval); RB_GC_GUARD(self); /* for: body = rb_proc_dup(body) */ return procval; } @@ -1375,7 +1375,19 @@ env_copy(const VALUE *src_ep, VALUE read_only_variables) const rb_env_t *copied_env = vm_env_new(ep, env_body, src_env->env_size, src_env->iseq); // Copy after allocations above, since they can move objects in src_ep. - RB_OBJ_WRITE(copied_env, &ep[VM_ENV_DATA_INDEX_ME_CREF], src_ep[VM_ENV_DATA_INDEX_ME_CREF]); + VALUE svar_val = src_ep[VM_ENV_DATA_INDEX_ME_CREF]; + if (imemo_type_p(svar_val, imemo_svar)) { + const struct vm_svar *svar = (struct vm_svar *)svar_val; + + if (svar->cref_or_me) { + svar_val = svar->cref_or_me; + } + else { + svar_val = Qfalse; + } + } + RB_OBJ_WRITE(copied_env, &ep[VM_ENV_DATA_INDEX_ME_CREF], svar_val); + ep[VM_ENV_DATA_INDEX_FLAGS] = src_ep[VM_ENV_DATA_INDEX_FLAGS] | VM_ENV_FLAG_ISOLATED; if (!VM_ENV_LOCAL_P(src_ep)) { VM_ENV_FLAGS_SET(ep, VM_ENV_FLAG_LOCAL); @@ -1427,6 +1439,7 @@ env_copy(const VALUE *src_ep, VALUE read_only_variables) ep[VM_ENV_DATA_INDEX_SPECVAL] = VM_BLOCK_HANDLER_NONE; } + RB_OBJ_SET_SHAREABLE((VALUE)copied_env); return copied_env; } @@ -1493,9 +1506,10 @@ rb_proc_isolate_bang(VALUE self, VALUE replace_self) proc_isolate_env(self, proc, Qfalse); proc->is_isolated = TRUE; + RB_OBJ_WRITE(self, &proc->block.as.captured.self, Qnil); } - FL_SET_RAW(self, RUBY_FL_SHAREABLE); + RB_OBJ_SET_SHAREABLE(self); return self; } @@ -1537,10 +1551,16 @@ rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self) proc_isolate_env(self, proc, read_only_variables); proc->is_isolated = TRUE; } + else { + VALUE proc_self = vm_block_self(vm_proc_block(self)); + if (!rb_ractor_shareable_p(proc_self)) { + rb_raise(rb_eRactorIsolationError, + "Proc's self is not shareable: %" PRIsVALUE, + self); + } + } - rb_obj_freeze(self); - FL_SET_RAW(self, RUBY_FL_SHAREABLE); - + RB_OBJ_SET_FROZEN_SHAREABLE(self); return self; } diff --git a/vm_callinfo.h b/vm_callinfo.h index 80b5b9a0966c1e..6701b17d761cda 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -340,7 +340,7 @@ vm_cc_new(VALUE klass, enum vm_cc_type type) { cc_check_class(klass); - struct rb_callcache *cc = IMEMO_NEW(struct rb_callcache, imemo_callcache, klass); + struct rb_callcache *cc = SHAREABLE_IMEMO_NEW(struct rb_callcache, imemo_callcache, klass); *((struct rb_callable_method_entry_struct **)&cc->cme_) = (struct rb_callable_method_entry_struct *)cme; *((vm_call_handler *)&cc->call_) = call; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index b99ffdc4fdbbe6..cbed6143297b81 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6568,12 +6568,15 @@ vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep, const return; } - struct iseq_inline_constant_cache_entry *ice = IMEMO_NEW(struct iseq_inline_constant_cache_entry, imemo_constcache, 0); + struct iseq_inline_constant_cache_entry *ice = SHAREABLE_IMEMO_NEW(struct iseq_inline_constant_cache_entry, imemo_constcache, 0); RB_OBJ_WRITE(ice, &ice->value, val); ice->ic_cref = vm_get_const_key_cref(reg_ep); - if (rb_ractor_shareable_p(val)) ice->flags |= IMEMO_CONST_CACHE_SHAREABLE; - RB_OBJ_WRITE(iseq, &ic->entry, ice); + if (rb_ractor_shareable_p(val)) { + RUBY_ASSERT((rb_gc_verify_shareable(val), 1)); + ice->flags |= IMEMO_CONST_CACHE_SHAREABLE; + } + RB_OBJ_WRITE(iseq, &ic->entry, ice); RUBY_ASSERT(pc >= ISEQ_BODY(iseq)->iseq_encoded); unsigned pos = (unsigned)(pc - ISEQ_BODY(iseq)->iseq_encoded); rb_yjit_constant_ic_update(iseq, ic, pos); @@ -6585,6 +6588,7 @@ rb_vm_opt_getconstant_path(rb_execution_context_t *ec, rb_control_frame_t *const VALUE val; const ID *segments = ic->segments; struct iseq_inline_constant_cache_entry *ice = ic->entry; + if (ice && vm_ic_hit_p(ice, GET_EP())) { val = ice->value; @@ -6615,7 +6619,14 @@ vm_once_dispatch(rb_execution_context_t *ec, ISEQ iseq, ISE is) VALUE val; is->once.running_thread = th; val = rb_ensure(vm_once_exec, (VALUE)iseq, vm_once_clear, (VALUE)is); + // TODO: confirm that it is shareable + + if (RB_FL_ABLE(val)) { + RB_OBJ_SET_SHAREABLE(val); + } + RB_OBJ_WRITE(ec->cfp->iseq, &is->once.value, val); + /* is->once.running_thread is cleared by vm_once_clear() */ is->once.running_thread = RUNNING_THREAD_ONCE_DONE; /* success */ return val; diff --git a/vm_method.c b/vm_method.c index 60c273ff2f34fb..2cd41bd3774046 100644 --- a/vm_method.c +++ b/vm_method.c @@ -682,7 +682,7 @@ rb_vm_ci_lookup(ID mid, unsigned int flag, unsigned int argc, const struct rb_ca ((struct rb_callinfo_kwarg *)kwarg)->references++; } - struct rb_callinfo *new_ci = IMEMO_NEW(struct rb_callinfo, imemo_callinfo, (VALUE)kwarg); + struct rb_callinfo *new_ci = SHAREABLE_IMEMO_NEW(struct rb_callinfo, imemo_callinfo, (VALUE)kwarg); new_ci->mid = mid; new_ci->flag = flag; new_ci->argc = argc; @@ -1008,7 +1008,9 @@ rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *de if (cfp && (line = rb_vm_get_sourceline(cfp))) { VALUE location = rb_ary_new3(2, rb_iseq_path(cfp->iseq), INT2FIX(line)); - RB_OBJ_WRITE(me, &def->body.attr.location, rb_ary_freeze(location)); + rb_ary_freeze(location); + RB_OBJ_SET_SHAREABLE(location); + RB_OBJ_WRITE(me, &def->body.attr.location, location); } else { VM_ASSERT(def->body.attr.location == 0); @@ -1099,7 +1101,7 @@ rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, rb_method_ // not negative cache VM_ASSERT_TYPE2(defined_class, T_CLASS, T_ICLASS); } - rb_method_entry_t *me = IMEMO_NEW(rb_method_entry_t, imemo_ment, defined_class); + rb_method_entry_t *me = SHAREABLE_IMEMO_NEW(rb_method_entry_t, imemo_ment, defined_class); *((rb_method_definition_t **)&me->def) = def; me->called_id = called_id; me->owner = owner; From 024bbf54018dce223663be4057316006faab4295 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 22 Oct 2025 03:52:40 +0900 Subject: [PATCH 0471/2435] NameError (NoMethodError) is copyable because ISeq is shareable now. --- bootstraptest/test_ractor.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 634a3e3e6102b9..65ef07fb73e291 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1381,18 +1381,17 @@ class C }.map{|r| r.value}.join } -# NameError -assert_equal "ok", %q{ +# Now NoMethodError is copyable +assert_equal "NoMethodError", %q{ obj = "".freeze # NameError refers the receiver indirectly begin obj.bar rescue => err end - begin - Ractor.new{} << err - rescue TypeError - 'ok' - end + + r = Ractor.new{ Ractor.receive } + r << err + r.value.class } assert_equal "ok", %q{ From a177799807006b15341aa87b4f0ad9bb26c6b89c Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 22 Oct 2025 04:47:18 +0900 Subject: [PATCH 0472/2435] catch up modular-gc --- gc.c | 19 +++++++++++++++++++ gc/default/default.c | 19 ++++++++----------- gc/gc.h | 2 ++ gc/mmtk/mmtk.c | 6 ++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/gc.c b/gc.c index 231f5fb38853fe..53cfe839d5b912 100644 --- a/gc.c +++ b/gc.c @@ -2806,6 +2806,13 @@ mark_m_tbl(void *objspace, struct rb_id_table *tbl) bool rb_gc_impl_checking_shareable(void *objspace_ptr); // in defaut/deafult.c +bool +rb_gc_checking_shareable(void) +{ + return rb_gc_impl_checking_shareable(rb_gc_get_objspace()); +} + + static enum rb_id_table_iterator_result mark_const_entry_i(VALUE value, void *objspace) { @@ -5420,6 +5427,18 @@ rb_gc_after_fork(rb_pid_t pid) rb_gc_impl_after_fork(rb_gc_get_objspace(), pid); } +bool +rb_gc_obj_shareable_p(VALUE obj) +{ + return RB_OBJ_SHAREABLE_P(obj); +} + +void +rb_gc_rp(VALUE obj) +{ + rp(obj); +} + /* * Document-module: ObjectSpace * diff --git a/gc/default/default.c b/gc/default/default.c index b1c3bbf1a6b5a9..0a9945cdac98b2 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1463,12 +1463,6 @@ rb_gc_impl_checking_shareable(void *objspace_ptr) return objspace->flags.check_shareable; } -bool -rb_gc_checking_shareable(void) -{ - return rb_gc_impl_checking_shareable(rb_gc_get_objspace()); -} - bool rb_gc_impl_gc_enabled_p(void *objspace_ptr) { @@ -4981,11 +4975,11 @@ check_shareable_i(const VALUE child, void *ptr) { struct verify_internal_consistency_struct *data = (struct verify_internal_consistency_struct *)ptr; - if (!RB_OBJ_SHAREABLE_P(child)) { + if (!rb_gc_obj_shareable_p(child)) { fprintf(stderr, "(a) "); - rp(data->parent); + rb_gc_rp(data->parent); fprintf(stderr, "(b) "); - rp(child); + rb_gc_rp(child); fprintf(stderr, "check_shareable_i: shareable (a) -> unshareable (b)\n"); data->err_count++; @@ -4999,11 +4993,14 @@ gc_verify_shareable(rb_objspace_t *objspace, VALUE obj, void *data) // while objspace->flags.check_shareable is true, // other Ractors should not run the GC, until the flag is not local. // TODO: remove VM locking if the flag is Ractor local - RB_VM_LOCKING() { + + unsigned int lev = RB_GC_VM_LOCK(); + { objspace->flags.check_shareable = true; rb_objspace_reachable_objects_from(obj, check_shareable_i, (void *)data); objspace->flags.check_shareable = false; } + RB_GC_VM_UNLOCK(lev); } // TODO: only one level (non-recursive) @@ -5053,7 +5050,7 @@ verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, rb_objspace_reachable_objects_from(obj, check_generation_i, (void *)data); } - if (!is_marking(objspace) && RB_OBJ_SHAREABLE_P(obj)) { + if (!is_marking(objspace) && rb_gc_obj_shareable_p(obj)) { gc_verify_shareable(objspace, obj, data); } diff --git a/gc/gc.h b/gc/gc.h index 8ca9987477488c..89219eb7934692 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -96,6 +96,8 @@ MODULAR_GC_FN bool rb_memerror_reentered(void); MODULAR_GC_FN bool rb_obj_id_p(VALUE); MODULAR_GC_FN void rb_gc_before_updating_jit_code(void); MODULAR_GC_FN void rb_gc_after_updating_jit_code(void); +MODULAR_GC_FN bool rb_gc_obj_shareable_p(VALUE); +MODULAR_GC_FN void rb_gc_rp(VALUE); #if USE_MODULAR_GC MODULAR_GC_FN bool rb_gc_event_hook_required_p(rb_event_flag_t event); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 9dd3129e01664e..5861f5e70fdb66 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1260,6 +1260,12 @@ rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj) rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj); } +bool +rb_gc_impl_checking_shareable(void *ptr) +{ + return false; +} + // GC Identification const char * From 3b190855ba965c179693a5baf25f365d9d445c09 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 22 Oct 2025 05:10:15 +0900 Subject: [PATCH 0473/2435] skip jit payload They should be checked, but not sure JIT code... --- iseq.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/iseq.c b/iseq.c index daa684713f1bb8..aabeb83b3c349e 100644 --- a/iseq.c +++ b/iseq.c @@ -412,12 +412,15 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) #endif } else { + // TODO: check jit payload + if (!rb_gc_checking_shareable()) { #if USE_YJIT - rb_yjit_iseq_mark(body->yjit_payload); + rb_yjit_iseq_mark(body->yjit_payload); #endif #if USE_ZJIT - rb_zjit_iseq_mark(body->zjit_payload); + rb_zjit_iseq_mark(body->zjit_payload); #endif + } } } From e529bf7d675506f744e44d2aafddc35aec84731d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 23 Oct 2025 14:30:41 +0900 Subject: [PATCH 0474/2435] CI: Enable `check` on clangarm64 --- .github/workflows/mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 61ee5209592d1b..c071aee30bfe5c 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -69,7 +69,7 @@ jobs: test-all-opts: '--name=!/TestObjSpace#test_reachable_objects_during_iteration/' - msystem: 'CLANGARM64' os: 11-arm - test_task: 'test' + test_task: 'check' fail-fast: false if: >- From add78e76cedbe9ce430a0219dd80cbee734080b3 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 23 Oct 2025 15:30:17 +0100 Subject: [PATCH 0475/2435] ZJIT: Use iseq pointer directly in get/set class var codegen (#14921) Addresses https://github.com/ruby/ruby/pull/14918#discussion_r2453802886 --- zjit/src/codegen.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 029e144303f999..0a3be9277bb0e8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -506,12 +506,9 @@ fn gen_get_ep(asm: &mut Assembler, level: u32) -> Opnd { fn gen_objtostring(jit: &mut JITState, asm: &mut Assembler, val: Opnd, cd: *const rb_call_data, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - - let iseq_opnd = Opnd::Value(jit.iseq.into()); - // TODO: Specialize for immediate types // Call rb_vm_objtostring(iseq, recv, cd) - let ret = asm_ccall!(asm, rb_vm_objtostring, iseq_opnd, val, (cd as usize).into()); + let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE(jit.iseq as usize).into(), val, (cd as usize).into()); // TODO: Call `to_s` on the receiver if rb_vm_objtostring returns Qundef // Need to replicate what CALL_SIMPLE_METHOD does @@ -836,16 +833,12 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - - let iseq = asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ)); - asm_ccall!(asm, rb_vm_getclassvariable, iseq, CFP, id.0.into(), Opnd::const_ptr(ic)) + asm_ccall!(asm, rb_vm_getclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) } fn gen_setclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) { gen_prepare_non_leaf_call(jit, asm, state); - - let iseq = asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ)); - asm_ccall!(asm, rb_vm_setclassvariable, iseq, CFP, id.0.into(), val, Opnd::const_ptr(ic)); + asm_ccall!(asm, rb_vm_setclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic)); } /// Look up global variables From 6dc879f9d6ca85cd46b54a79a43162c483b36ecc Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 23 Oct 2025 10:24:02 -0500 Subject: [PATCH 0476/2435] [ruby/stringio] [DOC] Tweaks for StringIO.new (https://github.com/ruby/stringio/pull/144) https://github.com/ruby/stringio/commit/d33ac815c1 --- ext/stringio/stringio.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d37dee59ff963c..18a9ef1523ec5d 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -225,17 +225,32 @@ strio_s_allocate(VALUE klass) * call-seq: * StringIO.new(string = '', mode = 'r+') -> new_stringio * - * Note that +mode+ defaults to 'r' if +string+ is frozen. - * * Returns a new \StringIO instance formed from +string+ and +mode+; - * see {Access Modes}[rdoc-ref:File@Access+Modes]: + * the instance should be closed when no longer needed: * - * strio = StringIO.new # => # + * strio = StringIO.new + * strio.string # => "" + * strio.closed_read? # => false + * strio.closed_write? # => false * strio.close * - * The instance should be closed when no longer needed. + * If +string+ is frozen, the default +mode+ is 'r': + * + * strio = StringIO.new('foo'.freeze) + * strio.string # => "foo" + * strio.closed_read? # => false + * strio.closed_write? # => true + * strio.close + * + * Argument +mode+ must be a valid + * {Access Mode}[rdoc-ref:File@Access+Modes], + * which may be a string or an integer constant: + * + * StringIO.new('foo', 'w+') + * StringIO.new('foo', File::RDONLY) * - * Related: StringIO.open (accepts block; closes automatically). + * Related: StringIO.open + * (passes the \StringIO object to the block; closes the object automatically on block exit). */ static VALUE strio_initialize(int argc, VALUE *argv, VALUE self) @@ -712,7 +727,7 @@ strio_set_lineno(VALUE self, VALUE lineno) * binmode -> self * * Sets the data mode in +self+ to binary mode; - * see {Data Mode}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Data+Mode]. + * see {Data Mode}[rdoc-ref:File@Data+Mode]. * */ static VALUE From b3fb91f4d1cc651a112e2e26422e76c6f8323eeb Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 23 Oct 2025 09:45:20 -0400 Subject: [PATCH 0477/2435] Make Namespace::Root TypedData inherit from Namespace::Entry The two types of TypedData objects should be treated the same except for the free function. Since Namespace::Root did not inherit from Namespace::Entry, all the TypedData_Get_Struct calls for Namespace::Root would raise "wrong argument type Namespace::Root (expected Namespace::Entry)". --- namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index b85cbf57157924..4e5a4e9bc42491 100644 --- a/namespace.c +++ b/namespace.c @@ -268,7 +268,7 @@ const rb_data_type_t rb_root_namespace_data_type = { namespace_entry_memsize, rb_namespace_gc_update_references, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers + &rb_namespace_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers }; VALUE From 1d835539524cab6cd8b9210f34690e5e5e705f39 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 23 Oct 2025 17:24:47 -0400 Subject: [PATCH 0478/2435] ZJIT: Inline << and push for Array in single arg case (#14926) Fixes https://github.com/Shopify/ruby/issues/813 --- zjit/src/cruby_methods.rs | 11 +++++++ zjit/src/hir.rs | 64 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index ee10eaa681c7e4..0d77b1d6cc7e11 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -203,6 +203,8 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); + annotate!(rb_cArray, "<<", inline_array_push); + annotate!(rb_cArray, "push", inline_array_push); annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); @@ -266,6 +268,15 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + // Inline only the case of `<<` or `push` when called with a single argument. + if let &[val] = args { + let _ = fun.push_insn(block, hir::Insn::ArrayPush { array: recv, val, state }); + return Some(recv); + } + None +} + fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if let &[key] = args { let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9f422c0146cce9..7ee2308eb5d888 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -14008,7 +14008,69 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallWithFrame <<@0x1038, v26, v13 + ArrayPush v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_push_single_arg() { + eval(" + def test(arr) + arr.push(1) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + ArrayPush v24, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_do_not_optimize_array_push_multi_arg() { + eval(" + def test(arr) + arr.push(1,2,3) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v14:Fixnum[2] = Const Value(2) + v15:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 CheckInterrupts Return v27 "); From f33cd1289e75c7091e61e54792a2b7e1f84ea697 Mon Sep 17 00:00:00 2001 From: Daniel Colson Date: Thu, 23 Oct 2025 17:47:58 -0400 Subject: [PATCH 0479/2435] ZJIT: Add tests for non-leaf classvar get and set (#14924) This commit adds tests that raise on `getclassvariable` and `setclassvariable` to exercise the non-leaf cases as suggested in https://github.com/ruby/ruby/pull/14918#discussion_r2453804603 --- test/ruby/test_zjit.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 13c78170177a20..e76e140ba13f2b 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1675,6 +1675,20 @@ def self.test = @@x } end + def test_getclassvariable_raises + assert_compiles '"uninitialized class variable @@x in Foo"', %q{ + class Foo + def self.test = @@x + end + + begin + Foo.test + rescue NameError => e + e.message + end + } + end + def test_setclassvariable assert_compiles '42', %q{ class Foo @@ -1686,6 +1700,21 @@ def self.test = @@x = 42 } end + def test_setclassvariable_raises + assert_compiles '"can\'t modify frozen #: Foo"', %q{ + class Foo + def self.test = @@x = 42 + freeze + end + + begin + Foo.test + rescue FrozenError => e + e.message + end + } + end + def test_attr_reader assert_compiles '[4, 4]', %q{ class C From c2bce540f93aba77ddf89c7931a63b4e7108e466 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 23 Oct 2025 15:16:41 -0400 Subject: [PATCH 0480/2435] ZJIT: Replace `as usize` casts in codegen.rs The `as` casts are somewhat dangerous since when the type on either side change, it silently becomes a lossy conversion. This is why we have `IntoUsize` as well as other guaranteed lossless conversion utilities in stdlib. Use them. For pointers-to-address, `ptr::addr` is more informative. See also: https://tratt.net/laurie/blog/2021/static_integer_types.html --- zjit/src/codegen.rs | 36 ++++++++++++++++++------------------ zjit/src/virtualmem.rs | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0a3be9277bb0e8..81608e5ae2528d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -188,8 +188,8 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result let (code_ptr, gc_offsets) = asm.compile(cb)?; assert!(gc_offsets.is_empty()); if get_option!(perf) { - let start_ptr = code_ptr.raw_ptr(cb) as usize; - let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize; + let start_ptr = code_ptr.raw_addr(cb); + let end_ptr = cb.get_write_ptr().raw_addr(cb); let code_size = end_ptr - start_ptr; let iseq_name = iseq_get_location(iseq, 0); register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size); @@ -298,8 +298,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul let result = asm.compile(cb); if let Ok((start_ptr, _)) = result { if get_option!(perf) { - let start_usize = start_ptr.raw_ptr(cb) as usize; - let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize; + let start_usize = start_ptr.raw_addr(cb); + let end_usize = cb.get_write_ptr().raw_addr(cb); let code_size = end_usize - start_usize; let iseq_name = iseq_get_location(iseq, 0); register_with_perf(iseq_name, start_usize, code_size); @@ -508,7 +508,7 @@ fn gen_objtostring(jit: &mut JITState, asm: &mut Assembler, val: Opnd, cd: *cons gen_prepare_non_leaf_call(jit, asm, state); // TODO: Specialize for immediate types // Call rb_vm_objtostring(iseq, recv, cd) - let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE(jit.iseq as usize).into(), val, (cd as usize).into()); + let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE::from(jit.iseq).into(), val, Opnd::const_ptr(cd)); // TODO: Call `to_s` on the receiver if rb_vm_objtostring returns Qundef // Need to replicate what CALL_SIMPLE_METHOD does @@ -833,12 +833,12 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - asm_ccall!(asm, rb_vm_getclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) + asm_ccall!(asm, rb_vm_getclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) } fn gen_setclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) { gen_prepare_non_leaf_call(jit, asm, state); - asm_ccall!(asm, rb_vm_setclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic)); + asm_ccall!(asm, rb_vm_setclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic)); } /// Look up global variables @@ -975,7 +975,7 @@ fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index); - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index as usize) as i32; + let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.as_usize()) as i32; let self_val = asm.load(self_val); let ivar_opnd = Opnd::mem(64, self_val, offs); asm.load(ivar_opnd) @@ -990,7 +990,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index as usize) as i32); + let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.as_usize()) as i32); asm.load(ivar_opnd) } @@ -1113,7 +1113,7 @@ fn gen_send( } asm.ccall( rb_vm_send as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1136,7 +1136,7 @@ fn gen_send_forward( } asm.ccall( rb_vm_sendforward as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1157,7 +1157,7 @@ fn gen_send_without_block( } asm.ccall( rb_vm_opt_send_without_block as *const u8, - vec![EC, CFP, (cd as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd)], ) } @@ -1263,7 +1263,7 @@ fn gen_invokeblock( } asm.ccall( rb_vm_invokeblock as *const u8, - vec![EC, CFP, (cd as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd)], ) } @@ -1285,7 +1285,7 @@ fn gen_invokesuper( } asm.ccall( rb_vm_invokesuper as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1544,7 +1544,7 @@ fn gen_is_method_cfunc(jit: &JITState, asm: &mut Assembler, val: lir::Opnd, cd: unsafe extern "C" { fn rb_vm_method_cfunc_is(iseq: IseqPtr, cd: *const rb_call_data, recv: VALUE, cfunc: *const u8) -> VALUE; } - asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE(jit.iseq as usize).into(), (cd as usize).into(), val, (cfunc as usize).into()) + asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE::from(jit.iseq).into(), Opnd::const_ptr(cd), val, Opnd::const_ptr(cfunc)) } fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { @@ -1889,7 +1889,7 @@ fn param_opnd(idx: usize) -> Opnd { /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. pub fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 { let local_size = unsafe { get_iseq_body_local_table_size(iseq) }; - local_size_and_idx_to_ep_offset(local_size as usize, local_idx) + local_size_and_idx_to_ep_offset(local_size.as_usize(), local_idx) } /// Convert the number of locals and a local index to an offset from the EP @@ -2005,8 +2005,8 @@ c_callable! { rb_set_cfp_sp(cfp, sp); // Fill nils to uninitialized (non-argument) locals - let local_size = get_iseq_body_local_table_size(iseq) as usize; - let num_params = get_iseq_body_param_size(iseq) as usize; + let local_size = get_iseq_body_local_table_size(iseq).as_usize(); + let num_params = get_iseq_body_param_size(iseq).as_usize(); let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); } diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 5af5c0e8b999a4..2717dfcf225f23 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -86,7 +86,7 @@ impl CodePtr { /// Get the address of the code pointer. pub fn raw_addr(self, base: &impl CodePtrBase) -> usize { - self.raw_ptr(base) as usize + self.raw_ptr(base).addr() } /// Get the offset component for the code pointer. Useful finding the distance between two From 8de628dc8055e1d812fdf326e4a6f74ce11a283d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 23 Oct 2025 15:22:23 -0400 Subject: [PATCH 0481/2435] ZJIT: s/as_usize/to_usize/ to comply with rust API guidelines When the name is `as_*`, the guideline expects the return type to be a reference type. Also, it's good to have contrast in the naming from the more dangerous `as usize` cast `IntoUsize` is meant to be preferred over. See: https://rust-lang.github.io/api-guidelines/naming.html --- zjit/src/cast.rs | 10 +++++----- zjit/src/codegen.rs | 20 ++++++++++---------- zjit/src/hir.rs | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/zjit/src/cast.rs b/zjit/src/cast.rs index c6d11ef4af18dc..52e2078cde3cc3 100644 --- a/zjit/src/cast.rs +++ b/zjit/src/cast.rs @@ -16,19 +16,19 @@ /// the method `into()` also causes a name conflict. pub(crate) trait IntoUsize { /// Convert to usize. Implementation conditional on width of [usize]. - fn as_usize(self) -> usize; + fn to_usize(self) -> usize; } #[cfg(target_pointer_width = "64")] impl IntoUsize for u64 { - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self as usize } } #[cfg(target_pointer_width = "64")] impl IntoUsize for u32 { - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self as usize } } @@ -36,7 +36,7 @@ impl IntoUsize for u32 { impl IntoUsize for u16 { /// Alias for `.into()`. For convenience so you could use the trait for /// all unsgined types. - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self.into() } } @@ -44,7 +44,7 @@ impl IntoUsize for u16 { impl IntoUsize for u8 { /// Alias for `.into()`. For convenience so you could use the trait for /// all unsgined types. - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self.into() } } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 81608e5ae2528d..16b5e94d342263 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -736,7 +736,7 @@ fn gen_ccall_with_frame( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -792,7 +792,7 @@ fn gen_ccall_variadic( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -975,7 +975,7 @@ fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index); - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.as_usize()) as i32; + let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.to_usize()) as i32; let self_val = asm.load(self_val); let ivar_opnd = Opnd::mem(64, self_val, offs); asm.load(ivar_opnd) @@ -990,7 +990,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.as_usize()) as i32); + let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.to_usize()) as i32); asm.load(ivar_opnd) } @@ -1174,8 +1174,8 @@ fn gen_send_without_block_direct( ) -> lir::Opnd { gen_incr_counter(asm, Counter::iseq_optimized_send_count); - let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.as_usize(); - let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.as_usize(); + let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.to_usize(); + let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.to_usize(); gen_stack_overflow_check(jit, asm, state, stack_growth); // Save cfp->pc and cfp->sp for the caller frame @@ -1211,7 +1211,7 @@ fn gen_send_without_block_direct( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -1889,7 +1889,7 @@ fn param_opnd(idx: usize) -> Opnd { /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. pub fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 { let local_size = unsafe { get_iseq_body_local_table_size(iseq) }; - local_size_and_idx_to_ep_offset(local_size.as_usize(), local_idx) + local_size_and_idx_to_ep_offset(local_size.to_usize(), local_idx) } /// Convert the number of locals and a local index to an offset from the EP @@ -2005,8 +2005,8 @@ c_callable! { rb_set_cfp_sp(cfp, sp); // Fill nils to uninitialized (non-argument) locals - let local_size = get_iseq_body_local_table_size(iseq).as_usize(); - let num_params = get_iseq_body_param_size(iseq).as_usize(); + let local_size = get_iseq_body_local_table_size(iseq).to_usize(); + let num_params = get_iseq_body_param_size(iseq).to_usize(); let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7ee2308eb5d888..dbb9177ee3e6e8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1873,7 +1873,7 @@ impl Function { /// Set self.param_types. They are copied to the param types of jit_entry_blocks. fn set_param_types(&mut self) { let iseq = self.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let rest_param_idx = iseq_rest_param_idx(iseq); self.param_types.push(types::BasicObject); // self @@ -3885,7 +3885,7 @@ pub enum ParseError { /// Return the number of locals in the current ISEQ (includes parameters) fn num_locals(iseq: *const rb_iseq_t) -> usize { - (unsafe { get_iseq_body_local_table_size(iseq) }).as_usize() + (unsafe { get_iseq_body_local_table_size(iseq) }).to_usize() } /// If we can't handle the type of send (yet), bail out. @@ -4896,7 +4896,7 @@ fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32]) { /// Compile initial locals for an entry_block for the interpreter fn compile_entry_state(fun: &mut Function, entry_block: BlockId) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let rest_param_idx = iseq_rest_param_idx(iseq); let self_param = fun.push_insn(entry_block, Insn::LoadSelf); @@ -4929,7 +4929,7 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc /// Compile params and initial locals for a jit_entry_block fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let self_param = fun.push_insn(jit_entry_block, Insn::Param); let mut entry_state = FrameState::new(iseq); From 8b0d405337781205412f7eb8fd7d3ae684f3469a Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 20:53:46 +0100 Subject: [PATCH 0482/2435] [DOC] Tweaks for String#start_with? --- doc/string/start_with_p.rdoc | 11 +++++------ string.c | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/string/start_with_p.rdoc b/doc/string/start_with_p.rdoc index 5d1f9f954370e0..298a5572769ea8 100644 --- a/doc/string/start_with_p.rdoc +++ b/doc/string/start_with_p.rdoc @@ -1,10 +1,9 @@ -Returns whether +self+ starts with any of the given +string_or_regexp+. +Returns whether +self+ starts with any of the given +patterns+. -Matches patterns against the beginning of +self+. -For each given +string_or_regexp+, the pattern is: +For each argument, the pattern used is: -- +string_or_regexp+ itself, if it is a Regexp. -- Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string. +- The pattern itself, if it is a Regexp. +- Regexp.quote(pattern), if it is a string. Returns +true+ if any pattern matches the beginning, +false+ otherwise: @@ -15,4 +14,4 @@ Returns +true+ if any pattern matches the beginning, +false+ otherwise: 'тест'.start_with?('т') # => true 'こんにちは'.start_with?('こ') # => true -Related: String#end_with?. +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/string.c b/string.c index 1de87071272938..d90f606b821c2d 100644 --- a/string.c +++ b/string.c @@ -11214,7 +11214,7 @@ rb_str_rpartition(VALUE str, VALUE sep) /* * call-seq: - * start_with?(*string_or_regexp) -> true or false + * start_with?(*patterns) -> true or false * * :include: doc/string/start_with_p.rdoc * From 0227ad07a41eea95349a9f26946dbf00291da17f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 21:34:55 +0100 Subject: [PATCH 0483/2435] [DOC] Tweaks for String#strip! --- string.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index d90f606b821c2d..eec64ac63f959a 100644 --- a/string.c +++ b/string.c @@ -10482,10 +10482,12 @@ rb_str_rstrip(VALUE str) * call-seq: * strip! -> self or nil * - * Like String#strip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * Like String#strip, except that: * - * Related: String#lstrip!, String#strip!. + * - Any modifications are made to +self+. + * - Returns +self+ if any modification are made, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From ab94bce885314d0065514a88cd356a89642292b0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 23 Oct 2025 17:13:37 -0500 Subject: [PATCH 0484/2435] [DOC] Tweaks for String#squeeze! --- string.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index eec64ac63f959a..373c76a26fc69e 100644 --- a/string.c +++ b/string.c @@ -8903,8 +8903,12 @@ rb_str_delete(int argc, VALUE *argv, VALUE str) * call-seq: * squeeze!(*selectors) -> self or nil * - * Like String#squeeze, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#squeeze, except that: + * + * - Characters are squeezed in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. + * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From 230276dd42e49059c920c3230268ebed776e74a1 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 21:12:54 +0100 Subject: [PATCH 0485/2435] [DOC] Tweaks for String#strip --- string.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 373c76a26fc69e..55a7eebc5a579f 100644 --- a/string.c +++ b/string.c @@ -10525,15 +10525,15 @@ rb_str_strip_bang(VALUE str) * call-seq: * strip -> new_string * - * Returns a copy of the receiver with leading and trailing whitespace removed; + * Returns a copy of +self+ with leading and trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.strip # => "abc" * - * Related: String#lstrip, String#rstrip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From 877ebe5b89addd75f8b4b0e5593cfb0e66342428 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 23 Oct 2025 20:13:39 -0500 Subject: [PATCH 0486/2435] [ruby/stringio] [DOC] Tweaks for StringIO#close (https://github.com/ruby/stringio/pull/148) Make examples do more work (instead of text). https://github.com/ruby/stringio/commit/7f4662438f --- ext/stringio/stringio.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 18a9ef1523ec5d..965355df67d87c 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -549,11 +549,16 @@ strio_set_string(VALUE self, VALUE string) * call-seq: * close -> nil * - * Closes +self+ for both reading and writing. + * Closes +self+ for both reading and writing; returns +nil+: * - * Raises IOError if reading or writing is attempted. + * strio = StringIO.new + * strio.closed? # => false + * strio.close # => nil + * strio.closed? # => true + * strio.read # Raises IOError: not opened for reading + * strio.write # Raises IOError: not opened for writing * - * Related: StringIO#close_read, StringIO#close_write. + * Related: StringIO#close_read, StringIO#close_write, StringIO.closed?. */ static VALUE strio_close(VALUE self) From daca69c8e922da693d45e71db8cb96c06cc1907f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:14:29 +0900 Subject: [PATCH 0487/2435] Restore primary maintainer name at the bundled gems --- doc/maintainers.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/doc/maintainers.md b/doc/maintainers.md index 353909fe152baa..ffd3f84bb1c916 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -396,34 +396,42 @@ have commit right, others don't. ### minitest +* Ryan Davis ([zenspider]) * https://github.com/minitest/minitest ### power_assert +* Tsujimoto Kenta ([k-tsj]) * https://github.com/ruby/power_assert ### rake +* Hiroshi SHIBATA ([hsbt]) * https://github.com/ruby/rake ### test-unit +* Kouhei Sutou ([kou]) * https://github.com/test-unit/test-unit ### rexml +* Kouhei Sutou ([kou]) * https://github.com/ruby/rexml ### rss +* Kouhei Sutou ([kou]) * https://github.com/ruby/rss ### net-ftp +* Shugo Maeda ([shugo]) * https://github.com/ruby/net-ftp ### net-imap +* Nicholas A. Evans ([nevans]) * https://github.com/ruby/net-imap ### net-pop @@ -432,10 +440,12 @@ have commit right, others don't. ### net-smtp +* TOMITA Masahiro ([tmtm]) * https://github.com/ruby/net-smtp ### matrix +* Marc-André Lafortune ([marcandre]) * https://github.com/ruby/matrix ### prime @@ -444,18 +454,22 @@ have commit right, others don't. ### rbs +* Soutaro Matsumoto ([soutaro]) * https://github.com/ruby/rbs ### typeprof +* Yusuke Endoh ([mame]) * https://github.com/ruby/typeprof ### debug +* Koichi Sasada ([ko1]) * https://github.com/ruby/debug ### racc +* Yuichi Kaneko ([yui-knk]) * https://github.com/ruby/racc #### mutex_m @@ -468,10 +482,12 @@ have commit right, others don't. #### base64 +* Yusuke Endoh ([mame]) * https://github.com/ruby/base64 #### bigdecimal +* Kenta Murata ([mrkn]) * https://github.com/ruby/bigdecimal #### observer @@ -480,34 +496,42 @@ have commit right, others don't. #### abbrev +* Akinori MUSHA ([knu]) * https://github.com/ruby/abbrev #### resolv-replace +* Akira TANAKA ([akr]) * https://github.com/ruby/resolv-replace #### rinda +* Masatoshi SEKI ([seki]) * https://github.com/ruby/rinda #### drb +* Masatoshi SEKI ([seki]) * https://github.com/ruby/drb #### nkf +* Naruse Yusuke ([nurse]) * https://github.com/ruby/nkf #### syslog +* Akinori Musha ([knu]) * https://github.com/ruby/syslog #### csv +* Kouhei Sutou ([kou]) * https://github.com/ruby/csv #### ostruct +* Marc-André Lafortune ([marcandre]) * https://github.com/ruby/ostruct #### pstore @@ -520,22 +544,34 @@ have commit right, others don't. #### logger +* Naotoshi Seo ([sonots]) * https://github.com/ruby/logger #### rdoc +* Stan Lo ([st0012]) +* Nobuyoshi Nakada ([nobu]) * https://github.com/ruby/rdoc #### win32ole +* Masaki Suketa ([suketa]) * https://github.com/ruby/win32ole #### irb +* Tomoya Ishida ([tompng]) +* Stan Lo ([st0012]) +* Mari Imaizumi ([ima1zumi]) +* HASUMI Hitoshi ([hasumikin]) * https://github.com/ruby/irb #### reline +* Tomoya Ishida ([tompng]) +* Stan Lo ([st0012]) +* Mari Imaizumi ([ima1zumi]) +* HASUMI Hitoshi ([hasumikin]) * https://github.com/ruby/reline #### readline @@ -544,6 +580,7 @@ have commit right, others don't. #### fiddle +* Kouhei Sutou ([kou]) * https://github.com/ruby/fiddle ## Platform Maintainers @@ -625,3 +662,12 @@ have commit right, others don't. [tompng]: https://github.com/tompng [unak]: https://github.com/unak [yuki24]: https://github.com/yuki24 +[zenspider]: https://github.com/zenspider +[k-tsj]: https://github.com/k-tsj +[nevans]: https://github.com/nevans +[tmtm]: https://github.com/tmtm +[shugo]: https://github.com/shugo +[soutaro]: https://github.com/soutaro +[yui-knk]: https://github.com/yui-knk +[hasumikin]: https://github.com/hasumikin +[suketa]: https://github.com/suketa From f31cd3759b9b30d8b01bf613cfdc81ffc07cd1ed Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:29:52 +0900 Subject: [PATCH 0488/2435] Revised unmaintained to reflect the current situation --- doc/maintainers.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index ffd3f84bb1c916..084e8851636fc9 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -28,6 +28,10 @@ not have authority to change/add a feature on his/her part. They need consensus on ruby-core/ruby-dev before changing/adding. Some of submaintainers have commit right, others don't. +No maintainer means that there is no specific maintainer for the part now. +The member of ruby core team can fix issues at anytime. But major changes need +consensus on ruby-core/ruby-dev. + ### Language core features including security * Yukihiro Matsumoto ([matz]) @@ -46,7 +50,7 @@ have commit right, others don't. #### lib/mkmf.rb -* *unmaintained* +* *No maintainer* #### lib/rubygems.rb, lib/rubygems/* @@ -78,15 +82,15 @@ have commit right, others don't. #### ext/objspace -* *unmaintained* +* *No maintainer* #### ext/pty -* *unmaintained* +* *No maintainer* #### ext/ripper -* *unmaintained* +* *No maintainer* #### ext/socket @@ -109,17 +113,17 @@ have commit right, others don't. #### lib/cgi/escape.rb -* *unmaintained* +* *No maintainer* #### lib/English.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/English * https://rubygems.org/gems/English #### lib/delegate.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/delegate * https://rubygems.org/gems/delegate @@ -150,7 +154,7 @@ have commit right, others don't. #### lib/fileutils.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/fileutils * https://rubygems.org/gems/fileutils @@ -185,13 +189,13 @@ have commit right, others don't. #### lib/net/protocol.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/net-protocol * https://rubygems.org/gems/net-protocol #### lib/open3.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/open3 * https://rubygems.org/gems/open3 @@ -246,7 +250,7 @@ have commit right, others don't. #### lib/tempfile.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/tempfile * https://rubygems.org/gems/tempfile @@ -270,7 +274,7 @@ have commit right, others don't. #### lib/tmpdir.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/tmpdir * https://rubygems.org/gems/tmpdir @@ -301,7 +305,7 @@ have commit right, others don't. #### lib/weakref.rb -* *unmaintained* +* *No maintainer* * https://github.com/ruby/weakref * https://rubygems.org/gems/weakref @@ -313,19 +317,19 @@ have commit right, others don't. #### ext/date -* *unmaintained* +* *No maintainer* * https://github.com/ruby/date * https://rubygems.org/gems/date #### ext/etc -* *unmaintained* +* *No maintainer* * https://github.com/ruby/etc * https://rubygems.org/gems/etc #### ext/fcntl -* *unmaintained* +* *No maintainer* * https://github.com/ruby/fcntl * https://rubygems.org/gems/fcntl @@ -619,7 +623,7 @@ have commit right, others don't. ### cygwin, ... -* none. (Maintainer WANTED) +* **No maintainer** ### WebAssembly/WASI From b58b89b92088bff4fceb8c417502762ed7a3ddfc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:41:07 +0900 Subject: [PATCH 0489/2435] Revised structure of maintenance policy --- doc/maintainers.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index 084e8851636fc9..2813afb6b85510 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -44,9 +44,7 @@ consensus on ruby-core/ruby-dev. * Yukihiro Matsumoto ([matz]) -## Standard Library Maintainers - -### Libraries +### Standard Library Maintainers #### lib/mkmf.rb @@ -62,7 +60,7 @@ consensus on ruby-core/ruby-dev. * Martin J. Dürst ([duerst]) -### Extensions +### Standard Library(Extensions) Maintainers #### ext/continuation @@ -101,9 +99,7 @@ consensus on ruby-core/ruby-dev. * NAKAMURA Usaku ([unak]) -## Default gems Maintainers - -### Libraries +### Default gems(Libraries) Maintainers #### lib/bundler.rb, lib/bundler/* @@ -309,7 +305,7 @@ consensus on ruby-core/ruby-dev. * https://github.com/ruby/weakref * https://rubygems.org/gems/weakref -### Extensions +### Default gems(Extensions) Maintainers #### ext/cgi @@ -396,7 +392,7 @@ consensus on ruby-core/ruby-dev. * https://github.com/ruby/zlib * https://rubygems.org/gems/zlib -## Bundled gems upstream repositories +## Bundled gems upstream repositories and maintainers ### minitest From f388647ecc0a3157047f3356c88096327808f224 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:42:04 +0900 Subject: [PATCH 0490/2435] Added policy of bundled gems --- doc/maintainers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/maintainers.md b/doc/maintainers.md index 2813afb6b85510..afd6e99d946aa1 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -394,6 +394,12 @@ consensus on ruby-core/ruby-dev. ## Bundled gems upstream repositories and maintainers +The maintanance policy of bundled gems is different from Module Maintainers above. +Please check the policies for each repository. + +The ruby core team tries to maintain the repositories with no maintainers. +It may needs to make consensus on ruby-core/ruby-dev before making major changes. + ### minitest * Ryan Davis ([zenspider]) From 7aac92abe899525efb4f53934e475263e76412ee Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:43:24 +0900 Subject: [PATCH 0491/2435] Update the primary maintainer of rubygems --- doc/maintainers.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index afd6e99d946aa1..891a58b0d169d7 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -52,7 +52,6 @@ consensus on ruby-core/ruby-dev. #### lib/rubygems.rb, lib/rubygems/* -* Eric Hodel ([drbrain]) * Hiroshi SHIBATA ([hsbt]) * https://github.com/ruby/rubygems From 0eaea7bdd30e21977f0f959e5b35bbf060ca3304 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Oct 2025 11:56:21 +0900 Subject: [PATCH 0492/2435] thwait has been extracted to http://github.com/ruby/thwait --- doc/maintainers.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index 891a58b0d169d7..9aa1a03bf62682 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -261,12 +261,6 @@ consensus on ruby-core/ruby-dev. * https://github.com/ruby/timeout * https://rubygems.org/gems/timeout -#### lib/thwait.rb - -* Keiju ISHITSUKA ([keiju]) -* https://github.com/ruby/thwait -* https://rubygems.org/gems/thwait - #### lib/tmpdir.rb * *No maintainer* From f8ccc0afb9e6a1687c181aad8df617c513760c75 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 23 Oct 2025 13:35:46 +0900 Subject: [PATCH 0493/2435] [ruby/rubygems] Forcely activate irb if that isn't available when running with bundle console https://github.com/ruby/rubygems/commit/42e22fd367 --- lib/bundler/cli/console.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb index f6389e8ea00484..2d1a2ce458dd44 100644 --- a/lib/bundler/cli/console.rb +++ b/lib/bundler/cli/console.rb @@ -21,6 +21,11 @@ def get_console(name) get_constant(name) rescue LoadError if name == "irb" + if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate) + Gem::BUNDLED_GEMS.force_activate "irb" + require name + return get_constant(name) + end Bundler.ui.error "#{name} is not available" exit 1 else From a9f24aaccb28b786068cce9df0d6beaf84f7ce7f Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 24 Oct 2025 11:50:47 -0400 Subject: [PATCH 0494/2435] ZJIT: Specialize string length, bytesize, and size (#14928) Don't push frame for String#size, String#bytesize, and String#length. --- zjit/src/cruby_methods.rs | 3 + zjit/src/hir.rs | 177 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0d77b1d6cc7e11..b6b91ab0a6db7a 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -190,6 +190,9 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); + annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index dbb9177ee3e6e8..b358ac5d51ca4d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -15499,4 +15499,181 @@ mod opt_tests { Return v28 "); } + + #[test] + fn test_specialize_string_size() { + eval(r#" + def test(s) + s.size + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_size() { + eval(r#" + def test(s) + s.size + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_specialize_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v25:Fixnum = CCall bytesize@0x1038, v23 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_elide_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_string_length() { + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_length() { + eval(r#" + def test(s) + s.length + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } } From 8b6564d1493a59d437dd88b4cdfad44ca2889138 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 17:03:14 -0400 Subject: [PATCH 0495/2435] ZJIT: Add gen_stack_overflow_check to `CCallWithFrame` --- zjit/src/codegen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 16b5e94d342263..e16588e3e33bbd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -706,6 +706,7 @@ fn gen_ccall_with_frame( state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::non_variadic_cfunc_optimized_send_count); + gen_stack_overflow_check(jit, asm, state, state.stack_size()); let caller_stack_size = state.stack_size() - args.len(); From 245df86ec3ed30cb844a16b5a4df7e4d91d7c293 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 23:07:54 +0100 Subject: [PATCH 0496/2435] [DOC] Tweaks for String#sub --- doc/string/sub.rdoc | 33 +++++++++++++++++++++++++++++++++ string.c | 8 +------- 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 doc/string/sub.rdoc diff --git a/doc/string/sub.rdoc b/doc/string/sub.rdoc new file mode 100644 index 00000000000000..ff051ea1776be4 --- /dev/null +++ b/doc/string/sub.rdoc @@ -0,0 +1,33 @@ +Returns a copy of self, possibly with a substring replaced. + +Argument +pattern+ may be a string or a Regexp; +argument +replacement+ may be a string or a Hash. + +Varying types for the argument values makes this method very versatile. + +Below are some simple examples; for many more examples, +see {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + +With arguments +pattern+ and string +replacement+ given, +replaces the first matching substring with the given replacement string: + + s = 'abracadabra' # => "abracadabra" + s.sub('bra', 'xyzzy') # => "axyzzycadabra" + s.sub(/bra/, 'xyzzy') # => "axyzzycadabra" + s.sub('nope', 'xyzzy') # => "abracadabra" + +With arguments +pattern+ and hash +replacement+ given, +replaces the first matching substring with a value from the given replacement hash, or removes it: + + h = {'a' => 'A', 'b' => 'B', 'c' => 'C'} + s.sub('b', h) # => "aBracadabra" + s.sub(/b/, h) # => "aBracadabra" + s.sub(/d/, h) # => "abracaabra" # 'd' removed. + +With argument +pattern+ and a block given, +calls the block with each matching substring; +replaces that substring with the block’s return value: + + s.sub('b') {|match| match.upcase } # => "aBracadabra" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 55a7eebc5a579f..824c04a5b20468 100644 --- a/string.c +++ b/string.c @@ -6361,13 +6361,7 @@ rb_str_sub_bang(int argc, VALUE *argv, VALUE str) * sub(pattern, replacement) -> new_string * sub(pattern) {|match| ... } -> new_string * - * Returns a copy of +self+ with only the first occurrence - * (not all occurrences) of the given +pattern+ replaced. - * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Related: String#sub!, String#gsub, String#gsub!. - * + * :include: doc/string/sub.rdoc */ static VALUE From fcae206232d923af0de7b0df3917e28f36e116f1 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 24 Oct 2025 21:18:40 +0100 Subject: [PATCH 0497/2435] [DOC] Tweaks for String#sub! --- string.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/string.c b/string.c index 824c04a5b20468..89445df2445d9e 100644 --- a/string.c +++ b/string.c @@ -6236,13 +6236,12 @@ rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) * sub!(pattern, replacement) -> self or nil * sub!(pattern) {|match| ... } -> self or nil * - * Replaces the first occurrence (not all occurrences) of the given +pattern+ - * on +self+; returns +self+ if a replacement occurred, +nil+ otherwise. + * Like String#sub, except that: * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Related: String#sub, String#gsub, String#gsub!. + * - Changed are made to +self+, not to copy of +self+. + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From cb302881629f997f403e705425f69e5f6b0741ac Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 22 Oct 2025 19:47:20 -0400 Subject: [PATCH 0498/2435] Fix memory leak of transcoding when fallback raises When the fallback function in transcode_loop raises, it will leak the memory in rb_econv_t. The following script reproduces the leak: 10.times do 100_000.times do "\ufffd".encode(Encoding::US_ASCII, fallback: proc { raise }) rescue end puts `ps -o rss= -p #{$$}` end Before: 451196 889980 1328508 1767676 2206460 2645372 3083900 3522428 3960956 4399484 After: 12508 12636 12892 12892 13148 13404 13532 13788 13916 13916 --- test/ruby/test_string.rb | 28 ++++++++++++++++++++++++++++ transcode.c | 30 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 1e0f31ba7c540a..6445832798ec1a 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3747,6 +3747,34 @@ def test_chilled_string_substring Warning[:deprecated] = deprecated end + def test_encode_fallback_raise_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { raise } + RUBY + "proc" => <<~RUBY, + fallback = proc { raise } + RUBY + "method" => <<~RUBY, + def my_method = raise + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[] = raise + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue + end + RUBY + end + end + private def assert_bytesplice_result(expected, s, *args) diff --git a/transcode.c b/transcode.c index 072e1942b1ae14..09ecb1c6513171 100644 --- a/transcode.c +++ b/transcode.c @@ -2349,6 +2349,20 @@ aref_fallback(VALUE fallback, VALUE c) return rb_funcallv_public(fallback, idAREF, 1, &c); } +struct transcode_loop_fallback_args { + VALUE (*fallback_func)(VALUE, VALUE); + VALUE fallback; + VALUE rep; +}; + +static VALUE +transcode_loop_fallback_try(VALUE a) +{ + struct transcode_loop_fallback_args *args = (struct transcode_loop_fallback_args *)a; + + return args->fallback_func(args->fallback, args->rep); +} + static void transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, const unsigned char *in_stop, unsigned char *out_stop, @@ -2398,7 +2412,21 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, (const char *)ec->last_error.error_bytes_start, ec->last_error.error_bytes_len, rb_enc_find(ec->last_error.source_encoding)); - rep = (*fallback_func)(fallback, rep); + + + struct transcode_loop_fallback_args args = { + .fallback_func = fallback_func, + .fallback = fallback, + .rep = rep, + }; + + int state; + rep = rb_protect(transcode_loop_fallback_try, (VALUE)&args, &state); + if (state) { + rb_econv_close(ec); + rb_jump_tag(state); + } + if (!UNDEF_P(rep) && !NIL_P(rep)) { StringValue(rep); ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep), From 8e8e327870b23bcf263e0210472e6ee87d19c424 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 25 Oct 2025 10:31:04 +0200 Subject: [PATCH 0499/2435] [ruby/json] Fix concurrent usage of JSON::Coder#dump Fix: https://github.com/rails/rails/commit/90616277e3d8fc46c9cf35d6a7470ff1ea0092f7#r168784389 Because the `depth` counter is inside `JSON::State` it can't be used concurrently, and in case of a circular reference the counter may be left at the max value. The depth counter should be moved outside `JSON_Generator_State` and into `struct generate_json_data`, but it's a larger refactor. In the meantime, `JSON::Coder` calls `State#generate_new` so I changed that method so that it first copy the state on the stack. https://github.com/ruby/json/commit/aefa671eca --- ext/json/generator/generator.c | 39 +++++++++++++++++++++++++++++----- test/json/json_coder_test.rb | 10 +++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 6a38cc60a7964f..f3f27b29d58ecd 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1124,7 +1124,7 @@ static inline long increase_depth(struct generate_json_data *data) JSON_Generator_State *state = data->state; long depth = ++state->depth; if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) { - rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); + rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth); } return depth; } @@ -1491,10 +1491,39 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 1, 2); VALUE obj = argv[0]; VALUE io = argc > 1 ? argv[1] : Qnil; - VALUE result = cState_partial_generate(self, obj, generate_json, io); + return cState_partial_generate(self, obj, generate_json, io); +} + +static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) +{ + rb_check_arity(argc, 1, 2); + VALUE obj = argv[0]; + VALUE io = argc > 1 ? argv[1] : Qnil; + GET_STATE(self); - (void)state; - return result; + + JSON_Generator_State new_state; + MEMCPY(&new_state, state, JSON_Generator_State, 1); + + // FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently. + new_state.depth = 0; + + char stack_buffer[FBUFFER_STACK_SIZE]; + FBuffer buffer = { + .io = RTEST(io) ? io : Qfalse, + }; + fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); + + struct generate_json_data data = { + .buffer = &buffer, + .vstate = Qfalse, + .state = &new_state, + .obj = obj, + .func = generate_json + }; + rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); + + return fbuffer_finalize(&buffer); } static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) @@ -2072,7 +2101,7 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); - rb_define_alias(cState, "generate_new", "generate"); // :nodoc: + rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc: rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index c7248353769969..fb9d7b30a59a11 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -66,4 +66,14 @@ def test_json_coder_dump_NaN_or_Infinity_loop end assert_include error.message, "NaN not allowed in JSON" end + + def test_nesting_recovery + coder = JSON::Coder.new + ary = [] + ary << ary + assert_raise JSON::NestingError do + coder.dump(ary) + end + assert_equal '{"a":1}', coder.dump({ a: 1 }) + end end From 226caf1a1f3542d9e39e1a72fb43fd4486540cc4 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 25 Oct 2025 10:59:15 +0200 Subject: [PATCH 0500/2435] [ruby/json] Release 2.15.2 https://github.com/ruby/json/commit/5e61cd7dce --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index be5daf4c5b6902..dc01ba290b8cc2 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.15.1' + VERSION = '2.15.2' end From 10f0abeef1992f8708e3d9b2ecad54db8de7c2a3 Mon Sep 17 00:00:00 2001 From: git Date: Sat, 25 Oct 2025 09:01:37 +0000 Subject: [PATCH 0501/2435] Update default gems list at 226caf1a1f3542d9e39e1a72fb43fd [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 4939962a4049c9..f847fe6817821d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,7 +188,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 -* json 2.15.1 +* json 2.15.2 * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 From 31e14ac7dadc99851fefbb5d5d4232ba9f568f1b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Oct 2025 14:37:39 +0900 Subject: [PATCH 0502/2435] [DOC] Follow up GH-14470 `IS_TYPED_DATA` is no longer a flag in `type`, and the "embedded" flag has been shifted accordingly. ruby/ruby#14470 --- include/ruby/internal/core/rtypeddata.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 8e16c31d998080..bf0f60b9132029 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -360,8 +360,7 @@ struct RTypedData { /** * This is a `const rb_data_type_t *const` value, with the low bits set: * - * 1: Always set, to differentiate RTypedData from RData. - * 2: Set if object is embedded. + * 1: Set if object is embedded. * * This field stores various information about how Ruby should handle a * data. This roughly resembles a Ruby level class (apart from method From 377aa2a336cc700485c699ac49330f2a58b74906 Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 9 Sep 2025 21:21:22 +0900 Subject: [PATCH 0503/2435] Improve performance of UnicodeNormalize.canonical_ordering_one Use array_of_integer.sort! instead of buble-sort-like algorithm --- lib/unicode_normalize/normalize.rb | 22 ++++++++++++++-------- test/test_unicode_normalize.rb | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index e67fad187a5fb1..7872f8a0bcffdd 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -82,16 +82,22 @@ def self.hangul_comp_one(string) ## Canonical Ordering def self.canonical_ordering_one(string) - sorting = string.each_char.collect { |c| [c, CLASS_TABLE[c]] } - (sorting.length-2).downto(0) do |i| # almost, but not exactly bubble sort - (0..i).each do |j| - later_class = sorting[j+1].last - if 0 Date: Sat, 25 Oct 2025 21:08:20 +0900 Subject: [PATCH 0504/2435] Add configured environment variables first And `LD_PRELOAD` is set to `PRELOADENV` on Linux. --- test/ruby/test_process.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 3bbff73df8c070..a26712d0e6d2ba 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -283,20 +283,18 @@ def test_overwrite_ENV end MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT] - case RbConfig::CONFIG['target_os'] - when /linux/ - MANDATORY_ENVS << 'LD_PRELOAD' - when /mswin|mingw/ - MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE]) - when /darwin/ - MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/)) - end if e = RbConfig::CONFIG['LIBPATHENV'] MANDATORY_ENVS << e end if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty? MANDATORY_ENVS << e end + case RbConfig::CONFIG['target_os'] + when /mswin|mingw/ + MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE]) + when /darwin/ + MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/)) + end PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"] ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }'] ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG) From 8e3957cc760ddc55cfb55993b70e9f4c1a230ac2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Oct 2025 21:11:23 +0900 Subject: [PATCH 0505/2435] Remove automatically set environment variables Probably since macOS Runner Image Version 20251020.XXXX, spawned processes initialize `TMPDIR` environment variable under the hood. --- test/ruby/test_process.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index a26712d0e6d2ba..857ceab6762fda 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -293,7 +293,10 @@ def test_overwrite_ENV when /mswin|mingw/ MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE]) when /darwin/ - MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/)) + MANDATORY_ENVS.concat(%w[TMPDIR], ENV.keys.grep(/\A__CF_/)) + # IO.popen([ENV.keys.to_h {|e| [e, nil]}, + # RUBY, "-e", %q[print ENV.keys.join(?\0)]], + # &:read).split(?\0) end PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"] ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }'] From 0a924d46156a9c86ba657f2cb4e2fcee9aee81bf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Oct 2025 11:57:25 +0900 Subject: [PATCH 0506/2435] Use pointer to the member Instead of the offset calculation. --- include/ruby/internal/core/rtypeddata.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index bf0f60b9132029..24e87e63f979f9 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -543,11 +543,10 @@ RTYPEDDATA_GET_DATA(VALUE obj) } #endif - /* We reuse the data pointer in embedded TypedData. We can't use offsetof - * since RTypedData a non-POD type in C++. */ - const size_t embedded_typed_data_size = sizeof(struct RTypedData) - sizeof(void *); - - return RTYPEDDATA_EMBEDDED_P(obj) ? (char *)obj + embedded_typed_data_size : RTYPEDDATA(obj)->data; + /* We reuse the data pointer in embedded TypedData. */ + return RTYPEDDATA_EMBEDDED_P(obj) ? + RBIMPL_CAST((void *)&(RTYPEDDATA(obj)->data)) : + RTYPEDDATA(obj)->data; } RBIMPL_ATTR_PURE() From c6d1458421796786d26e084b48a0a4a7e3b40867 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Oct 2025 19:10:55 +0900 Subject: [PATCH 0507/2435] [DOC] Link github style references in ChangeLog --- tool/lib/vcs.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 45b84b303503ad..602f6dd519c5b3 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -592,6 +592,10 @@ def changelog_formatter(path, arg, base_url = nil) s = s.join('') end + s.gsub!(%r[(?!<\w)([-\w]+/[-\w]+)(?:@(\h{8,40})|#(\d{5,}))\b]) do + path = defined?($2) ? "commit/#{$2}" : "pull/#{$3}" + "[#$&](https://github.com/#{$1}/#{path})" + end if %r[^ +(https://github\.com/[^/]+/[^/]+/)commit/\h+\n(?=(?: +\n(?i: +Co-authored-by: .*\n)+)?(?:\n|\Z))] =~ s issue = "#{$1}pull/" s.gsub!(/\b(?:(?i:fix(?:e[sd])?) +|GH-)\K#(?=\d+\b)|\(\K#(?=\d+\))/) {issue} From e3a717512e6d374d5c2355fbeac07d32afffb27b Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 02:04:40 -0500 Subject: [PATCH 0508/2435] [ruby/stringio] [DOC] Tweaks for StringIO#closed? (https://github.com/ruby/stringio/pull/151) https://github.com/ruby/stringio/commit/94bd4a3d87 --- ext/stringio/stringio.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 965355df67d87c..0dbf1fa3ec0918 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -614,8 +614,16 @@ strio_close_write(VALUE self) * call-seq: * closed? -> true or false * - * Returns +true+ if +self+ is closed for both reading and writing, - * +false+ otherwise. + * Returns whether +self+ is closed for both reading and writing: + * + * strio = StringIO.new + * strio.closed? # => false # Open for reading and writing. + * strio.close_read + * strio.closed? # => false # Still open for writing. + * strio.close_write + * strio.closed? # => true # Now closed for both. + * + * Related: StringIO.closed_read?, StringIO.closed_write?. */ static VALUE strio_closed(VALUE self) From efe9a3cda93ec399b96cc1bb8d4a4e9b7ba1d3b1 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 25 Oct 2025 13:31:36 +0900 Subject: [PATCH 0509/2435] Add several very basic tests to be run with RUBY_NAMESPACE=1 on CI To detect breaking namespace features on CI. --- test/ruby/test_namespace.rb | 117 ++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 5661a98ca2b5f1..bfbbe38e051ab2 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -538,51 +538,6 @@ def test_load_path_and_loaded_features assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb')) end - def test_prelude_gems_and_loaded_features - assert_in_out_err([ENV_ENABLE_NAMESPACE, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| - begin; - puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join - puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join - - require "error_highlight" - - puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join - puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join - end; - - # No additional warnings except for experimental warnings - assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS - assert_equal 2, error.size - - assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' - assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' - assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' - assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' - end - end - - def test_prelude_gems_and_loaded_features_with_disable_gems - assert_in_out_err([ENV_ENABLE_NAMESPACE, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| - begin; - puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join - puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join - - require "error_highlight" - - puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join - puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join - end; - - assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS - assert_equal 2, error.size - - refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' - refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' - refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' - assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' - end - end - def test_eval_basic pend unless Namespace.enabled? @@ -664,4 +619,76 @@ def test_eval_error_handling assert_equal 4, result end end + + # Tests which run always (w/o RUBY_NAMESPACE=1 globally) + + def test_prelude_gems_and_loaded_features + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + # No additional warnings except for experimental warnings + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal 2, error.size + + assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_prelude_gems_and_loaded_features_with_disable_gems + assert_in_out_err([ENV_ENABLE_NAMESPACE, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS + assert_equal 2, error.size + + refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_root_and_main_methods + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + pend unless Namespace.respond_to?(:root) and Namespace.respond_to?(:main) # for RUBY_DEBUG > 0 + + assert Namespace.root.respond_to?(:root?) + assert Namespace.main.respond_to?(:main?) + + assert Namespace.root.root? + assert Namespace.main.main? + assert_equal Namespace.main, Namespace.current + + $a = 1 + $LOADED_FEATURES.push("/tmp/foobar") + + assert_equal 2, Namespace.root.eval('$a = 2; $a') + assert !Namespace.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') + assert "FooClass", Namespace.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + + assert_equal 1, $a + assert !$LOADED_FEATURES.include?("/tmp/barbaz") + assert !Object.const_defined?(:FooClass) + end; + end end From be118cf946316766d1b14ff68c2540870f6717ca Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 25 Oct 2025 13:35:15 +0900 Subject: [PATCH 0510/2435] classext replacement never happen on non-iclass classes --- class.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/class.c b/class.c index 4dddf08c67af38..561823006ace3e 100644 --- a/class.c +++ b/class.c @@ -144,11 +144,11 @@ rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, s struct rb_class_set_namespace_classext_args *args = (struct rb_class_set_namespace_classext_args *)a; if (existing) { - if (BUILTIN_TYPE(args->obj) == T_ICLASS) { + if (LIKELY(BUILTIN_TYPE(args->obj) == T_ICLASS)) { rb_iclass_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); } else { - rb_class_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + rb_bug("Updating existing classext for non-iclass never happen"); } } @@ -375,6 +375,8 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n RCLASSEXT_SET_INCLUDER(ext, iclass, RCLASSEXT_INCLUDER(src)); + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); + first_set = RCLASS_SET_NAMESPACE_CLASSEXT(iclass, ns, ext); if (first_set) { RCLASS_SET_PRIME_CLASSEXT_WRITABLE(iclass, false); @@ -454,6 +456,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace if (RBASIC_CLASS(iclass) == klass) { // Is the subclass an ICLASS including this module into another class // If so we need to re-associate it under our namespace with the new ext + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); class_duplicate_iclass_classext(iclass, ext, ns); } } From 1c81bbf03533eb44f3c8f25a872ec85b6563c65f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 25 Oct 2025 13:35:47 +0900 Subject: [PATCH 0511/2435] free the entry after deleting the reference --- class.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/class.c b/class.c index 561823006ace3e..f4e2cff2fd34b5 100644 --- a/class.c +++ b/class.c @@ -613,17 +613,18 @@ remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) next->prev = prev; } - xfree(entry); - if (first_entry) { if (next) { st_update(tbl, ns_id, remove_class_from_subclasses_replace_first_entry, (st_data_t)next); } else { - // no subclass entries in this ns + // no subclass entries in this ns after the deletion st_delete(tbl, &ns_id, NULL); } } + + xfree(entry); + break; } else if (first_entry) { From f53b8194cd5958619dc295e51dbdec29351a6b0a Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 25 Oct 2025 13:36:20 +0900 Subject: [PATCH 0512/2435] Stop deleting the reference from superclass when replacing classext. Calling the usual rb_iclass_classext_free() causes SEGV because duplicating a newer classext of iclass had set the reference from superclass to the newer classext, but calling rb_iclass_classext_free() deletes it. --- class.c | 31 ++++++++++++++++++++++++------- internal/class.h | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/class.c b/class.c index f4e2cff2fd34b5..5e50a281d9a9fe 100644 --- a/class.c +++ b/class.c @@ -102,7 +102,7 @@ rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_id_table_free(tbl); } - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { RUBY_ASSERT(is_prime); // superclasses should only be used on prime @@ -126,13 +126,30 @@ rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); } - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); if (!is_prime) { // the prime classext will be freed with RClass xfree(ext); } } +static void +iclass_free_orphan_classext(VALUE klass, rb_classext_t *ext) +{ + if (RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext)) { + /* Method table is not shared for origin iclasses of classes */ + rb_id_table_free(RCLASSEXT_M_TBL(ext)); + } + + if (RCLASSEXT_CALLABLE_M_TBL(ext) != NULL) { + rb_id_table_free(RCLASSEXT_CALLABLE_M_TBL(ext)); + } + + rb_class_classext_free_subclasses(ext, klass, true); // replacing this classext with a newer one + + xfree(ext); +} + struct rb_class_set_namespace_classext_args { VALUE obj; rb_classext_t *ext; @@ -145,7 +162,7 @@ rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, s if (existing) { if (LIKELY(BUILTIN_TYPE(args->obj) == T_ICLASS)) { - rb_iclass_classext_free(args->obj, (rb_classext_t *)*val_ptr, false); + iclass_free_orphan_classext(args->obj, (rb_classext_t *)*val_ptr); } else { rb_bug("Updating existing classext for non-iclass never happen"); @@ -577,7 +594,7 @@ void rb_class_remove_subclass_head(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_class_classext_free_subclasses(ext, klass); + rb_class_classext_free_subclasses(ext, klass, false); } static struct rb_subclass_entry * @@ -659,7 +676,7 @@ rb_class_remove_from_module_subclasses(VALUE klass) } void -rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass) +rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass, bool replacing) { rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); struct st_table *tbl = anchor->ns_subclasses->tbl; @@ -678,12 +695,12 @@ rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass) rb_ns_subclasses_ref_dec(anchor->ns_subclasses); xfree(anchor); - if (RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { + if (!replacing && RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); rb_ns_subclasses_ref_dec(ns_sub); } - if (RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { + if (!replacing && RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); rb_ns_subclasses_ref_dec(ns_sub); diff --git a/internal/class.h b/internal/class.h index 5d843e58da3923..138620dd6f0469 100644 --- a/internal/class.h +++ b/internal/class.h @@ -490,7 +490,7 @@ void rb_class_classext_foreach(VALUE klass, rb_class_classext_foreach_callback_f void rb_class_subclass_add(VALUE super, VALUE klass); void rb_class_remove_from_super_subclasses(VALUE); void rb_class_remove_from_module_subclasses(VALUE); -void rb_class_classext_free_subclasses(rb_classext_t *, VALUE); +void rb_class_classext_free_subclasses(rb_classext_t *, VALUE, bool); void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE); void rb_class_detach_subclasses(VALUE); void rb_class_detach_module_subclasses(VALUE); From 7690309a0271fea9c8e6ce58afd90cd4c1afb85c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 26 Oct 2025 13:32:00 +0100 Subject: [PATCH 0513/2435] Mention that `require 'pathname'` is necessary for #find, #rmtree and .mktmpdir * See https://bugs.ruby-lang.org/issues/21640#note-16 --- lib/pathname.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/pathname.rb b/lib/pathname.rb index a0da5ed93f1dab..b19e379cd48d9b 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -15,6 +15,8 @@ class Pathname # * Find * # Iterates over the directory tree in a depth first manner, yielding a # Pathname for each file under "this" directory. # + # Note that you need to require 'pathname' to use this method. + # # Returns an Enumerator if no block is given. # # Since it is implemented by the standard library module Find, Find.prune can @@ -40,6 +42,8 @@ def find(ignore_error: true) # :yield: pathname class Pathname # * FileUtils * # Recursively deletes a directory, including all directories beneath it. # + # Note that you need to require 'pathname' to use this method. + # # See FileUtils.rm_rf def rmtree(noop: nil, verbose: nil, secure: nil) # The name "rmtree" is borrowed from File::Path of Perl. @@ -53,6 +57,8 @@ def rmtree(noop: nil, verbose: nil, secure: nil) class Pathname # * tmpdir * # Creates a tmp directory and wraps the returned path in a Pathname object. # + # Note that you need to require 'pathname' to use this method. + # # See Dir.mktmpdir def self.mktmpdir require 'tmpdir' unless defined?(Dir.mktmpdir) From 5c683bd9b35212dc6d4970ca3a842e175ef0c203 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 14:04:16 -0500 Subject: [PATCH 0514/2435] [DOC] Tweaks for String#succ --- doc/string/succ.rdoc | 53 ++++++++++++++++++++++++++++++++++++++++++++ string.c | 52 +------------------------------------------ 2 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 doc/string/succ.rdoc diff --git a/doc/string/succ.rdoc b/doc/string/succ.rdoc new file mode 100644 index 00000000000000..3653112b837e3c --- /dev/null +++ b/doc/string/succ.rdoc @@ -0,0 +1,53 @@ +Returns the successor to +self+. The successor is calculated by +incrementing characters. + +The first character to be incremented is the rightmost alphanumeric: +or, if no alphanumerics, the rightmost character: + + 'THX1138'.succ # => "THX1139" + '<>'.succ # => "<>" + '***'.succ # => '**+' + 'тест'.succ # => "тесу" + 'こんにちは'.succ # => "こんにちば" + +The successor to a digit is another digit, "carrying" to the next-left +character for a "rollover" from 9 to 0, and prepending another digit +if necessary: + + '00'.succ # => "01" + '09'.succ # => "10" + '99'.succ # => "100" + +The successor to a letter is another letter of the same case, +carrying to the next-left character for a rollover, +and prepending another same-case letter if necessary: + + 'aa'.succ # => "ab" + 'az'.succ # => "ba" + 'zz'.succ # => "aaa" + 'AA'.succ # => "AB" + 'AZ'.succ # => "BA" + 'ZZ'.succ # => "AAA" + +The successor to a non-alphanumeric character is the next character +in the underlying character set's collating sequence, +carrying to the next-left character for a rollover, +and prepending another character if necessary: + + s = 0.chr * 3 # => "\x00\x00\x00" + s.succ # => "\x00\x00\x01" + s = 255.chr * 3 # => "\xFF\xFF\xFF" + s.succ # => "\x01\x00\x00\x00" + +Carrying can occur between and among mixtures of alphanumeric characters: + + s = 'zz99zz99' # => "zz99zz99" + s.succ # => "aaa00aa00" + s = '99zz99zz' # => "99zz99zz" + s.succ # => "100aa00aa" + +The successor to an empty +String+ is a new empty +String+: + + ''.succ # => "" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 89445df2445d9e..58fb70a8e5bb60 100644 --- a/string.c +++ b/string.c @@ -5315,57 +5315,7 @@ static VALUE str_succ(VALUE str); * call-seq: * succ -> new_str * - * Returns the successor to +self+. The successor is calculated by - * incrementing characters. - * - * The first character to be incremented is the rightmost alphanumeric: - * or, if no alphanumerics, the rightmost character: - * - * 'THX1138'.succ # => "THX1139" - * '<>'.succ # => "<>" - * '***'.succ # => '**+' - * - * The successor to a digit is another digit, "carrying" to the next-left - * character for a "rollover" from 9 to 0, and prepending another digit - * if necessary: - * - * '00'.succ # => "01" - * '09'.succ # => "10" - * '99'.succ # => "100" - * - * The successor to a letter is another letter of the same case, - * carrying to the next-left character for a rollover, - * and prepending another same-case letter if necessary: - * - * 'aa'.succ # => "ab" - * 'az'.succ # => "ba" - * 'zz'.succ # => "aaa" - * 'AA'.succ # => "AB" - * 'AZ'.succ # => "BA" - * 'ZZ'.succ # => "AAA" - * - * The successor to a non-alphanumeric character is the next character - * in the underlying character set's collating sequence, - * carrying to the next-left character for a rollover, - * and prepending another character if necessary: - * - * s = 0.chr * 3 - * s # => "\x00\x00\x00" - * s.succ # => "\x00\x00\x01" - * s = 255.chr * 3 - * s # => "\xFF\xFF\xFF" - * s.succ # => "\x01\x00\x00\x00" - * - * Carrying can occur between and among mixtures of alphanumeric characters: - * - * s = 'zz99zz99' - * s.succ # => "aaa00aa00" - * s = '99zz99zz' - * s.succ # => "100aa00aa" - * - * The successor to an empty +String+ is a new empty +String+: - * - * ''.succ # => "" + * :include: doc/string/succ.rdoc * */ From 9e49ee7937836875cc362fa4c948803e5da665c7 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 24 Oct 2025 23:24:17 +0100 Subject: [PATCH 0515/2435] [DOC] Tweaks for String#succ! --- string.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/string.c b/string.c index 58fb70a8e5bb60..b4585fd3205913 100644 --- a/string.c +++ b/string.c @@ -5420,7 +5420,9 @@ str_succ(VALUE str) * call-seq: * succ! -> self * - * Equivalent to String#succ, but modifies +self+ in place; returns +self+. + * Like String#succ, but modifies +self+ in place; returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From b66fbd59ae5eb4ef994e0a1c007caaf8fbd3c897 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 26 Oct 2025 10:36:24 -0400 Subject: [PATCH 0516/2435] Make rb_vm_ccs_invalidate_and_free static --- vm_method.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm_method.c b/vm_method.c index 2cd41bd3774046..f5ad38e07c10fe 100644 --- a/vm_method.c +++ b/vm_method.c @@ -184,7 +184,7 @@ vm_ccs_invalidate(struct rb_class_cc_entries *ccs) } } -void +static void rb_vm_ccs_invalidate_and_free(struct rb_class_cc_entries *ccs) { RB_DEBUG_COUNTER_INC(ccs_free); From 52ea222027c7315a5d66f0d7b4ab73c1cc0c7344 Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Sat, 25 Oct 2025 10:10:06 +0530 Subject: [PATCH 0517/2435] Fix segfault when moving nested objects between ractors during GC Fixes a segmentation fault when moving nested objects between ractors with GC stress enabled and YJIT. The issue is a timing problem: `move_enter` allocates new object shells but leaves their contents uninitialized until `move_leave` copies the actual data. If GC runs between these steps (which GC stress makes likely), it tries to follow what appear to be object pointers but are actually uninitialized memory, encountering null or invalid addresses. The fix zero-initializes the object contents immediately after allocation in `move_enter`, ensuring the GC finds safe null pointers instead of garbage data. The crash reproduced most consistently with nested hashes and YJIT, likely because nested structures create multiple uninitialized objects simultaneously while YJIT's memory usage increases the probability of GC triggering during moves. --- ractor.c | 4 +++- test/ruby/test_ractor.rb | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ractor.c b/ractor.c index 68ef0a87ac4cc7..ed0b023b1feec1 100644 --- a/ractor.c +++ b/ractor.c @@ -1940,8 +1940,10 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) } else { VALUE type = RB_BUILTIN_TYPE(obj); + size_t slot_size = rb_gc_obj_slot_size(obj); type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0; - NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0); + NEWOBJ_OF(moved, struct RBasic, 0, type, slot_size, 0); + MEMZERO(&moved[1], char, slot_size - sizeof(*moved)); data->replacement = (VALUE)moved; return traverse_cont; } diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index c4154cd2632b27..ccc551acef6192 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -99,6 +99,19 @@ def initialize(*) RUBY end + def test_move_nested_hash_during_gc_with_yjit + original_gc_stress = GC.stress + assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }]) + GC.stress = true + hash = { foo: { bar: "hello" }, baz: { qux: "there" } } + result = Ractor.new { Ractor.receive }.send(hash, move: true).value + assert_equal "hello", result[:foo][:bar] + assert_equal "there", result[:baz][:qux] + RUBY + ensure + GC.stress = original_gc_stress + end + def test_fork_raise_isolation_error assert_ractor(<<~'RUBY') ractor = Ractor.new do From 947e5baa8c24cdf2f2d146005e9c91a5733f80f6 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 20:01:08 -0500 Subject: [PATCH 0518/2435] [ruby/stringio] [DOC] Tweaks for StringIO.closed_write? (https://github.com/ruby/stringio/pull/153) https://github.com/ruby/stringio/commit/3e9d576441 Co-authored-by: Sutou Kouhei --- ext/stringio/stringio.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 0dbf1fa3ec0918..b24ab3f5817187 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -651,7 +651,14 @@ strio_closed_read(VALUE self) * call-seq: * closed_write? -> true or false * - * Returns +true+ if +self+ is closed for writing, +false+ otherwise. + * Returns whether +self+ is closed for writing: + * + * strio = StringIO.new + * strio.closed_write? # => false + * strio.close_write + * strio.closed_write? # => true + * + * Related: StringIO#close_write, StringIO#closed?, StringIO#closed_read?. */ static VALUE strio_closed_write(VALUE self) From daaa09bcc2c803ed52f744207d6f951d5b363226 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 20:01:33 -0500 Subject: [PATCH 0519/2435] [ruby/stringio] [DOC] Tweaks for StringIO.closed_read? (https://github.com/ruby/stringio/pull/152) https://github.com/ruby/stringio/commit/7ded426718 --- ext/stringio/stringio.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index b24ab3f5817187..d593508e46b605 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -637,7 +637,14 @@ strio_closed(VALUE self) * call-seq: * closed_read? -> true or false * - * Returns +true+ if +self+ is closed for reading, +false+ otherwise. + * Returns whether +self+ is closed for reading: + * + * strio = StringIO.new + * strio.closed_read? # => false + * strio.close_read + * strio.closed_read? # => true + * + * Related: StringIO#closed?, StringIO#closed_write?, StringIO#close_read. */ static VALUE strio_closed_read(VALUE self) From fcf8b10b3c674eecf16c14fa6ee7f4211fa3b673 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 20:02:02 -0500 Subject: [PATCH 0520/2435] [ruby/stringio] [DOC] Tweaks for StringIO#close_read (https://github.com/ruby/stringio/pull/149) https://github.com/ruby/stringio/commit/11995db341 --- ext/stringio/stringio.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d593508e46b605..3bb2247fb038a2 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -572,9 +572,16 @@ strio_close(VALUE self) * call-seq: * close_read -> nil * - * Closes +self+ for reading; closed-write setting remains unchanged. + * Closes +self+ for reading; + * closed-write setting remains unchanged; + * returns +nil+: * - * Raises IOError if reading is attempted. + * strio = StringIO.new + * strio.closed_read? # => false + * strio.close_read # => nil + * strio.closed_read? # => true + * strio.closed_write? # => false + * strio.read # Raises IOError: not opened for reading * * Related: StringIO#close, StringIO#close_write. */ From 4bd9cbd3eba28c855efa0e669ea5be302224e338 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 10:14:33 +0900 Subject: [PATCH 0521/2435] Omit unknown DNS issue of macOS 26.1 beta ``` TestResolvDNS#test_no_server: Test::Unit::ProxyError: Timeout::Error /path/to/ruby/test/resolv/test_dns.rb:531:in 'TestResolvDNS#test_no_server' ``` --- test/resolv/test_dns.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 87b3bf9f37a927..2bb8061edbd1ac 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -525,6 +525,8 @@ def test_no_server if RUBY_PLATFORM.match?(/mingw/) # cannot repo locally omit 'Timeout Error on MinGW CI' + elsif macos?(26,1) + omit 'Timeout Error on macOS 26.1+' else raise Timeout::Error end From 342dfd780bbc0bf1308fe37a80fa482339811927 Mon Sep 17 00:00:00 2001 From: fukunori03 <43785209+fukunori03@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:57:28 +0000 Subject: [PATCH 0522/2435] [ruby/resolv] win32/resolv: fix loading of `NV Domain` https://github.com/ruby/resolv/commit/da14f1f2fc --- ext/win32/lib/win32/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index 67762da375af39..226de7cba36769 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -82,7 +82,7 @@ def get_info nvdom = get_item_property(TCPIP_NT, 'NV Domain') unless nvdom.empty? - @search = [ nvdom ] + search = [ nvdom ] udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution').to_i if udmnd != 0 if /^\w+\./ =~ nvdom From a1a3857ccd71d9e97f7d6c3c9999969d0ab56b6b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 23 Oct 2025 09:31:45 +0900 Subject: [PATCH 0523/2435] [ruby/win32-registry] Use pathspecs to select needed files in the gem file Exclude other git management files too. https://github.com/ruby/win32-registry/commit/3be0188111 --- ext/win32/win32-registry.gemspec | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec index d747daf458ddf1..a690dd5525ed14 100644 --- a/ext/win32/win32-registry.gemspec +++ b/ext/win32/win32-registry.gemspec @@ -17,13 +17,10 @@ Gem::Specification.new do |spec| # The `git ls-files -z` loads the files in the RubyGem that have been added into git. excludes = %w[ bin/ test/ spec/ features/ rakelib/ - .git .github .mailmap appveyor Rakefile Gemfile + .git* .mailmap appveyor Rakefile Gemfile ] - spec.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| - File.identical?(f, __FILE__) || f.start_with?(*excludes) - end - end + git_files = %w[git ls-files -z --] + excludes.map {|x| ":^/#{x}"} + spec.files = IO.popen(git_files, chdir: __dir__, &:read).split("\x0") spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] From 2dd01c8f5c58faf084061ff687fb5f3d6a2cd264 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 23:22:53 -0500 Subject: [PATCH 0524/2435] [ruby/stringio] [DOC] Tweaks for StringIO#close_write (https://github.com/ruby/stringio/pull/150) https://github.com/ruby/stringio/commit/ea6e36f797 Co-authored-by: Sutou Kouhei --- ext/stringio/stringio.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 3bb2247fb038a2..50aea7642ece92 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -600,11 +600,16 @@ strio_close_read(VALUE self) * call-seq: * close_write -> nil * - * Closes +self+ for writing; closed-read setting remains unchanged. + * Closes +self+ for writing; closed-read setting remains unchanged; returns +nil+: * - * Raises IOError if writing is attempted. + * strio = StringIO.new + * strio.closed_write? # => false + * strio.close_write # => nil + * strio.closed_write? # => true + * strio.closed_read? # => false + * strio.write('foo') # Raises IOError: not opened for writing * - * Related: StringIO#close, StringIO#close_read. + * Related: StringIO#close, StringIO#close_read, StringIO#closed_write?. */ static VALUE strio_close_write(VALUE self) From e221a4b73e2adb5d5d9c383e8f6be7a455e036f0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 23:24:00 -0500 Subject: [PATCH 0525/2435] [ruby/stringio] [DOC] Doc for StringIO#each (https://github.com/ruby/stringio/pull/154) https://github.com/ruby/stringio/commit/eca2588274 --- ext/stringio/stringio.c | 193 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 6 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 50aea7642ece92..3c842265af2630 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1462,15 +1462,177 @@ strio_readline(int argc, VALUE *argv, VALUE self) } /* + * :markup: markdown + * * call-seq: * each_line(sep = $/, chomp: false) {|line| ... } -> self * each_line(limit, chomp: false) {|line| ... } -> self * each_line(sep, limit, chomp: false) {|line| ... } -> self * - * Calls the block with each remaining line read from the stream; - * does nothing if already at end-of-file; - * returns +self+. - * See {Line IO}[rdoc-ref:IO@Line+IO]. + * With a block given calls the block with each remaining line (see "Position" below) in the stream; + * returns `self`. + * + * Leaves stream position as end-of-stream. + * + * **No Arguments** + * + * With no arguments given, + * reads lines using the default record separator global variable `$/`, whose initial value is `"\n"`. + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line {|line| p line } + * strio.eof? # => true + * ``` + * + * Output: + * + * ``` + * "First line\n" + * "Second line\n" + * "\n" + * "Fourth line\n" + * "Fifth line\n" + * ``` + * + * **Argument `sep`** + * + * With only string argument `sep` given, + * reads lines using that string as the record separator: + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line(' ') {|line| p line } + * ``` + * + * Output: + * + * ``` + * "First " + * "line\nSecond " + * "line\n\nFourth " + * "line\nFifth " + * "line\n" + * ``` + * + * **Argument `limit`** + * + * With only integer argument `limit` given, + * reads lines using the default record separator global variable `$/`, whose initial value is `"\n"`; + * also limits the size (in characters) of each line to the given limit: + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line(10) {|line| p line } + * ``` + * + * Output: + * + * ``` + * "First line" + * "\n" + * "Second lin" + * "e\n" + * "\n" + * "Fourth lin" + * "e\n" + * "Fifth line" + * "\n" + * ``` + * **Arguments `sep` and `limit`** + * + * With arguments `sep` and `limit` both given, + * honors both: + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line(' ', 10) {|line| p line } + * ``` + * + * Output: + * + * ``` + * "First " + * "line\nSecon" + * "d " + * "line\n\nFour" + * "th " + * "line\nFifth" + * " " + * "line\n" + * ``` + * + * **Position** + * + * As stated above, method `each` _remaining_ line in the stream. + * + * In the examples above each `strio` object starts with its position at beginning-of-stream; + * but in other cases the position may be anywhere (see StringIO#pos): + * + * ``` + * strio = StringIO.new(TEXT) + * strio.pos = 30 # Set stream position to character 30. + * strio.each_line {|line| p line } + * ``` + * + * Output: + * + * ``` + * " line\n" + * "Fifth line\n" + * ``` + * + * **Special Record Separators** + * + * Like some methds in class `IO`, StringIO.each honors two special record separators; + * see {Special Line Separators}[rdoc-ref:IO@Special+Line+Separator+Values]. + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line('') {|line| p line } # Read as paragraphs (separated by blank lines). + * ``` + * + * Output: + * + * ``` + * "First line\nSecond line\n\n" + * "Fourth line\nFifth line\n" + * ``` + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line(nil) {|line| p line } # "Slurp"; read it all. + * ``` + * + * Output: + * + * ``` + * "First line\nSecond line\n\nFourth line\nFifth line\n" + * ``` + * + * **Keyword Argument `chomp`** + * + * With keyword argument `chomp` given as `true` (the default is `false`), + * removes trailing newline (if any) from each line: + * + * ``` + * strio = StringIO.new(TEXT) + * strio.each_line(chomp: true) {|line| p line } + * ``` + * + * Output: + * + * ``` + * "First line" + * "Second line" + * "" + * "Fourth line" + * "Fifth line" + * ``` + * + * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * + * Related: StringIO.each_byte, StringIO.each_char, StringIO.each_codepoint. */ static VALUE strio_each(int argc, VALUE *argv, VALUE self) @@ -1969,15 +2131,34 @@ strio_set_encoding_by_bom(VALUE self) } /* + * :markup: markdown + * * \IO streams for strings, with access similar to * {IO}[rdoc-ref:IO]; * see {IO}[rdoc-ref:IO]. * - * === About the Examples + * ### About the Examples * * Examples on this page assume that \StringIO has been required: * - * require 'stringio' + * ``` + * require 'stringio' + * ``` + * + * And that these constants have been defined: + * + * ``` + * TEXT = < Date: Tue, 21 Oct 2025 10:23:33 +0100 Subject: [PATCH 0526/2435] [ruby/openssl] Update link to OpenSSL configuration file docs https://github.com/ruby/openssl/commit/02ff964114 --- ext/openssl/ossl_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c index ee2ff6786fbb28..9be5fe6f6320dc 100644 --- a/ext/openssl/ossl_config.c +++ b/ext/openssl/ossl_config.c @@ -426,7 +426,7 @@ Init_ossl_config(void) * configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for * the location of the file for your host. * - * See also http://www.openssl.org/docs/apps/config.html + * See also https://docs.openssl.org/master/man5/config/ */ cConfig = rb_define_class_under(mOSSL, "Config", rb_cObject); From db59619ce5f7edd2ea0fdf60f29cfed466628987 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 10:41:05 +0900 Subject: [PATCH 0527/2435] [ruby/rubygems] Fixup https://github.com/ruby/rubygems/commit/9b3a5a8ae9cd https://github.com/ruby/rubygems/commit/8f6eb4ac64 --- spec/bundler/other/major_deprecation_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 947800be22bad1..dc96fb852a8be4 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -598,7 +598,7 @@ L end - it "raises a helpful error" do + it "warns a helpful error" do bundle "install", raise_on_error: false expect(err).to include("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") From b839deec4914b096988d44016c37737bc7d1254f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 21 Oct 2025 14:10:40 -0700 Subject: [PATCH 0528/2435] [ruby/openssl] Check NULL values for deprecated EVP_PKEY_get0() functions In OpenSSL <= 1.1.1, EVP_PKEY_get0() always returned a valid object, so a NULL check was not necessary. In OpenSSL 3.0, the function can return NULL (https://docs.openssl.org/3.0/man7/migration_guide/#deprecated-function-mappings), so guard against this issue. https://github.com/ruby/openssl/commit/dc90b9c51e --- ext/openssl/ossl_pkey_dh.c | 2 ++ ext/openssl/ossl_pkey_dsa.c | 2 ++ ext/openssl/ossl_pkey_ec.c | 2 ++ ext/openssl/ossl_pkey_rsa.c | 2 ++ 4 files changed, 8 insertions(+) diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 77082d5c348acb..561007fec8b058 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -21,6 +21,8 @@ EVP_PKEY *_pkey; \ GetPKeyDH((obj), _pkey); \ (dh) = EVP_PKEY_get0_DH(_pkey); \ + if ((dh) == NULL) \ + ossl_raise(eDHError, "failed to get DH from EVP_PKEY"); \ } while (0) /* diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index bf92e1ceac6412..cb38786b560c0a 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -21,6 +21,8 @@ EVP_PKEY *_pkey; \ GetPKeyDSA((obj), _pkey); \ (dsa) = EVP_PKEY_get0_DSA(_pkey); \ + if ((dsa) == NULL) \ + ossl_raise(eDSAError, "failed to get DSA from EVP_PKEY"); \ } while (0) static inline int diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index e3553c44188012..8c97297a56193e 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -22,6 +22,8 @@ static const rb_data_type_t ossl_ec_point_type; EVP_PKEY *_pkey; \ GetPKeyEC(obj, _pkey); \ (key) = EVP_PKEY_get0_EC_KEY(_pkey); \ + if ((key) == NULL) \ + ossl_raise(eECError, "failed to get EC_KEY from EVP_PKEY"); \ } while (0) #define GetECGroup(obj, group) do { \ diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 4f7862023a61c7..b2983d3b53cfc2 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -21,6 +21,8 @@ EVP_PKEY *_pkey; \ GetPKeyRSA((obj), _pkey); \ (rsa) = EVP_PKEY_get0_RSA(_pkey); \ + if ((rsa) == NULL) \ + ossl_raise(eRSAError, "failed to get RSA from EVP_PKEY"); \ } while (0) static inline int From ecf5aa18cab6b7f770acea25bee4f93dc6e41e07 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 27 Oct 2025 15:16:20 +0900 Subject: [PATCH 0529/2435] [ruby/openssl] Sync History.md --- ext/openssl/History.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ext/openssl/History.md b/ext/openssl/History.md index ecc53fad3beb7c..32a2c0b2fb6ea7 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,9 @@ +Version 3.3.2 +============= + +Merged changes in 3.1.3 and 3.2.3. + + Version 3.3.1 ============= @@ -80,6 +86,12 @@ And various non-user-visible changes and bug fixes. Please see the commit history for more details. +Version 3.2.3 +============= + +Merged changes in 3.1.3. + + Version 3.2.2 ============= @@ -132,6 +144,16 @@ Notable changes [[GitHub #141]](https://github.com/ruby/openssl/pull/141) +Version 3.1.3 +============= + +Bug fixes +--------- + +* Fix missing NULL check for `EVP_PKEY_get0()` functions with OpenSSL 3.x. + [[GitHub #957]](https://github.com/ruby/openssl/pull/957) + + Version 3.1.2 ============= From e5ac2b8956ecf6b6843f5078219542561cd7ecc2 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 14 Oct 2025 18:19:52 +0900 Subject: [PATCH 0530/2435] [ruby/openssl] cipher: various docs improvements The EVP_CIPHER_CTX = OpenSSL::Cipher interface for AEAD ciphers is notoriously complicated and full of pitfalls. I tried to clarify docs so that users can hopefully connect the Ruby methods with the corresponding OpenSSL man pages more easily. - Call out the common mistakes with Cipher#iv= and Cipher#auth_tag= with AES-GCM. - Update outdated notes about the method calling order requirements with AEAD ciphers. - Add references to the man page where the behavior varies according to the specific cipher algorithm and we cannot document every detail. - Various style/wording updates. https://github.com/ruby/openssl/commit/30e22d9bbb --- ext/openssl/ossl_cipher.c | 203 +++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 80 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index bec634ae11a1f0..f449c63b695b8b 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -219,9 +219,8 @@ ossl_cipher_init(VALUE self, int enc) * * Initializes the Cipher for encryption. * - * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the - * following methods: - * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen] + * Make sure to call either #encrypt or #decrypt before using the Cipher for + * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1). */ @@ -237,9 +236,8 @@ ossl_cipher_encrypt(VALUE self) * * Initializes the Cipher for decryption. * - * Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the - * following methods: - * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen] + * Make sure to call either #encrypt or #decrypt before using the Cipher for + * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0). */ @@ -255,19 +253,15 @@ ossl_cipher_decrypt(VALUE self) * * Generates and sets the key/IV based on a password. * - * *WARNING*: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40, - * or DES with MD5 or SHA1. Using anything else (like AES) will generate the - * key/iv using an OpenSSL specific method. This method is deprecated and - * should no longer be used. Use a PKCS5 v2 key generation method from - * OpenSSL::PKCS5 instead. + * *WARNING*: This method is deprecated and should not be used. This method + * corresponds to EVP_BytesToKey(), a non-standard OpenSSL extension of the + * legacy PKCS #5 v1.5 key derivation function. See OpenSSL::KDF for other + * options to derive keys from passwords. * * === Parameters * * _salt_ must be an 8 byte string if provided. * * _iterations_ is an integer with a default of 2048. * * _digest_ is a Digest object that defaults to 'MD5' - * - * A minimum of 1000 iterations is recommended. - * */ static VALUE ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) @@ -339,6 +333,9 @@ ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_p * * If _buffer_ is given, the encryption/decryption result will be written to * it. _buffer_ will be resized automatically. + * + * *NOTE*: When decrypting using an AEAD cipher, the integrity of the output + * is not verified until #final has been called. */ static VALUE ossl_cipher_update(int argc, VALUE *argv, VALUE self) @@ -398,14 +395,17 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) * cipher.final -> string * * Returns the remaining data held in the cipher object. Further calls to - * Cipher#update or Cipher#final will return garbage. This call should always + * Cipher#update or Cipher#final are invalid. This call should always * be made as the last call of an encryption or decryption operation, after * having fed the entire plaintext or ciphertext to the Cipher instance. * - * If an authenticated cipher was used, a CipherError is raised if the tag - * could not be authenticated successfully. Only call this method after - * setting the authentication tag and passing the entire contents of the - * ciphertext into the cipher. + * When encrypting using an AEAD cipher, the authentication tag can be + * retrieved by #auth_tag after #final has been called. + * + * When decrypting using an AEAD cipher, this method will verify the integrity + * of the ciphertext and the associated data with the authentication tag, + * which must be set by #auth_tag= prior to calling this method. + * If the verification fails, CipherError will be raised. */ static VALUE ossl_cipher_final(VALUE self) @@ -452,7 +452,7 @@ ossl_cipher_name(VALUE self) /* * call-seq: - * cipher.key = string -> string + * cipher.key = string * * Sets the cipher key. To generate a key, you should either use a secure * random byte string or, if the key is to be derived from a password, you @@ -460,6 +460,8 @@ ossl_cipher_name(VALUE self) * generate a secure random-based key, Cipher#random_key may be used. * * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * + * See also the man page EVP_CipherInit_ex(3). */ static VALUE ossl_cipher_set_key(VALUE self, VALUE key) @@ -484,15 +486,21 @@ ossl_cipher_set_key(VALUE self, VALUE key) /* * call-seq: - * cipher.iv = string -> string + * cipher.iv = string * * Sets the cipher IV. Please note that since you should never be using ECB * mode, an IV is always explicitly required and should be set prior to - * encryption. The IV itself can be safely transmitted in public, but it - * should be unpredictable to prevent certain kinds of attacks. You may use - * Cipher#random_iv to create a secure random IV. + * encryption. The IV itself can be safely transmitted in public. * - * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * This method expects the String to have the length equal to #iv_len. To use + * a different IV length with an AEAD cipher, #iv_len= must be set prior to + * calling this method. + * + * *NOTE*: In OpenSSL API conventions, the IV value may correspond to the + * "nonce" instead in some cipher modes. Refer to the OpenSSL man pages for + * details. + * + * See also the man page EVP_CipherInit_ex(3). */ static VALUE ossl_cipher_set_iv(VALUE self, VALUE iv) @@ -520,8 +528,7 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) * call-seq: * cipher.authenticated? -> true | false * - * Indicated whether this Cipher instance uses an Authenticated Encryption - * mode. + * Indicates whether this Cipher instance uses an AEAD mode. */ static VALUE ossl_cipher_is_authenticated(VALUE self) @@ -535,21 +542,23 @@ ossl_cipher_is_authenticated(VALUE self) /* * call-seq: - * cipher.auth_data = string -> string + * cipher.auth_data = string + * + * Sets additional authenticated data (AAD), also called associated data, for + * this Cipher. This method is available for AEAD ciphers. * - * Sets the cipher's additional authenticated data. This field must be - * set when using AEAD cipher modes such as GCM or CCM. If no associated - * data shall be used, this method must *still* be called with a value of "". * The contents of this field should be non-sensitive data which will be * added to the ciphertext to generate the authentication tag which validates * the contents of the ciphertext. * - * The AAD must be set prior to encryption or decryption. In encryption mode, - * it must be set after calling Cipher#encrypt and setting Cipher#key= and - * Cipher#iv=. When decrypting, the authenticated data must be set after key, - * iv and especially *after* the authentication tag has been set. I.e. set it - * only after calling Cipher#decrypt, Cipher#key=, Cipher#iv= and - * Cipher#auth_tag= first. + * This method must be called after #key= and #iv= have been set, but before + * starting actual encryption or decryption with #update. In some cipher modes, + * #auth_tag_len= and #ccm_data_len= may also need to be called before this + * method. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CipherUpdate() with the output buffer + * set to NULL. */ static VALUE ossl_cipher_set_auth_data(VALUE self, VALUE data) @@ -577,16 +586,17 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data) * call-seq: * cipher.auth_tag(tag_len = 16) -> String * - * Gets the authentication tag generated by Authenticated Encryption Cipher - * modes (GCM for example). This tag may be stored along with the ciphertext, - * then set on the decryption cipher to authenticate the contents of the - * ciphertext against changes. If the optional integer parameter _tag_len_ is - * given, the returned tag will be _tag_len_ bytes long. If the parameter is - * omitted, the default length of 16 bytes or the length previously set by - * #auth_tag_len= will be used. For maximum security, the longest possible - * should be chosen. + * Gets the generated authentication tag. This method is available for AEAD + * ciphers, and should be called after encryption has been finalized by calling + * #final. + * + * The returned tag will be _tag_len_ bytes long. Some cipher modes require + * the desired length in advance using a separate call to #auth_tag_len=, + * before starting encryption. * - * The tag may only be retrieved after calling Cipher#final. + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_GET_TAG. */ static VALUE ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) @@ -615,16 +625,24 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) /* * call-seq: - * cipher.auth_tag = string -> string + * cipher.auth_tag = string * * Sets the authentication tag to verify the integrity of the ciphertext. - * This can be called only when the cipher supports AE. The tag must be set - * after calling Cipher#decrypt, Cipher#key= and Cipher#iv=, but before - * calling Cipher#final. After all decryption is performed, the tag is - * verified automatically in the call to Cipher#final. * - * For OCB mode, the tag length must be supplied with #auth_tag_len= - * beforehand. + * The authentication tag must be set before #final is called. The tag is + * verified during the #final call. + * + * Note that, for CCM mode and OCB mode, the expected length of the tag must + * be set before starting decryption by a separate call to #auth_tag_len=. + * The content of the tag can be provided at any time before #final is called. + * + * *NOTE*: The caller must ensure that the String passed to this method has + * the desired length. Some cipher modes support variable tag lengths, and + * this method may accept a truncated tag without raising an exception. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_TAG. */ static VALUE ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) @@ -649,14 +667,17 @@ ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) /* * call-seq: - * cipher.auth_tag_len = Integer -> Integer + * cipher.auth_tag_len = integer * - * Sets the length of the authentication tag to be generated or to be given for - * AEAD ciphers that requires it as in input parameter. Note that not all AEAD - * ciphers support this method. + * Sets the length of the expected authentication tag for this Cipher. This + * method is available for some of AEAD ciphers that require the length to be + * set before starting encryption or decryption, such as CCM mode or OCB mode. * - * In OCB mode, the length must be supplied both when encrypting and when - * decrypting, and must be before specifying an IV. + * For CCM mode and OCB mode, the tag length must be set before #iv= is set. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_TAG and a NULL buffer. */ static VALUE ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) @@ -679,11 +700,16 @@ ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) /* * call-seq: - * cipher.iv_len = integer -> integer + * cipher.iv_len = integer + * + * Sets the IV/nonce length for this Cipher. This method is available for AEAD + * ciphers that support variable IV lengths. This method can be called if a + * different IV length than OpenSSL's default is desired, prior to calling + * #iv=. * - * Sets the IV/nonce length of the Cipher. Normally block ciphers don't allow - * changing the IV length, but some make use of IV for 'nonce'. You may need - * this for interoperability with other applications. + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). + * This method internally calls EVP_CIPHER_CTX_ctrl() with + * EVP_CTRL_AEAD_SET_IVLEN. */ static VALUE ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) @@ -709,13 +735,14 @@ ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) /* * call-seq: - * cipher.key_len = integer -> integer + * cipher.key_len = integer * * Sets the key length of the cipher. If the cipher is a fixed length cipher * then attempting to set the key length to any value other than the fixed * value is an error. * - * Under normal circumstances you do not need to call this method (and probably shouldn't). + * Under normal circumstances you do not need to call this method (and + * probably shouldn't). * * See EVP_CIPHER_CTX_set_key_length for further information. */ @@ -732,13 +759,16 @@ ossl_cipher_set_key_length(VALUE self, VALUE key_length) return key_length; } +// TODO: Should #padding= take a boolean value instead? /* * call-seq: - * cipher.padding = integer -> integer + * cipher.padding = 1 or 0 * - * Enables or disables padding. By default encryption operations are padded using standard block padding and the - * padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the - * total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur. + * Enables or disables padding. By default encryption operations are padded + * using standard block padding and the padding is checked and removed when + * decrypting. If the pad parameter is zero then no padding is performed, the + * total amount of data encrypted or decrypted must then be a multiple of the + * block size or an error will occur. * * See EVP_CIPHER_CTX_set_padding for further information. */ @@ -809,13 +839,17 @@ ossl_cipher_block_size(VALUE self) /* * call-seq: - * cipher.ccm_data_len = integer -> integer + * cipher.ccm_data_len = integer * - * Sets the length of the plaintext / ciphertext message that will be - * processed in CCM mode. Make sure to call this method after #key= and - * #iv= have been set, and before #auth_data=. + * Sets the total length of the plaintext / ciphertext message that will be + * processed by #update in CCM mode. * - * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + * Make sure to call this method after #key= and #iv= have been set, and + * before #auth_data= or #update are called. + * + * This method is only available for CCM mode ciphers. + * + * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). */ static VALUE ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) @@ -998,24 +1032,28 @@ Init_ossl_cipher(void) * could otherwise be exploited to modify ciphertexts in ways beneficial to * potential attackers. * - * An associated data is used where there is additional information, such as + * Associated data, also called additional authenticated data (AAD), is + * optionally used where there is additional information, such as * headers or some metadata, that must be also authenticated but not - * necessarily need to be encrypted. If no associated data is needed for - * encryption and later decryption, the OpenSSL library still requires a - * value to be set - "" may be used in case none is available. + * necessarily need to be encrypted. * * An example using the GCM (Galois/Counter Mode). You have 16 bytes _key_, * 12 bytes (96 bits) _nonce_ and the associated data _auth_data_. Be sure * not to reuse the _key_ and _nonce_ pair. Reusing an nonce ruins the * security guarantees of GCM mode. * + * key = OpenSSL::Random.random_bytes(16) + * nonce = OpenSSL::Random.random_bytes(12) + * auth_data = "authenticated but unencrypted data" + * data = "encrypted data" + * * cipher = OpenSSL::Cipher.new('aes-128-gcm').encrypt * cipher.key = key * cipher.iv = nonce * cipher.auth_data = auth_data * * encrypted = cipher.update(data) + cipher.final - * tag = cipher.auth_tag # produces 16 bytes tag by default + * tag = cipher.auth_tag(16) * * Now you are the receiver. You know the _key_ and have received _nonce_, * _auth_data_, _encrypted_ and _tag_ through an untrusted network. Note @@ -1028,12 +1066,17 @@ Init_ossl_cipher(void) * decipher = OpenSSL::Cipher.new('aes-128-gcm').decrypt * decipher.key = key * decipher.iv = nonce - * decipher.auth_tag = tag + * decipher.auth_tag = tag # could be called at any time before #final * decipher.auth_data = auth_data * * decrypted = decipher.update(encrypted) + decipher.final * * puts data == decrypted #=> true + * + * Note that other AEAD ciphers may require additional steps, such as + * setting the expected tag length (#auth_tag_len=) or the total data + * length (#ccm_data_len=) in advance. Make sure to read the relevant man + * page for details. */ cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject); eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError); From 1c709970f4e29b58cc45703d3596555767fcea2f Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 25 Jul 2025 02:45:26 +0900 Subject: [PATCH 0531/2435] [ruby/openssl] x509: update keys used in tests Replace fixed-sized RSA keys with the generic rsa-{1,2,3}.pem keys. Those test cases do not depend on specific keys or key sizes, and just need several different keys. Replace DSA keys with EC keys so that we can run more tests in the FIPS mode, which do not seem to support DSA anymore. Also, clean up duplicate test cases using very small keys or obsolete hash functions. rake test_fips no longer skips those test cases. https://github.com/ruby/openssl/commit/3f3105429a --- test/openssl/test_x509cert.rb | 167 +++++++++++----------------------- test/openssl/test_x509crl.rb | 77 +++++++--------- test/openssl/test_x509name.rb | 16 +--- test/openssl/test_x509req.rb | 89 +++++++----------- 4 files changed, 122 insertions(+), 227 deletions(-) diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb index 5fc87d9c675d46..55481690e9a49a 100644 --- a/test/openssl/test_x509cert.rb +++ b/test/openssl/test_x509cert.rb @@ -6,17 +6,16 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase def setup super - @rsa1024 = Fixtures.pkey("rsa1024") - @rsa2048 = Fixtures.pkey("rsa2048") - @dsa256 = Fixtures.pkey("dsa256") - @dsa512 = Fixtures.pkey("dsa512") + @rsa1 = Fixtures.pkey("rsa-1") + @rsa2 = Fixtures.pkey("rsa-2") + @ec1 = Fixtures.pkey("p256") @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") end def test_serial [1, 2**32, 2**100].each{|s| - cert = issue_cert(@ca, @rsa2048, s, [], nil, nil) + cert = issue_cert(@ca, @rsa1, s, [], nil, nil) assert_equal(s, cert.serial) cert = OpenSSL::X509::Certificate.new(cert.to_der) assert_equal(s, cert.serial) @@ -29,40 +28,34 @@ def test_public_key ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","keyid:always",false], ] - - [ - @rsa1024, @rsa2048, @dsa256, @dsa512, - ].each{|pk| - cert = issue_cert(@ca, pk, 1, exts, nil, nil) - assert_equal(cert.extensions.sort_by(&:to_s)[2].value, - OpenSSL::TestUtils.get_subject_key_id(cert)) - cert = OpenSSL::X509::Certificate.new(cert.to_der) - assert_equal(cert.extensions.sort_by(&:to_s)[2].value, - OpenSSL::TestUtils.get_subject_key_id(cert)) - } + cert = issue_cert(@ca, @rsa1, 1, exts, nil, nil) + assert_kind_of(OpenSSL::PKey::RSA, cert.public_key) + assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der) + cert = OpenSSL::X509::Certificate.new(cert.to_der) + assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der) end def test_validity now = Time.at(Time.now.to_i + 0.9) - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now+3600) assert_equal(Time.at(now.to_i), cert.not_before) assert_equal(Time.at(now.to_i+3600), cert.not_after) now = Time.at(now.to_i) - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now+3600) assert_equal(now.getutc, cert.not_before) assert_equal((now+3600).getutc, cert.not_after) now = Time.at(0) - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now) assert_equal(now.getutc, cert.not_before) assert_equal(now.getutc, cert.not_after) now = Time.at(0x7fffffff) - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now) assert_equal(now.getutc, cert.not_before) assert_equal(now.getutc, cert.not_after) @@ -75,7 +68,7 @@ def test_extension_factory ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","issuer:always,keyid:always",false], ] - ca_cert = issue_cert(@ca, @rsa2048, 1, ca_exts, nil, nil) + ca_cert = issue_cert(@ca, @rsa1, 1, ca_exts, nil, nil) ca_cert.extensions.each_with_index{|ext, i| assert_equal(ca_exts[i].first, ext.oid) assert_equal(ca_exts[i].last, ext.critical?) @@ -88,7 +81,7 @@ def test_extension_factory ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], ["subjectAltName","email:ee1@ruby-lang.org",false], ] - ee1_cert = issue_cert(@ee1, @rsa1024, 2, ee1_exts, ca_cert, @rsa2048) + ee1_cert = issue_cert(@ee1, @rsa2, 2, ee1_exts, ca_cert, @rsa1) assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der) ee1_cert.extensions.each_with_index{|ext, i| assert_equal(ee1_exts[i].first, ext.oid) @@ -97,25 +90,25 @@ def test_extension_factory end def test_akiski - ca_cert = generate_cert(@ca, @rsa2048, 4, nil) + ca_cert = generate_cert(@ca, @rsa1, 4, nil) ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert) ca_cert.add_extension( ef.create_extension("subjectKeyIdentifier", "hash", false)) ca_cert.add_extension( ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) - ca_cert.sign(@rsa2048, "sha256") + ca_cert.sign(@rsa1, "sha256") ca_keyid = get_subject_key_id(ca_cert.to_der, hex: false) assert_equal ca_keyid, ca_cert.authority_key_identifier assert_equal ca_keyid, ca_cert.subject_key_identifier - ee_cert = generate_cert(@ee1, Fixtures.pkey("p256"), 5, ca_cert) + ee_cert = generate_cert(@ee1, @rsa2, 5, ca_cert) ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ee_cert) ee_cert.add_extension( ef.create_extension("subjectKeyIdentifier", "hash", false)) ee_cert.add_extension( ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) - ee_cert.sign(@rsa2048, "sha256") + ee_cert.sign(@rsa1, "sha256") ee_keyid = get_subject_key_id(ee_cert.to_der, hex: false) assert_equal ca_keyid, ee_cert.authority_key_identifier @@ -123,13 +116,13 @@ def test_akiski end def test_akiski_missing - cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.authority_key_identifier) assert_nil(cert.subject_key_identifier) end def test_crl_uris_no_crl_distribution_points - cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.crl_uris) end @@ -141,10 +134,10 @@ def test_crl_uris URI.1 = http://www.example.com/crl URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary _cnf_ - cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) + cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts")) - cdp_cert.sign(@rsa2048, "sha256") + cdp_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], cdp_cert.crl_uris @@ -158,10 +151,10 @@ def test_crl_uris_multiple_general_names [crlDistPts_section] fullname = URI:http://www.example.com/crl, URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary _cnf_ - cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) + cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) - cdp_cert.sign(@rsa2048, "sha256") + cdp_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], cdp_cert.crl_uris @@ -177,22 +170,22 @@ def test_crl_uris_no_uris [dirname_section] CN = dirname _cnf_ - cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) + cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) - cdp_cert.sign(@rsa2048, "sha256") + cdp_cert.sign(@rsa1, "sha256") assert_nil(cdp_cert.crl_uris) end def test_aia_missing - cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.ca_issuer_uris) assert_nil(cert.ocsp_uris) end def test_aia ef = OpenSSL::X509::ExtensionFactory.new - aia_cert = generate_cert(@ee1, @rsa2048, 4, nil) + aia_cert = generate_cert(@ee1, @rsa1, 4, nil) ef.subject_certificate = aia_cert aia_cert.add_extension( ef.create_extension( @@ -204,7 +197,7 @@ def test_aia false ) ) - aia_cert.sign(@rsa2048, "sha256") + aia_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/caIssuers", "ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary"], aia_cert.ca_issuer_uris @@ -217,7 +210,7 @@ def test_aia def test_invalid_extension integer = OpenSSL::ASN1::Integer.new(0) - invalid_exts_cert = generate_cert(@ee1, @rsa1024, 1, nil) + invalid_exts_cert = generate_cert(@ee1, @rsa1, 1, nil) ["subjectKeyIdentifier", "authorityKeyIdentifier", "crlDistributionPoints", "authorityInfoAccess"].each do |ext| invalid_exts_cert.add_extension( OpenSSL::X509::Extension.new(ext, integer.to_der) @@ -241,57 +234,16 @@ def test_invalid_extension } end - def test_sign_and_verify_rsa_sha1 - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "SHA1") - assert_equal(false, cert.verify(@rsa1024)) - assert_equal(true, cert.verify(@rsa2048)) - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) + def test_sign_and_verify + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, digest: "SHA256") + assert_equal(true, cert.verify(@rsa1)) + assert_equal(false, cert.verify(@rsa2)) + assert_equal(false, certificate_error_returns_false { cert.verify(@ec1) }) cert.serial = 2 - assert_equal(false, cert.verify(@rsa2048)) - rescue OpenSSL::X509::CertificateError # RHEL 9 disables SHA1 - end - - def test_sign_and_verify_rsa_md5 - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "md5") - assert_equal(false, cert.verify(@rsa1024)) - assert_equal(true, cert.verify(@rsa2048)) - - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) - cert.subject = @ee1 - assert_equal(false, cert.verify(@rsa2048)) - rescue OpenSSL::X509::CertificateError # RHEL7 disables MD5 - end - - def test_sign_and_verify_dsa - cert = issue_cert(@ca, @dsa512, 1, [], nil, nil) - assert_equal(false, certificate_error_returns_false { cert.verify(@rsa1024) }) - assert_equal(false, certificate_error_returns_false { cert.verify(@rsa2048) }) - assert_equal(false, cert.verify(@dsa256)) - assert_equal(true, cert.verify(@dsa512)) - cert.not_after = Time.now - assert_equal(false, cert.verify(@dsa512)) + assert_equal(false, cert.verify(@rsa1)) end - def test_sign_and_verify_rsa_dss1 - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: OpenSSL::Digest.new('DSS1')) - assert_equal(false, cert.verify(@rsa1024)) - assert_equal(true, cert.verify(@rsa2048)) - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) }) - assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) }) - cert.subject = @ee1 - assert_equal(false, cert.verify(@rsa2048)) - rescue OpenSSL::X509::CertificateError - end if defined?(OpenSSL::Digest::DSS1) - - def test_sign_and_verify_dsa_md5 - assert_raise(OpenSSL::X509::CertificateError){ - issue_cert(@ca, @dsa512, 1, [], nil, nil, digest: "md5") - } - end - - def test_sign_and_verify_ed25519 + def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") @@ -299,24 +251,13 @@ def test_sign_and_verify_ed25519 assert_equal(true, cert.verify(ed25519)) end - def test_dsa_with_sha2 - cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha256") - assert_equal("dsa_with_SHA256", cert.signature_algorithm) - # TODO: need more tests for dsa + sha2 - - # SHA1 is allowed from OpenSSL 1.0.0 (0.9.8 requires DSS1) - cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha1") - assert_equal("dsaWithSHA1", cert.signature_algorithm) - rescue OpenSSL::X509::CertificateError # RHEL 9 disables SHA1 - end - def test_check_private_key - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) - assert_equal(true, cert.check_private_key(@rsa2048)) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + assert_equal(true, cert.check_private_key(@rsa1)) end def test_read_from_file - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) Tempfile.create("cert") { |f| f << cert.to_pem f.rewind @@ -325,12 +266,12 @@ def test_read_from_file end def test_read_der_then_pem - cert1 = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert1 = issue_cert(@ca, @rsa1, 1, [], nil, nil) exts = [ # A new line before PEM block ["nsComment", "Another certificate:\n" + cert1.to_pem], ] - cert2 = issue_cert(@ca, @rsa2048, 2, exts, nil, nil) + cert2 = issue_cert(@ca, @rsa1, 2, exts, nil, nil) assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_der) assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_pem) @@ -338,15 +279,15 @@ def test_read_der_then_pem def test_eq now = Time.now - cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil, + cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now + 3600) - cert1 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + cert1 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) - cert2 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + cert2 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) - cert3 = issue_cert(@ee1, @rsa2048, 3, [], cacert, @rsa1024, + cert3 = issue_cert(@ee1, @rsa2, 3, [], cacert, @rsa1, not_before: now, not_after: now + 3600) - cert4 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + cert4 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, digest: "sha512", not_before: now, not_after: now + 3600) assert_equal false, cert1 == 12345 @@ -358,9 +299,9 @@ def test_eq def test_marshal now = Time.now - cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil, + cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now + 3600) - cert = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + cert = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) deserialized = Marshal.load(Marshal.dump(cert)) @@ -378,8 +319,8 @@ def test_load_file_empty_pem end def test_load_file_fullchain_pem - cert1 = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) - cert2 = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert1 = issue_cert(@ee1, @rsa1, 1, [], nil, nil) + cert2 = issue_cert(@ca, @rsa2, 1, [], nil, nil) Tempfile.create("fullchain.pem") do |f| f.puts cert1.to_pem @@ -394,7 +335,7 @@ def test_load_file_fullchain_pem end def test_load_file_certificate_der - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) Tempfile.create("certificate.der", binmode: true) do |f| f.write cert.to_der f.close @@ -419,7 +360,7 @@ def test_load_file_fullchain_garbage end def test_tbs_precert_bytes - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) seq = OpenSSL::ASN1.decode(cert.tbs_bytes) assert_equal 7, seq.value.size diff --git a/test/openssl/test_x509crl.rb b/test/openssl/test_x509crl.rb index 89165388db197a..3c364f57d535e1 100644 --- a/test/openssl/test_x509crl.rb +++ b/test/openssl/test_x509crl.rb @@ -6,21 +6,16 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase def setup super - @rsa1024 = Fixtures.pkey("rsa1024") - @rsa2048 = Fixtures.pkey("rsa2048") - @dsa256 = Fixtures.pkey("dsa256") - @dsa512 = Fixtures.pkey("dsa512") + @rsa1 = Fixtures.pkey("rsa-1") + @rsa2 = Fixtures.pkey("rsa-2") @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") - @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") - @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") end def test_basic now = Time.at(Time.now.to_i) - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) - crl = issue_crl([], 1, now, now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + crl = issue_crl([], 1, now, now+1600, [], cert, @rsa1, "SHA256") assert_equal(1, crl.version) assert_equal(cert.issuer.to_der, crl.issuer.to_der) assert_equal(now, crl.last_update) @@ -55,9 +50,9 @@ def test_revoked [4, now, 4], [5, now, 5], ] - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") revoked = crl.revoked assert_equal(5, revoked.size) assert_equal(1, revoked[0].serial) @@ -98,7 +93,7 @@ def test_revoked revoke_info = (1..1000).collect{|i| [i, now, 0] } crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") revoked = crl.revoked assert_equal(1000, revoked.size) assert_equal(1, revoked[0].serial) @@ -122,9 +117,9 @@ def test_extension ["issuerAltName", "issuer:copy", false], ] - cert = issue_cert(@ca, @rsa2048, 1, cert_exts, nil, nil) + cert = issue_cert(@ca, @rsa1, 1, cert_exts, nil, nil) crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts, - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") exts = crl.extensions assert_equal(3, exts.size) assert_equal("1", exts[0].value) @@ -160,59 +155,55 @@ def test_extension assert_equal(false, exts[2].critical?) no_ext_crl = issue_crl([], 1, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") assert_equal nil, no_ext_crl.authority_key_identifier end def test_crlnumber - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) - crl = issue_crl([], 1, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") assert_match(1.to_s, crl.extensions[0].value) assert_match(/X509v3 CRL Number:\s+#{1}/m, crl.to_text) crl = issue_crl([], 2**32, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") assert_match((2**32).to_s, crl.extensions[0].value) assert_match(/X509v3 CRL Number:\s+#{2**32}/m, crl.to_text) crl = issue_crl([], 2**100, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) + cert, @rsa1, "SHA256") assert_match(/X509v3 CRL Number:\s+#{2**100}/m, crl.to_text) assert_match((2**100).to_s, crl.extensions[0].value) end def test_sign_and_verify - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) - crl = issue_crl([], 1, Time.now, Time.now+1600, [], - cert, @rsa2048, OpenSSL::Digest.new('SHA256')) - assert_equal(false, crl.verify(@rsa1024)) - assert_equal(true, crl.verify(@rsa2048)) - assert_equal(false, crl_error_returns_false { crl.verify(@dsa256) }) - assert_equal(false, crl_error_returns_false { crl.verify(@dsa512) }) + p256 = Fixtures.pkey("p256") + + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") + assert_equal(true, crl.verify(@rsa1)) + assert_equal(false, crl.verify(@rsa2)) + assert_equal(false, crl_error_returns_false { crl.verify(p256) }) crl.version = 0 - assert_equal(false, crl.verify(@rsa2048)) + assert_equal(false, crl.verify(@rsa1)) - cert = issue_cert(@ca, @dsa512, 1, [], nil, nil) - crl = issue_crl([], 1, Time.now, Time.now+1600, [], - cert, @dsa512, OpenSSL::Digest.new('SHA256')) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) }) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) }) - assert_equal(false, crl.verify(@dsa256)) - assert_equal(true, crl.verify(@dsa512)) + cert = issue_cert(@ca, p256, 1, [], nil, nil) + crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, p256, "SHA256") + assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) }) + assert_equal(false, crl_error_returns_false { crl.verify(@rsa2) }) + assert_equal(true, crl.verify(p256)) crl.version = 0 - assert_equal(false, crl.verify(@dsa512)) + assert_equal(false, crl.verify(p256)) end - def test_sign_and_verify_ed25519 + def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, ed25519, nil) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) }) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) }) + assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) }) assert_equal(false, crl.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, crl.verify(ed25519)) crl.version = 0 @@ -245,8 +236,8 @@ def test_revoked_to_der def test_eq now = Time.now - cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil) - crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256") + cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256") rev1 = OpenSSL::X509::Revoked.new.tap { |rev| rev.serial = 1 rev.time = now @@ -274,8 +265,8 @@ def test_eq def test_marshal now = Time.now - cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil) - crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256") + cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256") rev = OpenSSL::X509::Revoked.new.tap { |rev| rev.serial = 1 rev.time = now diff --git a/test/openssl/test_x509name.rb b/test/openssl/test_x509name.rb index c6d15219f58f2d..223c575e4e9edd 100644 --- a/test/openssl/test_x509name.rb +++ b/test/openssl/test_x509name.rb @@ -423,24 +423,14 @@ def test_spaceship assert_equal(nil, n3 <=> nil) end - def name_hash(name) - # OpenSSL 1.0.0 uses SHA1 for canonical encoding (not just a der) of - # X509Name for X509_NAME_hash. - name.respond_to?(:hash_old) ? name.hash_old : name.hash - end + def test_hash_old + omit_on_fips # MD5 - def test_hash dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" name = OpenSSL::X509::Name.parse(dn) d = OpenSSL::Digest.digest('MD5', name.to_der) expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24 - assert_equal(expected, name_hash(name)) - # - dn = "/DC=org/DC=ruby-lang/CN=baz.ruby-lang.org" - name = OpenSSL::X509::Name.parse(dn) - d = OpenSSL::Digest.digest('MD5', name.to_der) - expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24 - assert_equal(expected, name_hash(name)) + assert_equal(expected, name.hash_old) end def test_equality diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb index 18d3e7f8f30450..0a2df47bcac7eb 100644 --- a/test/openssl/test_x509req.rb +++ b/test/openssl/test_x509req.rb @@ -6,10 +6,8 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase def setup super - @rsa1024 = Fixtures.pkey("rsa1024") - @rsa2048 = Fixtures.pkey("rsa2048") - @dsa256 = Fixtures.pkey("dsa256") - @dsa512 = Fixtures.pkey("dsa512") + @rsa1 = Fixtures.pkey("rsa-1") + @rsa2 = Fixtures.pkey("rsa-2") @dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou") end @@ -23,26 +21,22 @@ def issue_csr(ver, dn, key, digest) end def test_public_key - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) - assert_equal(@rsa1024.public_to_der, req.public_key.public_to_der) + req = issue_csr(0, @dn, @rsa1, "SHA256") + assert_kind_of(OpenSSL::PKey::RSA, req.public_key) + assert_equal(@rsa1.public_to_der, req.public_key.public_to_der) req = OpenSSL::X509::Request.new(req.to_der) - assert_equal(@rsa1024.public_to_der, req.public_key.public_to_der) - - req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256')) - assert_equal(@dsa512.public_to_der, req.public_key.public_to_der) - req = OpenSSL::X509::Request.new(req.to_der) - assert_equal(@dsa512.public_to_der, req.public_key.public_to_der) + assert_equal(@rsa1.public_to_der, req.public_key.public_to_der) end def test_version - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(0, req.version) req = OpenSSL::X509::Request.new(req.to_der) assert_equal(0, req.version) end def test_subject - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(@dn.to_der, req.subject.to_der) req = OpenSSL::X509::Request.new(req.to_der) assert_equal(@dn.to_der, req.subject.to_der) @@ -73,9 +67,9 @@ def test_attr OpenSSL::X509::Attribute.new("msExtReq", attrval), ] - req0 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req0 = issue_csr(0, @dn, @rsa1, "SHA256") attrs.each{|attr| req0.add_attribute(attr) } - req1 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req1 = issue_csr(0, @dn, @rsa1, "SHA256") req1.attributes = attrs assert_equal(req0.to_der, req1.to_der) @@ -95,65 +89,44 @@ def test_attr assert_equal(exts, get_ext_req(attrs[1].value)) end - def test_sign_and_verify_rsa_sha1 - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA1')) - assert_equal(true, req.verify(@rsa1024)) - assert_equal(false, req.verify(@rsa2048)) - assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) - assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) - req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBarFooBar") - assert_equal(false, req.verify(@rsa1024)) - rescue OpenSSL::X509::RequestError # RHEL 9 disables SHA1 - end - - def test_sign_and_verify_rsa_md5 - req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest.new('MD5')) - assert_equal(false, req.verify(@rsa1024)) - assert_equal(true, req.verify(@rsa2048)) - assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) - assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) - req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar") - assert_equal(false, req.verify(@rsa2048)) - rescue OpenSSL::X509::RequestError # RHEL7 disables MD5 - end - - def test_sign_and_verify_dsa - req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256')) - assert_equal(false, request_error_returns_false { req.verify(@rsa1024) }) - assert_equal(false, request_error_returns_false { req.verify(@rsa2048) }) - assert_equal(false, req.verify(@dsa256)) - assert_equal(true, req.verify(@dsa512)) - req.public_key = @rsa1024.public_key - assert_equal(false, req.verify(@dsa512)) + def test_sign_digest_instance + req1 = issue_csr(0, @dn, @rsa1, "SHA256") + req2 = issue_csr(0, @dn, @rsa1, OpenSSL::Digest.new("SHA256")) + assert_equal(req1.to_der, req2.to_der) end - def test_sign_and_verify_dsa_md5 - assert_raise(OpenSSL::X509::RequestError){ - issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('MD5')) } + def test_sign_and_verify + req = issue_csr(0, @dn, @rsa1, "SHA256") + assert_equal(true, req.verify(@rsa1)) + assert_equal(false, req.verify(@rsa2)) + ec = OpenSSL::PKey::EC.generate("prime256v1") + assert_equal(false, request_error_returns_false { req.verify(ec) }) + req.subject = OpenSSL::X509::Name.parse_rfc2253("CN=FooBarFooBar,C=JP") + assert_equal(false, req.verify(@rsa1)) end - def test_sign_and_verify_ed25519 + def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") req = issue_csr(0, @dn, ed25519, nil) - assert_equal(false, request_error_returns_false { req.verify(@rsa1024) }) - assert_equal(false, request_error_returns_false { req.verify(@rsa2048) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa1) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa2) }) assert_equal(false, req.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, req.verify(ed25519)) - req.public_key = @rsa1024.public_key + req.public_key = @rsa1 assert_equal(false, req.verify(ed25519)) end def test_dup - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(req.to_der, req.dup.to_der) end def test_eq - req1 = issue_csr(0, @dn, @rsa1024, "sha256") - req2 = issue_csr(0, @dn, @rsa1024, "sha256") - req3 = issue_csr(0, @dn, @rsa1024, "sha512") + req1 = issue_csr(0, @dn, @rsa1, "SHA256") + req2 = issue_csr(0, @dn, @rsa1, "SHA256") + req3 = issue_csr(0, @dn, @rsa1, "SHA512") assert_equal false, req1 == 12345 assert_equal true, req1 == req2 @@ -161,7 +134,7 @@ def test_eq end def test_marshal - req = issue_csr(0, @dn, @rsa1024, "sha256") + req = issue_csr(0, @dn, @rsa1, "SHA256") deserialized = Marshal.load(Marshal.dump(req)) assert_equal req.to_der, deserialized.to_der From bf244356ea11c1cc67221de6b3cfeebe4f333df2 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 18 Oct 2025 01:52:15 +0900 Subject: [PATCH 0532/2435] [ruby/openssl] asn1: update keys used in tests Use generic keys whenever possible. https://github.com/ruby/openssl/commit/689fc271b1 --- test/openssl/test_asn1.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index 1b933a78bf3226..501e35151fb5e4 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -6,7 +6,7 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase def test_decode_x509_certificate subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa-1") now = Time.at(Time.now.to_i) # suppress usec s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf exts = [ From 709aa7284ba8c54763516f450fa8359431c66626 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 18 Oct 2025 01:52:43 +0900 Subject: [PATCH 0533/2435] [ruby/openssl] ns_spki: update keys used in tests Use generic keys whenever possible. https://github.com/ruby/openssl/commit/ef4fa5e9b4 --- test/openssl/test_ns_spki.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb index d76fc9e5cfb6cd..04844292897a08 100644 --- a/test/openssl/test_ns_spki.rb +++ b/test/openssl/test_ns_spki.rb @@ -17,8 +17,8 @@ def setup end def test_build_data - key1 = Fixtures.pkey("rsa1024") - key2 = Fixtures.pkey("rsa2048") + key1 = Fixtures.pkey("rsa-1") + key2 = Fixtures.pkey("rsa-2") spki = OpenSSL::Netscape::SPKI.new spki.challenge = "RandomString" spki.public_key = key1.public_key From f90ca1a0b133a32932a1b9fbdb30845cfc2ee0ac Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 18 Oct 2025 01:53:09 +0900 Subject: [PATCH 0534/2435] [ruby/openssl] ocsp: update keys used in tests Use generic keys whenever possible. https://github.com/ruby/openssl/commit/cc4d40525c --- test/openssl/test_ocsp.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/openssl/test_ocsp.rb b/test/openssl/test_ocsp.rb index cf96fc22e515ba..b9b66ad37ad088 100644 --- a/test/openssl/test_ocsp.rb +++ b/test/openssl/test_ocsp.rb @@ -13,7 +13,7 @@ def setup # @cert2 @ocsp_cert ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") - @ca_key = Fixtures.pkey("rsa1024") + @ca_key = Fixtures.pkey("rsa-1") ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], @@ -22,7 +22,7 @@ def setup ca_subj, @ca_key, 1, ca_exts, nil, nil) cert_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA2") - @cert_key = Fixtures.pkey("rsa1024") + @cert_key = Fixtures.pkey("rsa-2") cert_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], @@ -31,14 +31,14 @@ def setup cert_subj, @cert_key, 5, cert_exts, @ca_cert, @ca_key) cert2_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert") - @cert2_key = Fixtures.pkey("rsa1024") + @cert2_key = Fixtures.pkey("rsa-3") cert2_exts = [ ] @cert2 = OpenSSL::TestUtils.issue_cert( cert2_subj, @cert2_key, 10, cert2_exts, @cert, @cert_key) ocsp_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCAOCSP") - @ocsp_key = Fixtures.pkey("rsa2048") + @ocsp_key = Fixtures.pkey("p256") ocsp_exts = [ ["extendedKeyUsage", "OCSPSigning", true], ] @@ -63,8 +63,10 @@ def test_certificate_id_issuer_name_hash def test_certificate_id_issuer_key_hash cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert) - assert_equal OpenSSL::Digest.hexdigest('SHA1', OpenSSL::ASN1.decode(@ca_cert.to_der).value[0].value[6].value[1].value), cid.issuer_key_hash - assert_equal "d1fef9fbf8ae1bc160cbfa03e2596dd873089213", cid.issuer_key_hash + # content of subjectPublicKey (bit string) in SubjectPublicKeyInfo + spki = OpenSSL::ASN1.decode(@ca_key.public_to_der) + assert_equal OpenSSL::Digest.hexdigest("SHA1", spki.value[1].value), + cid.issuer_key_hash end def test_certificate_id_hash_algorithm From a9ba78e4c29d3821d7e86e89993e30202892d851 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 25 Jul 2025 20:12:37 +0900 Subject: [PATCH 0535/2435] [ruby/openssl] pkey: update keys used in tests Use generic keys whenever possible. https://github.com/ruby/openssl/commit/90d6af60b9 --- test/openssl/test_pkey.rb | 10 +- test/openssl/test_pkey_dsa.rb | 99 ++++++---------- test/openssl/test_pkey_rsa.rb | 209 +++++++++++----------------------- test/openssl/utils.rb | 27 +++++ 4 files changed, 133 insertions(+), 212 deletions(-) diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 8066c4dc190fa9..0943a7737db707 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -293,10 +293,10 @@ def test_raw_initialize_errors end def test_compare? - key1 = Fixtures.pkey("rsa1024") - key2 = Fixtures.pkey("rsa1024") - key3 = Fixtures.pkey("rsa2048") - key4 = Fixtures.pkey("dh-1") + key1 = Fixtures.pkey("rsa-1") + key2 = Fixtures.pkey("rsa-1") + key3 = Fixtures.pkey("rsa-2") + key4 = Fixtures.pkey("p256") assert_equal(true, key1.compare?(key2)) assert_equal(true, key1.public_key.compare?(key2)) @@ -311,7 +311,7 @@ def test_compare? end def test_to_text - rsa = Fixtures.pkey("rsa1024") + rsa = Fixtures.pkey("rsa-1") assert_include rsa.to_text, "publicExponent" end end diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 0779483bd482b1..ef0fdf9182fb1b 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -10,7 +10,7 @@ def setup end def test_private - key = Fixtures.pkey("dsa1024") + key = Fixtures.pkey("dsa2048") assert_equal true, key.private? key2 = OpenSSL::PKey::DSA.new(key.to_der) assert_equal true, key2.private? @@ -114,105 +114,76 @@ def test_sign_verify_raw def test_DSAPrivateKey # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey - dsa512 = Fixtures.pkey("dsa512") + orig = Fixtures.pkey("dsa2048") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), - OpenSSL::ASN1::Integer(dsa512.p), - OpenSSL::ASN1::Integer(dsa512.q), - OpenSSL::ASN1::Integer(dsa512.g), - OpenSSL::ASN1::Integer(dsa512.pub_key), - OpenSSL::ASN1::Integer(dsa512.priv_key) + OpenSSL::ASN1::Integer(orig.p), + OpenSSL::ASN1::Integer(orig.q), + OpenSSL::ASN1::Integer(orig.g), + OpenSSL::ASN1::Integer(orig.pub_key), + OpenSSL::ASN1::Integer(orig.priv_key) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_predicate key, :private? - assert_same_dsa dsa512, key - - pem = <<~EOF - -----BEGIN DSA PRIVATE KEY----- - MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok - RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D - AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR - S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ - Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S - 55jreJD3Se3slps= - -----END DSA PRIVATE KEY----- - EOF + assert_same_dsa orig, key + + pem = der_to_pem(asn1.to_der, "DSA PRIVATE KEY") key = OpenSSL::PKey::DSA.new(pem) - assert_same_dsa dsa512, key + assert_same_dsa orig, key - assert_equal asn1.to_der, dsa512.to_der - assert_equal pem, dsa512.export + assert_equal asn1.to_der, orig.to_der + assert_equal pem, orig.export end def test_DSAPrivateKey_encrypted - # key = abcdef - dsa512 = Fixtures.pkey("dsa512") - pem = <<~EOF - -----BEGIN DSA PRIVATE KEY----- - Proc-Type: 4,ENCRYPTED - DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9 - - D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB - 74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw - QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt - OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7 - XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta - Wgx6c8K+qBAIVrilw3EWxw== - -----END DSA PRIVATE KEY----- - EOF + # OpenSSL DSAPrivateKey with OpenSSL encryption + orig = Fixtures.pkey("dsa2048") + + pem = der_to_encrypted_pem(orig.to_der, "DSA PRIVATE KEY", "abcdef") key = OpenSSL::PKey::DSA.new(pem, "abcdef") - assert_same_dsa dsa512, key + assert_same_dsa orig, key key = OpenSSL::PKey::DSA.new(pem) { "abcdef" } - assert_same_dsa dsa512, key + assert_same_dsa orig, key cipher = OpenSSL::Cipher.new("aes-128-cbc") - exported = dsa512.to_pem(cipher, "abcdef\0\1") - assert_same_dsa dsa512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") + exported = orig.to_pem(cipher, "abcdef\0\1") + assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") assert_raise(OpenSSL::PKey::DSAError) { OpenSSL::PKey::DSA.new(exported, "abcdef") } end def test_PUBKEY - dsa512 = Fixtures.pkey("dsa512") - dsa512pub = OpenSSL::PKey::DSA.new(dsa512.public_to_der) + orig = Fixtures.pkey("dsa2048") + pub = OpenSSL::PKey::DSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("DSA"), OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(dsa512.p), - OpenSSL::ASN1::Integer(dsa512.q), - OpenSSL::ASN1::Integer(dsa512.g) + OpenSSL::ASN1::Integer(orig.p), + OpenSSL::ASN1::Integer(orig.q), + OpenSSL::ASN1::Integer(orig.g) ]) ]), OpenSSL::ASN1::BitString( - OpenSSL::ASN1::Integer(dsa512.pub_key).to_der + OpenSSL::ASN1::Integer(orig.pub_key).to_der ) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_not_predicate key, :private? - assert_same_dsa dsa512pub, key - - pem = <<~EOF - -----BEGIN PUBLIC KEY----- - MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT - YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB - 4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo - ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg - sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX - oXi9OA== - -----END PUBLIC KEY----- - EOF + assert_same_dsa pub, key + + pem = der_to_pem(asn1.to_der, "PUBLIC KEY") key = OpenSSL::PKey::DSA.new(pem) - assert_same_dsa dsa512pub, key + assert_same_dsa pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export - assert_equal asn1.to_der, dsa512.public_to_der + assert_equal asn1.to_der, orig.public_to_der assert_equal asn1.to_der, key.public_to_der - assert_equal pem, dsa512.public_to_pem + assert_equal pem, orig.public_to_pem assert_equal pem, key.public_to_pem end @@ -263,7 +234,7 @@ def test_params end def test_dup - key = Fixtures.pkey("dsa1024") + key = Fixtures.pkey("dsa2048") key2 = key.dup assert_equal key.params, key2.params @@ -275,7 +246,7 @@ def test_dup end def test_marshal - key = Fixtures.pkey("dsa1024") + key = Fixtures.pkey("dsa2048") deserialized = Marshal.load(Marshal.dump(key)) assert_equal key.to_der, deserialized.to_der diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 6a8768d1fffb3b..90dd0481edfc67 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -6,7 +6,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase def test_no_private_exp key = OpenSSL::PKey::RSA.new - rsa = Fixtures.pkey("rsa2048") + rsa = Fixtures.pkey("rsa-1") key.set_key(rsa.n, rsa.e, nil) key.set_factors(rsa.p, rsa.q) assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt("foo") } @@ -14,32 +14,30 @@ def test_no_private_exp end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0 def test_private - key = Fixtures.pkey("rsa2048") + key = Fixtures.pkey("rsa-1") # Generated by DER key2 = OpenSSL::PKey::RSA.new(key.to_der) - assert(key2.private?) + assert_true(key2.private?) # public key key3 = key.public_key - assert(!key3.private?) + assert_false(key3.private?) # Generated by public key DER key4 = OpenSSL::PKey::RSA.new(key3.to_der) - assert(!key4.private?) - rsa1024 = Fixtures.pkey("rsa1024") + assert_false(key4.private?) if !openssl?(3, 0, 0) - key = OpenSSL::PKey::RSA.new # Generated by RSA#set_key key5 = OpenSSL::PKey::RSA.new - key5.set_key(rsa1024.n, rsa1024.e, rsa1024.d) - assert(key5.private?) + key5.set_key(key.n, key.e, key.d) + assert_true(key5.private?) # Generated by RSA#set_key, without d key6 = OpenSSL::PKey::RSA.new - key6.set_key(rsa1024.n, rsa1024.e, nil) - assert(!key6.private?) + key6.set_key(key.n, key.e, nil) + assert_false(key6.private?) end end @@ -280,57 +278,57 @@ def test_encrypt_decrypt_legacy end def test_export - rsa1024 = Fixtures.pkey("rsa1024") + orig = Fixtures.pkey("rsa-1") - pub = OpenSSL::PKey.read(rsa1024.public_to_der) - assert_not_equal rsa1024.export, pub.export - assert_equal rsa1024.public_to_pem, pub.export + pub = OpenSSL::PKey.read(orig.public_to_der) + assert_not_equal orig.export, pub.export + assert_equal orig.public_to_pem, pub.export # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key = OpenSSL::PKey::RSA.new # key has only n, e and d - key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) - assert_equal rsa1024.public_key.export, key.export + key.set_key(orig.n, orig.e, orig.d) + assert_equal orig.public_key.export, key.export # key has only n, e, d, p and q - key.set_factors(rsa1024.p, rsa1024.q) - assert_equal rsa1024.public_key.export, key.export + key.set_factors(orig.p, orig.q) + assert_equal orig.public_key.export, key.export # key has n, e, d, p, q, dmp1, dmq1 and iqmp - key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) - assert_equal rsa1024.export, key.export + key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) + assert_equal orig.export, key.export end end def test_to_der - rsa1024 = Fixtures.pkey("rsa1024") + orig = Fixtures.pkey("rsa-1") - pub = OpenSSL::PKey.read(rsa1024.public_to_der) - assert_not_equal rsa1024.to_der, pub.to_der - assert_equal rsa1024.public_to_der, pub.to_der + pub = OpenSSL::PKey.read(orig.public_to_der) + assert_not_equal orig.to_der, pub.to_der + assert_equal orig.public_to_der, pub.to_der # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key = OpenSSL::PKey::RSA.new # key has only n, e and d - key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) - assert_equal rsa1024.public_key.to_der, key.to_der + key.set_key(orig.n, orig.e, orig.d) + assert_equal orig.public_key.to_der, key.to_der # key has only n, e, d, p and q - key.set_factors(rsa1024.p, rsa1024.q) - assert_equal rsa1024.public_key.to_der, key.to_der + key.set_factors(orig.p, orig.q) + assert_equal orig.public_key.to_der, key.to_der # key has n, e, d, p, q, dmp1, dmq1 and iqmp - key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) - assert_equal rsa1024.to_der, key.to_der + key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) + assert_equal orig.to_der, key.to_der end end def test_RSAPrivateKey - rsa = Fixtures.pkey("rsa2048") + rsa = Fixtures.pkey("rsa-1") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(rsa.n), @@ -346,35 +344,7 @@ def test_RSAPrivateKey assert_predicate key, :private? assert_same_rsa rsa, key - pem = <<~EOF - -----BEGIN RSA PRIVATE KEY----- - MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN - s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign - 4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D - kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl - NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J - DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb - I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq - PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V - seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0 - Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc - VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW - wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G - 0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj - XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb - aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n - h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw - Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k - IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb - v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId - U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr - vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS - Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC - 9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41 - gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG - 4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw== - -----END RSA PRIVATE KEY----- - EOF + pem = der_to_pem(asn1.to_der, "RSA PRIVATE KEY") key = OpenSSL::PKey::RSA.new(pem) assert_same_rsa rsa, key @@ -389,69 +359,46 @@ def test_RSAPrivateKey end def test_RSAPrivateKey_encrypted + # PKCS #1 RSAPrivateKey with OpenSSL encryption omit_on_fips - rsa1024 = Fixtures.pkey("rsa1024") - # key = abcdef - pem = <<~EOF - -----BEGIN RSA PRIVATE KEY----- - Proc-Type: 4,ENCRYPTED - DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 - - zgJniZZQfvv8TFx3LzV6zhAQVayvQVZlAYqFq2yWbbxzF7C+IBhKQle9IhUQ9j/y - /jkvol550LS8vZ7TX5WxyDLe12cdqzEvpR6jf3NbxiNysOCxwG4ErhaZGP+krcoB - ObuL0nvls/+3myy5reKEyy22+0GvTDjaChfr+FwJjXMG+IBCLscYdgZC1LQL6oAn - 9xY5DH3W7BW4wR5ttxvtN32TkfVQh8xi3jrLrduUh+hV8DTiAiLIhv0Vykwhep2p - WZA+7qbrYaYM8GLLgLrb6LfBoxeNxAEKiTpl1quFkm+Hk1dKq0EhVnxHf92x0zVF - jRGZxAMNcrlCoE4f5XK45epVZSZvihdo1k73GPbp84aZ5P/xlO4OwZ3i4uCQXynl - jE9c+I+4rRWKyPz9gkkqo0+teJL8ifeKt/3ab6FcdA0aArynqmsKJMktxmNu83We - YVGEHZPeOlyOQqPvZqWsLnXQUfg54OkbuV4/4mWSIzxFXdFy/AekSeJugpswMXqn - oNck4qySNyfnlyelppXyWWwDfVus9CVAGZmJQaJExHMT/rQFRVchlmY0Ddr5O264 - gcjv90o1NBOc2fNcqjivuoX7ROqys4K/YdNQ1HhQ7usJghADNOtuLI8ZqMh9akXD - Eqp6Ne97wq1NiJj0nt3SJlzTnOyTjzrTe0Y+atPkVKp7SsjkATMI9JdhXwGhWd7a - qFVl0owZiDasgEhyG2K5L6r+yaJLYkPVXZYC/wtWC3NEchnDWZGQcXzB4xROCQkD - OlWNYDkPiZioeFkA3/fTMvG4moB2Pp9Q4GU5fJ6k43Ccu1up8dX/LumZb4ecg5/x - -----END RSA PRIVATE KEY----- - EOF + rsa = Fixtures.pkey("rsa2048") + + pem = der_to_encrypted_pem(rsa.to_der, "RSA PRIVATE KEY", "abcdef") key = OpenSSL::PKey::RSA.new(pem, "abcdef") - assert_same_rsa rsa1024, key + assert_same_rsa rsa, key key = OpenSSL::PKey::RSA.new(pem) { "abcdef" } - assert_same_rsa rsa1024, key + assert_same_rsa rsa, key cipher = OpenSSL::Cipher.new("aes-128-cbc") - exported = rsa1024.to_pem(cipher, "abcdef\0\1") - assert_same_rsa rsa1024, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1") + exported = rsa.to_pem(cipher, "abcdef\0\1") + assert_same_rsa rsa, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1") assert_raise(OpenSSL::PKey::RSAError) { OpenSSL::PKey::RSA.new(exported, "abcdef") } end def test_RSAPublicKey - rsa1024 = Fixtures.pkey("rsa1024") - rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + # PKCS #1 RSAPublicKey. Only decoding is supported + orig = Fixtures.pkey("rsa-1") + pub = OpenSSL::PKey::RSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(rsa1024.n), - OpenSSL::ASN1::Integer(rsa1024.e) + OpenSSL::ASN1::Integer(orig.n), + OpenSSL::ASN1::Integer(orig.e) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_not_predicate key, :private? - assert_same_rsa rsa1024pub, key + assert_same_rsa pub, key - pem = <<~EOF - -----BEGIN RSA PUBLIC KEY----- - MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF - geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u - /xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE= - -----END RSA PUBLIC KEY----- - EOF + pem = der_to_pem(asn1.to_der, "RSA PUBLIC KEY") key = OpenSSL::PKey::RSA.new(pem) - assert_same_rsa rsa1024pub, key + assert_same_rsa pub, key end def test_PUBKEY - rsa1024 = Fixtures.pkey("rsa1024") - rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + orig = Fixtures.pkey("rsa-1") + pub = OpenSSL::PKey::RSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ @@ -460,39 +407,32 @@ def test_PUBKEY ]), OpenSSL::ASN1::BitString( OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(rsa1024.n), - OpenSSL::ASN1::Integer(rsa1024.e) + OpenSSL::ASN1::Integer(orig.n), + OpenSSL::ASN1::Integer(orig.e) ]).to_der ) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_not_predicate key, :private? - assert_same_rsa rsa1024pub, key + assert_same_rsa pub, key - pem = <<~EOF - -----BEGIN PUBLIC KEY----- - MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLwsSw1ECnPtT+PkOgHhcGA71n - wC2/nL85VBGnRqDxOqjVh7CxaKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbC - z0layNqHyywQEVLFmp1cpIt/Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU - 3+l54E6lF/JfFEU5hwIDAQAB - -----END PUBLIC KEY----- - EOF + pem = der_to_pem(asn1.to_der, "PUBLIC KEY") key = OpenSSL::PKey::RSA.new(pem) - assert_same_rsa rsa1024pub, key + assert_same_rsa pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export - assert_equal asn1.to_der, rsa1024.public_to_der + assert_equal asn1.to_der, orig.public_to_der assert_equal asn1.to_der, key.public_to_der - assert_equal pem, rsa1024.public_to_pem + assert_equal pem, orig.public_to_pem assert_equal pem, key.public_to_pem end def test_pem_passwd omit_on_fips - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa-1") pem3c = key.to_pem("aes-128-cbc", "key") assert_match (/ENCRYPTED/), pem3c assert_equal key.to_der, OpenSSL::PKey.read(pem3c, "key").to_der @@ -503,38 +443,21 @@ def test_pem_passwd end def test_private_encoding - rsa1024 = Fixtures.pkey("rsa1024") + pkey = Fixtures.pkey("rsa-1") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("rsaEncryption"), OpenSSL::ASN1::Null(nil) ]), - OpenSSL::ASN1::OctetString(rsa1024.to_der) + OpenSSL::ASN1::OctetString(pkey.to_der) ]) - assert_equal asn1.to_der, rsa1024.private_to_der - assert_same_rsa rsa1024, OpenSSL::PKey.read(asn1.to_der) + assert_equal asn1.to_der, pkey.private_to_der + assert_same_rsa pkey, OpenSSL::PKey.read(asn1.to_der) - pem = <<~EOF - -----BEGIN PRIVATE KEY----- - MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMvCxLDUQKc+1P4+ - Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RFgeyTgE8KQTduu1OE9Zz2SMcR - BDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u/xkP2mKGjAokPIwOI3oCthSZ - lzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAECgYEApKX8xBqvJ7XI7Kypfo/x8MVC - 3rxW+1eQ2aVKIo4a7PKGjQz5RVIVyzqTUvSZoMTbkAxlSIbO5YfJpTnl3tFcOB6y - QMxqQPW/pl6Ni3EmRJdsRM5MsPBRZOfrXxOCdvXu1TWOS1S1TrvEr/TyL9eh2WCd - CGzpWgdO4KHce7vs7pECQQDv6DGoG5lHnvbvj9qSJb9K5ebRJc8S+LI7Uy5JHC0j - zsHTYPSqBXwPVQdGbgCEycnwwKzXzT2QxAQmJBQKun2ZAkEA2W3aeAE7Xi6zo2eG - 4Cx4UNMHMIdfBRS7VgoekwybGmcapqV0aBew5kHeWAmxP1WUZ/dgZh2QtM1VuiBA - qUqkHwJBAOJLCRvi/JB8N7z82lTk2i3R8gjyOwNQJv6ilZRMyZ9vFZFHcUE27zCf - Kb+bX03h8WPwupjMdfgpjShU+7qq8nECQQDBrmyc16QVyo40sgTgblyiysitvviy - ovwZsZv4q5MCmvOPnPUrwGbRRb2VONUOMOKpFiBl9lIv7HU//nj7FMVLAkBjUXED - 83dA8JcKM+HlioXEAxCzZVVhN+D63QwRwkN08xAPklfqDkcqccWDaZm2hdCtaYlK - funwYkrzI1OikQSs - -----END PRIVATE KEY----- - EOF - assert_equal pem, rsa1024.private_to_pem - assert_same_rsa rsa1024, OpenSSL::PKey.read(pem) + pem = der_to_pem(asn1.to_der, "PRIVATE KEY") + assert_equal pem, pkey.private_to_pem + assert_same_rsa pkey, OpenSSL::PKey.read(pem) end def test_private_encoding_encrypted @@ -610,7 +533,7 @@ def test_params end def test_dup - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa-1") key2 = key.dup assert_equal key.params, key2.params @@ -622,7 +545,7 @@ def test_dup end def test_marshal - key = Fixtures.pkey("rsa2048") + key = Fixtures.pkey("rsa-1") deserialized = Marshal.load(Marshal.dump(key)) assert_equal key.to_der, deserialized.to_der diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index 8d6261253159aa..7e6fe8b1633cf8 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -294,6 +294,33 @@ def assert_sign_verify_false_or_error else assert_equal(false, ret) end + + def der_to_pem(der, pem_header) + # RFC 7468 + <<~EOS + -----BEGIN #{pem_header}----- + #{[der].pack("m0").scan(/.{1,64}/).join("\n")} + -----END #{pem_header}----- + EOS + end + + def der_to_encrypted_pem(der, pem_header, password) + # OpenSSL encryption, non-standard + iv = 16.times.to_a.pack("C*") + encrypted = OpenSSL::Cipher.new("aes-128-cbc").encrypt.then { |cipher| + cipher.key = OpenSSL::Digest.digest("MD5", password + iv[0, 8]) + cipher.iv = iv + cipher.update(der) << cipher.final + } + <<~EOS + -----BEGIN #{pem_header}----- + Proc-Type: 4,ENCRYPTED + DEK-Info: AES-128-CBC,#{iv.unpack1("H*").upcase} + + #{[encrypted].pack("m0").scan(/.{1,64}/).join("\n")} + -----END #{pem_header}----- + EOS + end end module OpenSSL::Certs From d0ea9c0cea1f5c70042a890177c9c29ada1a5927 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 25 Jul 2025 21:25:43 +0900 Subject: [PATCH 0536/2435] [ruby/openssl] ssl: update keys used in tests Use generic keys whenever possible. https://github.com/ruby/openssl/commit/73d6a25360 --- test/openssl/test_ssl.rb | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 3ec1a710436f38..e700e53e3b58c0 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -2079,7 +2079,7 @@ def test_pqc_sigalg digest: nil) mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key, digest: nil) - rsa = Fixtures.pkey("rsa2048") + rsa = Fixtures.pkey("rsa-1") rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key) ctx_proc = -> ctx { # Unset values set by start_server @@ -2246,22 +2246,30 @@ def test_security_level end assert_equal(1, ctx.security_level) - dsa512 = Fixtures.pkey("dsa512") - dsa512_cert = issue_cert(@svr, dsa512, 50, [], @ca_cert, @ca_key) - rsa1024 = Fixtures.pkey("rsa1024") - rsa1024_cert = issue_cert(@svr, rsa1024, 51, [], @ca_cert, @ca_key) + # See SSL_CTX_set_security_level(3). Definitions of security levels may + # change in future OpenSSL versions. As of OpenSSL 1.1.0: + # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys. + # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys. + begin + ec112 = OpenSSL::PKey::EC.generate("secp112r1") + ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key) + ec192 = OpenSSL::PKey::EC.generate("prime192v1") + ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key) + rescue OpenSSL::PKey::PKeyError + # Distro-provided OpenSSL may refuse to generate small keys + return + end assert_raise(OpenSSL::SSL::SSLError) { - # 512 bit DSA key is rejected because it offers < 80 bits of security - ctx.add_certificate(dsa512_cert, dsa512) + ctx.add_certificate(ec112_cert, ec112) } assert_nothing_raised { - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } ctx.security_level = 2 assert_raise(OpenSSL::SSL::SSLError) { # < 112 bits of security - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } end From 1b57e5574df360340cfe8511ba58d27a22226183 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 26 Jul 2025 05:59:14 +0900 Subject: [PATCH 0537/2435] [ruby/openssl] test: remove unused small test keys Previous commits removed all usages of those small keys. https://github.com/ruby/openssl/commit/f9d87d7912 --- test/openssl/fixtures/pkey/dsa1024.pem | 12 ------------ test/openssl/fixtures/pkey/dsa256.pem | 8 -------- test/openssl/fixtures/pkey/dsa512.pem | 8 -------- test/openssl/fixtures/pkey/rsa1024.pem | 15 --------------- 4 files changed, 43 deletions(-) delete mode 100644 test/openssl/fixtures/pkey/dsa1024.pem delete mode 100644 test/openssl/fixtures/pkey/dsa256.pem delete mode 100644 test/openssl/fixtures/pkey/dsa512.pem delete mode 100644 test/openssl/fixtures/pkey/rsa1024.pem diff --git a/test/openssl/fixtures/pkey/dsa1024.pem b/test/openssl/fixtures/pkey/dsa1024.pem deleted file mode 100644 index 1bf498895e2a09..00000000000000 --- a/test/openssl/fixtures/pkey/dsa1024.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQCH9aAoXvWWThIjkA6D+nI1F9ksF9iDq594rkiGNOT9sPDOdB+n -D+qeeeeloRlj19ymCSADPI0ZLRgkchkAEnY2RnqnhHOjVf/roGgRbW+iQDMbQ9wa -/pvc6/fAbsu1goE1hBYjm98/sZEeXavj8tR56IXnjF1b6Nx0+sgeUKFKEQIVAMiz -4BJUFeTtddyM4uadBM7HKLPRAoGAZdLBSYNGiij7vAjesF5mGUKTIgPd+JKuBEDx -OaBclsgfdoyoF/TMOkIty+PVlYD+//Vl2xnoUEIRaMXHwHfm0r2xUX++oeRaSScg -YizJdUxe5jvBuBszGPRc/mGpb9YvP0sB+FL1KmuxYmdODfCe51zl8uM/CVhouJ3w -DjmRGscCgYAuFlfC7p+e8huCKydfcv/beftqjewiOPpQ3u5uI6KPCtCJPpDhs3+4 -IihH2cPsAlqwGF4tlibW1+/z/OZ1AZinPK3y7b2jSJASEaPeEltVzB92hcd1khk2 -jTYcmSsV4VddplOPK9czytR/GbbibxsrhhgZUbd8LPbvIgaiadJ1PgIUBnJ/5vN2 -CVArsEzlPUCbohPvZnE= ------END DSA PRIVATE KEY----- diff --git a/test/openssl/fixtures/pkey/dsa256.pem b/test/openssl/fixtures/pkey/dsa256.pem deleted file mode 100644 index d9a407f736897d..00000000000000 --- a/test/openssl/fixtures/pkey/dsa256.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE -9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed -AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM -3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT -b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn -ISNX5cMzFHRW3Q== ------END DSA PRIVATE KEY----- diff --git a/test/openssl/fixtures/pkey/dsa512.pem b/test/openssl/fixtures/pkey/dsa512.pem deleted file mode 100644 index 962c41cc67fc39..00000000000000 --- a/test/openssl/fixtures/pkey/dsa512.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok -RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D -AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR -S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ -Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S -55jreJD3Se3slps= ------END DSA PRIVATE KEY----- diff --git a/test/openssl/fixtures/pkey/rsa1024.pem b/test/openssl/fixtures/pkey/rsa1024.pem deleted file mode 100644 index 464de074be895f..00000000000000 --- a/test/openssl/fixtures/pkey/rsa1024.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx -aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ -Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB -AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 -maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T -gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 -74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE -JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX -sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII -8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA -wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi -qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD -dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== ------END RSA PRIVATE KEY----- From 504a1ba7eec657c195450dda7625aa8825b2ecb9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 15:04:14 +0900 Subject: [PATCH 0538/2435] [ruby/rubygems] Use dummy gem instead of uri. If we install uri-1.0.4 as default gems. The example may be failed with version miss-match. https://github.com/ruby/rubygems/commit/fd2dcb502b --- spec/bundler/commands/lock_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 36493e108a2cf4..b7f475f72a7f0e 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -313,14 +313,14 @@ it "updates a specific gem and write to a custom location" do build_repo4 do - build_gem "uri", %w[1.0.2 1.0.3] + build_gem "foo", %w[1.0.2 1.0.3] build_gem "warning", %w[1.4.0 1.5.0] end gemfile <<~G source "https://gem.repo4" - gem "uri" + gem "foo" gem "warning" G @@ -328,7 +328,7 @@ GEM remote: https://gem.repo4 specs: - uri (1.0.2) + foo (1.0.2) warning (1.4.0) PLATFORMS @@ -342,10 +342,10 @@ #{Bundler::VERSION} L - bundle "lock --update uri --lockfile=lock" + bundle "lock --update foo --lockfile=lock" lockfile_content = read_lockfile("lock") - expect(lockfile_content).to include("uri (1.0.3)") + expect(lockfile_content).to include("foo (1.0.3)") expect(lockfile_content).to include("warning (1.4.0)") end From 6a1644ddca64e81839bc5f9953dea692dcac7b70 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 15:05:33 +0900 Subject: [PATCH 0539/2435] [ruby/rubygems] If we use shared GEM_HOME and install multiple versions, it may cause unexpected test failures. ``` Fetching gem metadata from https://gem.repo4/. Resolving dependencies... Resolving dependencies... # $? => 0 cannot load such file -- diff/lcs ``` https://github.com/ruby/rubygems/commit/668b300261 --- spec/bundler/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 87bdf8b9f3a1d2..d750218452ebcf 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -19,6 +19,10 @@ ENV["BUNDLER_EDITOR"] = nil require "bundler" +# If we use shared GEM_HOME and install multiple versions, it may cause +# unexpected test failures. +gem "diff-lcs" + require "rspec/core" require "rspec/expectations" require "rspec/mocks" From 79684cea9f7178389f2b430abdd09ad0fee1e66d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 15:17:29 +0900 Subject: [PATCH 0540/2435] [ruby/rubygems] Drop to support old git https://github.com/ruby/rubygems/commit/687ffd7265 --- lib/bundler/source/git/git_proxy.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index b6f8460400f841..02ec121abeca05 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -408,11 +408,7 @@ def capture(cmd, dir, ignore_err: false) def capture3_args_for(cmd, dir) return ["git", *cmd] unless dir - if Bundler.feature_flag.bundler_4_mode? || supports_minus_c? - ["git", "-C", dir.to_s, *cmd] - else - ["git", *cmd, { chdir: dir.to_s }] - end + ["git", "-C", dir.to_s, *cmd] end def extra_clone_args @@ -451,10 +447,6 @@ def full_clone? depth.nil? end - def supports_minus_c? - @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5") - end - def needs_allow_any_sha1_in_want? @needs_allow_any_sha1_in_want ||= Gem::Version.new(version) <= Gem::Version.new("2.13.7") end From d17d49d4aa81ea7a134629aefd1efa5e9c7a6d1a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 27 Oct 2025 15:18:58 +0900 Subject: [PATCH 0541/2435] [ruby/rubygems] bundler_4_mode always return true https://github.com/ruby/rubygems/commit/b2e1810067 --- lib/bundler/cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index b8a8be82c18530..af634291dd4737 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -492,7 +492,7 @@ def version build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.bundler_4_mode? + if !cli_help Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" From d0a6780d1ec267e382053eae46ccf681f13dd50f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 27 Oct 2025 16:52:28 +0900 Subject: [PATCH 0542/2435] [ruby/stringio] [DOC] Split the examples `StringIO` into the document file https://github.com/ruby/stringio/commit/04ba28af00 --- doc/stringio/stringio.md | 26 ++++++++++++++++++++++++++ ext/stringio/stringio.c | 28 +--------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 doc/stringio/stringio.md diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md new file mode 100644 index 00000000000000..345fc5f20707df --- /dev/null +++ b/doc/stringio/stringio.md @@ -0,0 +1,26 @@ +\IO streams for strings, with access similar to +{IO}[rdoc-ref:IO]; +see {IO}[rdoc-ref:IO]. + +### About the Examples + +Examples on this page assume that \StringIO has been required: + +``` +require 'stringio' +``` + +And that these constants have been defined: + +``` +TEXT = < Date: Mon, 27 Oct 2025 09:52:58 +0100 Subject: [PATCH 0543/2435] [ruby/json] Use locale indepenent version of `islapha` https://github.com/ruby/json/commit/1ba1e9bef9 --- ext/json/parser/parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 297031dcf17bb6..2522c562248a02 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -124,7 +124,7 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon return Qfalse; } - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { + if (RB_UNLIKELY(!rb_isalpha((unsigned char)str[0]))) { // Simple heuristic, if the first character isn't a letter, // we're much less likely to see this string again. // We mostly want to cache strings that are likely to be repeated. @@ -176,7 +176,7 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon return Qfalse; } - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { + if (RB_UNLIKELY(!rb_isalpha((unsigned char)str[0]))) { // Simple heuristic, if the first character isn't a letter, // we're much less likely to see this string again. // We mostly want to cache strings that are likely to be repeated. From 2a9d15b9699d15fe93249d9551a34de77908e5f0 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 27 Oct 2025 10:22:04 +0100 Subject: [PATCH 0544/2435] [ruby/json] parser.c: Fix indentation in json_decode_integer https://github.com/ruby/json/commit/f228b30635 --- ext/json/parser/parser.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 2522c562248a02..538cddb6d66aec 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -790,11 +790,11 @@ static VALUE json_decode_large_integer(const char *start, long len) static inline VALUE json_decode_integer(const char *start, const char *end) { - long len = end - start; - if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { - return fast_decode_integer(start, end); - } - return json_decode_large_integer(start, len); + long len = end - start; + if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { + return fast_decode_integer(start, end); + } + return json_decode_large_integer(start, len); } static VALUE json_decode_large_float(const char *start, long len) From 308fb9c8b46f35cacfe7c62cd66776183cbdae1a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 27 Oct 2025 20:47:24 +0900 Subject: [PATCH 0545/2435] Fix a comment [ci skip] --- tool/m4/ruby_append_option.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/m4/ruby_append_option.m4 b/tool/m4/ruby_append_option.m4 index 98359fa1f95f52..8cd2741ae89de6 100644 --- a/tool/m4/ruby_append_option.m4 +++ b/tool/m4/ruby_append_option.m4 @@ -4,6 +4,6 @@ AC_DEFUN([RUBY_APPEND_OPTION], AS_CASE([" [$]{$1-} "], [*" $2 "*], [], [' '], [ $1="$2"], [ $1="[$]$1 $2"])])dnl AC_DEFUN([RUBY_PREPEND_OPTION], - [# RUBY_APPEND_OPTION($1) + [# RUBY_PREPEND_OPTION($1) AS_CASE([" [$]{$1-} "], [*" $2 "*], [], [' '], [ $1="$2"], [ $1="$2 [$]$1"])])dnl From 0b0da6c4b26f80ad6985722d3fc0f5cdee09125d Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:17:36 +0100 Subject: [PATCH 0546/2435] Correctly compile splats in for-loop index in prism Fixes [Bug #21648] This is a followup to https://github.com/ruby/ruby/pull/13597. The added test passed but didn't emit the same instructions. This also handles bare splats and aligns instructions for all cases --- prism_compile.c | 10 ++++++++-- test/ruby/test_compile_prism.rb | 15 +++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 6b4e32f629a6a3..811ce4781696e7 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -5338,8 +5338,7 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c case PM_INSTANCE_VARIABLE_TARGET_NODE: case PM_CONSTANT_PATH_TARGET_NODE: case PM_CALL_TARGET_NODE: - case PM_INDEX_TARGET_NODE: - case PM_SPLAT_NODE: { + case PM_INDEX_TARGET_NODE: { // For other targets, we need to potentially compile the parent or // owning expression of this target, then retrieve the value, expand it, // and then compile the necessary writes. @@ -5359,6 +5358,7 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c pm_multi_target_state_update(&state); break; } + case PM_SPLAT_NODE: case PM_MULTI_TARGET_NODE: { DECL_ANCHOR(writes); DECL_ANCHOR(cleanup); @@ -5394,6 +5394,12 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c PUSH_INSN(ret, location, pop); PUSH_LABEL(ret, not_single); + + if (PM_NODE_TYPE_P(node, PM_SPLAT_NODE)) { + const pm_splat_node_t *cast = (const pm_splat_node_t *) node; + PUSH_INSN2(ret, location, expandarray, INT2FIX(0), INT2FIX(cast->expression == NULL ? 0 : 1)); + } + PUSH_SEQ(ret, writes); PUSH_SEQ(ret, cleanup); break; diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index b95add5bd45fe4..76b961b37efd57 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -1046,16 +1046,19 @@ def o.bar = @ret.length < 3 end def test_ForNode - assert_prism_eval("for i in [1,2] do; i; end") - assert_prism_eval("for @i in [1,2] do; @i; end") - assert_prism_eval("for $i in [1,2] do; $i; end") + assert_prism_eval("r = []; for i in [1,2] do; r << i; end; r") + assert_prism_eval("r = []; for @i in [1,2] do; r << @i; end; r") + assert_prism_eval("r = []; for $i in [1,2] do; r << $i; end; r") - assert_prism_eval("for foo, in [1,2,3] do end") + assert_prism_eval("r = []; for foo, in [1,2,3] do r << foo end; r") - assert_prism_eval("for i, j in {a: 'b'} do; i; j; end") + assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r") # Test splat node as index in for loop - assert_prism_eval("for *x in [[1,2], [3,4]] do; x; end") + assert_prism_eval("r = []; for *x in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for * in [[1,2], [3,4]] do; r << 'ok'; end; r") + assert_prism_eval("r = []; for x, * in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for x, *y in [[1,2], [3,4]] do; r << [x, y]; end; r") end ############################################################################ From 74c3bd7718319868c89a6d2a5827802d2be2d272 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 27 Oct 2025 12:26:03 -0700 Subject: [PATCH 0547/2435] ZJIT: Remove a duplicated annotation (#14968) --- zjit/src/cruby_methods.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index b6b91ab0a6db7a..09d52b9f49c74a 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -189,7 +189,6 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); - annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); From 1d897f5628ba9c2e64f2455affbd9c2fab79e99b Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:26:59 -0400 Subject: [PATCH 0548/2435] ZJIT: Elide unnecessary return statements --- zjit/src/backend/tests.rs | 4 ++-- zjit/src/cruby_methods.rs | 3 +-- zjit/src/hir.rs | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 1b72777212cb23..b21e1586d5ccc2 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -63,10 +63,10 @@ fn test_alloc_regs() { } fn setup_asm() -> (Assembler, CodeBlock) { - return ( + ( Assembler::new(), CodeBlock::new_dummy() - ); + ) } // Test full codegen pipeline diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 09d52b9f49c74a..720683f407a8b1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -255,8 +255,7 @@ fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hi fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[] = args else { return None; }; // TODO(max): In local iseq types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. - let result = fun.push_insn(block, hir::Insn::IsBlockGiven); - return Some(result); + Some(fun.push_insn(block, hir::Insn::IsBlockGiven)) } fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b358ac5d51ca4d..1b4dc5b011e7bd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1428,7 +1428,7 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: // TODO(max): Support only_kwparam case where the local_idx is a positional parameter - return None; + None } YARVINSN_putnil => Some(IseqReturn::Value(Qnil)), YARVINSN_putobject => Some(IseqReturn::Value(unsafe { *rb_iseq_pc_at_idx(iseq, 1) })), @@ -2618,16 +2618,16 @@ impl Function { blockiseq, }); fun.make_equal_to(send_insn_id, ccall); - return Ok(()); + Ok(()) } // Variadic method -1 => { // func(int argc, VALUE *argv, VALUE recv) - return Err(()); + Err(()) } -2 => { // (self, args_ruby_array) - return Err(()); + Err(()) } _ => unreachable!("unknown cfunc kind: argc={argc}") } From a395d9c036e998217af4cfeed73550fc1c0275c8 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:27:55 -0400 Subject: [PATCH 0549/2435] ZJIT: Simplify complex type to BranchEncoder --- zjit/src/asm/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index 32dc633a2941ce..dca2b7b0cf018a 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -19,6 +19,9 @@ pub mod arm64; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Label(pub usize); +/// The object that knows how to encode the branch instruction. +type BranchEncoder = Box; + /// Reference to an ASM label pub struct LabelRef { // Position in the code block where the label reference exists @@ -33,7 +36,7 @@ pub struct LabelRef { num_bytes: usize, /// The object that knows how to encode the branch instruction. - encode: Box, + encode: BranchEncoder, } /// Block of memory into which instructions can be assembled From 5c4d76c93a817df3e8acfdabad535bfa95671158 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:28:16 -0400 Subject: [PATCH 0550/2435] ZJIT: Remove unnecessary #[test] annotation --- zjit/src/backend/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index b21e1586d5ccc2..02635e1066fca5 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -1,4 +1,3 @@ -#![cfg(test)] use crate::asm::CodeBlock; use crate::backend::lir::*; use crate::cruby::*; From c112368e3013fc1d27f217f40714a250019befa7 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:28:31 -0400 Subject: [PATCH 0551/2435] ZJIT: Use std::ptr::null instead of casts --- zjit/src/backend/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 02635e1066fca5..52547cb31fde65 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -197,8 +197,8 @@ fn test_c_call() #[test] fn test_alloc_ccall_regs() { let mut asm = Assembler::new(); - let out1 = asm.ccall(0 as *const u8, vec![]); - let out2 = asm.ccall(0 as *const u8, vec![out1]); + let out1 = asm.ccall(std::ptr::null::(), vec![]); + let out2 = asm.ccall(std::ptr::null::(), vec![out1]); asm.mov(EC, out2); let mut cb = CodeBlock::new_dummy(); asm.compile_with_regs(&mut cb, Assembler::get_alloc_regs()).unwrap(); From 79db7d5204c7177a80a6df6eb9b7c47866c0fa17 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:11 -0400 Subject: [PATCH 0552/2435] ZJIT: Remove unnecessary `as` casts --- zjit/src/hir.rs | 2 +- zjit/src/hir_type/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1b4dc5b011e7bd..4eba78b7523620 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2026,7 +2026,7 @@ impl Function { return true; } let frame_state = self.frame_state(state); - let iseq_insn_idx = frame_state.insn_idx as usize; + let iseq_insn_idx = frame_state.insn_idx; let Some(profiled_type) = self.profiled_type_of_at(val, iseq_insn_idx) else { return false; }; diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 7e6da62fd0ff44..7c10ef4425c6fd 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -252,7 +252,7 @@ impl Type { Const::CInt8(v) => Self::from_cint(types::CInt8, v as i64), Const::CInt16(v) => Self::from_cint(types::CInt16, v as i64), Const::CInt32(v) => Self::from_cint(types::CInt32, v as i64), - Const::CInt64(v) => Self::from_cint(types::CInt64, v as i64), + Const::CInt64(v) => Self::from_cint(types::CInt64, v), Const::CUInt8(v) => Self::from_cint(types::CUInt8, v as i64), Const::CUInt16(v) => Self::from_cint(types::CUInt16, v as i64), Const::CUInt32(v) => Self::from_cint(types::CUInt32, v as i64), From aabec60c2e4fd96ceb8239b45ed2388309239742 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:23 -0400 Subject: [PATCH 0553/2435] ZJIT: Elide unnecessary 'static annotation --- zjit/src/hir_type/hir_type.inc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index bad45a737644fb..a330440612a739 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -70,7 +70,7 @@ mod bits { pub const Symbol: u64 = DynamicSymbol | StaticSymbol; pub const TrueClass: u64 = 1u64 << 42; pub const Undef: u64 = 1u64 << 43; - pub const AllBitPatterns: [(&'static str, u64); 70] = [ + pub const AllBitPatterns: [(&str, u64); 70] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), From fa0eab2848f6ed73f34bf5d1d7e0fe6bf4e2f6e4 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:41 -0400 Subject: [PATCH 0554/2435] ZJIT: Use .first() in lieu of .get(0) --- zjit/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e16588e3e33bbd..1179b9bf16924b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2057,7 +2057,7 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result // We currently don't support JIT-to-JIT calls for ISEQs with optional arguments. // So we only need to use jit_entry_ptrs[0] for now. TODO(Shopify/ruby#817): Support optional arguments. - let Some(&jit_entry_ptr) = jit_entry_ptrs.get(0) else { + let Some(&jit_entry_ptr) = jit_entry_ptrs.first() else { return Err(CompileError::JitToJitOptional) }; From ac57a5c43ea6b12350eb7efbee50d6682021dc6c Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:52 -0400 Subject: [PATCH 0555/2435] ZJIT: Use .is_empty() for clarity --- zjit/src/cruby_methods.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 720683f407a8b1..9297327d739c24 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -237,7 +237,7 @@ fn no_inline(_fun: &mut hir::Function, _block: hir::BlockId, _recv: hir::InsnId, } fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { - if args.len() == 0 && fun.likely_a(recv, types::StringExact, state) { + if args.is_empty() && fun.likely_a(recv, types::StringExact, state) { let recv = fun.coerce_to(block, recv, types::StringExact, state); return Some(recv); } @@ -245,7 +245,7 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I } fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { - if args.len() == 0 { + if args.is_empty() { // No need to coerce the receiver; that is done by the SendWithoutBlock rewriting. return Some(recv); } From a12aa2b5aa0598ccf5bb9ff0f9b5dfde8a873be5 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:30:31 -0400 Subject: [PATCH 0556/2435] ZJIT: Since Param is unit struct, elide destructuring --- zjit/src/hir.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4eba78b7523620..61014d5fd4f59b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -841,7 +841,7 @@ impl Insn { fn has_effects(&self) -> bool { match self { Insn::Const { .. } => false, - Insn::Param { .. } => false, + Insn::Param => false, Insn::StringCopy { .. } => false, Insn::NewArray { .. } => false, // NewHash's operands may be hashed and compared for equality, which could have @@ -1469,7 +1469,7 @@ impl Function { // Add an instruction to an SSA block pub fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { - let is_param = matches!(insn, Insn::Param { .. }); + let is_param = matches!(insn, Insn::Param); let id = self.new_insn(insn); if is_param { self.blocks[block.0].params.push(id); @@ -1576,7 +1576,7 @@ impl Function { use Insn::*; match &self.insns[insn_id.0] { result@(Const {..} - | Param {..} + | Param | GetConstantPath {..} | IsBlockGiven | PatchPoint {..} @@ -1771,7 +1771,7 @@ impl Function { fn infer_type(&self, insn: InsnId) -> Type { assert!(self.insns[insn.0].has_output()); match &self.insns[insn.0] { - Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), + Insn::Param => unimplemented!("params should not be present in block.insns"), Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::EntryPoint { .. } | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } @@ -1847,7 +1847,7 @@ impl Function { Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, - Insn::IsBlockGiven { .. } => types::BoolExact, + Insn::IsBlockGiven => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, @@ -3024,7 +3024,7 @@ impl Function { fn worklist_traverse_single_insn(&self, insn: &Insn, worklist: &mut VecDeque) { match insn { &Insn::Const { .. } - | &Insn::Param { .. } + | &Insn::Param | &Insn::EntryPoint { .. } | &Insn::LoadPC | &Insn::LoadSelf From 68d9f7c3e6da11690ead9b525e08a8f2c4afc4be Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 27 Oct 2025 15:01:01 -0400 Subject: [PATCH 0557/2435] ZJIT: Remove unnecessary 'static annotation from gen_hir_type.rb --- zjit/src/hir_type/gen_hir_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 653efa6f8f2544..4e0ecc718f8f82 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -190,7 +190,7 @@ def add_union name, type_names subtypes = $bits[type_name].join(" | ") puts " pub const #{type_name}: u64 = #{subtypes};" } -puts " pub const AllBitPatterns: [(&'static str, u64); #{$bits.size}] = [" +puts " pub const AllBitPatterns: [(&str, u64); #{$bits.size}] = [" # Sort the bit patterns by decreasing value so that we can print the densest # possible to-string representation of a Type. For example, CSigned instead of # CInt8|CInt16|... From d97fb3b424cebb812012a4e8a497a88510be9b72 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 27 Oct 2025 16:45:50 -0400 Subject: [PATCH 0558/2435] ZJIT: Print out full path to --zjit-trace-exits output (#14966) * ZJIT: Print out full path to --zjit-trace-exits output This helps with any `chdir`-related issues. * Don't include dot Co-authored-by: Takashi Kokubun --------- Co-authored-by: Takashi Kokubun --- zjit.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index 8b44330b36043a..9ea83fe10f716f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -283,6 +283,7 @@ def dump_locations # :nodoc: filename = "zjit_exits_#{Process.pid}.dump" n_bytes = dump_exit_locations(filename) - $stderr.puts("#{n_bytes} bytes written to #{filename}.") + absolute_filename = File.expand_path(filename) + $stderr.puts("#{n_bytes} bytes written to #{absolute_filename}") end end From 8d45e1f34e9a169987587d99a837b4ee035d7000 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 27 Oct 2025 18:53:28 -0400 Subject: [PATCH 0559/2435] ZJIT: Fix internal compiler error looking up profiles for trace_getinstancevariable (#14969) We treat getinstancevariable differently from other opcodes: it does not look at the stack for its self operand, but instead looks at `cfp->self`. In some cases, we might see the `trace_` variant in the front-end, so make sure we treat that the same. Example repro: ``` def test @foo end 28.times do test end trace = TracePoint.trace(:call) do |tp| puts tp.method_id end trace.enable do 30.times do test end end ``` --- zjit/src/hir.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 61014d5fd4f59b..e948ee4452ac67 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4021,7 +4021,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { .try_into() .unwrap(); - if opcode == YARVINSN_getinstancevariable { + // If TracePoint has been enabled after we have collected profiles, we'll see + // trace_getinstancevariable in the ISEQ. We have to treat it like getinstancevariable + // for profiling purposes: there is no operand on the stack to look up; we have + // profiled cfp->self. + if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable { profiles.profile_self(&exit_state, self_param); } else { profiles.profile_stack(&exit_state); @@ -7408,6 +7412,29 @@ mod tests { "); } + #[test] + fn test_trace_getinstancevariable() { + eval(" + def test = @foo + test + trace = TracePoint.trace(:call) { |tp| } + trace.enable { test } + "); + assert_contains_opcode("test", YARVINSN_trace_getinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit UnhandledYARVInsn(trace_getinstancevariable) + "); + } + #[test] fn test_setinstancevariable() { eval(" From 3fb96ee93bb8e541fb510bd2a1a454f82160ba96 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:41:08 -0700 Subject: [PATCH 0560/2435] ZJIT: Inline method calls to ISEQs that just do leaf Primitive calls --- zjit/src/hir.rs | 95 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e948ee4452ac67..706982625ade9d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1379,6 +1379,7 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, + InvokeLeafBuiltin(rb_builtin_function), } unsafe extern "C" { @@ -1390,10 +1391,6 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: // Expect only two instructions and one possible operand // NOTE: If an ISEQ has an optional keyword parameter with a default value that requires // computation, the ISEQ will always have more than two instructions and won't be inlined. - let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; - if !(2..=3).contains(&iseq_size) { - return None; - } // Get the first two instructions let first_insn = iseq_opcode_at_idx(iseq, 0); @@ -1436,6 +1433,16 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: YARVINSN_putobject_INT2FIX_1_ => Some(IseqReturn::Value(VALUE::fixnum_from_usize(1))), // We don't support invokeblock for now. Such ISEQs are likely not used by blocks anyway. YARVINSN_putself if captured_opnd.is_none() => Some(IseqReturn::Receiver), + YARVINSN_opt_invokebuiltin_delegate_leave => { + let pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; + let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() }; + let argc = bf.argc as usize; + if argc != 0 { return None; } + let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; + let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; + if !leaf { return None; } + Some(IseqReturn::InvokeLeafBuiltin(bf)) + } _ => None, } } @@ -2437,7 +2444,7 @@ impl Function { for insn_id in old_insns { match self.find(insn_id) { // Reject block ISEQs to avoid autosplat and other block parameter complications. - Insn::SendWithoutBlockDirect { recv, iseq, cd, args, .. } => { + Insn::SendWithoutBlockDirect { recv, iseq, cd, args, state, .. } => { let call_info = unsafe { (*cd).ci }; let ci_flags = unsafe { vm_ci_flag(call_info) }; // .send call is not currently supported for builtins @@ -2461,6 +2468,17 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); self.make_equal_to(insn_id, recv); } + IseqReturn::InvokeLeafBuiltin(bf) => { + self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); + let replacement = self.push_insn(block, Insn::InvokeBuiltin { + bf, + args: vec![recv], + state, + leaf: true, + return_type: None, + }); + self.make_equal_to(insn_id, replacement); + } } } _ => { self.push_insn_id(block, insn_id); } @@ -10955,9 +10973,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): v13:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) - v22:BasicObject = SendWithoutBlockDirect v13, :zero? (0x1038) + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi285, v13 CheckInterrupts - Return v22 + Return v24 "); } @@ -10986,9 +11005,10 @@ mod opt_tests { v18:ArrayExact = ArrayDup v16 PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v30:BasicObject = SendWithoutBlockDirect v18, :first (0x1040) + IncrCounter inline_iseq_optimized_send_count + v32:BasicObject = InvokeBuiltin leaf _bi132, v18 CheckInterrupts - Return v30 + Return v32 "); } @@ -11015,9 +11035,10 @@ mod opt_tests { v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) - v24:BasicObject = SendWithoutBlockDirect v21, :class (0x1048) + IncrCounter inline_iseq_optimized_send_count + v26:BasicObject = InvokeBuiltin leaf _bi20, v21 CheckInterrupts - Return v24 + Return v26 "); } @@ -15345,6 +15366,58 @@ mod opt_tests { "); } + #[test] + fn test_inline_symbol_name() { + eval(" + def test(x) = x.to_s + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_inline_symbol_to_s() { + eval(" + def test(x) = x.to_s + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_optimize_stringexact_eq_stringexact() { eval(r#" From 46525fa7b8c91d05a24571337eff76f1023cb152 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:53:06 -0700 Subject: [PATCH 0561/2435] ZJIT: Add return_type to inlined InvokeBuiltin --- zjit/src/hir.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 706982625ade9d..8074a50e2b66a5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1379,7 +1379,8 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, - InvokeLeafBuiltin(rb_builtin_function), + // Builtin descriptor and return type (if known) + InvokeLeafBuiltin(rb_builtin_function, Option), } unsafe extern "C" { @@ -1441,7 +1442,11 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; if !leaf { return None; } - Some(IseqReturn::InvokeLeafBuiltin(bf)) + // Check if this builtin is annotated + let return_type = ZJITState::get_method_annotations() + .get_builtin_properties(&bf) + .map(|props| props.return_type); + Some(IseqReturn::InvokeLeafBuiltin(bf, return_type)) } _ => None, } @@ -2468,14 +2473,14 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); self.make_equal_to(insn_id, recv); } - IseqReturn::InvokeLeafBuiltin(bf) => { + IseqReturn::InvokeLeafBuiltin(bf, return_type) => { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); let replacement = self.push_insn(block, Insn::InvokeBuiltin { bf, args: vec![recv], state, leaf: true, - return_type: None, + return_type, }); self.make_equal_to(insn_id, replacement); } @@ -11036,7 +11041,7 @@ mod opt_tests { PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_iseq_optimized_send_count - v26:BasicObject = InvokeBuiltin leaf _bi20, v21 + v26:Class = InvokeBuiltin leaf _bi20, v21 CheckInterrupts Return v26 "); From c3c254439f06073a9c7e167f89e1c1e97629d947 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:54:15 -0700 Subject: [PATCH 0562/2435] ZJIT: Annotate Symbol#to_s and Symbol#name as returning StringExact --- zjit/src/cruby_methods.rs | 2 ++ zjit/src/hir.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 9297327d739c24..6a16ae3e464d65 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,8 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + annotate_builtin!(rb_cSymbol, "name", types::StringExact); + annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); Annotations { cfuncs: std::mem::take(cfuncs), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8074a50e2b66a5..776b1582a75022 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -8185,9 +8185,9 @@ mod tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin leaf _bi28, v6 + v11:StringExact = InvokeBuiltin leaf _bi28, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): + bb3(v13:BasicObject, v14:StringExact): CheckInterrupts Return v14 "); @@ -8207,9 +8207,9 @@ mod tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin leaf _bi12, v6 + v11:StringExact = InvokeBuiltin leaf _bi12, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): + bb3(v13:BasicObject, v14:StringExact): CheckInterrupts Return v14 "); @@ -15391,7 +15391,7 @@ mod opt_tests { PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) v21:StaticSymbol = GuardType v9, StaticSymbol IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + v24:StringExact = InvokeBuiltin leaf _bi12, v21 CheckInterrupts Return v24 "); @@ -15417,7 +15417,7 @@ mod opt_tests { PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) v21:StaticSymbol = GuardType v9, StaticSymbol IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + v24:StringExact = InvokeBuiltin leaf _bi12, v21 CheckInterrupts Return v24 "); From e5e32acc7ec020e3fd7a7dff76e19a6f39ffb3ab Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 16:12:24 -0700 Subject: [PATCH 0563/2435] ZJIT: Annotate Kernel#frozen? as returning BoolExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 6a16ae3e464d65..dd33bb206a5bb3 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,7 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 776b1582a75022..e0e682ccb2fc71 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -15346,6 +15346,32 @@ mod opt_tests { "); } + #[test] + fn test_inline_kernel_frozen_p() { + eval(r#" + def test(o) = o.frozen? + test :foo + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, frozen?@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BoolExact = InvokeBuiltin leaf _bi69, v21 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_inline_integer_to_i() { eval(r#" From bf2663ce0645dcf5b375829d31e755e13da9852e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 26 Oct 2025 21:35:07 +0100 Subject: [PATCH 0564/2435] [DOC] Tweaks for String#sum --- doc/string/sum.rdoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/string/sum.rdoc b/doc/string/sum.rdoc index 5de24e6402f692..125611411e34ab 100644 --- a/doc/string/sum.rdoc +++ b/doc/string/sum.rdoc @@ -1,4 +1,4 @@ -Returns a basic +n+-bit checksum of the characters in +self+; +Returns a basic +n+-bit {checksum}[https://en.wikipedia.org/wiki/Checksum] of the characters in +self+; the checksum is the sum of the binary value of each byte in +self+, modulo 2**n - 1: @@ -9,3 +9,5 @@ modulo 2**n - 1: 'こんにちは'.sum # => 2582 This is not a particularly strong checksum. + +Related: see {Querying}[rdoc-ref:String@Querying]. From e3c4298d404d6a2cdfca006bf71f83360f24a5b0 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 26 Oct 2025 21:59:16 +0100 Subject: [PATCH 0565/2435] [DOC] Tweaks for String#swapcase! --- string.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/string.c b/string.c index b4585fd3205913..01af7f66351584 100644 --- a/string.c +++ b/string.c @@ -6190,7 +6190,7 @@ rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) * * Like String#sub, except that: * - * - Changed are made to +self+, not to copy of +self+. + * - Changes are made to +self+, not to copy of +self+. * - Returns +self+ if any changes are made, +nil+ otherwise. * * Related: see {Modifying}[rdoc-ref:String@Modifying]. @@ -8203,20 +8203,12 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) * call-seq: * swapcase!(mapping) -> self or nil * - * Upcases each lowercase character in +self+; - * downcases uppercase character; - * returns +self+ if any changes were made, +nil+ otherwise: - * - * s = 'Hello World!' # => "Hello World!" - * s.swapcase! # => "hELLO wORLD!" - * s # => "hELLO wORLD!" - * ''.swapcase! # => nil - * - * The casing may be affected by the given +mapping+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + * Like String#swapcase, except that: * - * Related: String#swapcase. + * - Changes are made to +self+, not to copy of +self+. + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From 836fc00e191c372d858aefd16c9f5836d1c7dc9e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 26 Oct 2025 21:49:05 +0100 Subject: [PATCH 0566/2435] [DOC] Tweaks for String#swapcase --- doc/string/swapcase.rdoc | 19 +++++++++++++++++++ string.c | 14 ++------------ 2 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 doc/string/swapcase.rdoc diff --git a/doc/string/swapcase.rdoc b/doc/string/swapcase.rdoc new file mode 100644 index 00000000000000..93859ec8237fcc --- /dev/null +++ b/doc/string/swapcase.rdoc @@ -0,0 +1,19 @@ +Returns a string containing the characters in +self+, with cases reversed: + +- Each uppercase character is downcased. +- Each lowercase character is upcased. + +Examples: + + 'Hello World!'.swapcase # => "hELLO wORLD!" + 'тест'.swapcase # => "ТЕСТ" + +Some characters (and even character sets) do not have casing: + + '12345'.swapcase # => "12345" + 'こんにちは'.swapcase # => "こんにちは" + +The casing may be affected by the given +mapping+; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 01af7f66351584..fa6ce7f3ec1d07 100644 --- a/string.c +++ b/string.c @@ -8232,19 +8232,9 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(mapping) -> string + * swapcase(mapping) -> new_string * - * Returns a string containing the characters in +self+, with cases reversed; - * each uppercase character is downcased; - * each lowercase character is upcased: - * - * s = 'Hello World!' # => "Hello World!" - * s.swapcase # => "hELLO wORLD!" - * - * The casing may be affected by the given +mapping+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#swapcase!. + * :include: doc/string/swapcase.rdoc * */ From 9fd32ee414cf8d697839c129052836e74176c40b Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Oct 2025 22:06:07 -0500 Subject: [PATCH 0567/2435] [ruby/stringio] [DOC] Doc for StringIO#each_char (https://github.com/ruby/stringio/pull/158) https://github.com/ruby/stringio/commit/ec6bf815ae Co-authored-by: Sutou Kouhei --- ext/stringio/stringio.c | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index b6ab8cd6cfdeb7..95736fe3852f05 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1162,12 +1162,44 @@ strio_readbyte(VALUE self) /* * call-seq: - * each_char {|c| ... } -> self + * each_char {|char| ... } -> self * * With a block given, calls the block with each remaining character in the stream; - * see {Character IO}[rdoc-ref:IO@Character+IO]. - * - * With no block given, returns an enumerator. + * positions the stream at end-of-file; + * returns +self+: + * + * chars = [] + * strio = StringIO.new('hello') + * strio.each_char {|char| chars.push(char) } + * strio.eof? # => true + * chars # => ["h", "e", "l", "l", "o"] + * chars = [] + * strio = StringIO.new('тест') + * strio.each_char {|char| chars.push(char) } + * chars # => ["т", "е", "с", "т"] + * chars = [] + * strio = StringIO.new('こんにちは') + * strio.each_char {|char| chars.push(char) } + * chars # => ["こ", "ん", "に", "ち", "は"] + * + * Stream position matters: + * + * chars = [] + * strio = StringIO.new('こんにちは') + * strio.getc # => "こ" + * strio.pos # => 3 # 3-byte character was read. + * strio.each_char {|char| chars.push(char) } + * chars # => ["ん", "に", "ち", "は"] + * + * When at end-of-stream does not call the block: + * + * strio.eof? # => true + * strio.each_char {|char| fail 'Boo!' } + * strio.eof? # => true + * + * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * + * Related: StringIO#each_byte, StringIO#each_codepoint, StringIO#each_line. */ static VALUE strio_each_char(VALUE self) From 218c2805f94dd986108556ccbb6c219969418377 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Oct 2025 22:09:13 -0500 Subject: [PATCH 0568/2435] [ruby/stringio] [DOC] Doc for StringIO#each_codepoint (https://github.com/ruby/stringio/pull/159) https://github.com/ruby/stringio/commit/6628d4837b Co-authored-by: Sutou Kouhei --- ext/stringio/stringio.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 95736fe3852f05..d58acd114f6946 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1218,10 +1218,44 @@ strio_each_char(VALUE self) * call-seq: * each_codepoint {|codepoint| ... } -> self * - * With a block given, calls the block with each remaining codepoint in the stream; - * see {Codepoint IO}[rdoc-ref:IO@Codepoint+IO]. + * With a block given, calls the block with each successive codepoint from self; + * sets the position to end-of-stream; + * returns +self+. * - * With no block given, returns an enumerator. + * Each codepoint is the integer value for a character; returns self: + * + * codepoints = [] + * strio = StringIO.new('hello') + * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + * strio.eof? # => true + * codepoints # => [104, 101, 108, 108, 111] + * codepoints = [] + * strio = StringIO.new('тест') + * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + * codepoints # => [1090, 1077, 1089, 1090] + * codepoints = [] + * strio = StringIO.new('こんにちは') + * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + * codepoints # => [12371, 12435, 12395, 12385, 12399] + * + * Position in the stream matters: + * + * codepoints = [] + * strio = StringIO.new('こんにちは') + * strio.getc # => "こ" + * strio.pos # => 3 + * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + * codepoints # => [12435, 12395, 12385, 12399] + * + * When at end-of-stream, the block is not called: + * + * strio.eof? # => true + * strio.each_codepoint {|codepoint| fail 'Boo!' } + * strio.eof? # => true + * + * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * + * Related: StringIO#each_byte, StringIO#each_char, StringIO#each_line. */ static VALUE strio_each_codepoint(VALUE self) From f7eee3427dcc8b644b9fd6c854f3e176cfb8395e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 27 Oct 2025 20:48:29 -0700 Subject: [PATCH 0569/2435] [ruby/erb] Version 5.1.2 https://github.com/ruby/erb/commit/daa0e8712f --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index ceca10731e4ce4..adc06d92911ee4 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '5.1.1' + VERSION = '5.1.2' end From 4511e9621a11c99611638267923c4ff8de29410b Mon Sep 17 00:00:00 2001 From: git Date: Tue, 28 Oct 2025 03:50:50 +0000 Subject: [PATCH 0570/2435] Update default gems list at f7eee3427dcc8b644b9fd6c854f3e1 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f847fe6817821d..875ac2ecd374aa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,7 +182,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev -* erb 5.1.1 +* erb 5.1.2 * etc 1.4.6 * fcntl 1.3.0 * io-console 0.8.1 From b3191d204b8b4fe4b29cf73cd09a1b83e144f62d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 27 Oct 2025 20:57:18 -0700 Subject: [PATCH 0571/2435] [ruby/erb] Version 5.1.3 https://github.com/ruby/erb/commit/e8d382a83e --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index adc06d92911ee4..138bf3988206fc 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '5.1.2' + VERSION = '5.1.3' end From d864bd1a581417dd8ad4e2e89b80ea23e26ac74c Mon Sep 17 00:00:00 2001 From: git Date: Tue, 28 Oct 2025 03:58:48 +0000 Subject: [PATCH 0572/2435] Update default gems list at b3191d204b8b4fe4b29cf73cd09a1b [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 875ac2ecd374aa..047bdeeb3e235f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,7 +182,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev -* erb 5.1.2 +* erb 5.1.3 * etc 1.4.6 * fcntl 1.3.0 * io-console 0.8.1 From 02d53bab5675665951a6340328a09f914bf23f21 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Oct 2025 21:41:41 -0500 Subject: [PATCH 0573/2435] [ruby/stringio] [DOC] Doc for StringIO#each_byte (https://github.com/ruby/stringio/pull/157) https://github.com/ruby/stringio/commit/624ce56b4e Co-authored-by: Sutou Kouhei --- ext/stringio/stringio.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d58acd114f6946..146f9c0e7d3e71 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -937,9 +937,41 @@ strio_get_sync(VALUE self) * each_byte {|byte| ... } -> self * * With a block given, calls the block with each remaining byte in the stream; - * see {Byte IO}[rdoc-ref:IO@Byte+IO]. + * positions the stream at end-of-file; + * returns +self+: + * + * bytes = [] + * strio = StringIO.new('hello') # Five 1-byte characters. + * strio.each_byte {|byte| bytes.push(byte) } + * strio.eof? # => true + * bytes # => [104, 101, 108, 108, 111] + * bytes = [] + * strio = StringIO.new('тест') # Four 2-byte characters. + * strio.each_byte {|byte| bytes.push(byte) } + * bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + * bytes = [] + * strio = StringIO.new('こんにちは') # Five 3-byte characters. + * strio.each_byte {|byte| bytes.push(byte) } + * bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + * + * The position in the stream matters: + * + * bytes = [] + * strio = StringIO.new('こんにちは') + * strio.getc # => "こ" + * strio.pos # => 3 # 3-byte character was read. + * strio.each_byte {|byte| bytes.push(byte) } + * bytes # => [227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] * - * With no block given, returns an enumerator. + * If at end-of-file, does not call the block: + * + * strio.eof? # => true + * strio.each_byte {|byte| fail 'Boo!' } + * strio.eof? # => true + * + * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * + * Related: StringIO#each_char, StringIO#each_codepoint, StringIO#each_line. */ static VALUE strio_each_byte(VALUE self) @@ -1696,7 +1728,7 @@ strio_readline(int argc, VALUE *argv, VALUE self) * "Fifth line" * ``` * - * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. * * Related: StringIO.each_byte, StringIO.each_char, StringIO.each_codepoint. */ From 0f5c69b317b5ccf00739de77058b01d08cebeb3f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 28 Oct 2025 12:37:18 +0900 Subject: [PATCH 0574/2435] [DOC] Moved non ASCII documents to separated files C99 does not declare ways to designate the charset encoding of the source file. We can assume just US-ASCII characters will be safe. --- doc/stringio/each_byte.rdoc | 34 ++++++++++ doc/stringio/each_char.rdoc | 34 ++++++++++ doc/stringio/each_codepoint.rdoc | 36 +++++++++++ ext/stringio/stringio.c | 107 +------------------------------ 4 files changed, 107 insertions(+), 104 deletions(-) create mode 100644 doc/stringio/each_byte.rdoc create mode 100644 doc/stringio/each_char.rdoc create mode 100644 doc/stringio/each_codepoint.rdoc diff --git a/doc/stringio/each_byte.rdoc b/doc/stringio/each_byte.rdoc new file mode 100644 index 00000000000000..65e81c53a7cd0f --- /dev/null +++ b/doc/stringio/each_byte.rdoc @@ -0,0 +1,34 @@ +With a block given, calls the block with each remaining byte in the stream; +positions the stream at end-of-file; +returns +self+: + + bytes = [] + strio = StringIO.new('hello') # Five 1-byte characters. + strio.each_byte {|byte| bytes.push(byte) } + strio.eof? # => true + bytes # => [104, 101, 108, 108, 111] + bytes = [] + strio = StringIO.new('тест') # Four 2-byte characters. + strio.each_byte {|byte| bytes.push(byte) } + bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + bytes = [] + strio = StringIO.new('こんにちは') # Five 3-byte characters. + strio.each_byte {|byte| bytes.push(byte) } + bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + +The position in the stream matters: + + bytes = [] + strio = StringIO.new('こんにちは') + strio.getc # => "こ" + strio.pos # => 3 # 3-byte character was read. + strio.each_byte {|byte| bytes.push(byte) } + bytes # => [227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + +If at end-of-file, does not call the block: + + strio.eof? # => true + strio.each_byte {|byte| fail 'Boo!' } + strio.eof? # => true + +With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. diff --git a/doc/stringio/each_char.rdoc b/doc/stringio/each_char.rdoc new file mode 100644 index 00000000000000..d0b5e4082cc4d3 --- /dev/null +++ b/doc/stringio/each_char.rdoc @@ -0,0 +1,34 @@ +With a block given, calls the block with each remaining character in the stream; +positions the stream at end-of-file; +returns +self+: + + chars = [] + strio = StringIO.new('hello') + strio.each_char {|char| chars.push(char) } + strio.eof? # => true + chars # => ["h", "e", "l", "l", "o"] + chars = [] + strio = StringIO.new('тест') + strio.each_char {|char| chars.push(char) } + chars # => ["т", "е", "с", "т"] + chars = [] + strio = StringIO.new('こんにちは') + strio.each_char {|char| chars.push(char) } + chars # => ["こ", "ん", "に", "ち", "は"] + +Stream position matters: + + chars = [] + strio = StringIO.new('こんにちは') + strio.getc # => "こ" + strio.pos # => 3 # 3-byte character was read. + strio.each_char {|char| chars.push(char) } + chars # => ["ん", "に", "ち", "は"] + +When at end-of-stream does not call the block: + + strio.eof? # => true + strio.each_char {|char| fail 'Boo!' } + strio.eof? # => true + +With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. diff --git a/doc/stringio/each_codepoint.rdoc b/doc/stringio/each_codepoint.rdoc new file mode 100644 index 00000000000000..ede16de599cf0c --- /dev/null +++ b/doc/stringio/each_codepoint.rdoc @@ -0,0 +1,36 @@ +With a block given, calls the block with each successive codepoint from self; +sets the position to end-of-stream; +returns +self+. + +Each codepoint is the integer value for a character; returns self: + + codepoints = [] + strio = StringIO.new('hello') + strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + strio.eof? # => true + codepoints # => [104, 101, 108, 108, 111] + codepoints = [] + strio = StringIO.new('тест') + strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + codepoints # => [1090, 1077, 1089, 1090] + codepoints = [] + strio = StringIO.new('こんにちは') + strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + codepoints # => [12371, 12435, 12395, 12385, 12399] + +Position in the stream matters: + + codepoints = [] + strio = StringIO.new('こんにちは') + strio.getc # => "こ" + strio.pos # => 3 + strio.each_codepoint {|codepoint| codepoints.push(codepoint) } + codepoints # => [12435, 12395, 12385, 12399] + +When at end-of-stream, the block is not called: + + strio.eof? # => true + strio.each_codepoint {|codepoint| fail 'Boo!' } + strio.eof? # => true + +With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 146f9c0e7d3e71..b96010dfbf3f96 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -936,40 +936,7 @@ strio_get_sync(VALUE self) * call-seq: * each_byte {|byte| ... } -> self * - * With a block given, calls the block with each remaining byte in the stream; - * positions the stream at end-of-file; - * returns +self+: - * - * bytes = [] - * strio = StringIO.new('hello') # Five 1-byte characters. - * strio.each_byte {|byte| bytes.push(byte) } - * strio.eof? # => true - * bytes # => [104, 101, 108, 108, 111] - * bytes = [] - * strio = StringIO.new('тест') # Four 2-byte characters. - * strio.each_byte {|byte| bytes.push(byte) } - * bytes # => [209, 130, 208, 181, 209, 129, 209, 130] - * bytes = [] - * strio = StringIO.new('こんにちは') # Five 3-byte characters. - * strio.each_byte {|byte| bytes.push(byte) } - * bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] - * - * The position in the stream matters: - * - * bytes = [] - * strio = StringIO.new('こんにちは') - * strio.getc # => "こ" - * strio.pos # => 3 # 3-byte character was read. - * strio.each_byte {|byte| bytes.push(byte) } - * bytes # => [227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] - * - * If at end-of-file, does not call the block: - * - * strio.eof? # => true - * strio.each_byte {|byte| fail 'Boo!' } - * strio.eof? # => true - * - * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * :include: stringio/each_byte.rdoc * * Related: StringIO#each_char, StringIO#each_codepoint, StringIO#each_line. */ @@ -1196,40 +1163,7 @@ strio_readbyte(VALUE self) * call-seq: * each_char {|char| ... } -> self * - * With a block given, calls the block with each remaining character in the stream; - * positions the stream at end-of-file; - * returns +self+: - * - * chars = [] - * strio = StringIO.new('hello') - * strio.each_char {|char| chars.push(char) } - * strio.eof? # => true - * chars # => ["h", "e", "l", "l", "o"] - * chars = [] - * strio = StringIO.new('тест') - * strio.each_char {|char| chars.push(char) } - * chars # => ["т", "е", "с", "т"] - * chars = [] - * strio = StringIO.new('こんにちは') - * strio.each_char {|char| chars.push(char) } - * chars # => ["こ", "ん", "に", "ち", "は"] - * - * Stream position matters: - * - * chars = [] - * strio = StringIO.new('こんにちは') - * strio.getc # => "こ" - * strio.pos # => 3 # 3-byte character was read. - * strio.each_char {|char| chars.push(char) } - * chars # => ["ん", "に", "ち", "は"] - * - * When at end-of-stream does not call the block: - * - * strio.eof? # => true - * strio.each_char {|char| fail 'Boo!' } - * strio.eof? # => true - * - * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * :include: stringio/each_char.rdoc * * Related: StringIO#each_byte, StringIO#each_codepoint, StringIO#each_line. */ @@ -1250,42 +1184,7 @@ strio_each_char(VALUE self) * call-seq: * each_codepoint {|codepoint| ... } -> self * - * With a block given, calls the block with each successive codepoint from self; - * sets the position to end-of-stream; - * returns +self+. - * - * Each codepoint is the integer value for a character; returns self: - * - * codepoints = [] - * strio = StringIO.new('hello') - * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } - * strio.eof? # => true - * codepoints # => [104, 101, 108, 108, 111] - * codepoints = [] - * strio = StringIO.new('тест') - * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } - * codepoints # => [1090, 1077, 1089, 1090] - * codepoints = [] - * strio = StringIO.new('こんにちは') - * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } - * codepoints # => [12371, 12435, 12395, 12385, 12399] - * - * Position in the stream matters: - * - * codepoints = [] - * strio = StringIO.new('こんにちは') - * strio.getc # => "こ" - * strio.pos # => 3 - * strio.each_codepoint {|codepoint| codepoints.push(codepoint) } - * codepoints # => [12435, 12395, 12385, 12399] - * - * When at end-of-stream, the block is not called: - * - * strio.eof? # => true - * strio.each_codepoint {|codepoint| fail 'Boo!' } - * strio.eof? # => true - * - * With no block given, returns a new {Enumerator}[rdoc-ref:Enumerator]. + * :include: stringio/each_codepoint.rdoc * * Related: StringIO#each_byte, StringIO#each_char, StringIO#each_line. */ From d82a590a58c1a3898b997b9db939e7747a65a409 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 00:24:17 -0700 Subject: [PATCH 0575/2435] sync_default_gems.rb: Show `git diff` on failed sync --- tool/sync_default_gems.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 55e8492851d363..3cba7010df3c80 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -626,8 +626,9 @@ def pickup_commit(gem, sha, edit) # If the cherry-pick attempt failed, try to resolve conflicts. # Skip the commit, if it contains unresolved conflicts or no files to pick up. unless picked or resolve_conflicts(gem, sha, edit) - `git reset` && `git checkout .` && `git clean -fd` - return picked || nil # Fail unless cherry-picked + system(*%w"git --no-pager diff") if !picked && !edit # If failed, show `git diff` unless editing + `git reset` && `git checkout .` && `git clean -fd` # Clean up un-committed diffs + return picked || nil # Fail unless cherry-picked end # Commit cherry-picked commit From aab390aa5ac863a74f867fec93eebab087f11060 Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Tue, 28 Oct 2025 08:37:32 +0100 Subject: [PATCH 0576/2435] [ruby/json] Use Vector API in the Java Extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overview This PR uses the [jdk.incubator.vector module](https://docs.oracle.com/en/java/javase/20/docs/api/jdk.incubator.vector/jdk/incubator/vector/package-summary.html) as mentioned in [issue #739](https://github.com/ruby/json/issues/739) to accelerate generating JSON with the same algorithm as the C extension. The PR as it exists right now, it will attempt to build the `json.ext.VectorizedEscapeScanner` class with a target release of `16`. This is the first version of Java with support for the `jdk.incubator.vector` module. The remaining code is built for Java 1.8. The code will attempt to load the `json.ext.VectorizedEscapeScanner` only if the `json.enableVectorizedEscapeScanner` system property is set to `true` (or `1`). I'm not entirely sure how this is packaged / included with JRuby so I'd love @byroot and @headius's (and others?) thought about how to potential package and/or structure the JARs. I did consider adding the `json.ext.VectorizedEscapeScanner` to a separate `generator-vectorized.jar` but I thought I'd solicit feedback before spending any more time on the build / package process. Benchmarks Machine M1 Macbook Air Note: I've had trouble modifying the `compare.rb` I was using for the C extension to work reliability with the Java extension. I'll probably spend more time trying to get it to work, but as of right now these are pretty raw benchmarks. Below are two sample runs of the real-world benchmarks. The benchmarks are much more variable then the C extension for some reason. I'm not sure if HotSpot is doing something slightly different per execution. Vector API Enabled ``` scott@Scotts-MacBook-Air json % ONLY=json JAVA_OPTS='--add-modules jdk.incubator.vector -Djson.enableVectorizedEscapeScanner=true' ruby -I"lib" benchmark/encoder-realworld.rb WARNING: Using incubator modules: jdk.incubator.vector == Encoding activitypub.json (52595 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 1.384k i/100ms Calculating ------------------------------------- json 15.289k (± 0.8%) i/s (65.41 μs/i) - 153.624k in 10.048481s == Encoding citm_catalog.json (500298 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 76.000 i/100ms Calculating ------------------------------------- json 753.787 (± 3.6%) i/s (1.33 ms/i) - 7.524k in 9.997059s == Encoding twitter.json (466906 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 173.000 i/100ms Calculating ------------------------------------- json 1.751k (± 1.1%) i/s (571.24 μs/i) - 17.646k in 10.081260s == Encoding ohai.json (20147 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 2.390k i/100ms Calculating ------------------------------------- json 23.829k (± 0.8%) i/s (41.97 μs/i) - 239.000k in 10.030503s ``` Vector API Disabled ``` scott@Scotts-MacBook-Air json % ONLY=json JAVA_OPTS='--add-modules jdk.incubator.vector -Djson.enableVectorizedEscapeScanner=false' ruby -I"lib" benchmark/encoder-realworld.rb WARNING: Using incubator modules: jdk.incubator.vector VectorizedEscapeScanner disabled. == Encoding activitypub.json (52595 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 1.204k i/100ms Calculating ------------------------------------- json 12.937k (± 1.1%) i/s (77.30 μs/i) - 130.032k in 10.052234s == Encoding citm_catalog.json (500298 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 80.000 i/100ms Calculating ------------------------------------- json 817.378 (± 1.0%) i/s (1.22 ms/i) - 8.240k in 10.082058s == Encoding twitter.json (466906 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 147.000 i/100ms Calculating ------------------------------------- json 1.499k (± 1.3%) i/s (667.08 μs/i) - 14.994k in 10.004181s == Encoding ohai.json (20147 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 2.269k i/100ms Calculating ------------------------------------- json 22.366k (± 5.7%) i/s (44.71 μs/i) - 224.631k in 10.097069s ``` `master` as of commit `https://github.com/ruby/json/commit/c5af1b68c582` ``` scott@Scotts-MacBook-Air json % ONLY=json ruby -I"lib" benchmark/encoder-realworld.rb == Encoding activitypub.json (52595 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 886.000 i/100ms Calculating ------------------------------------- json^C% scott@Scotts-MacBook-Air json % ONLY=json ruby -I"lib" benchmark/encoder-realworld.rb == Encoding activitypub.json (52595 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 1.031k i/100ms Calculating ------------------------------------- json 10.812k (± 1.3%) i/s (92.49 μs/i) - 108.255k in 10.014260s == Encoding citm_catalog.json (500298 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 82.000 i/100ms Calculating ------------------------------------- json 824.921 (± 1.0%) i/s (1.21 ms/i) - 8.282k in 10.040787s == Encoding twitter.json (466906 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 141.000 i/100ms Calculating ------------------------------------- json 1.421k (± 0.7%) i/s (703.85 μs/i) - 14.241k in 10.023979s == Encoding ohai.json (20147 bytes) jruby 9.4.12.0 (3.1.4) 2025-02-11 https://github.com/ruby/json/commit/f4ab75096a Java HotSpot(TM) 64-Bit Server VM 21.0.7+8-LTS-245 on 21.0.7+8-LTS-245 +jit [arm64-darwin] Warming up -------------------------------------- json 2.274k i/100ms Calculating ------------------------------------- json 22.612k (± 0.9%) i/s (44.22 μs/i) - 227.400k in 10.057516s ``` Observations `activitypub.json` and `twitter.json` seem to be consistently faster with the Vector API enabled. `citm_catalog.json` seems consistently a bit slower and `ohai.json` is fairly close to even. https://github.com/ruby/json/commit/d40b2703a8 --- test/json/json_encoding_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb index 2789e94b5b3f2b..7ac06b2a7b0093 100644 --- a/test/json/json_encoding_test.rb +++ b/test/json/json_encoding_test.rb @@ -37,6 +37,10 @@ def test_generate_shared_string assert_equal '"234567890"', JSON.dump(s[2..-1]) s = '01234567890123456789"a"b"c"d"e"f"g"h' assert_equal '"\"a\"b\"c\"d\"e\"f\"g\""', JSON.dump(s[20, 15]) + s = "0123456789001234567890012345678900123456789001234567890" + assert_equal '"23456789001234567890012345678900123456789001234567890"', JSON.dump(s[2..-1]) + s = "0123456789001234567890012345678900123456789001234567890" + assert_equal '"567890012345678900123456789001234567890012345678"', JSON.dump(s[5..-3]) end def test_unicode From 7550f7e453713b776cf39d9ccd0154d9e33cf6f1 Mon Sep 17 00:00:00 2001 From: dysonreturns <22199434-dysonreturns@users.noreply.gitlab.com> Date: Tue, 21 Oct 2025 11:21:09 -0700 Subject: [PATCH 0577/2435] [ruby/rubygems] Update new gem CoC and prompt Prompt wording "prefer safe, respectful, productive, and collaborative spaces" is copied verbatim from Ruby Community Conduct Guideline. https://github.com/ruby/rubygems/commit/6cdf5f6d8a --- lib/bundler/cli/gem.rb | 8 +- .../templates/newgem/CODE_OF_CONDUCT.md.tt | 138 ++---------------- 2 files changed, 11 insertions(+), 135 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 56d23d9e9fc293..23b29bf36bee36 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -178,12 +178,8 @@ def run if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?", "Codes of conduct can increase contributions to your project by contributors who " \ - "prefer collaborative, safe spaces. You can read more about the code of conduct at " \ - "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \ - "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \ - "address is specified as a contact in the generated code of conduct so that people know " \ - "who to contact in case of a violation. For suggestions about " \ - "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.") + "prefer safe, respectful, productive, and collaborative spaces. \n" \ + "See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md") config[:coc] = true Bundler.ui.info "Code of conduct enabled in config" templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md") diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt index 67fe8cee798a7d..29e43a688ad049 100644 --- a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -1,132 +1,12 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct -## Our Pledge +These practices were derived from the Ruby Community Conduct Guideline. -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. +This document provides community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the project. +It applies to all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.). +Other communities may pick their own Code of Conduct. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations +* Participants will be tolerant of opposing views. +* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. +* When interpreting the words and actions of others, participants should always assume good intentions. +* Behaviour which can be reasonably considered harassment will not be tolerated. From a27f430de7fdf9b4ab9b158c44aa40b81bd705cd Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 23 Oct 2025 09:08:33 +0900 Subject: [PATCH 0578/2435] [ruby/rubygems] Make more shortly with https://rubyonrails.org/conduct https://github.com/ruby/rubygems/commit/62ba34d6c9 --- lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt index 29e43a688ad049..633baebdd558a9 100644 --- a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -1,12 +1,10 @@ # Code of Conduct -These practices were derived from the Ruby Community Conduct Guideline. - -This document provides community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the project. -It applies to all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.). -Other communities may pick their own Code of Conduct. +<%= config[:name].inspect %> follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.): * Participants will be tolerant of opposing views. * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. * When interpreting the words and actions of others, participants should always assume good intentions. * Behaviour which can be reasonably considered harassment will not be tolerated. + +If you have any concerns about behaviour within this project, please contact us at [<%= config[:email].inspect %>](mailto:<%= config[:email].inspect %>). From 523474bdfcae518ff74d2455f5199ea1df0af760 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 15:28:50 +0900 Subject: [PATCH 0579/2435] [ruby/rubygems] Deprecate --default option https://github.com/ruby/rubygems/commit/55745ee0f8 --- lib/rubygems/install_update_options.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 0d0f0dc211ef1c..57c41b75861c1c 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -158,7 +158,7 @@ def add_install_update_options options[:without_groups].concat v.map(&:intern) end - add_option(:"Install/Update", "--default", + add_option(:Deprecated, "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| options[:install_as_default] = v From d67aba8a5d81383880e05504de9a52ab6b2a015e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 15:38:00 +0900 Subject: [PATCH 0580/2435] [ruby/rubygems] Completely removed install_as_default feature https://github.com/ruby/rubygems/commit/15e46a3a68 --- lib/rubygems/commands/install_command.rb | 6 +- lib/rubygems/commands/setup_command.rb | 1 - lib/rubygems/dependency_installer.rb | 3 - lib/rubygems/install_default_message.rb | 13 --- lib/rubygems/install_update_options.rb | 1 - lib/rubygems/installer.rb | 31 ++----- test/rubygems/helper.rb | 2 +- test/rubygems/test_gem_installer.rb | 113 ----------------------- 8 files changed, 10 insertions(+), 160 deletions(-) delete mode 100644 lib/rubygems/install_default_message.rb diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 2888b6c55a8372..70d32013ba6ad0 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -239,11 +239,7 @@ def install_gems # :nodoc: # Loads post-install hooks def load_hooks # :nodoc: - if options[:install_as_default] - require_relative "../install_default_message" - else - require_relative "../install_message" - end + require_relative "../install_message" require_relative "../rdoc" end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 85e28cceddf608..2b9c522a6b6a21 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -398,7 +398,6 @@ def install_default_bundler_gem(bin_dir) env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], - install_as_default: true, bin_dir: bin_dir, install_dir: default_dir, wrappers: true diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index b4152e83e913b4..6fbfe53643a2ae 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -28,7 +28,6 @@ class Gem::DependencyInstaller wrappers: true, build_args: nil, build_docs_in_background: false, - install_as_default: false, }.freeze ## @@ -87,7 +86,6 @@ def initialize(options = {}) @wrappers = options[:wrappers] @build_args = options[:build_args] @build_docs_in_background = options[:build_docs_in_background] - @install_as_default = options[:install_as_default] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @prog_mode = options[:prog_mode] @@ -240,7 +238,6 @@ def install(dep_or_name, version = Gem::Requirement.default) user_install: @user_install, wrappers: @wrappers, build_root: @build_root, - install_as_default: @install_as_default, dir_mode: @dir_mode, data_mode: @data_mode, prog_mode: @prog_mode, diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb deleted file mode 100644 index 0640eaaf08832e..00000000000000 --- a/lib/rubygems/install_default_message.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require_relative "../rubygems" -require_relative "user_interaction" - -## -# A post-install hook that displays "Successfully installed -# some_gem-1.0 as a default gem" - -Gem.post_install do |installer| - ui = Gem::DefaultUserInteraction.ui - ui.say "Successfully installed #{installer.spec.full_name} as a default gem" -end diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 57c41b75861c1c..2d80e997879715 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -161,7 +161,6 @@ def add_install_update_options add_option(:Deprecated, "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| - options[:install_as_default] = v end add_option(:"Install/Update", "--explain", diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 0cfe59b5bb5be3..46c4844ab96188 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -274,11 +274,7 @@ def install run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct - if @options[:install_as_default] - spec.loaded_from = default_spec_file - else - spec.loaded_from = spec_file - end + spec.loaded_from = spec_file # Completely remove any previous gem files FileUtils.rm_rf gem_dir @@ -287,24 +283,17 @@ def install dir_mode = options[:dir_mode] FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 - if @options[:install_as_default] - extract_bin - write_default_spec - else - extract_files + extract_files - build_extensions - write_build_info_file - run_post_build_hooks - end + build_extensions + write_build_info_file + run_post_build_hooks generate_bin generate_plugins - unless @options[:install_as_default] - write_spec - write_cache_file - end + write_spec + write_cache_file File.chmod(dir_mode, gem_dir) if dir_mode @@ -889,11 +878,7 @@ def pre_install_checks ensure_loadable_spec - if options[:install_as_default] - Gem.ensure_default_gem_subdirectories gem_home - else - Gem.ensure_gem_subdirectories gem_home - end + Gem.ensure_gem_subdirectories gem_home return true if @force diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index b797960ac936a0..ecd92b0a572e8e 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -811,7 +811,7 @@ def install_specs(*specs) def install_default_gems(*specs) specs.each do |spec| - installer = Gem::Installer.for_spec(spec, install_as_default: true) + installer = Gem::Installer.for_spec(spec) installer.install Gem.register_default_spec(spec) end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 34415aa7dd7e1d..3fa617ef50e04a 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -2425,119 +2425,6 @@ def test_dir assert_match %r{/gemhome/gems/a-2$}, installer.dir end - def test_default_gem_loaded_from - spec = util_spec "a" - installer = Gem::Installer.for_spec spec, install_as_default: true - installer.install - assert_predicate spec, :default_gem? - end - - def test_default_gem_without_wrappers - installer = setup_base_installer - - FileUtils.rm_rf File.join(Gem.default_dir, "specifications") - - installer.wrappers = false - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - use_ui @ui do - installer.install - end - - assert_directory_exists File.join(@spec.gem_dir, "bin") - installed_exec = File.join @spec.gem_dir, "bin", "executable" - assert_path_exist installed_exec - - assert_directory_exists File.join(Gem.default_dir, "specifications") - assert_directory_exists File.join(Gem.default_dir, "specifications", "default") - - default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "a-2.gemspec") - assert_equal Gem::Version.new("2"), default_spec.version - assert_equal ["bin/executable"], default_spec.files - - assert_directory_exists util_inst_bindir - - installed_exec = File.join util_inst_bindir, "executable" - assert_path_exist installed_exec - - wrapper = File.read installed_exec - - if symlink_supported? - refute_match(/generated by RubyGems/, wrapper) - else # when symlink not supported, it warns and fallbacks back to installing wrapper - assert_match(/Unable to use symlinks, installing wrapper/, @ui.error) - assert_match(/generated by RubyGems/, wrapper) - end - end - - def test_default_gem_with_wrappers - installer = setup_base_installer - - installer.wrappers = true - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - use_ui @ui do - installer.install - end - - assert_directory_exists util_inst_bindir - - installed_exec = File.join util_inst_bindir, "executable" - assert_path_exist installed_exec - - wrapper = File.read installed_exec - assert_match(/generated by RubyGems/, wrapper) - end - - def test_default_gem_with_exe_as_bindir - @spec = quick_gem "c" do |spec| - util_make_exec spec, "#!/usr/bin/ruby", "exe" - end - - util_build_gem @spec - - @spec.cache_file - - installer = util_installer @spec, @gemhome - - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - use_ui @ui do - installer.install - end - - assert_directory_exists File.join(@spec.gem_dir, "exe") - installed_exec = File.join @spec.gem_dir, "exe", "executable" - assert_path_exist installed_exec - - assert_directory_exists File.join(Gem.default_dir, "specifications") - assert_directory_exists File.join(Gem.default_dir, "specifications", "default") - - default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "c-2.gemspec") - assert_equal Gem::Version.new("2"), default_spec.version - assert_equal ["exe/executable"], default_spec.files - end - - def test_default_gem_to_specific_install_dir - @gem = setup_base_gem - installer = util_installer @spec, "#{@gemhome}2" - installer.options[:install_as_default] = true - - use_ui @ui do - installer.install - end - - assert_directory_exists File.join("#{@gemhome}2", "specifications") - assert_directory_exists File.join("#{@gemhome}2", "specifications", "default") - - default_spec = eval File.read File.join("#{@gemhome}2", "specifications", "default", "a-2.gemspec") - assert_equal Gem::Version.new("2"), default_spec.version - assert_equal ["bin/executable"], default_spec.files - end - def test_package_attribute gem = quick_gem "c" do |spec| util_make_exec spec, "#!/usr/bin/ruby", "exe" From ceb2b569af29ae515042c5fbab533e003ac42a4d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 15:59:19 +0900 Subject: [PATCH 0581/2435] [ruby/rubygems] Added install_default_gem method for testing https://github.com/ruby/rubygems/commit/81dbd42abf --- test/rubygems/helper.rb | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index ecd92b0a572e8e..53bed0b4151b97 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -60,6 +60,44 @@ def self.specific_extra_args_hash=(value) end end +class Gem::Installer + # Copy from Gem::Installer#install with install_as_default option from old version + def install_default_gem + pre_install_checks + + run_pre_install_hooks + + spec.loaded_from = default_spec_file + + FileUtils.rm_rf gem_dir + FileUtils.rm_rf spec.extension_dir + + dir_mode = options[:dir_mode] + FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 + + extract_bin + write_default_spec + + generate_bin + generate_plugins + + File.chmod(dir_mode, gem_dir) if dir_mode + + say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? + + Gem::Specification.add_spec(spec) + + load_plugin + + run_post_install_hooks + + spec + rescue Errno::EACCES => e + # Permission denied - /path/to/foo + raise Gem::FilePermissionError, e.message.split(" - ").last + end +end + ## # RubyGemTestCase provides a variety of methods for testing rubygems and # gem-related behavior in a sandbox. Through RubyGemTestCase you can install @@ -812,7 +850,7 @@ def install_specs(*specs) def install_default_gems(*specs) specs.each do |spec| installer = Gem::Installer.for_spec(spec) - installer.install + installer.install_default_gem Gem.register_default_spec(spec) end end From 7bd7bcbf3e683e3d4d88696b72444e6c14685cc2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 16:29:10 +0900 Subject: [PATCH 0582/2435] [ruby/rubygems] Removed default bundler spec from specification directory https://github.com/ruby/rubygems/commit/6fbbde48e2 --- lib/rubygems/commands/setup_command.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 2b9c522a6b6a21..8fcece5d5ce252 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -393,7 +393,7 @@ def install_default_bundler_gem(bin_dir) Dir.chdir("bundler") do built_gem = Gem::Package.build(new_bundler_spec) begin - Gem::Installer.at( + installer = Gem::Installer.at( built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], @@ -401,7 +401,10 @@ def install_default_bundler_gem(bin_dir) bin_dir: bin_dir, install_dir: default_dir, wrappers: true - ).install + ) + installer.install + File.delete installer.spec_file + installer.write_default_spec ensure FileUtils.rm_f built_gem end From 3afe8ed46f3cf71ae48703c5264d2d0712597a59 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 17:40:29 +0900 Subject: [PATCH 0583/2435] [ruby/rubygems] Introduce default_spec_dir when it's not provided https://github.com/ruby/rubygems/commit/e9bd59699c --- lib/rubygems/installer.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 46c4844ab96188..52a352162e89c0 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -398,12 +398,18 @@ def spec_file File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end + def default_spec_dir + dir = File.join(gem_home, "specifications", "default") + FileUtils.mkdir_p dir + dir + end + ## # The location of the default spec file for default gems. # def default_spec_file - File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" + File.join default_spec_dir, "#{spec.full_name}.gemspec" end ## From 52451798d2647739912b8a32b055ff998abe69a7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Apr 2024 17:40:49 +0900 Subject: [PATCH 0584/2435] [ruby/rubygems] Simulate default gems manually https://github.com/ruby/rubygems/commit/c3cc38c72c --- spec/bundler/support/helpers.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 17c38c77607d4f..719a6e65d2626c 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -333,9 +333,20 @@ def install_gem(path, install_dir, default = false) raise "OMG `#{path}` does not exist!" unless File.exist?(path) args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" - args += " --default" if default gem_command "install #{args} '#{path}'" + + if default + gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1] + + # Revert Gem::Installer#write_spec and apply Gem::Installer#write_default_spec + FileUtils.mkdir_p File.join(install_dir, "specifications", "default") + File.rename File.join(install_dir, "specifications", gem + ".gemspec"), + File.join(install_dir, "specifications", "default", gem + ".gemspec") + + # Revert Gem::Installer#write_cache_file + File.delete File.join(install_dir, "cache", gem + ".gem") + end end def with_built_bundler(version = nil, opts = {}, &block) From b0825d78f3e42565af47a53d93713c7e1dd7a04d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 28 Oct 2025 20:27:53 +0900 Subject: [PATCH 0585/2435] Restore old version of Gem::Installer#install for default gems installation --- tool/rbinstall.rb | 73 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index df15c65b54b11a..936ef8242d7ecb 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -709,6 +709,77 @@ def extract_files(destination_dir, pattern = "*") end class UnpackedInstaller < Gem::Installer + # This method is mostly copied from old version of Gem::Installer#install + def install_with_default_gem + verify_gem_home + + # The name and require_paths must be verified first, since it could contain + # ruby code that would be eval'ed in #ensure_loadable_spec + verify_spec + + ensure_loadable_spec + + if options[:install_as_default] + Gem.ensure_default_gem_subdirectories gem_home + else + Gem.ensure_gem_subdirectories gem_home + end + + return true if @force + + ensure_dependencies_met unless @ignore_dependencies + + run_pre_install_hooks + + # Set loaded_from to ensure extension_dir is correct + if @options[:install_as_default] + spec.loaded_from = default_spec_file + else + spec.loaded_from = spec_file + end + + # Completely remove any previous gem files + FileUtils.rm_rf gem_dir + FileUtils.rm_rf spec.extension_dir + + dir_mode = options[:dir_mode] + FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 + + if @options[:install_as_default] + extract_bin + write_default_spec + else + extract_files + + build_extensions + write_build_info_file + run_post_build_hooks + end + + generate_bin + generate_plugins + + unless @options[:install_as_default] + write_spec + write_cache_file + end + + File.chmod(dir_mode, gem_dir) if dir_mode + + say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? + + Gem::Specification.add_spec(spec) unless @install_dir + + load_plugin + + run_post_install_hooks + + spec + rescue Errno::EACCES => e + # Permission denied - /path/to/foo + raise Gem::FilePermissionError, e.message.split(" - ").last + end + def write_cache_file end @@ -754,7 +825,7 @@ def write_default_spec def install spec.post_install_message = nil dir_creating(without_destdir(gem_dir)) - RbInstall.no_write(options) {super} + RbInstall.no_write(options) { install_with_default_gem } end # Now build-ext builds all extensions including bundled gems. From 3624031bb594d856c9449130cb162aad612435e7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:19:40 -0700 Subject: [PATCH 0586/2435] ZJIT: Inline Kernel#nil? and NilClass#nil? We can fully remove the CCall now. --- zjit/src/cruby_methods.rs | 14 ++++++++++++-- zjit/src/hir.rs | 36 ++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index dd33bb206a5bb3..5530b555bd64e1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -210,8 +210,8 @@ pub fn init() -> Annotations { annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); - annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); + annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p); + annotate!(rb_mKernel, "nil?", inline_kernel_nil_p); annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); @@ -363,6 +363,16 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, Some(result) } +fn inline_nilclass_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qtrue) })) +} + +fn inline_kernel_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) +} + fn inline_kernel_respond_to_p( fun: &mut hir::Function, block: hir::BlockId, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e0e682ccb2fc71..0f415a4a21cf1b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12724,10 +12724,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:NilClass = Const Value(nil) PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v22:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - v23:TrueClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v23 + Return v22 "); } @@ -12775,10 +12775,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v22:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v23:FalseClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v23 + Return v22 "); } @@ -12829,10 +12829,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) v24:NilClass = GuardType v9, NilClass + v25:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - v26:TrueClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12856,10 +12856,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) v24:FalseClass = GuardType v9, FalseClass + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12883,10 +12883,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) v24:TrueClass = GuardType v9, TrueClass + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12910,10 +12910,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) v24:StaticSymbol = GuardType v9, StaticSymbol + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12937,10 +12937,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) v24:Fixnum = GuardType v9, Fixnum + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12964,10 +12964,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) v24:Flonum = GuardType v9, Flonum + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12992,10 +12992,10 @@ mod opt_tests { PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v25:StringExact = GuardType v9, StringExact + v26:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v27:FalseClass = CCall nil?@0x1038, v25 CheckInterrupts - Return v27 + Return v26 "); } From 7425520415991f38e048ff6acbee3af0731baead Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:22:53 -0700 Subject: [PATCH 0587/2435] ZJIT: Remove redundant annotation --- zjit/src/cruby_methods.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 5530b555bd64e1..4ded84c4fb2ee7 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -192,7 +192,6 @@ pub fn init() -> Annotations { annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); - annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); From 2a6e5d7d94103f73d56c8d6f1393eb5b4c7297d1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:29:15 -0700 Subject: [PATCH 0588/2435] ZJIT: Allow both inlining and annotating properties --- zjit/src/cruby_methods.rs | 10 +++++++++- zjit/src/hir.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 4ded84c4fb2ee7..51841d6f781102 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -159,6 +159,14 @@ pub fn init() -> Annotations { let props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: types::BasicObject, inline: $inline }; annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; + ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { + #[allow(unused_mut)] + let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: $inline }; + $( + props.$properties = true; + )* + annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); + }; ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { #[allow(unused_mut)] let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: no_inline }; @@ -217,7 +225,7 @@ pub fn init() -> Annotations { annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cInteger, "^", inline_integer_xor); - annotate!(rb_cString, "to_s", inline_string_to_s); + annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 0f415a4a21cf1b..be8f060e6753cd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13711,6 +13711,33 @@ mod opt_tests { "); } + #[test] + fn test_string_subclass_to_s_returns_string_exact() { + eval(r#" + class C < String; end + def test(o) = o.to_s + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, to_s@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v23:StringSubclass[class_exact:C] = GuardType v9, StringSubclass[class_exact:C] + v24:StringExact = CCallWithFrame to_s@0x1038, v23 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_inline_string_literal_to_s() { eval(r#" From a4f8afcec835401d356350ad4a20a78b9aaa30f2 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:34:32 -0700 Subject: [PATCH 0589/2435] ZJIT: Use FnProperties::default() --- zjit/src/cruby_methods.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 51841d6f781102..b434cfe25a8940 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -156,20 +156,22 @@ pub fn init() -> Annotations { macro_rules! annotate { ($module:ident, $method_name:literal, $inline:ident) => { - let props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: types::BasicObject, inline: $inline }; + let mut props = FnProperties::default(); + props.inline = $inline; annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { - #[allow(unused_mut)] - let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: $inline }; + let mut props = FnProperties::default(); + props.return_type = $return_type; + props.inline = $inline; $( props.$properties = true; )* annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { - #[allow(unused_mut)] - let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: no_inline }; + let mut props = FnProperties::default(); + props.return_type = $return_type; $( props.$properties = true; )* @@ -183,13 +185,8 @@ pub fn init() -> Annotations { annotate_builtin!($module, $method_name, $return_type, no_gc, leaf, elidable) }; ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { - let mut props = FnProperties { - no_gc: false, - leaf: false, - elidable: false, - return_type: $return_type, - inline: no_inline, - }; + let mut props = FnProperties::default(); + props.return_type = $return_type; $(props.$properties = true;)+ annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); } From e973baa837a9cc17189ed4e32e43e047f622766b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:06:20 -0700 Subject: [PATCH 0590/2435] ZJIT: Add BoxBool and remove CCall from BasicObject#== --- zjit/src/codegen.rs | 6 ++++ zjit/src/cruby_methods.rs | 9 ++++- zjit/src/hir.rs | 72 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1179b9bf16924b..7f93d226019c15 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -405,6 +405,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), + &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), @@ -1553,6 +1554,11 @@ fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> l asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } +fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { + asm.test(val, val); + asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse)) +} + fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index b434cfe25a8940..e7be5ab4454bb3 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -217,7 +217,7 @@ pub fn init() -> Annotations { annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p); annotate!(rb_mKernel, "nil?", inline_kernel_nil_p); annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); - annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); @@ -361,6 +361,13 @@ fn inline_integer_xor(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I None } +fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index be8f060e6753cd..6d0b120a3702c2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -610,6 +610,7 @@ pub enum Insn { IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId }, /// Return C `true` if left == right IsBitEqual { left: InsnId, right: InsnId }, + BoxBool { val: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, @@ -997,6 +998,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::IsMethodCfunc { val, cd, .. } => { write!(f, "IsMethodCFunc {val}, :{}", ruby_call_method_name(*cd)) } Insn::IsBitEqual { left, right } => write!(f, "IsBitEqual {left}, {right}"), + Insn::BoxBool { val } => write!(f, "BoxBool {val}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") } @@ -1623,6 +1625,7 @@ impl Function { &IsNil { val } => IsNil { val: find!(val) }, &IsMethodCfunc { val, cd, cfunc, state } => IsMethodCfunc { val: find!(val), cd, cfunc, state }, &IsBitEqual { left, right } => IsBitEqual { left: find!(left), right: find!(right) }, + &BoxBool { val } => BoxBool { val: find!(val) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, &IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) }, @@ -1810,6 +1813,7 @@ impl Function { Insn::IsNil { .. } => types::CBool, Insn::IsMethodCfunc { .. } => types::CBool, Insn::IsBitEqual { .. } => types::CBool, + Insn::BoxBool { .. } => types::BoolExact, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, @@ -3094,6 +3098,7 @@ impl Function { | &Insn::Return { val } | &Insn::Test { val } | &Insn::SetLocal { val, .. } + | &Insn::BoxBool { val } | &Insn::IsNil { val } => worklist.push_back(val), &Insn::SetGlobal { val, state, .. } @@ -13084,7 +13089,7 @@ mod opt_tests { } #[test] - fn test_specialize_basic_object_eq_to_ccall() { + fn test_specialize_basic_object_eq() { eval(" class C; end def test(a, b) = a == b @@ -13106,13 +13111,76 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:CBool = IsBitEqual v28, v12 + v30:BoolExact = BoxBool v29 IncrCounter inline_cfunc_optimized_send_count - v30:BoolExact = CCall ==@0x1038, v28, v12 CheckInterrupts Return v30 "); } + #[test] + fn test_specialize_basic_object_eqq() { + eval(" + class C; end + def test(a, b) = a === b + + test(C.new, C.new) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v26:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) + PatchPoint NoSingletonClass(C@0x1000) + v30:CBool = IsBitEqual v26, v12 + v31:BoolExact = BoxBool v30 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_specialize_nil_eq() { + eval(" + def test(a, b) = a == b + + test(nil, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1008, cme:0x1010) + v27:NilClass = GuardType v11, NilClass + v28:CBool = IsBitEqual v27, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + #[test] fn test_guard_fixnum_and_fixnum() { eval(" From c2bef01b668174936c0a25358d9d50b38bcf341c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:07:04 -0700 Subject: [PATCH 0591/2435] ZJIT: Optimize Kernel#=== --- zjit/src/cruby_methods.rs | 12 ++++ zjit/src/hir.rs | 118 +++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index e7be5ab4454bb3..199b5a64ac5310 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -194,6 +194,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); + annotate!(rb_mKernel, "===", inline_eqq); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); @@ -379,6 +380,17 @@ fn inline_nilclass_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hi Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qtrue) })) } +fn inline_eqq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + if !fun.assume_expected_cfunc(block, recv_class, ID!(eq), rb_obj_equal as _, state) { + return None; + } + let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_kernel_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6d0b120a3702c2..c184c2d4b204b7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2037,6 +2037,21 @@ impl Function { None } + pub fn assume_expected_cfunc(&mut self, block: BlockId, class: VALUE, method_id: ID, cfunc: *mut c_void, state: InsnId) -> bool { + let cme = unsafe { rb_callable_method_entry(class, method_id) }; + if cme.is_null() { return false; } + let def_type = unsafe { get_cme_def_type(cme) }; + if def_type != VM_METHOD_TYPE_CFUNC { return false; } + if unsafe { get_mct_func(get_cme_def_body_cfunc(cme)) } != cfunc { + return false; + } + self.gen_patch_points_for_optimized_ccall(block, class, method_id, cme, state); + if class.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: class }, state }); + } + true + } + pub fn likely_a(&self, val: InsnId, ty: Type, state: InsnId) -> bool { if self.type_of(val).is_subtype(ty) { return true; @@ -2545,6 +2560,11 @@ impl Function { self.infer_types(); } + fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); + } + /// Optimize SendWithoutBlock that land in a C method to a direct CCall without /// runtime lookup. fn optimize_c_calls(&mut self) { @@ -2552,11 +2572,6 @@ impl Function { return; } - fn gen_patch_points_for_optimized_ccall(fun: &mut Function, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); - } - // Try to reduce a Send insn to a CCallWithFrame fn reduce_send_to_ccall( fun: &mut Function, @@ -2615,7 +2630,7 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } @@ -2717,7 +2732,7 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } @@ -2784,7 +2799,7 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); @@ -13181,6 +13196,93 @@ mod opt_tests { "); } + #[test] + fn test_specialize_nil_eqq() { + eval(" + def test(a, b) = a === b + test(nil, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, ===@0x1008, cme:0x1010) + v25:NilClass = GuardType v11, NilClass + PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_specialize_true_eqq() { + eval(" + def test(a, b) = a === b + test(true, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_specialize_false_eqq() { + eval(" + def test(a, b) = a === b + test(true, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + #[test] fn test_guard_fixnum_and_fixnum() { eval(" From 2e2f31c836c21b87e846d75372b06893f4226521 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:33:57 -0700 Subject: [PATCH 0592/2435] ZJIT: Add IsBitNotEqual and inline BasicObject#!= --- zjit/src/codegen.rs | 6 ++++++ zjit/src/cruby_methods.rs | 12 ++++++++++++ zjit/src/hir.rs | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7f93d226019c15..a1f318cab7781d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -405,6 +405,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), + &Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)), &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), @@ -1554,6 +1555,11 @@ fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> l asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } +fn gen_is_bit_not_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { + asm.cmp(left, right); + asm.csel_ne(Opnd::Imm(1), Opnd::Imm(0)) +} + fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { asm.test(val, val); asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse)) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 199b5a64ac5310..78a79b0faee7fc 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -220,6 +220,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cInteger, "^", inline_integer_xor); @@ -369,6 +370,17 @@ fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hi Some(result) } +fn inline_basic_object_neq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + if !fun.assume_expected_cfunc(block, recv_class, ID!(eq), rb_obj_equal as _, state) { + return None; + } + let c_result = fun.push_insn(block, hir::Insn::IsBitNotEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c184c2d4b204b7..8c4f9eb4fe43dc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -610,6 +610,9 @@ pub enum Insn { IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId }, /// Return C `true` if left == right IsBitEqual { left: InsnId, right: InsnId }, + /// Return C `true` if left != right + IsBitNotEqual { left: InsnId, right: InsnId }, + /// Convert a C `bool` to a Ruby `Qtrue`/`Qfalse`. Same as `RBOOL` macro. BoxBool { val: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, @@ -998,6 +1001,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::IsMethodCfunc { val, cd, .. } => { write!(f, "IsMethodCFunc {val}, :{}", ruby_call_method_name(*cd)) } Insn::IsBitEqual { left, right } => write!(f, "IsBitEqual {left}, {right}"), + Insn::IsBitNotEqual { left, right } => write!(f, "IsBitNotEqual {left}, {right}"), Insn::BoxBool { val } => write!(f, "BoxBool {val}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } @@ -1625,6 +1629,7 @@ impl Function { &IsNil { val } => IsNil { val: find!(val) }, &IsMethodCfunc { val, cd, cfunc, state } => IsMethodCfunc { val: find!(val), cd, cfunc, state }, &IsBitEqual { left, right } => IsBitEqual { left: find!(left), right: find!(right) }, + &IsBitNotEqual { left, right } => IsBitNotEqual { left: find!(left), right: find!(right) }, &BoxBool { val } => BoxBool { val: find!(val) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, @@ -1813,6 +1818,7 @@ impl Function { Insn::IsNil { .. } => types::CBool, Insn::IsMethodCfunc { .. } => types::CBool, Insn::IsBitEqual { .. } => types::CBool, + Insn::IsBitNotEqual { .. } => types::CBool, Insn::BoxBool { .. } => types::BoolExact, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, @@ -3156,6 +3162,7 @@ impl Function { | &Insn::FixnumOr { left, right } | &Insn::FixnumXor { left, right } | &Insn::IsBitEqual { left, right } + | &Insn::IsBitNotEqual { left, right } => { worklist.push_back(left); worklist.push_back(right); @@ -10556,6 +10563,38 @@ mod opt_tests { "); } + #[test] + fn test_specialize_basic_object_neq() { + eval(" + class C; end + def test(a, b) = a != b + test(C.new, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, !=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) + PatchPoint NoSingletonClass(C@0x1000) + v32:CBool = IsBitNotEqual v28, v12 + v33:BoolExact = BoxBool v32 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v33 + "); + } + #[test] fn test_do_not_eliminate_get_constant_path() { eval(" From 7a736545e948ec481ba2b2c46d78470194b624ba Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 28 Oct 2025 11:44:25 -0400 Subject: [PATCH 0593/2435] ZJIT: Specialize Array#pop for no argument case (#14933) Fixes https://github.com/Shopify/ruby/issues/814 This change specializes the case of calling `Array#pop` on a non frozen array with no arguments. `Array#pop` exists in the non-inlined C function list in the ZJIT SFR performance burndown list. If in the future it is helpful, this patch could be extended to support the case where an argument is provided, but this initial work seeks to elide the ruby frame normally pushed in the case of `Array#pop` without an argument. --- test/ruby/test_zjit.rb | 23 ++++++++ zjit/bindgen/src/main.rs | 2 + zjit/src/codegen.rs | 15 +++++ zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/cruby_methods.rs | 8 +++ zjit/src/hir.rs | 105 +++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 2 + 7 files changed, 156 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index e76e140ba13f2b..802370aa599405 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1191,6 +1191,29 @@ def test(x) = [1,2,3][x] }, call_threshold: 2, insns: [:opt_aref] end + def test_empty_array_pop + assert_compiles 'nil', %q{ + def test(arr) = arr.pop + test([]) + test([]) + }, call_threshold: 2 + end + + def test_array_pop_no_arg + assert_compiles '42', %q{ + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_array_pop_arg + assert_compiles '[33, 42]', %q{ + def test(arr) = arr.pop(2) + test([32, 33, 42]) + }, call_threshold: 2 + end + def test_new_range_inclusive assert_compiles '1..5', %q{ def test(a, b) = a..b diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 75dbd46794abe4..76f04f4369e3da 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -124,6 +124,7 @@ fn main() { .allowlist_function("rb_ary_clear") .allowlist_function("rb_ary_dup") .allowlist_function("rb_ary_push") + .allowlist_function("rb_ary_pop") .allowlist_function("rb_ary_unshift_m") .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") @@ -331,6 +332,7 @@ fn main() { .allowlist_function("rb_class_new_instance_pass_kw") .allowlist_function("rb_obj_alloc") .allowlist_function("rb_obj_info") + .allowlist_function("rb_obj_frozen_p") // From include/ruby/debug.h .allowlist_function("rb_profile_frames") .allowlist_function("ruby_xfree") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a1f318cab7781d..8ff1d400c32953 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -357,6 +357,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), + Insn::ArrayPop { array, state } => gen_array_pop(asm, opnd!(array), &function.frame_state(*state)), Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), &Insn::ObjectAllocClass { class, state } => gen_object_alloc_class(asm, class, &function.frame_state(state)), @@ -412,6 +413,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), + Insn::GuardNotFrozen { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &function.frame_state(*state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. @@ -630,6 +632,14 @@ fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc)); } +fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: &FrameState) -> Opnd { + let ret = asm_ccall!(asm, rb_obj_frozen_p, val); + asm_comment!(asm, "side-exit if rb_obj_frozen_p returns Qtrue"); + asm.cmp(ret, Qtrue.into()); + asm.je(side_exit(jit, state, GuardNotFrozen)); + val +} + fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { unsafe extern "C" { fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; @@ -1340,6 +1350,11 @@ fn gen_aref_fixnum( asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) } +fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::Opnd { + gen_prepare_leaf_call_with_gc(asm, state); + asm_ccall!(asm, rb_ary_pop, array) +} + fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm_ccall!(asm, rb_jit_array_len, array) } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index f7e6cdde9419b3..eb8ffcd1580e8c 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1158,6 +1158,7 @@ unsafe extern "C" { pub fn rb_ary_resurrect(ary: VALUE) -> VALUE; pub fn rb_ary_cat(ary: VALUE, train: *const VALUE, len: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_push(ary: VALUE, elem: VALUE) -> VALUE; + pub fn rb_ary_pop(ary: VALUE) -> VALUE; pub fn rb_ary_entry(ary: VALUE, off: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_ary_concat(lhs: VALUE, rhs: VALUE) -> VALUE; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 78a79b0faee7fc..12d226ce51bea2 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -212,6 +212,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "[]", inline_array_aref); annotate!(rb_cArray, "<<", inline_array_push); annotate!(rb_cArray, "push", inline_array_push); + annotate!(rb_cArray, "pop", inline_array_pop); annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); @@ -287,6 +288,13 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + // Only inline the case of no arguments. + let &[] = args else { return None; }; + let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state }); + Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state })) +} + fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if let &[key] = args { let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8c4f9eb4fe43dc..0f532cb459334f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -461,6 +461,7 @@ pub enum SideExitReason { GuardTypeNot(Type), GuardShape(ShapeId), GuardBitEquals(Const), + GuardNotFrozen, PatchPoint(Invariant), CalleeSideExit, ObjToStringFallback, @@ -585,6 +586,7 @@ pub enum Insn { /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + ArrayPop { array: InsnId, state: InsnId }, /// Return the length of the array as a C `long` ([`types::CInt64`]) ArrayLength { array: InsnId }, @@ -795,6 +797,8 @@ pub enum Insn { /// Side-exit if the block param has been modified or the block handler for the frame /// is neither ISEQ nor ifunc, which makes it incompatible with rb_block_param_proxy. GuardBlockParamProxy { level: u32, state: InsnId }, + /// Side-exit if val is frozen. + GuardNotFrozen { val: InsnId, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -921,6 +925,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayArefFixnum { array, index, .. } => { write!(f, "ArrayArefFixnum {array}, {index}") } + Insn::ArrayPop { array, .. } => { + write!(f, "ArrayPop {array}") + } Insn::ArrayLength { array } => { write!(f, "ArrayLength {array}") } @@ -1082,6 +1089,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), + Insn::GuardNotFrozen { val, .. } => write!(f, "GuardNotFrozen {val}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, @@ -1639,6 +1647,7 @@ impl Function { &GuardBitEquals { val, expected, state } => GuardBitEquals { val: find!(val), expected, state }, &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, + &GuardNotFrozen { val, state } => GuardNotFrozen { val: find!(val), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, &FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state }, &FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state }, @@ -1736,6 +1745,7 @@ impl Function { &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, + &ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) }, &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, @@ -1829,6 +1839,7 @@ impl Function { Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, Insn::ArrayArefFixnum { .. } => types::BasicObject, + Insn::ArrayPop { .. } => types::BasicObject, Insn::ArrayLength { .. } => types::CInt64, Insn::HashAref { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, @@ -1844,6 +1855,7 @@ impl Function { Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), + Insn::GuardNotFrozen { val, .. } => self.type_of(*val), Insn::FixnumAdd { .. } => types::Fixnum, Insn::FixnumSub { .. } => types::Fixnum, Insn::FixnumMult { .. } => types::Fixnum, @@ -3131,6 +3143,7 @@ impl Function { | &Insn::GuardTypeNot { val, state, .. } | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardShape { val, state, .. } + | &Insn::GuardNotFrozen { val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } => { @@ -3182,6 +3195,10 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::ArrayPop { array, state } => { + worklist.push_back(array); + worklist.push_back(state); + } &Insn::ArrayLength { array } => { worklist.push_back(array); } @@ -14421,6 +14438,94 @@ mod opt_tests { "); } + #[test] + fn test_optimize_array_pop_no_arg() { + eval(" + def test(arr) = arr.pop + test([1]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v23:ArrayExact = GuardType v9, ArrayExact + v24:ArrayExact = GuardNotFrozen v23 + v25:BasicObject = ArrayPop v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_do_not_optimize_array_pop_arg() { + eval(" + def test(arr) = arr.pop(4) + test([1]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + v25:BasicObject = CCallVariadic pop@0x1038, v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_array_pop_frozen() { + eval(" + def test(arr) + arr.pop + rescue FrozenError + nil + end + arr = [1].freeze + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v23:ArrayExact = GuardType v9, ArrayExact + v24:ArrayExact = GuardNotFrozen v23 + v25:BasicObject = ArrayPop v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + #[test] fn test_optimize_regexpmatch2() { eval(r#" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 4dd87d269ad4e7..5ab81f9ac6d71f 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -143,6 +143,7 @@ make_counters! { exit_guard_bit_equals_failure, exit_guard_int_equals_failure, exit_guard_shape_failure, + exit_guard_not_frozen_failure, exit_patchpoint_bop_redefined, exit_patchpoint_method_redefined, exit_patchpoint_stable_constant_names, @@ -342,6 +343,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardTypeNot(_) => exit_guard_type_not_failure, GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, + GuardNotFrozen => exit_guard_not_frozen_failure, CalleeSideExit => exit_callee_side_exit, ObjToStringFallback => exit_obj_to_string_fallback, Interrupt => exit_interrupt, From ec1b9bbd583f34d3d947f25e0bfc4b64a51f7e05 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 09:24:04 -0700 Subject: [PATCH 0594/2435] ZJIT: Call test again after profiling it It's a call_threshold: 2 test https://github.com/ruby/ruby/pull/14933#discussion_r2469731499 --- test/ruby/test_zjit.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 802370aa599405..6805d91406be5c 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1211,6 +1211,7 @@ def test_array_pop_arg assert_compiles '[33, 42]', %q{ def test(arr) = arr.pop(2) test([32, 33, 42]) + test([32, 33, 42]) }, call_threshold: 2 end From b66c57be1888a40cf6329c501275051e9cbfc1f7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 01:51:56 -0700 Subject: [PATCH 0595/2435] ZJIT: Print unexpected operands on x86_64 --- zjit/src/asm/x86_64/mod.rs | 4 ++-- zjit/src/backend/x86_64/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index 854438ad40d231..8077218c4ac720 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -509,7 +509,7 @@ fn write_rm_unary(cb: &mut CodeBlock, op_mem_reg_8: u8, op_mem_reg_pref: u8, op_ // Encode an add-like RM instruction with multiple possible encodings fn write_rm_multi(cb: &mut CodeBlock, op_mem_reg8: u8, op_mem_reg_pref: u8, op_reg_mem8: u8, op_reg_mem_pref: u8, op_mem_imm8: u8, op_mem_imm_sml: u8, op_mem_imm_lrg: u8, op_ext_imm: Option, opnd0: X86Opnd, opnd1: X86Opnd) { - assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_))); + assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_)), "unexpected opnd0: {opnd0:?}, {opnd1:?}"); // Check the size of opnd0 let opnd_size = opnd0.num_bits(); @@ -1334,7 +1334,7 @@ pub fn test(cb: &mut CodeBlock, rm_opnd: X86Opnd, test_opnd: X86Opnd) { write_rm(cb, rm_num_bits == 16, rm_num_bits == 64, test_opnd, rm_opnd, None, &[0x85]); } }, - _ => unreachable!() + _ => unreachable!("unexpected operands for test: {rm_opnd:?}, {test_opnd:?}") }; } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index b6c4658463048a..4fdbfe3f97c933 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -69,7 +69,7 @@ impl From for X86Opnd { "Attempted to lower an Opnd::None. This often happens when an out operand was not allocated for an instruction because the output of the instruction was not used. Please ensure you are using the output." ), - _ => panic!("unsupported x86 operand type") + _ => panic!("unsupported x86 operand type: {opnd:?}") } } } From 9b3df50d9015bc0295266c85712ba32c5b5c157d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 01:53:32 -0700 Subject: [PATCH 0596/2435] ZJIT: Allow ALLOC_REGS to have an odd number of regs --- zjit/src/codegen.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8ff1d400c32953..5360fd20469992 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2127,13 +2127,18 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Result Date: Tue, 28 Oct 2025 02:00:33 -0700 Subject: [PATCH 0597/2435] ZJIT: Stop computing offset on gen_push_opnds Once we add register spill, the C stack will have not only spilled basic block params but also spilled VRegs. We won't know how many stack slots are used for spilled VRegs until alloc_regs, so you can't compute an offset as of writing LIR. --- zjit/src/codegen.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5360fd20469992..d2ba14b1721e96 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -814,7 +814,7 @@ fn gen_ccall_variadic( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); - let argv_ptr = gen_push_opnds(jit, asm, &args); + let argv_ptr = gen_push_opnds(asm, &args); let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); gen_pop_opnds(asm, &args); @@ -1372,7 +1372,7 @@ fn gen_new_hash( let new_hash = asm_ccall!(asm, rb_hash_new_with_size, lir::Opnd::Imm(cap)); if !elements.is_empty() { - let argv = gen_push_opnds(jit, asm, &elements); + let argv = gen_push_opnds(asm, &elements); asm_ccall!(asm, rb_hash_bulk_insert, elements.len().into(), argv, new_hash); gen_pop_opnds(asm, &elements); @@ -2183,15 +2183,11 @@ pub fn gen_exit_trampoline_with_counter(cb: &mut CodeBlock, exit_trampoline: Cod }) } -fn gen_push_opnds(jit: &mut JITState, asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { +fn gen_push_opnds(asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { let n = opnds.len(); - - // Calculate the compile-time NATIVE_STACK_PTR offset from NATIVE_BASE_PTR - // At this point, frame_setup(&[], jit.c_stack_slots) has been called, - // which allocated aligned_stack_bytes(jit.c_stack_slots) on the stack - let frame_size = aligned_stack_bytes(jit.c_stack_slots); let allocation_size = aligned_stack_bytes(n); + // Bump the stack pointer to reserve the space for opnds if n != 0 { asm_comment!(asm, "allocate {} bytes on C stack for {} values", allocation_size, n); asm.sub_into(NATIVE_STACK_PTR, allocation_size.into()); @@ -2199,18 +2195,14 @@ fn gen_push_opnds(jit: &mut JITState, asm: &mut Assembler, opnds: &[Opnd]) -> li asm_comment!(asm, "no opnds to allocate"); } - // Calculate the total offset from NATIVE_BASE_PTR to our buffer - let total_offset_from_base = (frame_size + allocation_size) as i32; - + // Load NATIVE_STACK_PTR to get the address of a returned array + // to allow the backend to move it for its own use. + let argv = asm.load(NATIVE_STACK_PTR); for (idx, &opnd) in opnds.iter().enumerate() { - let slot_offset = -total_offset_from_base + (idx as i32 * SIZEOF_VALUE_I32); - asm.mov( - Opnd::mem(VALUE_BITS, NATIVE_BASE_PTR, slot_offset), - opnd - ); + asm.mov(Opnd::mem(VALUE_BITS, argv, idx as i32 * SIZEOF_VALUE_I32), opnd); } - asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base)) + argv } fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { @@ -2227,7 +2219,7 @@ fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_opnd_ptr = gen_push_opnds(jit, asm, &values); + let first_opnd_ptr = gen_push_opnds(asm, &values); let tmp_ary = asm_ccall!(asm, rb_ary_tmp_new_from_values, Opnd::Imm(0), values.len().into(), first_opnd_ptr); let result = asm_ccall!(asm, rb_reg_new_ary, tmp_ary, opt.into()); @@ -2241,7 +2233,7 @@ fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_string_ptr = gen_push_opnds(jit, asm, &strings); + let first_string_ptr = gen_push_opnds(asm, &strings); let result = asm_ccall!(asm, rb_str_concat_literals, strings.len().into(), first_string_ptr); gen_pop_opnds(asm, &strings); From cc051ef0e56b37c9bd29cabd4e930a170a832bcf Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 02:32:33 -0700 Subject: [PATCH 0598/2435] ZJIT: Simplify Assembler constructors --- zjit/src/backend/arm64/mod.rs | 9 ++++---- zjit/src/backend/lir.rs | 38 +++++++++++++++++++++------------- zjit/src/backend/x86_64/mod.rs | 9 ++++---- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index a6a5fc59582610..5760cadfc30490 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -207,7 +207,7 @@ impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) } /// Return true if opnd contains a scratch reg @@ -386,9 +386,9 @@ impl Assembler { } } + let mut asm_local = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); let asm = &mut asm_local; while let Some((index, mut insn)) = iterator.next() { @@ -691,9 +691,10 @@ impl Assembler { /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this /// splits them and uses scratch registers for it. - fn arm64_split_with_scratch_reg(mut self) -> Assembler { + fn arm64_split_with_scratch_reg(self) -> Assembler { + let mut asm = Assembler::new_with_asm(&self); + asm.accept_scratch_reg = true; let iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); for (_, mut insn) in iterator { match &mut insn { diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 6efb3e1259e4e6..55151d0605ade7 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1182,26 +1182,36 @@ pub struct Assembler { impl Assembler { - /// Create an Assembler + /// Create an Assembler with defaults pub fn new() -> Self { - Self::new_with_label_names(Vec::default(), 0, false) - } - - /// Create an Assembler with parameters that are populated by another Assembler instance. - /// This API is used for copying an Assembler for the next compiler pass. - pub fn new_with_label_names(label_names: Vec, num_vregs: usize, accept_scratch_reg: bool) -> Self { - let mut live_ranges = Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY); - live_ranges.resize(num_vregs, LiveRange { start: None, end: None }); - Self { insns: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), - live_ranges, - label_names, - accept_scratch_reg, + live_ranges: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), + label_names: Vec::default(), + accept_scratch_reg: false, leaf_ccall_stack_size: None, } } + /// Create an Assembler that allows the use of scratch registers. + /// This should be called only through [`Self::new_with_scratch_reg`]. + pub(super) fn new_with_accept_scratch_reg(accept_scratch_reg: bool) -> Self { + Self { accept_scratch_reg, ..Self::new() } + } + + /// Create an Assembler with parameters of another Assembler and empty instructions. + /// Compiler passes build a next Assembler with this API and insert new instructions to it. + pub(super) fn new_with_asm(old_asm: &Assembler) -> Self { + let mut asm = Self { + label_names: old_asm.label_names.clone(), + accept_scratch_reg: old_asm.accept_scratch_reg, + ..Self::new() + }; + // Bump the initial VReg index to allow the use of the VRegs for the old Assembler + asm.live_ranges.resize(old_asm.live_ranges.len(), LiveRange { start: None, end: None }); + asm + } + pub fn expect_leaf_ccall(&mut self, stack_size: usize) { self.leaf_ccall_stack_size = Some(stack_size); } @@ -1357,9 +1367,9 @@ impl Assembler let mut saved_regs: Vec<(Reg, usize)> = vec![]; // live_ranges is indexed by original `index` given by the iterator. + let mut asm = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 4fdbfe3f97c933..e7e2f796f190e2 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -102,7 +102,7 @@ const SCRATCH_OPND: Opnd = Opnd::Reg(R11_REG); impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) } /// Return true if opnd contains a scratch reg @@ -137,9 +137,9 @@ impl Assembler { /// Split IR instructions for the x86 platform fn x86_split(mut self) -> Assembler { + let mut asm = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let is_load = matches!(insn, Insn::Load { .. } | Insn::LoadInto { .. }); @@ -390,7 +390,7 @@ impl Assembler { /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so /// this splits them and uses scratch registers for it. - pub fn x86_split_with_scratch_reg(mut self) -> Assembler { + pub fn x86_split_with_scratch_reg(self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register /// allocator. So we just use the SCRATCH_OPND register temporarily to hold @@ -419,8 +419,9 @@ impl Assembler { } } + let mut asm = Assembler::new_with_asm(&self); + asm.accept_scratch_reg = true; let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); while let Some((_, mut insn)) = iterator.next() { match &mut insn { From 0e1d99ce69135bc875d23903eca3f75ad8ab6405 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 02:43:21 -0700 Subject: [PATCH 0599/2435] ZJIT: Move c_stack_slots to Assembler --- zjit/src/backend/arm64/mod.rs | 11 +++++++---- zjit/src/backend/lir.rs | 18 ++++++++++++++++- zjit/src/backend/x86_64/mod.rs | 35 ++++++++++++++++++++-------------- zjit/src/codegen.rs | 20 +++++++++---------- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 5760cadfc30490..867ad493ec2821 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1665,7 +1665,7 @@ mod tests { fn test_emit_frame() { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(&[], 0); + asm.frame_setup(&[]); asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); @@ -1684,7 +1684,8 @@ mod tests { // Test 3 preserved regs (odd), odd slot_count let cb1 = { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(THREE_REGS, 3); + asm.stack_base_idx = 3; + asm.frame_setup(THREE_REGS); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); cb @@ -1693,7 +1694,8 @@ mod tests { // Test 3 preserved regs (odd), even slot_count let cb2 = { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(THREE_REGS, 4); + asm.stack_base_idx = 4; + asm.frame_setup(THREE_REGS); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); cb @@ -1703,7 +1705,8 @@ mod tests { let cb3 = { static FOUR_REGS: &[Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG), Opnd::Reg(X22_REG)]; let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(FOUR_REGS, 3); + asm.stack_base_idx = 3; + asm.frame_setup(FOUR_REGS); asm.frame_teardown(FOUR_REGS); asm.compile_with_num_regs(&mut cb, 0); cb diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 55151d0605ade7..eb49b419d62046 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1034,6 +1034,9 @@ impl fmt::Debug for Insn { // Print list of operands let mut opnd_iter = self.opnd_iter(); + if let Insn::FrameSetup { slot_count, .. } = self { + write!(fmt, "{slot_count}")?; + } if let Some(first_opnd) = opnd_iter.next() { write!(fmt, "{first_opnd:?}")?; } @@ -1176,6 +1179,11 @@ pub struct Assembler { /// On `compile`, it also disables the backend's use of them. pub(super) accept_scratch_reg: bool, + /// The Assembler can use NATIVE_BASE_PTR + stack_base_idx as the + /// first stack slot in case it needs to allocate memory. This is + /// equal to the number of spilled basic block arguments. + pub(super) stack_base_idx: usize, + /// If Some, the next ccall should verify its leafness leaf_ccall_stack_size: Option } @@ -1189,10 +1197,16 @@ impl Assembler live_ranges: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), label_names: Vec::default(), accept_scratch_reg: false, + stack_base_idx: 0, leaf_ccall_stack_size: None, } } + /// Create an Assembler, reserving a specified number of stack slots + pub fn new_with_stack_slots(stack_base_idx: usize) -> Self { + Self { stack_base_idx, ..Self::new() } + } + /// Create an Assembler that allows the use of scratch registers. /// This should be called only through [`Self::new_with_scratch_reg`]. pub(super) fn new_with_accept_scratch_reg(accept_scratch_reg: bool) -> Self { @@ -1205,6 +1219,7 @@ impl Assembler let mut asm = Self { label_names: old_asm.label_names.clone(), accept_scratch_reg: old_asm.accept_scratch_reg, + stack_base_idx: old_asm.stack_base_idx, ..Self::new() }; // Bump the initial VReg index to allow the use of the VRegs for the old Assembler @@ -1841,7 +1856,8 @@ impl Assembler { out } - pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd], slot_count: usize) { + pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd]) { + let slot_count = self.stack_base_idx; self.push_insn(Insn::FrameSetup { preserved: preserved_regs, slot_count }); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index e7e2f796f190e2..81f44f2881976d 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -986,7 +986,9 @@ impl Assembler { #[cfg(test)] mod tests { use insta::assert_snapshot; - use crate::assert_disasm_snapshot; + #[cfg(feature = "disasm")] + use crate::disasms_with; + use crate::{assert_disasm_snapshot, hexdumps}; use super::*; fn setup_asm() -> (Assembler, CodeBlock) { @@ -1553,18 +1555,19 @@ mod tests { #[test] fn frame_setup_teardown() { - let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(JIT_PRESERVED_REGS, 0); + let (mut asm, mut cb1) = setup_asm(); + asm.frame_setup(JIT_PRESERVED_REGS); asm.frame_teardown(JIT_PRESERVED_REGS); - asm.cret(C_RET_OPND); + asm.compile_with_num_regs(&mut cb1, 0); - asm.frame_setup(&[], 5); + let (mut asm, mut cb2) = setup_asm(); + asm.stack_base_idx = 5; + asm.frame_setup(&[]); asm.frame_teardown(&[]); + asm.compile_with_num_regs(&mut cb2, 0); - asm.compile_with_num_regs(&mut cb, 0); - - assert_disasm_snapshot!(cb.disasm(), @" + assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2), @r" 0x0: push rbp 0x1: mov rbp, rsp 0x4: push r13 @@ -1577,13 +1580,17 @@ mod tests { 0x19: mov rsp, rbp 0x1c: pop rbp 0x1d: ret - 0x1e: push rbp - 0x1f: mov rbp, rsp - 0x22: sub rsp, 0x30 - 0x26: mov rsp, rbp - 0x29: pop rbp + + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: sub rsp, 0x30 + 0x8: mov rsp, rbp + 0xb: pop rbp + "); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + 554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3 + 554889e54883ec304889ec5d "); - assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3554889e54883ec304889ec5d"); } #[test] diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d2ba14b1721e96..bfbc8e88b962a1 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -41,21 +41,17 @@ struct JITState { /// ISEQ calls that need to be compiled later iseq_calls: Vec, - - /// The number of bytes allocated for basic block arguments spilled onto the C stack - c_stack_slots: usize, } impl JITState { /// Create a new JITState instance - fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize, c_stack_slots: usize) -> Self { + fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize) -> Self { JITState { iseq, opnds: vec![None; num_insns], labels: vec![None; num_blocks], jit_entries: Vec::default(), iseq_calls: Vec::default(), - c_stack_slots, } } @@ -246,9 +242,9 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, /// Compile a function fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(IseqCodePtrs, Vec, Vec), CompileError> { - let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len()); - let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots); - let mut asm = Assembler::new(); + let num_spilled_params = max_num_params(function).saturating_sub(ALLOC_REGS.len()); + let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks()); + let mut asm = Assembler::new_with_stack_slots(num_spilled_params); // Compile each basic block let reverse_post_order = function.rpo(); @@ -1011,7 +1007,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); // Save the registers we'll use for CFP, EP, SP - asm.frame_setup(lir::JIT_PRESERVED_REGS, 0); + asm.frame_setup(lir::JIT_PRESERVED_REGS); // EC and CFP are passed as arguments asm.mov(EC, C_ARG_OPNDS[0]); @@ -1439,7 +1435,7 @@ fn gen_entry_point(jit: &mut JITState, asm: &mut Assembler, jit_entry_idx: Optio jit_entry.borrow_mut().start_addr.set(Some(code_ptr)); }); } - asm.frame_setup(&[], jit.c_stack_slots); + asm.frame_setup(&[]); } /// Compile code that exits from JIT code with a return value @@ -1910,6 +1906,8 @@ fn param_opnd(idx: usize) -> Opnd { if idx < ALLOC_REGS.len() { Opnd::Reg(ALLOC_REGS[idx]) } else { + // With FrameSetup, the address that NATIVE_BASE_PTR points to stores an old value in the register. + // To avoid clobbering it, we need to start from the next slot, hence `+ 1` for the index. Opnd::mem(64, NATIVE_BASE_PTR, (idx - ALLOC_REGS.len() + 1) as i32 * -SIZEOF_VALUE_I32) } } @@ -2121,7 +2119,7 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Result Date: Tue, 28 Oct 2025 09:00:00 -0700 Subject: [PATCH 0600/2435] ZJIT: Split frame_setup_teardown tests --- zjit/src/backend/x86_64/mod.rs | 65 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 81f44f2881976d..bd2421823c9f3c 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -986,9 +986,7 @@ impl Assembler { #[cfg(test)] mod tests { use insta::assert_snapshot; - #[cfg(feature = "disasm")] - use crate::disasms_with; - use crate::{assert_disasm_snapshot, hexdumps}; + use crate::assert_disasm_snapshot; use super::*; fn setup_asm() -> (Assembler, CodeBlock) { @@ -1554,43 +1552,46 @@ mod tests { } #[test] - fn frame_setup_teardown() { - let (mut asm, mut cb1) = setup_asm(); + fn frame_setup_teardown_preserved_regs() { + let (mut asm, mut cb) = setup_asm(); asm.frame_setup(JIT_PRESERVED_REGS); asm.frame_teardown(JIT_PRESERVED_REGS); asm.cret(C_RET_OPND); - asm.compile_with_num_regs(&mut cb1, 0); + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: push r13 + 0x6: push rbx + 0x7: push r12 + 0x9: sub rsp, 8 + 0xd: mov r13, qword ptr [rbp - 8] + 0x11: mov rbx, qword ptr [rbp - 0x10] + 0x15: mov r12, qword ptr [rbp - 0x18] + 0x19: mov rsp, rbp + 0x1c: pop rbp + 0x1d: ret + "); + assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3"); + } - let (mut asm, mut cb2) = setup_asm(); + #[test] + fn frame_setup_teardown_stack_base_idx() { + let (mut asm, mut cb) = setup_asm(); asm.stack_base_idx = 5; asm.frame_setup(&[]); asm.frame_teardown(&[]); - asm.compile_with_num_regs(&mut cb2, 0); - - assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2), @r" - 0x0: push rbp - 0x1: mov rbp, rsp - 0x4: push r13 - 0x6: push rbx - 0x7: push r12 - 0x9: sub rsp, 8 - 0xd: mov r13, qword ptr [rbp - 8] - 0x11: mov rbx, qword ptr [rbp - 0x10] - 0x15: mov r12, qword ptr [rbp - 0x18] - 0x19: mov rsp, rbp - 0x1c: pop rbp - 0x1d: ret - - 0x0: push rbp - 0x1: mov rbp, rsp - 0x4: sub rsp, 0x30 - 0x8: mov rsp, rbp - 0xb: pop rbp - "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" - 554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3 - 554889e54883ec304889ec5d + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: sub rsp, 0x30 + 0x8: mov rsp, rbp + 0xb: pop rbp "); + assert_snapshot!(cb.hexdump(), @"554889e54883ec304889ec5d"); } #[test] From 2c90da465a7ff601007b324f5ab4959ef6277a89 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 28 Oct 2025 10:36:37 -0400 Subject: [PATCH 0601/2435] ZJIT: Count GuardType instructions We can measure how many we can remove by adding type information to C functions, etc. --- zjit.rb | 2 ++ zjit/src/codegen.rs | 1 + zjit/src/stats.rs | 3 +++ 3 files changed, 6 insertions(+) diff --git a/zjit.rb b/zjit.rb index 9ea83fe10f716f..b62d8e2b52808f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -197,6 +197,8 @@ def stats_string :vm_write_to_parent_iseq_local_count, :vm_read_from_parent_iseq_local_count, + :guard_type_count, + :code_region_bytes, :side_exit_count, :total_insn_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index bfbc8e88b962a1..2b71be0e15ba71 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1595,6 +1595,7 @@ fn gen_test(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { /// Compile a type check with a side exit fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> lir::Opnd { + gen_incr_counter(asm, Counter::guard_type_count); if guard_type.is_subtype(types::Fixnum) { asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)); asm.jz(side_exit(jit, state, GuardType(guard_type))); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 5ab81f9ac6d71f..4874d0fe64146b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -249,6 +249,9 @@ make_counters! { vm_read_from_parent_iseq_local_count, // TODO(max): Implement // vm_reify_stack_count, + + // The number of times we ran a dynamic check + guard_type_count, } /// Increase a counter by a specified amount From a9d42f7c4bff5cb0c14eec8944329b0b1fd0ad9b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 28 Oct 2025 13:04:37 -0400 Subject: [PATCH 0602/2435] ZJIT: Print percentage of GuardType failure --- zjit.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zjit.rb b/zjit.rb index b62d8e2b52808f..39e353f32741ca 100644 --- a/zjit.rb +++ b/zjit.rb @@ -151,6 +151,8 @@ def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats + stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 + # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs @@ -198,6 +200,7 @@ def stats_string :vm_read_from_parent_iseq_local_count, :guard_type_count, + :guard_type_exit_ratio, :code_region_bytes, :side_exit_count, @@ -234,6 +237,8 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) case key when :ratio_in_zjit value = '%0.1f%%' % value + when :guard_type_exit_ratio + value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') value = "#{number_with_delimiter(value / 10**6)}ms" From 8a765975354d38bd8845a990fa7e70e7126a2a22 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 28 Oct 2025 14:36:04 -0400 Subject: [PATCH 0603/2435] ZJIT: Split HIR tests (#14967) `hir.rs` was getting rather large, so I've opted to move the inline tests into their own files. This should also help when looking for where to put your tests, as the optimization tests have a dedicated file. Future follow up work could make the layout of test modules more idiomatic to Rust. --- zjit/src/hir.rs | 10831 +----------------------------------- zjit/src/hir/opt_tests.rs | 7050 +++++++++++++++++++++++ zjit/src/hir/tests.rs | 3207 +++++++++++ 3 files changed, 10348 insertions(+), 10740 deletions(-) create mode 100644 zjit/src/hir/opt_tests.rs create mode 100644 zjit/src/hir/tests.rs diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 0f532cb459334f..3f68764722b448 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -17,6 +17,9 @@ use crate::profile::{TypeDistributionSummary, ProfiledType}; use crate::stats::Counter; use SendFallbackReason::*; +mod tests; +mod opt_tests; + /// An index of an [`Insn`] in a [`Function`]. This is a popular /// type since this effectively acts as a pointer to an [`Insn`]. /// See also: [`Function::find`]. @@ -5384,7 +5387,7 @@ mod infer_tests { } #[cfg(test)] -mod snapshot_tests { +mod graphviz_tests { use super::*; use insta::assert_snapshot; @@ -5392,10760 +5395,108 @@ mod snapshot_tests { fn hir_string(method: &str) -> String { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let function = iseq_to_hir(iseq).unwrap(); - format!("{}", FunctionPrinter::with_snapshot(&function)) + let mut function = iseq_to_hir(iseq).unwrap(); + function.optimize(); + function.validate().unwrap(); + format!("{}", FunctionGraphvizPrinter::new(&function)) } #[test] - fn test_new_array_with_elements() { - eval("def test(a, b) = [a, b]"); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v13:Any = Snapshot FrameState { pc: 0x1000, stack: [], locals: [a=v11, b=v12] } - v14:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [a=v11, b=v12] } - PatchPoint NoTracePoint - v16:Any = Snapshot FrameState { pc: 0x1010, stack: [v11, v12], locals: [a=v11, b=v12] } - v17:ArrayExact = NewArray v11, v12 - v18:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } - PatchPoint NoTracePoint - v20:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } - CheckInterrupts - Return v17 - "); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use insta::assert_snapshot; - - fn iseq_contains_opcode(iseq: IseqPtr, expected_opcode: u32) -> bool { - let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; - let mut insn_idx = 0; - while insn_idx < iseq_size { - // Get the current pc and opcode - let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) }; - - // try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes. - let opcode: u32 = unsafe { rb_iseq_opcode_at_pc(iseq, pc) } - .try_into() - .unwrap(); - if opcode == expected_opcode { - return true; - } - insn_idx += insn_len(opcode as usize); - } - false - } - - #[track_caller] - pub fn assert_contains_opcode(method: &str, opcode: u32) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); - } + fn test_guard_fixnum_or_fixnum() { + eval(r#" + def test(x, y) = x | y - #[track_caller] - fn assert_contains_opcodes(method: &str, opcodes: &[u32]) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - for &opcode in opcodes { - assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + test(1, 2) + "#); + assert_snapshot!(hir_string("test"), @r#" + digraph G { # test@<compiled>:2 + node [shape=plaintext]; + mode=hier; overlap=false; splines=true; + bb0 [label=< + + + + + + +
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@5 
v3:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2, v3) 
>]; + bb0:v4 -> bb2:params:n; + bb1 [label=< + + + +
bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v6, v7, v8) 
>]; + bb1:v9 -> bb2:params:n; + bb2 [label=< + + + + + + + + + + +
bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject) 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) 
v26:Fixnum = GuardType v11, Fixnum 
v27:Fixnum = GuardType v12, Fixnum 
v28:Fixnum = FixnumOr v26, v27 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v28 
>]; } - } - - /// Combine multiple hir_string() results to match all of them at once, which allows - /// us to avoid running the set of zjit-test -> zjit-test-update multiple times. - #[macro_export] - macro_rules! hir_strings { - ($( $s:expr ),+ $(,)?) => {{ - vec![$( hir_string($s) ),+].join("\n") - }}; - } - - #[track_caller] - fn hir_string(method: &str) -> String { - hir_string_proc(&format!("{}.method(:{})", "self", method)) - } - - #[track_caller] - fn hir_string_proc(proc: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let function = iseq_to_hir(iseq).unwrap(); - hir_string_function(&function) - } - - #[track_caller] - fn hir_string_function(function: &Function) -> String { - format!("{}", FunctionPrinter::without_snapshot(function)) - } - - #[track_caller] - fn assert_compile_fails(method: &str, reason: ParseError) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let result = iseq_to_hir(iseq); - assert!(result.is_err(), "Expected an error but successfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap())); - assert_eq!(result.unwrap_err(), reason); - } - - #[test] - fn test_compile_optional() { - eval("def test(x=1) = 123"); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - v3:CPtr = LoadPC - v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) - v5:CBool = IsBitEqual v3, v4 - IfTrue v5, bb2(v1, v2) - Jump bb4(v1, v2) - bb1(v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - Jump bb2(v9, v10) - bb2(v12:BasicObject, v13:BasicObject): - v15:Fixnum[1] = Const Value(1) - Jump bb4(v12, v15) - bb3(v18:BasicObject, v19:BasicObject): - EntryPoint JIT(1) - Jump bb4(v18, v19) - bb4(v21:BasicObject, v22:BasicObject): - v26:Fixnum[123] = Const Value(123) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_putobject() { - eval("def test = 123"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[123] = Const Value(123) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_new_array() { - eval("def test = []"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_new_array_with_element() { - eval("def test(a) = [a]"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = NewArray v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_new_array_with_elements() { - eval("def test(a, b) = [a, b]"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_new_range_inclusive_with_one_element() { - eval("def test(a) = (a..10)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v15:RangeExact = NewRange v9 NewRangeInclusive v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_new_range_inclusive_with_two_elements() { - eval("def test(a, b) = (a..b)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:RangeExact = NewRange v11 NewRangeInclusive v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_new_range_exclusive_with_one_element() { - eval("def test(a) = (a...10)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v15:RangeExact = NewRange v9 NewRangeExclusive v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_new_range_exclusive_with_two_elements() { - eval("def test(a, b) = (a...b)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:RangeExact = NewRange v11 NewRangeExclusive v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_array_dup() { - eval("def test = [1, 2, 3]"); - assert_contains_opcode("test", YARVINSN_duparray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_hash_dup() { - eval("def test = {a: 1, b: 2}"); - assert_contains_opcode("test", YARVINSN_duphash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:HashExact = HashDup v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_new_hash_empty() { - eval("def test = {}"); - assert_contains_opcode("test", YARVINSN_newhash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_new_hash_with_elements() { - eval("def test(aval, bval) = {a: aval, b: bval}"); - assert_contains_opcode("test", YARVINSN_newhash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v16:StaticSymbol[:a] = Const Value(VALUE(0x1000)) - v17:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v19:HashExact = NewHash v16: v11, v17: v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_string_copy() { - eval("def test = \"hello\""); - assert_contains_opcode("test", YARVINSN_putchilledstring); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_bignum() { - eval("def test = 999999999999999999999999999999999999"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Bignum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_flonum() { - eval("def test = 1.5"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Flonum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_heap_float() { - eval("def test = 1.7976931348623157e+308"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:HeapFloat[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_static_sym() { - eval("def test = :foo"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_opt_plus() { - eval("def test = 1+2"); - assert_contains_opcode("test", YARVINSN_opt_plus); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - v15:BasicObject = SendWithoutBlock v10, :+, v11 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_opt_hash_freeze() { - eval(" - def test = {}.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_hash_freeze_rewritten() { - eval(" - class Hash - def freeze; 5; end - end - def test = {}.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_ary_freeze() { - eval(" - def test = [].freeze - "); - assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_ary_freeze_rewritten() { - eval(" - class Array - def freeze; 5; end - end - def test = [].freeze - "); - assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_str_freeze() { - eval(" - def test = ''.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_str_freeze_rewritten() { - eval(" - class String - def freeze; 5; end - end - def test = ''.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_str_uminus() { - eval(" - def test = -'' - "); - assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_str_uminus_rewritten() { - eval(" - class String - def -@; 5; end - end - def test = -'' - "); - assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)) - "); - } - - #[test] - fn test_setlocal_getlocal() { - eval(" - def test - a = 1 - a - end - "); - assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_nested_setlocal_getlocal() { - eval(" - l3 = 3 - _unused = _unused1 = nil - 1.times do |l2| - _ = nil - l2 = 2 - 1.times do |l1| - l1 = 1 - define_method(:test) do - l1 = l2 - l2 = l1 + l2 - l3 = l2 + l3 - end - end - end - "); - assert_contains_opcodes( - "test", - &[YARVINSN_getlocal_WC_1, YARVINSN_setlocal_WC_1, - YARVINSN_getlocal, YARVINSN_setlocal]); - assert_snapshot!(hir_string("test"), @r" - fn block (3 levels) in @:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:BasicObject = GetLocal l2, EP@4 - SetLocal l1, EP@3, v10 - v14:BasicObject = GetLocal l1, EP@3 - v15:BasicObject = GetLocal l2, EP@4 - v19:BasicObject = SendWithoutBlock v14, :+, v15 - SetLocal l2, EP@4, v19 - v23:BasicObject = GetLocal l2, EP@4 - v24:BasicObject = GetLocal l3, EP@5 - v28:BasicObject = SendWithoutBlock v23, :+, v24 - SetLocal l3, EP@5, v28 - CheckInterrupts - Return v28 - " - ); - } - - #[test] - fn test_setlocal_in_default_args() { - eval(" - def test(a = (b = 1)) = [a, b] - "); - assert_contains_opcode("test", YARVINSN_setlocal_WC_0); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - v4:CPtr = LoadPC - v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) - v6:CBool = IsBitEqual v4, v5 - IfTrue v6, bb2(v1, v2, v3) - Jump bb4(v1, v2, v3) - bb1(v10:BasicObject, v11:BasicObject): - EntryPoint JIT(0) - v12:NilClass = Const Value(nil) - Jump bb2(v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClass): - v20:Fixnum[1] = Const Value(1) - Jump bb4(v14, v20, v20) - bb3(v23:BasicObject, v24:BasicObject): - EntryPoint JIT(1) - v25:NilClass = Const Value(nil) - Jump bb4(v23, v24, v25) - bb4(v27:BasicObject, v28:BasicObject, v29:NilClass|Fixnum): - v34:ArrayExact = NewArray v28, v29 - CheckInterrupts - Return v34 - "); - } - - #[test] - fn test_setlocal_in_default_args_with_tracepoint() { - eval(" - def test(a = (b = 1)) = [a, b] - TracePoint.new(:line) {}.enable - test - "); - assert_compile_fails("test", ParseError::FailedOptionalArguments); - } - - #[test] - fn test_setlocal_in_default_args_with_side_exit() { - eval(" - def test(a = (def foo = nil)) = a - "); - assert_compile_fails("test", ParseError::FailedOptionalArguments); - } - - #[test] - fn test_setlocal_cyclic_default_args() { - eval(" - def test = proc { |a=a| a } - "); - assert_snapshot!(hir_string_proc("test"), @r" - fn block in test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - Return v9 - "); - } - - #[test] - fn defined_ivar() { - eval(" - def test = defined?(@foo) - "); - assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact|NilClass = DefinedIvar v6, :@foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn if_defined_ivar() { - eval(" - def test - if defined?(@foo) - 3 - else - 4 - end - end - "); - assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:TrueClass|NilClass = DefinedIvar v6, :@foo - CheckInterrupts - v14:CBool = Test v11 - IfFalse v14, bb3(v6) - v18:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v18 - bb3(v24:BasicObject): - v28:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v28 - "); - } - - #[test] - fn defined() { - eval(" - def test = return defined?(SeaChange), defined?(favourite), defined?($ruby) - "); - assert_contains_opcode("test", YARVINSN_defined); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - v12:StringExact|NilClass = Defined constant, v10 - v14:StringExact|NilClass = Defined func, v6 - v15:NilClass = Const Value(nil) - v17:StringExact|NilClass = Defined global-variable, v15 - v19:ArrayExact = NewArray v12, v14, v17 - CheckInterrupts - Return v19 - "); + "#); } #[test] - fn test_return_const() { - eval(" - def test(cond) - if cond + fn test_multiple_blocks() { + eval(r#" + def test(c) + if c 3 else 4 end end - "); - assert_contains_opcode("test", YARVINSN_leave); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - v15:CBool = Test v9 - IfFalse v15, bb3(v8, v9) - v19:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v19 - bb3(v25:BasicObject, v26:BasicObject): - v30:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_merge_const() { - eval(" - def test(cond) - if cond - result = 3 - else - result = 4 - end - result - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - CheckInterrupts - v18:CBool = Test v11 - IfFalse v18, bb3(v10, v11, v12) - v22:Fixnum[3] = Const Value(3) - PatchPoint NoEPEscape(test) - CheckInterrupts - Jump bb4(v10, v11, v22) - bb3(v28:BasicObject, v29:BasicObject, v30:NilClass): - v34:Fixnum[4] = Const Value(4) - PatchPoint NoEPEscape(test) - Jump bb4(v28, v29, v34) - bb4(v38:BasicObject, v39:BasicObject, v40:Fixnum): - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v40 - "); - } - - #[test] - fn test_opt_plus_fixnum() { - eval(" - def test(a, b) = a + b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_plus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :+, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_minus_fixnum() { - eval(" - def test(a, b) = a - b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_minus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :-, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_mult_fixnum() { - eval(" - def test(a, b) = a * b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_mult); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :*, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_div_fixnum() { - eval(" - def test(a, b) = a / b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_div); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :/, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_mod_fixnum() { - eval(" - def test(a, b) = a % b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_mod); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :%, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_eq_fixnum() { - eval(" - def test(a, b) = a == b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_eq); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :==, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_neq_fixnum() { - eval(" - def test(a, b) = a != b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_neq); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :!=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_lt_fixnum() { - eval(" - def test(a, b) = a < b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_lt); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_le_fixnum() { - eval(" - def test(a, b) = a <= b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_le); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_gt_fixnum() { - eval(" - def test(a, b) = a > b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_gt); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_loop() { - eval(" - def test - result = 0 - times = 10 - while times > 0 - result = result + 1 - times = times - 1 - end - result - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject): - EntryPoint JIT(0) - v7:NilClass = Const Value(nil) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:NilClass, v12:NilClass): - v16:Fixnum[0] = Const Value(0) - v19:Fixnum[10] = Const Value(10) - CheckInterrupts - Jump bb4(v10, v16, v19) - bb4(v25:BasicObject, v26:BasicObject, v27:BasicObject): - PatchPoint NoEPEscape(test) - v31:Fixnum[0] = Const Value(0) - v35:BasicObject = SendWithoutBlock v27, :>, v31 - CheckInterrupts - v38:CBool = Test v35 - IfTrue v38, bb3(v25, v26, v27) - v40:NilClass = Const Value(nil) - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v26 - bb3(v50:BasicObject, v51:BasicObject, v52:BasicObject): - PatchPoint NoEPEscape(test) - v58:Fixnum[1] = Const Value(1) - v62:BasicObject = SendWithoutBlock v51, :+, v58 - v65:Fixnum[1] = Const Value(1) - v69:BasicObject = SendWithoutBlock v52, :-, v65 - Jump bb4(v50, v62, v69) - "); - } - #[test] - fn test_opt_ge_fixnum() { - eval(" - def test(a, b) = a >= b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_ge); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_display_types() { - eval(" - def test - cond = true - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:TrueClass = Const Value(true) - CheckInterrupts - v18:CBool[true] = Test v13 - IfFalse v18, bb3(v8, v13) - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - bb3(v28, v29): - v33 = Const Value(4) - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_send_without_block() { - eval(" - def bar(a, b) - a+b - end - def test - bar(2, 3) - end - "); - assert_contains_opcode("test", YARVINSN_opt_send_without_block); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[3] = Const Value(3) - v13:BasicObject = SendWithoutBlock v6, :bar, v10, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_send_with_block() { - eval(" - def test(a) - a.each {|item| - item - } - end - test([1,2,3]) - "); - assert_contains_opcode("test", YARVINSN_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:BasicObject = GetLocal l0, EP@3 - v15:BasicObject = Send v13, 0x1000, :each - v16:BasicObject = GetLocal l0, EP@3 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_intern_interpolated_symbol() { - eval(r#" - def test - :"foo#{123}" - end - "#); - assert_contains_opcode("test", YARVINSN_intern); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[123] = Const Value(123) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - v19:Symbol = StringIntern v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn different_objects_get_addresses() { - eval("def test = unknown_method([0], [1], '2', '2')"); - - // The 2 string literals have the same address because they're deduped. - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - v13:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v15:ArrayExact = ArrayDup v13 - v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v18:StringExact = StringCopy v16 - v19:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v21:StringExact = StringCopy v19 - v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_cant_compile_splat() { - eval(" - def test(a) = foo(*a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToArray v9 - SideExit UnhandledCallType(Splat) - "); - } - - #[test] - fn test_compile_block_arg() { - eval(" - def test(a) = foo(&a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = Send v8, 0x1000, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_cant_compile_kwarg() { - eval(" - def test(a) = foo(a: 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn test_cant_compile_kw_splat() { - eval(" - def test(a) = foo(**a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendWithoutBlock v8, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - // TODO(max): Figure out how to generate a call with TAILCALL flag - - #[test] - fn test_compile_super() { - eval(" - def test = super() - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_compile_zsuper() { - eval(" - def test = super - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_cant_compile_super_nil_blockarg() { - eval(" - def test = super(&nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - v12:BasicObject = InvokeSuper v6, 0x1000, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_cant_compile_super_forward() { - eval(" - def test(...) = super(...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - SideExit UnhandledYARVInsn(invokesuperforward) - "); - } - - #[test] - fn test_compile_forwardable() { - eval("def forwardable(...) = nil"); - assert_snapshot!(hir_string("forwardable"), @r" - fn forwardable@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - CheckInterrupts - Return v13 - "); - } - - // TODO(max): Figure out how to generate a call with OPT_SEND flag - - #[test] - fn test_cant_compile_kw_splat_mut() { - eval(" - def test(a) = foo **a, b: 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v15:HashExact = NewHash - PatchPoint NoEPEscape(test) - v19:BasicObject = SendWithoutBlock v13, :core#hash_merge_kwd, v15, v9 - v20:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v21:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v22:Fixnum[1] = Const Value(1) - v24:BasicObject = SendWithoutBlock v20, :core#hash_merge_ptr, v19, v21, v22 - v26:BasicObject = SendWithoutBlock v8, :foo, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_cant_compile_splat_mut() { - eval(" - def test(*) = foo *, 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:ArrayExact): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:ArrayExact): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - ArrayPush v14, v15 - SideExit UnhandledCallType(Splat) - "); - } - - #[test] - fn test_compile_forwarding() { - eval(" - def test(...) = foo(...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendForward 0x1000, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_compile_triple_dots_with_positional_args() { - eval(" - def test(a, ...) = foo(a, ...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:ArrayExact = GetLocal l0, SP@7, * - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 - v6:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5, v6) - bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): - EntryPoint JIT(0) - v14:NilClass = Const Value(nil) - Jump bb2(v9, v10, v11, v12, v13, v14) - bb2(v16:BasicObject, v17:BasicObject, v18:ArrayExact, v19:BasicObject, v20:BasicObject, v21:NilClass): - v26:ArrayExact = ToArray v18 - PatchPoint NoEPEscape(test) - GuardBlockParamProxy l0 - v31:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - SideExit UnhandledYARVInsn(splatkw) - "); - } - - #[test] - fn test_opt_new() { - eval(" - class C; end - def test = C.new - "); - assert_contains_opcode("test", YARVINSN_opt_new); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v12:NilClass = Const Value(nil) - v14:CBool = IsMethodCFunc v11, :new - IfFalse v14, bb3(v6, v12, v11) - v16:HeapBasicObject = ObjectAlloc v11 - v18:BasicObject = SendWithoutBlock v16, :initialize - CheckInterrupts - Jump bb4(v6, v16, v18) - bb3(v22:BasicObject, v23:NilClass, v24:BasicObject): - v27:BasicObject = SendWithoutBlock v24, :new - Jump bb4(v22, v27, v23) - bb4(v29:BasicObject, v30:BasicObject, v31:BasicObject): - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_opt_newarray_send_max_no_elements() { - eval(" - def test = [].max - "); - // TODO(max): Rewrite to nil - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) - v12:BasicObject = ArrayMax - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_newarray_send_max() { - eval(" - def test(a,b) = [a,b].max - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) - v18:BasicObject = ArrayMax v11, v12 - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_opt_newarray_send_min() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].min - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(MIN) - "); - } - - #[test] - fn test_opt_newarray_send_hash() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].hash - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(HASH) - "); - } - - #[test] - fn test_opt_newarray_send_pack() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].pack 'C' - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - v28:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v30:StringExact = StringCopy v28 - SideExit UnknownNewarraySend(PACK) - "); - } - - // TODO(max): Add a test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER - - #[test] - fn test_opt_newarray_send_include_p() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].include? b - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(INCLUDE_P) - "); - } - - #[test] - fn test_opt_length() { - eval(" - def test(a,b) = [a,b].length - "); - assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v17, :length - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_opt_size() { - eval(" - def test(a,b) = [a,b].size - "); - assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v17, :size - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_getinstancevariable() { - eval(" - def test = @foo - test - "); - assert_contains_opcode("test", YARVINSN_getinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - v12:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_trace_getinstancevariable() { - eval(" - def test = @foo - test - trace = TracePoint.trace(:call) { |tp| } - trace.enable { test } - "); - assert_contains_opcode("test", YARVINSN_trace_getinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit UnhandledYARVInsn(trace_getinstancevariable) - "); - } - - #[test] - fn test_setinstancevariable() { - eval(" - def test = @foo = 1 - test - "); - assert_contains_opcode("test", YARVINSN_setinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_set_ivar_rescue_frozen() { - let result = eval(" - class Foo - attr_accessor :bar - def initialize - @bar = 1 - freeze - end - end - - def test(foo) - begin - foo.bar = 2 - rescue FrozenError - end - end - - foo = Foo.new - test(foo) - test(foo) - - foo.bar - "); - assert_eq!(VALUE::fixnum_from_usize(1), result); - } - - #[test] - fn test_getclassvariable() { - eval(" - class Foo - def self.test = @@foo - end - "); - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); - assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetClassVar :@@foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_setclassvariable() { - eval(" - class Foo - def self.test = @@foo = 42 - end - "); - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); - assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[42] = Const Value(42) - SetClassVar :@@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_setglobal() { - eval(" - def test = $foo = 1 - test - "); - assert_contains_opcode("test", YARVINSN_setglobal); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SetGlobal :$foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_getglobal() { - eval(" - def test = $foo - test - "); - assert_contains_opcode("test", YARVINSN_getglobal); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetGlobal :$foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_splatarray_mut() { - eval(" - def test(a) = [*a] - "); - assert_contains_opcode("test", YARVINSN_splatarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_concattoarray() { - eval(" - def test(a) = [1, *a] - "); - assert_contains_opcode("test", YARVINSN_concattoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v15:ArrayExact = NewArray v13 - v17:ArrayExact = ToArray v9 - ArrayExtend v15, v17 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_pushtoarray_one_element() { - eval(" - def test(a) = [*a, 1] - "); - assert_contains_opcode("test", YARVINSN_pushtoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - ArrayPush v14, v15 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_pushtoarray_multiple_elements() { - eval(" - def test(a) = [*a, 1, 2, 3] - "); - assert_contains_opcode("test", YARVINSN_pushtoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - v16:Fixnum[2] = Const Value(2) - v17:Fixnum[3] = Const Value(3) - ArrayPush v14, v15 - ArrayPush v14, v16 - ArrayPush v14, v17 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_aset() { - eval(" - def test(a, b) = a[b] = 1 - "); - assert_contains_opcode("test", YARVINSN_opt_aset); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v16:NilClass = Const Value(nil) - v17:Fixnum[1] = Const Value(1) - v21:BasicObject = SendWithoutBlock v11, :[]=, v12, v17 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_aref() { - eval(" - def test(a, b) = a[b] - "); - assert_contains_opcode("test", YARVINSN_opt_aref); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :[], v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_empty_p() { - eval(" - def test(x) = x.empty? - "); - assert_contains_opcode("test", YARVINSN_opt_empty_p); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :empty? - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_succ() { - eval(" - def test(x) = x.succ - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :succ - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_and() { - eval(" - def test(x, y) = x & y - "); - assert_contains_opcode("test", YARVINSN_opt_and); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :&, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_or() { - eval(" - def test(x, y) = x | y - "); - assert_contains_opcode("test", YARVINSN_opt_or); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :|, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_not() { - eval(" - def test(x) = !x - "); - assert_contains_opcode("test", YARVINSN_opt_not); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :! - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_regexpmatch2() { - eval(" - def test(regexp, matchee) = regexp =~ matchee - "); - assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :=~, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - // Tests for ConstBase requires either constant or class definition, both - // of which can't be performed inside a method. - fn test_putspecialobject_vm_core_and_cbase() { - eval(" - def test - alias aliased __callee__ - end - "); - assert_contains_opcode("test", YARVINSN_putspecialobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v11:BasicObject = PutSpecialObject CBase - v12:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) - v13:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) - v15:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v11, v12, v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn opt_reverse() { - eval(" - def reverse_odd - a, b, c = @a, @b, @c - [a, b, c] - end - - def reverse_even - a, b, c, d = @a, @b, @c, @d - [a, b, c, d] - end - "); - assert_contains_opcode("reverse_odd", YARVINSN_opt_reverse); - assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); - assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" - fn reverse_odd@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:NilClass, v14:NilClass, v15:NilClass): - PatchPoint SingleRactorMode - v21:BasicObject = GetIvar v12, :@a - PatchPoint SingleRactorMode - v24:BasicObject = GetIvar v12, :@b - PatchPoint SingleRactorMode - v27:BasicObject = GetIvar v12, :@c - PatchPoint NoEPEscape(reverse_odd) - v33:ArrayExact = NewArray v21, v24, v27 - CheckInterrupts - Return v33 - - fn reverse_even@:8: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:NilClass, v16:NilClass, v17:NilClass, v18:NilClass): - PatchPoint SingleRactorMode - v24:BasicObject = GetIvar v14, :@a - PatchPoint SingleRactorMode - v27:BasicObject = GetIvar v14, :@b - PatchPoint SingleRactorMode - v30:BasicObject = GetIvar v14, :@c - PatchPoint SingleRactorMode - v33:BasicObject = GetIvar v14, :@d - PatchPoint NoEPEscape(reverse_even) - v39:ArrayExact = NewArray v24, v27, v30, v33 - CheckInterrupts - Return v39 - "); - } - - #[test] - fn test_branchnil() { - eval(" - def test(x) = x&.itself - "); - assert_contains_opcode("test", YARVINSN_branchnil); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - v15:CBool = IsNil v9 - IfTrue v15, bb3(v8, v9, v9) - v18:BasicObject = SendWithoutBlock v9, :itself - Jump bb3(v8, v9, v18) - bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject): - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_invokebuiltin_delegate_annotated() { - assert_contains_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave); - assert_snapshot!(hir_string("Float"), @r" - fn Float@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - v20:Float = InvokeBuiltin rb_f_float, v12, v13, v14 - Jump bb3(v12, v13, v14, v15, v20) - bb3(v22:BasicObject, v23:BasicObject, v24:BasicObject, v25:BasicObject, v26:Float): - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_invokebuiltin_cexpr_annotated() { - assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); - assert_snapshot!(hir_string("class"), @r" - fn class@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:Class = InvokeBuiltin leaf _bi20, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:Class): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invokebuiltin_delegate_with_args() { - // Using an unannotated builtin to test InvokeBuiltin generation - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); - assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn open@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:BasicObject = GetLocal l0, SP@7 - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 - v6:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5, v6) - bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): - EntryPoint JIT(0) - v14:NilClass = Const Value(nil) - Jump bb2(v9, v10, v11, v12, v13, v14) - bb2(v16:BasicObject, v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:NilClass): - v26:BasicObject = InvokeBuiltin dir_s_open, v16, v17, v18 - PatchPoint NoEPEscape(open) - GuardBlockParamProxy l0 - v33:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - CheckInterrupts - v36:CBool[true] = Test v33 - IfFalse v36, bb3(v16, v17, v18, v19, v20, v26) - PatchPoint NoEPEscape(open) - v43:BasicObject = InvokeBlock, v26 - v47:BasicObject = InvokeBuiltin dir_s_close, v16, v26 - CheckInterrupts - Return v43 - bb3(v53, v54, v55, v56, v57, v58): - PatchPoint NoEPEscape(open) - CheckInterrupts - Return v58 - "); - } - - #[test] - fn test_invokebuiltin_delegate_without_args() { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "enable")); - assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate_leave), "iseq GC.enable does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn enable@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin gc_enable, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invokebuiltin_with_args() { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start")); - assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn start@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:BasicObject = GetLocal l0, SP@5 - v5:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): - EntryPoint JIT(0) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject): - v22:FalseClass = Const Value(false) - v24:BasicObject = InvokeBuiltin gc_start_internal, v14, v15, v16, v17, v22 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_invoke_leaf_builtin_symbol_name() { - let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn name@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact = InvokeBuiltin leaf _bi28, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:StringExact): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invoke_leaf_builtin_symbol_to_s() { - let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn to_s@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact = InvokeBuiltin leaf _bi12, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:StringExact): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn dupn() { - eval(" - def test(x) = (x[0, 1] ||= 2) - "); - assert_contains_opcode("test", YARVINSN_dupn); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - v14:Fixnum[0] = Const Value(0) - v15:Fixnum[1] = Const Value(1) - v17:BasicObject = SendWithoutBlock v9, :[], v14, v15 - CheckInterrupts - v20:CBool = Test v17 - IfTrue v20, bb3(v8, v9, v13, v9, v14, v15, v17) - v22:Fixnum[2] = Const Value(2) - v24:BasicObject = SendWithoutBlock v9, :[]=, v14, v15, v22 - CheckInterrupts - Return v22 - bb3(v30:BasicObject, v31:BasicObject, v32:NilClass, v33:BasicObject, v34:Fixnum[0], v35:Fixnum[1], v36:BasicObject): - CheckInterrupts - Return v36 - "); - } - - #[test] - fn test_objtostring_anytostring() { - eval(" - def test = \"#{1}\" - "); - assert_contains_opcode("test", YARVINSN_objtostring); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_string_concat() { - eval(r##" - def test = "#{1}#{2}#{3}" - "##); - assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v20:Fixnum[3] = Const Value(3) - v22:BasicObject = ObjToString v20 - v24:String = AnyToString v20, str: v22 - v26:StringExact = StringConcat v14, v19, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_string_concat_empty() { - eval(r##" - def test = "#{}" - "##); - assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:NilClass = Const Value(nil) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_toregexp() { - eval(r##" - def test = /#{1}#{2}#{3}/ - "##); - assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v20:Fixnum[3] = Const Value(3) - v22:BasicObject = ObjToString v20 - v24:String = AnyToString v20, str: v22 - v26:RegexpExact = ToRegexp v14, v19, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_toregexp_with_options() { - eval(r##" - def test = /#{1}#{2}/mixn - "##); - assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v21:RegexpExact = ToRegexp v14, v19, MULTILINE|IGNORECASE|EXTENDED|NOENCODING - CheckInterrupts - Return v21 - "); - } - - #[test] - fn throw() { - eval(" - define_method(:throw_return) { return 1 } - define_method(:throw_break) { break 2 } - "); - assert_contains_opcode("throw_return", YARVINSN_throw); - assert_contains_opcode("throw_break", YARVINSN_throw); - assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" - fn block in @:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v12:Fixnum[1] = Const Value(1) - Throw TAG_RETURN, v12 - - fn block in @:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v12:Fixnum[2] = Const Value(2) - Throw TAG_BREAK, v12 - "); - } - - #[test] - fn test_invokeblock() { - eval(r#" - def test - yield - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeBlock - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_invokeblock_with_args() { - eval(r#" - def test(x, y) - yield x, y - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:BasicObject = InvokeBlock, v11, v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_expandarray_no_splat() { - eval(r#" - def test(o) - a, b = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): - v20:ArrayExact = GuardType v13, ArrayExact - v21:CInt64 = ArrayLength v20 - v22:CInt64[2] = GuardBitEquals v21, CInt64(2) - v23:Fixnum[1] = Const Value(1) - v24:BasicObject = ArrayArefFixnum v20, v23 - v25:Fixnum[0] = Const Value(0) - v26:BasicObject = ArrayArefFixnum v20, v25 - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_expandarray_splat() { - eval(r#" - def test(o) - a, *b = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): - SideExit UnhandledYARVInsn(expandarray) - "); - } - - #[test] - fn test_expandarray_splat_post() { - eval(r#" - def test(o) - a, *b, c = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject): - EntryPoint JIT(0) - v10:NilClass = Const Value(nil) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClass, v17:NilClass, v18:NilClass): - SideExit UnhandledYARVInsn(expandarray) - "); - } -} - -#[cfg(test)] -mod graphviz_tests { - use super::*; - use insta::assert_snapshot; - - #[track_caller] - fn hir_string(method: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let mut function = iseq_to_hir(iseq).unwrap(); - function.optimize(); - function.validate().unwrap(); - format!("{}", FunctionGraphvizPrinter::new(&function)) - } - - #[test] - fn test_guard_fixnum_or_fixnum() { - eval(r#" - def test(x, y) = x | y - - test(1, 2) - "#); - assert_snapshot!(hir_string("test"), @r#" - digraph G { # test@<compiled>:2 - node [shape=plaintext]; - mode=hier; overlap=false; splines=true; - bb0 [label=< - - - - - - -
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@5 
v3:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2, v3) 
>]; - bb0:v4 -> bb2:params:n; - bb1 [label=< - - - -
bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v6, v7, v8) 
>]; - bb1:v9 -> bb2:params:n; - bb2 [label=< - - - - - - - - - - -
bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject) 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) 
v26:Fixnum = GuardType v11, Fixnum 
v27:Fixnum = GuardType v12, Fixnum 
v28:Fixnum = FixnumOr v26, v27 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v28 
>]; - } - "#); - } - - #[test] - fn test_multiple_blocks() { - eval(r#" - def test(c) - if c - 3 - else - 4 - end - end - - test(1) - test("x") - "#); - assert_snapshot!(hir_string("test"), @r#" - digraph G { # test@<compiled>:3 - node [shape=plaintext]; - mode=hier; overlap=false; splines=true; - bb0 [label=< - - - - - -
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2) 
>]; - bb0:v3 -> bb2:params:n; - bb1 [label=< - - - -
bb1(v5:BasicObject, v6:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v5, v6) 
>]; - bb1:v7 -> bb2:params:n; - bb2 [label=< - - - - - - - - - - -
bb2(v8:BasicObject, v9:BasicObject) 
PatchPoint NoTracePoint 
CheckInterrupts 
v15:CBool = Test v9 
IfFalse v15, bb3(v8, v9) 
PatchPoint NoTracePoint 
v19:Fixnum[3] = Const Value(3) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v19 
>]; - bb2:v16 -> bb3:params:n; - bb3 [label=< - - - - - - -
bb3(v25:BasicObject, v26:BasicObject) 
PatchPoint NoTracePoint 
v30:Fixnum[4] = Const Value(4) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v30 
>]; - } - "#); - } -} - -#[cfg(test)] -mod opt_tests { - use super::*; - use crate::{hir_strings, options::*}; - use insta::assert_snapshot; - use super::tests::assert_contains_opcode; - - #[track_caller] - fn hir_string_function(function: &Function) -> String { - format!("{}", FunctionPrinter::without_snapshot(function)) - } - - #[track_caller] - fn hir_string_proc(proc: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let mut function = iseq_to_hir(iseq).unwrap(); - function.optimize(); - function.validate().unwrap(); - hir_string_function(&function) - } - - #[track_caller] - fn hir_string(method: &str) -> String { - hir_string_proc(&format!("{}.method(:{})", "self", method)) - } - - #[test] - fn test_fold_iftrue_away() { - eval(" - def test - cond = true - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_iftrue_into_jump() { - eval(" - def test - cond = false - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:FalseClass = Const Value(false) - CheckInterrupts - v33:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_fold_fixnum_add() { - eval(" - def test - 1 + 2 + 3 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v30:Fixnum[3] = Const Value(3) - v16:Fixnum[3] = Const Value(3) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v31:Fixnum[6] = Const Value(6) - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_fold_fixnum_sub() { - eval(" - def test - 5 - 3 - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[5] = Const Value(5) - v11:Fixnum[3] = Const Value(3) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v30:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v31:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_fold_fixnum_sub_large_negative_result() { - eval(" - def test - 0 - 1073741825 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[0] = Const Value(0) - v11:Fixnum[1073741825] = Const Value(1073741825) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v23:Fixnum[-1073741825] = Const Value(-1073741825) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_fold_fixnum_mult() { - eval(" - def test - 6 * 7 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[6] = Const Value(6) - v11:Fixnum[7] = Const Value(7) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v23:Fixnum[42] = Const Value(42) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_fold_fixnum_mult_zero() { - eval(" - def test(n) - 0 * n + n * 0 - end - test 1; test 2 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v33:Fixnum = GuardType v9, Fixnum - v40:Fixnum[0] = Const Value(0) - v18:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v36:Fixnum = GuardType v9, Fixnum - v41:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v42:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v42 - "); - } - - #[test] - fn test_fold_fixnum_less() { - eval(" - def test - if 1 < 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_less_equal() { - eval(" - def test - if 1 <= 2 && 2 <= 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v52:TrueClass = Const Value(true) - CheckInterrupts - v20:Fixnum[2] = Const Value(2) - v21:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v54:TrueClass = Const Value(true) - CheckInterrupts - v32:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_greater() { - eval(" - def test - if 2 > 1 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_greater_equal() { - eval(" - def test - if 2 >= 1 && 2 >= 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v52:TrueClass = Const Value(true) - CheckInterrupts - v20:Fixnum[2] = Const Value(2) - v21:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v54:TrueClass = Const Value(true) - CheckInterrupts - v32:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_eq_false() { - eval(" - def test - if 1 == 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v40:FalseClass = Const Value(false) - CheckInterrupts - v32:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_eq_true() { - eval(" - def test - if 2 == 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_neq_true() { - eval(" - def test - if 1 != 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v41:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_neq_false() { - eval(" - def test - if 2 != 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v41:FalseClass = Const Value(false) - CheckInterrupts - v32:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_replace_guard_if_known_fixnum() { - eval(" - def test(a) - a + 1 - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_param_forms_get_bb_param() { - eval(" - def rest(*array) = array - def kw(k:) = k - def kw_rest(**k) = k - def post(*rest, post) = post - def block(&b) = nil - "); - assert_snapshot!(hir_strings!("rest", "kw", "kw_rest", "block", "post"), @r" - fn rest@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:ArrayExact): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:ArrayExact): - CheckInterrupts - Return v9 - - fn kw@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - CheckInterrupts - Return v11 - - fn kw_rest@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - Return v9 - - fn block@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - CheckInterrupts - Return v13 - - fn post@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@5, * - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:ArrayExact, v12:BasicObject): - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_optimize_top_level_call_into_send_direct() { - eval(" - def foo = [] - def test - foo - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_optimize_nonexistent_top_level_call() { - eval(" - def foo - end - def test - foo - end - test; test - undef :foo - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = SendWithoutBlock v6, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_optimize_private_top_level_call() { - eval(" - def foo = [] - private :foo - def test - foo - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_optimize_top_level_call_with_overloaded_cme() { - eval(" - def test - Integer(3) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v10 - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_optimize_top_level_call_with_args_into_send_direct() { - eval(" - def foo(a, b) = [] - def test - foo 1, 2 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038), v10, v11 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_top_level_sends_into_send_direct() { - eval(" - def foo = [] - def bar = [] - def test - foo - bar - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) - PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1000) - v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_variadic_ccall() { - eval(" - def test - puts 'Hello' - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Object@0x1008) - v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] - v24:BasicObject = CCallVariadic puts@0x1040, v23, v12 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_dont_optimize_fixnum_add_if_redefined() { - eval(" - class Integer - def +(other) - 100 - end - end - def test(a, b) = a + b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :+, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_both_profiled() { - eval(" - def test(a, b) = a + b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumAdd v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_left_profiled() { - eval(" - def test(a) = a + 1 - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_right_profiled() { - eval(" - def test(a) = 1 + a - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v13, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_both_profiled() { - eval(" - def test(a, b) = a < b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:BoolExact = FixnumLt v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_left_profiled() { - eval(" - def test(a) = a < 1 - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v24:Fixnum = GuardType v9, Fixnum - v25:BoolExact = FixnumLt v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_right_profiled() { - eval(" - def test(a) = 1 < a - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v24:Fixnum = GuardType v9, Fixnum - v25:BoolExact = FixnumLt v13, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_literals() { - eval(" - def test() - a = 2 - (1..a) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - v24:RangeExact = NewRangeFixnum v16 NewRangeInclusive v13 - CheckInterrupts - Return v24 - "); - } - - - #[test] - fn test_optimize_new_range_fixnum_exclusive_literals() { - eval(" - def test() - a = 2 - (1...a) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - v24:RangeExact = NewRangeFixnum v16 NewRangeExclusive v13 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_high_guarded() { - eval(" - def test(a) - (1..a) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v13 NewRangeInclusive v21 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_exclusive_high_guarded() { - eval(" - def test(a) - (1...a) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v13 NewRangeExclusive v21 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_low_guarded() { - eval(" - def test(a) - (a..10) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v21 NewRangeInclusive v13 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_exclusive_low_guarded() { - eval(" - def test(a) - (a...10) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v21 NewRangeExclusive v13 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_new_array() { - eval(" - def test() - c = [] - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - v17:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_opt_aref_array() { - eval(" - arr = [1,2,3] - def test(arr) = arr[0] - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = ArrayArefFixnum v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - assert_snapshot!(inspect("test [1,2,3]"), @"1"); - } - - #[test] - fn test_opt_aref_hash() { - eval(" - arr = {0 => 4} - def test(arr) = arr[0] - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v26:HashExact = GuardType v9, HashExact - v27:BasicObject = HashAref v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - assert_snapshot!(inspect("test({0 => 4})"), @"4"); - } - - #[test] - fn test_eliminate_new_range() { - eval(" - def test() - c = (1..2) - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:RangeExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v16:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_do_not_eliminate_new_range_non_fixnum() { - eval(" - def test() - _ = (-'a'..'b') - 0 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v15:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v16:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v18:StringExact = StringCopy v16 - v20:RangeExact = NewRange v15 NewRangeInclusive v18 - PatchPoint NoEPEscape(test) - v25:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_eliminate_new_array_with_elements() { - eval(" - def test(a) - c = [a] - 5 - end - test(1); test(2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - v17:ArrayExact = NewArray v11 - v20:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_eliminate_new_hash() { - eval(" - def test() - c = {} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:HashExact = NewHash - PatchPoint NoEPEscape(test) - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_no_eliminate_new_hash_with_elements() { - eval(" - def test(aval, bval) - c = {a: aval, b: bval} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): - EntryPoint JIT(0) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:NilClass): - v19:StaticSymbol[:a] = Const Value(VALUE(0x1000)) - v20:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v22:HashExact = NewHash v19: v13, v20: v14 - PatchPoint NoEPEscape(test) - v27:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_array_dup() { - eval(" - def test - c = [1, 2] - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:ArrayExact = ArrayDup v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_hash_dup() { - eval(" - def test - c = {a: 1, b: 2} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:HashExact = HashDup v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_putself() { - eval(" - def test() - c = self - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v15:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_eliminate_string_copy() { - eval(r#" - def test() - c = "abc" - 5 - end - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:StringExact = StringCopy v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_fixnum_add() { - eval(" - def test(a, b) - a + b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_sub() { - eval(" - def test(a, b) - a - b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_mul() { - eval(" - def test(a, b) - a * b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_do_not_eliminate_fixnum_div() { - eval(" - def test(a, b) - a / b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v31:Fixnum = FixnumDiv v29, v30 - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_do_not_eliminate_fixnum_mod() { - eval(" - def test(a, b) - a % b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v31:Fixnum = FixnumMod v29, v30 - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_lt() { - eval(" - def test(a, b) - a < b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_le() { - eval(" - def test(a, b) - a <= b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_gt() { - eval(" - def test(a, b) - a > b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_ge() { - eval(" - def test(a, b) - a >= b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_eq() { - eval(" - def test(a, b) - a == b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_neq() { - eval(" - def test(a, b) - a != b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v30:Fixnum = GuardType v11, Fixnum - v31:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_specialize_basic_object_neq() { - eval(" - class C; end - def test(a, b) = a != b - test(C.new, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, !=@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) - PatchPoint NoSingletonClass(C@0x1000) - v32:CBool = IsBitNotEqual v28, v12 - v33:BoolExact = BoxBool v32 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_do_not_eliminate_get_constant_path() { - eval(" - def test() - C - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v14:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v14 - "); - } - - #[test] - fn kernel_itself_const() { - eval(" - def test(x) = x.itself - test(0) # profile - test(1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v22:Fixnum = GuardType v9, Fixnum - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn kernel_itself_known_type() { - eval(" - def test = [].itself - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v11 - "); - } - - #[test] - fn eliminate_kernel_itself() { - eval(" - def test - x = [].itself - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - PatchPoint NoEPEscape(test) - v21:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn eliminate_module_name() { - eval(" - module M; end - def test - x = M.name - 1 - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, M) - v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Module@0x1010) - IncrCounter inline_cfunc_optimized_send_count - v34:StringExact|NilClass = CCall name@0x1048, v29 - PatchPoint NoEPEscape(test) - v21:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn eliminate_array_length() { - eval(" - def test - x = [].length - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall length@0x1038, v14 - v21:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn normal_class_type_inference() { - eval(" - class C; end - def test = C - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn core_classes_type_inference() { - eval(" - def test = [String, Class, Module, BasicObject] - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, String) - v27:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1010, Class) - v30:Class[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1020, Module) - v33:Class[VALUE(0x1028)] = Const Value(VALUE(0x1028)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1030, BasicObject) - v36:Class[VALUE(0x1038)] = Const Value(VALUE(0x1038)) - v19:ArrayExact = NewArray v27, v30, v33, v36 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn module_instances_are_module_exact() { - eval(" - def test = [Enumerable, Kernel] - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Enumerable) - v23:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1010, Kernel) - v26:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - v15:ArrayExact = NewArray v23, v26 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn module_subclasses_are_not_module_exact() { - eval(" - class ModuleSubclass < Module; end - MY_MODULE = ModuleSubclass.new - def test = MY_MODULE - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, MY_MODULE) - v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn eliminate_array_size() { - eval(" - def test - x = [].size - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall size@0x1038, v14 - v21:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn kernel_itself_argc_mismatch() { - eval(" - def test = 1.itself(0) - test rescue 0 - test rescue 0 - "); - // Not specialized - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[0] = Const Value(0) - v13:BasicObject = SendWithoutBlock v10, :itself, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_inline_kernel_block_given_p() { - eval(" - def test = block_given? - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BoolExact = IsBlockGiven - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_kernel_block_given_p_in_block() { - eval(" - TEST = proc { block_given? } - TEST.call - "); - assert_snapshot!(hir_string_proc("TEST"), @r" - fn block in @:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BoolExact = IsBlockGiven - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_elide_kernel_block_given_p() { - eval(" - def test - block_given? - 5 - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_cfunc_optimized_send_count - v14:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v14 - "); - } - - #[test] - fn const_send_direct_integer() { - eval(" - def test(x) = 1.zero? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) - IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi285, v13 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn class_known_send_direct_array() { - eval(" - def test(x) - a = [1,2,3] - a.first - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v18:ArrayExact = ArrayDup v16 - PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - IncrCounter inline_iseq_optimized_send_count - v32:BasicObject = InvokeBuiltin leaf _bi132, v18 - CheckInterrupts - Return v32 - "); - } - - #[test] - fn send_direct_to_module() { - eval(" - module M; end - def test = M.class - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, M) - v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Module@0x1010) - IncrCounter inline_iseq_optimized_send_count - v26:Class = InvokeBuiltin leaf _bi20, v21 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_send_direct_to_instance_method() { - eval(" - class C - def foo = [] - end - - def test(c) = c.foo - c = C.new - test c - test c - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_opt() { - eval(" - def foo(arg=1) = 1 - def test = foo 1 - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = SendWithoutBlock v6, :foo, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_block() { - eval(" - def foo(&block) = 1 - def test = foo {|| } - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn reload_local_across_send() { - eval(" - def foo(&block) = 1 - def test - a = 1 - foo {|| } - a - end - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - SetLocal l0, EP@3, v13 - v18:BasicObject = Send v8, 0x1000, :foo - v19:BasicObject = GetLocal l0, EP@3 - v22:BasicObject = GetLocal l0, EP@3 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_rest() { - eval(" - def foo(*args) = 1 - def test = foo 1 - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = SendWithoutBlock v6, :foo, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_kw() { - eval(" - def foo(a:) = 1 - def test = foo(a: 1) - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_kwrest() { - eval(" - def foo(**args) = 1 - def test = foo(a: 1) - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn string_bytesize_simple() { - eval(" - def test = 'abc'.bytesize - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - v24:Fixnum = CCall bytesize@0x1040, v12 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn dont_replace_get_constant_path_with_empty_ic() { - eval(" - def test = Kernel - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn dont_replace_get_constant_path_with_invalidated_ic() { - eval(" - def test = Kernel - test - Kernel = 5 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn replace_get_constant_path_with_const() { - eval(" - def test = Kernel - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Kernel) - v19:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn replace_nested_get_constant_path_with_const() { - eval(" - module Foo - module Bar - class C - end - end - end - def test = Foo::Bar::C - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:8: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_new_no_initialize() { - eval(" - class C; end - def test = C.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) - v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_initialize() { - eval(" - class C - def initialize x - @x = x - end - end - def test = C.new 1 - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) - v45:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v48:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 - CheckInterrupts - CheckInterrupts - Return v45 - "); - } - - #[test] - fn test_opt_new_object() { - eval(" - def test = Object.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Object) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) - v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) - PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_basic_object() { - eval(" - def test = BasicObject.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, BasicObject) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) - v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) - PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(BasicObject@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_hash() { - eval(" - def test = Hash.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Hash) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) - v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) - v18:BasicObject = SendWithoutBlock v43, :initialize - CheckInterrupts - CheckInterrupts - Return v43 - "); - assert_snapshot!(inspect("test"), @"{}"); - } - - #[test] - fn test_opt_new_array() { - eval(" - def test = Array.new 1 - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Array) - v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v53:BasicObject = CCallVariadic new@0x1048, v42, v13 - CheckInterrupts - Return v53 - "); - } - - #[test] - fn test_opt_new_set() { - eval(" - def test = Set.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Set) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) - v16:HeapBasicObject = ObjectAlloc v40 - PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Set@0x1008) - v46:SetExact = GuardType v16, SetExact - v47:BasicObject = CCallVariadic initialize@0x1070, v46 - CheckInterrupts - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_opt_new_string() { - eval(" - def test = String.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, String) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v51:BasicObject = CCallVariadic new@0x1048, v40 - CheckInterrupts - Return v51 - "); - } - - #[test] - fn test_opt_new_regexp() { - eval(" - def test = Regexp.new '' - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Regexp) - v44:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v15:StringExact = StringCopy v13 - PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) - v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) - PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) - PatchPoint NoSingletonClass(Regexp@0x1008) - v51:BasicObject = CCallVariadic initialize@0x1078, v47, v15 - CheckInterrupts - CheckInterrupts - Return v47 - "); - } - - #[test] - fn test_opt_length() { - eval(" - def test(a,b) = [a,b].length - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall length@0x1038, v17 - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_opt_size() { - eval(" - def test(a,b) = [a,b].size - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall size@0x1038, v17 - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_getblockparamproxy() { - eval(" - def test(&block) = tap(&block) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - GuardBlockParamProxy l0 - v15:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - v17:BasicObject = Send v8, 0x1008, :tap, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_getinstancevariable() { - eval(" - def test = @foo - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - v12:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_setinstancevariable() { - eval(" - def test = @foo = 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_hash() { - eval(" - def test = {}.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_dont_optimize_hash_freeze_if_redefined() { - eval(" - class Hash - def freeze; end - end - def test = {}.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_hash() { - eval(" - def test = {}.freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_hash() { - eval(" - def test = {}.dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v24:BasicObject = CCallWithFrame dup@0x1038, v11 - v15:BasicObject = SendWithoutBlock v24, :freeze - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_no_elide_freeze_hash_with_args() { - eval(" - def test = {}.freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v11, :freeze, v12 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_ary() { - eval(" - def test = [].freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_ary() { - eval(" - def test = [].freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_ary() { - eval(" - def test = [].dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:BasicObject = CCallWithFrame dup@0x1038, v11 - v15:BasicObject = SendWithoutBlock v24, :freeze - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_no_elide_freeze_ary_with_args() { - eval(" - def test = [].freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v11, :freeze, v12 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_str() { - eval(" - def test = ''.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_str() { - eval(" - def test = ''.freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_str() { - eval(" - def test = ''.dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CCallWithFrame dup@0x1040, v12 - v16:BasicObject = SendWithoutBlock v25, :freeze - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_no_elide_freeze_str_with_args() { - eval(" - def test = ''.freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - v13:NilClass = Const Value(nil) - v15:BasicObject = SendWithoutBlock v12, :freeze, v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_elide_uminus_with_frozen_str() { - eval(" - def test = -'' - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_uminus_with_refrozen_str() { - eval(" - def test = -''.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_uminus_with_unfrozen_str() { - eval(" - def test = -''.dup - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CCallWithFrame dup@0x1040, v12 - v16:BasicObject = SendWithoutBlock v25, :-@ - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_objtostring_anytostring_string() { - eval(r##" - def test = "#{('foo')}" - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v15:StringExact = StringCopy v13 - v21:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_objtostring_anytostring_with_non_string() { - eval(r##" - def test = "#{1}" - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_optimize_objtostring_anytostring_recv_profiled() { - eval(" - def test(a) - \"#{a}\" - end - test('foo'); test('foo') - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(String@0x1008) - v26:String = GuardType v9, String - v19:StringExact = StringConcat v13, v26 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_objtostring_anytostring_recv_profiled_string_subclass() { - eval(" - class MyString < String; end - - def test(a) - \"#{a}\" - end - foo = MyString.new('foo') - test(MyString.new(foo)); test(MyString.new(foo)) - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(MyString@0x1008) - v26:String = GuardType v9, String - v19:StringExact = StringConcat v13, v26 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_objtostring_profiled_nonstring_falls_back_to_send() { - eval(" - def test(a) - \"#{a}\" - end - test([1,2,3]); test([1,2,3]) # No fast path for array - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v25:BasicObject = GuardTypeNot v9, String - PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v30:ArrayExact = GuardType v9, ArrayExact - v31:BasicObject = CCallWithFrame to_s@0x1040, v30 - v17:String = AnyToString v9, str: v31 - v19:StringExact = StringConcat v13, v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_branchnil_nil() { - eval(" - def test - x = nil - x&.itself - end - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:NilClass = Const Value(nil) - CheckInterrupts - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_branchnil_truthy() { - eval(" - def test - x = 1 - x&.itself - end - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - CheckInterrupts - PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_dont_eliminate_load_from_non_frozen_array() { - eval(r##" - S = [4,5,6] - def test = S[0] - test - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, S) - v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Array@0x1010) - v28:BasicObject = ArrayArefFixnum v24, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we - // actually do the load at run-time. - } - - #[test] - fn test_eliminate_load_from_frozen_array_in_bounds() { - eval(r##" - def test = [4,5,6].freeze[1] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:Fixnum[5] = Const Value(5) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_negative() { - eval(r##" - def test = [4,5,6].freeze[-3] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[-3] = Const Value(-3) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:Fixnum[4] = Const Value(4) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_negative_out_of_bounds() { - eval(r##" - def test = [4,5,6].freeze[-10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[-10] = Const Value(-10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_out_of_bounds() { - eval(r##" - def test = [4,5,6].freeze[10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_dont_optimize_array_aref_if_redefined() { - eval(r##" - class Array - def [](index) = [] - end - def test = [4,5,6].freeze[10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_dont_optimize_array_max_if_redefined() { - eval(r##" - class Array - def max = [] - end - def test = [4,5,6].max - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v22:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_set_type_from_constant() { - eval(" - MY_SET = Set.new - - def test = MY_SET - - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, MY_SET) - v19:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_regexp_type() { - eval(" - def test = /a/ - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_bmethod_send_direct() { - eval(" - define_method(:zero) { :b } - define_method(:one) { |arg| arg } - - def test = one(zero) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v30:StaticSymbol[:b] = Const Value(VALUE(0x1038)) - PatchPoint SingleRactorMode - PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1000) - v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_symbol_block_bmethod() { - eval(" - define_method(:identity, &:itself) - def test = identity(100) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[100] = Const Value(100) - v12:BasicObject = SendWithoutBlock v6, :identity, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_call_bmethod_with_block() { - eval(" - define_method(:bmethod) { :b } - def test = (bmethod {}) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :bmethod - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_call_shareable_bmethod() { - eval(" - class Foo - class << self - define_method(:identity, &(Ractor.make_shareable ->(val){val})) - end - end - def test = Foo.identity(100) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Foo) - v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:Fixnum[100] = Const Value(100) - PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Class@0x1010) - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_nil_nil_specialized_to_ccall() { - eval(" - def test = nil.nil? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v22:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_nil_nil_specialized_to_ccall() { - eval(" - def test - nil.nil? - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_non_nil_nil_specialized_to_ccall() { - eval(" - def test = 1.nil? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v22:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_non_nil_nil_specialized_to_ccall() { - eval(" - def test - 1.nil? - 2 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[2] = Const Value(2) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_guard_nil_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v24:NilClass = GuardType v9, NilClass - v25:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_false_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(false) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) - v24:FalseClass = GuardType v9, FalseClass - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_true_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(true) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) - v24:TrueClass = GuardType v9, TrueClass - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_symbol_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) - v24:StaticSymbol = GuardType v9, StaticSymbol - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_fixnum_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v24:Fixnum = GuardType v9, Fixnum - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_float_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(1.0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) - v24:Flonum = GuardType v9, Flonum - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_string_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test('foo') - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - v26:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_specialize_basicobject_not_to_ccall() { - eval(" - def test(a) = !a - - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall !@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_array_empty_p_to_ccall() { - eval(" - def test(a) = a.empty? - - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_hash_empty_p_to_ccall() { - eval(" - def test(a) = a.empty? - - test({}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v25:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_basic_object_eq() { - eval(" - class C; end - def test(a, b) = a == b - - test(C.new, C.new) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v29:CBool = IsBitEqual v28, v12 - v30:BoolExact = BoxBool v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_specialize_basic_object_eqq() { - eval(" - class C; end - def test(a, b) = a === b - - test(C.new, C.new) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v26:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) - PatchPoint NoSingletonClass(C@0x1000) - v30:CBool = IsBitEqual v26, v12 - v31:BoolExact = BoxBool v30 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_specialize_nil_eq() { - eval(" - def test(a, b) = a == b - - test(nil, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1008, cme:0x1010) - v27:NilClass = GuardType v11, NilClass - v28:CBool = IsBitEqual v27, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_nil_eqq() { - eval(" - def test(a, b) = a === b - test(nil, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, ===@0x1008, cme:0x1010) - v25:NilClass = GuardType v11, NilClass - PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_true_eqq() { - eval(" - def test(a, b) = a === b - test(true, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_false_eqq() { - eval(" - def test(a, b) = a === b - test(true, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_guard_fixnum_and_fixnum() { - eval(" - def test(x, y) = x & y - - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 28) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumAnd v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_guard_fixnum_or_fixnum() { - eval(" - def test(x, y) = x | y - - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumOr v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_method_redefinition_patch_point_on_top_level_method() { - eval(" - def foo; end - def test = foo - - test; test - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:NilClass = Const Value(nil) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_getivar_embedded() { - eval(" - class C - attr_reader :foo - def initialize - @foo = 42 - end - end - - O = C.new - def test(o) = o.foo - test O - test O - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_getivar_extended() { - eval(" - class C - attr_reader :foo - def initialize - @foo = 42 - end - end - - O = C.new - def test(o) = o.foo - test O - test O - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_optimize_getivar_polymorphic() { - set_call_threshold(3); - eval(" - class C - attr_reader :foo, :bar - - def foo_then_bar - @foo = 1 - @bar = 2 - end - - def bar_then_foo - @bar = 3 - @foo = 4 - end - end - - O1 = C.new - O1.foo_then_bar - O2 = C.new - O2.bar_then_foo - def test(o) = o.foo - test O1 - test O2 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:20: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendWithoutBlock v9, :foo - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_optimize_send_with_block() { - eval(r#" - def test = [1, 2, 3].map { |x| x * 2 } - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v23:BasicObject = CCallWithFrame map@0x1040, v12, block=0x1048 - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_do_not_optimize_send_variadic_with_block() { - eval(r#" - def test = [1, 2, 3].index { |x| x == 2 } - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - v14:BasicObject = Send v12, 0x1008, :index - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_do_not_optimize_send_with_block_forwarding() { - eval(r#" - def test(&block) = [].map(&block) - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = NewArray - GuardBlockParamProxy l0 - v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - v19:BasicObject = Send v14, 0x1008, :map, v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_do_not_optimize_send_to_iseq_method_with_block() { - eval(r#" - def foo - yield 1 - end - - def test = foo {} - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_inline_attr_reader_constant() { - eval(" - class C - attr_reader :foo - end - - O = C.new - def test = O.foo - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(C@0x1010) - v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v27:NilClass = Const Value(nil) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_inline_attr_accessor_constant() { - eval(" - class C - attr_accessor :foo - end - - O = C.new - def test = O.foo - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(C@0x1010) - v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v27:NilClass = Const Value(nil) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_inline_attr_reader() { - eval(" - class C - attr_reader :foo - end - - def test(o) = o.foo - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:NilClass = Const Value(nil) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_inline_attr_accessor() { - eval(" - class C - attr_accessor :foo - end - - def test(o) = o.foo - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:NilClass = Const Value(nil) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_inline_attr_accessor_set() { - eval(" - class C - attr_accessor :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_inline_attr_writer_set() { - eval(" - class C - attr_writer :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_array_reverse_returns_array() { - eval(r#" - def test = [].reverse - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_array_reverse_is_elidable() { - eval(r#" - def test - [].reverse - 5 - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v16:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_array_join_returns_string() { - eval(r#" - def test = [].join "," - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v14:StringExact = StringCopy v12 - PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v25:StringExact = CCallVariadic join@0x1040, v11, v14 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_string_to_s_returns_string() { - eval(r#" - def test = "".to_s - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_string_subclass_to_s_returns_string_exact() { - eval(r#" - class C < String; end - def test(o) = o.to_s - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, to_s@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v23:StringSubclass[class_exact:C] = GuardType v9, StringSubclass[class_exact:C] - v24:StringExact = CCallWithFrame to_s@0x1038, v23 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_string_literal_to_s() { - eval(r#" - def test = "foo".to_s - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_inline_profiled_string_to_s() { - eval(r#" - def test(o) = o.to_s - test "foo" - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v23:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_array_aref_fixnum_literal() { - eval(" - def test - arr = [1, 2, 3] - arr[0] - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:ArrayExact = ArrayDup v13 - v18:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v31:BasicObject = ArrayArefFixnum v15, v18 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_array_aref_fixnum_profiled() { - eval(" - def test(arr, idx) - arr[idx] - end - test([1, 2, 3], 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v28:ArrayExact = GuardType v11, ArrayExact - v29:Fixnum = GuardType v12, Fixnum - v30:BasicObject = ArrayArefFixnum v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_array_aref_fixnum_array_subclass() { - eval(" - class C < Array; end - def test(arr, idx) - arr[idx] - end - test(C.new([1, 2, 3]), 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] - v29:Fixnum = GuardType v12, Fixnum - v30:BasicObject = ArrayArefFixnum v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_hash_aref_literal() { - eval(" - def test - arr = {1 => 3} - arr[1] - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:HashExact = HashDup v13 - v18:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Hash@0x1008) - v31:BasicObject = HashAref v15, v18 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_hash_aref_profiled() { - eval(" - def test(hash, key) - hash[key] - end - test({1 => 3}, 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v28:HashExact = GuardType v11, HashExact - v29:BasicObject = HashAref v28, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_hash_aref_subclass() { - eval(" - class C < Hash; end - def test(hash, key) - hash[key] - end - test(C.new({0 => 3}), 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] - v29:BasicObject = HashAref v28, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_does_not_fold_hash_aref_with_frozen_hash() { - eval(" - H = {a: 0}.freeze - def test = H[:a] - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, H) - v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) - PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) - PatchPoint NoSingletonClass(Hash@0x1018) - v28:BasicObject = HashAref v24, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_thread_current() { - eval(" - def test = Thread.current - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Thread) - v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Class@0x1010) - IncrCounter inline_cfunc_optimized_send_count - v26:BasicObject = CCall current@0x1048, v21 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_array_aset() { - eval(" - def test(arr) - arr[1] = 10 - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[1] = Const Value(1) - v15:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v28:ArrayExact = GuardType v9, ArrayExact - v29:BasicObject = CCallVariadic []=@0x1038, v28, v14, v15 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_optimize_array_ltlt() { - eval(" - def test(arr) - arr << 1 - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - ArrayPush v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_array_push_single_arg() { - eval(" - def test(arr) - arr.push(1) - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:ArrayExact = GuardType v9, ArrayExact - ArrayPush v24, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_do_not_optimize_array_push_multi_arg() { - eval(" - def test(arr) - arr.push(1,2,3) - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v14:Fixnum[2] = Const Value(2) - v15:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_length() { - eval(" - def test(arr) = arr.length - test([]) - "); - assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall length@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_size() { - eval(" - def test(arr) = arr.size - test([]) - "); - assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_pop_no_arg() { - eval(" - def test(arr) = arr.pop - test([1]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v23:ArrayExact = GuardType v9, ArrayExact - v24:ArrayExact = GuardNotFrozen v23 - v25:BasicObject = ArrayPop v24 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_do_not_optimize_array_pop_arg() { - eval(" - def test(arr) = arr.pop(4) - test([1]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[4] = Const Value(4) - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:ArrayExact = GuardType v9, ArrayExact - v25:BasicObject = CCallVariadic pop@0x1038, v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_array_pop_frozen() { - eval(" - def test(arr) - arr.pop - rescue FrozenError - nil - end - arr = [1].freeze - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v23:ArrayExact = GuardType v9, ArrayExact - v24:ArrayExact = GuardNotFrozen v23 - v25:BasicObject = ArrayPop v24 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_regexpmatch2() { - eval(r#" - def test(s) = s =~ /a/ - test("foo") - "#); - assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v26:StringExact = GuardType v9, StringExact - v27:BasicObject = CCallWithFrame =~@0x1040, v26, v13 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_string_getbyte_fixnum() { - eval(r#" - def test(s, i) = s.getbyte(i) - test("foo", 0) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:Fixnum = GuardType v12, Fixnum - v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_elide_string_getbyte_fixnum() { - eval(r#" - def test(s, i) - s.getbyte(i) - 5 - end - test("foo", 0) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v29:StringExact = GuardType v11, StringExact - v30:Fixnum = GuardType v12, Fixnum - IncrCounter inline_cfunc_optimized_send_count - v20:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_specialize_string_empty() { - eval(r#" - def test(s) - s.empty? - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_string_empty() { - eval(r#" - def test(s) - s.empty? - 4 - end - test("this should get removed") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_inline_integer_succ_with_fixnum() { - eval(" - def test(x) = x.succ - test(4) - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum[1] = Const Value(1) - v26:Fixnum = FixnumAdd v24, v25 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_inline_integer_succ_with_bignum() { - eval(" - def test(x) = x.succ - test(4 << 70) - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) - v24:Integer = GuardType v9, Integer - v25:BasicObject = CCallWithFrame succ@0x1038, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_string_append() { - eval(r#" - def test(x, y) = x << y - test("iron", "fish") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:StringExact = StringAppend v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - // TODO: This should be inlined just as in the interpreter - #[test] - fn test_optimize_string_append_non_string() { - eval(r#" - def test(x, y) = x << y - test("iron", 4) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_string_append_string_subclass() { - eval(r#" - class MyString < String - end - def test(x, y) = x << y - test("iron", MyString.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:StringExact = StringAppend v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_do_not_optimize_string_subclass_append_string() { - eval(r#" - class MyString < String - end - def test(x, y) = x << y - test(MyString.new, "iron") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(MyString@0x1000) - v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] - v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_dont_inline_integer_succ_with_args() { - eval(" - def test = 4.succ 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[4] = Const Value(4) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = SendWithoutBlock v10, :succ, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_inline_integer_xor_with_fixnum() { - eval(" - def test(x, y) = x ^ y - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Fixnum = GuardType v11, Fixnum - v26:Fixnum = GuardType v12, Fixnum - v27:Fixnum = FixnumXor v25, v26 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_integer_xor() { - eval(r#" - def test(x, y) - x ^ y - 42 - end - test(1, 2) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v28:Fixnum = GuardType v11, Fixnum - v29:Fixnum = GuardType v12, Fixnum - IncrCounter inline_cfunc_optimized_send_count - v20:Fixnum[42] = Const Value(42) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_dont_inline_integer_xor_with_bignum_or_boolean() { - eval(" - def test(x, y) = x ^ y - test(4 << 70, 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Integer = GuardType v11, Integer - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - - eval(" - def test(x, y) = x ^ y - test(1, 4 << 70) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Fixnum = GuardType v11, Fixnum - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - - eval(" - def test(x, y) = x ^ y - test(true, 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_inline_integer_xor_with_args() { - eval(" - def test(x, y) = x.^() - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:BasicObject = SendWithoutBlock v11, :^ - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_specialize_hash_size() { - eval(" - def test(hash) = hash.size - test({foo: 3, bar: 1, baz: 4}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v25:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_hash_size() { - eval(" - def test(hash) - hash.size - 5 - end - test({foo: 3, bar: 1, baz: 4}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v28:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_respond_to_p_true() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v28:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_no_method() { - eval(r#" - class C - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) - PatchPoint NoSingletonClass(C@0x1008) - v30:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_default_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v28:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, false) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:FalseClass = Const Value(false) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_falsy_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, nil) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_true_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, true) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:TrueClass = Const Value(true) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_truthy() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo, 4) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:Fixnum[4] = Const Value(4) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_falsy() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo, nil) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_missing() { - eval(r#" - class C - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) - PatchPoint NoSingletonClass(C@0x1008) - v30:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_do_not_optimize_redefined_respond_to_missing() { - eval(r#" - class C - def respond_to_missing?(method, include_private = false) - true - end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putself() { - eval(r#" - def callee = self - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_string() { - eval(r#" - # frozen_string_literal: true - def callee = "abc" - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putnil() { - eval(r#" - def callee = nil - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:NilClass = Const Value(nil) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_true() { - eval(r#" - def callee = true - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:TrueClass = Const Value(true) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_false() { - eval(r#" - def callee = false - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:FalseClass = Const Value(false) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_zero() { - eval(r#" - def callee = 0 - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_one() { - eval(r#" - def callee = 1 - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_parameter() { - eval(r#" - def callee(x) = x - def test = callee 3 - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_inline_send_without_block_direct_last_parameter() { - eval(r#" - def callee(x, y, z) = z - def test = callee 1, 2, 3 - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - v12:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_inline_symbol_to_sym() { - eval(r#" - def test(o) = o.to_sym - test :foo - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_sym@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_kernel_frozen_p() { - eval(r#" - def test(o) = o.frozen? - test :foo - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, frozen?@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:BoolExact = InvokeBuiltin leaf _bi69, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_integer_to_i() { - eval(r#" - def test(o) = o.to_i - test 5 - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, to_i@0x1008, cme:0x1010) - v21:Fixnum = GuardType v9, Fixnum - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_symbol_name() { - eval(" - def test(x) = x.to_s - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:StringExact = InvokeBuiltin leaf _bi12, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_symbol_to_s() { - eval(" - def test(x) = x.to_s - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:StringExact = InvokeBuiltin leaf _bi12, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_optimize_stringexact_eq_stringexact() { - eval(r#" - def test(l, r) = l == r - test("a", "b") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_string_eq_string() { - eval(r#" - class C < String - end - def test(l, r) = l == r - test(C.new("a"), C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_stringexact_eq_string() { - eval(r#" - class C < String - end - def test(l, r) = l == r - test("a", C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_stringexact_eqq_stringexact() { - eval(r#" - def test(l, r) = l === r - test("a", "b") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_string_eqq_string() { - eval(r#" - class C < String - end - def test(l, r) = l === r - test(C.new("a"), C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_stringexact_eqq_string() { - eval(r#" - class C < String - end - def test(l, r) = l === r - test("a", C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_specialize_string_size() { - eval(r#" - def test(s) - s.size - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_elide_string_size() { - eval(r#" - def test(s) - s.size - 5 - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_specialize_string_bytesize() { - eval(r#" - def test(s) - s.bytesize - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v23:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall bytesize@0x1038, v23 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_elide_string_bytesize() { - eval(r#" - def test(s) - s.bytesize - 5 - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_specialize_string_length() { - eval(r#" - def test(s) - s.length - end - test("asdf") + test(1) + test("x") "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall length@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_elide_string_length() { - eval(r#" - def test(s) - s.length - 4 - end - test("this should get removed") + assert_snapshot!(hir_string("test"), @r#" + digraph G { # test@<compiled>:3 + node [shape=plaintext]; + mode=hier; overlap=false; splines=true; + bb0 [label=< + + + + + +
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2) 
>]; + bb0:v3 -> bb2:params:n; + bb1 [label=< + + + +
bb1(v5:BasicObject, v6:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v5, v6) 
>]; + bb1:v7 -> bb2:params:n; + bb2 [label=< + + + + + + + + + + +
bb2(v8:BasicObject, v9:BasicObject) 
PatchPoint NoTracePoint 
CheckInterrupts 
v15:CBool = Test v9 
IfFalse v15, bb3(v8, v9) 
PatchPoint NoTracePoint 
v19:Fixnum[3] = Const Value(3) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v19 
>]; + bb2:v16 -> bb3:params:n; + bb3 [label=< + + + + + + +
bb3(v25:BasicObject, v26:BasicObject) 
PatchPoint NoTracePoint 
v30:Fixnum[4] = Const Value(4) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v30 
>]; + } "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v19 - "); } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs new file mode 100644 index 00000000000000..897cd6e9566966 --- /dev/null +++ b/zjit/src/hir/opt_tests.rs @@ -0,0 +1,7050 @@ +#[cfg(test)] +mod hir_opt_tests { + use crate::hir::*; + + use crate::{hir_strings, options::*}; + use insta::assert_snapshot; + use crate::hir::tests::hir_build_tests::assert_contains_opcode; + + #[track_caller] + fn hir_string_function(function: &Function) -> String { + format!("{}", FunctionPrinter::without_snapshot(function)) + } + + #[track_caller] + fn hir_string_proc(proc: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let mut function = iseq_to_hir(iseq).unwrap(); + function.optimize(); + function.validate().unwrap(); + hir_string_function(&function) + } + + #[track_caller] + fn hir_string(method: &str) -> String { + hir_string_proc(&format!("{}.method(:{})", "self", method)) + } + + #[test] + fn test_fold_iftrue_away() { + eval(" + def test + cond = true + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_iftrue_into_jump() { + eval(" + def test + cond = false + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:FalseClass = Const Value(false) + CheckInterrupts + v33:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v33 + "); + } + + #[test] + fn test_fold_fixnum_add() { + eval(" + def test + 1 + 2 + 3 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v30:Fixnum[3] = Const Value(3) + v16:Fixnum[3] = Const Value(3) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v31:Fixnum[6] = Const Value(6) + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_fold_fixnum_sub() { + eval(" + def test + 5 - 3 - 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + v11:Fixnum[3] = Const Value(3) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v30:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v31:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_fold_fixnum_sub_large_negative_result() { + eval(" + def test + 0 - 1073741825 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[0] = Const Value(0) + v11:Fixnum[1073741825] = Const Value(1073741825) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v23:Fixnum[-1073741825] = Const Value(-1073741825) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_fold_fixnum_mult() { + eval(" + def test + 6 * 7 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[6] = Const Value(6) + v11:Fixnum[7] = Const Value(7) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v23:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_fold_fixnum_mult_zero() { + eval(" + def test(n) + 0 * n + n * 0 + end + test 1; test 2 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v33:Fixnum = GuardType v9, Fixnum + v40:Fixnum[0] = Const Value(0) + v18:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v36:Fixnum = GuardType v9, Fixnum + v41:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v42:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v42 + "); + } + + #[test] + fn test_fold_fixnum_less() { + eval(" + def test + if 1 < 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_less_equal() { + eval(" + def test + if 1 <= 2 && 2 <= 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v52:TrueClass = Const Value(true) + CheckInterrupts + v20:Fixnum[2] = Const Value(2) + v21:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v54:TrueClass = Const Value(true) + CheckInterrupts + v32:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_greater() { + eval(" + def test + if 2 > 1 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_greater_equal() { + eval(" + def test + if 2 >= 1 && 2 >= 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v52:TrueClass = Const Value(true) + CheckInterrupts + v20:Fixnum[2] = Const Value(2) + v21:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v54:TrueClass = Const Value(true) + CheckInterrupts + v32:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_eq_false() { + eval(" + def test + if 1 == 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v40:FalseClass = Const Value(false) + CheckInterrupts + v32:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_eq_true() { + eval(" + def test + if 2 == 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_neq_true() { + eval(" + def test + if 1 != 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v41:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_neq_false() { + eval(" + def test + if 2 != 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v41:FalseClass = Const Value(false) + CheckInterrupts + v32:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_replace_guard_if_known_fixnum() { + eval(" + def test(a) + a + 1 + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_param_forms_get_bb_param() { + eval(" + def rest(*array) = array + def kw(k:) = k + def kw_rest(**k) = k + def post(*rest, post) = post + def block(&b) = nil + "); + assert_snapshot!(hir_strings!("rest", "kw", "kw_rest", "block", "post"), @r" + fn rest@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:ArrayExact): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:ArrayExact): + CheckInterrupts + Return v9 + + fn kw@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + CheckInterrupts + Return v11 + + fn kw_rest@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + Return v9 + + fn block@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + CheckInterrupts + Return v13 + + fn post@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@5, * + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:ArrayExact, v12:BasicObject): + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_optimize_top_level_call_into_send_direct() { + eval(" + def foo = [] + def test + foo + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_optimize_nonexistent_top_level_call() { + eval(" + def foo + end + def test + foo + end + test; test + undef :foo + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = SendWithoutBlock v6, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_optimize_private_top_level_call() { + eval(" + def foo = [] + private :foo + def test + foo + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_optimize_top_level_call_with_overloaded_cme() { + eval(" + def test + Integer(3) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v10 + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_optimize_top_level_call_with_args_into_send_direct() { + eval(" + def foo(a, b) = [] + def test + foo 1, 2 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038), v10, v11 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_top_level_sends_into_send_direct() { + eval(" + def foo = [] + def bar = [] + def test + foo + bar + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) + PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_variadic_ccall() { + eval(" + def test + puts 'Hello' + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Object@0x1008) + v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] + v24:BasicObject = CCallVariadic puts@0x1040, v23, v12 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_optimize_fixnum_add_if_redefined() { + eval(" + class Integer + def +(other) + 100 + end + end + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :+, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_both_profiled() { + eval(" + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumAdd v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_left_profiled() { + eval(" + def test(a) = a + 1 + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_right_profiled() { + eval(" + def test(a) = 1 + a + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v13, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_both_profiled() { + eval(" + def test(a, b) = a < b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:BoolExact = FixnumLt v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_left_profiled() { + eval(" + def test(a) = a < 1 + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v24:Fixnum = GuardType v9, Fixnum + v25:BoolExact = FixnumLt v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_right_profiled() { + eval(" + def test(a) = 1 < a + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v24:Fixnum = GuardType v9, Fixnum + v25:BoolExact = FixnumLt v13, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_literals() { + eval(" + def test() + a = 2 + (1..a) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + v24:RangeExact = NewRangeFixnum v16 NewRangeInclusive v13 + CheckInterrupts + Return v24 + "); + } + + + #[test] + fn test_optimize_new_range_fixnum_exclusive_literals() { + eval(" + def test() + a = 2 + (1...a) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + v24:RangeExact = NewRangeFixnum v16 NewRangeExclusive v13 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_high_guarded() { + eval(" + def test(a) + (1..a) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v13 NewRangeInclusive v21 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_exclusive_high_guarded() { + eval(" + def test(a) + (1...a) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v13 NewRangeExclusive v21 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_low_guarded() { + eval(" + def test(a) + (a..10) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v21 NewRangeInclusive v13 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_exclusive_low_guarded() { + eval(" + def test(a) + (a...10) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v21 NewRangeExclusive v13 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_new_array() { + eval(" + def test() + c = [] + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_opt_aref_array() { + eval(" + arr = [1,2,3] + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = ArrayArefFixnum v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test [1,2,3]"), @"1"); + } + + #[test] + fn test_opt_aref_hash() { + eval(" + arr = {0 => 4} + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v26:HashExact = GuardType v9, HashExact + v27:BasicObject = HashAref v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test({0 => 4})"), @"4"); + } + + #[test] + fn test_eliminate_new_range() { + eval(" + def test() + c = (1..2) + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:RangeExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_do_not_eliminate_new_range_non_fixnum() { + eval(" + def test() + _ = (-'a'..'b') + 0 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v15:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v16:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:StringExact = StringCopy v16 + v20:RangeExact = NewRange v15 NewRangeInclusive v18 + PatchPoint NoEPEscape(test) + v25:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_eliminate_new_array_with_elements() { + eval(" + def test(a) + c = [a] + 5 + end + test(1); test(2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + v17:ArrayExact = NewArray v11 + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_eliminate_new_hash() { + eval(" + def test() + c = {} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:HashExact = NewHash + PatchPoint NoEPEscape(test) + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_no_eliminate_new_hash_with_elements() { + eval(" + def test(aval, bval) + c = {a: aval, b: bval} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): + EntryPoint JIT(0) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:NilClass): + v19:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v20:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v22:HashExact = NewHash v19: v13, v20: v14 + PatchPoint NoEPEscape(test) + v27:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_array_dup() { + eval(" + def test + c = [1, 2] + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_hash_dup() { + eval(" + def test + c = {a: 1, b: 2} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:HashExact = HashDup v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_putself() { + eval(" + def test() + c = self + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v15:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_eliminate_string_copy() { + eval(r#" + def test() + c = "abc" + 5 + end + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:StringExact = StringCopy v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_fixnum_add() { + eval(" + def test(a, b) + a + b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_sub() { + eval(" + def test(a, b) + a - b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_mul() { + eval(" + def test(a, b) + a * b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_fixnum_div() { + eval(" + def test(a, b) + a / b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v31:Fixnum = FixnumDiv v29, v30 + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_fixnum_mod() { + eval(" + def test(a, b) + a % b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v31:Fixnum = FixnumMod v29, v30 + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_lt() { + eval(" + def test(a, b) + a < b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_le() { + eval(" + def test(a, b) + a <= b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_gt() { + eval(" + def test(a, b) + a > b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_ge() { + eval(" + def test(a, b) + a >= b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_eq() { + eval(" + def test(a, b) + a == b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_neq() { + eval(" + def test(a, b) + a != b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v30:Fixnum = GuardType v11, Fixnum + v31:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_get_constant_path() { + eval(" + def test() + C + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + v14:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v14 + "); + } + + #[test] + fn kernel_itself_const() { + eval(" + def test(x) = x.itself + test(0) # profile + test(1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) + v22:Fixnum = GuardType v9, Fixnum + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn kernel_itself_known_type() { + eval(" + def test = [].itself + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v11 + "); + } + + #[test] + fn eliminate_kernel_itself() { + eval(" + def test + x = [].itself + 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + PatchPoint NoEPEscape(test) + v21:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn eliminate_module_name() { + eval(" + module M; end + def test + x = M.name + 1 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, M) + v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Module@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v34:StringExact|NilClass = CCall name@0x1048, v29 + PatchPoint NoEPEscape(test) + v21:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn eliminate_array_length() { + eval(" + def test + x = [].length + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v14 + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn normal_class_type_inference() { + eval(" + class C; end + def test = C + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn core_classes_type_inference() { + eval(" + def test = [String, Class, Module, BasicObject] + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v27:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Class) + v30:Class[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1020, Module) + v33:Class[VALUE(0x1028)] = Const Value(VALUE(0x1028)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1030, BasicObject) + v36:Class[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + v19:ArrayExact = NewArray v27, v30, v33, v36 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn module_instances_are_module_exact() { + eval(" + def test = [Enumerable, Kernel] + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Enumerable) + v23:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Kernel) + v26:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v15:ArrayExact = NewArray v23, v26 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn module_subclasses_are_not_module_exact() { + eval(" + class ModuleSubclass < Module; end + MY_MODULE = ModuleSubclass.new + def test = MY_MODULE + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_MODULE) + v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn eliminate_array_size() { + eval(" + def test + x = [].size + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v14 + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn kernel_itself_argc_mismatch() { + eval(" + def test = 1.itself(0) + test rescue 0 + test rescue 0 + "); + // Not specialized + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[0] = Const Value(0) + v13:BasicObject = SendWithoutBlock v10, :itself, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_inline_kernel_block_given_p() { + eval(" + def test = block_given? + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_kernel_block_given_p_in_block() { + eval(" + TEST = proc { block_given? } + TEST.call + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_elide_kernel_block_given_p() { + eval(" + def test + block_given? + 5 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_cfunc_optimized_send_count + v14:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v14 + "); + } + + #[test] + fn const_send_direct_integer() { + eval(" + def test(x) = 1.zero? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi285, v13 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn class_known_send_direct_array() { + eval(" + def test(x) + a = [1,2,3] + a.first + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v18:ArrayExact = ArrayDup v16 + PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + IncrCounter inline_iseq_optimized_send_count + v32:BasicObject = InvokeBuiltin leaf _bi132, v18 + CheckInterrupts + Return v32 + "); + } + + #[test] + fn send_direct_to_module() { + eval(" + module M; end + def test = M.class + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, M) + v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Module@0x1010) + IncrCounter inline_iseq_optimized_send_count + v26:Class = InvokeBuiltin leaf _bi20, v21 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_send_direct_to_instance_method() { + eval(" + class C + def foo = [] + end + + def test(c) = c.foo + c = C.new + test c + test c + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_opt() { + eval(" + def foo(arg=1) = 1 + def test = foo 1 + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_block() { + eval(" + def foo(&block) = 1 + def test = foo {|| } + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn reload_local_across_send() { + eval(" + def foo(&block) = 1 + def test + a = 1 + foo {|| } + a + end + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + SetLocal l0, EP@3, v13 + v18:BasicObject = Send v8, 0x1000, :foo + v19:BasicObject = GetLocal l0, EP@3 + v22:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_rest() { + eval(" + def foo(*args) = 1 + def test = foo 1 + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_kw() { + eval(" + def foo(a:) = 1 + def test = foo(a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_kwrest() { + eval(" + def foo(**args) = 1 + def test = foo(a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn string_bytesize_simple() { + eval(" + def test = 'abc'.bytesize + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + v24:Fixnum = CCall bytesize@0x1040, v12 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn dont_replace_get_constant_path_with_empty_ic() { + eval(" + def test = Kernel + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn dont_replace_get_constant_path_with_invalidated_ic() { + eval(" + def test = Kernel + test + Kernel = 5 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn replace_get_constant_path_with_const() { + eval(" + def test = Kernel + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Kernel) + v19:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn replace_nested_get_constant_path_with_const() { + eval(" + module Foo + module Bar + class C + end + end + end + def test = Foo::Bar::C + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:8: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Foo::Bar::C) + v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_new_no_initialize() { + eval(" + class C; end + def test = C.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_initialize() { + eval(" + class C + def initialize x + @x = x + end + end + def test = C.new 1 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + v45:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v48:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 + CheckInterrupts + CheckInterrupts + Return v45 + "); + } + + #[test] + fn test_opt_new_object() { + eval(" + def test = Object.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Object) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) + v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) + PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_basic_object() { + eval(" + def test = BasicObject.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, BasicObject) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) + v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) + PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(BasicObject@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_hash() { + eval(" + def test = Hash.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Hash) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) + v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) + v18:BasicObject = SendWithoutBlock v43, :initialize + CheckInterrupts + CheckInterrupts + Return v43 + "); + assert_snapshot!(inspect("test"), @"{}"); + } + + #[test] + fn test_opt_new_array() { + eval(" + def test = Array.new 1 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Array) + v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Class@0x1040) + v53:BasicObject = CCallVariadic new@0x1048, v42, v13 + CheckInterrupts + Return v53 + "); + } + + #[test] + fn test_opt_new_set() { + eval(" + def test = Set.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Set) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) + v16:HeapBasicObject = ObjectAlloc v40 + PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Set@0x1008) + v46:SetExact = GuardType v16, SetExact + v47:BasicObject = CCallVariadic initialize@0x1070, v46 + CheckInterrupts + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_opt_new_string() { + eval(" + def test = String.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Class@0x1040) + v51:BasicObject = CCallVariadic new@0x1048, v40 + CheckInterrupts + Return v51 + "); + } + + #[test] + fn test_opt_new_regexp() { + eval(" + def test = Regexp.new '' + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Regexp) + v44:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v15:StringExact = StringCopy v13 + PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) + v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) + PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) + PatchPoint NoSingletonClass(Regexp@0x1008) + v51:BasicObject = CCallVariadic initialize@0x1078, v47, v15 + CheckInterrupts + CheckInterrupts + Return v47 + "); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v17 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v17 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_getblockparamproxy() { + eval(" + def test(&block) = tap(&block) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + GuardBlockParamProxy l0 + v15:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v17:BasicObject = Send v8, 0x1008, :tap, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_getinstancevariable() { + eval(" + def test = @foo + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v12:BasicObject = GetIvar v6, :@foo + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_setinstancevariable() { + eval(" + def test = @foo = 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + SetIvar v6, :@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_hash() { + eval(" + def test = {}.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_dont_optimize_hash_freeze_if_redefined() { + eval(" + class Hash + def freeze; end + end + def test = {}.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_hash() { + eval(" + def test = {}.freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_hash() { + eval(" + def test = {}.dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v24:BasicObject = CCallWithFrame dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_no_elide_freeze_hash_with_args() { + eval(" + def test = {}.freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + v12:NilClass = Const Value(nil) + v14:BasicObject = SendWithoutBlock v11, :freeze, v12 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_ary() { + eval(" + def test = [].freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_ary() { + eval(" + def test = [].freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_ary() { + eval(" + def test = [].dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:BasicObject = CCallWithFrame dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_no_elide_freeze_ary_with_args() { + eval(" + def test = [].freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:NilClass = Const Value(nil) + v14:BasicObject = SendWithoutBlock v11, :freeze, v12 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_str() { + eval(" + def test = ''.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_str() { + eval(" + def test = ''.freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_str() { + eval(" + def test = ''.dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CCallWithFrame dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :freeze + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_no_elide_freeze_str_with_args() { + eval(" + def test = ''.freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + v13:NilClass = Const Value(nil) + v15:BasicObject = SendWithoutBlock v12, :freeze, v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_elide_uminus_with_frozen_str() { + eval(" + def test = -'' + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_uminus_with_refrozen_str() { + eval(" + def test = -''.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_uminus_with_unfrozen_str() { + eval(" + def test = -''.dup + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CCallWithFrame dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :-@ + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_objtostring_anytostring_string() { + eval(r##" + def test = "#{('foo')}" + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v15:StringExact = StringCopy v13 + v21:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_objtostring_anytostring_with_non_string() { + eval(r##" + def test = "#{1}" + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_optimize_objtostring_anytostring_recv_profiled() { + eval(" + def test(a) + \"#{a}\" + end + test('foo'); test('foo') + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint NoSingletonClass(String@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_objtostring_anytostring_recv_profiled_string_subclass() { + eval(" + class MyString < String; end + + def test(a) + \"#{a}\" + end + foo = MyString.new('foo') + test(MyString.new(foo)); test(MyString.new(foo)) + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint NoSingletonClass(MyString@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_objtostring_profiled_nonstring_falls_back_to_send() { + eval(" + def test(a) + \"#{a}\" + end + test([1,2,3]); test([1,2,3]) # No fast path for array + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v25:BasicObject = GuardTypeNot v9, String + PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v30:ArrayExact = GuardType v9, ArrayExact + v31:BasicObject = CCallWithFrame to_s@0x1040, v30 + v17:String = AnyToString v9, str: v31 + v19:StringExact = StringConcat v13, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_branchnil_nil() { + eval(" + def test + x = nil + x&.itself + end + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:NilClass = Const Value(nil) + CheckInterrupts + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_branchnil_truthy() { + eval(" + def test + x = 1 + x&.itself + end + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + CheckInterrupts + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_dont_eliminate_load_from_non_frozen_array() { + eval(r##" + S = [4,5,6] + def test = S[0] + test + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, S) + v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Array@0x1010) + v28:BasicObject = ArrayArefFixnum v24, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we + // actually do the load at run-time. + } + + #[test] + fn test_eliminate_load_from_frozen_array_in_bounds() { + eval(r##" + def test = [4,5,6].freeze[1] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:Fixnum[5] = Const Value(5) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_negative() { + eval(r##" + def test = [4,5,6].freeze[-3] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[-3] = Const Value(-3) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:Fixnum[4] = Const Value(4) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_negative_out_of_bounds() { + eval(r##" + def test = [4,5,6].freeze[-10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[-10] = Const Value(-10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_out_of_bounds() { + eval(r##" + def test = [4,5,6].freeze[10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_dont_optimize_array_aref_if_redefined() { + eval(r##" + class Array + def [](index) = [] + end + def test = [4,5,6].freeze[10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_optimize_array_max_if_redefined() { + eval(r##" + class Array + def max = [] + end + def test = [4,5,6].max + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v22:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_set_type_from_constant() { + eval(" + MY_SET = Set.new + + def test = MY_SET + + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_SET) + v19:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_regexp_type() { + eval(" + def test = /a/ + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_bmethod_send_direct() { + eval(" + define_method(:zero) { :b } + define_method(:one) { |arg| arg } + + def test = one(zero) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v30:StaticSymbol[:b] = Const Value(VALUE(0x1038)) + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_symbol_block_bmethod() { + eval(" + define_method(:identity, &:itself) + def test = identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[100] = Const Value(100) + v12:BasicObject = SendWithoutBlock v6, :identity, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_call_bmethod_with_block() { + eval(" + define_method(:bmethod) { :b } + def test = (bmethod {}) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :bmethod + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_call_shareable_bmethod() { + eval(" + class Foo + class << self + define_method(:identity, &(Ractor.make_shareable ->(val){val})) + end + end + def test = Foo.identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Foo) + v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[100] = Const Value(100) + PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_nil_nil_specialized_to_ccall() { + eval(" + def test = nil.nil? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v22:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_nil_nil_specialized_to_ccall() { + eval(" + def test + nil.nil? + 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_non_nil_nil_specialized_to_ccall() { + eval(" + def test = 1.nil? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v22:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_non_nil_nil_specialized_to_ccall() { + eval(" + def test + 1.nil? + 2 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[2] = Const Value(2) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_guard_nil_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v24:NilClass = GuardType v9, NilClass + v25:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_false_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(false) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) + v24:FalseClass = GuardType v9, FalseClass + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_true_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(true) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) + v24:TrueClass = GuardType v9, TrueClass + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_symbol_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) + v24:StaticSymbol = GuardType v9, StaticSymbol + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_fixnum_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_float_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(1.0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) + v24:Flonum = GuardType v9, Flonum + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_string_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test('foo') + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + v26:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_specialize_basicobject_not_to_ccall() { + eval(" + def test(a) = !a + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall !@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_array_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_hash_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test({}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_basic_object_eq_to_ccall() { + eval(" + class C; end + def test(a, b) = a == b + + test(C.new, C.new) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:CBool = IsBitEqual v28, v12 + v30:BoolExact = BoxBool v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_guard_fixnum_and_fixnum() { + eval(" + def test(x, y) = x & y + + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 28) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumAnd v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_guard_fixnum_or_fixnum() { + eval(" + def test(x, y) = x | y + + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumOr v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_method_redefinition_patch_point_on_top_level_method() { + eval(" + def foo; end + def test = foo + + test; test + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_getivar_embedded() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 42 + end + end + + O = C.new + def test(o) = o.foo + test O + test O + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_getivar_extended() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 42 + end + end + + O = C.new + def test(o) = o.foo + test O + test O + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_optimize_getivar_polymorphic() { + set_call_threshold(3); + eval(" + class C + attr_reader :foo, :bar + + def foo_then_bar + @foo = 1 + @bar = 2 + end + + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + + O1 = C.new + O1.foo_then_bar + O2 = C.new + O2.bar_then_foo + def test(o) = o.foo + test O1 + test O2 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:20: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendWithoutBlock v9, :foo + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_optimize_send_with_block() { + eval(r#" + def test = [1, 2, 3].map { |x| x * 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v23:BasicObject = CCallWithFrame map@0x1040, v12, block=0x1048 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_do_not_optimize_send_variadic_with_block() { + eval(r#" + def test = [1, 2, 3].index { |x| x == 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + v14:BasicObject = Send v12, 0x1008, :index + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_do_not_optimize_send_with_block_forwarding() { + eval(r#" + def test(&block) = [].map(&block) + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = NewArray + GuardBlockParamProxy l0 + v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v19:BasicObject = Send v14, 0x1008, :map, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_do_not_optimize_send_to_iseq_method_with_block() { + eval(r#" + def foo + yield 1 + end + + def test = foo {} + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_inline_attr_reader_constant() { + eval(" + class C + attr_reader :foo + end + + O = C.new + def test = O.foo + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_inline_attr_accessor_constant() { + eval(" + class C + attr_accessor :foo + end + + O = C.new + def test = O.foo + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_inline_attr_reader() { + eval(" + class C + attr_reader :foo + end + + def test(o) = o.foo + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_inline_attr_accessor() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_inline_attr_accessor_set() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_inline_attr_writer_set() { + eval(" + class C + attr_writer :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_array_reverse_returns_array() { + eval(r#" + def test = [].reverse + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_array_reverse_is_elidable() { + eval(r#" + def test + [].reverse + 5 + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_array_join_returns_string() { + eval(r#" + def test = [].join "," + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v14:StringExact = StringCopy v12 + PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:StringExact = CCallVariadic join@0x1040, v11, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_string_to_s_returns_string() { + eval(r#" + def test = "".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_string_literal_to_s() { + eval(r#" + def test = "foo".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_profiled_string_to_s() { + eval(r#" + def test(o) = o.to_s + test "foo" + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_array_aref_fixnum_literal() { + eval(" + def test + arr = [1, 2, 3] + arr[0] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v31:BasicObject = ArrayArefFixnum v15, v18 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_array_aref_fixnum_profiled() { + eval(" + def test(arr, idx) + arr[idx] + end + test([1, 2, 3], 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v11, ArrayExact + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_array_aref_fixnum_array_subclass() { + eval(" + class C < Array; end + def test(arr, idx) + arr[idx] + end + test(C.new([1, 2, 3]), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_hash_aref_literal() { + eval(" + def test + arr = {1 => 3} + arr[1] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:HashExact = HashDup v13 + v18:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Hash@0x1008) + v31:BasicObject = HashAref v15, v18 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_hash_aref_profiled() { + eval(" + def test(hash, key) + hash[key] + end + test({1 => 3}, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v11, HashExact + v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_hash_aref_subclass() { + eval(" + class C < Hash; end + def test(hash, key) + hash[key] + end + test(C.new({0 => 3}), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] + v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_does_not_fold_hash_aref_with_frozen_hash() { + eval(" + H = {a: 0}.freeze + def test = H[:a] + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, H) + v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) + PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) + PatchPoint NoSingletonClass(Hash@0x1018) + v28:BasicObject = HashAref v24, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_thread_current() { + eval(" + def test = Thread.current + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Thread) + v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v26:BasicObject = CCall current@0x1048, v21 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_aset() { + eval(" + def test(arr) + arr[1] = 10 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[1] = Const Value(1) + v15:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v9, ArrayExact + v29:BasicObject = CCallVariadic []=@0x1038, v28, v14, v15 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_optimize_array_ltlt() { + eval(" + def test(arr) + arr << 1 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + ArrayPush v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_push_single_arg() { + eval(" + def test(arr) + arr.push(1) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + ArrayPush v24, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_do_not_optimize_array_push_multi_arg() { + eval(" + def test(arr) + arr.push(1,2,3) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v14:Fixnum[2] = Const Value(2) + v15:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_array_length() { + eval(" + def test(arr) = arr.length + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_length); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_array_size() { + eval(" + def test(arr) = arr.size + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_size); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_regexpmatch2() { + eval(r#" + def test(s) = s =~ /a/ + test("foo") + "#); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v26:StringExact = GuardType v9, StringExact + v27:BasicObject = CCallWithFrame =~@0x1040, v26, v13 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_string_getbyte_fixnum() { + eval(r#" + def test(s, i) = s.getbyte(i) + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:Fixnum = GuardType v12, Fixnum + v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_elide_string_getbyte_fixnum() { + eval(r#" + def test(s, i) + s.getbyte(i) + 5 + end + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v29:StringExact = GuardType v11, StringExact + v30:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_specialize_string_empty() { + eval(r#" + def test(s) + s.empty? + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_string_empty() { + eval(r#" + def test(s) + s.empty? + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_inline_integer_succ_with_fixnum() { + eval(" + def test(x) = x.succ + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum[1] = Const Value(1) + v26:Fixnum = FixnumAdd v24, v25 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_bignum() { + eval(" + def test(x) = x.succ + test(4 << 70) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Integer = GuardType v9, Integer + v25:BasicObject = CCallWithFrame succ@0x1038, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_string_append() { + eval(r#" + def test(x, y) = x << y + test("iron", "fish") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + // TODO: This should be inlined just as in the interpreter + #[test] + fn test_optimize_string_append_non_string() { + eval(r#" + def test(x, y) = x << y + test("iron", 4) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_string_append_string_subclass() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test("iron", MyString.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_do_not_optimize_string_subclass_append_string() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test(MyString.new, "iron") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyString@0x1000) + v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_args() { + eval(" + def test = 4.succ 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[4] = Const Value(4) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = SendWithoutBlock v10, :succ, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_inline_integer_xor_with_fixnum() { + eval(" + def test(x, y) = x ^ y + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:Fixnum = GuardType v12, Fixnum + v27:Fixnum = FixnumXor v25, v26 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_integer_xor() { + eval(r#" + def test(x, y) + x ^ y + 42 + end + test(1, 2) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v28:Fixnum = GuardType v11, Fixnum + v29:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count + v20:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_bignum_or_boolean() { + eval(" + def test(x, y) = x ^ y + test(4 << 70, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Integer = GuardType v11, Integer + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(1, 4 << 70) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(true, 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_args() { + eval(" + def test(x, y) = x.^() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:BasicObject = SendWithoutBlock v11, :^ + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_hash_size() { + eval(" + def test(hash) = hash.size + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_hash_size() { + eval(" + def test(hash) + hash.size + 5 + end + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_respond_to_p_true() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_no_method() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_default_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, false) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:FalseClass = Const Value(false) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_true_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, true) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:TrueClass = Const Value(true) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_truthy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, 4) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_missing() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_do_not_optimize_redefined_respond_to_missing() { + eval(r#" + class C + def respond_to_missing?(method, include_private = false) + true + end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putself() { + eval(r#" + def callee = self + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_string() { + eval(r#" + # frozen_string_literal: true + def callee = "abc" + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putnil() { + eval(r#" + def callee = nil + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_true() { + eval(r#" + def callee = true + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:TrueClass = Const Value(true) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_false() { + eval(r#" + def callee = false + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:FalseClass = Const Value(false) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_zero() { + eval(r#" + def callee = 0 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_one() { + eval(r#" + def callee = 1 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_parameter() { + eval(r#" + def callee(x) = x + def test = callee 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_inline_send_without_block_direct_last_parameter() { + eval(r#" + def callee(x, y, z) = z + def test = callee 1, 2, 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + v12:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_symbol_to_sym() { + eval(r#" + def test(o) = o.to_sym + test :foo + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_sym@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_integer_to_i() { + eval(r#" + def test(o) = o.to_i + test 5 + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, to_i@0x1008, cme:0x1010) + v21:Fixnum = GuardType v9, Fixnum + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_optimize_stringexact_eq_stringexact() { + eval(r#" + def test(l, r) = l == r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_string_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_stringexact() { + eval(r#" + def test(l, r) = l === r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_string_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_specialize_string_size() { + eval(r#" + def test(s) + s.size + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_size() { + eval(r#" + def test(s) + s.size + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_specialize_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v25:Fixnum = CCall bytesize@0x1038, v23 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_elide_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_string_length() { + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_length() { + eval(r#" + def test(s) + s.length + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } +} diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs new file mode 100644 index 00000000000000..df14ba5a7c2026 --- /dev/null +++ b/zjit/src/hir/tests.rs @@ -0,0 +1,3207 @@ +#[cfg(test)] +use super::*; + +#[cfg(test)] +mod snapshot_tests { + use super::*; + use insta::assert_snapshot; + + #[track_caller] + fn hir_string(method: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let function = iseq_to_hir(iseq).unwrap(); + format!("{}", FunctionPrinter::with_snapshot(&function)) + } + + #[test] + fn test_new_array_with_elements() { + eval("def test(a, b) = [a, b]"); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v13:Any = Snapshot FrameState { pc: 0x1000, stack: [], locals: [a=v11, b=v12] } + v14:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [a=v11, b=v12] } + PatchPoint NoTracePoint + v16:Any = Snapshot FrameState { pc: 0x1010, stack: [v11, v12], locals: [a=v11, b=v12] } + v17:ArrayExact = NewArray v11, v12 + v18:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } + PatchPoint NoTracePoint + v20:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } + CheckInterrupts + Return v17 + "); + } +} + +#[cfg(test)] +pub mod hir_build_tests { + use super::*; + use insta::assert_snapshot; + + fn iseq_contains_opcode(iseq: IseqPtr, expected_opcode: u32) -> bool { + let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; + let mut insn_idx = 0; + while insn_idx < iseq_size { + // Get the current pc and opcode + let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) }; + + // try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes. + let opcode: u32 = unsafe { rb_iseq_opcode_at_pc(iseq, pc) } + .try_into() + .unwrap(); + if opcode == expected_opcode { + return true; + } + insn_idx += insn_len(opcode as usize); + } + false + } + + #[track_caller] + pub fn assert_contains_opcode(method: &str, opcode: u32) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + } + + #[track_caller] + fn assert_contains_opcodes(method: &str, opcodes: &[u32]) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + for &opcode in opcodes { + assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + } + } + + /// Combine multiple hir_string() results to match all of them at once, which allows + /// us to avoid running the set of zjit-test -> zjit-test-update multiple times. + #[macro_export] + macro_rules! hir_strings { + ($( $s:expr ),+ $(,)?) => {{ + vec![$( hir_string($s) ),+].join("\n") + }}; + } + + #[track_caller] + fn hir_string(method: &str) -> String { + hir_string_proc(&format!("{}.method(:{})", "self", method)) + } + + #[track_caller] + fn hir_string_proc(proc: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let function = iseq_to_hir(iseq).unwrap(); + hir_string_function(&function) + } + + #[track_caller] + fn hir_string_function(function: &Function) -> String { + format!("{}", FunctionPrinter::without_snapshot(function)) + } + + #[track_caller] + fn assert_compile_fails(method: &str, reason: ParseError) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let result = iseq_to_hir(iseq); + assert!(result.is_err(), "Expected an error but successfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap())); + assert_eq!(result.unwrap_err(), reason); + } + + #[test] + fn test_compile_optional() { + eval("def test(x=1) = 123"); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + v3:CPtr = LoadPC + v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) + v5:CBool = IsBitEqual v3, v4 + IfTrue v5, bb2(v1, v2) + Jump bb4(v1, v2) + bb1(v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v9, v10) + bb2(v12:BasicObject, v13:BasicObject): + v15:Fixnum[1] = Const Value(1) + Jump bb4(v12, v15) + bb3(v18:BasicObject, v19:BasicObject): + EntryPoint JIT(1) + Jump bb4(v18, v19) + bb4(v21:BasicObject, v22:BasicObject): + v26:Fixnum[123] = Const Value(123) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_putobject() { + eval("def test = 123"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[123] = Const Value(123) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_new_array() { + eval("def test = []"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_new_array_with_element() { + eval("def test(a) = [a]"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = NewArray v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_new_array_with_elements() { + eval("def test(a, b) = [a, b]"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_new_range_inclusive_with_one_element() { + eval("def test(a) = (a..10)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v15:RangeExact = NewRange v9 NewRangeInclusive v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_new_range_inclusive_with_two_elements() { + eval("def test(a, b) = (a..b)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:RangeExact = NewRange v11 NewRangeInclusive v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_new_range_exclusive_with_one_element() { + eval("def test(a) = (a...10)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v15:RangeExact = NewRange v9 NewRangeExclusive v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_new_range_exclusive_with_two_elements() { + eval("def test(a, b) = (a...b)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:RangeExact = NewRange v11 NewRangeExclusive v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_array_dup() { + eval("def test = [1, 2, 3]"); + assert_contains_opcode("test", YARVINSN_duparray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_hash_dup() { + eval("def test = {a: 1, b: 2}"); + assert_contains_opcode("test", YARVINSN_duphash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:HashExact = HashDup v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_new_hash_empty() { + eval("def test = {}"); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_new_hash_with_elements() { + eval("def test(aval, bval) = {a: aval, b: bval}"); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v16:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v17:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v19:HashExact = NewHash v16: v11, v17: v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_string_copy() { + eval("def test = \"hello\""); + assert_contains_opcode("test", YARVINSN_putchilledstring); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_bignum() { + eval("def test = 999999999999999999999999999999999999"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Bignum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_flonum() { + eval("def test = 1.5"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Flonum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_heap_float() { + eval("def test = 1.7976931348623157e+308"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:HeapFloat[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_static_sym() { + eval("def test = :foo"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_opt_plus() { + eval("def test = 1+2"); + assert_contains_opcode("test", YARVINSN_opt_plus); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + v15:BasicObject = SendWithoutBlock v10, :+, v11 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_opt_hash_freeze() { + eval(" + def test = {}.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_hash_freeze_rewritten() { + eval(" + class Hash + def freeze; 5; end + end + def test = {}.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_ary_freeze() { + eval(" + def test = [].freeze + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_ary_freeze_rewritten() { + eval(" + class Array + def freeze; 5; end + end + def test = [].freeze + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_str_freeze() { + eval(" + def test = ''.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_str_freeze_rewritten() { + eval(" + class String + def freeze; 5; end + end + def test = ''.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_str_uminus() { + eval(" + def test = -'' + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_str_uminus_rewritten() { + eval(" + class String + def -@; 5; end + end + def test = -'' + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)) + "); + } + + #[test] + fn test_setlocal_getlocal() { + eval(" + def test + a = 1 + a + end + "); + assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_nested_setlocal_getlocal() { + eval(" + l3 = 3 + _unused = _unused1 = nil + 1.times do |l2| + _ = nil + l2 = 2 + 1.times do |l1| + l1 = 1 + define_method(:test) do + l1 = l2 + l2 = l1 + l2 + l3 = l2 + l3 + end + end + end + "); + assert_contains_opcodes( + "test", + &[YARVINSN_getlocal_WC_1, YARVINSN_setlocal_WC_1, + YARVINSN_getlocal, YARVINSN_setlocal]); + assert_snapshot!(hir_string("test"), @r" + fn block (3 levels) in @:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:BasicObject = GetLocal l2, EP@4 + SetLocal l1, EP@3, v10 + v14:BasicObject = GetLocal l1, EP@3 + v15:BasicObject = GetLocal l2, EP@4 + v19:BasicObject = SendWithoutBlock v14, :+, v15 + SetLocal l2, EP@4, v19 + v23:BasicObject = GetLocal l2, EP@4 + v24:BasicObject = GetLocal l3, EP@5 + v28:BasicObject = SendWithoutBlock v23, :+, v24 + SetLocal l3, EP@5, v28 + CheckInterrupts + Return v28 + " + ); + } + + #[test] + fn test_setlocal_in_default_args() { + eval(" + def test(a = (b = 1)) = [a, b] + "); + assert_contains_opcode("test", YARVINSN_setlocal_WC_0); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + v4:CPtr = LoadPC + v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) + v6:CBool = IsBitEqual v4, v5 + IfTrue v6, bb2(v1, v2, v3) + Jump bb4(v1, v2, v3) + bb1(v10:BasicObject, v11:BasicObject): + EntryPoint JIT(0) + v12:NilClass = Const Value(nil) + Jump bb2(v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:NilClass): + v20:Fixnum[1] = Const Value(1) + Jump bb4(v14, v20, v20) + bb3(v23:BasicObject, v24:BasicObject): + EntryPoint JIT(1) + v25:NilClass = Const Value(nil) + Jump bb4(v23, v24, v25) + bb4(v27:BasicObject, v28:BasicObject, v29:NilClass|Fixnum): + v34:ArrayExact = NewArray v28, v29 + CheckInterrupts + Return v34 + "); + } + + #[test] + fn test_setlocal_in_default_args_with_tracepoint() { + eval(" + def test(a = (b = 1)) = [a, b] + TracePoint.new(:line) {}.enable + test + "); + assert_compile_fails("test", ParseError::FailedOptionalArguments); + } + + #[test] + fn test_setlocal_in_default_args_with_side_exit() { + eval(" + def test(a = (def foo = nil)) = a + "); + assert_compile_fails("test", ParseError::FailedOptionalArguments); + } + + #[test] + fn test_setlocal_cyclic_default_args() { + eval(" + def test = proc { |a=a| a } + "); + assert_snapshot!(hir_string_proc("test"), @r" + fn block in test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + Return v9 + "); + } + + #[test] + fn defined_ivar() { + eval(" + def test = defined?(@foo) + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact|NilClass = DefinedIvar v6, :@foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn if_defined_ivar() { + eval(" + def test + if defined?(@foo) + 3 + else + 4 + end + end + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:TrueClass|NilClass = DefinedIvar v6, :@foo + CheckInterrupts + v14:CBool = Test v11 + IfFalse v14, bb3(v6) + v18:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v18 + bb3(v24:BasicObject): + v28:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v28 + "); + } + + #[test] + fn defined() { + eval(" + def test = return defined?(SeaChange), defined?(favourite), defined?($ruby) + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:StringExact|NilClass = Defined constant, v10 + v14:StringExact|NilClass = Defined func, v6 + v15:NilClass = Const Value(nil) + v17:StringExact|NilClass = Defined global-variable, v15 + v19:ArrayExact = NewArray v12, v14, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_return_const() { + eval(" + def test(cond) + if cond + 3 + else + 4 + end + end + "); + assert_contains_opcode("test", YARVINSN_leave); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + v15:CBool = Test v9 + IfFalse v15, bb3(v8, v9) + v19:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v19 + bb3(v25:BasicObject, v26:BasicObject): + v30:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_merge_const() { + eval(" + def test(cond) + if cond + result = 3 + else + result = 4 + end + result + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + CheckInterrupts + v18:CBool = Test v11 + IfFalse v18, bb3(v10, v11, v12) + v22:Fixnum[3] = Const Value(3) + PatchPoint NoEPEscape(test) + CheckInterrupts + Jump bb4(v10, v11, v22) + bb3(v28:BasicObject, v29:BasicObject, v30:NilClass): + v34:Fixnum[4] = Const Value(4) + PatchPoint NoEPEscape(test) + Jump bb4(v28, v29, v34) + bb4(v38:BasicObject, v39:BasicObject, v40:Fixnum): + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v40 + "); + } + + #[test] + fn test_opt_plus_fixnum() { + eval(" + def test(a, b) = a + b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_plus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :+, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_minus_fixnum() { + eval(" + def test(a, b) = a - b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_minus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :-, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_mult_fixnum() { + eval(" + def test(a, b) = a * b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_mult); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :*, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_div_fixnum() { + eval(" + def test(a, b) = a / b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :/, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_mod_fixnum() { + eval(" + def test(a, b) = a % b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_mod); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :%, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_eq_fixnum() { + eval(" + def test(a, b) = a == b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :==, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_neq_fixnum() { + eval(" + def test(a, b) = a != b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_neq); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :!=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_lt_fixnum() { + eval(" + def test(a, b) = a < b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :<, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_le_fixnum() { + eval(" + def test(a, b) = a <= b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_le); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :<=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_gt_fixnum() { + eval(" + def test(a, b) = a > b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_gt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :>, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_loop() { + eval(" + def test + result = 0 + times = 10 + while times > 0 + result = result + 1 + times = times - 1 + end + result + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject): + EntryPoint JIT(0) + v7:NilClass = Const Value(nil) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:NilClass, v12:NilClass): + v16:Fixnum[0] = Const Value(0) + v19:Fixnum[10] = Const Value(10) + CheckInterrupts + Jump bb4(v10, v16, v19) + bb4(v25:BasicObject, v26:BasicObject, v27:BasicObject): + PatchPoint NoEPEscape(test) + v31:Fixnum[0] = Const Value(0) + v35:BasicObject = SendWithoutBlock v27, :>, v31 + CheckInterrupts + v38:CBool = Test v35 + IfTrue v38, bb3(v25, v26, v27) + v40:NilClass = Const Value(nil) + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v26 + bb3(v50:BasicObject, v51:BasicObject, v52:BasicObject): + PatchPoint NoEPEscape(test) + v58:Fixnum[1] = Const Value(1) + v62:BasicObject = SendWithoutBlock v51, :+, v58 + v65:Fixnum[1] = Const Value(1) + v69:BasicObject = SendWithoutBlock v52, :-, v65 + Jump bb4(v50, v62, v69) + "); + } + + #[test] + fn test_opt_ge_fixnum() { + eval(" + def test(a, b) = a >= b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_ge); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :>=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_display_types() { + eval(" + def test + cond = true + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:TrueClass = Const Value(true) + CheckInterrupts + v18:CBool[true] = Test v13 + IfFalse v18, bb3(v8, v13) + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + bb3(v28, v29): + v33 = Const Value(4) + CheckInterrupts + Return v33 + "); + } + + #[test] + fn test_send_without_block() { + eval(" + def bar(a, b) + a+b + end + def test + bar(2, 3) + end + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[3] = Const Value(3) + v13:BasicObject = SendWithoutBlock v6, :bar, v10, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_send_with_block() { + eval(" + def test(a) + a.each {|item| + item + } + end + test([1,2,3]) + "); + assert_contains_opcode("test", YARVINSN_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:BasicObject = GetLocal l0, EP@3 + v15:BasicObject = Send v13, 0x1000, :each + v16:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_intern_interpolated_symbol() { + eval(r#" + def test + :"foo#{123}" + end + "#); + assert_contains_opcode("test", YARVINSN_intern); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[123] = Const Value(123) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + v19:Symbol = StringIntern v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn different_objects_get_addresses() { + eval("def test = unknown_method([0], [1], '2', '2')"); + + // The 2 string literals have the same address because they're deduped. + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + v13:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v15:ArrayExact = ArrayDup v13 + v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v18:StringExact = StringCopy v16 + v19:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v21:StringExact = StringCopy v19 + v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_cant_compile_splat() { + eval(" + def test(a) = foo(*a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToArray v9 + SideExit UnhandledCallType(Splat) + "); + } + + #[test] + fn test_compile_block_arg() { + eval(" + def test(a) = foo(&a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = Send v8, 0x1000, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_cant_compile_kwarg() { + eval(" + def test(a) = foo(a: 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn test_cant_compile_kw_splat() { + eval(" + def test(a) = foo(**a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendWithoutBlock v8, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + // TODO(max): Figure out how to generate a call with TAILCALL flag + + #[test] + fn test_compile_super() { + eval(" + def test = super() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeSuper v6, 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_compile_zsuper() { + eval(" + def test = super + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeSuper v6, 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_cant_compile_super_nil_blockarg() { + eval(" + def test = super(&nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:BasicObject = InvokeSuper v6, 0x1000, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_cant_compile_super_forward() { + eval(" + def test(...) = super(...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + SideExit UnhandledYARVInsn(invokesuperforward) + "); + } + + #[test] + fn test_compile_forwardable() { + eval("def forwardable(...) = nil"); + assert_snapshot!(hir_string("forwardable"), @r" + fn forwardable@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + CheckInterrupts + Return v13 + "); + } + + // TODO(max): Figure out how to generate a call with OPT_SEND flag + + #[test] + fn test_cant_compile_kw_splat_mut() { + eval(" + def test(a) = foo **a, b: 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v15:HashExact = NewHash + PatchPoint NoEPEscape(test) + v19:BasicObject = SendWithoutBlock v13, :core#hash_merge_kwd, v15, v9 + v20:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v21:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v22:Fixnum[1] = Const Value(1) + v24:BasicObject = SendWithoutBlock v20, :core#hash_merge_ptr, v19, v21, v22 + v26:BasicObject = SendWithoutBlock v8, :foo, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_cant_compile_splat_mut() { + eval(" + def test(*) = foo *, 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:ArrayExact): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:ArrayExact): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + ArrayPush v14, v15 + SideExit UnhandledCallType(Splat) + "); + } + + #[test] + fn test_compile_forwarding() { + eval(" + def test(...) = foo(...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendForward 0x1000, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_compile_triple_dots_with_positional_args() { + eval(" + def test(a, ...) = foo(a, ...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:ArrayExact = GetLocal l0, SP@7, * + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 + v6:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5, v6) + bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): + EntryPoint JIT(0) + v14:NilClass = Const Value(nil) + Jump bb2(v9, v10, v11, v12, v13, v14) + bb2(v16:BasicObject, v17:BasicObject, v18:ArrayExact, v19:BasicObject, v20:BasicObject, v21:NilClass): + v26:ArrayExact = ToArray v18 + PatchPoint NoEPEscape(test) + GuardBlockParamProxy l0 + v31:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + SideExit UnhandledYARVInsn(splatkw) + "); + } + + #[test] + fn test_opt_new() { + eval(" + class C; end + def test = C.new + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + v12:NilClass = Const Value(nil) + v14:CBool = IsMethodCFunc v11, :new + IfFalse v14, bb3(v6, v12, v11) + v16:HeapBasicObject = ObjectAlloc v11 + v18:BasicObject = SendWithoutBlock v16, :initialize + CheckInterrupts + Jump bb4(v6, v16, v18) + bb3(v22:BasicObject, v23:NilClass, v24:BasicObject): + v27:BasicObject = SendWithoutBlock v24, :new + Jump bb4(v22, v27, v23) + bb4(v29:BasicObject, v30:BasicObject, v31:BasicObject): + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_opt_newarray_send_max_no_elements() { + eval(" + def test = [].max + "); + // TODO(max): Rewrite to nil + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v12:BasicObject = ArrayMax + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_newarray_send_max() { + eval(" + def test(a,b) = [a,b].max + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v18:BasicObject = ArrayMax v11, v12 + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_opt_newarray_send_min() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].min + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(MIN) + "); + } + + #[test] + fn test_opt_newarray_send_hash() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].hash + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(HASH) + "); + } + + #[test] + fn test_opt_newarray_send_pack() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].pack 'C' + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + v28:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v30:StringExact = StringCopy v28 + SideExit UnknownNewarraySend(PACK) + "); + } + + // TODO(max): Add a test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER + + #[test] + fn test_opt_newarray_send_include_p() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].include? b + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(INCLUDE_P) + "); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_contains_opcode("test", YARVINSN_opt_length); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + v21:BasicObject = SendWithoutBlock v17, :length + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_contains_opcode("test", YARVINSN_opt_size); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + v21:BasicObject = SendWithoutBlock v17, :size + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_getinstancevariable() { + eval(" + def test = @foo + test + "); + assert_contains_opcode("test", YARVINSN_getinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v12:BasicObject = GetIvar v6, :@foo + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_setinstancevariable() { + eval(" + def test = @foo = 1 + test + "); + assert_contains_opcode("test", YARVINSN_setinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + SetIvar v6, :@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_set_ivar_rescue_frozen() { + let result = eval(" + class Foo + attr_accessor :bar + def initialize + @bar = 1 + freeze + end + end + + def test(foo) + begin + foo.bar = 2 + rescue FrozenError + end + end + + foo = Foo.new + test(foo) + test(foo) + + foo.bar + "); + assert_eq!(VALUE::fixnum_from_usize(1), result); + } + + #[test] + fn test_getclassvariable() { + eval(" + class Foo + def self.test = @@foo + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetClassVar :@@foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_setclassvariable() { + eval(" + class Foo + def self.test = @@foo = 42 + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[42] = Const Value(42) + SetClassVar :@@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_setglobal() { + eval(" + def test = $foo = 1 + test + "); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SetGlobal :$foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_getglobal() { + eval(" + def test = $foo + test + "); + assert_contains_opcode("test", YARVINSN_getglobal); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetGlobal :$foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_splatarray_mut() { + eval(" + def test(a) = [*a] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_concattoarray() { + eval(" + def test(a) = [1, *a] + "); + assert_contains_opcode("test", YARVINSN_concattoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v15:ArrayExact = NewArray v13 + v17:ArrayExact = ToArray v9 + ArrayExtend v15, v17 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_pushtoarray_one_element() { + eval(" + def test(a) = [*a, 1] + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + ArrayPush v14, v15 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_pushtoarray_multiple_elements() { + eval(" + def test(a) = [*a, 1, 2, 3] + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + v16:Fixnum[2] = Const Value(2) + v17:Fixnum[3] = Const Value(3) + ArrayPush v14, v15 + ArrayPush v14, v16 + ArrayPush v14, v17 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_aset() { + eval(" + def test(a, b) = a[b] = 1 + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v16:NilClass = Const Value(nil) + v17:Fixnum[1] = Const Value(1) + v21:BasicObject = SendWithoutBlock v11, :[]=, v12, v17 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_aref() { + eval(" + def test(a, b) = a[b] + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :[], v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_empty_p() { + eval(" + def test(x) = x.empty? + "); + assert_contains_opcode("test", YARVINSN_opt_empty_p); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :empty? + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_succ() { + eval(" + def test(x) = x.succ + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :succ + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_and() { + eval(" + def test(x, y) = x & y + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :&, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_or() { + eval(" + def test(x, y) = x | y + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :|, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_not() { + eval(" + def test(x) = !x + "); + assert_contains_opcode("test", YARVINSN_opt_not); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :! + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_regexpmatch2() { + eval(" + def test(regexp, matchee) = regexp =~ matchee + "); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :=~, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + // Tests for ConstBase requires either constant or class definition, both + // of which can't be performed inside a method. + fn test_putspecialobject_vm_core_and_cbase() { + eval(" + def test + alias aliased __callee__ + end + "); + assert_contains_opcode("test", YARVINSN_putspecialobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v11:BasicObject = PutSpecialObject CBase + v12:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) + v13:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) + v15:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v11, v12, v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn opt_reverse() { + eval(" + def reverse_odd + a, b, c = @a, @b, @c + [a, b, c] + end + + def reverse_even + a, b, c, d = @a, @b, @c, @d + [a, b, c, d] + end + "); + assert_contains_opcode("reverse_odd", YARVINSN_opt_reverse); + assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); + assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" + fn reverse_odd@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:NilClass, v14:NilClass, v15:NilClass): + PatchPoint SingleRactorMode + v21:BasicObject = GetIvar v12, :@a + PatchPoint SingleRactorMode + v24:BasicObject = GetIvar v12, :@b + PatchPoint SingleRactorMode + v27:BasicObject = GetIvar v12, :@c + PatchPoint NoEPEscape(reverse_odd) + v33:ArrayExact = NewArray v21, v24, v27 + CheckInterrupts + Return v33 + + fn reverse_even@:8: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:NilClass, v16:NilClass, v17:NilClass, v18:NilClass): + PatchPoint SingleRactorMode + v24:BasicObject = GetIvar v14, :@a + PatchPoint SingleRactorMode + v27:BasicObject = GetIvar v14, :@b + PatchPoint SingleRactorMode + v30:BasicObject = GetIvar v14, :@c + PatchPoint SingleRactorMode + v33:BasicObject = GetIvar v14, :@d + PatchPoint NoEPEscape(reverse_even) + v39:ArrayExact = NewArray v24, v27, v30, v33 + CheckInterrupts + Return v39 + "); + } + + #[test] + fn test_branchnil() { + eval(" + def test(x) = x&.itself + "); + assert_contains_opcode("test", YARVINSN_branchnil); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + v15:CBool = IsNil v9 + IfTrue v15, bb3(v8, v9, v9) + v18:BasicObject = SendWithoutBlock v9, :itself + Jump bb3(v8, v9, v18) + bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject): + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_invokebuiltin_delegate_annotated() { + assert_contains_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave); + assert_snapshot!(hir_string("Float"), @r" + fn Float@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + v20:Float = InvokeBuiltin rb_f_float, v12, v13, v14 + Jump bb3(v12, v13, v14, v15, v20) + bb3(v22:BasicObject, v23:BasicObject, v24:BasicObject, v25:BasicObject, v26:Float): + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_invokebuiltin_cexpr_annotated() { + assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); + assert_snapshot!(hir_string("class"), @r" + fn class@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Class = InvokeBuiltin leaf _bi20, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:Class): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invokebuiltin_delegate_with_args() { + // Using an unannotated builtin to test InvokeBuiltin generation + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); + assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn open@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:BasicObject = GetLocal l0, SP@7 + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 + v6:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5, v6) + bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): + EntryPoint JIT(0) + v14:NilClass = Const Value(nil) + Jump bb2(v9, v10, v11, v12, v13, v14) + bb2(v16:BasicObject, v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:NilClass): + v26:BasicObject = InvokeBuiltin dir_s_open, v16, v17, v18 + PatchPoint NoEPEscape(open) + GuardBlockParamProxy l0 + v33:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + CheckInterrupts + v36:CBool[true] = Test v33 + IfFalse v36, bb3(v16, v17, v18, v19, v20, v26) + PatchPoint NoEPEscape(open) + v43:BasicObject = InvokeBlock, v26 + v47:BasicObject = InvokeBuiltin dir_s_close, v16, v26 + CheckInterrupts + Return v43 + bb3(v53, v54, v55, v56, v57, v58): + PatchPoint NoEPEscape(open) + CheckInterrupts + Return v58 + "); + } + + #[test] + fn test_invokebuiltin_delegate_without_args() { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "enable")); + assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate_leave), "iseq GC.enable does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn enable@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBuiltin gc_enable, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invokebuiltin_with_args() { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start")); + assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn start@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:BasicObject = GetLocal l0, SP@5 + v5:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): + EntryPoint JIT(0) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject): + v22:FalseClass = Const Value(false) + v24:BasicObject = InvokeBuiltin gc_start_internal, v14, v15, v16, v17, v22 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_invoke_leaf_builtin_symbol_name() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn name@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact = InvokeBuiltin leaf _bi28, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:StringExact): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invoke_leaf_builtin_symbol_to_s() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn to_s@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact = InvokeBuiltin leaf _bi12, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:StringExact): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn dupn() { + eval(" + def test(x) = (x[0, 1] ||= 2) + "); + assert_contains_opcode("test", YARVINSN_dupn); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + v14:Fixnum[0] = Const Value(0) + v15:Fixnum[1] = Const Value(1) + v17:BasicObject = SendWithoutBlock v9, :[], v14, v15 + CheckInterrupts + v20:CBool = Test v17 + IfTrue v20, bb3(v8, v9, v13, v9, v14, v15, v17) + v22:Fixnum[2] = Const Value(2) + v24:BasicObject = SendWithoutBlock v9, :[]=, v14, v15, v22 + CheckInterrupts + Return v22 + bb3(v30:BasicObject, v31:BasicObject, v32:NilClass, v33:BasicObject, v34:Fixnum[0], v35:Fixnum[1], v36:BasicObject): + CheckInterrupts + Return v36 + "); + } + + #[test] + fn test_objtostring_anytostring() { + eval(" + def test = \"#{1}\" + "); + assert_contains_opcode("test", YARVINSN_objtostring); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_string_concat() { + eval(r##" + def test = "#{1}#{2}#{3}" + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v20:Fixnum[3] = Const Value(3) + v22:BasicObject = ObjToString v20 + v24:String = AnyToString v20, str: v22 + v26:StringExact = StringConcat v14, v19, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_string_concat_empty() { + eval(r##" + def test = "#{}" + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:NilClass = Const Value(nil) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_toregexp() { + eval(r##" + def test = /#{1}#{2}#{3}/ + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v20:Fixnum[3] = Const Value(3) + v22:BasicObject = ObjToString v20 + v24:String = AnyToString v20, str: v22 + v26:RegexpExact = ToRegexp v14, v19, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_toregexp_with_options() { + eval(r##" + def test = /#{1}#{2}/mixn + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v21:RegexpExact = ToRegexp v14, v19, MULTILINE|IGNORECASE|EXTENDED|NOENCODING + CheckInterrupts + Return v21 + "); + } + + #[test] + fn throw() { + eval(" + define_method(:throw_return) { return 1 } + define_method(:throw_break) { break 2 } + "); + assert_contains_opcode("throw_return", YARVINSN_throw); + assert_contains_opcode("throw_break", YARVINSN_throw); + assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v12:Fixnum[1] = Const Value(1) + Throw TAG_RETURN, v12 + + fn block in @:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v12:Fixnum[2] = Const Value(2) + Throw TAG_BREAK, v12 + "); + } + + #[test] + fn test_invokeblock() { + eval(r#" + def test + yield + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBlock + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_invokeblock_with_args() { + eval(r#" + def test(x, y) + yield x, y + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:BasicObject = InvokeBlock, v11, v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_expandarray_no_splat() { + eval(r#" + def test(o) + a, b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + v20:ArrayExact = GuardType v13, ArrayExact + v21:CInt64 = ArrayLength v20 + v22:CInt64[2] = GuardBitEquals v21, CInt64(2) + v23:Fixnum[1] = Const Value(1) + v24:BasicObject = ArrayArefFixnum v20, v23 + v25:Fixnum[0] = Const Value(0) + v26:BasicObject = ArrayArefFixnum v20, v25 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_expandarray_splat() { + eval(r#" + def test(o) + a, *b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } + + #[test] + fn test_expandarray_splat_post() { + eval(r#" + def test(o) + a, *b, c = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject): + EntryPoint JIT(0) + v10:NilClass = Const Value(nil) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:NilClass, v17:NilClass, v18:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } +} From 882e16776866813e0df40e835b9f6cf1b5999d1c Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Tue, 28 Oct 2025 17:25:50 +0200 Subject: [PATCH 0604/2435] Update to ruby/spec@3bc45ba --- spec/ruby/core/proc/element_reference_spec.rb | 2 +- spec/ruby/core/symbol/inspect_spec.rb | 4 ++++ spec/ruby/language/assignments_spec.rb | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/ruby/core/proc/element_reference_spec.rb b/spec/ruby/core/proc/element_reference_spec.rb index 9077e44c346254..81ceb91af5c40c 100644 --- a/spec/ruby/core/proc/element_reference_spec.rb +++ b/spec/ruby/core/proc/element_reference_spec.rb @@ -17,7 +17,7 @@ it_behaves_like :proc_call_on_proc_or_lambda, :call end -describe "Proc#[] with frozen_string_literals" do +describe "Proc#[] with frozen_string_literal: true/false" do it "doesn't duplicate frozen strings" do ProcArefSpecs.aref.frozen?.should be_false ProcArefSpecs.aref_freeze.frozen?.should be_true diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index 6dbb36c2adbf91..26229da9442807 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -96,6 +96,10 @@ :"foo " => ":\"foo \"", :" foo" => ":\" foo\"", :" " => ":\" \"", + + :"ê" => ":ê", + :"测" => ":测", + :"🦊" => ":🦊", } symbols.each do |input, expected| diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index d469459c43d727..c4adf73c1cbf67 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -93,7 +93,7 @@ def []=(*args, **kw) end ruby_version_is "3.4" do - it "raies SyntaxError when given keyword arguments in index assignments" do + it "raises SyntaxError when given keyword arguments in index assignments" do a = @klass.new -> { eval "a[1, 2, 3, b: 4] = 5" }.should raise_error(SyntaxError, /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y @@ -236,7 +236,7 @@ def []=(*args, **kw) end ruby_version_is "3.4" do - it "raies SyntaxError when given keyword arguments in index assignments" do + it "raises SyntaxError when given keyword arguments in index assignments" do a = @klass.new -> { eval "a[1, 2, 3, b: 4] += 5" }.should raise_error(SyntaxError, /keywords are not allowed in index assignment expressions|keyword arg given in index assignment/) # prism|parse.y From 599de290a030927734eac93db66de18c653b6ed2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 13:14:47 -0700 Subject: [PATCH 0605/2435] YJIT, ZJIT: Fix unnecessary `use` of macros https://github.com/ruby/ruby/actions/runs/18887695798/job/53907237061?pr=14975 --- yjit/src/stats.rs | 1 - zjit/src/codegen.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index a53d23435b49a7..817c842cf4144d 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -318,7 +318,6 @@ macro_rules! ptr_to_counter { } }; } -pub(crate) use ptr_to_counter; // Declare all the counters we track make_counters! { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2b71be0e15ba71..63b5b6cb52cab3 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2008,6 +2008,7 @@ macro_rules! c_callable { extern "C" fn $f $args $(-> $ret)? $body }; } +#[cfg(test)] pub(crate) use c_callable; c_callable! { From d0c7234bc79e9b0e415c29ae3250bde12d791b4c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 13:18:05 -0700 Subject: [PATCH 0606/2435] ZJIT: Support ParallelMov into memory (#14975) --- zjit/src/backend/arm64/mod.rs | 35 ++++---- zjit/src/backend/lir.rs | 155 ++++++++++++++++++++++++++++----- zjit/src/backend/x86_64/mod.rs | 26 +++--- zjit/src/codegen.rs | 19 +--- 4 files changed, 169 insertions(+), 66 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 867ad493ec2821..e090d8ce446aaa 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -212,13 +212,7 @@ impl Assembler { /// Return true if opnd contains a scratch reg pub fn has_scratch_reg(opnd: Opnd) -> bool { - match opnd { - Opnd::Reg(_) => opnd == SCRATCH_OPND, - Opnd::Mem(Mem { base: MemBase::Reg(reg_no), .. }) => { - reg_no == SCRATCH_OPND.unwrap_reg().reg_no - } - _ => false, - } + Self::has_reg(opnd, SCRATCH_OPND.unwrap_reg()) } /// Get the list of registers from which we will allocate on this platform @@ -492,7 +486,7 @@ impl Assembler { // Note: the iteration order is reversed to avoid corrupting x0, // which is both the return value and first argument register if !opnds.is_empty() { - let mut args: Vec<(Reg, Opnd)> = vec![]; + let mut args: Vec<(Opnd, Opnd)> = vec![]; for (idx, opnd) in opnds.iter_mut().enumerate().rev() { // If the value that we're sending is 0, then we can use // the zero register, so in this case we'll just send @@ -502,7 +496,7 @@ impl Assembler { Opnd::Mem(_) => split_memory_address(asm, *opnd), _ => *opnd }; - args.push((C_ARG_OPNDS[idx].unwrap_reg(), value)); + args.push((C_ARG_OPNDS[idx], value)); } asm.parallel_mov(args); } @@ -725,10 +719,21 @@ impl Assembler { asm.lea_into(SCRATCH_OPND, Opnd::mem(64, SCRATCH_OPND, 0)); asm.incr_counter(SCRATCH_OPND, value); } + &mut Insn::Mov { dest, src } => { + match dest { + Opnd::Reg(_) => asm.load_into(dest, src), + Opnd::Mem(_) => asm.store(dest, src), + _ => asm.push_insn(insn), + } + } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { - for (reg, opnd) in Self::resolve_parallel_moves(moves, Some(SCRATCH_OPND)).unwrap() { - asm.load_into(Opnd::Reg(reg), opnd); + for (dst, src) in Self::resolve_parallel_moves(moves, Some(SCRATCH_OPND)).unwrap() { + match dst { + Opnd::Reg(_) => asm.load_into(dst, src), + Opnd::Mem(_) => asm.store(dst, src), + _ => asm.mov(dst, src), + } } } _ => { @@ -2385,7 +2390,7 @@ mod tests { } #[test] - fn test_reorder_c_args_no_cycle() { + fn test_ccall_resolve_parallel_moves_no_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -2403,7 +2408,7 @@ mod tests { } #[test] - fn test_reorder_c_args_single_cycle() { + fn test_ccall_resolve_parallel_moves_single_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -2426,7 +2431,7 @@ mod tests { } #[test] - fn test_reorder_c_args_two_cycles() { + fn test_ccall_resolve_parallel_moves_two_cycles() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -2453,7 +2458,7 @@ mod tests { } #[test] - fn test_reorder_c_args_large_cycle() { + fn test_ccall_resolve_parallel_moves_large_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index eb49b419d62046..72ebeaf11be61a 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -454,9 +454,9 @@ pub enum Insn { /// Shift a value left by a certain amount. LShift { opnd: Opnd, shift: Opnd, out: Opnd }, - /// A set of parallel moves into registers. + /// A set of parallel moves into registers or memory. /// The backend breaks cycles if there are any cycles between moves. - ParallelMov { moves: Vec<(Reg, Opnd)> }, + ParallelMov { moves: Vec<(Opnd, Opnd)> }, // A low-level mov instruction. It accepts two operands. Mov { dest: Opnd, src: Opnd }, @@ -1227,6 +1227,15 @@ impl Assembler asm } + /// Return true if `opnd` is or depends on `reg` + pub fn has_reg(opnd: Opnd, reg: Reg) -> bool { + match opnd { + Opnd::Reg(opnd_reg) => opnd_reg == reg, + Opnd::Mem(Mem { base: MemBase::Reg(reg_no), .. }) => reg_no == reg.reg_no, + _ => false, + } + } + pub fn expect_leaf_ccall(&mut self, stack_size: usize) { self.leaf_ccall_stack_size = Some(stack_size); } @@ -1307,17 +1316,23 @@ impl Assembler // Shuffle register moves, sometimes adding extra moves using scratch_reg, // so that they will not rewrite each other before they are used. - pub fn resolve_parallel_moves(old_moves: &[(Reg, Opnd)], scratch_reg: Option) -> Option> { + pub fn resolve_parallel_moves(old_moves: &[(Opnd, Opnd)], scratch_opnd: Option) -> Option> { // Return the index of a move whose destination is not used as a source if any. - fn find_safe_move(moves: &[(Reg, Opnd)]) -> Option { - moves.iter().enumerate().find(|&(_, &(dest_reg, _))| { - moves.iter().all(|&(_, src_opnd)| src_opnd != Opnd::Reg(dest_reg)) + fn find_safe_move(moves: &[(Opnd, Opnd)]) -> Option { + moves.iter().enumerate().find(|&(_, &(dst, src))| { + // Check if `dst` is used in other moves. If `dst` is not used elsewhere, it's safe to write into `dst` now. + moves.iter().filter(|&&other_move| other_move != (dst, src)).all(|&(other_dst, other_src)| + match dst { + Opnd::Reg(reg) => !Assembler::has_reg(other_dst, reg) && !Assembler::has_reg(other_src, reg), + _ => other_dst != dst && other_src != dst, + } + ) }).map(|(index, _)| index) } // Remove moves whose source and destination are the same - let mut old_moves: Vec<(Reg, Opnd)> = old_moves.iter().copied() - .filter(|&(reg, opnd)| Opnd::Reg(reg) != opnd).collect(); + let mut old_moves: Vec<(Opnd, Opnd)> = old_moves.iter().copied() + .filter(|&(dst, src)| dst != src).collect(); let mut new_moves = vec![]; while !old_moves.is_empty() { @@ -1326,18 +1341,19 @@ impl Assembler new_moves.push(old_moves.remove(index)); } - // No safe move. Load the source of one move into scratch_reg, and - // then load scratch_reg into the destination when it's safe. + // No safe move. Load the source of one move into scratch_opnd, and + // then load scratch_opnd into the destination when it's safe. if !old_moves.is_empty() { - // If scratch_reg is None, return None and leave it to *_split_with_scratch_regs to resolve it. - let scratch_reg = scratch_reg?.unwrap_reg(); + // If scratch_opnd is None, return None and leave it to *_split_with_scratch_regs to resolve it. + let scratch_opnd = scratch_opnd?; + let scratch_reg = scratch_opnd.unwrap_reg(); // Make sure it's safe to use scratch_reg - assert!(old_moves.iter().all(|&(_, opnd)| opnd != Opnd::Reg(scratch_reg))); + assert!(old_moves.iter().all(|&(dst, src)| !Self::has_reg(dst, scratch_reg) && !Self::has_reg(src, scratch_reg))); - // Move scratch_reg <- opnd, and delay reg <- scratch_reg - let (reg, opnd) = old_moves.remove(0); - new_moves.push((scratch_reg, opnd)); - old_moves.push((reg, Opnd::Reg(scratch_reg))); + // Move scratch_opnd <- src, and delay dst <- scratch_opnd + let (dst, src) = old_moves.remove(0); + new_moves.push((scratch_opnd, src)); + old_moves.push((dst, scratch_opnd)); } } Some(new_moves) @@ -1551,8 +1567,8 @@ impl Assembler Insn::ParallelMov { moves } => { // For trampolines that use scratch registers, attempt to lower ParallelMov without scratch_reg. if let Some(moves) = Self::resolve_parallel_moves(&moves, None) { - for (reg, opnd) in moves { - asm.load_into(Opnd::Reg(reg), opnd); + for (dst, src) in moves { + asm.mov(dst, src); } } else { // If it needs a scratch_reg, leave it to *_split_with_scratch_regs to handle it. @@ -1980,7 +1996,7 @@ impl Assembler { out } - pub fn parallel_mov(&mut self, moves: Vec<(Reg, Opnd)>) { + pub fn parallel_mov(&mut self, moves: Vec<(Opnd, Opnd)>) { self.push_insn(Insn::ParallelMov { moves }); } @@ -2096,6 +2112,10 @@ pub(crate) use asm_ccall; mod tests { use super::*; + fn scratch_reg() -> Opnd { + Assembler::new_with_scratch_reg().1 + } + #[test] fn test_opnd_iter() { let insn = Insn::Add { left: Opnd::None, right: Opnd::None, out: Opnd::None }; @@ -2125,4 +2145,99 @@ mod tests { let mem = Opnd::mem(64, SP, 0); asm.load_into(mem, mem); } + + #[test] + fn test_resolve_parallel_moves_reorder_registers() { + let result = Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], SP), + (C_ARG_OPNDS[1], C_ARG_OPNDS[0]), + ], None); + assert_eq!(result, Some(vec![ + (C_ARG_OPNDS[1], C_ARG_OPNDS[0]), + (C_ARG_OPNDS[0], SP), + ])); + } + + #[test] + fn test_resolve_parallel_moves_give_up_register_cycle() { + // If scratch_opnd is not given, it cannot break cycles. + let result = Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], C_ARG_OPNDS[0]), + ], None); + assert_eq!(result, None); + } + + #[test] + fn test_resolve_parallel_moves_break_register_cycle() { + let scratch_reg = scratch_reg(); + let result = Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], C_ARG_OPNDS[0]), + ], Some(scratch_reg)); + assert_eq!(result, Some(vec![ + (scratch_reg, C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], C_ARG_OPNDS[0]), + (C_ARG_OPNDS[0], scratch_reg), + ])); + } + + #[test] + fn test_resolve_parallel_moves_break_memory_memory_cycle() { + let scratch_reg = scratch_reg(); + let result = Assembler::resolve_parallel_moves(&[ + (Opnd::mem(64, C_ARG_OPNDS[0], 0), C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], Opnd::mem(64, C_ARG_OPNDS[0], 0)), + ], Some(scratch_reg)); + assert_eq!(result, Some(vec![ + (scratch_reg, C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], Opnd::mem(64, C_ARG_OPNDS[0], 0)), + (Opnd::mem(64, C_ARG_OPNDS[0], 0), scratch_reg), + ])); + } + + #[test] + fn test_resolve_parallel_moves_break_register_memory_cycle() { + let scratch_reg = scratch_reg(); + let result = Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], Opnd::mem(64, C_ARG_OPNDS[0], 0)), + ], Some(scratch_reg)); + assert_eq!(result, Some(vec![ + (scratch_reg, C_ARG_OPNDS[1]), + (C_ARG_OPNDS[1], Opnd::mem(64, C_ARG_OPNDS[0], 0)), + (C_ARG_OPNDS[0], scratch_reg), + ])); + } + + #[test] + fn test_resolve_parallel_moves_reorder_memory_destination() { + let scratch_reg = scratch_reg(); + let result = Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], SP), + (Opnd::mem(64, C_ARG_OPNDS[0], 0), CFP), + ], Some(scratch_reg)); + assert_eq!(result, Some(vec![ + (Opnd::mem(64, C_ARG_OPNDS[0], 0), CFP), + (C_ARG_OPNDS[0], SP), + ])); + } + + #[test] + #[should_panic] + fn test_resolve_parallel_moves_into_same_register() { + Assembler::resolve_parallel_moves(&[ + (C_ARG_OPNDS[0], SP), + (C_ARG_OPNDS[0], CFP), + ], Some(scratch_reg())); + } + + #[test] + #[should_panic] + fn test_resolve_parallel_moves_into_same_memory() { + Assembler::resolve_parallel_moves(&[ + (Opnd::mem(64, C_ARG_OPNDS[0], 0), SP), + (Opnd::mem(64, C_ARG_OPNDS[0], 0), CFP), + ], Some(scratch_reg())); + } } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index bd2421823c9f3c..f76be64ec0025c 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -107,13 +107,7 @@ impl Assembler { /// Return true if opnd contains a scratch reg pub fn has_scratch_reg(opnd: Opnd) -> bool { - match opnd { - Opnd::Reg(_) => opnd == SCRATCH_OPND, - Opnd::Mem(Mem { base: MemBase::Reg(reg_no), .. }) => { - reg_no == SCRATCH_OPND.unwrap_reg().reg_no - } - _ => false, - } + Self::has_reg(opnd, SCRATCH_OPND.unwrap_reg()) } /// Get the list of registers from which we can allocate on this platform @@ -354,9 +348,9 @@ impl Assembler { // Load each operand into the corresponding argument register. if !opnds.is_empty() { - let mut args: Vec<(Reg, Opnd)> = vec![]; + let mut args: Vec<(Opnd, Opnd)> = vec![]; for (idx, opnd) in opnds.iter_mut().enumerate() { - args.push((C_ARG_OPNDS[idx].unwrap_reg(), *opnd)); + args.push((C_ARG_OPNDS[idx], *opnd)); } asm.parallel_mov(args); } @@ -489,8 +483,8 @@ impl Assembler { } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { - for (reg, opnd) in Self::resolve_parallel_moves(&moves, Some(SCRATCH_OPND)).unwrap() { - asm.load_into(Opnd::Reg(reg), opnd); + for (dst, src) in Self::resolve_parallel_moves(&moves, Some(SCRATCH_OPND)).unwrap() { + asm.mov(dst, src) } } // Handle various operand combinations for spills on compile_side_exits. @@ -1368,7 +1362,7 @@ mod tests { } #[test] - fn test_reorder_c_args_no_cycle() { + fn test_ccall_resolve_parallel_moves_no_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -1386,7 +1380,7 @@ mod tests { } #[test] - fn test_reorder_c_args_single_cycle() { + fn test_ccall_resolve_parallel_moves_single_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -1409,7 +1403,7 @@ mod tests { } #[test] - fn test_reorder_c_args_two_cycles() { + fn test_ccall_resolve_parallel_moves_two_cycles() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -1436,7 +1430,7 @@ mod tests { } #[test] - fn test_reorder_c_args_large_cycle() { + fn test_ccall_resolve_parallel_moves_large_cycle() { crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); @@ -1461,7 +1455,7 @@ mod tests { #[test] #[ignore] - fn test_reorder_c_args_with_insn_out() { + fn test_ccall_resolve_parallel_moves_with_insn_out() { let (mut asm, mut cb) = setup_asm(); let rax = asm.load(Opnd::UImm(1)); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 63b5b6cb52cab3..5ead4870dff517 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -8,7 +8,7 @@ use std::ffi::{c_int, c_long, c_void}; use std::slice; use crate::asm::Label; -use crate::backend::current::{Reg, ALLOC_REGS}; +use crate::backend::current::ALLOC_REGS; use crate::invariants::{ track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption @@ -1024,20 +1024,9 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg } asm_comment!(asm, "set branch params: {}", branch.args.len()); - let mut moves: Vec<(Reg, Opnd)> = vec![]; - for (idx, &arg) in branch.args.iter().enumerate() { - match param_opnd(idx) { - Opnd::Reg(reg) => { - // If a parameter is a register, we need to parallel-move it - moves.push((reg, jit.get_opnd(arg))); - }, - param => { - // If a parameter is memory, we set it beforehand - asm.mov(param, jit.get_opnd(arg)); - } - } - } - asm.parallel_mov(moves); + asm.parallel_mov(branch.args.iter().enumerate().map(|(idx, &arg)| + (param_opnd(idx), jit.get_opnd(arg)) + ).collect()); } /// Compile a constant From afb0d43181429d393b3db614cd8246c33c331626 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 13:47:15 -0700 Subject: [PATCH 0607/2435] ZJIT: Drop --seed from test-all on CI (#14976) --- .github/workflows/zjit-macos.yml | 3 --- .github/workflows/zjit-ubuntu.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 0d2b564c41c802..f687672ac7a9e3 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -36,13 +36,11 @@ jobs: run_opts: '--zjit-call-threshold=1' specopts: '-T --zjit-call-threshold=1' configure: '--enable-zjit=dev' - test_all_opts: '--seed=46450' - test_task: 'check' run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1' specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1' configure: '--enable-zjit=dev' - test_all_opts: '--seed=46450' - test_task: 'zjit-check' # zjit-test + quick feedback of test_zjit.rb configure: '--enable-yjit=dev --enable-zjit' @@ -127,7 +125,6 @@ jobs: TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' SYNTAX_SUGGEST_TIMEOUT: '5' PRECHECK_BUNDLED_GEMS: 'no' - TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} - name: Dump crash logs diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index ec92cd5342588c..5163076be5d98a 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -55,13 +55,11 @@ jobs: run_opts: '--zjit-call-threshold=1' specopts: '-T --zjit-call-threshold=1' configure: '--enable-zjit=dev' - test_all_opts: '--seed=39471' - test_task: 'check' run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1' specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1' configure: '--enable-zjit=dev' - test_all_opts: '--seed=39471' - test_task: 'zjit-check' # zjit-test + quick feedback of test_zjit.rb configure: '--enable-yjit --enable-zjit=dev' @@ -177,7 +175,6 @@ jobs: SYNTAX_SUGGEST_TIMEOUT: '5' ZJIT_BINDGEN_DIFF_OPTS: '--exit-code' CLANG_PATH: ${{ matrix.clang_path }} - TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} - name: Dump crash logs From 5a14999d0d54b3af4b91860922168d0e3de19bc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:21:26 +0000 Subject: [PATCH 0608/2435] Bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check_misc.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/wasm.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 38c23b41107a28..bbbaae07b20765 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -103,7 +103,7 @@ jobs: }} - name: Upload docs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: path: html name: ${{ steps.docs.outputs.htmlout }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 34a301ddb020d5..ec3dc873b1a877 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index fcb062a4c1d5c0..10c6afbe5e4228 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -140,7 +140,7 @@ jobs: - run: tar cfz ../install.tar.gz -C ../install . - name: Upload artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ruby-wasm-install path: ${{ github.workspace }}/install.tar.gz @@ -168,7 +168,7 @@ jobs: - name: Save Pull Request number if: ${{ github.event_name == 'pull_request' }} run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: ${{ github.event_name == 'pull_request' }} with: name: github-pr-info From 8dc276f3e1a2d16814eace023840e9eabe7e4101 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:21:57 +0000 Subject: [PATCH 0609/2435] Bump github.com/microsoft/vcpkg from master to 2025.10.17 Bumps [github.com/microsoft/vcpkg](https://github.com/microsoft/vcpkg) from master to 2025.10.17. This release includes the previously tagged commit. - [Release notes](https://github.com/microsoft/vcpkg/releases) - [Commits](https://github.com/microsoft/vcpkg/compare/4334d8b4c8916018600212ab4dd4bbdc343065d1...74e6536215718009aae747d86d84b78376bf9e09) --- updated-dependencies: - dependency-name: github.com/microsoft/vcpkg dependency-version: 2025.10.17 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index b8b0faf93edb1a..1e4b65d89c9632 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,5 +7,5 @@ "openssl", "zlib" ], - "builtin-baseline": "4334d8b4c8916018600212ab4dd4bbdc343065d1" + "builtin-baseline": "74e6536215718009aae747d86d84b78376bf9e09" } \ No newline at end of file From d6d095e2fc4e6ec4d965811de98333bd71076555 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 16:06:38 -0700 Subject: [PATCH 0610/2435] ZJIT: Rename SCRATCH_OPND to SCRATCH0_OPND for x86_64 --- zjit/src/backend/x86_64/mod.rs | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index f76be64ec0025c..2590ceaf7d0e2b 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -83,7 +83,7 @@ impl From<&Opnd> for X86Opnd { /// List of registers that can be used for register allocation. /// This has the same number of registers for x86_64 and arm64. -/// SCRATCH_OPND is excluded. +/// SCRATCH0_OPND is excluded. pub const ALLOC_REGS: &[Reg] = &[ RDI_REG, RSI_REG, @@ -97,17 +97,17 @@ pub const ALLOC_REGS: &[Reg] = &[ /// Special scratch register for intermediate processing. It should be used only by /// [`Assembler::x86_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. -const SCRATCH_OPND: Opnd = Opnd::Reg(R11_REG); +const SCRATCH0_OPND: Opnd = Opnd::Reg(R11_REG); impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH0_OPND) } /// Return true if opnd contains a scratch reg pub fn has_scratch_reg(opnd: Opnd) -> bool { - Self::has_reg(opnd, SCRATCH_OPND.unwrap_reg()) + Self::has_reg(opnd, SCRATCH0_OPND.unwrap_reg()) } /// Get the list of registers from which we can allocate on this platform @@ -387,15 +387,15 @@ impl Assembler { pub fn x86_split_with_scratch_reg(self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register - /// allocator. So we just use the SCRATCH_OPND register temporarily to hold + /// allocator. So we just use the SCRATCH0_OPND register temporarily to hold /// the value before we immediately use it. fn split_64bit_immediate(asm: &mut Assembler, opnd: Opnd) -> Opnd { match opnd { Opnd::Imm(value) => { // 32-bit values will be sign-extended if imm_num_bits(value) > 32 { - asm.mov(SCRATCH_OPND, opnd); - SCRATCH_OPND + asm.mov(SCRATCH0_OPND, opnd); + SCRATCH0_OPND } else { opnd } @@ -403,8 +403,8 @@ impl Assembler { Opnd::UImm(value) => { // 32-bit values will be sign-extended if imm_num_bits(value as i64) > 32 { - asm.mov(SCRATCH_OPND, opnd); - SCRATCH_OPND + asm.mov(SCRATCH0_OPND, opnd); + SCRATCH0_OPND } else { Opnd::Imm(value as i64) } @@ -460,8 +460,8 @@ impl Assembler { match (opnd, out) { // Split here for compile_side_exits (Opnd::Mem(_), Opnd::Mem(_)) => { - asm.lea_into(SCRATCH_OPND, opnd); - asm.store(out, SCRATCH_OPND); + asm.lea_into(SCRATCH0_OPND, opnd); + asm.store(out, SCRATCH0_OPND); } _ => { asm.push_insn(insn); @@ -470,20 +470,20 @@ impl Assembler { } Insn::LeaJumpTarget { target, out } => { if let Target::Label(_) = target { - asm.push_insn(Insn::LeaJumpTarget { out: SCRATCH_OPND, target: target.clone() }); - asm.mov(*out, SCRATCH_OPND); + asm.push_insn(Insn::LeaJumpTarget { out: SCRATCH0_OPND, target: target.clone() }); + asm.mov(*out, SCRATCH0_OPND); } } // Convert Opnd::const_ptr into Opnd::Mem. This split is done here to give // a register for compile_side_exits. &mut Insn::IncrCounter { mem, value } => { assert!(matches!(mem, Opnd::UImm(_))); - asm.load_into(SCRATCH_OPND, mem); - asm.incr_counter(Opnd::mem(64, SCRATCH_OPND, 0), value); + asm.load_into(SCRATCH0_OPND, mem); + asm.incr_counter(Opnd::mem(64, SCRATCH0_OPND, 0), value); } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { - for (dst, src) in Self::resolve_parallel_moves(&moves, Some(SCRATCH_OPND)).unwrap() { + for (dst, src) in Self::resolve_parallel_moves(&moves, Some(SCRATCH0_OPND)).unwrap() { asm.mov(dst, src) } } @@ -496,14 +496,14 @@ impl Assembler { let src = match src { Opnd::Reg(_) => src, Opnd::Mem(_) => { - asm.mov(SCRATCH_OPND, src); - SCRATCH_OPND + asm.mov(SCRATCH0_OPND, src); + SCRATCH0_OPND } Opnd::Imm(imm) => { // For 64 bit destinations, 32-bit values will be sign-extended if num_bits == 64 && imm_num_bits(imm) > 32 { - asm.mov(SCRATCH_OPND, src); - SCRATCH_OPND + asm.mov(SCRATCH0_OPND, src); + SCRATCH0_OPND } else if uimm_num_bits(imm as u64) <= num_bits { // If the bit string is short enough for the destination, use the unsigned representation. // Note that 64-bit and negative values are ruled out. @@ -515,15 +515,15 @@ impl Assembler { Opnd::UImm(imm) => { // For 64 bit destinations, 32-bit values will be sign-extended if num_bits == 64 && imm_num_bits(imm as i64) > 32 { - asm.mov(SCRATCH_OPND, src); - SCRATCH_OPND + asm.mov(SCRATCH0_OPND, src); + SCRATCH0_OPND } else { src.into() } } Opnd::Value(_) => { - asm.load_into(SCRATCH_OPND, src); - SCRATCH_OPND + asm.load_into(SCRATCH0_OPND, src); + SCRATCH0_OPND } src @ (Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during x86_split_with_scratch_reg: {src:?}"), }; From 7428dc7348c37703dbc36fa959f8b4254af17a60 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 15:07:19 -0700 Subject: [PATCH 0611/2435] ZJIT: Migrate an arm64 register from emit to split --- zjit/src/backend/arm64/mod.rs | 205 +++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 92 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index e090d8ce446aaa..234f5ac0590ba5 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -113,8 +113,8 @@ fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { b(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); 1 } else { - let num_insns = emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr as u64); - br(cb, Assembler::EMIT0_OPND); + let num_insns = emit_load_value(cb, Assembler::EMIT_OPND, dst_addr as u64); + br(cb, Assembler::EMIT_OPND); num_insns + 1 }; @@ -181,7 +181,7 @@ fn emit_load_value(cb: &mut CodeBlock, rd: A64Opnd, value: u64) -> usize { /// List of registers that can be used for register allocation. /// This has the same number of registers for x86_64 and arm64. -/// SCRATCH_OPND, EMIT0_OPND, and EMIT1_OPND are excluded. +/// SCRATCH_OPND, SCRATCH1_OPND, and EMIT_OPND are excluded. pub const ALLOC_REGS: &[Reg] = &[ X0_REG, X1_REG, @@ -193,26 +193,24 @@ pub const ALLOC_REGS: &[Reg] = &[ X12_REG, ]; -/// Special scratch register for intermediate processing. It should be used only by +/// Special scratch registers for intermediate processing. They should be used only by /// [`Assembler::arm64_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. -const SCRATCH_OPND: Opnd = Opnd::Reg(X15_REG); +const SCRATCH0_OPND: Opnd = Opnd::Reg(X15_REG); +const SCRATCH1_OPND: Opnd = Opnd::Reg(X17_REG); impl Assembler { - /// Special registers for intermediate processing in arm64_emit. It should be used only by arm64_emit. - /// TODO: Remove the use of these registers by splitting instructions in arm64_split_with_scratch_reg. - const EMIT0_REG: Reg = X16_REG; - const EMIT1_REG: Reg = X17_REG; - const EMIT0_OPND: A64Opnd = A64Opnd::Reg(Self::EMIT0_REG); - const EMIT1_OPND: A64Opnd = A64Opnd::Reg(Self::EMIT1_REG); + /// Special register for intermediate processing in arm64_emit. It should be used only by arm64_emit. + const EMIT_REG: Reg = X16_REG; + const EMIT_OPND: A64Opnd = A64Opnd::Reg(Self::EMIT_REG); /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH0_OPND) } /// Return true if opnd contains a scratch reg pub fn has_scratch_reg(opnd: Opnd) -> bool { - Self::has_reg(opnd, SCRATCH_OPND.unwrap_reg()) + Self::has_reg(opnd, SCRATCH0_OPND.unwrap_reg()) } /// Get the list of registers from which we will allocate on this platform @@ -688,10 +686,20 @@ impl Assembler { fn arm64_split_with_scratch_reg(self) -> Assembler { let mut asm = Assembler::new_with_asm(&self); asm.accept_scratch_reg = true; - let iterator = self.insns.into_iter().enumerate().peekable(); + let mut iterator = self.insns.into_iter().enumerate().peekable(); - for (_, mut insn) in iterator { + while let Some((_, mut insn)) = iterator.next() { match &mut insn { + &mut Insn::Mul { out, .. } => { + asm.push_insn(insn); + + // If the next instruction is JoMul + if matches!(iterator.peek(), Some((_, Insn::JoMul(_)))) { + // Produce a register that is all zeros or all ones + // Based on the sign bit of the 64-bit mul result + asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: out, shift: Opnd::UImm(63) }); + } + } // For compile_side_exits, support splitting simple C arguments here Insn::CCall { opnds, .. } if !opnds.is_empty() => { for (i, opnd) in opnds.iter().enumerate() { @@ -704,20 +712,43 @@ impl Assembler { match (opnd, out) { // Split here for compile_side_exits (Opnd::Mem(_), Opnd::Mem(_)) => { - asm.lea_into(SCRATCH_OPND, opnd); - asm.store(out, SCRATCH_OPND); + asm.lea_into(SCRATCH0_OPND, opnd); + asm.store(out, SCRATCH0_OPND); } _ => { asm.push_insn(insn); } } } - // Convert Opnd::const_ptr into Opnd::Mem. It's split here compile_side_exits. &mut Insn::IncrCounter { mem, value } => { + // Convert Opnd::const_ptr into Opnd::Mem. + // It's split here to support IncrCounter in compile_side_exits. assert!(matches!(mem, Opnd::UImm(_))); - asm.load_into(SCRATCH_OPND, mem); - asm.lea_into(SCRATCH_OPND, Opnd::mem(64, SCRATCH_OPND, 0)); - asm.incr_counter(SCRATCH_OPND, value); + asm.load_into(SCRATCH0_OPND, mem); + asm.lea_into(SCRATCH0_OPND, Opnd::mem(64, SCRATCH0_OPND, 0)); + + // Create a local loop to atomically increment a counter using SCRATCH1_OPND to check if it succeeded. + // Note that arm64_emit will peek at the next Cmp to set a status into SCRATCH1_OPND on IncrCounter. + let label = asm.new_label("incr_counter_loop"); + asm.write_label(label.clone()); + asm.incr_counter(SCRATCH0_OPND, value); + asm.cmp(SCRATCH1_OPND, 0.into()); + asm.jne(label); + } + &mut Insn::Store { dest, src } => { + let Opnd::Mem(Mem { num_bits: dest_num_bits, disp: dest_disp, .. }) = dest else { + panic!("Insn::Store destination must be Opnd::Mem: {dest:?}, {src:?}"); + }; + + // Split dest using a scratch register if necessary. + let dest = if mem_disp_fits_bits(dest_disp) { + dest + } else { + asm.lea_into(SCRATCH0_OPND, dest); + Opnd::mem(dest_num_bits, SCRATCH0_OPND, 0) + }; + + asm.store(dest, src); } &mut Insn::Mov { dest, src } => { match dest { @@ -728,7 +759,7 @@ impl Assembler { } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { - for (dst, src) in Self::resolve_parallel_moves(moves, Some(SCRATCH_OPND)).unwrap() { + for (dst, src) in Self::resolve_parallel_moves(moves, Some(SCRATCH0_OPND)).unwrap() { match dst { Opnd::Reg(_) => asm.load_into(dst, src), Opnd::Mem(_) => asm.store(dst, src), @@ -805,8 +836,8 @@ impl Assembler { // that if it doesn't match it will skip over the // instructions used for branching. bcond(cb, Condition::inverse(CONDITION), (load_insns + 2).into()); - emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr as u64); - br(cb, Assembler::EMIT0_OPND); + emit_load_value(cb, Assembler::EMIT_OPND, dst_addr as u64); + br(cb, Assembler::EMIT_OPND); // Here we'll return the number of instructions that it // took to write out the destination address + 1 for the @@ -870,8 +901,8 @@ impl Assembler { } else { cbz(cb, reg, InstructionOffset::from_insns(load_insns + 2)); } - emit_load_value(cb, Assembler::EMIT0_OPND, dst_addr); - br(cb, Assembler::EMIT0_OPND); + emit_load_value(cb, Assembler::EMIT_OPND, dst_addr); + br(cb, Assembler::EMIT_OPND); } } else { unreachable!("We should only generate Joz/Jonz with side-exit targets"); @@ -1039,25 +1070,25 @@ impl Assembler { } }, Insn::Mul { left, right, out } => { - // If the next instruction is jo (jump on overflow) + // If the next instruction is JoMul with RShift created by arm64_split_with_scratch_reg match (self.insns.get(insn_idx + 1), self.insns.get(insn_idx + 2)) { - (Some(Insn::JoMul(_)), _) | - (Some(Insn::PosMarker(_)), Some(Insn::JoMul(_))) => { + (Some(Insn::RShift { out: out_sign, opnd: out_opnd, shift: out_shift }), Some(Insn::JoMul(_))) => { // Compute the high 64 bits - smulh(cb, Self::EMIT0_OPND, left.into(), right.into()); + smulh(cb, Self::EMIT_OPND, left.into(), right.into()); // Compute the low 64 bits // This may clobber one of the input registers, // so we do it after smulh mul(cb, out.into(), left.into(), right.into()); - // Produce a register that is all zeros or all ones - // Based on the sign bit of the 64-bit mul result - asr(cb, Self::EMIT1_OPND, out.into(), A64Opnd::UImm(63)); + // Insert the shift instruction created by arm64_split_with_scratch_reg + // to prepare the register that has the sign bit of the high 64 bits after mul. + asr(cb, out_sign.into(), out_opnd.into(), out_shift.into()); + insn_idx += 1; // skip the next Insn::RShift // If the high 64-bits are not all zeros or all ones, // matching the sign bit, then we have an overflow - cmp(cb, Self::EMIT0_OPND, Self::EMIT1_OPND); + cmp(cb, Self::EMIT_OPND, out_sign.into()); // Insn::JoMul will emit_conditional_jump::<{Condition::NE}> } _ => { @@ -1087,11 +1118,6 @@ impl Assembler { lsl(cb, out.into(), opnd.into(), shift.into()); }, Insn::Store { dest, src } => { - // With minor exceptions, as long as `dest` is a Mem, all forms of `src` are accepted. - let &Opnd::Mem(Mem { num_bits: dest_num_bits, base: MemBase::Reg(base_reg_no), disp }) = dest else { - panic!("Unexpected Insn::Store destination in arm64_emit: {dest:?}"); - }; - // Split src into EMIT0_OPND if necessary let src_reg: A64Reg = match src { Opnd::Reg(reg) => *reg, @@ -1099,16 +1125,16 @@ impl Assembler { Opnd::UImm(0) | Opnd::Imm(0) => XZR_REG, // Immediates &Opnd::Imm(imm) => { - emit_load_value(cb, Self::EMIT0_OPND, imm as u64); - Self::EMIT0_REG + emit_load_value(cb, Self::EMIT_OPND, imm as u64); + Self::EMIT_REG } &Opnd::UImm(imm) => { - emit_load_value(cb, Self::EMIT0_OPND, imm); - Self::EMIT0_REG + emit_load_value(cb, Self::EMIT_OPND, imm); + Self::EMIT_REG } &Opnd::Value(value) => { - emit_load_gc_value(cb, &mut gc_offsets, Self::EMIT0_OPND, value); - Self::EMIT0_REG + emit_load_gc_value(cb, &mut gc_offsets, Self::EMIT_OPND, value); + Self::EMIT_REG } src_mem @ &Opnd::Mem(Mem { num_bits: src_num_bits, base: MemBase::Reg(src_base_reg_no), disp: src_disp }) => { // For mem-to-mem store, load the source into EMIT0_OPND @@ -1116,36 +1142,28 @@ impl Assembler { src_mem.into() } else { // Split the load address into EMIT0_OPND first if necessary - load_effective_address(cb, Self::EMIT0_OPND, src_base_reg_no, src_disp); - A64Opnd::new_mem(dest_num_bits, Self::EMIT0_OPND, 0) + load_effective_address(cb, Self::EMIT_OPND, src_base_reg_no, src_disp); + A64Opnd::new_mem(dest.rm_num_bits(), Self::EMIT_OPND, 0) }; match src_num_bits { - 64 | 32 => ldur(cb, Self::EMIT0_OPND, src_mem), - 16 => ldurh(cb, Self::EMIT0_OPND, src_mem), - 8 => ldurb(cb, Self::EMIT0_OPND, src_mem), + 64 | 32 => ldur(cb, Self::EMIT_OPND, src_mem), + 16 => ldurh(cb, Self::EMIT_OPND, src_mem), + 8 => ldurb(cb, Self::EMIT_OPND, src_mem), num_bits => panic!("unexpected num_bits: {num_bits}") }; - Self::EMIT0_REG + Self::EMIT_REG } src @ (Opnd::Mem(_) | Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during arm64_emit: {src:?}") }; let src = A64Opnd::Reg(src_reg); - // Split dest into EMIT1_OPND if necessary. - let dest = if mem_disp_fits_bits(disp) { - dest.into() - } else { - load_effective_address(cb, Self::EMIT1_OPND, base_reg_no, disp); - A64Opnd::new_mem(dest_num_bits, Self::EMIT1_OPND, 0) - }; - // This order may be surprising but it is correct. The way // the Arm64 assembler works, the register that is going to // be stored is first and the address is second. However in // our IR we have the address first and the register second. - match dest_num_bits { - 64 | 32 => stur(cb, src, dest), - 16 => sturh(cb, src, dest), + match dest.rm_num_bits() { + 64 | 32 => stur(cb, src, dest.into()), + 16 => sturh(cb, src, dest.into()), num_bits => panic!("unexpected dest num_bits: {} (src: {:#?}, dest: {:#?})", num_bits, src, dest), } }, @@ -1219,7 +1237,7 @@ impl Assembler { } else { // Use a scratch reg for `out += displacement` let disp_reg = if out_reg_no == base_reg_no { - Self::EMIT0_OPND + Self::EMIT_OPND } else { out }; @@ -1234,10 +1252,10 @@ impl Assembler { if let Target::Label(label_idx) = target { // Set output to the raw address of the label cb.label_ref(*label_idx, 4, |cb, end_addr, dst_addr| { - adr(cb, Self::EMIT0_OPND, A64Opnd::new_imm(dst_addr - (end_addr - 4))); + adr(cb, Self::EMIT_OPND, A64Opnd::new_imm(dst_addr - (end_addr - 4))); }); - mov(cb, out.into(), Self::EMIT0_OPND); + mov(cb, out.into(), Self::EMIT_OPND); } else { // Set output to the jump target's raw address let target_code = target.unwrap_code_ptr(); @@ -1262,15 +1280,15 @@ impl Assembler { } // Push the flags/state register - mrs(cb, Self::EMIT0_OPND, SystemRegister::NZCV); - emit_push(cb, Self::EMIT0_OPND); + mrs(cb, Self::EMIT_OPND, SystemRegister::NZCV); + emit_push(cb, Self::EMIT_OPND); }, Insn::CPopAll => { let regs = Assembler::get_caller_save_regs(); // Pop the state/flags register - msr(cb, SystemRegister::NZCV, Self::EMIT0_OPND); - emit_pop(cb, Self::EMIT0_OPND); + msr(cb, SystemRegister::NZCV, Self::EMIT_OPND); + emit_pop(cb, Self::EMIT_OPND); for reg in regs.into_iter().rev() { emit_pop(cb, A64Opnd::Reg(reg)); @@ -1286,8 +1304,8 @@ impl Assembler { if b_offset_fits_bits((dst_addr - src_addr) / 4) { bl(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); } else { - emit_load_value(cb, Self::EMIT0_OPND, dst_addr as u64); - blr(cb, Self::EMIT0_OPND); + emit_load_value(cb, Self::EMIT_OPND, dst_addr as u64); + blr(cb, Self::EMIT_OPND); } }, Insn::CRet { .. } => { @@ -1364,21 +1382,24 @@ impl Assembler { last_patch_pos = Some(cb.get_write_pos()); }, Insn::IncrCounter { mem, value } => { - let label = cb.new_label("incr_counter_loop".to_string()); - cb.write_label(label); + // Get the status register allocated by arm64_split_with_scratch_reg + let Some(Insn::Cmp { + left: status_reg @ Opnd::Reg(_), + right: Opnd::UImm(_) | Opnd::Imm(_), + }) = self.insns.get(insn_idx + 1) else { + panic!("arm64_split_with_scratch_reg should add Cmp after IncrCounter: {:?}", self.insns.get(insn_idx + 1)); + }; - ldaxr(cb, Self::EMIT0_OPND, mem.into()); - add(cb, Self::EMIT0_OPND, Self::EMIT0_OPND, value.into()); + // Attempt to increment a counter + ldaxr(cb, Self::EMIT_OPND, mem.into()); + add(cb, Self::EMIT_OPND, Self::EMIT_OPND, value.into()); // The status register that gets used to track whether or // not the store was successful must be 32 bytes. Since we // store the EMIT registers as their 64-bit versions, we // need to rewrap it here. - let status = A64Opnd::Reg(Self::EMIT1_REG.with_num_bits(32)); - stlxr(cb, status, Self::EMIT0_OPND, mem.into()); - - cmp(cb, Self::EMIT1_OPND, A64Opnd::new_uimm(0)); - emit_conditional_jump::<{Condition::NE}>(cb, Target::Label(label)); + let status = A64Opnd::Reg(status_reg.unwrap_reg().with_num_bits(32)); + stlxr(cb, status, Self::EMIT_OPND, mem.into()); }, Insn::Breakpoint => { brk(cb, A64Opnd::None); @@ -1865,19 +1886,19 @@ mod tests { asm.store(large_mem, large_mem); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @" - 0x0: sub x16, sp, #0x305 - 0x4: ldur x16, [x16] - 0x8: stur x16, [x0] - 0xc: ldur x16, [x0] - 0x10: sub x17, sp, #0x305 - 0x14: stur x16, [x17] - 0x18: sub x16, sp, #0x305 - 0x1c: ldur x16, [x16] - 0x20: sub x17, sp, #0x305 - 0x24: stur x16, [x17] + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: sub x16, sp, #0x305 + 0x4: ldur x16, [x16] + 0x8: stur x16, [x0] + 0xc: sub x15, sp, #0x305 + 0x10: ldur x16, [x0] + 0x14: stur x16, [x15] + 0x18: sub x15, sp, #0x305 + 0x1c: sub x16, sp, #0x305 + 0x20: ldur x16, [x16] + 0x24: stur x16, [x15] "); - assert_snapshot!(cb.hexdump(), @"f0170cd1100240f8100000f8100040f8f1170cd1300200f8f0170cd1100240f8f1170cd1300200f8"); + assert_snapshot!(cb.hexdump(), @"f0170cd1100240f8100000f8ef170cd1100040f8f00100f8ef170cd1f0170cd1100240f8f00100f8"); } #[test] From 4925bec65d17b1330d6b39f0c5a3ffe7ed9abde2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 26 Oct 2025 15:23:05 -0400 Subject: [PATCH 0612/2435] Fix TestString#test_encode_fallback_raise_memory_leak The method and aref cases need to accept a parameter. --- test/ruby/test_string.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 6445832798ec1a..e4c14fd488456d 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3756,12 +3756,12 @@ def test_encode_fallback_raise_memory_leak fallback = proc { raise } RUBY "method" => <<~RUBY, - def my_method = raise + def my_method(_str) = raise fallback = method(:my_method) RUBY "aref" => <<~RUBY, fallback = Object.new - def fallback.[] = raise + def fallback.[](_str) = raise RUBY }.each do |type, code| assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) From 9f4a76ff51530e3ff7a9adf0c689f75f1a1cc6d2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 26 Oct 2025 15:24:44 -0400 Subject: [PATCH 0613/2435] Add a custom error class to TestString#test_encode_fallback_raise_memory_leak This prevents a generic RuntimeError from being raised so we can ensure that the correct error is being rescued. --- test/ruby/test_string.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index e4c14fd488456d..f6adef6efc16da 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3750,26 +3750,28 @@ def test_chilled_string_substring def test_encode_fallback_raise_memory_leak { "hash" => <<~RUBY, - fallback = Hash.new { raise } + fallback = Hash.new { raise MyError } RUBY "proc" => <<~RUBY, - fallback = proc { raise } + fallback = proc { raise MyError } RUBY "method" => <<~RUBY, - def my_method(_str) = raise + def my_method(_str) = raise MyError fallback = method(:my_method) RUBY "aref" => <<~RUBY, fallback = Object.new - def fallback.[](_str) = raise + def fallback.[](_str) = raise MyError RUBY }.each do |type, code| assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + #{code} 100_000.times do |i| "\\ufffd".encode(Encoding::US_ASCII, fallback:) - rescue + rescue MyError end RUBY end From 80be97e4a2c878d7c5a129b245f1e2430b99b19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luiz=20Tiago=20Soares?= Date: Tue, 28 Oct 2025 20:48:38 -0300 Subject: [PATCH 0614/2435] ZJIT: Fill `cfp->pc` with trap value for C methods in debug builds --- zjit/src/codegen.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5ead4870dff517..2ac7ca348ad492 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -25,6 +25,13 @@ use crate::hir_type::{types, Type}; use crate::options::get_option; use crate::cast::IntoUsize; +/// Sentinel program counter stored in C frames when runtime checks are enabled. +const PC_POISON: Option<*const VALUE> = if cfg!(feature = "runtime_checks") { + Some(usize::MAX as *const VALUE) +} else { + None +}; + /// Ephemeral code generation state struct JITState { /// Instruction sequence for the method being compiled @@ -741,6 +748,7 @@ fn gen_ccall_with_frame( iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, + pc: PC_POISON, specval: block_handler_specval, }); @@ -798,6 +806,7 @@ fn gen_ccall_variadic( cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, specval: VM_BLOCK_HANDLER_NONE.into(), + pc: PC_POISON, }); asm_comment!(asm, "switch to new SP register"); @@ -1205,6 +1214,7 @@ fn gen_send_without_block_direct( iseq: Some(iseq), cme, frame_type, + pc: None, specval, }); @@ -1828,6 +1838,7 @@ struct ControlFrame { /// The [`VM_ENV_DATA_INDEX_SPECVAL`] slot of the frame. /// For the type of frames we push, block handler or the parent EP. specval: lir::Opnd, + pc: Option<*const VALUE>, } /// Compile an interpreter frame @@ -1862,8 +1873,11 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C // cfp_opnd(RUBY_OFFSET_CFP_SP): written by the callee frame on side-exits or non-leaf calls asm.mov(cfp_opnd(RUBY_OFFSET_CFP_ISEQ), VALUE::from(iseq).into()); } else { - // C frames don't have a PC and ISEQ - asm.mov(cfp_opnd(RUBY_OFFSET_CFP_PC), 0.into()); + // C frames don't have a PC and ISEQ in normal operation. + // When runtime checks are enabled we poison the PC so accidental reads stand out. + if let Some(pc) = frame.pc { + asm.mov(cfp_opnd(RUBY_OFFSET_CFP_PC), Opnd::const_ptr(pc)); + } let new_sp = asm.lea(Opnd::mem(64, SP, (ep_offset + 1) * SIZEOF_VALUE_I32)); asm.mov(cfp_opnd(RUBY_OFFSET_CFP_SP), new_sp); asm.mov(cfp_opnd(RUBY_OFFSET_CFP_ISEQ), 0.into()); From f8a333ae193017999b38f6a4838582cc2c333063 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 28 Oct 2025 20:11:27 -0400 Subject: [PATCH 0615/2435] ZJIT: Add type checker to HIR (#14978) Allow instructions to constrain their operands' input types to avoid accidentally creating invalid HIR. --- zjit/src/hir.rs | 162 ++++++++++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 4 ++ 2 files changed, 166 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3f68764722b448..b284ae6c118d86 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1356,6 +1356,9 @@ pub enum ValidationError { OperandNotDefined(BlockId, InsnId, InsnId), /// The offending block and instruction DuplicateInstruction(BlockId, InsnId), + /// The offending instruction, its operand, expected type string, actual type string + MismatchedOperandType(InsnId, InsnId, String, String), + MiscValidationError(InsnId, String), } fn can_direct_send(iseq: *const rb_iseq_t) -> bool { @@ -3565,11 +3568,170 @@ impl Function { Ok(()) } + fn assert_subtype(&self, user: InsnId, operand: InsnId, expected: Type) -> Result<(), ValidationError> { + let actual = self.type_of(operand); + if !actual.is_subtype(expected) { + return Err(ValidationError::MismatchedOperandType(user, operand, format!("{}", expected), format!("{}", actual))); + } + Ok(()) + } + + fn validate_insn_type(&self, insn_id: InsnId) -> Result<(), ValidationError> { + let insn_id = self.union_find.borrow().find_const(insn_id); + let insn = self.find(insn_id); + match insn { + Insn::StringCopy { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), + Insn::StringIntern { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), + Insn::ArrayDup { val, .. } => self.assert_subtype(insn_id, val, types::ArrayExact), + Insn::StringAppend { recv, other, .. } => { + self.assert_subtype(insn_id, recv, types::StringExact)?; + self.assert_subtype(insn_id, other, types::String) + } + Insn::NewHash { ref elements, .. } => { + if elements.len() % 2 != 0 { + return Err(ValidationError::MiscValidationError(insn_id, "NewHash elements length is not even".to_string())); + } + Ok(()) + } + Insn::NewRangeFixnum { low, high, .. } => { + self.assert_subtype(insn_id, low, types::Fixnum)?; + self.assert_subtype(insn_id, high, types::Fixnum) + } + Insn::ArrayExtend { left, right, .. } => { + // TODO(max): Do left and right need to be ArrayExact? + self.assert_subtype(insn_id, left, types::Array)?; + self.assert_subtype(insn_id, right, types::Array) + } + Insn::ArrayPush { array, .. } => self.assert_subtype(insn_id, array, types::Array), + Insn::ArrayPop { array, .. } => self.assert_subtype(insn_id, array, types::Array), + Insn::ArrayLength { array, .. } => self.assert_subtype(insn_id, array, types::Array), + Insn::HashAref { hash, .. } => self.assert_subtype(insn_id, hash, types::Hash), + Insn::HashDup { val, .. } => self.assert_subtype(insn_id, val, types::HashExact), + Insn::ObjectAllocClass { class, .. } => { + let has_leaf_allocator = unsafe { rb_zjit_class_has_default_allocator(class) } || class_has_leaf_allocator(class); + if !has_leaf_allocator { + return Err(ValidationError::MiscValidationError(insn_id, "ObjectAllocClass must have leaf allocator".to_string())); + } + Ok(()) + } + Insn::Test { val } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::IsNil { val } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::IsMethodCfunc { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::IsBitEqual { left, right } + | Insn::IsBitNotEqual { left, right } => { + if self.is_a(left, types::CInt) && self.is_a(right, types::CInt) { + // TODO(max): Check that int sizes match + Ok(()) + } else if self.is_a(left, types::CPtr) && self.is_a(right, types::CPtr) { + Ok(()) + } else if self.is_a(left, types::RubyValue) && self.is_a(right, types::RubyValue) { + Ok(()) + } else { + return Err(ValidationError::MiscValidationError(insn_id, "IsBitEqual can only compare CInt/CInt or RubyValue/RubyValue".to_string())); + } + } + Insn::BoxBool { val } => self.assert_subtype(insn_id, val, types::CBool), + Insn::SetGlobal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::GetIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), + Insn::SetIvar { self_val, val, .. } => { + self.assert_subtype(insn_id, self_val, types::BasicObject)?; + self.assert_subtype(insn_id, val, types::BasicObject) + } + Insn::DefinedIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), + Insn::LoadIvarEmbedded { self_val, .. } => self.assert_subtype(insn_id, self_val, types::HeapBasicObject), + Insn::LoadIvarExtended { self_val, .. } => self.assert_subtype(insn_id, self_val, types::HeapBasicObject), + Insn::SetLocal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::SetClassVar { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::IfTrue { val, .. } | Insn::IfFalse { val, .. } => self.assert_subtype(insn_id, val, types::CBool), + Insn::SendWithoutBlock { recv, ref args, .. } + | Insn::SendWithoutBlockDirect { recv, ref args, .. } + | Insn::Send { recv, ref args, .. } + | Insn::SendForward { recv, ref args, .. } + | Insn::InvokeSuper { recv, ref args, .. } + | Insn::CCallVariadic { recv, ref args, .. } => { + self.assert_subtype(insn_id, recv, types::BasicObject)?; + for &arg in args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) + } + Insn::CCallWithFrame { ref args, .. } + | Insn::InvokeBuiltin { ref args, .. } + | Insn::InvokeBlock { ref args, .. } => { + for &arg in args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) + } + Insn::Return { val } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::Throw { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::FixnumAdd { left, right, .. } + | Insn::FixnumSub { left, right, .. } + | Insn::FixnumMult { left, right, .. } + | Insn::FixnumDiv { left, right, .. } + | Insn::FixnumMod { left, right, .. } + | Insn::FixnumEq { left, right } + | Insn::FixnumNeq { left, right } + | Insn::FixnumLt { left, right } + | Insn::FixnumLe { left, right } + | Insn::FixnumGt { left, right } + | Insn::FixnumGe { left, right } + | Insn::FixnumAnd { left, right } + | Insn::FixnumOr { left, right } + | Insn::FixnumXor { left, right } + => { + self.assert_subtype(insn_id, left, types::Fixnum)?; + self.assert_subtype(insn_id, right, types::Fixnum) + } + Insn::ObjToString { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::AnyToString { val, str, .. } => { + self.assert_subtype(insn_id, val, types::BasicObject)?; + self.assert_subtype(insn_id, str, types::BasicObject) + } + Insn::GuardType { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::GuardTypeNot { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::GuardBitEquals { val, expected, .. } => { + match expected { + Const::Value(_) => self.assert_subtype(insn_id, val, types::RubyValue), + Const::CInt8(_) => self.assert_subtype(insn_id, val, types::CInt8), + Const::CInt16(_) => self.assert_subtype(insn_id, val, types::CInt16), + Const::CInt32(_) => self.assert_subtype(insn_id, val, types::CInt32), + Const::CInt64(_) => self.assert_subtype(insn_id, val, types::CInt64), + Const::CUInt8(_) => self.assert_subtype(insn_id, val, types::CUInt8), + Const::CUInt16(_) => self.assert_subtype(insn_id, val, types::CUInt16), + Const::CUInt32(_) => self.assert_subtype(insn_id, val, types::CUInt32), + Const::CUInt64(_) => self.assert_subtype(insn_id, val, types::CUInt64), + Const::CBool(_) => self.assert_subtype(insn_id, val, types::CBool), + Const::CDouble(_) => self.assert_subtype(insn_id, val, types::CDouble), + Const::CPtr(_) => self.assert_subtype(insn_id, val, types::CPtr), + } + } + Insn::GuardShape { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::GuardNotFrozen { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::StringGetbyteFixnum { string, index } => { + self.assert_subtype(insn_id, string, types::String)?; + self.assert_subtype(insn_id, index, types::Fixnum) + } + _ => Ok(()), + } + } + + /// Check that insn types match the expected types for each instruction. + fn validate_types(&self) -> Result<(), ValidationError> { + for block_id in self.rpo() { + for &insn_id in &self.blocks[block_id.0].insns { + self.validate_insn_type(insn_id)?; + } + } + Ok(()) + } + /// Run all validation passes we have. pub fn validate(&self) -> Result<(), ValidationError> { self.validate_block_terminators_and_jumps()?; self.validate_definite_assignment()?; self.validate_insn_uniqueness()?; + self.validate_types()?; Ok(()) } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 4874d0fe64146b..9965526b7607cb 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -202,6 +202,8 @@ make_counters! { compile_error_validation_jump_target_not_in_rpo, compile_error_validation_operand_not_defined, compile_error_validation_duplicate_instruction, + compile_error_validation_type_check_failure, + compile_error_validation_misc_validation_error, // The number of times YARV instructions are executed on JIT code zjit_insn_count, @@ -320,6 +322,8 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { JumpTargetNotInRPO(_) => compile_error_validation_jump_target_not_in_rpo, OperandNotDefined(_, _, _) => compile_error_validation_operand_not_defined, DuplicateInstruction(_, _) => compile_error_validation_duplicate_instruction, + MismatchedOperandType(..) => compile_error_validation_type_check_failure, + MiscValidationError(..) => compile_error_validation_misc_validation_error, }, } } From c4e090def134f9b109991b74c027648564963763 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 10:06:02 +0900 Subject: [PATCH 0616/2435] test_commit_email.rb: Clean up temporary files --- tool/test/test_commit_email.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index b948987ed96411..988a26cc2eab0c 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -29,6 +29,12 @@ def setup @commit_email = File.expand_path('../../tool/commit-email.rb', __dir__) end + def teardown + File.unlink(@sendmail) + Dir.rmdir(File.dirname(@sendmail)) + FileUtils.rm_rf(@ruby) + end + def test_sendmail_encoding omit 'the sendmail script does not work on windows' if windows? From d17ce4bd05dc05d48f5f4bc75171fcebda1678ed Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 10:07:32 +0900 Subject: [PATCH 0617/2435] [Bug #21652] [DOC] Update unmarshalable object list The `Data` mentioned here was the old `T_DATA` class, not the current extended `Struct`. --- marshal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marshal.c b/marshal.c index 4372960a7190ef..8cd4dc6079a39a 100644 --- a/marshal.c +++ b/marshal.c @@ -1189,7 +1189,7 @@ io_needed(void) * * anonymous Class/Module. * * objects which are related to system (ex: Dir, File::Stat, IO, File, Socket * and so on) - * * an instance of MatchData, Data, Method, UnboundMethod, Proc, Thread, + * * an instance of MatchData, Method, UnboundMethod, Proc, Thread, * ThreadGroup, Continuation * * objects which define singleton methods */ From e4219e2742ac9f8a8647a0a028e63abb0d968057 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 28 Oct 2025 21:24:02 -0400 Subject: [PATCH 0618/2435] Fix flaky require test --- test/ruby/test_require.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 49afcceb331e57..bbec9a5f954f93 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -857,7 +857,13 @@ def to_str def to_path = @path end - def create_ruby_file = Tempfile.open(["test", ".rb"]).path + FILES = [] + + def create_ruby_file + file = Tempfile.open(["test", ".rb"]) + FILES << file + file.path + end require MyString.new(create_ruby_file) $LOADED_FEATURES.unshift(create_ruby_file) From 7bc72469564b8f55b90aa9c0586854c14337aa75 Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 29 Oct 2025 05:17:11 +0330 Subject: [PATCH 0619/2435] [DOC] Update refinements.rdoc removes documentation for the activation order of `include` and `prepend` inside a refinement --- doc/syntax/refinements.rdoc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/syntax/refinements.rdoc b/doc/syntax/refinements.rdoc index 17d5e67c21a377..4095977284cf55 100644 --- a/doc/syntax/refinements.rdoc +++ b/doc/syntax/refinements.rdoc @@ -212,10 +212,7 @@ all refinements from the same module are active when a refined method When looking up a method for an instance of class +C+ Ruby checks: -* If refinements are active for +C+, in the reverse order they were activated: - * The prepended modules from the refinement for +C+ - * The refinement for +C+ - * The included modules from the refinement for +C+ +* The refinements of +C+, in reverse order of activation * The prepended modules of +C+ * +C+ * The included modules of +C+ From e1a0bcde769b26ab5dc47569981c8c7d0af21072 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 14:12:22 +0900 Subject: [PATCH 0620/2435] Added missing rubygems pages for default/bundled gems --- doc/maintainers.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/doc/maintainers.md b/doc/maintainers.md index 9aa1a03bf62682..fdd7697ee6b370 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -175,6 +175,7 @@ consensus on ruby-core/ruby-dev. * Nobuyuki Nakada ([nobu]) * https://github.com/ruby/optparse +* https://rubygems.org/gems/optparse #### lib/net/http.rb, lib/net/https.rb @@ -198,6 +199,7 @@ consensus on ruby-core/ruby-dev. * Tanaka Akira ([akr]) * https://github.com/ruby/open-uri +* https://rubygems.org/gems/open-uri #### lib/pp.rb @@ -397,165 +399,199 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes * Ryan Davis ([zenspider]) * https://github.com/minitest/minitest +* https://rubygems.org/gems/minitest ### power_assert * Tsujimoto Kenta ([k-tsj]) * https://github.com/ruby/power_assert +* https://rubygems.org/gems/power_assert ### rake * Hiroshi SHIBATA ([hsbt]) * https://github.com/ruby/rake +* https://rubygems.org/gems/rake ### test-unit * Kouhei Sutou ([kou]) * https://github.com/test-unit/test-unit +* https://rubygems.org/gems/test-unit ### rexml * Kouhei Sutou ([kou]) * https://github.com/ruby/rexml +* https://rubygems.org/gems/rexml ### rss * Kouhei Sutou ([kou]) * https://github.com/ruby/rss +* https://rubygems.org/gems/rss ### net-ftp * Shugo Maeda ([shugo]) * https://github.com/ruby/net-ftp +* https://rubygems.org/gems/net-ftp ### net-imap * Nicholas A. Evans ([nevans]) * https://github.com/ruby/net-imap +* https://rubygems.org/gems/net-imap ### net-pop * https://github.com/ruby/net-pop +* https://rubygems.org/gems/net-pop ### net-smtp * TOMITA Masahiro ([tmtm]) * https://github.com/ruby/net-smtp +* https://rubygems.org/gems/net-smtp ### matrix * Marc-André Lafortune ([marcandre]) * https://github.com/ruby/matrix +* https://rubygems.org/gems/matrix ### prime * https://github.com/ruby/prime +* https://rubygems.org/gems/prime ### rbs * Soutaro Matsumoto ([soutaro]) * https://github.com/ruby/rbs +* https://rubygems.org/gems/rbs ### typeprof * Yusuke Endoh ([mame]) * https://github.com/ruby/typeprof +* https://rubygems.org/gems/typeprof ### debug * Koichi Sasada ([ko1]) * https://github.com/ruby/debug +* https://rubygems.org/gems/debug ### racc * Yuichi Kaneko ([yui-knk]) * https://github.com/ruby/racc +* https://rubygems.org/gems/racc #### mutex_m * https://github.com/ruby/mutex_m +* https://rubygems.org/gems/mutex_m #### getoptlong * https://github.com/ruby/getoptlong +* https://rubygems.org/gems/getoptlong #### base64 * Yusuke Endoh ([mame]) * https://github.com/ruby/base64 +* https://rubygems.org/gems/base64 #### bigdecimal * Kenta Murata ([mrkn]) * https://github.com/ruby/bigdecimal +* https://rubygems.org/gems/bigdecimal #### observer * https://github.com/ruby/observer +* https://rubygems.org/gems/observer #### abbrev * Akinori MUSHA ([knu]) * https://github.com/ruby/abbrev +* https://rubygems.org/gems/abbrev #### resolv-replace * Akira TANAKA ([akr]) * https://github.com/ruby/resolv-replace +* https://rubygems.org/gems/resolv-replace #### rinda * Masatoshi SEKI ([seki]) * https://github.com/ruby/rinda +* https://rubygems.org/gems/rinda #### drb * Masatoshi SEKI ([seki]) * https://github.com/ruby/drb +* https://rubygems.org/gems/drb #### nkf * Naruse Yusuke ([nurse]) * https://github.com/ruby/nkf +* https://rubygems.org/gems/nkf #### syslog * Akinori Musha ([knu]) * https://github.com/ruby/syslog +* https://rubygems.org/gems/syslog #### csv * Kouhei Sutou ([kou]) * https://github.com/ruby/csv +* https://rubygems.org/gems/csv #### ostruct * Marc-André Lafortune ([marcandre]) * https://github.com/ruby/ostruct +* https://rubygems.org/gems/ostruct #### pstore * https://github.com/ruby/pstore +* https://rubygems.org/gems/pstore #### benchmark * https://github.com/ruby/benchmark +* https://rubygems.org/gems/benchmark #### logger * Naotoshi Seo ([sonots]) * https://github.com/ruby/logger +* https://rubygems.org/gems/logger #### rdoc * Stan Lo ([st0012]) * Nobuyoshi Nakada ([nobu]) * https://github.com/ruby/rdoc +* https://rubygems.org/gems/rdoc #### win32ole * Masaki Suketa ([suketa]) * https://github.com/ruby/win32ole +* https://rubygems.org/gems/win32ole #### irb @@ -564,6 +600,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes * Mari Imaizumi ([ima1zumi]) * HASUMI Hitoshi ([hasumikin]) * https://github.com/ruby/irb +* https://rubygems.org/gems/irb #### reline @@ -572,15 +609,18 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes * Mari Imaizumi ([ima1zumi]) * HASUMI Hitoshi ([hasumikin]) * https://github.com/ruby/reline +* https://rubygems.org/gems/reline #### readline * https://github.com/ruby/readline +* https://rubygems.org/gems/readline #### fiddle * Kouhei Sutou ([kou]) * https://github.com/ruby/fiddle +* https://rubygems.org/gems/fiddle ## Platform Maintainers From 103e91a06376c98e8d1cd876dc7cdb8cc6e087ef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 13:56:58 +0900 Subject: [PATCH 0621/2435] Remove alternatives for ruby 1.8 and earliers --- spec/ruby/core/symbol/inspect_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index 26229da9442807..5af03a64848f6d 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -6,7 +6,7 @@ :fred? => ":fred?", :fred! => ":fred!", :BAD! => ":BAD!", - :_BAD! => ":_BAD!", + :_BAD! => ":_BAD!", :$ruby => ":$ruby", :@ruby => ":@ruby", :@@ruby => ":@@ruby", @@ -66,9 +66,9 @@ :~ => ":~", :| => ":|", - :"!" => [":\"!\"", ":!" ], - :"!=" => [":\"!=\"", ":!="], - :"!~" => [":\"!~\"", ":!~"], + :"!" => ":!", + :"!=" => ":!=", + :"!~" => ":!~", :"\$" => ":\"$\"", # for justice! :"&&" => ":\"&&\"", :"'" => ":\"\'\"", @@ -103,7 +103,6 @@ } symbols.each do |input, expected| - expected = expected[1] if expected.is_a?(Array) it "returns self as a symbol literal for #{expected}" do input.inspect.should == expected end From f8d7482d0269ab287a0254f33e53f55f4d4bf343 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 14:21:51 +0900 Subject: [PATCH 0622/2435] Fix for other than UTF-8 environments --- spec/ruby/core/symbol/inspect_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index 5af03a64848f6d..df4566c48e6449 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -97,12 +97,14 @@ :" foo" => ":\" foo\"", :" " => ":\" \"", - :"ê" => ":ê", - :"测" => ":测", - :"🦊" => ":🦊", + :"ê" => [":ê", ":\"\\u00EA\""], + :"测" => [":测", ":\"\\u6D4B\""], + :"🦊" => [":🦊", ":\"\\u{1F98A}\""], } + expected_by_encoding = Encoding::default_external == Encoding::UTF_8 ? 0 : 1 symbols.each do |input, expected| + expected = expected[expected_by_encoding] if expected.is_a?(Array) it "returns self as a symbol literal for #{expected}" do input.inspect.should == expected end From 4f223b72a9976cd015c4d926297c615098d04d23 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 15:29:10 +0900 Subject: [PATCH 0623/2435] [ruby/zlib] Bump up v3.2.2 https://github.com/ruby/zlib/commit/5d50b223b1 --- ext/zlib/zlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 640c28122c48ac..d019891afeacae 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "3.2.1" +#define RUBY_ZLIB_VERSION "3.2.2" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) From 582475b317e8ee88fc530499c316d135b8fbf1fc Mon Sep 17 00:00:00 2001 From: git Date: Wed, 29 Oct 2025 06:31:45 +0000 Subject: [PATCH 0624/2435] Update default gems list at 4f223b72a9976cd015c4d926297c61 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 047bdeeb3e235f..c6bcce74e338f2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -199,6 +199,7 @@ The following default gems are updated. * strscan 3.1.6.dev * uri 1.0.4 * weakref 0.1.4 +* zlib 3.2.2 The following bundled gems are added. From 2dcf1f74a417c01c64f45f1ea73d9346ab444774 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 15:49:48 +0900 Subject: [PATCH 0625/2435] [ruby/timeout] v0.4.4 https://github.com/ruby/timeout/commit/f42b47d383 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index f5f232ad2ae072..e1f0a4a78c6f59 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.3" + VERSION = "0.4.4" # Internal error raised to when a timeout is triggered. class ExitException < Exception From bb5f5a5293191d8d81dea4f4da3add47584b1854 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 29 Oct 2025 06:58:01 +0000 Subject: [PATCH 0626/2435] Update default gems list at 2dcf1f74a417c01c64f45f1ea73d93 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index c6bcce74e338f2..2eae6577ed3922 100644 --- a/NEWS.md +++ b/NEWS.md @@ -197,6 +197,7 @@ The following default gems are updated. * resolv 0.6.2 * stringio 3.1.8.dev * strscan 3.1.6.dev +* timeout 0.4.4 * uri 1.0.4 * weakref 0.1.4 * zlib 3.2.2 From 337c4bc16384fa44f7a486d550461af93e2e6001 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 16:00:40 +0900 Subject: [PATCH 0627/2435] [ruby/date] v3.5.0 https://github.com/ruby/date/commit/d535f7e85f --- ext/date/lib/date.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb index aa630eb6d172b5..b33f6e65f466b0 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,7 +4,7 @@ require 'date_core' class Date - VERSION = "3.4.1" # :nodoc: + VERSION = "3.5.0" # :nodoc: # call-seq: # infinite? -> false From b8f7c18aa83d14e14357b2013758b439ca572722 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 29 Oct 2025 07:10:08 +0000 Subject: [PATCH 0628/2435] Update default gems list at 337c4bc16384fa44f7a486d550461a [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 2eae6577ed3922..5c9900cbb1ab68 100644 --- a/NEWS.md +++ b/NEWS.md @@ -182,6 +182,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev +* date 3.5.0 * erb 5.1.3 * etc 1.4.6 * fcntl 1.3.0 From 8f0b9b27cec1ec187c6357586dc98d17c42c5c0a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 16:34:26 +0900 Subject: [PATCH 0629/2435] [ruby/fileutils] v1.8.0 https://github.com/ruby/fileutils/commit/29de582f68 --- lib/fileutils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fileutils.rb b/lib/fileutils.rb index 6ef71d75ef39f3..0706e007ca4979 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -181,7 +181,7 @@ # module FileUtils # The version number. - VERSION = "1.7.3" + VERSION = "1.8.0" def self.private_module_function(name) #:nodoc: module_function name From b55e91156c520109c961b4a0626f567821fd6f31 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 29 Oct 2025 07:36:10 +0000 Subject: [PATCH 0630/2435] Update default gems list at 8f0b9b27cec1ec187c6357586dc98d [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 5c9900cbb1ab68..0b622064720f22 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,6 +186,7 @@ The following default gems are updated. * erb 5.1.3 * etc 1.4.6 * fcntl 1.3.0 +* fileutils 1.8.0 * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 From 6e618a2c79973557847ad23d5a5d9f97146068ba Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 16:48:50 +0900 Subject: [PATCH 0631/2435] Added repl_type_completor to bundled gems section --- doc/maintainers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/maintainers.md b/doc/maintainers.md index fdd7697ee6b370..244912cb20c9ca 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -622,6 +622,12 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes * https://github.com/ruby/fiddle * https://rubygems.org/gems/fiddle +#### repl_type_completor + +* Tomoya Ishida ([tompng]) +* https://github.com/ruby/repl_type_completor +* https://rubygems.org/gems/repl_type_completor + ## Platform Maintainers ### mswin64 (Microsoft Windows) From 12350eb9e0d3317da57b5a37c0c2810946b48850 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 28 Oct 2025 21:55:18 +0900 Subject: [PATCH 0632/2435] [Bug #21625] Allow io/wait methods with `IO#ungetc` in text mode --- ext/io/wait/wait.c | 8 +++---- io.c | 4 ++-- test/io/wait/test_io_wait_uncommon.rb | 30 +++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c index 88e6dd2af1fb58..bec04cb727e054 100644 --- a/ext/io/wait/wait.c +++ b/ext/io/wait/wait.c @@ -84,7 +84,7 @@ io_nread(VALUE io) ioctl_arg n; GetOpenFile(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); len = rb_io_read_pending(fptr); if (len > 0) return INT2FIX(len); @@ -143,7 +143,7 @@ io_ready_p(VALUE io) #endif GetOpenFile(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); if (rb_io_read_pending(fptr)) return Qtrue; #ifndef HAVE_RB_IO_WAIT @@ -178,7 +178,7 @@ io_wait_readable(int argc, VALUE *argv, VALUE io) #endif GetOpenFile(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); #ifndef HAVE_RB_IO_WAIT tv = get_timeout(argc, argv, &timerec); @@ -252,7 +252,7 @@ io_wait_priority(int argc, VALUE *argv, VALUE io) rb_io_t *fptr = NULL; RB_IO_POINTER(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); if (rb_io_read_pending(fptr)) return Qtrue; diff --git a/io.c b/io.c index d412d14963bded..78ac0bb2c65d9e 100644 --- a/io.c +++ b/io.c @@ -9832,7 +9832,7 @@ io_wait_readable(int argc, VALUE *argv, VALUE io) rb_io_t *fptr; RB_IO_POINTER(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); if (rb_io_read_pending(fptr)) return Qtrue; @@ -9879,7 +9879,7 @@ io_wait_priority(int argc, VALUE *argv, VALUE io) rb_io_t *fptr = NULL; RB_IO_POINTER(io, fptr); - rb_io_check_readable(fptr); + rb_io_check_char_readable(fptr); if (rb_io_read_pending(fptr)) return Qtrue; diff --git a/test/io/wait/test_io_wait_uncommon.rb b/test/io/wait/test_io_wait_uncommon.rb index 0f922f4e24b34f..0f97ac35d9124e 100644 --- a/test/io/wait/test_io_wait_uncommon.rb +++ b/test/io/wait/test_io_wait_uncommon.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'test/unit' +require 'io/wait' # test uncommon device types to check portability problems # We may optimize IO#wait_*able for non-Linux kernels in the future @@ -74,4 +75,33 @@ def test_wait_readable_zero def test_wait_writable_null check_dev(IO::NULL, :wait_writable) end + + def test_after_ungetc_ready? + check_dev(IO::NULL, mode: "r") {|fp| + assert_respond_to fp, :ready? + fp.ungetc(?a) + assert_predicate fp, :ready? + } + end + + def test_after_ungetc_wait_readable + check_dev(IO::NULL, mode: "r") {|fp| + fp.ungetc(?a) + assert_predicate fp, :wait_readable + } + end + + def test_after_ungetc_in_text_ready? + check_dev(IO::NULL, mode: "rt") {|fp| + fp.ungetc(?a) + assert_predicate fp, :ready? + } + end + + def test_after_ungetc_in_text_wait_readable + check_dev(IO::NULL, mode: "rt") {|fp| + fp.ungetc(?a) + assert_predicate fp, :wait_readable + } + end end From 0f629083722a09f0b5a75040ca0511e71eb6032a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 18:12:32 +0900 Subject: [PATCH 0633/2435] [ruby/date] Prefer `method_defined?` over `allocate.respond_to?` https://github.com/ruby/date/commit/fd8e3725f8 --- test/date/test_date_conv.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/date/test_date_conv.rb b/test/date/test_date_conv.rb index 772fbee9a4e6ce..c54a118f8ff0e4 100644 --- a/test/date/test_date_conv.rb +++ b/test/date/test_date_conv.rb @@ -82,7 +82,7 @@ def test_to_time__from_datetime assert_equal([1582, 10, 13, 1, 2, 3, 456789], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.usec]) - if Time.allocate.respond_to?(:nsec) + if Time.method_defined?(:nsec) d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123.to_r/86400000000000 t = d.to_time.utc assert_equal([2004, 9, 19, 1, 2, 3, 456789123], @@ -91,7 +91,7 @@ def test_to_time__from_datetime # TruffleRuby does not support more than nanoseconds unless RUBY_ENGINE == 'truffleruby' - if Time.allocate.respond_to?(:subsec) + if Time.method_defined?(:subsec) d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123456789123.to_r/86400000000000000000000 t = d.to_time.utc assert_equal([2004, 9, 19, 1, 2, 3, Rational(456789123456789123,1000000000000000000)], From e49e0bb6c9b4ade9d8fb615a1f7cc65f9ea13a7f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 18:40:25 +0900 Subject: [PATCH 0634/2435] [ruby/date] Remove archaic conditions `Time#nsec` and `Time#subsec` were both introduced in Ruby 1.9. https://github.com/ruby/date/commit/2c310d9f5c --- test/date/test_date_conv.rb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/date/test_date_conv.rb b/test/date/test_date_conv.rb index c54a118f8ff0e4..8d810844355956 100644 --- a/test/date/test_date_conv.rb +++ b/test/date/test_date_conv.rb @@ -82,21 +82,17 @@ def test_to_time__from_datetime assert_equal([1582, 10, 13, 1, 2, 3, 456789], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.usec]) - if Time.method_defined?(:nsec) - d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123.to_r/86400000000000 - t = d.to_time.utc - assert_equal([2004, 9, 19, 1, 2, 3, 456789123], - [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.nsec]) - end + d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123.to_r/86400000000000 + t = d.to_time.utc + assert_equal([2004, 9, 19, 1, 2, 3, 456789123], + [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.nsec]) # TruffleRuby does not support more than nanoseconds unless RUBY_ENGINE == 'truffleruby' - if Time.method_defined?(:subsec) - d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123456789123.to_r/86400000000000000000000 - t = d.to_time.utc - assert_equal([2004, 9, 19, 1, 2, 3, Rational(456789123456789123,1000000000000000000)], - [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.subsec]) - end + d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123456789123.to_r/86400000000000000000000 + t = d.to_time.utc + assert_equal([2004, 9, 19, 1, 2, 3, Rational(456789123456789123,1000000000000000000)], + [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.subsec]) end end From 606abf2faba96e301e8f7445fc9fcc272cbf6b9e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 19:41:51 +0900 Subject: [PATCH 0635/2435] Clean extension libraries copied for the namespace --- test/ruby/test_namespace.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index bfbbe38e051ab2..6bc529b2811dd0 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -668,7 +668,9 @@ def test_prelude_gems_and_loaded_features_with_disable_gems end def test_root_and_main_methods - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + tmp = Dir.mktmpdir("namespace_so") + + assert_separately([{"TMPDIR"=>tmp, **ENV_ENABLE_NAMESPACE}], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; pend unless Namespace.respond_to?(:root) and Namespace.respond_to?(:main) # for RUBY_DEBUG > 0 @@ -690,5 +692,8 @@ def test_root_and_main_methods assert !$LOADED_FEATURES.include?("/tmp/barbaz") assert !Object.const_defined?(:FooClass) end; + ensure + File.unlink(*Dir.glob("_ruby_ns_*."+RbConfig::CONFIG["DLEXT"], base: tmp).map {|so| "#{tmp}/#{so}"}) + Dir.rmdir(tmp) end end From 2096ca8030f506b2f37d0c41796c5318e8a13126 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 22 Oct 2025 22:49:59 +0900 Subject: [PATCH 0636/2435] win32: Optimize `FIMETIME` calculations Assume that `FILETIME` and `ULARGE_INTEGER::u` are the same layout actually. --- win32/win32.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index c13c4e17320268..6db7fa60f48479 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4724,24 +4724,27 @@ waitpid(rb_pid_t pid, int *stat_loc, int options) #include +/* License: Ruby's */ +typedef union { + /* FILETIME and ULARGE_INTEGER::u are the same layout */ + FILETIME ft; + ULARGE_INTEGER i; +} FILETIME_INTEGER; + /* License: Ruby's */ /* split FILETIME value into UNIX time and sub-seconds in NT ticks */ static time_t filetime_split(const FILETIME* ft, long *subsec) { - ULARGE_INTEGER tmp; - unsigned LONG_LONG lt; const unsigned LONG_LONG subsec_unit = (unsigned LONG_LONG)10 * 1000 * 1000; - - tmp.LowPart = ft->dwLowDateTime; - tmp.HighPart = ft->dwHighDateTime; - lt = tmp.QuadPart; + FILETIME_INTEGER fi = {.ft = *ft}; + ULONGLONG lt = fi.i.QuadPart; /* lt is now 100-nanosec intervals since 1601/01/01 00:00:00 UTC, convert it into UNIX time (since 1970/01/01 00:00:00 UTC). the first leap second is at 1972/06/30, so we doesn't need to think about it. */ - lt -= (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60 * subsec_unit; + lt -= (ULONGLONG)((1970-1601)*365.2425) * 24 * 60 * 60 * subsec_unit; *subsec = (long)(lt % subsec_unit); return (time_t)(lt / subsec_unit); @@ -4762,15 +4765,6 @@ gettimeofday(struct timeval *tv, struct timezone *tz) } /* License: Ruby's */ -static FILETIME -filetimes_plus(FILETIME t1, FILETIME t2) -{ - ULARGE_INTEGER i1 = {.u = {.LowPart = t1.dwLowDateTime, .HighPart = t1.dwHighDateTime}}; - ULARGE_INTEGER i2 = {.u = {.LowPart = t2.dwLowDateTime, .HighPart = t2.dwHighDateTime}}; - ULARGE_INTEGER i = {.QuadPart = i1.QuadPart + i2.QuadPart}; - return (FILETIME){.dwLowDateTime = i.LowPart, .dwHighDateTime = i.HighPart}; -} - static void filetime_to_timespec(FILETIME ft, struct timespec *sp) { @@ -4800,11 +4794,11 @@ clock_gettime(clockid_t clock_id, struct timespec *sp) { LARGE_INTEGER freq; LARGE_INTEGER count; - if (!QueryPerformanceFrequency(&freq)) { + if (UNLIKELY(!QueryPerformanceFrequency(&freq))) { errno = map_errno(GetLastError()); return -1; } - if (!QueryPerformanceCounter(&count)) { + if (UNLIKELY(!QueryPerformanceCounter(&count))) { errno = map_errno(GetLastError()); return -1; } @@ -4818,19 +4812,20 @@ clock_gettime(clockid_t clock_id, struct timespec *sp) case CLOCK_PROCESS_CPUTIME_ID: case CLOCK_THREAD_CPUTIME_ID: { - FILETIME ct, et, kt, ut; + FILETIME_INTEGER c, e, k, u, total; BOOL ok; if (clock_id == CLOCK_PROCESS_CPUTIME_ID) { - ok = GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut); + ok = GetProcessTimes(GetCurrentProcess(), &c.ft, &e.ft, &k.ft, &u.ft); } else { - ok = GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut); + ok = GetThreadTimes(GetCurrentThread(), &c.ft, &e.ft, &k.ft, &u.ft); } - if (!ok) { + if (UNLIKELY(!ok)) { errno = map_errno(GetLastError()); return -1; } - filetime_to_timespec(filetimes_plus(kt, ut), sp); + total.i.QuadPart = k.i.QuadPart + u.i.QuadPart; + filetime_to_timespec(total.ft, sp); return 0; } default: From c90184828d5f5d627e185ede2433f556ebca1e8b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 22 Oct 2025 22:54:43 +0900 Subject: [PATCH 0637/2435] win32: Extract `FILETIME` related constants. --- win32/win32.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index 6db7fa60f48479..e7dfe2b065975a 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4724,6 +4724,13 @@ waitpid(rb_pid_t pid, int *stat_loc, int options) #include +/* License: Ruby's */ +#define filetime_unit (10UL * 1000 * 1000) +#define filetime_diff_days ((1970-1601)*3652425UL/10000) +#define filetime_diff_secs (filetime_diff_days * (24ULL * 60 * 60)) +#define unix_to_filetime(sec) (((sec) + filetime_diff_secs) * filetime_unit) +#define filetime_unix_offset unix_to_filetime(0ULL) + /* License: Ruby's */ typedef union { /* FILETIME and ULARGE_INTEGER::u are the same layout */ @@ -4736,7 +4743,6 @@ typedef union { static time_t filetime_split(const FILETIME* ft, long *subsec) { - const unsigned LONG_LONG subsec_unit = (unsigned LONG_LONG)10 * 1000 * 1000; FILETIME_INTEGER fi = {.ft = *ft}; ULONGLONG lt = fi.i.QuadPart; @@ -4744,10 +4750,10 @@ filetime_split(const FILETIME* ft, long *subsec) convert it into UNIX time (since 1970/01/01 00:00:00 UTC). the first leap second is at 1972/06/30, so we doesn't need to think about it. */ - lt -= (ULONGLONG)((1970-1601)*365.2425) * 24 * 60 * 60 * subsec_unit; + lt -= unix_to_filetime(0); - *subsec = (long)(lt % subsec_unit); - return (time_t)(lt / subsec_unit); + *subsec = (long)(lt % filetime_unit); + return (time_t)(lt / filetime_unit); } /* License: Ruby's */ @@ -7593,7 +7599,7 @@ unixtime_to_filetime(time_t time, FILETIME *ft) { ULARGE_INTEGER tmp; - tmp.QuadPart = ((LONG_LONG)time + (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60) * 10 * 1000 * 1000; + tmp.QuadPart = unix_to_filetime((ULONGLONG)time); ft->dwLowDateTime = tmp.LowPart; ft->dwHighDateTime = tmp.HighPart; return 0; @@ -7606,7 +7612,7 @@ timespec_to_filetime(const struct timespec *ts, FILETIME *ft) { ULARGE_INTEGER tmp; - tmp.QuadPart = ((LONG_LONG)ts->tv_sec + (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60) * 10 * 1000 * 1000; + tmp.QuadPart = unix_to_filetime((ULONGLONG)ts->tv_sec); tmp.QuadPart += ts->tv_nsec / 100; ft->dwLowDateTime = tmp.LowPart; ft->dwHighDateTime = tmp.HighPart; From cee4a46c966200dda44c1d7de203a22629f95b03 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 20:35:03 +0900 Subject: [PATCH 0638/2435] Use the given `TMPDIR` --- test/ruby/test_namespace.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 6bc529b2811dd0..5a014ad7148a51 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -668,9 +668,7 @@ def test_prelude_gems_and_loaded_features_with_disable_gems end def test_root_and_main_methods - tmp = Dir.mktmpdir("namespace_so") - - assert_separately([{"TMPDIR"=>tmp, **ENV_ENABLE_NAMESPACE}], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; pend unless Namespace.respond_to?(:root) and Namespace.respond_to?(:main) # for RUBY_DEBUG > 0 @@ -693,7 +691,8 @@ def test_root_and_main_methods assert !Object.const_defined?(:FooClass) end; ensure - File.unlink(*Dir.glob("_ruby_ns_*."+RbConfig::CONFIG["DLEXT"], base: tmp).map {|so| "#{tmp}/#{so}"}) - Dir.rmdir(tmp) + tmp = ENV["TMPDIR"] || ENV["TMP"] || Etc.systmpdir || "/tmp" + pat = "_ruby_ns_*."+RbConfig::CONFIG["DLEXT"] + File.unlink(*Dir.glob(pat, base: tmp).map {|so| "#{tmp}/#{so}"}) end end From fcd7da15e693206941f388aa260b59fd9301833a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 08:17:11 -0700 Subject: [PATCH 0639/2435] ZJIT: Introduce a better LIR printer (#14986) --- zjit/src/asm/arm64/opnd.rs | 78 ++++++++++- zjit/src/asm/x86_64/mod.rs | 75 ++++++++++- zjit/src/backend/arm64/mod.rs | 67 +++++++++- zjit/src/backend/lir.rs | 234 +++++++++++++++++++++++++++++++-- zjit/src/backend/tests.rs | 11 +- zjit/src/backend/x86_64/mod.rs | 70 +++++++++- zjit/src/codegen.rs | 16 +-- zjit/src/options.rs | 82 +++++++++++- 8 files changed, 580 insertions(+), 53 deletions(-) diff --git a/zjit/src/asm/arm64/opnd.rs b/zjit/src/asm/arm64/opnd.rs index 8246bea08e69ee..667533ab938e0e 100644 --- a/zjit/src/asm/arm64/opnd.rs +++ b/zjit/src/asm/arm64/opnd.rs @@ -1,4 +1,4 @@ - +use std::fmt; /// This operand represents a register. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -18,7 +18,7 @@ impl A64Reg { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct A64Mem { // Size in bits @@ -42,7 +42,7 @@ impl A64Mem { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum A64Opnd { // Dummy operand @@ -196,3 +196,75 @@ pub const W31: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 31 }); // C argument registers pub const C_ARG_REGS: [A64Opnd; 4] = [X0, X1, X2, X3]; pub const C_ARG_REGREGS: [A64Reg; 4] = [X0_REG, X1_REG, X2_REG, X3_REG]; + +impl fmt::Display for A64Reg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match A64Opnd::Reg(*self) { + X0 => write!(f, "x0"), + X1 => write!(f, "x1"), + X2 => write!(f, "x2"), + X3 => write!(f, "x3"), + X4 => write!(f, "x4"), + X5 => write!(f, "x5"), + X6 => write!(f, "x6"), + X7 => write!(f, "x7"), + X8 => write!(f, "x8"), + X9 => write!(f, "x9"), + X10 => write!(f, "x10"), + X11 => write!(f, "x11"), + X12 => write!(f, "x12"), + X13 => write!(f, "x13"), + X14 => write!(f, "x14"), + X15 => write!(f, "x15"), + X16 => write!(f, "x16"), + X17 => write!(f, "x17"), + X18 => write!(f, "x18"), + X19 => write!(f, "x19"), + X20 => write!(f, "x20"), + X21 => write!(f, "x21"), + X22 => write!(f, "x22"), + X23 => write!(f, "x23"), + X24 => write!(f, "x24"), + X25 => write!(f, "x25"), + X26 => write!(f, "x26"), + X27 => write!(f, "x27"), + X28 => write!(f, "x28"), + X29 => write!(f, "x29"), + X30 => write!(f, "x30"), + X31 => write!(f, "x31"), + W0 => write!(f, "w0"), + W1 => write!(f, "w1"), + W2 => write!(f, "w2"), + W3 => write!(f, "w3"), + W4 => write!(f, "w4"), + W5 => write!(f, "w5"), + W6 => write!(f, "w6"), + W7 => write!(f, "w7"), + W8 => write!(f, "w8"), + W9 => write!(f, "w9"), + W10 => write!(f, "w10"), + W11 => write!(f, "w11"), + W12 => write!(f, "w12"), + W13 => write!(f, "w13"), + W14 => write!(f, "w14"), + W15 => write!(f, "w15"), + W16 => write!(f, "w16"), + W17 => write!(f, "w17"), + W18 => write!(f, "w18"), + W19 => write!(f, "w19"), + W20 => write!(f, "w20"), + W21 => write!(f, "w21"), + W22 => write!(f, "w22"), + W23 => write!(f, "w23"), + W24 => write!(f, "w24"), + W25 => write!(f, "w25"), + W26 => write!(f, "w26"), + W27 => write!(f, "w27"), + W28 => write!(f, "w28"), + W29 => write!(f, "w29"), + W30 => write!(f, "w30"), + W31 => write!(f, "w31"), + _ => write!(f, "{self:?}"), + } + } +} diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index 8077218c4ac720..9d3bf18dcdab41 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -47,7 +47,7 @@ pub struct X86Reg pub reg_no: u8, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct X86Mem { // Size in bits @@ -66,7 +66,7 @@ pub struct X86Mem pub disp: i32, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum X86Opnd { // Dummy operand @@ -1380,3 +1380,74 @@ pub fn xor(cb: &mut CodeBlock, opnd0: X86Opnd, opnd1: X86Opnd) { opnd1 ); } + +impl fmt::Display for X86Reg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match X86Opnd::Reg(*self) { + RAX => write!(f, "rax"), + RCX => write!(f, "rcx"), + RDX => write!(f, "rdx"), + RBX => write!(f, "rbx"), + RSP => write!(f, "rsp"), + RBP => write!(f, "rbp"), + RSI => write!(f, "rsi"), + RDI => write!(f, "rdi"), + R8 => write!(f, "r8"), + R9 => write!(f, "r9"), + R10 => write!(f, "r10"), + R11 => write!(f, "r11"), + R12 => write!(f, "r12"), + R13 => write!(f, "r13"), + R14 => write!(f, "r14"), + R15 => write!(f, "r15"), + EAX => write!(f, "eax"), + ECX => write!(f, "ecx"), + EDX => write!(f, "edx"), + EBX => write!(f, "ebx"), + ESP => write!(f, "esp"), + EBP => write!(f, "ebp"), + ESI => write!(f, "esi"), + EDI => write!(f, "edi"), + R8D => write!(f, "r8d"), + R9D => write!(f, "r9d"), + R10D => write!(f, "r10d"), + R11D => write!(f, "r11d"), + R12D => write!(f, "r12d"), + R13D => write!(f, "r13d"), + R14D => write!(f, "r14d"), + R15D => write!(f, "r15d"), + AX => write!(f, "ax"), + CX => write!(f, "cx"), + DX => write!(f, "dx"), + BX => write!(f, "bx"), + BP => write!(f, "bp"), + SI => write!(f, "si"), + DI => write!(f, "di"), + R8W => write!(f, "r8w"), + R9W => write!(f, "r9w"), + R10W => write!(f, "r10w"), + R11W => write!(f, "r11w"), + R12W => write!(f, "r12w"), + R13W => write!(f, "r13w"), + R14W => write!(f, "r14w"), + R15W => write!(f, "r15w"), + AL => write!(f, "al"), + CL => write!(f, "cl"), + DL => write!(f, "dl"), + BL => write!(f, "bl"), + SPL => write!(f, "spl"), + BPL => write!(f, "bpl"), + SIL => write!(f, "sil"), + DIL => write!(f, "dil"), + R8B => write!(f, "r8b"), + R9B => write!(f, "r9b"), + R10B => write!(f, "r10b"), + R11B => write!(f, "r11b"), + R12B => write!(f, "r12b"), + R13B => write!(f, "r13b"), + R14B => write!(f, "r14b"), + R15B => write!(f, "r15b"), + _ => write!(f, "{self:?}"), + } + } +} diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 234f5ac0590ba5..d7f929a0219653 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -4,6 +4,7 @@ use crate::asm::{CodeBlock, Label}; use crate::asm::arm64::*; use crate::cruby::*; use crate::backend::lir::*; +use crate::options::asm_dump; use crate::stats::CompileError; use crate::virtualmem::CodePtr; use crate::cast::*; @@ -11,6 +12,11 @@ use crate::cast::*; // Use the arm64 register type for this platform pub type Reg = A64Reg; +/// Convert reg_no for MemBase::Reg into Reg, assuming it's a 64-bit register +pub fn mem_base_reg(reg_no: u8) -> Reg { + Reg { num_bits: 64, reg_no } +} + // Callee-saved registers pub const CFP: Opnd = Opnd::Reg(X19_REG); pub const EC: Opnd = Opnd::Reg(X20_REG); @@ -194,7 +200,7 @@ pub const ALLOC_REGS: &[Reg] = &[ ]; /// Special scratch registers for intermediate processing. They should be used only by -/// [`Assembler::arm64_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. +/// [`Assembler::arm64_scratch_split`] or [`Assembler::new_with_scratch_reg`]. const SCRATCH0_OPND: Opnd = Opnd::Reg(X15_REG); const SCRATCH1_OPND: Opnd = Opnd::Reg(X17_REG); @@ -683,7 +689,7 @@ impl Assembler { /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this /// splits them and uses scratch registers for it. - fn arm64_split_with_scratch_reg(self) -> Assembler { + fn arm64_scratch_split(self) -> Assembler { let mut asm = Assembler::new_with_asm(&self); asm.accept_scratch_reg = true; let mut iterator = self.insns.into_iter().enumerate().peekable(); @@ -971,6 +977,9 @@ impl Assembler { // For each instruction let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { + // Dump Assembler with insn_idx if --zjit-dump-lir=panic is given + let _hook = AssemblerPanicHook::new(self, insn_idx); + match insn { Insn::Comment(text) => { cb.add_comment(text); @@ -1070,7 +1079,7 @@ impl Assembler { } }, Insn::Mul { left, right, out } => { - // If the next instruction is JoMul with RShift created by arm64_split_with_scratch_reg + // If the next instruction is JoMul with RShift created by arm64_scratch_split match (self.insns.get(insn_idx + 1), self.insns.get(insn_idx + 2)) { (Some(Insn::RShift { out: out_sign, opnd: out_opnd, shift: out_shift }), Some(Insn::JoMul(_))) => { // Compute the high 64 bits @@ -1081,7 +1090,7 @@ impl Assembler { // so we do it after smulh mul(cb, out.into(), left.into(), right.into()); - // Insert the shift instruction created by arm64_split_with_scratch_reg + // Insert the shift instruction created by arm64_scratch_split // to prepare the register that has the sign bit of the high 64 bits after mul. asr(cb, out_sign.into(), out_opnd.into(), out_shift.into()); insn_idx += 1; // skip the next Insn::RShift @@ -1382,12 +1391,12 @@ impl Assembler { last_patch_pos = Some(cb.get_write_pos()); }, Insn::IncrCounter { mem, value } => { - // Get the status register allocated by arm64_split_with_scratch_reg + // Get the status register allocated by arm64_scratch_split let Some(Insn::Cmp { left: status_reg @ Opnd::Reg(_), right: Opnd::UImm(_) | Opnd::Imm(_), }) = self.insns.get(insn_idx + 1) else { - panic!("arm64_split_with_scratch_reg should add Cmp after IncrCounter: {:?}", self.insns.get(insn_idx + 1)); + panic!("arm64_scratch_split should add Cmp after IncrCounter: {:?}", self.insns.get(insn_idx + 1)); }; // Attempt to increment a counter @@ -1451,13 +1460,21 @@ impl Assembler { pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Result<(CodePtr, Vec), CompileError> { // The backend is allowed to use scratch registers only if it has not accepted them so far. let use_scratch_reg = !self.accept_scratch_reg; + asm_dump!(self, init); let asm = self.arm64_split(); + asm_dump!(asm, split); + let mut asm = asm.alloc_regs(regs)?; + asm_dump!(asm, alloc_regs); + // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. asm.compile_side_exits(); + asm_dump!(asm, compile_side_exits); + if use_scratch_reg { - asm = asm.arm64_split_with_scratch_reg(); + asm = asm.arm64_scratch_split(); + asm_dump!(asm, scratch_split); } // Create label instances in the code block @@ -1528,6 +1545,42 @@ mod tests { (Assembler::new(), CodeBlock::new_dummy()) } + #[test] + fn test_lir_string() { + use crate::hir::SideExitReason; + + let mut asm = Assembler::new(); + asm.stack_base_idx = 1; + + let label = asm.new_label("bb0"); + asm.write_label(label.clone()); + asm.push_insn(Insn::Comment("bb0(): foo@/tmp/a.rb:1".into())); + asm.frame_setup(JIT_PRESERVED_REGS); + + let val64 = asm.add(CFP, Opnd::UImm(64)); + asm.store(Opnd::mem(64, SP, 0x10), val64); + let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None }; + asm.push_insn(Insn::Joz(val64, side_exit)); + + let val32 = asm.sub(Opnd::Value(Qtrue), Opnd::Imm(1)); + asm.store(Opnd::mem(64, EC, 0x10).with_num_bits(32), val32.with_num_bits(32)); + asm.cret(val64); + + asm.frame_teardown(JIT_PRESERVED_REGS); + assert_disasm_snapshot!(lir_string(&mut asm), @r" + bb0: + # bb0(): foo@/tmp/a.rb:1 + FrameSetup 1, x19, x21, x20 + v0 = Add x19, 0x40 + Store [x21 + 0x10], v0 + Joz Exit(Interrupt), v0 + v1 = Sub Value(0x14), Imm(1) + Store Mem32[x20 + 0x10], VReg32(v1) + CRet v0 + FrameTeardown x19, x21, x20 + "); + } + #[test] fn test_mul_with_immediate() { let (mut asm, mut cb) = setup_asm(); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 72ebeaf11be61a..e5707beb51bcf6 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1,10 +1,13 @@ use std::collections::HashMap; use std::fmt; use std::mem::take; +use std::panic; +use std::rc::Rc; +use std::sync::Arc; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::SideExitReason; -use crate::options::{debug, get_option, TraceExits}; +use crate::options::{TraceExits, debug, dump_lir_option, get_option}; use crate::cruby::VALUE; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; @@ -12,6 +15,7 @@ use crate::asm::{CodeBlock, Label}; use crate::state::rb_zjit_record_exit_stack; pub use crate::backend::current::{ + mem_base_reg, Reg, EC, CFP, SP, NATIVE_STACK_PTR, NATIVE_BASE_PTR, @@ -42,6 +46,28 @@ pub struct Mem pub num_bits: u8, } +impl fmt::Display for Mem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.num_bits != 64 { + write!(f, "Mem{}", self.num_bits)?; + } + write!(f, "[")?; + match self.base { + MemBase::Reg(reg_no) => write!(f, "{}", mem_base_reg(reg_no))?, + MemBase::VReg(idx) => write!(f, "v{idx}")?, + } + if self.disp != 0 { + let sign = if self.disp > 0 { '+' } else { '-' }; + write!(f, " {sign} ")?; + if self.disp.abs() >= 10 { + write!(f, "0x")?; + } + write!(f, "{:x}", self.disp.abs())?; + } + write!(f, "]") + } +} + impl fmt::Debug for Mem { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "Mem{}[{:?}", self.num_bits, self.base)?; @@ -73,6 +99,25 @@ pub enum Opnd Reg(Reg), // Machine register } +impl fmt::Display for Opnd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Opnd::*; + match self { + None => write!(f, "None"), + Value(VALUE(value)) if *value < 10 => write!(f, "Value({value:x})"), + Value(VALUE(value)) => write!(f, "Value(0x{value:x})"), + VReg { idx, num_bits } if *num_bits == 64 => write!(f, "v{idx}"), + VReg { idx, num_bits } => write!(f, "VReg{num_bits}(v{idx})"), + Imm(value) if value.abs() < 10 => write!(f, "Imm({value:x})"), + Imm(value) => write!(f, "Imm(0x{value:x})"), + UImm(value) if *value < 10 => write!(f, "{value:x}"), + UImm(value) => write!(f, "0x{value:x}"), + Mem(mem) => write!(f, "{mem}"), + Reg(reg) => write!(f, "{reg}"), + } + } +} + impl fmt::Debug for Opnd { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use Opnd::*; @@ -291,9 +336,10 @@ impl From for Target { } } -type PosMarkerFn = Box; +type PosMarkerFn = Rc; /// ZJIT Low-level IR instruction +#[derive(Clone)] pub enum Insn { /// Add two operands together, and return the result as a new operand. Add { left: Opnd, right: Opnd, out: Opnd }, @@ -793,8 +839,6 @@ impl<'a> Iterator for InsnOpndIterator<'a> { Insn::CPop { .. } | Insn::CPopAll | Insn::CPushAll | - Insn::FrameSetup { .. } | - Insn::FrameTeardown { .. } | Insn::PadPatchPoint | Insn::PosMarker(_) => None, @@ -860,14 +904,29 @@ impl<'a> Iterator for InsnOpndIterator<'a> { } }, Insn::ParallelMov { moves } => { - if self.idx < moves.len() { - let opnd = &moves[self.idx].1; + if self.idx < moves.len() * 2 { + let move_idx = self.idx / 2; + let opnd = if self.idx % 2 == 0 { + &moves[move_idx].0 + } else { + &moves[move_idx].1 + }; self.idx += 1; Some(opnd) } else { None } }, + Insn::FrameSetup { preserved, .. } | + Insn::FrameTeardown { preserved } => { + if self.idx < preserved.len() { + let opnd = &preserved[self.idx]; + self.idx += 1; + Some(opnd) + } else { + None + } + } } } } @@ -1016,8 +1075,13 @@ impl<'a> InsnOpndMutIterator<'a> { } }, Insn::ParallelMov { moves } => { - if self.idx < moves.len() { - let opnd = &mut moves[self.idx].1; + if self.idx < moves.len() * 2 { + let move_idx = self.idx / 2; + let opnd = if self.idx % 2 == 0 { + &mut moves[move_idx].0 + } else { + &mut moves[move_idx].1 + }; self.idx += 1; Some(opnd) } else { @@ -1166,6 +1230,7 @@ const ASSEMBLER_INSNS_CAPACITY: usize = 256; /// Object into which we assemble instructions to be /// optimized and lowered +#[derive(Clone)] pub struct Assembler { pub(super) insns: Vec, @@ -1714,6 +1779,93 @@ impl Assembler } } +const BOLD_BEGIN: &str = "\x1b[1m"; +const BOLD_END: &str = "\x1b[22m"; + +/// Return a result of fmt::Display for Assembler without escape sequence +pub fn lir_string(asm: &Assembler) -> String { + format!("{asm}").replace(BOLD_BEGIN, "").replace(BOLD_END, "") +} + +impl fmt::Display for Assembler { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for insn in self.insns.iter() { + match insn { + Insn::Comment(comment) => { + writeln!(f, " {BOLD_BEGIN}# {comment}{BOLD_END}")?; + } + Insn::Label(target) => { + let &Target::Label(Label(label_idx)) = target else { + panic!("unexpected target for Insn::Label: {target:?}"); + }; + writeln!(f, " {}:", self.label_names[label_idx])?; + } + _ => { + write!(f, " ")?; + + // Print output operand if any + if let Some(out) = insn.out_opnd() { + write!(f, "{out} = ")?; + } + + write!(f, "{}", insn.op())?; + + // Show slot_count for FrameSetup + if let Insn::FrameSetup { slot_count, preserved } = insn { + write!(f, " {slot_count}")?; + if !preserved.is_empty() { + write!(f, ",")?; + } + } + + // Print target + if let Some(target) = insn.target() { + match target { + Target::CodePtr(code_ptr) => write!(f, " {code_ptr:?}")?, + Target::Label(Label(label_idx)) => write!(f, " {}", self.label_names[*label_idx])?, + Target::SideExit { reason, .. } => write!(f, " Exit({reason})")?, + } + } + + // Print list of operands + if let Some(Target::SideExit { .. }) = insn.target() { + // If the instruction has a SideExit, avoid using opnd_iter(), which has stack/locals. + // Here, only handle instructions that have both Opnd and Target. + match insn { + Insn::Joz(opnd, _) | + Insn::Jonz(opnd, _) | + Insn::LeaJumpTarget { out: opnd, target: _ } => { + write!(f, ", {opnd}")?; + } + _ => {} + } + } else if let Insn::ParallelMov { moves } = insn { + // Print operands with a special syntax for ParallelMov + let mut moves_iter = moves.iter(); + if let Some((first_dst, first_src)) = moves_iter.next() { + write!(f, " {first_dst} <- {first_src}")?; + } + for (dst, src) in moves_iter { + write!(f, ", {dst} <- {src}")?; + } + } else { + let mut opnd_iter = insn.opnd_iter(); + if let Some(first_opnd) = opnd_iter.next() { + write!(f, " {first_opnd}")?; + } + for opnd in opnd_iter { + write!(f, ", {opnd}")?; + } + } + + write!(f, "\n")?; + } + } + } + Ok(()) + } +} + impl fmt::Debug for Assembler { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { writeln!(fmt, "Assembler")?; @@ -1777,8 +1929,8 @@ impl Assembler { self.push_insn(Insn::CCall { fptr, opnds, - start_marker: Some(Box::new(start_marker)), - end_marker: Some(Box::new(end_marker)), + start_marker: Some(Rc::new(start_marker)), + end_marker: Some(Rc::new(end_marker)), out, }); out @@ -2028,7 +2180,7 @@ impl Assembler { } pub fn pos_marker(&mut self, marker_fn: impl Fn(CodePtr, &CodeBlock) + 'static) { - self.push_insn(Insn::PosMarker(Box::new(marker_fn))); + self.push_insn(Insn::PosMarker(Rc::new(marker_fn))); } #[must_use] @@ -2092,7 +2244,7 @@ impl Assembler { /// when not dumping disassembly. macro_rules! asm_comment { ($asm:expr, $($fmt:tt)*) => { - if $crate::options::get_option!(dump_disasm) { + if $crate::options::get_option!(dump_disasm) || $crate::options::get_option!(dump_lir).is_some() { $asm.push_insn(crate::backend::lir::Insn::Comment(format!($($fmt)*))); } }; @@ -2108,6 +2260,64 @@ macro_rules! asm_ccall { } pub(crate) use asm_ccall; +// Allow moving Assembler to panic hooks. Since we take the VM lock on compilation, +// no other threads should reference the same Assembler instance. +unsafe impl Send for Insn {} +unsafe impl Sync for Insn {} + +/// Dump Assembler with insn_idx on panic. Restore the original panic hook on drop. +pub struct AssemblerPanicHook { + /// Original panic hook before AssemblerPanicHook is installed. + prev_hook: Box) + Sync + Send + 'static>, +} + +impl AssemblerPanicHook { + pub fn new(asm: &Assembler, insn_idx: usize) -> Option> { + // Install a panic hook if --zjit-dump-lir=panic is specified. + if dump_lir_option!(panic) { + // Wrap prev_hook with Arc to share it among the new hook and Self to be dropped. + let prev_hook = panic::take_hook(); + let panic_hook_ref = Arc::new(Self { prev_hook }); + let weak_hook = Arc::downgrade(&panic_hook_ref); + + // Install a new hook to dump Assembler with insn_idx + let asm = asm.clone(); + panic::set_hook(Box::new(move |panic_info| { + if let Some(panic_hook) = weak_hook.upgrade() { + // Dump Assembler, highlighting the insn_idx line + Self::dump_asm(&asm, insn_idx); + + // Call the previous panic hook + (panic_hook.prev_hook)(panic_info); + } + })); + + Some(panic_hook_ref) + } else { + None + } + } + + /// Dump Assembler, highlighting the insn_idx line + fn dump_asm(asm: &Assembler, insn_idx: usize) { + println!("Failed to compile LIR at insn_idx={insn_idx}:"); + for (idx, line) in lir_string(asm).split('\n').enumerate() { + if idx == insn_idx && line.starts_with(" ") { + println!("{BOLD_BEGIN}=>{}{BOLD_END}", &line[" ".len()..]); + } else { + println!("{line}"); + } + } + } +} + +impl Drop for AssemblerPanicHook { + fn drop(&mut self) { + // Restore the original hook + panic::set_hook(std::mem::replace(&mut self.prev_hook, Box::new(|_| {}))); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 52547cb31fde65..6e62b3068d5a29 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -62,10 +62,8 @@ fn test_alloc_regs() { } fn setup_asm() -> (Assembler, CodeBlock) { - ( - Assembler::new(), - CodeBlock::new_dummy() - ) + rb_zjit_prepare_options(); // for get_option! on asm.compile + (Assembler::new(), CodeBlock::new_dummy()) } // Test full codegen pipeline @@ -87,7 +85,6 @@ fn test_compile() fn test_mov_mem2mem() { let (mut asm, mut cb) = setup_asm(); - rb_zjit_prepare_options(); // for asm_comment asm_comment!(asm, "check that comments work too"); asm.mov(Opnd::mem(64, SP, 0), Opnd::mem(64, SP, 8)); @@ -181,7 +178,6 @@ fn test_c_call() } let (mut asm, mut cb) = setup_asm(); - rb_zjit_prepare_options(); // for asm.compile let ret_val = asm.ccall( dummy_c_fun as *const u8, @@ -196,11 +192,10 @@ fn test_c_call() #[test] fn test_alloc_ccall_regs() { - let mut asm = Assembler::new(); + let (mut asm, mut cb) = setup_asm(); let out1 = asm.ccall(std::ptr::null::(), vec![]); let out2 = asm.ccall(std::ptr::null::(), vec![out1]); asm.mov(EC, out2); - let mut cb = CodeBlock::new_dummy(); asm.compile_with_regs(&mut cb, Assembler::get_alloc_regs()).unwrap(); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 2590ceaf7d0e2b..a2e27028e6a90a 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -7,10 +7,16 @@ use crate::virtualmem::CodePtr; use crate::cruby::*; use crate::backend::lir::*; use crate::cast::*; +use crate::options::asm_dump; // Use the x86 register type for this platform pub type Reg = X86Reg; +/// Convert reg_no for MemBase::Reg into Reg, assuming it's a 64-bit GP register +pub fn mem_base_reg(reg_no: u8) -> Reg { + Reg { num_bits: 64, reg_type: RegType::GP, reg_no } +} + // Callee-saved registers pub const CFP: Opnd = Opnd::Reg(R13_REG); pub const EC: Opnd = Opnd::Reg(R12_REG); @@ -96,7 +102,7 @@ pub const ALLOC_REGS: &[Reg] = &[ ]; /// Special scratch register for intermediate processing. It should be used only by -/// [`Assembler::x86_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`]. +/// [`Assembler::x86_scratch_split`] or [`Assembler::new_with_scratch_reg`]. const SCRATCH0_OPND: Opnd = Opnd::Reg(R11_REG); impl Assembler { @@ -384,7 +390,7 @@ impl Assembler { /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so /// this splits them and uses scratch registers for it. - pub fn x86_split_with_scratch_reg(self) -> Assembler { + pub fn x86_scratch_split(self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register /// allocator. So we just use the SCRATCH0_OPND register temporarily to hold @@ -490,7 +496,7 @@ impl Assembler { // Handle various operand combinations for spills on compile_side_exits. &mut Insn::Store { dest, src } => { let Opnd::Mem(Mem { num_bits, .. }) = dest else { - panic!("Unexpected Insn::Store destination in x86_split_with_scratch_reg: {dest:?}"); + panic!("Unexpected Insn::Store destination in x86_scratch_split: {dest:?}"); }; let src = match src { @@ -525,7 +531,7 @@ impl Assembler { asm.load_into(SCRATCH0_OPND, src); SCRATCH0_OPND } - src @ (Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during x86_split_with_scratch_reg: {src:?}"), + src @ (Opnd::None | Opnd::VReg { .. }) => panic!("Unexpected source operand during x86_scratch_split: {src:?}"), }; asm.store(dest, src); } @@ -587,6 +593,9 @@ impl Assembler { // For each instruction let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { + // Dump Assembler with insn_idx if --zjit-dump-lir=panic is given + let _hook = AssemblerPanicHook::new(self, insn_idx); + match insn { Insn::Comment(text) => { cb.add_comment(text); @@ -949,13 +958,21 @@ impl Assembler { pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Result<(CodePtr, Vec), CompileError> { // The backend is allowed to use scratch registers only if it has not accepted them so far. let use_scratch_regs = !self.accept_scratch_reg; + asm_dump!(self, init); let asm = self.x86_split(); + asm_dump!(asm, split); + let mut asm = asm.alloc_regs(regs)?; + asm_dump!(asm, alloc_regs); + // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. asm.compile_side_exits(); + asm_dump!(asm, compile_side_exits); + if use_scratch_regs { - asm = asm.x86_split_with_scratch_reg(); + asm = asm.x86_scratch_split(); + asm_dump!(asm, scratch_split); } // Create label instances in the code block @@ -981,12 +998,53 @@ impl Assembler { mod tests { use insta::assert_snapshot; use crate::assert_disasm_snapshot; + use crate::options::rb_zjit_prepare_options; use super::*; + const BOLD_BEGIN: &str = "\x1b[1m"; + const BOLD_END: &str = "\x1b[22m"; + fn setup_asm() -> (Assembler, CodeBlock) { + rb_zjit_prepare_options(); // for get_option! on asm.compile (Assembler::new(), CodeBlock::new_dummy()) } + #[test] + fn test_lir_string() { + use crate::hir::SideExitReason; + + let mut asm = Assembler::new(); + asm.stack_base_idx = 1; + + let label = asm.new_label("bb0"); + asm.write_label(label.clone()); + asm.push_insn(Insn::Comment("bb0(): foo@/tmp/a.rb:1".into())); + asm.frame_setup(JIT_PRESERVED_REGS); + + let val64 = asm.add(CFP, Opnd::UImm(64)); + asm.store(Opnd::mem(64, SP, 0x10), val64); + let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None }; + asm.push_insn(Insn::Joz(val64, side_exit)); + + let val32 = asm.sub(Opnd::Value(Qtrue), Opnd::Imm(1)); + asm.store(Opnd::mem(64, EC, 0x10).with_num_bits(32), val32.with_num_bits(32)); + asm.cret(val64); + + asm.frame_teardown(JIT_PRESERVED_REGS); + assert_disasm_snapshot!(lir_string(&mut asm), @r" + bb0: + # bb0(): foo@/tmp/a.rb:1 + FrameSetup 1, r13, rbx, r12 + v0 = Add r13, 0x40 + Store [rbx + 0x10], v0 + Joz Exit(Interrupt), v0 + v1 = Sub Value(0x14), Imm(1) + Store Mem32[r12 + 0x10], VReg32(v1) + CRet v0 + FrameTeardown r13, rbx, r12 + "); + } + #[test] #[ignore] fn test_emit_add_lt_32_bits() { @@ -1596,7 +1654,7 @@ mod tests { assert!(imitation_heap_value.heap_object_p()); asm.store(Opnd::mem(VALUE_BITS, SP, 0), imitation_heap_value.into()); - asm = asm.x86_split_with_scratch_reg(); + asm = asm.x86_scratch_split(); let gc_offsets = asm.x86_emit(&mut cb).unwrap(); assert_eq!(1, gc_offsets.len(), "VALUE source operand should be reported as gc offset"); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2ac7ca348ad492..db9d6a14e3d1b5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -184,10 +184,6 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result asm.frame_teardown(lir::JIT_PRESERVED_REGS); asm.cret(C_RET_OPND); - if get_option!(dump_lir) { - println!("LIR:\nJIT entry for {}:\n{:?}", iseq_name(iseq), asm); - } - let (code_ptr, gc_offsets) = asm.compile(cb)?; assert!(gc_offsets.is_empty()); if get_option!(perf) { @@ -256,6 +252,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul // Compile each basic block let reverse_post_order = function.rpo(); for &block_id in reverse_post_order.iter() { + // Write a label to jump to the basic block + let label = jit.get_label(&mut asm, block_id); + asm.write_label(label); + let block = function.block(block_id); asm_comment!( asm, "{block_id}({}): {}", @@ -263,10 +263,6 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul iseq_get_location(iseq, block.insn_idx), ); - // Write a label to jump to the basic block - let label = jit.get_label(&mut asm, block_id); - asm.write_label(label); - // Compile all parameters for (idx, &insn_id) in block.params().enumerate() { match function.find(insn_id) { @@ -293,10 +289,6 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul asm.pad_patch_point(); } - if get_option!(dump_lir) { - println!("LIR:\nfn {}:\n{:?}", iseq_name(iseq), asm); - } - // Generate code if everything can be compiled let result = asm.compile(cb); if let Ok((start_ptr, _)) = result { diff --git a/zjit/src/options.rs b/zjit/src/options.rs index f4a52e1ccdc6bd..44f4dbc7a475be 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -67,7 +67,7 @@ pub struct Options { pub dump_hir_graphviz: Option, /// Dump low-level IR - pub dump_lir: bool, + pub dump_lir: Option>, /// Dump all compiled machine code. pub dump_disasm: bool, @@ -101,7 +101,7 @@ impl Default for Options { dump_hir_init: None, dump_hir_opt: None, dump_hir_graphviz: None, - dump_lir: false, + dump_lir: None, dump_disasm: false, trace_side_exits: None, trace_side_exits_sample_interval: 0, @@ -150,6 +150,55 @@ pub enum DumpHIR { Debug, } +/// --zjit-dump-lir values. Using snake_case to stringify the exact filter value. +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum DumpLIR { + /// Dump the initial LIR + init, + /// Dump LIR after {arch}_split + split, + /// Dump LIR after alloc_regs + alloc_regs, + /// Dump LIR after compile_side_exits + compile_side_exits, + /// Dump LIR after {arch}_scratch_split + scratch_split, + /// Dump LIR at panic on {arch}_emit + panic, +} + +/// All compiler stages for --zjit-dump-lir=all. This does NOT include DumpLIR::panic. +const DUMP_LIR_ALL: &[DumpLIR] = &[ + DumpLIR::init, + DumpLIR::split, + DumpLIR::alloc_regs, + DumpLIR::compile_side_exits, + DumpLIR::scratch_split, +]; + +/// Macro to dump LIR if --zjit-dump-lir is specified +macro_rules! asm_dump { + ($asm:expr, $target:ident) => { + if crate::options::dump_lir_option!($target) { + println!("LIR {}:\n{}", stringify!($target), $asm); + } + }; +} +pub(crate) use asm_dump; + +/// Macro to check if a particular dump_lir option is enabled +macro_rules! dump_lir_option { + ($target:ident) => { + if let Some(crate::options::Options { dump_lir: Some(dump_lirs), .. }) = unsafe { crate::options::OPTIONS.as_ref() } { + dump_lirs.contains(&crate::options::DumpLIR::$target) + } else { + false + } + }; +} +pub(crate) use dump_lir_option; + /// Macro to get an option value by name macro_rules! get_option { // Unsafe is ok here because options are initialized @@ -301,7 +350,34 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.dump_hir_graphviz = Some(opt_val); } - ("dump-lir", "") => options.dump_lir = true, + ("dump-lir", "") => options.dump_lir = Some(HashSet::from([DumpLIR::init])), + ("dump-lir", filters) => { + let mut dump_lirs = HashSet::new(); + for filter in filters.split(',') { + let dump_lir = match filter { + "all" => { + for &dump_lir in DUMP_LIR_ALL { + dump_lirs.insert(dump_lir); + } + continue; + } + "init" => DumpLIR::init, + "split" => DumpLIR::split, + "alloc_regs" => DumpLIR::alloc_regs, + "compile_side_exits" => DumpLIR::compile_side_exits, + "scratch_split" => DumpLIR::scratch_split, + "panic" => DumpLIR::panic, + _ => { + let valid_options = DUMP_LIR_ALL.iter().map(|opt| format!("{opt:?}")).collect::>().join(", "); + eprintln!("invalid --zjit-dump-lir option: '{filter}'"); + eprintln!("valid --zjit-dump-lir options: all, {}, panic", valid_options); + return None; + } + }; + dump_lirs.insert(dump_lir); + } + options.dump_lir = Some(dump_lirs); + } ("dump-disasm", "") => options.dump_disasm = true, From 0d9f8580f5a9fc7ae39a8e9e2275d9303874cde9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 29 Oct 2025 12:49:26 -0400 Subject: [PATCH 0640/2435] ZJIT: Fix LoadIvarExtended test (#14989) --- zjit/src/hir/opt_tests.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 897cd6e9566966..c7764bd290ca57 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4554,10 +4554,13 @@ mod hir_opt_tests { #[test] fn test_optimize_getivar_extended() { - eval(" + eval(r#" class C attr_reader :foo def initialize + 1000.times do |i| + instance_variable_set("@v#{i}", i) + end @foo = 42 end end @@ -4566,9 +4569,9 @@ mod hir_opt_tests { def test(o) = o.foo test O test O - "); + "#); assert_snapshot!(hir_string("test"), @r" - fn test@:10: + fn test@:13: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf @@ -4582,7 +4585,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + v26:BasicObject = LoadIvarExtended v25, :@foo@0x1039 CheckInterrupts Return v26 "); From eed9441afc861f10d113102536d0e616f44a069f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 11:24:49 -0700 Subject: [PATCH 0641/2435] Revert "CI: Re-enable Ubuntu arm with CAPI check" This reverts commit 02fd62895d9a13f56253521dedd80e39d4eafbb5. It stopped working again. --- .github/workflows/ubuntu.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 0dfba09bc39556..786b6c177a4b90 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -40,9 +40,9 @@ jobs: - test_task: check os: ubuntu-24.04 extra_checks: [capi] - - test_task: check - os: ubuntu-24.04-arm - extra_checks: [capi] + # ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-10-29 + #- test_task: check + # os: ubuntu-24.04-arm fail-fast: false env: &make-env From 1775447c89c8947574d830c010f2e4efb356827b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 12:00:40 -0700 Subject: [PATCH 0642/2435] ZJIT: Refactor operand printer --- zjit/src/backend/arm64/mod.rs | 4 ++++ zjit/src/backend/lir.rs | 19 ++++--------------- zjit/src/backend/x86_64/mod.rs | 4 ++++ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index d7f929a0219653..6b1cebf15e1cbd 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1561,9 +1561,11 @@ mod tests { asm.store(Opnd::mem(64, SP, 0x10), val64); let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None }; asm.push_insn(Insn::Joz(val64, side_exit)); + asm.parallel_mov(vec![(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32)), (C_ARG_OPNDS[1], Opnd::mem(64, SP, -8))]); let val32 = asm.sub(Opnd::Value(Qtrue), Opnd::Imm(1)); asm.store(Opnd::mem(64, EC, 0x10).with_num_bits(32), val32.with_num_bits(32)); + asm.je(label); asm.cret(val64); asm.frame_teardown(JIT_PRESERVED_REGS); @@ -1574,8 +1576,10 @@ mod tests { v0 = Add x19, 0x40 Store [x21 + 0x10], v0 Joz Exit(Interrupt), v0 + ParallelMov x0 <- w0, x1 <- [x21 - 8] v1 = Sub Value(0x14), Imm(1) Store Mem32[x20 + 0x10], VReg32(v1) + Je bb0 CRet v0 FrameTeardown x19, x21, x20 "); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index e5707beb51bcf6..f8a8352604f781 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1808,6 +1808,7 @@ impl fmt::Display for Assembler { write!(f, "{out} = ")?; } + // Print the instruction name write!(f, "{}", insn.op())?; // Show slot_count for FrameSetup @@ -1841,21 +1842,9 @@ impl fmt::Display for Assembler { } } else if let Insn::ParallelMov { moves } = insn { // Print operands with a special syntax for ParallelMov - let mut moves_iter = moves.iter(); - if let Some((first_dst, first_src)) = moves_iter.next() { - write!(f, " {first_dst} <- {first_src}")?; - } - for (dst, src) in moves_iter { - write!(f, ", {dst} <- {src}")?; - } - } else { - let mut opnd_iter = insn.opnd_iter(); - if let Some(first_opnd) = opnd_iter.next() { - write!(f, " {first_opnd}")?; - } - for opnd in opnd_iter { - write!(f, ", {opnd}")?; - } + moves.iter().try_fold(" ", |prefix, (dst, src)| write!(f, "{prefix}{dst} <- {src}").and(Ok(", ")))?; + } else if insn.opnd_iter().count() > 0 { + insn.opnd_iter().try_fold(" ", |prefix, opnd| write!(f, "{prefix}{opnd}").and(Ok(", ")))?; } write!(f, "\n")?; diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index a2e27028e6a90a..78e2f663a542c3 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -1025,9 +1025,11 @@ mod tests { asm.store(Opnd::mem(64, SP, 0x10), val64); let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None }; asm.push_insn(Insn::Joz(val64, side_exit)); + asm.parallel_mov(vec![(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32)), (C_ARG_OPNDS[1], Opnd::mem(64, SP, -8))]); let val32 = asm.sub(Opnd::Value(Qtrue), Opnd::Imm(1)); asm.store(Opnd::mem(64, EC, 0x10).with_num_bits(32), val32.with_num_bits(32)); + asm.je(label); asm.cret(val64); asm.frame_teardown(JIT_PRESERVED_REGS); @@ -1038,8 +1040,10 @@ mod tests { v0 = Add r13, 0x40 Store [rbx + 0x10], v0 Joz Exit(Interrupt), v0 + ParallelMov rdi <- eax, rsi <- [rbx - 8] v1 = Sub Value(0x14), Imm(1) Store Mem32[r12 + 0x10], VReg32(v1) + Je bb0 CRet v0 FrameTeardown r13, rbx, r12 "); From 8b289cde22fabe62bea1f14a1b8477e4f825e604 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 13:06:41 -0700 Subject: [PATCH 0643/2435] ZJIT: Print operands with Display on side exit --- zjit/src/backend/lir.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f8a8352604f781..b15029df973cd2 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1702,6 +1702,10 @@ impl Assembler /// Compile Target::SideExit and convert it into Target::CodePtr for all instructions pub fn compile_side_exits(&mut self) { + fn join_opnds(opnds: &Vec, delimiter: &str) -> String { + opnds.iter().map(|opnd| format!("{opnd}")).collect::>().join(delimiter) + } + let mut targets = HashMap::new(); for (idx, insn) in self.insns.iter().enumerate() { if let Some(target @ Target::SideExit { .. }) = insn.target() { @@ -1723,12 +1727,12 @@ impl Assembler // Restore the PC and the stack for regular side exits. We don't do this for // side exits right after JIT-to-JIT calls, which restore them before the call. - asm_comment!(self, "write stack slots: {stack:?}"); + asm_comment!(self, "write stack slots: {}", join_opnds(&stack, ", ")); for (idx, &opnd) in stack.iter().enumerate() { self.store(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), opnd); } - asm_comment!(self, "write locals: {locals:?}"); + asm_comment!(self, "write locals: {}", join_opnds(&locals, ", ")); for (idx, &opnd) in locals.iter().enumerate() { self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd); } From 397cb107b034bf2b3d2656c07757e6b422cf19c9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 13:39:07 -0700 Subject: [PATCH 0644/2435] ZJIT: Suffix a label index to duplicated label names --- zjit/src/backend/lir.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index b15029df973cd2..1e7a2c0cdea312 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1793,6 +1793,24 @@ pub fn lir_string(asm: &Assembler) -> String { impl fmt::Display for Assembler { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Count the number of duplicated label names to disambiguate them if needed + let mut label_counts: HashMap<&String, usize> = HashMap::new(); + for label_name in self.label_names.iter() { + let counter = label_counts.entry(label_name).or_insert(0); + *counter += 1; + } + + /// Return a label name String. Suffix "_{label_idx}" if the label name is used multiple times. + fn label_name(asm: &Assembler, label_idx: usize, label_counts: &HashMap<&String, usize>) -> String { + let label_name = &asm.label_names[label_idx]; + let label_count = label_counts.get(&label_name).unwrap_or(&0); + if *label_count > 1 { + format!("{label_name}_{label_idx}") + } else { + label_name.to_string() + } + } + for insn in self.insns.iter() { match insn { Insn::Comment(comment) => { @@ -1802,7 +1820,7 @@ impl fmt::Display for Assembler { let &Target::Label(Label(label_idx)) = target else { panic!("unexpected target for Insn::Label: {target:?}"); }; - writeln!(f, " {}:", self.label_names[label_idx])?; + writeln!(f, " {}:", label_name(self, label_idx, &label_counts))?; } _ => { write!(f, " ")?; @@ -1827,7 +1845,7 @@ impl fmt::Display for Assembler { if let Some(target) = insn.target() { match target { Target::CodePtr(code_ptr) => write!(f, " {code_ptr:?}")?, - Target::Label(Label(label_idx)) => write!(f, " {}", self.label_names[*label_idx])?, + Target::Label(Label(label_idx)) => write!(f, " {}", label_name(self, *label_idx, &label_counts))?, Target::SideExit { reason, .. } => write!(f, " Exit({reason})")?, } } From 16a7a22c5a3eee13a91239da42e64417aa08457e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 14:58:58 -0700 Subject: [PATCH 0645/2435] ZJIT: Avoid cloning Assembler repeatedly --- zjit/src/backend/arm64/mod.rs | 7 +++++-- zjit/src/backend/lir.rs | 23 ++++++++++++++++------- zjit/src/backend/x86_64/mod.rs | 7 +++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 6b1cebf15e1cbd..9e94fd0e496643 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -974,11 +974,14 @@ impl Assembler { // The write_pos for the last Insn::PatchPoint, if any let mut last_patch_pos: Option = None; + // Install a panic hook to dump Assembler with insn_idx on dev builds + let (_hook, mut hook_insn_idx) = AssemblerPanicHook::new(self, 0); + // For each instruction let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { - // Dump Assembler with insn_idx if --zjit-dump-lir=panic is given - let _hook = AssemblerPanicHook::new(self, insn_idx); + // Update insn_idx that is shown on panic + hook_insn_idx.as_mut().map(|idx| idx.lock().map(|mut idx| *idx = insn_idx).unwrap()); match insn { Insn::Comment(text) => { diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 1e7a2c0cdea312..3d2cfb678664fe 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -3,7 +3,7 @@ use std::fmt; use std::mem::take; use std::panic; use std::rc::Rc; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::SideExitReason; @@ -2283,29 +2283,38 @@ pub struct AssemblerPanicHook { } impl AssemblerPanicHook { - pub fn new(asm: &Assembler, insn_idx: usize) -> Option> { - // Install a panic hook if --zjit-dump-lir=panic is specified. + /// Install a panic hook to dump Assembler with insn_idx on dev builds. + /// This returns shared references to the previous hook and insn_idx. + /// It takes insn_idx as an argument so that you can manually use it + /// on non-emit passes that keep mutating the Assembler to be dumped. + pub fn new(asm: &Assembler, insn_idx: usize) -> (Option>, Option>>) { if dump_lir_option!(panic) { // Wrap prev_hook with Arc to share it among the new hook and Self to be dropped. let prev_hook = panic::take_hook(); let panic_hook_ref = Arc::new(Self { prev_hook }); let weak_hook = Arc::downgrade(&panic_hook_ref); + // Wrap insn_idx with Arc to share it among the new hook and the caller mutating it. + let insn_idx = Arc::new(Mutex::new(insn_idx)); + let insn_idx_ref = insn_idx.clone(); + // Install a new hook to dump Assembler with insn_idx let asm = asm.clone(); panic::set_hook(Box::new(move |panic_info| { if let Some(panic_hook) = weak_hook.upgrade() { - // Dump Assembler, highlighting the insn_idx line - Self::dump_asm(&asm, insn_idx); + if let Ok(insn_idx) = insn_idx_ref.lock() { + // Dump Assembler, highlighting the insn_idx line + Self::dump_asm(&asm, *insn_idx); + } // Call the previous panic hook (panic_hook.prev_hook)(panic_info); } })); - Some(panic_hook_ref) + (Some(panic_hook_ref), Some(insn_idx)) } else { - None + (None, None) } } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 78e2f663a542c3..eb0e70330287b2 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -590,11 +590,14 @@ impl Assembler { // The write_pos for the last Insn::PatchPoint, if any let mut last_patch_pos: Option = None; + // Install a panic hook to dump Assembler with insn_idx on dev builds + let (_hook, mut hook_insn_idx) = AssemblerPanicHook::new(self, 0); + // For each instruction let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { - // Dump Assembler with insn_idx if --zjit-dump-lir=panic is given - let _hook = AssemblerPanicHook::new(self, insn_idx); + // Update insn_idx that is shown on panic + hook_insn_idx.as_mut().map(|idx| idx.lock().map(|mut idx| *idx = insn_idx).unwrap()); match insn { Insn::Comment(text) => { From 534aeaef09ecd0101230065ecbfb59c2146f7954 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 14:58:27 -0700 Subject: [PATCH 0646/2435] ZJIT: Dump Assembler on panic by default for dev builds --- zjit/src/backend/lir.rs | 4 ++-- zjit/src/options.rs | 25 ++++++------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 3d2cfb678664fe..5980e9e0224622 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::SideExitReason; -use crate::options::{TraceExits, debug, dump_lir_option, get_option}; +use crate::options::{TraceExits, debug, get_option}; use crate::cruby::VALUE; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; @@ -2288,7 +2288,7 @@ impl AssemblerPanicHook { /// It takes insn_idx as an argument so that you can manually use it /// on non-emit passes that keep mutating the Assembler to be dumped. pub fn new(asm: &Assembler, insn_idx: usize) -> (Option>, Option>>) { - if dump_lir_option!(panic) { + if cfg!(debug_assertions) { // Wrap prev_hook with Arc to share it among the new hook and Self to be dropped. let prev_hook = panic::take_hook(); let panic_hook_ref = Arc::new(Self { prev_hook }); diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 44f4dbc7a475be..34a9afce062bc8 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -164,11 +164,9 @@ pub enum DumpLIR { compile_side_exits, /// Dump LIR after {arch}_scratch_split scratch_split, - /// Dump LIR at panic on {arch}_emit - panic, } -/// All compiler stages for --zjit-dump-lir=all. This does NOT include DumpLIR::panic. +/// All compiler stages for --zjit-dump-lir=all. const DUMP_LIR_ALL: &[DumpLIR] = &[ DumpLIR::init, DumpLIR::split, @@ -180,24 +178,14 @@ const DUMP_LIR_ALL: &[DumpLIR] = &[ /// Macro to dump LIR if --zjit-dump-lir is specified macro_rules! asm_dump { ($asm:expr, $target:ident) => { - if crate::options::dump_lir_option!($target) { - println!("LIR {}:\n{}", stringify!($target), $asm); - } - }; -} -pub(crate) use asm_dump; - -/// Macro to check if a particular dump_lir option is enabled -macro_rules! dump_lir_option { - ($target:ident) => { if let Some(crate::options::Options { dump_lir: Some(dump_lirs), .. }) = unsafe { crate::options::OPTIONS.as_ref() } { - dump_lirs.contains(&crate::options::DumpLIR::$target) - } else { - false + if dump_lirs.contains(&crate::options::DumpLIR::$target) { + println!("LIR {}:\n{}", stringify!($target), $asm); + } } }; } -pub(crate) use dump_lir_option; +pub(crate) use asm_dump; /// Macro to get an option value by name macro_rules! get_option { @@ -366,11 +354,10 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { "alloc_regs" => DumpLIR::alloc_regs, "compile_side_exits" => DumpLIR::compile_side_exits, "scratch_split" => DumpLIR::scratch_split, - "panic" => DumpLIR::panic, _ => { let valid_options = DUMP_LIR_ALL.iter().map(|opt| format!("{opt:?}")).collect::>().join(", "); eprintln!("invalid --zjit-dump-lir option: '{filter}'"); - eprintln!("valid --zjit-dump-lir options: all, {}, panic", valid_options); + eprintln!("valid --zjit-dump-lir options: all, {}", valid_options); return None; } }; From f2192cfde1bee833691a9f8768b3cad60de5e874 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 15:26:15 -0700 Subject: [PATCH 0647/2435] ZJIT: Rename compile_side_exits to compile_exits so that it can be easily specified with `--zjit-dump-lir=`. --- zjit/src/backend/arm64/mod.rs | 18 ++++++++-------- zjit/src/backend/lir.rs | 2 +- zjit/src/backend/x86_64/mod.rs | 38 +++++++++++++++++----------------- zjit/src/options.rs | 8 +++---- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 9e94fd0e496643..d762b14c911503 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -687,7 +687,7 @@ impl Assembler { /// Split instructions using scratch registers. To maximize the use of the register pool for /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions - /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this + /// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so this /// splits them and uses scratch registers for it. fn arm64_scratch_split(self) -> Assembler { let mut asm = Assembler::new_with_asm(&self); @@ -706,7 +706,7 @@ impl Assembler { asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: out, shift: Opnd::UImm(63) }); } } - // For compile_side_exits, support splitting simple C arguments here + // For compile_exits, support splitting simple C arguments here Insn::CCall { opnds, .. } if !opnds.is_empty() => { for (i, opnd) in opnds.iter().enumerate() { asm.load_into(C_ARG_OPNDS[i], *opnd); @@ -716,7 +716,7 @@ impl Assembler { } &mut Insn::Lea { opnd, out } => { match (opnd, out) { - // Split here for compile_side_exits + // Split here for compile_exits (Opnd::Mem(_), Opnd::Mem(_)) => { asm.lea_into(SCRATCH0_OPND, opnd); asm.store(out, SCRATCH0_OPND); @@ -728,7 +728,7 @@ impl Assembler { } &mut Insn::IncrCounter { mem, value } => { // Convert Opnd::const_ptr into Opnd::Mem. - // It's split here to support IncrCounter in compile_side_exits. + // It's split here to support IncrCounter in compile_exits. assert!(matches!(mem, Opnd::UImm(_))); asm.load_into(SCRATCH0_OPND, mem); asm.lea_into(SCRATCH0_OPND, Opnd::mem(64, SCRATCH0_OPND, 0)); @@ -872,7 +872,7 @@ impl Assembler { }); }, Target::SideExit { .. } => { - unreachable!("Target::SideExit should have been compiled by compile_side_exits") + unreachable!("Target::SideExit should have been compiled by compile_exits") }, }; } @@ -1349,7 +1349,7 @@ impl Assembler { }); }, Target::SideExit { .. } => { - unreachable!("Target::SideExit should have been compiled by compile_side_exits") + unreachable!("Target::SideExit should have been compiled by compile_exits") }, }; }, @@ -1471,9 +1471,9 @@ impl Assembler { let mut asm = asm.alloc_regs(regs)?; asm_dump!(asm, alloc_regs); - // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. - asm.compile_side_exits(); - asm_dump!(asm, compile_side_exits); + // We put compile_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. + asm.compile_exits(); + asm_dump!(asm, compile_exits); if use_scratch_reg { asm = asm.arm64_scratch_split(); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 5980e9e0224622..005df140b8a613 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1701,7 +1701,7 @@ impl Assembler } /// Compile Target::SideExit and convert it into Target::CodePtr for all instructions - pub fn compile_side_exits(&mut self) { + pub fn compile_exits(&mut self) { fn join_opnds(opnds: &Vec, delimiter: &str) -> String { opnds.iter().map(|opnd| format!("{opnd}")).collect::>().join(delimiter) } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index eb0e70330287b2..14c0df8dd02e04 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -388,7 +388,7 @@ impl Assembler { /// Split instructions using scratch registers. To maximize the use of the register pool /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions - /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so + /// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so /// this splits them and uses scratch registers for it. pub fn x86_scratch_split(self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand @@ -454,7 +454,7 @@ impl Assembler { } asm.push_insn(insn); } - // For compile_side_exits, support splitting simple C arguments here + // For compile_exits, support splitting simple C arguments here Insn::CCall { opnds, .. } if !opnds.is_empty() => { for (i, opnd) in opnds.iter().enumerate() { asm.load_into(C_ARG_OPNDS[i], *opnd); @@ -464,7 +464,7 @@ impl Assembler { } &mut Insn::Lea { opnd, out } => { match (opnd, out) { - // Split here for compile_side_exits + // Split here for compile_exits (Opnd::Mem(_), Opnd::Mem(_)) => { asm.lea_into(SCRATCH0_OPND, opnd); asm.store(out, SCRATCH0_OPND); @@ -481,7 +481,7 @@ impl Assembler { } } // Convert Opnd::const_ptr into Opnd::Mem. This split is done here to give - // a register for compile_side_exits. + // a register for compile_exits. &mut Insn::IncrCounter { mem, value } => { assert!(matches!(mem, Opnd::UImm(_))); asm.load_into(SCRATCH0_OPND, mem); @@ -493,7 +493,7 @@ impl Assembler { asm.mov(dst, src) } } - // Handle various operand combinations for spills on compile_side_exits. + // Handle various operand combinations for spills on compile_exits. &mut Insn::Store { dest, src } => { let Opnd::Mem(Mem { num_bits, .. }) = dest else { panic!("Unexpected Insn::Store destination in x86_scratch_split: {dest:?}"); @@ -801,7 +801,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jmp_ptr(cb, code_ptr), Target::Label(label) => jmp_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -809,7 +809,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => je_ptr(cb, code_ptr), Target::Label(label) => je_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -817,7 +817,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jne_ptr(cb, code_ptr), Target::Label(label) => jne_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -825,7 +825,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jl_ptr(cb, code_ptr), Target::Label(label) => jl_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } }, @@ -833,7 +833,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jg_ptr(cb, code_ptr), Target::Label(label) => jg_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } }, @@ -841,7 +841,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jge_ptr(cb, code_ptr), Target::Label(label) => jge_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } }, @@ -849,7 +849,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jbe_ptr(cb, code_ptr), Target::Label(label) => jbe_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } }, @@ -857,7 +857,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jb_ptr(cb, code_ptr), Target::Label(label) => jb_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } }, @@ -865,7 +865,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jz_ptr(cb, code_ptr), Target::Label(label) => jz_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -873,7 +873,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jnz_ptr(cb, code_ptr), Target::Label(label) => jnz_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -882,7 +882,7 @@ impl Assembler { match *target { Target::CodePtr(code_ptr) => jo_ptr(cb, code_ptr), Target::Label(label) => jo_label(cb, label), - Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"), + Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_exits"), } } @@ -969,9 +969,9 @@ impl Assembler { let mut asm = asm.alloc_regs(regs)?; asm_dump!(asm, alloc_regs); - // We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. - asm.compile_side_exits(); - asm_dump!(asm, compile_side_exits); + // We put compile_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits. + asm.compile_exits(); + asm_dump!(asm, compile_exits); if use_scratch_regs { asm = asm.x86_scratch_split(); diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 34a9afce062bc8..f6471b5461497f 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -160,8 +160,8 @@ pub enum DumpLIR { split, /// Dump LIR after alloc_regs alloc_regs, - /// Dump LIR after compile_side_exits - compile_side_exits, + /// Dump LIR after compile_exits + compile_exits, /// Dump LIR after {arch}_scratch_split scratch_split, } @@ -171,7 +171,7 @@ const DUMP_LIR_ALL: &[DumpLIR] = &[ DumpLIR::init, DumpLIR::split, DumpLIR::alloc_regs, - DumpLIR::compile_side_exits, + DumpLIR::compile_exits, DumpLIR::scratch_split, ]; @@ -352,7 +352,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { "init" => DumpLIR::init, "split" => DumpLIR::split, "alloc_regs" => DumpLIR::alloc_regs, - "compile_side_exits" => DumpLIR::compile_side_exits, + "compile_exits" => DumpLIR::compile_exits, "scratch_split" => DumpLIR::scratch_split, _ => { let valid_options = DUMP_LIR_ALL.iter().map(|opt| format!("{opt:?}")).collect::>().join(", "); From b8c82a99a03722c4641d5fe41ebd393e0e4806c2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 28 Oct 2025 19:46:36 -0400 Subject: [PATCH 0648/2435] Fix memory leak in String#encode when fallback too big The following script leaks memory: 10.times do 100_000.times do "\ufffd".encode(Encoding::US_ASCII, fallback: proc { "\uffee" }) rescue end puts `ps -o rss= -p #{$$}` end Before: 451196 889596 1328252 1766524 2204668 2643068 3081724 3520124 3958524 4396796 After: 12800 13056 13184 13312 13312 13312 13312 13312 13312 13312 --- test/ruby/test_string.rb | 30 ++++++++++++++++++++++++++++++ transcode.c | 1 + 2 files changed, 31 insertions(+) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index f6adef6efc16da..adbfe328cccfbf 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3777,6 +3777,36 @@ class MyError < StandardError; end end end + def test_encode_fallback_too_big_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { "\\uffee" } + RUBY + "proc" => <<~RUBY, + fallback = proc { "\\uffee" } + RUBY + "method" => <<~RUBY, + def my_method(_str) = "\\uffee" + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = "\\uffee" + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue ArgumentError + end + RUBY + end + end + private def assert_bytesplice_result(expected, s, *args) diff --git a/transcode.c b/transcode.c index 09ecb1c6513171..20b92b66f7ae21 100644 --- a/transcode.c +++ b/transcode.c @@ -2432,6 +2432,7 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep), RSTRING_LEN(rep), rb_enc_name(rb_enc_get(rep))); if ((int)ret == -1) { + rb_econv_close(ec); rb_raise(rb_eArgError, "too big fallback string"); } goto resume; From 750c75096d0ddff76b6ed65059e4fedf9bd22675 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 30 Oct 2025 00:35:35 +0000 Subject: [PATCH 0649/2435] [DOC] ZJIT: Add documentation about native stack and Ruby's VM stack (#14993) ZJIT: Add documentation about native stack and Ruby's VM stack --- doc/zjit.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/zjit.md b/doc/zjit.md index a45128adbdd438..0c50bd44120639 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -189,6 +189,33 @@ To run code snippets with ZJIT: You can also try https://www.rubyexplorer.xyz/ to view Ruby YARV disasm output with syntax highlighting in a way that can be easily shared with other team members. +## Understanding Ruby Stacks + +Ruby execution involves three distinct stacks and understanding them will help you understand ZJIT's implementation: + +### 1. Native Stack + +- **Purpose**: Return addresses and saved registers. ZJIT also uses it for some C functions' argument arrays +- **Management**: OS-managed, one per native thread +- **Growth**: Downward from high addresses +- **Constants**: `NATIVE_STACK_PTR`, `NATIVE_BASE_PTR` + +### 2. Ruby VM Stack + +The Ruby VM uses a single contiguous memory region (`ec->vm_stack`) containing two sub-stacks that grow toward each other. When they meet, stack overflow occurs. + +**Control Frame Stack:** + +- **Stores**: Frame metadata (`rb_control_frame_t` structures) +- **Growth**: Downward from `vm_stack + size` (high addresses) +- **Constants**: `CFP` + +**Value Stack:** + +- **Stores**: YARV bytecode operands (self, arguments, locals, temporaries) +- **Growth**: Upward from `vm_stack` (low addresses) +- **Constants**: `SP` + ## ZJIT Glossary This glossary contains terms that are helpful for understanding ZJIT. From 78e4a36fb17853e74978addedf663851a124bff5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 29 Oct 2025 12:37:07 -0700 Subject: [PATCH 0650/2435] [ruby/error_highlight] Support cases where there are multiple missing / wrong kwargs This commit fixes the case when there are multiple missing or incorrect keywords provided to a method. Without this fix, ErrorHighlight itself will raise an exception https://github.com/ruby/error_highlight/commit/8bde92b36e --- lib/error_highlight/core_ext.rb | 2 +- test/error_highlight/test_error_highlight.rb | 44 +++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index d4f91e118567bc..e6cacbaf9e832a 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -3,7 +3,7 @@ module ErrorHighlight module CoreExt private def generate_snippet - if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword|unknown keyword|no keywords accepted)\b/ + if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ locs = self.backtrace_locations return "" if locs.size < 2 callee_loc, caller_loc = locs diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index 208dea8ea97128..43e232071f9371 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -44,7 +44,7 @@ def preprocess(msg) def assert_error_message(klass, expected_msg, &blk) omit unless klass < ErrorHighlight::CoreExt err = assert_raise(klass, &blk) - unless klass == ArgumentError && err.message =~ /\A(?:wrong number of arguments|missing keyword|unknown keyword|no keywords accepted)\b/ + unless klass == ArgumentError && err.message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ spot = ErrorHighlight.spot(err) if spot assert_kind_of(Integer, spot[:first_lineno]) @@ -1502,6 +1502,27 @@ def test_missing_keyword end end + def test_missing_keywords # multiple missing keywords + lineno = __LINE__ + assert_error_message(ArgumentError, <<~END) do +missing keywords: :kw2, :kw3 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 16 } + | keyword_test(kw1: 1) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + #{ + MethodDefLocationSupported ? + "| def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^" : + "(cannot highlight method definition; try Ruby 3.5 or later)" + } + END + + keyword_test(kw1: 1) + end + end + def test_unknown_keyword lineno = __LINE__ assert_error_message(ArgumentError, <<~END) do @@ -1523,6 +1544,27 @@ def test_unknown_keyword end end + def test_unknown_keywords + lineno = __LINE__ + assert_error_message(ArgumentError, <<~END) do +unknown keywords: :kw4, :kw5 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 16 } + | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + #{ + MethodDefLocationSupported ? + "| def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^" : + "(cannot highlight method definition; try Ruby 3.5 or later)" + } + END + + keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) + end + end + WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO = __LINE__ + 1 def wrong_number_of_arguments_test2( long_argument_name_x, From 02c9ffd16b3324449cf8e6612a9896148a1be435 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 17:28:01 -0700 Subject: [PATCH 0651/2435] ZJIT: Enable comments for --zjit-debug on dev builds I wanted to see comments on test_zjit.rb failures. --- zjit/src/backend/lir.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 005df140b8a613..cad369357f8117 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2255,7 +2255,12 @@ impl Assembler { /// when not dumping disassembly. macro_rules! asm_comment { ($asm:expr, $($fmt:tt)*) => { - if $crate::options::get_option!(dump_disasm) || $crate::options::get_option!(dump_lir).is_some() { + // If --zjit-dump-disasm or --zjit-dump-lir is given, enrich them with comments. + // Also allow --zjit-debug on dev builds to enable comments since dev builds dump LIR on panic. + let enable_comment = $crate::options::get_option!(dump_disasm) || + $crate::options::get_option!(dump_lir).is_some() || + (cfg!(debug_assertions) && $crate::options::get_option!(debug)); + if enable_comment { $asm.push_insn(crate::backend::lir::Insn::Comment(format!($($fmt)*))); } }; From 80e2b06d39d0986e8c88cba8946447c1e380aa85 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 Oct 2025 17:42:37 -0700 Subject: [PATCH 0652/2435] ZJIT: Limit the default size of LIR dump For test_zjit.rb output, it was too long for a single test to print thousands of lines. --- zjit/src/backend/lir.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index cad369357f8117..a90e80bf4187b1 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2288,6 +2288,10 @@ pub struct AssemblerPanicHook { } impl AssemblerPanicHook { + /// Maximum number of lines [`Self::dump_asm`] is allowed to dump by default. + /// When --zjit-dump-lir is given, this limit is ignored. + const MAX_DUMP_LINES: usize = 40; + /// Install a panic hook to dump Assembler with insn_idx on dev builds. /// This returns shared references to the previous hook and insn_idx. /// It takes insn_idx as an argument so that you can manually use it @@ -2325,8 +2329,19 @@ impl AssemblerPanicHook { /// Dump Assembler, highlighting the insn_idx line fn dump_asm(asm: &Assembler, insn_idx: usize) { + let lir_string = lir_string(asm); + let lines: Vec<&str> = lir_string.split('\n').collect(); + + // By default, dump only MAX_DUMP_LINES lines. + // Ignore it if --zjit-dump-lir is given. + let (min_idx, max_idx) = if get_option!(dump_lir).is_some() { + (0, lines.len()) + } else { + (insn_idx.saturating_sub(Self::MAX_DUMP_LINES / 2), insn_idx.saturating_add(Self::MAX_DUMP_LINES / 2)) + }; + println!("Failed to compile LIR at insn_idx={insn_idx}:"); - for (idx, line) in lir_string(asm).split('\n').enumerate() { + for (idx, line) in lines.iter().enumerate().filter(|(idx, _)| (min_idx..=max_idx).contains(idx)) { if idx == insn_idx && line.starts_with(" ") { println!("{BOLD_BEGIN}=>{}{BOLD_END}", &line[" ".len()..]); } else { From 15f2dcceb4787c5738dde48f580019c3765ce1b8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 30 Oct 2025 09:36:11 +0900 Subject: [PATCH 0653/2435] [ruby/resolv] Fallback to powershell implementation under the bundle environment [Bug #21645] win32-registory can't load fiddle if Gemfile didn't have that dependency. https://github.com/ruby/resolv/commit/1319183a4b --- ext/win32/lib/win32/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index 226de7cba36769..ef74a890bbaeea 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -58,7 +58,7 @@ def read_s(key) end end using SZ - rescue LoadError + rescue LoadError, Gem::LoadError require "open3" end From 6b9b048a926fcbfcdf1145769af98e46f2c41f05 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 30 Oct 2025 06:53:30 +0000 Subject: [PATCH 0654/2435] Update bundled gems list as of 2025-10-30 --- NEWS.md | 3 ++- gems/bundled_gems | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0b622064720f22..a83f31ed0bf24f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -210,9 +210,10 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.26.0 -* rake 13.3.0 +* rake 13.3.1 * test-unit 3.7.0 * rexml 3.4.4 +* net-ftp 0.3.9 * net-imap 0.5.12 * net-smtp 0.5.1 * matrix 0.4.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index 64c8b2df5e196f..19ed86f4534994 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -8,11 +8,11 @@ minitest 5.26.0 https://github.com/minitest/minitest power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a -rake 13.3.0 https://github.com/ruby/rake +rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss -net-ftp 0.3.8 https://github.com/ruby/net-ftp +net-ftp 0.3.9 https://github.com/ruby/net-ftp net-imap 0.5.12 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp From 8e4dc1099a8681a70f547d0349ab8a5ff32d4da1 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 23 Oct 2025 14:57:19 +0900 Subject: [PATCH 0655/2435] Ractor's name should be shareable --- ractor.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ractor.c b/ractor.c index ed0b023b1feec1..8d324b3c3f24b9 100644 --- a/ractor.c +++ b/ractor.c @@ -497,8 +497,9 @@ ractor_init(rb_ractor_t *r, VALUE name, VALUE loc) rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)", rb_enc_name(enc)); } - name = rb_str_new_frozen(name); + name = RB_OBJ_SET_SHAREABLE(rb_str_new_frozen(name)); } + if (!SPECIAL_CONST_P(loc)) RB_OBJ_SET_SHAREABLE(loc); r->loc = loc; r->name = name; From b09f782fee51c25fd0531ab2833d303cf0c14bbc Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 23 Oct 2025 14:57:50 +0900 Subject: [PATCH 0656/2435] Ractor's queue can contain unshareable objects --- ractor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ractor.c b/ractor.c index 8d324b3c3f24b9..2820a848a26465 100644 --- a/ractor.c +++ b/ractor.c @@ -209,9 +209,6 @@ ractor_mark(void *ptr) rb_ractor_t *r = (rb_ractor_t *)ptr; bool checking_shareable = rb_gc_checking_shareable(); - // mark received messages - ractor_sync_mark(r); - rb_gc_mark(r->loc); rb_gc_mark(r->name); @@ -223,6 +220,9 @@ ractor_mark(void *ptr) rb_gc_mark(r->verbose); rb_gc_mark(r->debug); + // mark received messages + ractor_sync_mark(r); + rb_hook_list_mark(&r->pub.hooks); if (r->threads.cnt > 0) { From 860bad854f9b9dcc565f20387eaf49fff6110768 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 23 Oct 2025 17:18:13 +0900 Subject: [PATCH 0657/2435] specific traces can be unshareable --- vm_trace.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vm_trace.c b/vm_trace.c index 1069d36dd08762..58424008fd4f0e 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -74,11 +74,15 @@ rb_hook_list_mark(rb_hook_list_t *hooks) void rb_hook_list_mark_and_move(rb_hook_list_t *hooks) { - rb_event_hook_t *hook = hooks->hooks; + if (!rb_gc_checking_shareable()) { + // hooks can be unshareable - while (hook) { - rb_gc_mark_and_move(&hook->data); - hook = hook->next; + rb_event_hook_t *hook = hooks->hooks; + + while (hook) { + rb_gc_mark_and_move(&hook->data); + hook = hook->next; + } } } From 09e9247edc35b910bf8ca49c05151709929b79b4 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 24 Oct 2025 16:58:41 +0900 Subject: [PATCH 0658/2435] MatchData may refer a String --- ractor.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ractor.c b/ractor.c index 2820a848a26465..29eae005621a3b 100644 --- a/ractor.c +++ b/ractor.c @@ -3,9 +3,9 @@ #include "ruby/ruby.h" #include "ruby/thread.h" #include "ruby/ractor.h" +#include "ruby/re.h" #include "ruby/thread_native.h" #include "vm_core.h" -#include "eval_intern.h" #include "vm_sync.h" #include "ractor_core.h" #include "internal/complex.h" @@ -1276,7 +1276,6 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) case T_REGEXP: case T_FILE: case T_SYMBOL: - case T_MATCH: break; case T_OBJECT: @@ -1318,6 +1317,10 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) } break; + case T_MATCH: + if (obj_traverse_i(RMATCH(obj)->str, data)) return 1; + break; + case T_RATIONAL: if (obj_traverse_i(RRATIONAL(obj)->num, data)) return 1; if (obj_traverse_i(RRATIONAL(obj)->den, data)) return 1; @@ -1769,7 +1772,6 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) case T_REGEXP: case T_FILE: case T_SYMBOL: - case T_MATCH: break; case T_STRING: rb_str_make_independent(obj); @@ -1854,6 +1856,10 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) } break; + case T_MATCH: + CHECK_AND_REPLACE(obj, RMATCH(obj)->str); + break; + case T_RATIONAL: CHECK_AND_REPLACE(obj, RRATIONAL(obj)->num); CHECK_AND_REPLACE(obj, RRATIONAL(obj)->den); From b9188901c07649c3af3a5f925ec0dead444a4134 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 27 Oct 2025 18:14:28 +0900 Subject: [PATCH 0659/2435] allow Ractor::Port shareable --- ractor_sync.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ractor_sync.c b/ractor_sync.c index c208ee6b2d9792..57ae13e88de50f 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -49,7 +49,7 @@ static const rb_data_type_t ractor_port_data_type = { ractor_port_memsize, NULL, // update }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; static st_data_t @@ -72,6 +72,7 @@ ractor_port_alloc(VALUE klass) { struct ractor_port *rp; VALUE rpv = TypedData_Make_Struct(klass, struct ractor_port, &ractor_port_data_type, rp); + rb_obj_freeze(rpv); return rpv; } From a003903e62446f45c89655360ea27661062040dd Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 30 Oct 2025 15:31:50 +0900 Subject: [PATCH 0660/2435] fix cross ractor require `cross_ractor_require` is sharable object but it refers to unsharable objects. To fix it, make the process simple. --- ractor.c | 99 ++++++++++++++++++++++---------------------------------- 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/ractor.c b/ractor.c index 29eae005621a3b..70594443d6ffbf 100644 --- a/ractor.c +++ b/ractor.c @@ -2352,8 +2352,7 @@ ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_la struct cross_ractor_require { VALUE port; - VALUE result; - VALUE exception; + bool raised; union { struct { @@ -2371,8 +2370,6 @@ struct cross_ractor_require { RUBY_REFERENCES(cross_ractor_require_refs) = { RUBY_REF_EDGE(struct cross_ractor_require, port), - RUBY_REF_EDGE(struct cross_ractor_require, result), - RUBY_REF_EDGE(struct cross_ractor_require, exception), RUBY_REF_EDGE(struct cross_ractor_require, as.require.feature), RUBY_REF_END }; @@ -2393,20 +2390,18 @@ require_body(VALUE crr_obj) { struct cross_ractor_require *crr; TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); + VALUE feature = crr->as.require.feature; ID require; CONST_ID(require, "require"); if (crr->silent) { int rb_require_internal_silent(VALUE fname); - - RB_OBJ_WRITE(crr_obj, &crr->result, INT2NUM(rb_require_internal_silent(crr->as.require.feature))); + return INT2NUM(rb_require_internal_silent(feature)); } else { - RB_OBJ_WRITE(crr_obj, &crr->result, rb_funcallv(Qnil, require, 1, &crr->as.require.feature)); + return rb_funcallv(Qnil, require, 1, &feature); } - - return Qnil; } static VALUE @@ -2414,38 +2409,27 @@ require_rescue(VALUE crr_obj, VALUE errinfo) { struct cross_ractor_require *crr; TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); - - RB_OBJ_WRITE(crr_obj, &crr->exception, errinfo); - - return Qundef; + crr->raised = true; + return errinfo; } static VALUE -require_result_copy_body(VALUE crr_obj) +require_result_send_body(VALUE ary) { - struct cross_ractor_require *crr; - TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); + VALUE port = RARRAY_AREF(ary, 0); + VALUE results = RARRAY_AREF(ary, 1); - if (crr->exception != Qundef) { - VM_ASSERT(crr->result == Qundef); - RB_OBJ_WRITE(crr_obj, &crr->exception, ractor_copy(crr->exception)); - } - else{ - VM_ASSERT(crr->result != Qundef); - RB_OBJ_WRITE(crr_obj, &crr->result, ractor_copy(crr->result)); - } + rb_execution_context_t *ec = GET_EC(); + ractor_port_send(ec, port, results, Qfalse); return Qnil; } static VALUE -require_result_copy_resuce(VALUE crr_obj, VALUE errinfo) +require_result_send_resuce(VALUE port, VALUE errinfo) { - struct cross_ractor_require *crr; - TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); - - RB_OBJ_WRITE(crr_obj, &crr->exception, errinfo); - + // TODO: need rescue? + ractor_port_send(GET_EC(), port, errinfo, Qfalse); return Qnil; } @@ -2456,25 +2440,26 @@ ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE)) TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); const bool silent = crr->silent; + VALUE debug, errinfo; if (silent) { debug = ruby_debug; errinfo = rb_errinfo(); } - // catch any error - rb_rescue2(func, crr_obj, - require_rescue, crr_obj, rb_eException, 0); + // get normal result or raised exception (with crr->raised == true) + VALUE result = rb_rescue2(func, crr_obj, require_rescue, crr_obj, rb_eException, 0); if (silent) { ruby_debug = debug; rb_set_errinfo(errinfo); } - rb_rescue2(require_result_copy_body, crr_obj, - require_result_copy_resuce, crr_obj, rb_eException, 0); + rb_rescue2(require_result_send_body, + // [port, [result, raised]] + rb_ary_new_from_args(2, crr->port, rb_ary_new_from_args(2, result, crr->raised ? Qtrue : Qfalse)), + require_result_send_resuce, rb_eException, crr->port); - ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse); RB_GC_GUARD(crr_obj); return Qnil; } @@ -2493,13 +2478,12 @@ rb_ractor_require(VALUE feature, bool silent) struct cross_ractor_require *crr; VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr); - FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE); + RB_OBJ_SET_SHAREABLE(crr_obj); // TODO: internal data? // Convert feature to proper file path and make it shareable as fstring RB_OBJ_WRITE(crr_obj, &crr->as.require.feature, rb_fstring(FilePathValue(feature))); - RB_OBJ_WRITE(crr_obj, &crr->port, ractor_port_new(GET_RACTOR())); - crr->result = Qundef; - crr->exception = Qundef; + RB_OBJ_WRITE(crr_obj, &crr->port, rb_ractor_make_shareable(ractor_port_new(GET_RACTOR()))); + crr->raised = false; crr->silent = silent; rb_execution_context_t *ec = GET_EC(); @@ -2507,20 +2491,17 @@ rb_ractor_require(VALUE feature, bool silent) rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data); // wait for require done - ractor_port_receive(ec, crr->port); + VALUE results = ractor_port_receive(ec, crr->port); ractor_port_close(ec, crr->port); - VALUE exc = crr->exception; - VALUE result = crr->result; + VALUE exc = rb_ary_pop(results); + VALUE result = rb_ary_pop(results); RB_GC_GUARD(crr_obj); - if (exc != Qundef) { - ractor_reset_belonging(exc); - rb_exc_raise(exc); + if (RTEST(exc)) { + rb_exc_raise(result); } else { - RUBY_ASSERT(result != Qundef); - ractor_reset_belonging(result); return result; } } @@ -2536,10 +2517,7 @@ autoload_load_body(VALUE crr_obj) { struct cross_ractor_require *crr; TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); - - RB_OBJ_WRITE(crr_obj, &crr->result, rb_autoload_load(crr->as.autoload.module, crr->as.autoload.name)); - - return Qnil; + return rb_autoload_load(crr->as.autoload.module, crr->as.autoload.name); } static VALUE @@ -2553,27 +2531,26 @@ rb_ractor_autoload_load(VALUE module, ID name) { struct cross_ractor_require *crr; VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr); - FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE); + RB_OBJ_SET_SHAREABLE(crr_obj); // TODO: internal data? + RB_OBJ_WRITE(crr_obj, &crr->as.autoload.module, module); RB_OBJ_WRITE(crr_obj, &crr->as.autoload.name, name); - RB_OBJ_WRITE(crr_obj, &crr->port, ractor_port_new(GET_RACTOR())); - crr->result = Qundef; - crr->exception = Qundef; + RB_OBJ_WRITE(crr_obj, &crr->port, rb_ractor_make_shareable(ractor_port_new(GET_RACTOR()))); rb_execution_context_t *ec = GET_EC(); rb_ractor_t *main_r = GET_VM()->ractor.main_ractor; rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data); // wait for require done - ractor_port_receive(ec, crr->port); + VALUE results = ractor_port_receive(ec, crr->port); ractor_port_close(ec, crr->port); - VALUE exc = crr->exception; - VALUE result = crr->result; + VALUE exc = rb_ary_pop(results); + VALUE result = rb_ary_pop(results); RB_GC_GUARD(crr_obj); - if (exc != Qundef) { - rb_exc_raise(exc); + if (RTEST(exc)) { + rb_exc_raise(result); } else { return result; From ef558ceaefb91a55e97a8b5766e0643d0dcd078e Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 30 Oct 2025 16:21:33 +0900 Subject: [PATCH 0661/2435] fix ibf and coverage sharable issue --- iseq.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/iseq.c b/iseq.c index aabeb83b3c349e..df849d05215a6d 100644 --- a/iseq.c +++ b/iseq.c @@ -356,8 +356,6 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) rb_iseq_mark_and_move_each_body_value(iseq, reference_updating ? ISEQ_ORIGINAL_ISEQ(iseq) : NULL); - rb_gc_mark_and_move(&body->variable.coverage); - rb_gc_mark_and_move(&body->variable.pc2branchindex); rb_gc_mark_and_move(&body->variable.script_lines); rb_gc_mark_and_move(&body->location.label); rb_gc_mark_and_move(&body->location.base_label); @@ -422,10 +420,18 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) #endif } } + + // TODO: ractor aware coverage + if (!rb_gc_checking_shareable()) { + rb_gc_mark_and_move(&body->variable.coverage); + rb_gc_mark_and_move(&body->variable.pc2branchindex); + } } if (FL_TEST_RAW((VALUE)iseq, ISEQ_NOT_LOADED_YET)) { - rb_gc_mark_and_move(&iseq->aux.loader.obj); + if (!rb_gc_checking_shareable()) { + rb_gc_mark_and_move(&iseq->aux.loader.obj); + } } else if (FL_TEST_RAW((VALUE)iseq, ISEQ_USE_COMPILE_DATA)) { if (!rb_gc_checking_shareable()) { From 3f230c7eb4cb23b2c3fa47704d92666106b2ba79 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 30 Oct 2025 17:31:47 +0900 Subject: [PATCH 0662/2435] add deps --- depend | 3 +++ 1 file changed, 3 insertions(+) diff --git a/depend b/depend index b56378ebce14c8..c908bb6b0102b4 100644 --- a/depend +++ b/depend @@ -12625,6 +12625,7 @@ ractor.$(OBJEXT): {$(VPATH)}internal/core/rclass.h ractor.$(OBJEXT): {$(VPATH)}internal/core/rdata.h ractor.$(OBJEXT): {$(VPATH)}internal/core/rfile.h ractor.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +ractor.$(OBJEXT): {$(VPATH)}internal/core/rmatch.h ractor.$(OBJEXT): {$(VPATH)}internal/core/robject.h ractor.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h ractor.$(OBJEXT): {$(VPATH)}internal/core/rstring.h @@ -12722,6 +12723,8 @@ ractor.$(OBJEXT): {$(VPATH)}ractor.h ractor.$(OBJEXT): {$(VPATH)}ractor.rbinc ractor.$(OBJEXT): {$(VPATH)}ractor_core.h ractor.$(OBJEXT): {$(VPATH)}ractor_sync.c +ractor.$(OBJEXT): {$(VPATH)}re.h +ractor.$(OBJEXT): {$(VPATH)}regex.h ractor.$(OBJEXT): {$(VPATH)}ruby_assert.h ractor.$(OBJEXT): {$(VPATH)}ruby_atomic.h ractor.$(OBJEXT): {$(VPATH)}rubyparser.h From d1d85bb4b799693cead87afaf49ecfb4526d26a2 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 9 Sep 2025 12:10:22 +0300 Subject: [PATCH 0663/2435] [ruby/resolv] Fix invalid "Broken registry" warning for UseDomainNameDevolution This value is dword, not a string. Amends https://github.com/ruby/resolv/commit/720e25034042. https://github.com/ruby/resolv/commit/bf00ed8585 --- ext/win32/lib/win32/resolv.rb | 18 ++++-- test/resolv/test_win32_config.rb | 104 +++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 test/resolv/test_win32_config.rb diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index ef74a890bbaeea..43ec10cf98acd3 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -83,7 +83,7 @@ def get_info unless nvdom.empty? search = [ nvdom ] - udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution').to_i + udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution', dword: true) if udmnd != 0 if /^\w+\./ =~ nvdom devo = $' @@ -126,17 +126,23 @@ def get_info [ search.uniq, nameserver.uniq ] end - def get_item_property(path, name, expand: false) + def get_item_property(path, name, expand: false, dword: false) if defined?(Win32::Registry) - Registry::HKEY_LOCAL_MACHINE.open(path) do |reg| - expand ? reg.read_s_expand(name) : reg.read_s(name) + begin + Registry::HKEY_LOCAL_MACHINE.open(path) do |reg| + if dword + reg.read_i(name) + else + expand ? reg.read_s_expand(name) : reg.read_s(name) + end + end rescue Registry::Error - "" + dword ? 0 : "" end else cmd = "Get-ItemProperty -Path 'HKLM:\\#{path}' -Name '#{name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty '#{name}'" output, _ = Open3.capture2('powershell', '-Command', cmd) - output.strip + dword ? output.strip.to_i : output.strip end end end diff --git a/test/resolv/test_win32_config.rb b/test/resolv/test_win32_config.rb new file mode 100644 index 00000000000000..f44d19544ac5b4 --- /dev/null +++ b/test/resolv/test_win32_config.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'test/unit' +require 'resolv' + +class TestWin32Config < Test::Unit::TestCase + def setup + omit 'Win32::Resolv tests only run on Windows' unless RUBY_PLATFORM =~ /mswin|mingw|cygwin/ + end + + def test_get_item_property_string + # Test reading a string registry value + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'DataBasePath') + + # Should return a string (empty or with a path) + assert_instance_of String, result + end + + def test_get_item_property_with_expand + # Test reading an expandable string registry value + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'DataBasePath', + expand: true) + + # Should return a string with environment variables expanded + assert_instance_of String, result + end + + def test_get_item_property_dword + # Test reading a DWORD registry value + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'UseDomainNameDevolution', + dword: true) + + # Should return an integer (0 or 1 typically) + assert_kind_of Integer, result + end + + def test_get_item_property_nonexistent_key + # Test reading a non-existent registry key + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'NonExistentKeyThatShouldNotExist') + + # Should return empty string for non-existent string values + assert_equal '', result + end + + def test_get_item_property_nonexistent_key_dword + # Test reading a non-existent registry key as DWORD + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'NonExistentKeyThatShouldNotExist', + dword: true) + + # Should return 0 for non-existent DWORD values + assert_equal 0, result + end + + def test_get_item_property_search_list + # Test reading SearchList which may exist in the registry + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'SearchList') + + # Should return a string (may be empty if not configured) + assert_instance_of String, result + end + + def test_get_item_property_nv_domain + # Test reading NV Domain which may exist in the registry + result = Win32::Resolv.send(:get_item_property, + Win32::Resolv::TCPIP_NT, + 'NV Domain') + + # Should return a string (may be empty if not configured) + assert_instance_of String, result + end + + def test_get_item_property_with_invalid_path + # Test with an invalid registry path + result = Win32::Resolv.send(:get_item_property, + 'SYSTEM\NonExistent\Path', + 'SomeKey') + + # Should return empty string for invalid path + assert_equal '', result + end + + def test_get_item_property_with_invalid_path_dword + # Test with an invalid registry path as DWORD + result = Win32::Resolv.send(:get_item_property, + 'SYSTEM\NonExistent\Path', + 'SomeKey', + dword: true) + + # Should return 0 for invalid path + assert_equal 0, result + end +end From 4b1279b6e9daf080b0ca4e97b6a5ee2dfa2d7077 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 10:52:20 +0900 Subject: [PATCH 0664/2435] Take `MAJOR` and `MINOR` from `$(NEW)` if given --- defs/gmake.mk | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index 1173a147a2a118..c7eaca91a54992 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -539,13 +539,19 @@ ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) matz: up $(eval OLD := $(MAJOR).$(MINOR).0) +ifdef NEW + $(eval MAJOR := $(word 1,$(subst ., ,$(NEW)))) + $(eval MINOR := $(word 2,$(subst ., ,$(NEW)))) +else $(eval MINOR := $(shell expr $(MINOR) + 1)) - $(eval NEW := $(MAJOR).$(MINOR).0) +endif + $(eval override NEW := $(MAJOR).$(MINOR).0) $(eval message := Development of $(NEW) started.) $(eval files := include/ruby/version.h include/ruby/internal/abi.h) $(GIT_IN_SRC) mv -f NEWS.md doc/NEWS/NEWS-$(OLD).md $(GIT_IN_SRC) commit -m "[DOC] Flush NEWS.md" sed -i~ \ + -e "s/^\(#define RUBY_API_VERSION_MAJOR\) .*/\1 $(MAJOR)/" \ -e "s/^\(#define RUBY_API_VERSION_MINOR\) .*/\1 $(MINOR)/" \ -e "s/^\(#define RUBY_ABI_VERSION\) .*/\1 0/" \ $(files:%=$(srcdir)/%) From 317f102cbb6d10cad3a5a7bb27f09bd39ec943c6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 30 Oct 2025 11:33:26 +0100 Subject: [PATCH 0665/2435] [ruby/json] Fix GeneratorError messages to be UTF-8 encoded https://github.com/ruby/json/commit/965ba6c5d4 --- ext/json/generator/generator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index f3f27b29d58ecd..d04c8a90797b7d 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -90,6 +90,7 @@ RBIMPL_ATTR_NORETURN() #endif static void raise_generator_error_str(VALUE invalid_object, VALUE str) { + rb_enc_associate_index(str, utf8_encindex); VALUE exc = rb_exc_new_str(eGeneratorError, str); rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object); rb_exc_raise(exc); From 3ca4321680faea98b30fb41d5b18c21b74e1261c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20=C5=A0im=C3=A1nek?= Date: Sun, 26 Oct 2025 21:09:06 +0100 Subject: [PATCH 0666/2435] [ruby/json] Add ryu float parser. https://github.com/ruby/json/commit/9c4db31908 Co-Authored-By: Jean Boussier --- ext/json/parser/parser.c | 141 +++-- ext/json/vendor/ryu.h | 819 ++++++++++++++++++++++++++++ test/json/json_ryu_fallback_test.rb | 169 ++++++ 3 files changed, 1077 insertions(+), 52 deletions(-) create mode 100644 ext/json/vendor/ryu.h create mode 100644 test/json/json_ryu_fallback_test.rb diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 538cddb6d66aec..ca8f501539c322 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1,5 +1,6 @@ #include "ruby.h" #include "ruby/encoding.h" +#include "../vendor/ryu.h" /* shims */ /* This is the fallback definition from Ruby 3.4 */ @@ -20,6 +21,16 @@ typedef unsigned char _Bool; #endif #endif +#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG +# define INT64T2NUM(x) LL2NUM(x) +# define UINT64T2NUM(x) ULL2NUM(x) +#elif SIZEOF_UINT64_T == SIZEOF_LONG +# define INT64T2NUM(x) LONG2NUM(x) +# define UINT64T2NUM(x) ULONG2NUM(x) +#else +# error No uint64_t conversion +#endif + #include "../simd/simd.h" #ifndef RB_UNLIKELY @@ -755,26 +766,6 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c } #define MAX_FAST_INTEGER_SIZE 18 -static inline VALUE fast_decode_integer(const char *p, const char *pe) -{ - bool negative = false; - if (*p == '-') { - negative = true; - p++; - } - - long long memo = 0; - while (p < pe) { - memo *= 10; - memo += *p - '0'; - p++; - } - - if (negative) { - memo = -memo; - } - return LL2NUM(memo); -} static VALUE json_decode_large_integer(const char *start, long len) { @@ -788,17 +779,27 @@ static VALUE json_decode_large_integer(const char *start, long len) } static inline VALUE -json_decode_integer(const char *start, const char *end) +json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end) { - long len = end - start; - if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { - return fast_decode_integer(start, end); + if (RB_LIKELY(mantissa_digits < MAX_FAST_INTEGER_SIZE)) { + if (negative) { + return INT64T2NUM(-((int64_t)mantissa)); + } + return UINT64T2NUM(mantissa); } - return json_decode_large_integer(start, len); + + return json_decode_large_integer(start, end - start); } static VALUE json_decode_large_float(const char *start, long len) { + if (RB_LIKELY(len < 64)) { + char buffer[64]; + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); + } + VALUE buffer_v; char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); MEMCPY(buffer, start, char, len); @@ -808,21 +809,24 @@ static VALUE json_decode_large_float(const char *start, long len) return number; } -static VALUE json_decode_float(JSON_ParserConfig *config, const char *start, const char *end) +/* Ruby JSON optimized float decoder using vendored Ryu algorithm + * Accepts pre-extracted mantissa and exponent from first-pass validation + */ +static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int32_t exponent, bool negative, + const char *start, const char *end) { - long len = end - start; - if (RB_UNLIKELY(config->decimal_class)) { - VALUE text = rb_str_new(start, len); + VALUE text = rb_str_new(start, end - start); return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text); - } else if (RB_LIKELY(len < 64)) { - char buffer[64]; - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - } else { - return json_decode_large_float(start, len); } + + // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case) + // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) + if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) { + return json_decode_large_float(start, end - start); + } + + return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative)); } static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) @@ -1082,57 +1086,90 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { bool integer = true; + // Variables for Ryu optimization - extract digits during parsing + uint64_t mantissa = 0; + int mantissa_digits = 0; + int32_t exponent = 0; + bool negative = false; + int decimal_point_pos = -1; + // /\A-?(0|[1-9]\d*)(\.\d+)?([Ee][-+]?\d+)?/ const char *start = state->cursor; - state->cursor++; - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { + // Handle optional negative sign + if (*state->cursor == '-') { + negative = true; state->cursor++; + if (state->cursor >= state->end || !rb_isdigit(*state->cursor)) { + raise_parse_error_at("invalid number: %s", state, start); + } } - long integer_length = state->cursor - start; + // Parse integer part and extract mantissa digits + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + mantissa = mantissa * 10 + (*state->cursor - '0'); + mantissa_digits++; + state->cursor++; + } - if (RB_UNLIKELY(start[0] == '0' && integer_length > 1)) { + if (RB_UNLIKELY(start[0] == '0' && mantissa_digits > 1)) { raise_parse_error_at("invalid number: %s", state, start); - } else if (RB_UNLIKELY(integer_length > 2 && start[0] == '-' && start[1] == '0')) { - raise_parse_error_at("invalid number: %s", state, start); - } else if (RB_UNLIKELY(integer_length == 1 && start[0] == '-')) { + } else if (RB_UNLIKELY(mantissa_digits > 1 && negative && start[1] == '0')) { raise_parse_error_at("invalid number: %s", state, start); } + // Parse fractional part if ((state->cursor < state->end) && (*state->cursor == '.')) { integer = false; + decimal_point_pos = mantissa_digits; // Remember position of decimal point state->cursor++; - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state); + if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { + raise_parse_error_at("invalid number: %s", state, start); } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + mantissa = mantissa * 10 + (*state->cursor - '0'); + mantissa_digits++; state->cursor++; } } + // Parse exponent if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { integer = false; state->cursor++; - if ((state->cursor < state->end) && ((*state->cursor == '+') || (*state->cursor == '-'))) { + + bool negative_exponent = false; + if ((state->cursor < state->end) && ((*state->cursor == '-') || (*state->cursor == '+'))) { + negative_exponent = (*state->cursor == '-'); state->cursor++; } - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state); + if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { + raise_parse_error_at("invalid number: %s", state, start); } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + exponent = exponent * 10 + (*state->cursor - '0'); state->cursor++; } + + if (negative_exponent) { + exponent = -exponent; + } } if (integer) { - return json_push_value(state, config, json_decode_integer(start, state->cursor)); + return json_push_value(state, config, json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor)); } - return json_push_value(state, config, json_decode_float(config, start, state->cursor)); + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) { + exponent -= (mantissa_digits - decimal_point_pos); + } + + return json_push_value(state, config, json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor)); } case '"': { // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} diff --git a/ext/json/vendor/ryu.h b/ext/json/vendor/ryu.h new file mode 100644 index 00000000000000..f06ec814b4d7a9 --- /dev/null +++ b/ext/json/vendor/ryu.h @@ -0,0 +1,819 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +// +// --- +// +// Apache License +// Version 2.0, January 2004 +// http://www.apache.org/licenses/ +// +// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +// +// 1. Definitions. +// +// "License" shall mean the terms and conditions for use, reproduction, +// and distribution as defined by Sections 1 through 9 of this document. +// +// "Licensor" shall mean the copyright owner or entity authorized by +// the copyright owner that is granting the License. +// +// "Legal Entity" shall mean the union of the acting entity and all +// other entities that control, are controlled by, or are under common +// control with that entity. For the purposes of this definition, +// "control" means (i) the power, direct or indirect, to cause the +// direction or management of such entity, whether by contract or +// otherwise, or (ii) ownership of fifty percent (50%) or more of the +// outstanding shares, or (iii) beneficial ownership of such entity. +// +// "You" (or "Your") shall mean an individual or Legal Entity +// exercising permissions granted by this License. +// +// "Source" form shall mean the preferred form for making modifications, +// including but not limited to software source code, documentation +// source, and configuration files. +// +// "Object" form shall mean any form resulting from mechanical +// transformation or translation of a Source form, including but +// not limited to compiled object code, generated documentation, +// and conversions to other media types. +// +// "Work" shall mean the work of authorship, whether in Source or +// Object form, made available under the License, as indicated by a +// copyright notice that is included in or attached to the work +// (an example is provided in the Appendix below). +// +// "Derivative Works" shall mean any work, whether in Source or Object +// form, that is based on (or derived from) the Work and for which the +// editorial revisions, annotations, elaborations, or other modifications +// represent, as a whole, an original work of authorship. For the purposes +// of this License, Derivative Works shall not include works that remain +// separable from, or merely link (or bind by name) to the interfaces of, +// the Work and Derivative Works thereof. +// +// "Contribution" shall mean any work of authorship, including +// the original version of the Work and any modifications or additions +// to that Work or Derivative Works thereof, that is intentionally +// submitted to Licensor for inclusion in the Work by the copyright owner +// or by an individual or Legal Entity authorized to submit on behalf of +// the copyright owner. For the purposes of this definition, "submitted" +// means any form of electronic, verbal, or written communication sent +// to the Licensor or its representatives, including but not limited to +// communication on electronic mailing lists, source code control systems, +// and issue tracking systems that are managed by, or on behalf of, the +// Licensor for the purpose of discussing and improving the Work, but +// excluding communication that is conspicuously marked or otherwise +// designated in writing by the copyright owner as "Not a Contribution." +// +// "Contributor" shall mean Licensor and any individual or Legal Entity +// on behalf of whom a Contribution has been received by Licensor and +// subsequently incorporated within the Work. +// +// 2. Grant of Copyright License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// copyright license to reproduce, prepare Derivative Works of, +// publicly display, publicly perform, sublicense, and distribute the +// Work and such Derivative Works in Source or Object form. +// +// 3. Grant of Patent License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// (except as stated in this section) patent license to make, have made, +// use, offer to sell, sell, import, and otherwise transfer the Work, +// where such license applies only to those patent claims licensable +// by such Contributor that are necessarily infringed by their +// Contribution(s) alone or by combination of their Contribution(s) +// with the Work to which such Contribution(s) was submitted. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that the Work +// or a Contribution incorporated within the Work constitutes direct +// or contributory patent infringement, then any patent licenses +// granted to You under this License for that Work shall terminate +// as of the date such litigation is filed. +// +// 4. Redistribution. You may reproduce and distribute copies of the +// Work or Derivative Works thereof in any medium, with or without +// modifications, and in Source or Object form, provided that You +// meet the following conditions: +// +// (a) You must give any other recipients of the Work or +// Derivative Works a copy of this License; and +// +// (b) You must cause any modified files to carry prominent notices +// stating that You changed the files; and +// +// (c) You must retain, in the Source form of any Derivative Works +// that You distribute, all copyright, patent, trademark, and +// attribution notices from the Source form of the Work, +// excluding those notices that do not pertain to any part of +// the Derivative Works; and +// +// (d) If the Work includes a "NOTICE" text file as part of its +// distribution, then any Derivative Works that You distribute must +// include a readable copy of the attribution notices contained +// within such NOTICE file, excluding those notices that do not +// pertain to any part of the Derivative Works, in at least one +// of the following places: within a NOTICE text file distributed +// as part of the Derivative Works; within the Source form or +// documentation, if provided along with the Derivative Works; or, +// within a display generated by the Derivative Works, if and +// wherever such third-party notices normally appear. The contents +// of the NOTICE file are for informational purposes only and +// do not modify the License. You may add Your own attribution +// notices within Derivative Works that You distribute, alongside +// or as an addendum to the NOTICE text from the Work, provided +// that such additional attribution notices cannot be construed +// as modifying the License. +// +// You may add Your own copyright statement to Your modifications and +// may provide additional or different license terms and conditions +// for use, reproduction, or distribution of Your modifications, or +// for any such Derivative Works as a whole, provided Your use, +// reproduction, and distribution of the Work otherwise complies with +// the conditions stated in this License. +// +// 5. Submission of Contributions. Unless You explicitly state otherwise, +// any Contribution intentionally submitted for inclusion in the Work +// by You to the Licensor shall be under the terms and conditions of +// this License, without any additional terms or conditions. +// Notwithstanding the above, nothing herein shall supersede or modify +// the terms of any separate license agreement you may have executed +// with Licensor regarding such Contributions. +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor, +// except as required for reasonable and customary use in describing the +// origin of the Work and reproducing the content of the NOTICE file. +// +// 7. Disclaimer of Warranty. Unless required by applicable law or +// agreed to in writing, Licensor provides the Work (and each +// Contributor provides its Contributions) on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied, including, without limitation, any warranties or conditions +// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +// PARTICULAR PURPOSE. You are solely responsible for determining the +// appropriateness of using or redistributing the Work and assume any +// risks associated with Your exercise of permissions under this License. +// +// 8. Limitation of Liability. In no event and under no legal theory, +// whether in tort (including negligence), contract, or otherwise, +// unless required by applicable law (such as deliberate and grossly +// negligent acts) or agreed to in writing, shall any Contributor be +// liable to You for damages, including any direct, indirect, special, +// incidental, or consequential damages of any character arising as a +// result of this License or out of the use or inability to use the +// Work (including but not limited to damages for loss of goodwill, +// work stoppage, computer failure or malfunction, or any and all +// other commercial damages or losses), even if such Contributor +// has been advised of the possibility of such damages. +// +// 9. Accepting Warranty or Additional Liability. While redistributing +// the Work or Derivative Works thereof, You may choose to offer, +// and charge a fee for, acceptance of support, warranty, indemnity, +// or other liability obligations and/or rights consistent with this +// License. However, in accepting such obligations, You may act only +// on Your own behalf and on Your sole responsibility, not on behalf +// of any other Contributor, and only if You agree to indemnify, +// defend, and hold each Contributor harmless for any liability +// incurred by, or claims asserted against, such Contributor by reason +// of your accepting any such warranty or additional liability. +// +// END OF TERMS AND CONDITIONS +// +// APPENDIX: How to apply the Apache License to your work. +// +// To apply the Apache License to your work, attach the following +// boilerplate notice, with the fields enclosed by brackets "[]" +// replaced with your own identifying information. (Don't include +// the brackets!) The text should be enclosed in the appropriate +// comment syntax for the file format. We also recommend that a +// file or class name and description of purpose be included on the +// same "printed page" as the copyright notice for easier +// identification within third-party archives. +// +// Copyright [yyyy] [name of copyright owner] +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// --- +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// --- +// Minimal Ryu implementation adapted for Ruby JSON gem by Josef Šimánek +// Optimized for pre-extracted mantissa/exponent from JSON parsing +// This is a stripped-down version containing only what's needed for +// converting decimal mantissa+exponent to IEEE 754 double precision. + +#ifndef RYU_H +#define RYU_H + +#include +#include +#include + +// Detect __builtin_clzll availability (for floor_log2) +// Note: MSVC doesn't have __builtin_clzll, so we provide a fallback +#ifdef __clang__ + #if __has_builtin(__builtin_clzll) + #define RYU_HAVE_BUILTIN_CLZLL 1 + #else + #define RYU_HAVE_BUILTIN_CLZLL 0 + #endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define RYU_HAVE_BUILTIN_CLZLL 1 +#else + #define RYU_HAVE_BUILTIN_CLZLL 0 +#endif + +// Count leading zeros (for floor_log2) +static inline uint32_t ryu_leading_zeros64(uint64_t input) +{ +#if RYU_HAVE_BUILTIN_CLZLL + return __builtin_clzll(input); +#else + // Fallback: binary search for the highest set bit + // This works on MSVC and other compilers without __builtin_clzll + if (input == 0) return 64; + uint32_t n = 0; + if (input <= 0x00000000FFFFFFFFULL) { n += 32; input <<= 32; } + if (input <= 0x0000FFFFFFFFFFFFULL) { n += 16; input <<= 16; } + if (input <= 0x00FFFFFFFFFFFFFFULL) { n += 8; input <<= 8; } + if (input <= 0x0FFFFFFFFFFFFFFFULL) { n += 4; input <<= 4; } + if (input <= 0x3FFFFFFFFFFFFFFFULL) { n += 2; input <<= 2; } + if (input <= 0x7FFFFFFFFFFFFFFFULL) { n += 1; } + return n; +#endif +} + +// These tables are generated by PrintDoubleLookupTable. +#define DOUBLE_POW5_INV_BITCOUNT 125 +#define DOUBLE_POW5_BITCOUNT 125 + +#define DOUBLE_POW5_INV_TABLE_SIZE 342 +#define DOUBLE_POW5_TABLE_SIZE 326 + +static const uint64_t DOUBLE_POW5_INV_SPLIT[DOUBLE_POW5_INV_TABLE_SIZE][2] = { + { 1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u }, + { 5165088340638674453u, 1475739525896764129u }, { 7821419487252849886u, 1180591620717411303u }, + { 8824922364862649494u, 1888946593147858085u }, { 7059937891890119595u, 1511157274518286468u }, + { 13026647942995916322u, 1208925819614629174u }, { 9774590264567735146u, 1934281311383406679u }, + { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u }, + { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u }, + { 9162556910162266299u, 1267650600228229401u }, { 7281393426775805432u, 2028240960365167042u }, + { 16893161185646375315u, 1622592768292133633u }, { 2446482504291369283u, 1298074214633706907u }, + { 7603720821608101175u, 2076918743413931051u }, { 2393627842544570617u, 1661534994731144841u }, + { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u }, + { 5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u }, + { 3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u }, + { 6382248639981364905u, 1393796574908163946u }, { 2832900194486363201u, 2230074519853062314u }, + { 5955668970331000884u, 1784059615882449851u }, { 1075186361522890384u, 1427247692705959881u }, + { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u }, + { 3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u }, + { 5547241898389809503u, 1870722095783555735u }, { 4437793518711847602u, 1496577676626844588u }, + { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u }, + { 6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u }, + { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u }, + { 14151565162110759939u, 1255420347077336152u }, { 7885109000409574610u, 2008672555323737844u }, + { 9997436015069570011u, 1606938044258990275u }, { 7997948812055656009u, 1285550435407192220u }, + { 12796718099289049614u, 2056880696651507552u }, { 2858676849947419045u, 1645504557321206042u }, + { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u }, + { 3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u }, + { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u }, + { 8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u }, + { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u }, + { 15986332124095098083u, 2261564242916331941u }, { 9099716884534168143u, 1809251394333065553u }, + { 14658471137111155161u, 1447401115466452442u }, { 4348079280205103483u, 1157920892373161954u }, + { 14335624477811986218u, 1852673427797059126u }, { 7779150767507678651u, 1482138742237647301u }, + { 2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u }, + { 12097921058438802501u, 1517710072051350836u }, { 5988988032009131678u, 1214168057641080669u }, + { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u }, + { 7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u }, + { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u }, + { 4951948911778577463u, 2037035976334486086u }, { 272210314680951647u, 1629628781067588869u }, + { 3907117066486671641u, 1303703024854071095u }, { 6251387306378674625u, 2085924839766513752u }, + { 16069156289328670670u, 1668739871813211001u }, { 9165976216721026213u, 1334991897450568801u }, + { 7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u }, + { 13518030318433270404u, 1367031702989382452u }, { 6871453250525591353u, 2187250724783011924u }, + { 9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u }, + { 10282995085511086630u, 2239744742177804210u }, { 8226396068408869304u, 1791795793742243368u }, + { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u }, + { 5324776569667477496u, 1834798892792057209u }, { 7949170070475892320u, 1467839114233645767u }, + { 17427382500606444826u, 1174271291386916613u }, { 5747719112518849781u, 1878834066219066582u }, + { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u }, + { 5295368560860596524u, 1923926083808324180u }, { 4236294848688477220u, 1539140867046659344u }, + { 7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u }, + { 9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u }, + { 12335095245094488799u, 2017382717255397335u }, { 9868076196075591040u, 1613906173804317868u }, + { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u }, + { 7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u }, + { 11918976037903224966u, 2115379100128795516u }, { 5845832015580669650u, 1692303280103036413u }, + { 12055363241948356366u, 1353842624082429130u }, { 841837113407818570u, 2166148198531886609u }, + { 4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u }, + { 12225235553534690011u, 2218135755296651887u }, { 2401490813343931363u, 1774508604237321510u }, + { 1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u }, + { 6886345170554478103u, 1817096810739017226u }, { 1819727321701672159u, 1453677448591213781u }, + { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u }, + { 15587778368262418694u, 1488565707357402911u }, { 8780873879868024632u, 1190852565885922329u }, + { 2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u }, + { 7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u }, + { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u }, + { 1553625868034358140u, 1997919072202235028u }, { 8621598323911307159u, 1598335257761788022u }, + { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u }, + { 121653480894270168u, 1636695303948070935u }, { 97322784715416134u, 1309356243158456748u }, + { 14913111714512307107u, 2094969989053530796u }, { 8241140556867935363u, 1675975991242824637u }, + { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u }, + { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u }, + { 14650556434236701088u, 2196735251241795108u }, { 652398703163629901u, 1757388200993436087u }, + { 11589965406756634890u, 1405910560794748869u }, { 7475898206584884855u, 2249456897271598191u }, + { 2291369750525997561u, 1799565517817278553u }, { 9211793429904618695u, 1439652414253822842u }, + { 18428218302589300235u, 2303443862806116547u }, { 7363877012587619542u, 1842755090244893238u }, + { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u }, + { 2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u }, + { 13231802778477390823u, 1207667975942893232u }, { 6413489186596184024u, 1932268761508629172u }, + { 16198837793502678189u, 1545815009206903337u }, { 5580372605318321905u, 1236652007365522670u }, + { 8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u }, + { 7190041073742725760u, 1266331655542295214u }, { 436019273762630246u, 2026130648867672343u }, + { 7727513048493924843u, 1620904519094137874u }, { 9871359253537050198u, 1296723615275310299u }, + { 4726128361433549347u, 2074757784440496479u }, { 7470251503888749801u, 1659806227552397183u }, + { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u }, + { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u }, + { 4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u }, + { 3483374466074094362u, 1392346379889585943u }, { 1884050330976640656u, 2227754207823337509u }, + { 5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u }, + { 5913764258841343181u, 2281220308811097609u }, { 8420360221814984868u, 1824976247048878087u }, + { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u }, + { 10245762345624985047u, 1868775676978051161u }, { 4507261061758077715u, 1495020541582440929u }, + { 7295157664148372495u, 1196016433265952743u }, { 7982903447895485668u, 1913626293225524389u }, + { 10075671573058298858u, 1530901034580419511u }, { 4371188443704728763u, 1224720827664335609u }, + { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u }, + { 15839291315758726049u, 1254114127528279663u }, { 3206773216762499739u, 2006582604045247462u }, + { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u }, + { 4907049252451240275u, 2054740586542333401u }, { 236290587219081897u, 1643792469233866721u }, + { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u }, + { 5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u }, + { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u }, + { 12584037722297574282u, 1378913065775496824u }, { 9066413911450387881u, 2206260905240794919u }, + { 10942479943902220628u, 1765008724192635935u }, { 8753983955121776503u, 1412006979354108748u }, + { 10317025513452932081u, 2259211166966573997u }, { 874922781278525018u, 1807368933573259198u }, + { 8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u }, + { 14767872471458792434u, 1850745787979017418u }, { 746251532941302978u, 1480596630383213935u }, + { 597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u }, + { 8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u }, + { 17270291175157100626u, 1940647615375886168u }, { 2748186495899949531u, 1552518092300708935u }, + { 2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u }, + { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u }, + { 8826220925580526867u, 2034916513940385215u }, { 7060976740464421494u, 1627933211152308172u }, + { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u }, + { 9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u }, + { 13015147745246481542u, 2133764618521553367u }, { 3033420566713364587u, 1707011694817242694u }, + { 6116085268112601993u, 1365609355853794155u }, { 9785736428980163188u, 2184974969366070648u }, + { 15207286772667951197u, 1747979975492856518u }, { 1097782973908629988u, 1398383980394285215u }, + { 1756452758253807981u, 2237414368630856344u }, { 5094511021344956708u, 1789931494904685075u }, + { 4075608817075965366u, 1431945195923748060u }, { 6520974107321544586u, 2291112313477996896u }, + { 1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u }, + { 17210690286378213644u, 1173049504500734410u }, { 9090360384495590213u, 1876879207201175057u }, + { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u }, + { 16096930852848599373u, 1921924308174003258u }, { 1809498238053148529u, 1537539446539202607u }, + { 12515645034668249793u, 1230031557231362085u }, { 1578287981759648052u, 1968050491570179337u }, + { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u }, + { 3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u }, + { 6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u }, + { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u }, + { 7551343283653851484u, 2113178124542660985u }, { 6041074626923081187u, 1690542499634128788u }, + { 12211557331022285596u, 1352433999707303030u }, { 1091747655926105338u, 2163894399531684849u }, + { 4562746939482794594u, 1731115519625347879u }, { 7339546366328145998u, 1384892415700278303u }, + { 8053925371383123274u, 2215827865120445285u }, { 6443140297106498619u, 1772662292096356228u }, + { 12533209867169019542u, 1418129833677084982u }, { 5295740528502789974u, 2269007733883335972u }, + { 15304638867027962949u, 1815206187106668777u }, { 4865013464138549713u, 1452164949685335022u }, + { 14960057215536570740u, 1161731959748268017u }, { 9178696285890871890u, 1858771135597228828u }, + { 14721654658196518159u, 1487016908477783062u }, { 4398626097073393881u, 1189613526782226450u }, + { 7037801755317430209u, 1903381642851562320u }, { 5630241404253944167u, 1522705314281249856u }, + { 814844308661245011u, 1218164251424999885u }, { 1303750893857992017u, 1949062802279999816u }, + { 15800395974054034906u, 1559250241823999852u }, { 5261619149759407279u, 1247400193459199882u }, + { 12107939454356961969u, 1995840309534719811u }, { 5997002748743659252u, 1596672247627775849u }, + { 8486951013736837725u, 1277337798102220679u }, { 2511075177753209390u, 2043740476963553087u }, + { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u }, + { 4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u }, + { 3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u }, + { 9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u }, + { 5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u }, + { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u }, + { 13453306206113055875u, 1797693134862315907u }, { 3383947335406624054u, 1438154507889852726u }, + { 16482362180876329456u, 2301047212623764361u }, { 9496540929959153242u, 1840837770099011489u }, + { 11286581558709232917u, 1472670216079209191u }, { 5339916432225476010u, 1178136172863367353u }, + { 4854517476818851293u, 1885017876581387765u }, { 3883613981455081034u, 1508014301265110212u }, + { 14174937629389795797u, 1206411441012088169u }, { 11611853762797942306u, 1930258305619341071u }, + { 5600134195496443521u, 1544206644495472857u }, { 15548153800622885787u, 1235365315596378285u }, + { 6430302007287065643u, 1976584504954205257u }, { 16212288050055383484u, 1581267603963364205u }, + { 12969830440044306787u, 1265014083170691364u }, { 9683682259845159889u, 2024022533073106183u }, + { 15125643437359948558u, 1619218026458484946u }, { 8411165935146048523u, 1295374421166787957u }, + { 17147214310975587960u, 2072599073866860731u }, { 10028422634038560045u, 1658079259093488585u }, + { 8022738107230848036u, 1326463407274790868u }, { 9147032156827446534u, 2122341451639665389u }, + { 11006974540203867551u, 1697873161311732311u }, { 5116230817421183718u, 1358298529049385849u }, + { 15564666937357714594u, 2173277646479017358u }, { 1383687105660440706u, 1738622117183213887u }, + { 12174996128754083534u, 1390897693746571109u }, { 8411947361780802685u, 2225436309994513775u }, + { 6729557889424642148u, 1780349047995611020u }, { 5383646311539713719u, 1424279238396488816u }, + { 1235136468979721303u, 2278846781434382106u }, { 15745504434151418335u, 1823077425147505684u }, + { 16285752362063044992u, 1458461940118004547u }, { 5649904260166615347u, 1166769552094403638u }, + { 5350498001524674232u, 1866831283351045821u }, { 591049586477829062u, 1493465026680836657u }, + { 11540886113407994219u, 1194772021344669325u }, { 18673707743239135u, 1911635234151470921u }, + { 14772334225162232601u, 1529308187321176736u }, { 8128518565387875758u, 1223446549856941389u }, + { 1937583260394870242u, 1957514479771106223u }, { 8928764237799716840u, 1566011583816884978u }, + { 14521709019723594119u, 1252809267053507982u }, { 8477339172590109297u, 2004494827285612772u }, + { 17849917782297818407u, 1603595861828490217u }, { 6901236596354434079u, 1282876689462792174u }, + { 18420676183650915173u, 2052602703140467478u }, { 3668494502695001169u, 1642082162512373983u }, + { 10313493231639821582u, 1313665730009899186u }, { 9122891541139893884u, 2101865168015838698u }, + { 14677010862395735754u, 1681492134412670958u }, { 673562245690857633u, 1345193707530136767u } +}; + +static const uint64_t DOUBLE_POW5_SPLIT[DOUBLE_POW5_TABLE_SIZE][2] = { + { 0u, 1152921504606846976u }, { 0u, 1441151880758558720u }, + { 0u, 1801439850948198400u }, { 0u, 2251799813685248000u }, + { 0u, 1407374883553280000u }, { 0u, 1759218604441600000u }, + { 0u, 2199023255552000000u }, { 0u, 1374389534720000000u }, + { 0u, 1717986918400000000u }, { 0u, 2147483648000000000u }, + { 0u, 1342177280000000000u }, { 0u, 1677721600000000000u }, + { 0u, 2097152000000000000u }, { 0u, 1310720000000000000u }, + { 0u, 1638400000000000000u }, { 0u, 2048000000000000000u }, + { 0u, 1280000000000000000u }, { 0u, 1600000000000000000u }, + { 0u, 2000000000000000000u }, { 0u, 1250000000000000000u }, + { 0u, 1562500000000000000u }, { 0u, 1953125000000000000u }, + { 0u, 1220703125000000000u }, { 0u, 1525878906250000000u }, + { 0u, 1907348632812500000u }, { 0u, 1192092895507812500u }, + { 0u, 1490116119384765625u }, { 4611686018427387904u, 1862645149230957031u }, + { 9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u }, + { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u }, + { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u }, + { 15629249925580062720u, 2220446049250313080u }, { 9768281203487539200u, 1387778780781445675u }, + { 7598665485932036096u, 1734723475976807094u }, { 274959820560269312u, 2168404344971008868u }, + { 9395221924704944128u, 1355252715606880542u }, { 2520655369026404352u, 1694065894508600678u }, + { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u }, + { 13702562178731606016u, 1654361225106055349u }, { 3293144668132343808u, 2067951531382569187u }, + { 18199116482078572544u, 1292469707114105741u }, { 8913837547316051968u, 1615587133892632177u }, + { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u }, + { 15190102943214346240u, 1577721810442023610u }, { 9764256642163156992u, 1972152263052529513u }, + { 17631875447420442880u, 1232595164407830945u }, { 8204786253993389888u, 1540743955509788682u }, + { 1032610780636961552u, 1925929944387235853u }, { 2951224747111794922u, 1203706215242022408u }, + { 3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u }, + { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u }, + { 3710578054803671186u, 1836709923159824231u }, { 26536550077201078u, 2295887403949780289u }, + { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u }, + { 8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u }, + { 12076381983474541759u, 1751623080406021338u }, { 5872105442488401391u, 2189528850507526673u }, + { 15199280947623720629u, 1368455531567204170u }, { 9775729147674874978u, 1710569414459005213u }, + { 16831347453020981627u, 2138211768073756516u }, { 1296220121283337709u, 1336382355046097823u }, + { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u }, + { 6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u }, + { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u }, + { 532749306367912313u, 1593091911132452277u }, { 5277622651387278295u, 1991364888915565346u }, + { 7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u }, + { 8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u }, + { 10989071563364309441u, 1519290839321567799u }, { 9124653435777998898u, 1899113549151959749u }, + { 8008751406574943263u, 1186945968219974843u }, { 5399253239791291175u, 1483682460274968554u }, + { 15972438586593889776u, 1854603075343710692u }, { 759402079766405302u, 1159126922089819183u }, + { 14784310654990170340u, 1448908652612273978u }, { 9257016281882937117u, 1811135815765342473u }, + { 16182956370781059300u, 2263919769706678091u }, { 7808504722524468110u, 1414949856066673807u }, + { 5148944884728197234u, 1768687320083342259u }, { 1824495087482858639u, 2210859150104177824u }, + { 1140309429676786649u, 1381786968815111140u }, { 1425386787095983311u, 1727233711018888925u }, + { 6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u }, + { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u }, + { 803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u }, + { 4714634887749086344u, 2059023035787211567u }, { 9864175832484260821u, 1286889397367007229u }, + { 16941905809032713930u, 1608611746708759036u }, { 2730638187581340797u, 2010764683385948796u }, + { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u }, + { 4396021111970173586u, 1963637386119090621u }, { 5053356204195052443u, 1227273366324431638u }, + { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u }, + { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u }, + { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u }, + { 4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u }, + { 7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u }, + { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u }, + { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u }, + { 10956397575869330579u, 2180075438084173168u }, { 6847748484918331612u, 1362547148802608230u }, + { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u }, + { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u }, + { 13024893955298202172u, 2079081953128979843u }, { 5834715712847682405u, 1299426220705612402u }, + { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u }, + { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u }, + { 12596021324643517422u, 1982767060402850955u }, { 5566670318688504437u, 1239229412751781847u }, + { 2346651879933242642u, 1549036765939727309u }, { 7545000868343941206u, 1936295957424659136u }, + { 4715625542714963254u, 1210184973390411960u }, { 5894531928393704067u, 1512731216738014950u }, + { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u }, + { 16997363506238734644u, 1477276578845717724u }, { 2799960309088866689u, 1846595723557147156u }, + { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u }, + { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u }, + { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u }, + { 3447025083132431277u, 2201313642927583642u }, { 6766076695385157452u, 1375821026829739776u }, + { 8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u }, + { 6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u }, + { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u }, + { 5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u }, + { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u }, + { 7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u }, + { 3885132398186337741u, 1564127418111797597u }, { 9468101516160310080u, 1955159272639746996u }, + { 15140935484454969608u, 1221974545399841872u }, { 479425281859160394u, 1527468181749802341u }, + { 5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u }, + { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u }, + { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u }, + { 8047776328842869663u, 1820883967578175474u }, { 836348374198811271u, 2276104959472719343u }, + { 7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u }, + { 8166621051047176104u, 2222758749485077483u }, { 2798295147690791113u, 1389224218428173427u }, + { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u }, + { 8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u }, + { 3828506775840797949u, 2119787930951192363u }, { 86973725686804766u, 1324867456844495227u }, + { 13943775212390669669u, 1656084321055619033u }, { 3594660960206173375u, 2070105401319523792u }, + { 2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u }, + { 5816254103165035138u, 2021587305976097453u }, { 5941001823691840913u, 1263492066235060908u }, + { 7426252279614801142u, 1579365082793826135u }, { 4671129331091113523u, 1974206353492282669u }, + { 5225298841145639904u, 1233878970932676668u }, { 6531623551432049880u, 1542348713665845835u }, + { 3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u }, + { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u }, + { 11355826773077190286u, 1176718684132267635u }, { 9583097447919099954u, 1470898355165334544u }, + { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u }, + { 2440964573842414192u, 1436424174966147016u }, { 3051205717303017741u, 1795530218707683770u }, + { 13037379183483547984u, 2244412773384604712u }, { 8148361989677217490u, 1402757983365377945u }, + { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u }, + { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u }, + { 9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u }, + { 3292878744173340370u, 1672217826086733276u }, { 4116098430216675462u, 2090272282608416595u }, + { 266718509671728212u, 1306420176630260372u }, { 333398137089660265u, 1633025220787825465u }, + { 5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u }, + { 12575375104699370472u, 1594751192175610805u }, { 1884160825592049379u, 1993438990219513507u }, + { 17318501580490888525u, 1245899368887195941u }, { 7813068920331446945u, 1557374211108994927u }, + { 5154650131986920777u, 1946717763886243659u }, { 915813323278131534u, 1216698602428902287u }, + { 14979824709379828129u, 1520873253036127858u }, { 9501408849870009354u, 1901091566295159823u }, + { 12855909558809837702u, 1188182228934474889u }, { 2234828893230133415u, 1485227786168093612u }, + { 2793536116537666769u, 1856534732710117015u }, { 8663489100477123587u, 1160334207943823134u }, + { 1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u }, + { 9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u }, + { 16011667041914537212u, 1770529492101780905u }, { 6179525747111007803u, 2213161865127226132u }, + { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u }, + { 15834525901571292854u, 2161290883913306769u }, { 2979049660840976177u, 1350806802445816731u }, + { 17558870131333383934u, 1688508503057270913u }, { 8113529608884566205u, 2110635628821588642u }, + { 9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u }, + { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u }, + { 1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u }, + { 4853210475701136017u, 1258036869061940099u }, { 1454827076199032118u, 1572546086327425124u }, + { 1818533845248790147u, 1965682607909281405u }, { 3442426662494187794u, 1228551629943300878u }, + { 13526405364972510550u, 1535689537429126097u }, { 3072948650933474476u, 1919611921786407622u }, + { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u }, + { 9630225068416591280u, 1874621017369538693u }, { 8324733676974063502u, 1171638135855961683u }, + { 5794231077790191473u, 1464547669819952104u }, { 7242788847237739342u, 1830684587274940130u }, + { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u }, + { 1596658836748081690u, 1787777917260683721u }, { 6607509564362490017u, 2234722396575854651u }, + { 1823850468512862308u, 1396701497859909157u }, { 6891499104068465790u, 1745876872324886446u }, + { 17837745916940358045u, 2182346090406108057u }, { 4231062170446641922u, 1363966306503817536u }, + { 5288827713058302403u, 1704957883129771920u }, { 6611034641322878003u, 2131197353912214900u }, + { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u }, + { 11644235287647684442u, 2081247415929897363u }, { 4971804045566108824u, 1300779634956185852u }, + { 6214755056957636030u, 1625974543695232315u }, { 3156757802769657134u, 2032468179619040394u }, + { 6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u }, + { 17206059723201118751u, 1984832206659219134u }, { 6142101308573311315u, 1240520129162011959u }, + { 3065940617289251240u, 1550650161452514949u }, { 8444111790038951954u, 1938312701815643686u }, + { 665883850346957067u, 1211445438634777304u }, { 832354812933696334u, 1514306798293471630u }, + { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u }, + { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u }, + { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u }, + { 1277659885424598868u, 1805194375864829576u }, { 1597074856780748586u, 2256492969831036970u }, + { 5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u }, + { 1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u }, + { 11243484188358716120u, 1721567512383298469u }, { 219297180166231438u, 2151959390479123087u }, + { 7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u }, + { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u }, + { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u }, + { 16925056528170176201u, 1282667750405742577u }, { 7321262604930556539u, 1603334688007178222u }, + { 18374950293017971482u, 2004168360008972777u }, { 4566814905495150320u, 1252605225005607986u }, + { 14931890668723713708u, 1565756531257009982u }, { 9441491299049866327u, 1957195664071262478u }, + { 1289246043478778550u, 1223247290044539049u }, { 6223243572775861092u, 1529059112555673811u }, + { 3167368447542438461u, 1911323890694592264u }, { 1979605279714024038u, 1194577431684120165u }, + { 7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u }, + { 13606538515115052232u, 1166579523129023598u }, { 7784801107039039482u, 1458224403911279498u }, + { 507629346944023544u, 1822780504889099373u }, { 5246222702107417334u, 2278475631111374216u }, + { 3278889188817135834u, 1424047269444608885u }, { 8710297504448807696u, 1780059086805761106u } +}; + +// IEEE 754 double precision constants +#define DOUBLE_MANTISSA_BITS 52 +#define DOUBLE_EXPONENT_BITS 11 +#define DOUBLE_EXPONENT_BIAS 1023 + +// Helper: floor(log2(value)) using ryu_leading_zeros64 +static inline uint32_t floor_log2(const uint64_t value) { + return 63 - ryu_leading_zeros64(value); +} + +// Helper: log2(5^e) approximation +static inline int32_t log2pow5(const int32_t e) { + return (int32_t) ((((uint32_t) e) * 1217359) >> 19); +} + +// Helper: ceil(log2(5^e)) +static inline int32_t ceil_log2pow5(const int32_t e) { + return log2pow5(e) + 1; +} + +// Helper: max of two int32 +static inline int32_t max32(int32_t a, int32_t b) { + return a < b ? b : a; +} + +// Helper: convert uint64 bits to double +static inline double int64Bits2Double(uint64_t bits) { + double f; + memcpy(&f, &bits, sizeof(double)); + return f; +} + +// Check if value is multiple of 2^p +static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) { + return (value & ((1ull << p) - 1)) == 0; +} + +// Count how many times value is divisible by 5 +// Uses modular inverse to avoid expensive division +static inline uint32_t pow5Factor(uint64_t value) { + const uint64_t m_inv_5 = 14757395258967641293u; // 5 * m_inv_5 = 1 (mod 2^64) + const uint64_t n_div_5 = 3689348814741910323u; // 2^64 / 5 + uint32_t count = 0; + for (;;) { + value *= m_inv_5; + if (value > n_div_5) + break; + ++count; + } + return count; +} + +// Check if value is multiple of 5^p +// Optimized: uses modular inverse instead of division +static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) { + return pow5Factor(value) >= p; +} + +// 128-bit multiplication with shift +// This is the core operation for converting decimal to binary +#if defined(__SIZEOF_INT128__) +// Use native 128-bit integers if available (GCC/Clang) +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + const unsigned __int128 b0 = ((unsigned __int128) m) * mul[0]; + const unsigned __int128 b2 = ((unsigned __int128) m) * mul[1]; + return (uint64_t) (((b0 >> 64) + b2) >> (j - 64)); +} +#else +// Fallback for systems without 128-bit integers +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + const uint32_t aLo = (uint32_t)a; + const uint32_t aHi = (uint32_t)(a >> 32); + const uint32_t bLo = (uint32_t)b; + const uint32_t bHi = (uint32_t)(b >> 32); + + const uint64_t b00 = (uint64_t)aLo * bLo; + const uint64_t b01 = (uint64_t)aLo * bHi; + const uint64_t b10 = (uint64_t)aHi * bLo; + const uint64_t b11 = (uint64_t)aHi * bHi; + + const uint32_t b00Lo = (uint32_t)b00; + const uint32_t b00Hi = (uint32_t)(b00 >> 32); + + const uint64_t mid1 = b10 + b00Hi; + const uint32_t mid1Lo = (uint32_t)(mid1); + const uint32_t mid1Hi = (uint32_t)(mid1 >> 32); + + const uint64_t mid2 = b01 + mid1Lo; + const uint32_t mid2Lo = (uint32_t)(mid2); + const uint32_t mid2Hi = (uint32_t)(mid2 >> 32); + + const uint64_t pHi = b11 + mid1Hi + mid2Hi; + const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo; + + *productHi = pHi; + return pLo; +} + +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + return (hi << (64 - dist)) | (lo >> dist); +} + +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + uint64_t high1; + const uint64_t low1 = umul128(m, mul[1], &high1); + uint64_t high0; + umul128(m, mul[0], &high0); + const uint64_t sum = high0 + low1; + if (sum < high0) { + ++high1; + } + return shiftright128(sum, high1, j - 64); +} +#endif + +// Main conversion function: decimal mantissa+exponent to IEEE 754 double +// Optimized for JSON parsing with fast paths for edge cases +static inline double ryu_s2d_from_parts(uint64_t m10, int m10digits, int32_t e10, bool signedM) { + // Fast path: handle zero explicitly (e.g., "0.0", "0e0") + if (m10 == 0) { + return int64Bits2Double(((uint64_t) signedM) << 63); + } + + // Fast path: handle overflow/underflow early + if (m10digits + e10 <= -324) { + // Underflow to zero + return int64Bits2Double(((uint64_t) signedM) << 63); + } + + if (m10digits + e10 >= 310) { + // Overflow to infinity + return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL); + } + + // Convert decimal to binary: m10 * 10^e10 = m2 * 2^e2 + int32_t e2; + uint64_t m2; + bool trailingZeros; + + if (e10 >= 0) { + // Positive exponent: multiply by 5^e10 and adjust binary exponent + e2 = floor_log2(m10) + e10 + log2pow5(e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 - ceil_log2pow5(e10) + DOUBLE_POW5_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[e10], j); + trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10, e2 - e10)); + } else { + // Negative exponent: divide by 5^(-e10) + e2 = floor_log2(m10) + e10 - ceil_log2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 + ceil_log2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[-e10], j); + trailingZeros = multipleOfPowerOf5(m10, -e10); + } + + // Compute IEEE 754 exponent + uint32_t ieee_e2 = (uint32_t) max32(0, e2 + DOUBLE_EXPONENT_BIAS + floor_log2(m2)); + + if (ieee_e2 > 0x7fe) { + // Overflow to infinity + return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL); + } + + // Compute shift amount for rounding + int32_t shift = (ieee_e2 == 0 ? 1 : ieee_e2) - e2 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + + // IEEE 754 round-to-even (banker's rounding) + trailingZeros &= (m2 & ((1ull << (shift - 1)) - 1)) == 0; + uint64_t lastRemovedBit = (m2 >> (shift - 1)) & 1; + bool roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> shift) & 1) != 0)); + + uint64_t ieee_m2 = (m2 >> shift) + roundUp; + ieee_m2 &= (1ull << DOUBLE_MANTISSA_BITS) - 1; + + if (ieee_m2 == 0 && roundUp) { + ieee_e2++; + } + + // Pack sign, exponent, and mantissa into IEEE 754 format + // Match original Ryu: group sign+exponent, then shift and add mantissa + uint64_t ieee = (((((uint64_t) signedM) << DOUBLE_EXPONENT_BITS) | (uint64_t)ieee_e2) << DOUBLE_MANTISSA_BITS) | ieee_m2; + return int64Bits2Double(ieee); +} + +#endif // RYU_H diff --git a/test/json/json_ryu_fallback_test.rb b/test/json/json_ryu_fallback_test.rb new file mode 100644 index 00000000000000..59ba76d392fc9b --- /dev/null +++ b/test/json/json_ryu_fallback_test.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true +require_relative 'test_helper' +begin + require 'bigdecimal' +rescue LoadError +end + +class JSONRyuFallbackTest < Test::Unit::TestCase + include JSON + + # Test that numbers with more than 17 significant digits fall back to rb_cstr_to_dbl + def test_more_than_17_significant_digits + # These numbers have > 17 significant digits and should use fallback path + # They should still parse correctly, just not via the Ryu optimization + + test_cases = [ + # input, expected (rounded to double precision) + ["1.23456789012345678901234567890", 1.2345678901234567], + ["123456789012345678.901234567890", 1.2345678901234568e+17], + ["0.123456789012345678901234567890", 0.12345678901234568], + ["9999999999999999999999999999.9", 1.0e+28], + # Edge case: exactly 18 digits + ["123456789012345678", 123456789012345680.0], + # Many fractional digits + ["0.12345678901234567890123456789", 0.12345678901234568], + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, 1e-10, + "Failed to parse #{input} correctly (>17 digits, fallback path)") + end + end + + # Test decimal_class option forces fallback + def test_decimal_class_option + input = "3.141" + + # Without decimal_class: uses Ryu, returns Float + result_float = JSON.parse(input) + assert_instance_of(Float, result_float) + assert_equal(3.141, result_float) + + # With decimal_class: uses fallback, returns BigDecimal + result_bigdecimal = JSON.parse(input, decimal_class: BigDecimal) + assert_instance_of(BigDecimal, result_bigdecimal) + assert_equal(BigDecimal("3.141"), result_bigdecimal) + end if defined?(::BigDecimal) + + # Test that numbers with <= 17 digits use Ryu optimization + def test_ryu_optimization_used_for_normal_numbers + test_cases = [ + ["3.141", 3.141], + ["1.23456789012345e100", 1.23456789012345e100], + ["0.00000000000001", 1.0e-14], + ["123456789012345.67", 123456789012345.67], + ["-1.7976931348623157e+308", -1.7976931348623157e+308], + ["2.2250738585072014e-308", 2.2250738585072014e-308], + # Exactly 17 significant digits + ["12345678901234567", 12345678901234567.0], + ["1.2345678901234567", 1.2345678901234567], + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-15, + "Failed to parse #{input} correctly (<=17 digits, Ryu path)") + end + end + + # Test edge cases at the boundary (17 digits) + def test_seventeen_digit_boundary + # Exactly 17 significant digits should use Ryu + input_17 = "12345678901234567.0" # Force it to be a float with .0 + result = JSON.parse(input_17) + assert_in_delta(12345678901234567.0, result, 1e-10) + + # 18 significant digits should use fallback + input_18 = "123456789012345678.0" + result = JSON.parse(input_18) + # Note: This will be rounded to double precision + assert_in_delta(123456789012345680.0, result, 1e-10) + end + + # Test that leading zeros don't count toward the 17-digit limit + def test_leading_zeros_dont_count + test_cases = [ + ["0.00012345678901234567", 0.00012345678901234567], # 17 significant digits + ["0.000000000000001234567890123456789", 1.234567890123457e-15], # >17 significant + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-10, + "Failed to parse #{input} correctly") + end + end + + # Test that Ryu handles special values correctly + def test_special_double_values + test_cases = [ + ["1.7976931348623157e+308", Float::MAX], # Largest finite double + ["2.2250738585072014e-308", Float::MIN], # Smallest normalized double + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-10, + "Failed to parse #{input} correctly") + end + + # Test zero separately + result_pos_zero = JSON.parse("0.0") + assert_equal(0.0, result_pos_zero) + + # Note: JSON.parse doesn't preserve -0.0 vs +0.0 distinction in standard mode + result_neg_zero = JSON.parse("-0.0") + assert_equal(0.0, result_neg_zero.abs) + end + + # Test subnormal numbers that caused precision issues before fallback was added + # These are extreme edge cases discovered by fuzzing (4 in 6 billion numbers tested) + def test_subnormal_edge_cases_round_trip + # These subnormal numbers (~1e-310) had 1 ULP rounding errors in original Ryu + # They now use rb_cstr_to_dbl fallback for exact precision + test_cases = [ + "-3.2652630314355e-310", + "3.9701623107025e-310", + "-3.6607772435415e-310", + "2.9714076801985e-310", + ] + + test_cases.each do |input| + # Parse the number + result = JSON.parse(input) + + # Should be bit-identical + assert_equal(result, JSON.parse(result.to_s), + "Subnormal #{input} failed round-trip test") + + # Should be bit-identical + assert_equal(result, JSON.parse(JSON.dump(result)), + "Subnormal #{input} failed round-trip test") + + # Verify the value is in the expected subnormal range + assert(result.abs < 2.225e-308, + "#{input} should be subnormal (< 2.225e-308)") + end + end + + # Test invalid numbers are properly rejected + def test_invalid_numbers_rejected + invalid_cases = [ + "-", + ".", + "-.", + "-.e10", + "1.2.3", + "1e", + "1e+", + ] + + invalid_cases.each do |input| + assert_raise(JSON::ParserError, "Should reject invalid number: #{input}") do + JSON.parse(input) + end + end + end +end From a88c5558fe5d0407af45e2bcae4a897e4038aec2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 21:28:09 +0900 Subject: [PATCH 0667/2435] Fix up JSON dependency --- ext/json/parser/depend | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/json/parser/depend b/ext/json/parser/depend index 1bb03d3517590e..b780afb5f94546 100644 --- a/ext/json/parser/depend +++ b/ext/json/parser/depend @@ -176,5 +176,6 @@ parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h parser.o: $(srcdir)/../simd/simd.h +parser.o: $(srcdir)/../vendor/ryu.h parser.o: parser.c # AUTOGENERATED DEPENDENCIES END From f6c2a63845765d8717ff307356d3327dc703db81 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 21:34:46 +0900 Subject: [PATCH 0668/2435] CI: Exclude vendored sources at cheching if US-ASCII clean --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index bbbaae07b20765..480a95118fc79f 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -40,7 +40,7 @@ jobs: - name: Check if C-sources are US-ASCII run: | - grep -r -n --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : + grep -r -n --exclude-dir=vendor --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : - name: Check for bash specific substitution in configure.ac run: | From d5fbff50c7ff880ae71b8a8ae9aad976c69bea73 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 21:47:12 +0900 Subject: [PATCH 0669/2435] [DOC] LEGAL for JSON vendored sources --- LEGAL | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 239 insertions(+), 26 deletions(-) diff --git a/LEGAL b/LEGAL index a05cd6eaa3b01b..cb1b867c23bd81 100644 --- a/LEGAL +++ b/LEGAL @@ -704,32 +704,7 @@ mentioned below. [ext/json/vendor/fpconv.c] - This file is under the Boost Software License. - - >>> - Boost Software License - Version 1.0 - August 17th, 2003 - - Permission is hereby granted, free of charge, to any person or organization - obtaining a copy of the software and accompanying documentation covered by - this license (the "Software") to use, reproduce, display, distribute, - execute, and transmit the Software, and to prepare derivative works of the - Software, and to permit third-parties to whom the Software is furnished to - do so, all subject to the following: - - The copyright notices in the Software and this entire statement, including - the above license grant, this restriction and the following disclaimer, - must be included in all copies of the Software, in whole or in part, and - all derivative works of the Software, unless such copies or derivative - works are solely in the form of machine-executable object code generated by - a source language processor. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. + This file is under the {Boost Software License}[rdoc-ref:@Boost+Software+License+1.0]. [ext/json/vendor/jeaiii-ltoa.h] @@ -740,6 +715,11 @@ mentioned below. {MIT License}[rdoc-ref:@MIT+License] +[ext/json/ext/vendor/ryu.h] + This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu. + It is dual-licensed under {Apache License 2.0}[rdoc-ref:@Apache+License+2.0] OR + {Boost Software License 1.0}[rdoc-ref:@Boost+Software+License+1.0]. + [ext/psych] [test/psych] @@ -1086,3 +1066,236 @@ mentioned below. From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change paragraph 3 above is now null and void. + +== Boost Software License 1.0 + +>>> + Boost Software License - Version 1.0 - August 17th, 2003 + + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +== Apache License 2.0 + +>>> + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + a. You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + b. You must cause any modified files to carry prominent notices + stating that You changed the files; and + + c. You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + d. If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + >>> + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 99382f7461db80497b69df84ac52fab6710ba160 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:24:45 +0100 Subject: [PATCH 0670/2435] [ruby/prism] Unescape unary method calls Followup to https://github.com/ruby/prism/pull/2213 Before: ```sh $ ruby -ve "puts 42.~@" ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/prism/commit/dbd83256b1) +PRISM [x86_64-linux] -e:1:in '
': undefined method '~@' for an instance of Integer (NoMethodError) Did you mean? ~ ``` After (matches parse.y): ```sh $ ./miniruby -ve "puts 42.~@" ruby 3.5.0dev (2025-10-16T03:40:45Z master https://github.com/ruby/prism/commit/1d95d75c3f) +PRISM [x86_64-linux] -43 ``` https://github.com/ruby/prism/commit/a755bf228f --- prism/prism.c | 10 ++++++++-- test/prism/fixtures/unary_method_calls.txt | 2 ++ test/prism/ruby/parser_test.rb | 3 +++ test/prism/ruby/ruby_parser_test.rb | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/prism/fixtures/unary_method_calls.txt diff --git a/prism/prism.c b/prism/prism.c index 95e7d0905040b1..a92de163f515ec 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2721,6 +2721,8 @@ pm_call_node_binary_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t return node; } +static const uint8_t * parse_operator_symbol_name(const pm_token_t *); + /** * Allocate and initialize a new CallNode node from a call expression. */ @@ -2749,7 +2751,11 @@ pm_call_node_call_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *o pm_node_flag_set((pm_node_t *)node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); } - node->name = pm_parser_constant_id_token(parser, message); + /** + * If the final character is `@` as is the case for `foo.~@`, + * we should ignore the @ in the same way we do for symbols. + */ + node->name = pm_parser_constant_id_location(parser, message->start, parse_operator_symbol_name(message)); return node; } @@ -19702,7 +19708,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_scope_pop(parser); /** - * If the final character is @. As is the case when defining + * If the final character is `@` as is the case when defining * methods to override the unary operators, we should ignore * the @ in the same way we do for symbols. */ diff --git a/test/prism/fixtures/unary_method_calls.txt b/test/prism/fixtures/unary_method_calls.txt new file mode 100644 index 00000000000000..dda85e4bdbf6f9 --- /dev/null +++ b/test/prism/fixtures/unary_method_calls.txt @@ -0,0 +1,2 @@ +42.~@ +42.!@ diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 016fda91f03819..3104369d3eadd3 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -109,6 +109,9 @@ class ParserTest < TestCase # Regex with \c escape "unescaping.txt", "seattlerb/regexp_esc_C_slash.txt", + + # https://github.com/whitequark/parser/issues/1084 + "unary_method_calls.txt", ] # These files are failing to translate their lexer output into the lexer diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 7640ddaf1ca6c0..42a888be820924 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -57,6 +57,7 @@ class RubyParserTest < TestCase "spanning_heredoc.txt", "symbols.txt", "tilde_heredocs.txt", + "unary_method_calls.txt", "unparser/corpus/literal/literal.txt", "while.txt", "whitequark/cond_eflipflop.txt", From 8e7d427a721ab715361c577fdea94fdebece6c6d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 26 Oct 2025 16:16:41 -0400 Subject: [PATCH 0671/2435] [ruby/prism] Add equal_loc to call nodes In the case of attribute writes, there are use cases where you want to know the location of the = sign. (Internally we actually need this for translation to the writequark AST.) https://github.com/ruby/prism/commit/bfc798a7ec --- lib/prism/translation/parser/compiler.rb | 32 ++++++++++++------------ prism/config.yml | 10 ++++++++ prism/prism.c | 3 +++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 6e0618890de6a7..88056146036411 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -217,7 +217,7 @@ def visit_begin_node(node) rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, token(rescue_clause.operator_loc), visit(rescue_clause.reference), - srange_find(find_start_offset, find_end_offset, ";"), + srange_semicolon(find_start_offset, find_end_offset), visit(rescue_clause.statements) ) end until (rescue_clause = rescue_clause.subsequent).nil? @@ -323,7 +323,7 @@ def visit_call_node(node) visit_all(arguments), token(node.closing_loc), ), - srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ), block @@ -340,7 +340,7 @@ def visit_call_node(node) if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? builder.assign( builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), - srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ) else @@ -789,7 +789,7 @@ def visit_for_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), token(node.end_keyword_loc) @@ -921,7 +921,7 @@ def visit_if_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), case node.subsequent @@ -987,7 +987,7 @@ def visit_in_node(node) if (then_loc = node.then_loc) token(then_loc) else - srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.pattern.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1808,7 +1808,7 @@ def visit_unless_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset) end, visit(node.else_clause), token(node.else_clause&.else_keyword_loc), @@ -1839,7 +1839,7 @@ def visit_until_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -1863,7 +1863,7 @@ def visit_when_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.conditions.last.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1883,7 +1883,7 @@ def visit_while_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -2012,16 +2012,16 @@ def srange_offsets(start_offset, end_offset) Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end - # Constructs a new source range by finding the given character between - # the given start offset and end offset. If the needle is not found, it - # returns nil. Importantly it does not search past newlines or comments. + # Constructs a new source range by finding a semicolon between the given + # start offset and end offset. If the semicolon is not found, it returns + # nil. Importantly it does not search past newlines or comments. # # Note that end_offset is allowed to be nil, in which case this will # search until the end of the string. - def srange_find(start_offset, end_offset, character) - if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/]) + def srange_semicolon(start_offset, end_offset) + if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*;/]) final_offset = start_offset + match.bytesize - [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])] + [";", Range.new(source_buffer, offset_cache[final_offset - 1], offset_cache[final_offset])] end end diff --git a/prism/config.yml b/prism/config.yml index ed5c9d8b9cde98..7d71d52de487df 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1515,6 +1515,16 @@ nodes: foo(bar) ^ + - name: equal_loc + type: location? + comment: | + Represents the location of the equal sign, in the case that this is an attribute write. + + foo.bar = value + ^ + + foo[bar] = value + ^ - name: block type: node? kind: diff --git a/prism/prism.c b/prism/prism.c index a92de163f515ec..6a77dd0febd5d3 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2650,6 +2650,7 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .arguments = NULL, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .equal_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .block = NULL, .name = 0 }; @@ -13810,6 +13811,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_arguments_node_arguments_append(arguments, value); call->base.location.end = arguments->base.location.end; + call->equal_loc = PM_LOCATION_TOKEN_VALUE(operator); parse_write_name(parser, &call->name); pm_node_flag_set((pm_node_t *) call, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); @@ -13831,6 +13833,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // Replace the name with "[]=". call->name = pm_parser_constant_id_constant(parser, "[]=", 3); + call->equal_loc = PM_LOCATION_TOKEN_VALUE(operator); // Ensure that the arguments for []= don't contain keywords pm_index_arguments_check(parser, call->arguments, call->block); From 244a37bd53fb5c7ba219ef958b6adaa2c4b0b67e Mon Sep 17 00:00:00 2001 From: Andre Muta Date: Fri, 24 Oct 2025 15:04:04 -0300 Subject: [PATCH 0672/2435] Fixes [Bug #21522] eval isolation in Ractors for Prism --- bootstraptest/test_ractor.rb | 31 ++++++++++++++++++++++++++ vm_eval.c | 42 ++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 65ef07fb73e291..fef55ffd688003 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -754,6 +754,37 @@ def ractor_local_globals end } +# eval with outer locals in a Ractor raises SyntaxError +# [Bug #21522] +assert_equal 'SyntaxError', %q{ + outer = 42 + r = Ractor.new do + eval("outer") + end + begin + r.value + rescue Ractor::RemoteError => e + e.cause.class + end +} + +# eval of an undefined name in a Ractor raises NameError +assert_equal 'NameError', %q{ + r = Ractor.new do + eval("totally_undefined_name") + end + begin + r.value + rescue Ractor::RemoteError => e + e.cause.class + end +} + +# eval of a local defined inside the Ractor works +assert_equal '99', %q{ + Ractor.new { inner = 99; eval("inner").to_s }.value +} + # ivar in shareable-objects are not allowed to access from non-main Ractor assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false class C diff --git a/vm_eval.c b/vm_eval.c index 71656f5a0f2ab1..b791cd4990b00f 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1669,6 +1669,24 @@ get_eval_default_path(void) return eval_default_path; } +static inline int +compute_isolated_depth_from_ep(const VALUE *ep) +{ + int depth = 1; + while (1) { + if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) return depth; + if (VM_ENV_LOCAL_P(ep)) return 0; + ep = VM_ENV_PREV_EP(ep); + depth++; + } +} + +static inline int +compute_isolated_depth_from_block(const struct rb_block *blk) +{ + return compute_isolated_depth_from_ep(vm_block_ep(blk)); +} + static const rb_iseq_t * pm_eval_make_iseq(VALUE src, VALUE fname, int line, const struct rb_block *base_block) @@ -1677,8 +1695,8 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, const rb_iseq_t *iseq = parent; VALUE name = rb_fstring_lit(""); - // Conditionally enable coverage depending on the current mode: int coverage_enabled = ((rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0) ? 1 : 0; + int isolated_depth = compute_isolated_depth_from_block(base_block); if (!fname) { fname = rb_source_location(&line); @@ -1872,7 +1890,7 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, #undef FORWARDING_ALL_STR int error_state; - iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, 0, &error_state); + iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, isolated_depth, &error_state); pm_scope_node_t *prev = result.node.previous; while (prev) { @@ -1908,27 +1926,9 @@ eval_make_iseq(VALUE src, VALUE fname, int line, rb_iseq_t *iseq = NULL; VALUE ast_value; rb_ast_t *ast; - int isolated_depth = 0; - // Conditionally enable coverage depending on the current mode: int coverage_enabled = (rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0; - - { - int depth = 1; - const VALUE *ep = vm_block_ep(base_block); - - while (1) { - if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) { - isolated_depth = depth; - break; - } - else if (VM_ENV_LOCAL_P(ep)) { - break; - } - ep = VM_ENV_PREV_EP(ep); - depth++; - } - } + int isolated_depth = compute_isolated_depth_from_block(base_block); if (!fname) { fname = rb_source_location(&line); From 481f994449f35ce4050757561e89a776903ee425 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 19:54:31 +0900 Subject: [PATCH 0673/2435] [Feature #19630] Limit the versions with the old behavior It is already declared as: > This behavior is slated to be removed in Ruby 4.0 --- spec/ruby/core/io/binread_spec.rb | 2 +- spec/ruby/core/io/foreach_spec.rb | 46 ++++++------ spec/ruby/core/io/read_spec.rb | 110 ++++++++++++++-------------- spec/ruby/core/io/readlines_spec.rb | 62 ++++++++-------- spec/ruby/core/io/write_spec.rb | 2 +- spec/ruby/core/kernel/open_spec.rb | 94 ++++++++++++------------ 6 files changed, 162 insertions(+), 154 deletions(-) diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 418e89213b8daf..9e36b84da97350 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -45,7 +45,7 @@ -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do cmd = "|echo ok" diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index c361d27879214d..6abe8901bac7a0 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -14,33 +14,35 @@ IO.foreach(@name) { $..should == @count += 1 } end - describe "when the filename starts with |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end + ruby_version_is ""..."4.0" do + describe "when the filename starts with |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach(cmd) { |l| ScratchPad << l } + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach(cmd) { |l| ScratchPad << l } + end + ScratchPad.recorded.should == ["hello\n", "line2\n"] end - ScratchPad.recorded.should == ["hello\n", "line2\n"] - end - platform_is_not :windows do - it "gets data from a fork when passed -" do - parent_pid = $$ + platform_is_not :windows do + it "gets data from a fork when passed -" do + parent_pid = $$ - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|-") { |l| ScratchPad << l } - end + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach("|-") { |l| ScratchPad << l } + end - if $$ == parent_pid - ScratchPad.recorded.should == ["hello\n", "from a fork\n"] - else # child - puts "hello" - puts "from a fork" - exit! + if $$ == parent_pid + ScratchPad.recorded.should == ["hello\n", "from a fork\n"] + else # child + puts "hello" + puts "from a fork" + exit! + end end end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 567daa55dfc464..988ec2ce30df25 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -168,76 +168,78 @@ end end -describe "IO.read from a pipe" do - it "runs the rest as a subprocess and returns the standard output" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end - - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd).should == "hello\n" - end - end - - platform_is_not :windows do - it "opens a pipe to a fork if the rest is -" do - str = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - str = IO.read("|-") +ruby_version_is ""..."4.0" do + describe "IO.read from a pipe" do + it "runs the rest as a subprocess and returns the standard output" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" end - if str # parent - str.should == "hello from child\n" - else #child - puts "hello from child" - exit! + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd).should == "hello\n" end end - end - it "reads only the specified number of bytes requested" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end + platform_is_not :windows do + it "opens a pipe to a fork if the rest is -" do + str = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + str = IO.read("|-") + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd, 1).should == "h" + if str # parent + str.should == "hello from child\n" + else #child + puts "hello from child" + exit! + end + end end - end - platform_is_not :windows do - it "raises Errno::ESPIPE if passed an offset" do - -> { - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|sh -c 'echo hello'", 1, 1) - end - }.should raise_error(Errno::ESPIPE) + it "reads only the specified number of bytes requested" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd, 1).should == "h" + end end - end - quarantine! do # The process tried to write to a nonexistent pipe. - platform_is :windows do - # TODO: It should raise Errno::ESPIPE on Windows as well - # once https://bugs.ruby-lang.org/issues/12230 is fixed. - it "raises Errno::EINVAL if passed an offset" do + platform_is_not :windows do + it "raises Errno::ESPIPE if passed an offset" do -> { suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|cmd.exe /C echo hello", 1, 1) + IO.read("|sh -c 'echo hello'", 1, 1) end - }.should raise_error(Errno::EINVAL) + }.should raise_error(Errno::ESPIPE) end end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.read(cmd) - }.should complain(/IO process creation with a leading '\|'/) + quarantine! do # The process tried to write to a nonexistent pipe. + platform_is :windows do + # TODO: It should raise Errno::ESPIPE on Windows as well + # once https://bugs.ruby-lang.org/issues/12230 is fixed. + it "raises Errno::EINVAL if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|cmd.exe /C echo hello", 1, 1) + end + }.should raise_error(Errno::EINVAL) + end + end + end + + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation" do + cmd = "|echo ok" + -> { + IO.read(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end end diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index 3a6ff3d0f34230..b4770775d1e813 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -174,45 +174,47 @@ $_.should == "test" end - describe "when passed a string that starts with a |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end - - lines = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines(cmd) - end - lines.should == ["hello\n", "line2\n"] - end + ruby_version_is ""..."4.0" do + describe "when passed a string that starts with a |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - platform_is_not :windows do - it "gets data from a fork when passed -" do lines = nil suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines("|-") + lines = IO.readlines(cmd) end + lines.should == ["hello\n", "line2\n"] + end - if lines # parent - lines.should == ["hello\n", "from a fork\n"] - else - puts "hello" - puts "from a fork" - exit! + platform_is_not :windows do + it "gets data from a fork when passed -" do + lines = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + lines = IO.readlines("|-") + end + + if lines # parent + lines.should == ["hello\n", "from a fork\n"] + else + puts "hello" + puts "from a fork" + exit! + end end end end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.readlines(cmd) - }.should complain(/IO process creation with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.readlines(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 4a26f8dbaf9fec..e58100f8467d9c 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -220,7 +220,7 @@ end end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do -> { diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 6d00af395d9d3c..b967d5044ba92b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -27,64 +27,66 @@ open(@name, "r") { |f| f.gets }.should == @content end - platform_is_not :windows, :wasi do - it "opens an io when path starts with a pipe" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date") + ruby_version_is ""..."4.0" do + platform_is_not :windows, :wasi do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close - end - end - it "opens an io when called with a block" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @output = open("|date") { |f| f.read } + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date") { |f| f.read } + end + @output.should_not == '' end - @output.should_not == '' - end - it "opens an io for writing" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - -> { - bytes = open("|cat", "w") { |io| io.write(".") } - bytes.should == 1 - }.should output_to_fd(".") + it "opens an io for writing" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + -> { + bytes = open("|cat", "w") { |io| io.write(".") } + bytes.should == 1 + }.should output_to_fd(".") + end end end - end - platform_is :windows do - it "opens an io when path starts with a pipe" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date /t") + platform_is :windows do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date /t") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close - end - end - it "opens an io when called with a block" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @output = open("|date /t") { |f| f.read } + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date /t") { |f| f.read } + end + @output.should_not == '' end - @output.should_not == '' end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - open(cmd) { |f| f.read } - }.should complain(/Kernel#open with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + open(cmd) { |f| f.read } + }.should complain(/Kernel#open with a leading '\|'/) + end end end From 57f76f62d5e12766465f11ebb0d0b0b0d4d549ce Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 30 Oct 2025 13:30:08 -0400 Subject: [PATCH 0674/2435] ZJIT: Fix incorrect self.class.respond_to? folding (#15001) Right now we have a subtle type system bug around `types::Class`. Until that is resolved, stop marking `Kernel#class` as returning `types::Class`, which fixes Rubocop. Re: https://github.com/Shopify/ruby/issues/850 --- zjit/src/cruby_methods.rs | 4 +- zjit/src/hir/opt_tests.rs | 86 ++++++++++++++++++++++++++++++++++++++- zjit/src/hir/tests.rs | 4 +- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 12d226ce51bea2..bd3409ff07ce5c 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -231,7 +231,9 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); - annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + // TODO(max): Annotate rb_mKernel#class as returning types::Class. Right now there is a subtle + // type system bug that causes an issue if we make it return types::Class. + annotate_builtin!(rb_mKernel, "class", types::HeapObject, leaf); annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c7764bd290ca57..d697065da99848 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2358,7 +2358,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_iseq_optimized_send_count - v26:Class = InvokeBuiltin leaf _bi20, v21 + v26:HeapObject = InvokeBuiltin leaf _bi20, v21 CheckInterrupts Return v26 "); @@ -7050,4 +7050,88 @@ mod hir_opt_tests { Return v19 "); } + + #[test] + fn test_fold_self_class_respond_to_true() { + eval(r#" + class C + class << self + attr_accessor :_lex_actions + private :_lex_actions, :_lex_actions= + end + self._lex_actions = [1, 2, 3] + def initialize + if self.class.respond_to?(:_lex_actions, true) + :CORRECT + else + :oh_no_wrong + end + end + end + C.new # warm up + TEST = C.instance_method(:initialize) + "#); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn initialize@:9: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v40:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v43:HeapObject = InvokeBuiltin leaf _bi20, v40 + v12:StaticSymbol[:_lex_actions] = Const Value(VALUE(0x1038)) + v13:TrueClass = Const Value(true) + PatchPoint MethodRedefined(Class@0x1040, respond_to?@0x1048, cme:0x1050) + PatchPoint NoSingletonClass(Class@0x1040) + v47:ModuleSubclass[class_exact*:Class@VALUE(0x1040)] = GuardType v43, ModuleSubclass[class_exact*:Class@VALUE(0x1040)] + PatchPoint MethodRedefined(Class@0x1040, _lex_actions@0x1078, cme:0x1080) + PatchPoint NoSingletonClass(Class@0x1040) + v51:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + v22:StaticSymbol[:CORRECT] = Const Value(VALUE(0x10a8)) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_self_class_name() { + eval(r#" + class C; end + def test(o) = o.class.name + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v27:HeapObject = InvokeBuiltin leaf _bi20, v24 + PatchPoint MethodRedefined(Class@0x1038, name@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Class@0x1038) + v31:ModuleSubclass[class_exact*:Class@VALUE(0x1038)] = GuardType v27, ModuleSubclass[class_exact*:Class@VALUE(0x1038)] + IncrCounter inline_cfunc_optimized_send_count + v33:StringExact|NilClass = CCall name@0x1070, v31 + CheckInterrupts + Return v33 + "); + } } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index df14ba5a7c2026..32c71dc5d354f4 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2712,9 +2712,9 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:Class = InvokeBuiltin leaf _bi20, v6 + v11:HeapObject = InvokeBuiltin leaf _bi20, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:Class): + bb3(v13:BasicObject, v14:HeapObject): CheckInterrupts Return v14 "); From 9b1894690fca424afa7d411748a315d880666376 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 30 Oct 2025 10:44:35 -0700 Subject: [PATCH 0675/2435] zjit-ubuntu.yml: Remove an extra empty line --- .github/workflows/zjit-ubuntu.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 5163076be5d98a..220f0ae2028b59 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,7 +114,6 @@ jobs: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} - - uses: ./.github/actions/setup/directories with: srcdir: src From 6707945f0c065d47a4a9e8fd1627345bfcb85c10 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 30 Oct 2025 15:15:09 -0400 Subject: [PATCH 0676/2435] ZJIT: Split out optimized method types in stats (#15002) We can see send/block call/struct aref/... e.g. on lobsters: ``` Top-9 not optimized method types for send_without_block (100.0% of total 3,133,812): iseq: 2,004,557 (64.0%) optimized_struct_aref: 496,232 (15.8%) alias: 268,579 ( 8.6%) optimized_call: 224,883 ( 7.2%) optimized_send: 120,531 ( 3.8%) bmethod: 12,011 ( 0.4%) null: 4,636 ( 0.1%) optimized_block_call: 1,930 ( 0.1%) cfunc: 453 ( 0.0%) ``` railsbench: ``` Top-8 not optimized method types for send_without_block (100.0% of total 5,735,608): iseq: 2,854,551 (49.8%) optimized_struct_aref: 871,459 (15.2%) optimized_call: 862,185 (15.0%) alias: 588,486 (10.3%) optimized_send: 482,171 ( 8.4%) null: 39,942 ( 0.7%) bmethod: 36,784 ( 0.6%) cfunc: 30 ( 0.0%) ``` shipit: ``` Top-10 not optimized method types for send_without_block (100.0% of total 4,844,304): iseq: 2,881,206 (59.5%) optimized_struct_aref: 1,158,935 (23.9%) optimized_call: 472,898 ( 9.8%) alias: 208,010 ( 4.3%) optimized_send: 55,479 ( 1.1%) null: 47,273 ( 1.0%) bmethod: 12,608 ( 0.3%) optimized_block_call: 7,860 ( 0.2%) cfunc: 31 ( 0.0%) optimized_struct_aset: 4 ( 0.0%) ``` --- zjit/src/codegen.rs | 5 ++++- zjit/src/hir.rs | 27 +++++++++++++++++++++++++++ zjit/src/stats.rs | 23 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index db9d6a14e3d1b5..6e8038335fc890 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -15,7 +15,7 @@ use crate::invariants::{ }; use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; -use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; +use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP}; @@ -1741,6 +1741,9 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso SendWithoutBlockNotOptimizedMethodType(method_type) => { gen_incr_counter(asm, send_without_block_fallback_counter_for_method_type(method_type)); } + SendWithoutBlockNotOptimizedOptimizedMethodType(method_type) => { + gen_incr_counter(asm, send_without_block_fallback_counter_for_optimized_method_type(method_type)); + } SendNotOptimizedMethodType(method_type) => { gen_incr_counter(asm, send_fallback_counter_for_method_type(method_type)); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b284ae6c118d86..ea8dacd40b990f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -512,6 +512,28 @@ impl From for MethodType { } } +#[derive(Debug, Clone, Copy)] +pub enum OptimizedMethodType { + Send, + Call, + BlockCall, + StructAref, + StructAset, +} + +impl From for OptimizedMethodType { + fn from(value: u32) -> Self { + match value { + OPTIMIZED_METHOD_TYPE_SEND => OptimizedMethodType::Send, + OPTIMIZED_METHOD_TYPE_CALL => OptimizedMethodType::Call, + OPTIMIZED_METHOD_TYPE_BLOCK_CALL => OptimizedMethodType::BlockCall, + OPTIMIZED_METHOD_TYPE_STRUCT_AREF => OptimizedMethodType::StructAref, + OPTIMIZED_METHOD_TYPE_STRUCT_ASET => OptimizedMethodType::StructAset, + _ => unreachable!("unknown send_without_block optimized method type: {}", value), + } + } +} + impl std::fmt::Display for SideExitReason { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { @@ -539,6 +561,7 @@ pub enum SendFallbackReason { SendWithoutBlockCfuncNotVariadic, SendWithoutBlockCfuncArrayVariadic, SendWithoutBlockNotOptimizedMethodType(MethodType), + SendWithoutBlockNotOptimizedOptimizedMethodType(OptimizedMethodType), SendWithoutBlockDirectTooManyArgs, SendPolymorphic, SendNoProfiles, @@ -2335,6 +2358,10 @@ impl Function { } self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); self.make_equal_to(insn_id, val); + } else if def_type == VM_METHOD_TYPE_OPTIMIZED { + let opt_type = unsafe { get_cme_def_body_optimized_type(cme) }; + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedOptimizedMethodType(OptimizedMethodType::from(opt_type))); + self.push_insn_id(block, insn_id); continue; } else { self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 9965526b7607cb..ad027ef5938fc3 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -167,6 +167,7 @@ make_counters! { send_fallback_send_without_block_cfunc_not_variadic, send_fallback_send_without_block_cfunc_array_variadic, send_fallback_send_without_block_not_optimized_method_type, + send_fallback_send_without_block_not_optimized_optimized_method_type, send_fallback_send_without_block_direct_too_many_args, send_fallback_send_polymorphic, send_fallback_send_no_profiles, @@ -227,6 +228,13 @@ make_counters! { unspecialized_send_without_block_def_type_refined, unspecialized_send_without_block_def_type_null, + // Method call optimized_type related to send without block fallback to dynamic dispatch + unspecialized_send_without_block_def_type_optimized_send, + unspecialized_send_without_block_def_type_optimized_call, + unspecialized_send_without_block_def_type_optimized_block_call, + unspecialized_send_without_block_def_type_optimized_struct_aref, + unspecialized_send_without_block_def_type_optimized_struct_aset, + // Method call def_type related to send fallback to dynamic dispatch unspecialized_send_def_type_iseq, unspecialized_send_def_type_cfunc, @@ -388,6 +396,8 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockCfuncNotVariadic => send_fallback_send_without_block_cfunc_not_variadic, SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, + SendWithoutBlockNotOptimizedOptimizedMethodType(_) + => send_fallback_send_without_block_not_optimized_optimized_method_type, SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, SendPolymorphic => send_fallback_send_polymorphic, SendNoProfiles => send_fallback_send_no_profiles, @@ -419,6 +429,19 @@ pub fn send_without_block_fallback_counter_for_method_type(method_type: crate::h } } +pub fn send_without_block_fallback_counter_for_optimized_method_type(method_type: crate::hir::OptimizedMethodType) -> Counter { + use crate::hir::OptimizedMethodType::*; + use crate::stats::Counter::*; + + match method_type { + Send => unspecialized_send_without_block_def_type_optimized_send, + Call => unspecialized_send_without_block_def_type_optimized_call, + BlockCall => unspecialized_send_without_block_def_type_optimized_block_call, + StructAref => unspecialized_send_without_block_def_type_optimized_struct_aref, + StructAset => unspecialized_send_without_block_def_type_optimized_struct_aset, + } +} + pub fn send_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter { use crate::hir::MethodType::*; use crate::stats::Counter::*; From 0531fa4d6fea100f69f0bac9e03973fe49ecd570 Mon Sep 17 00:00:00 2001 From: Andre Muta Date: Thu, 30 Oct 2025 00:55:40 -0300 Subject: [PATCH 0677/2435] mn timer thread: force wakeups for timeouts --- thread_pthread.c | 29 +++++++++++++++++------------ thread_pthread_mn.c | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 323659b64945b8..64912a58dacfff 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -2914,24 +2914,29 @@ timer_thread_set_timeout(rb_vm_t *vm) } ractor_sched_unlock(vm, NULL); - if (vm->ractor.sched.timeslice_wait_inf) { - rb_native_mutex_lock(&timer_th.waiting_lock); - { - struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); - rb_thread_t *th = thread_sched_waiting_thread(w); + // Always check waiting threads to find minimum timeout + // even when scheduler has work (grq_cnt > 0) + rb_native_mutex_lock(&timer_th.waiting_lock); + { + struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); + rb_thread_t *th = thread_sched_waiting_thread(w); - if (th && (th->sched.waiting_reason.flags & thread_sched_waiting_timeout)) { - rb_hrtime_t now = rb_hrtime_now(); - rb_hrtime_t hrrel = rb_hrtime_sub(th->sched.waiting_reason.data.timeout, now); + if (th && (th->sched.waiting_reason.flags & thread_sched_waiting_timeout)) { + rb_hrtime_t now = rb_hrtime_now(); + rb_hrtime_t hrrel = rb_hrtime_sub(th->sched.waiting_reason.data.timeout, now); - RUBY_DEBUG_LOG("th:%u now:%lu rel:%lu", rb_th_serial(th), (unsigned long)now, (unsigned long)hrrel); + RUBY_DEBUG_LOG("th:%u now:%lu rel:%lu", rb_th_serial(th), (unsigned long)now, (unsigned long)hrrel); - // TODO: overflow? - timeout = (int)((hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC); // ms + // TODO: overflow? + int thread_timeout = (int)((hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC); // ms + + // Use minimum of scheduler timeout and thread sleep timeout + if (timeout < 0 || thread_timeout < timeout) { + timeout = thread_timeout; } } - rb_native_mutex_unlock(&timer_th.waiting_lock); } + rb_native_mutex_unlock(&timer_th.waiting_lock); RUBY_DEBUG_LOG("timeout:%d inf:%d", timeout, (int)vm->ractor.sched.timeslice_wait_inf); diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 0598b8d295447a..f3451eb3531328 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -837,8 +837,8 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting verify_waiting_list(); - // update timeout seconds - timer_thread_wakeup(); + // update timeout seconds; force wake so timer thread notices short deadlines + timer_thread_wakeup_force(); } } else { From 34b0ac68b315a4ab485ce40bd88d5dc1f93b01ba Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 30 Oct 2025 16:47:22 -0400 Subject: [PATCH 0678/2435] ZJIT: Prevent specialization of splats instead of side-exiting (#15005) --- zjit/src/hir.rs | 17 +++++++++++++++-- zjit/src/hir/tests.rs | 8 ++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ea8dacd40b990f..d82d5837fe1e64 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2253,6 +2253,14 @@ impl Function { (recv_type.class(), Some(recv_type)) }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site + + // If the call site info indicates that the `Function` has `VM_CALL_ARGS_SPLAT` set, then + // do not optimize into a `SendWithoutBlockDirect`. + let flags = unsafe { rb_vm_ci_flag(ci) }; + if unspecializable_call_type(flags) { + self.push_insn_id(block, insn_id); continue; + } + let mid = unsafe { vm_ci_mid(ci) }; // Do method lookup let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; @@ -2676,7 +2684,7 @@ impl Function { // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding - if ci_flags & VM_CALL_ARGS_BLOCKARG != 0 { + if unspecializable_call_type(ci_flags) { return Err(()); } @@ -4149,12 +4157,17 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { /// If we can't handle the type of send (yet), bail out. fn unhandled_call_type(flags: u32) -> Result<(), CallType> { - if (flags & VM_CALL_ARGS_SPLAT) != 0 { return Err(CallType::Splat); } if (flags & VM_CALL_KWARG) != 0 { return Err(CallType::Kwarg); } if (flags & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } Ok(()) } +/// If a given call uses splatting or block arguments, then we won't specialize. +fn unspecializable_call_type(flags: u32) -> bool { + ((flags & VM_CALL_ARGS_SPLAT) != 0) || + ((flags & VM_CALL_ARGS_BLOCKARG) != 0) +} + /// We have IseqPayload, which keeps track of HIR Types in the interpreter, but this is not useful /// or correct to query from inside the optimizer. Instead, ProfileOracle provides an API to look /// up profiled type information by HIR InsnId at a given ISEQ instruction. diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 32c71dc5d354f4..c58e14ad4e0735 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1527,7 +1527,9 @@ pub mod hir_build_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v14:ArrayExact = ToArray v9 - SideExit UnhandledCallType(Splat) + v16:BasicObject = SendWithoutBlock v8, :foo, v14 + CheckInterrupts + Return v16 "); } @@ -1753,7 +1755,9 @@ pub mod hir_build_tests { v14:ArrayExact = ToNewArray v9 v15:Fixnum[1] = Const Value(1) ArrayPush v14, v15 - SideExit UnhandledCallType(Splat) + v19:BasicObject = SendWithoutBlock v8, :foo, v14 + CheckInterrupts + Return v19 "); } From 2afcdc6902971c3662f5d032ee1a25136e07a465 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 30 Oct 2025 17:14:36 -0400 Subject: [PATCH 0679/2435] Change load factor of concur. set from 0.5 to 0.75 (#15007) Before, the 50% load factor was not working correctly with the new capacity calculation on resize and too many resizes were seen. Before this change ------------------ Example: old_capacity = 32 old_size = 16 deleted_entries = 2 (almost all live) That means we have: expected_size = 14 We'll see that 64 > 14 * 4 We'll end up using 32 as the new capacity (same as old) even though that only leaves us two elements free before we'd have to rebuild again. Co-authored-by: John Hawthorn --- concurrent_set.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/concurrent_set.c b/concurrent_set.c index dab7ef2180b605..e48d9a46e89d3f 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -118,6 +118,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) // GC may also happen between now and the set being rebuilt int expected_size = rbimpl_atomic_load(&old_set->size, RBIMPL_ATOMIC_RELAXED) - old_set->deleted_entries; + // NOTE: new capacity must make sense with load factor, don't change one without checking the other. struct concurrent_set_entry *old_entries = old_set->entries; int old_capacity = old_set->capacity; int new_capacity = old_capacity * 2; @@ -287,7 +288,10 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED); - if (UNLIKELY(prev_size > set->capacity / 2)) { + // Load_factor reached at 75% full. ex: prev_size: 32, capacity: 64, load_factor: 50%. + bool load_factor_reached = (uint64_t)(prev_size * 4) >= (uint64_t)(set->capacity * 3); + + if (UNLIKELY(load_factor_reached)) { concurrent_set_try_resize(set_obj, set_obj_ptr); goto retry; From 68dd1abdec5c222f3a59401cb0ffa07454dcfbe3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 30 Oct 2025 15:02:05 -0700 Subject: [PATCH 0680/2435] ZJIT: Run ruby-bench as a test suite (#15003) --- .github/workflows/zjit-ubuntu.yml | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 220f0ae2028b59..a358c284b169ce 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -200,6 +200,58 @@ jobs: working-directory: if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + # Separated from `make` job to avoid making it a required status check for now + ruby-bench: + strategy: + matrix: + include: + # Test --call-threshold=2 with 2 iterations in total + - ruby_opts: '--zjit-call-threshold=2' + bench_opts: '--warmup=1 --bench=1' + configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow + + runs-on: ubuntu-24.04 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') + )}} + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: ./.github/actions/setup/ubuntu + + - uses: ./.github/actions/setup/directories + with: + srcdir: src + builddir: build + makeup: true + + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix="$(pwd)/install" ${{ matrix.configure }} + + - run: make install + + - name: Checkout ruby-bench + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: ruby/ruby-bench + path: ruby-bench + + - name: Run ruby-bench + run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + working-directory: ruby-bench + + - uses: ./.github/actions/slack + with: + label: ruby-bench ${{ matrix.bench_opts }} ${{ matrix.ruby_opts }} + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} + defaults: run: working-directory: build From 5b71b103b6e49bbe8b1a95112e393da552cf803e Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 28 Oct 2025 21:23:02 -0400 Subject: [PATCH 0681/2435] ZJIT: Unsupported call feature accounting, and new `send_fallback_fancy_call_feature` In cases we fall back when the callee has an unsupported signature, it was a little inaccurate to use `send_fallback_send_not_optimized_method_type`. We do support the method type in other situations. Add a new `send_fallback_fancy_call_feature` for these situations. Also, `send_fallback_bmethod_non_iseq_proc` so we can stop using `not_optimized_method_type` completely for bmethods. Add accompanying `fancy_arg_pass_*` counters. These don't sum to the number of unoptimized calls that run, but establishes the level of support the optimizer provides for a given workload. --- zjit.rb | 5 ++++ zjit/src/hir.rs | 44 ++++++++++++++++++------------ zjit/src/hir/opt_tests.rs | 57 +++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 14 ++++++++++ 4 files changed, 103 insertions(+), 17 deletions(-) diff --git a/zjit.rb b/zjit.rb index 39e353f32741ca..72a6e586513b56 100644 --- a/zjit.rb +++ b/zjit.rb @@ -164,6 +164,11 @@ def stats_string print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) + # Show most popular unsupported call features. Because each call can + # use multiple fancy features, a decrease in this number does not + # necessarily mean an increase in number of optimized calls. + print_counters_with_prefix(prefix: 'fancy_arg_pass_', prompt: 'popular unsupported argument-parameter features', buf:, stats:, limit: 10) + # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d82d5837fe1e64..013322537eee76 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -568,6 +568,11 @@ pub enum SendFallbackReason { SendNotOptimizedMethodType(MethodType), CCallWithFrameTooManyArgs, ObjToStringNotString, + /// The Proc object for a BMETHOD is not defined by an ISEQ. (See `enum rb_block_type`.) + BmethodNonIseqProc, + /// The call has at least one feature on the caller or callee side that the optimizer does not + /// support. + FancyFeatureUse, /// Initial fallback reason for every instruction, which should be mutated to /// a more actionable reason when an attempt to specialize the instruction fails. NotOptimizedInstruction(ruby_vminsn_type), @@ -1384,14 +1389,22 @@ pub enum ValidationError { MiscValidationError(InsnId, String), } -fn can_direct_send(iseq: *const rb_iseq_t) -> bool { - if unsafe { rb_get_iseq_flags_has_rest(iseq) } { false } - else if unsafe { rb_get_iseq_flags_has_opt(iseq) } { false } - else if unsafe { rb_get_iseq_flags_has_kw(iseq) } { false } - else if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { false } - else if unsafe { rb_get_iseq_flags_has_block(iseq) } { false } - else if unsafe { rb_get_iseq_flags_forwardable(iseq) } { false } - else { true } +fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t) -> bool { + let mut can_send = true; + let mut count_failure = |counter| { + can_send = false; + function.push_insn(block, Insn::IncrCounter(counter)); + }; + + use Counter::*; + if unsafe { rb_get_iseq_flags_has_rest(iseq) } { count_failure(fancy_arg_pass_param_rest) } + if unsafe { rb_get_iseq_flags_has_opt(iseq) } { count_failure(fancy_arg_pass_param_opt) } + if unsafe { rb_get_iseq_flags_has_kw(iseq) } { count_failure(fancy_arg_pass_param_kw) } + if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { count_failure(fancy_arg_pass_param_kwrest) } + if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(fancy_arg_pass_param_block) } + if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(fancy_arg_pass_param_forwardable) } + + can_send } /// A [`Function`], which is analogous to a Ruby ISeq, is a control-flow graph of [`Block`]s @@ -2277,8 +2290,8 @@ impl Function { // Only specialize positional-positional calls // TODO(max): Handle other kinds of parameter passing let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; - if !can_direct_send(iseq) { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Iseq)); + if !can_direct_send(self, block, iseq) { + self.set_dynamic_send_reason(insn_id, FancyFeatureUse); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -2297,21 +2310,18 @@ impl Function { // Target ISEQ bmethods. Can't handle for example, `define_method(:foo, &:foo)` // which makes a `block_type_symbol` bmethod. if proc_block.type_ != block_type_iseq { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); + self.set_dynamic_send_reason(insn_id, BmethodNonIseqProc); self.push_insn_id(block, insn_id); continue; } let capture = unsafe { proc_block.as_.captured.as_ref() }; let iseq = unsafe { *capture.code.iseq.as_ref() }; - if !can_direct_send(iseq) { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); + if !can_direct_send(self, block, iseq) { + self.set_dynamic_send_reason(insn_id, FancyFeatureUse); self.push_insn_id(block, insn_id); continue; } // Can't pass a block to a block for now - if (unsafe { rb_vm_ci_flag(ci) } & VM_CALL_ARGS_BLOCKARG) != 0 { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Bmethod)); - self.push_insn_id(block, insn_id); continue; - } + assert!((unsafe { rb_vm_ci_flag(ci) } & VM_CALL_ARGS_BLOCKARG) == 0, "SendWithoutBlock but has a block arg"); // Patch points: // Check for "defined with an un-shareable Proc in a different Ractor" diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index d697065da99848..f29af66bde670f 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2416,6 +2416,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) + IncrCounter fancy_arg_pass_param_opt v12:BasicObject = SendWithoutBlock v6, :foo, v10 CheckInterrupts Return v12 @@ -2499,6 +2500,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) + IncrCounter fancy_arg_pass_param_rest v12:BasicObject = SendWithoutBlock v6, :foo, v10 CheckInterrupts Return v12 @@ -2833,6 +2835,9 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) + IncrCounter fancy_arg_pass_param_opt + IncrCounter fancy_arg_pass_param_kw + IncrCounter fancy_arg_pass_param_block v18:BasicObject = SendWithoutBlock v43, :initialize CheckInterrupts CheckInterrupts @@ -7021,6 +7026,58 @@ mod hir_opt_tests { "); } + #[test] + fn counting_fancy_feature_use_for_fallback() { + eval(" + define_method(:fancy) { |_a, *_b, kw: 100, **kw_rest, &block| } + def test = fancy(1) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + IncrCounter fancy_arg_pass_param_rest + IncrCounter fancy_arg_pass_param_kw + IncrCounter fancy_arg_pass_param_kwrest + IncrCounter fancy_arg_pass_param_block + v12:BasicObject = SendWithoutBlock v6, :fancy, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn call_method_forwardable_param() { + eval(" + def forwardable(...) = itself(...) + def call_forwardable = forwardable + call_forwardable + "); + assert_snapshot!(hir_string("call_forwardable"), @r" + fn call_forwardable@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + IncrCounter fancy_arg_pass_param_forwardable + v11:BasicObject = SendWithoutBlock v6, :forwardable + CheckInterrupts + Return v11 + "); + } + #[test] fn test_elide_string_length() { eval(r#" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index ad027ef5938fc3..17ae27ac016b72 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -173,6 +173,10 @@ make_counters! { send_fallback_send_no_profiles, send_fallback_send_not_optimized_method_type, send_fallback_ccall_with_frame_too_many_args, + // The call has at least one feature on the caller or callee side + // that the optimizer does not support. + send_fallback_fancy_call_feature, + send_fallback_bmethod_non_iseq_proc, send_fallback_obj_to_string_not_string, send_fallback_not_optimized_instruction, } @@ -250,6 +254,14 @@ make_counters! { unspecialized_send_def_type_refined, unspecialized_send_def_type_null, + // Unsupported parameter features + fancy_arg_pass_param_rest, + fancy_arg_pass_param_opt, + fancy_arg_pass_param_kw, + fancy_arg_pass_param_kwrest, + fancy_arg_pass_param_block, + fancy_arg_pass_param_forwardable, + // Writes to the VM frame vm_write_pc_count, vm_write_sp_count, @@ -401,6 +413,8 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, SendPolymorphic => send_fallback_send_polymorphic, SendNoProfiles => send_fallback_send_no_profiles, + FancyFeatureUse => send_fallback_fancy_call_feature, + BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, ObjToStringNotString => send_fallback_obj_to_string_not_string, From 69ec1481e0ffa394ecfd4cecf67dc0a1cfa8610b Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 28 Oct 2025 21:53:16 -0400 Subject: [PATCH 0682/2435] ZJIT: Count unsupported fancy caller side features These count caller-side features we don't support. But because we side exit when we see them through unhandled_call_type(), these new counters currently don't trigger. --- zjit/src/hir.rs | 19 +++++++++++++++++-- zjit/src/stats.rs | 10 ++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 013322537eee76..574f5e40ea8635 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -9,7 +9,7 @@ use crate::{ cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState }; use std::{ - cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter + cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter }; use crate::hir_type::{Type, types}; use crate::bitset::BitSet; @@ -2146,6 +2146,18 @@ impl Function { self.likely_is_fixnum(left, left_profiled_type) && self.likely_is_fixnum(right, right_profiled_type) } + fn count_fancy_call_features(&mut self, block: BlockId, ci_flags: c_uint) { + use Counter::*; + if 0 != ci_flags & VM_CALL_ARGS_SPLAT { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_splat)); } + if 0 != ci_flags & VM_CALL_ARGS_BLOCKARG { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_blockarg)); } + if 0 != ci_flags & VM_CALL_KWARG { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_kwarg)); } + if 0 != ci_flags & VM_CALL_KW_SPLAT { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_kw_splat)); } + if 0 != ci_flags & VM_CALL_TAILCALL { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_tailcall)); } + if 0 != ci_flags & VM_CALL_SUPER { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_super)); } + if 0 != ci_flags & VM_CALL_ZSUPER { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_zsuper)); } + if 0 != ci_flags & VM_CALL_FORWARDING { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_forwarding)); } + } + fn try_rewrite_fixnum_op(&mut self, block: BlockId, orig_insn_id: InsnId, f: &dyn Fn(InsnId, InsnId) -> Insn, bop: u32, left: InsnId, right: InsnId, state: InsnId) { if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, INTEGER_REDEFINED_OP_FLAG) } { // If the basic operation is already redefined, we cannot optimize it. @@ -2797,6 +2809,7 @@ impl Function { // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { + fun.count_fancy_call_features(block, ci_flags); return Err(()); } @@ -2867,7 +2880,9 @@ impl Function { // The method gets a pointer to the first argument // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; - if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { + if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { + fun.count_fancy_call_features(block, ci_flags); + } else { fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 17ae27ac016b72..e69f1d95884564 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -262,6 +262,16 @@ make_counters! { fancy_arg_pass_param_block, fancy_arg_pass_param_forwardable, + // Unsupported caller side features + fancy_arg_pass_caller_splat, + fancy_arg_pass_caller_blockarg, + fancy_arg_pass_caller_kwarg, + fancy_arg_pass_caller_kw_splat, + fancy_arg_pass_caller_tailcall, + fancy_arg_pass_caller_super, + fancy_arg_pass_caller_zsuper, + fancy_arg_pass_caller_forwarding, + // Writes to the VM frame vm_write_pc_count, vm_write_sp_count, From c54faf29d32befe973476bde69f8b0b25b0f1866 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 30 Oct 2025 15:20:27 -0700 Subject: [PATCH 0683/2435] release.yml: Use workflow_dispatch for docker-images instead of repository_dispatch. Only that workflow reacts to repository_dispatch, so just using workflow_dispatch should be enough. We want to use workflow_dispatch for manual triggers, and I don't want to maintain two different dispatch methods in the workflow. --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aeb116bf453032..d14904d4191ff9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,8 +72,8 @@ jobs: -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/ruby/docker-images/dispatches \ - -d '{"event_type": "build", "client_payload": {"ruby_version": "${{ env.RUBY_VERSION }}"}}' + https://api.github.com/repos/ruby/docker-images/actions/workflows/build.yml/dispatches \ + -d '{"ref": "master", "inputs": {"ruby_version": "${{ env.RUBY_VERSION }}"}}' - name: Build snapcraft packages run: | From 0268c86b22385c354e063d19c753065ca09c9180 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 30 Oct 2025 16:15:10 -0400 Subject: [PATCH 0684/2435] ZJIT: Inline struct aref --- zjit/src/codegen.rs | 7 ++++ zjit/src/cruby.rs | 8 ++++ zjit/src/hir.rs | 60 +++++++++++++++++++++++++- zjit/src/hir/opt_tests.rs | 88 +++++++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 6 +++ 5 files changed, 167 insertions(+), 2 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6e8038335fc890..5faa1bbcc3f81a 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -448,6 +448,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)), Insn::LoadPC => gen_load_pc(asm), Insn::LoadSelf => gen_load_self(), + &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), &Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index), &Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), @@ -981,6 +982,12 @@ fn gen_load_self() -> Opnd { Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF) } +fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd { + asm_comment!(asm, "Load field id={} offset={}", id.contents_lossy(), offset); + let recv = asm.load(recv); + asm.load(Opnd::mem(64, recv, offset)) +} + fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd { // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index d4e4079b5c3c9b..89488fd2559e1c 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -584,6 +584,13 @@ impl VALUE { } } + pub fn struct_embedded_p(self) -> bool { + unsafe { + RB_TYPE_P(self, RUBY_T_STRUCT) && + FL_TEST_RAW(self, VALUE(RSTRUCT_EMBED_LEN_MASK)) != VALUE(0) + } + } + pub fn as_fixnum(self) -> i64 { assert!(self.fixnum_p()); (self.0 as i64) >> 1 @@ -1369,6 +1376,7 @@ pub(crate) mod ids { name: freeze name: minusat content: b"-@" name: aref content: b"[]" + name: _as_heap } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 574f5e40ea8635..269336fb16fb8c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -444,6 +444,10 @@ impl PtrPrintMap { self.map_ptr(id as *const c_void) } + fn map_offset(&self, id: i32) -> *const c_void { + self.map_ptr(id as *const c_void) + } + /// Map shape ID into a pointer for printing fn map_shape(&self, id: ShapeId) -> *const c_void { self.map_ptr(id.0 as *const c_void) @@ -671,6 +675,7 @@ pub enum Insn { LoadPC, /// Load cfp->self LoadSelf, + LoadField { recv: InsnId, id: ID, offset: i32, return_type: Type }, /// Read an instance variable at the given index, embedded in the object LoadIvarEmbedded { self_val: InsnId, id: ID, index: u16 }, /// Read an instance variable at the given index, from the extended table @@ -909,6 +914,7 @@ impl Insn { Insn::IsNil { .. } => false, Insn::LoadPC => false, Insn::LoadSelf => false, + Insn::LoadField { .. } => false, Insn::LoadIvarEmbedded { .. } => false, Insn::LoadIvarExtended { .. } => false, Insn::CCall { elidable, .. } => !elidable, @@ -1170,6 +1176,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy()), Insn::LoadPC => write!(f, "LoadPC"), Insn::LoadSelf => write!(f, "LoadSelf"), + &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), &Insn::LoadIvarEmbedded { self_val, id, index } => write!(f, "LoadIvarEmbedded {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), &Insn::LoadIvarExtended { self_val, id, index } => write!(f, "LoadIvarExtended {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), @@ -1792,6 +1799,7 @@ impl Function { &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, + &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, &LoadIvarEmbedded { self_val, id, index } => LoadIvarEmbedded { self_val: find!(self_val), id, index }, &LoadIvarExtended { self_val, id, index } => LoadIvarExtended { self_val: find!(self_val), id, index }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, @@ -1929,6 +1937,7 @@ impl Function { Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, Insn::LoadSelf => types::BasicObject, + &Insn::LoadField { return_type, .. } => return_type, Insn::LoadIvarEmbedded { .. } => types::BasicObject, Insn::LoadIvarExtended { .. } => types::BasicObject, Insn::GetSpecialSymbol { .. } => types::BasicObject, @@ -2390,8 +2399,52 @@ impl Function { self.make_equal_to(insn_id, val); } else if def_type == VM_METHOD_TYPE_OPTIMIZED { let opt_type = unsafe { get_cme_def_body_optimized_type(cme) }; - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedOptimizedMethodType(OptimizedMethodType::from(opt_type))); - self.push_insn_id(block, insn_id); continue; + if opt_type == OPTIMIZED_METHOD_TYPE_STRUCT_AREF { + if unsafe { vm_ci_argc(ci) } != 0 { + self.push_insn_id(block, insn_id); continue; + } + let index: i32 = unsafe { get_cme_def_body_optimized_index(cme) } + .try_into() + .unwrap(); + // We are going to use an encoding that takes a 4-byte immediate which + // limits the offset to INT32_MAX. + { + let native_index = (index as i64) * (SIZEOF_VALUE as i64); + if native_index > (i32::MAX as i64) { + self.push_insn_id(block, insn_id); continue; + } + } + // Get the profiled type to check if the fields is embedded or heap allocated. + let Some(is_embedded) = self.profiled_type_of_at(recv, frame_state.insn_idx).map(|t| t.flags().is_struct_embedded()) else { + // No (monomorphic) profile info + self.push_insn_id(block, insn_id); continue; + }; + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if klass.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); + } + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + // All structs from the same Struct class should have the same + // length. So if our recv is embedded all runtime + // structs of the same class should be as well, and the same is + // true of the converse. + // + // No need for a GuardShape. + let replacement = if is_embedded { + let offset = RUBY_OFFSET_RSTRUCT_AS_ARY + (SIZEOF_VALUE_I32 * index); + self.push_insn(block, Insn::LoadField { recv, id: mid, offset, return_type: types::BasicObject }) + } else { + let as_heap = self.push_insn(block, Insn::LoadField { recv, id: ID!(_as_heap), offset: RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR, return_type: types::CPtr }); + let offset = SIZEOF_VALUE_I32 * index; + self.push_insn(block, Insn::LoadField { recv: as_heap, id: mid, offset, return_type: types::BasicObject }) + }; + self.make_equal_to(insn_id, replacement); + } else { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedOptimizedMethodType(OptimizedMethodType::from(opt_type))); + self.push_insn_id(block, insn_id); continue; + } } else { self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; @@ -3320,6 +3373,9 @@ impl Function { worklist.push_back(str); worklist.push_back(state); } + &Insn::LoadField { recv, .. } => { + worklist.push_back(recv); + } &Insn::LoadIvarEmbedded { self_val, .. } | &Insn::LoadIvarExtended { self_val, .. } => { worklist.push_back(self_val); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index f29af66bde670f..fd93b4a444fa82 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4935,6 +4935,94 @@ mod hir_opt_tests { "); } + #[test] + fn test_inline_struct_aref_embedded() { + eval(r#" + C = Struct.new(:foo) + def test(o) = o.foo + test C.new + test C.new + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v23:BasicObject = LoadField v22, :foo@0x1038 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_inline_struct_aref_heap() { + eval(r#" + C = Struct.new(*(0..1000).map {|i| :"a#{i}"}, :foo) + def test(o) = o.foo + test C.new + test C.new + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v23:CPtr = LoadField v22, :_as_heap@0x1038 + v24:BasicObject = LoadField v23, :foo@0x1039 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_elide_struct_aref() { + eval(r#" + C = Struct.new(*(0..1000).map {|i| :"a#{i}"}, :foo) + def test(o) + o.foo + 5 + end + test C.new + test C.new + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + #[test] fn test_array_reverse_returns_array() { eval(r#" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index a6c837df5a48ff..08fdf3eb97a090 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -145,6 +145,8 @@ impl Flags { const IS_EMBEDDED: u32 = 1 << 1; /// Object is a T_OBJECT const IS_T_OBJECT: u32 = 1 << 2; + /// Object is a struct with embedded fields + const IS_STRUCT_EMBEDDED: u32 = 1 << 3; pub fn none() -> Self { Self(Self::NONE) } @@ -152,6 +154,7 @@ impl Flags { pub fn is_immediate(self) -> bool { (self.0 & Self::IS_IMMEDIATE) != 0 } pub fn is_embedded(self) -> bool { (self.0 & Self::IS_EMBEDDED) != 0 } pub fn is_t_object(self) -> bool { (self.0 & Self::IS_T_OBJECT) != 0 } + pub fn is_struct_embedded(self) -> bool { (self.0 & Self::IS_STRUCT_EMBEDDED) != 0 } } /// opt_send_without_block/opt_plus/... should store: @@ -214,6 +217,9 @@ impl ProfiledType { if obj.embedded_p() { flags.0 |= Flags::IS_EMBEDDED; } + if obj.struct_embedded_p() { + flags.0 |= Flags::IS_STRUCT_EMBEDDED; + } if unsafe { RB_TYPE_P(obj, RUBY_T_OBJECT) } { flags.0 |= Flags::IS_T_OBJECT; } From f8b4feb7f0de477bebb40966f80dcd9aabd3b06a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 30 Oct 2025 16:46:30 -0400 Subject: [PATCH 0685/2435] ZJIT: Use LoadField for specialized GetIvar --- zjit/src/codegen.rs | 25 ------------------------- zjit/src/hir.rs | 30 ++++++++---------------------- zjit/src/hir/opt_tests.rs | 7 ++++--- 3 files changed, 12 insertions(+), 50 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5faa1bbcc3f81a..d4ed6304cb5577 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -449,8 +449,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadPC => gen_load_pc(asm), Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), - &Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index), - &Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } @@ -988,29 +986,6 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd asm.load(Opnd::mem(64, recv, offset)) } -fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd { - // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h - - asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index); - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.to_usize()) as i32; - let self_val = asm.load(self_val); - let ivar_opnd = Opnd::mem(64, self_val, offs); - asm.load(ivar_opnd) -} - -fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd { - asm_comment!(asm, "Load extended ivar id={} index={}", id.contents_lossy(), index); - // Compile time value is *not* embedded. - - // Get a pointer to the extended table - let self_val = asm.load(self_val); - let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); - - // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.to_usize()) as i32); - asm.load(ivar_opnd) -} - /// Compile an interpreter entry block to be inserted into an ISEQ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 269336fb16fb8c..94becee9c8796a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -676,10 +676,6 @@ pub enum Insn { /// Load cfp->self LoadSelf, LoadField { recv: InsnId, id: ID, offset: i32, return_type: Type }, - /// Read an instance variable at the given index, embedded in the object - LoadIvarEmbedded { self_val: InsnId, id: ID, index: u16 }, - /// Read an instance variable at the given index, from the extended table - LoadIvarExtended { self_val: InsnId, id: ID, index: u16 }, /// Get a local variable from a higher scope or the heap. /// If `use_sp` is true, it uses the SP register to optimize the read. @@ -915,8 +911,6 @@ impl Insn { Insn::LoadPC => false, Insn::LoadSelf => false, Insn::LoadField { .. } => false, - Insn::LoadIvarEmbedded { .. } => false, - Insn::LoadIvarExtended { .. } => false, Insn::CCall { elidable, .. } => !elidable, Insn::CCallWithFrame { elidable, .. } => !elidable, Insn::ObjectAllocClass { .. } => false, @@ -1177,8 +1171,6 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::LoadPC => write!(f, "LoadPC"), Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), - &Insn::LoadIvarEmbedded { self_val, id, index } => write!(f, "LoadIvarEmbedded {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), - &Insn::LoadIvarExtended { self_val, id, index } => write!(f, "LoadIvarExtended {self_val}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_index(index as u64)), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()), @@ -1800,8 +1792,6 @@ impl Function { &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, - &LoadIvarEmbedded { self_val, id, index } => LoadIvarEmbedded { self_val: find!(self_val), id, index }, - &LoadIvarExtended { self_val, id, index } => LoadIvarExtended { self_val: find!(self_val), id, index }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, &GetClassVar { id, ic, state } => GetClassVar { id, ic, state }, &SetClassVar { id, val, ic, state } => SetClassVar { id, val: find!(val), ic, state }, @@ -1938,8 +1928,6 @@ impl Function { Insn::LoadPC => types::CPtr, Insn::LoadSelf => types::BasicObject, &Insn::LoadField { return_type, .. } => return_type, - Insn::LoadIvarEmbedded { .. } => types::BasicObject, - Insn::LoadIvarExtended { .. } => types::BasicObject, Insn::GetSpecialSymbol { .. } => types::BasicObject, Insn::GetSpecialNumber { .. } => types::BasicObject, Insn::GetClassVar { .. } => types::BasicObject, @@ -2678,13 +2666,17 @@ impl Function { // If there is no IVAR index, then the ivar was undefined when we // entered the compiler. That means we can just return nil for this // shape + iv name - Insn::Const { val: Const::Value(Qnil) } + self.push_insn(block, Insn::Const { val: Const::Value(Qnil) }) } else if recv_type.flags().is_embedded() { - Insn::LoadIvarEmbedded { self_val, id, index: ivar_index } + // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h + let offset = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; + self.push_insn(block, Insn::LoadField { recv: self_val, id, offset, return_type: types::BasicObject }) } else { - Insn::LoadIvarExtended { self_val, id, index: ivar_index } + let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: ID!(_as_heap), offset: ROBJECT_OFFSET_AS_HEAP_FIELDS as i32, return_type: types::CPtr }); + + let offset = SIZEOF_VALUE_I32 * ivar_index as i32; + self.push_insn(block, Insn::LoadField { recv: as_heap, id, offset, return_type: types::BasicObject }) }; - let replacement = self.push_insn(block, replacement); self.make_equal_to(insn_id, replacement); } _ => { self.push_insn_id(block, insn_id); } @@ -3376,10 +3368,6 @@ impl Function { &Insn::LoadField { recv, .. } => { worklist.push_back(recv); } - &Insn::LoadIvarEmbedded { self_val, .. } - | &Insn::LoadIvarExtended { self_val, .. } => { - worklist.push_back(self_val); - } &Insn::GuardBlockParamProxy { state, .. } | &Insn::GetGlobal { state, .. } | &Insn::GetSpecialSymbol { state, .. } | @@ -3754,8 +3742,6 @@ impl Function { self.assert_subtype(insn_id, val, types::BasicObject) } Insn::DefinedIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), - Insn::LoadIvarEmbedded { self_val, .. } => self.assert_subtype(insn_id, self_val, types::HeapBasicObject), - Insn::LoadIvarExtended { self_val, .. } => self.assert_subtype(insn_id, self_val, types::HeapBasicObject), Insn::SetLocal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::SetClassVar { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::IfTrue { val, .. } | Insn::IfFalse { val, .. } => self.assert_subtype(insn_id, val, types::CBool), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index fd93b4a444fa82..52471220234e8c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4551,7 +4551,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + v26:BasicObject = LoadField v25, :@foo@0x1039 CheckInterrupts Return v26 "); @@ -4590,9 +4590,10 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarExtended v25, :@foo@0x1039 + v26:CPtr = LoadField v25, :_as_heap@0x1039 + v27:BasicObject = LoadField v26, :@foo@0x103a CheckInterrupts - Return v26 + Return v27 "); } From a4c3361587f07633650d65baca4556c0c62fdf82 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 28 Oct 2025 20:29:50 -0400 Subject: [PATCH 0686/2435] [ruby/mmtk] Bump mmtk-core https://github.com/ruby/mmtk/commit/9876d8f0a1 --- gc/mmtk/Cargo.lock | 5 +++-- gc/mmtk/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gc/mmtk/Cargo.lock b/gc/mmtk/Cargo.lock index f7d62ddacb1428..56259d46d619d4 100644 --- a/gc/mmtk/Cargo.lock +++ b/gc/mmtk/Cargo.lock @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "mmtk" version = "0.31.0" -source = "git+https://github.com/mmtk/mmtk-core.git?rev=3d89bb51c191d3077278684ec5059726128d3e2b#3d89bb51c191d3077278684ec5059726128d3e2b" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=c6317a3f1c262e33fc2e427e4cc999c17bcc4791#c6317a3f1c262e33fc2e427e4cc999c17bcc4791" dependencies = [ "atomic", "atomic-traits", @@ -517,6 +517,7 @@ dependencies = [ "num_cpus", "portable-atomic", "probe", + "rayon-core", "regex", "rustversion", "spin", @@ -529,7 +530,7 @@ dependencies = [ [[package]] name = "mmtk-macros" version = "0.31.0" -source = "git+https://github.com/mmtk/mmtk-core.git?rev=3d89bb51c191d3077278684ec5059726128d3e2b#3d89bb51c191d3077278684ec5059726128d3e2b" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=c6317a3f1c262e33fc2e427e4cc999c17bcc4791#c6317a3f1c262e33fc2e427e4cc999c17bcc4791" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/gc/mmtk/Cargo.toml b/gc/mmtk/Cargo.toml index e1b1d1e13b14d5..d0bc5b3ff5d9f8 100644 --- a/gc/mmtk/Cargo.toml +++ b/gc/mmtk/Cargo.toml @@ -25,7 +25,7 @@ features = ["is_mmtk_object", "object_pinning", "sticky_immix_non_moving_nursery # Uncomment the following lines to use mmtk-core from the official repository. git = "https://github.com/mmtk/mmtk-core.git" -rev = "3d89bb51c191d3077278684ec5059726128d3e2b" +rev = "c6317a3f1c262e33fc2e427e4cc999c17bcc4791" # Uncomment the following line to use mmtk-core from a local repository. # path = "../../../mmtk-core" From 94e6d76714a4dc784cde503c8469981096547420 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 09:13:46 +0900 Subject: [PATCH 0687/2435] [ruby/English] v0.8.1 https://github.com/ruby/English/commit/c921886aaf --- lib/English.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/English.gemspec b/lib/English.gemspec index 261162dde3c676..9c09555ca1aba6 100644 --- a/lib/English.gemspec +++ b/lib/English.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "english" - spec.version = "0.8.0" + spec.version = "0.8.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] From 17e95cb4713fa465920b989ee49ff61eab8336ec Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 00:15:20 +0000 Subject: [PATCH 0688/2435] Update default gems list at 94e6d76714a4dc784cde503c846998 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a83f31ed0bf24f..ca551fabca3a25 100644 --- a/NEWS.md +++ b/NEWS.md @@ -183,6 +183,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev * date 3.5.0 +* english 0.8.1 * erb 5.1.3 * etc 1.4.6 * fcntl 1.3.0 From 2486e771b6ff6ab780d0514be809d801f88c0831 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 09:50:33 +0900 Subject: [PATCH 0689/2435] Added missing options to help message --- tool/sync_default_gems.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 3cba7010df3c80..f1a28f402a7e83 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -823,7 +823,13 @@ def update_default_gems(gem, release: false) puts <<-HELP \e[1mSync with upstream code of default libraries\e[0m -\e[1mImport a default library through `git clone` and `cp -rf` (git commits are lost)\e[0m +\e[1mImport all default gems through `git clone` and `cp -rf` (git commits are lost)\e[0m + ruby #$0 all + +\e[1mImport all released version of default gems\e[0m + ruby #$0 all release + +\e[1mImport a default gem with specific gem same as all command\e[0m ruby #$0 rubygems \e[1mPick a single commit from the upstream repository\e[0m @@ -835,6 +841,9 @@ def update_default_gems(gem, release: false) \e[1mPick all commits since the last picked commit\e[0m ruby #$0 -a rubygems +\e[1mUpdate repositories of default gems\e[0m + ruby #$0 up + \e[1mList known libraries\e[0m ruby #$0 list From 240962dffa59f398d5d61fe37a7bee8dc1ab3d43 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 10:27:22 +0900 Subject: [PATCH 0690/2435] [ruby/uri] Use generic version number to VERSION and generate VERSION_CODE from that https://github.com/ruby/uri/commit/1fc4f0496a --- lib/uri/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/uri/version.rb b/lib/uri/version.rb index 60ada985f91ca8..33e80740563eed 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '010004'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.0.4'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end From 08e822ba79d6d36f52bf07b06c5014024059b356 Mon Sep 17 00:00:00 2001 From: vivshaw Date: Wed, 18 Jun 2025 22:10:24 -0400 Subject: [PATCH 0691/2435] [ruby/uri] chore(docs): replace reference to the obsolete URI.escape with URI::RFC2396_PARSER.escape https://github.com/ruby/uri/commit/72e7d6b364 --- lib/uri/common.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 61221fafad1a7e..718dc3eb016ea6 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -237,7 +237,7 @@ def self.split(uri) # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => # # - # It's recommended to first ::escape string +uri+ + # It's recommended to first URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid URI characters. # def self.parse(uri) From 1dce0ae55a58aa11baf25e9ff92b64974e673361 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Mon, 14 Apr 2025 07:55:39 +0900 Subject: [PATCH 0692/2435] [ruby/uri] Switch a parsing behavior completely when switching a parser Currently, some methods' behavior(e.g. `URI.parse`) don't change when switching a parser. This is because some methods use `DEFAULT_PARSER`, but `parser=` doesn't change `DEFAULT_PARSER`. This PR introduces a constant to keep a parser's instance and change it when switching a parser. Also, change to use it in methods. https://github.com/ruby/uri/commit/aded210709 --- lib/uri/common.rb | 11 +++++++---- test/uri/test_common.rb | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 718dc3eb016ea6..0b3bb4f0995a8b 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -30,6 +30,9 @@ def self.parser=(parser = RFC3986_PARSER) remove_const(:Parser) if defined?(::URI::Parser) const_set("Parser", parser.class) + remove_const(:PARSER) if defined?(::URI::PARSER) + const_set("PARSER", parser) + remove_const(:REGEXP) if defined?(::URI::REGEXP) remove_const(:PATTERN) if defined?(::URI::PATTERN) if Parser == RFC2396_Parser @@ -227,7 +230,7 @@ class BadURIError < Error; end # ["fragment", "top"]] # def self.split(uri) - DEFAULT_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \URI object constructed from the given string +uri+: @@ -241,7 +244,7 @@ def self.split(uri) # if it may contain invalid URI characters. # def self.parse(uri) - DEFAULT_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given URI strings +str+ @@ -297,7 +300,7 @@ def self.join(*str) # def self.extract(str, schemes = nil, &block) # :nodoc: warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -334,7 +337,7 @@ def self.extract(str, schemes = nil, &block) # :nodoc: # def self.regexp(schemes = nil)# :nodoc: warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: diff --git a/test/uri/test_common.rb b/test/uri/test_common.rb index 12913669364cd9..569264005a8b89 100644 --- a/test/uri/test_common.rb +++ b/test/uri/test_common.rb @@ -31,12 +31,14 @@ def test_fallback_constants def test_parser_switch assert_equal(URI::Parser, URI::RFC3986_Parser) + assert_equal(URI::PARSER, URI::RFC3986_PARSER) refute defined?(URI::REGEXP) refute defined?(URI::PATTERN) URI.parser = URI::RFC2396_PARSER assert_equal(URI::Parser, URI::RFC2396_Parser) + assert_equal(URI::PARSER, URI::RFC2396_PARSER) assert defined?(URI::REGEXP) assert defined?(URI::PATTERN) assert defined?(URI::PATTERN::ESCAPED) @@ -45,6 +47,7 @@ def test_parser_switch URI.parser = URI::RFC3986_PARSER assert_equal(URI::Parser, URI::RFC3986_Parser) + assert_equal(URI::PARSER, URI::RFC3986_PARSER) refute defined?(URI::REGEXP) refute defined?(URI::PATTERN) ensure From f0993de1c20b5618df6915ef72321b0923c70874 Mon Sep 17 00:00:00 2001 From: sodacris Date: Tue, 12 Nov 2024 09:03:08 +0800 Subject: [PATCH 0693/2435] [ruby/uri] improve error message https://github.com/ruby/uri/commit/1c6e81b721 --- lib/uri/generic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 634da49fe98f90..6fd0f7c42075c6 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -1540,7 +1540,7 @@ def find_proxy(env=ENV) else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end From d2ffab11ce629367b13e7b607b9644cb83ac916f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 10:47:40 +0900 Subject: [PATCH 0694/2435] [ruby/uri] v1.1.0 https://github.com/ruby/uri/commit/c41903b3e4 --- lib/uri/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/version.rb b/lib/uri/version.rb index 33e80740563eed..03d6080fd33b0f 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION = '1.0.4'.freeze + VERSION = '1.1.0'.freeze VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end From 78966ae667c60e698dfe7a0d4be22ebc7fbe4d64 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 01:50:29 +0000 Subject: [PATCH 0695/2435] Update default gems list at d2ffab11ce629367b13e7b607b9644 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index ca551fabca3a25..59ab6c6ddd8230 100644 --- a/NEWS.md +++ b/NEWS.md @@ -201,7 +201,7 @@ The following default gems are updated. * stringio 3.1.8.dev * strscan 3.1.6.dev * timeout 0.4.4 -* uri 1.0.4 +* uri 1.1.0 * weakref 0.1.4 * zlib 3.2.2 From 8b1fd559b8f75caeeec4ceaa23df26ad64af4009 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 11:27:05 +0900 Subject: [PATCH 0696/2435] [ruby/net-http] v0.7.0 https://github.com/ruby/net-http/commit/ec9c70a6fb --- lib/net/http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 85051a44680118..6cacc556037993 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.6.0" + VERSION = "0.7.0" HTTPVersion = '1.1' begin require 'zlib' From 5b2707f39b1485620b9e91573a90d93c2129e126 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 02:30:05 +0000 Subject: [PATCH 0697/2435] Update default gems list at 8b1fd559b8f75caeeec4ceaa23df26 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 59ab6c6ddd8230..e04477c26c98ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -192,6 +192,7 @@ The following default gems are updated. * io-nonblock 0.3.2 * io-wait 0.3.2 * json 2.15.2 +* net-http 0.7.0 * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 From 2eae7049384fb764a45b04451c8cc7d80de587ed Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 12:05:15 +0900 Subject: [PATCH 0698/2435] [ruby/optparse] We should use VERSION instead of Version constant https://github.com/ruby/optparse/commit/94de48b47e --- lib/optparse.rb | 3 ++- lib/optparse/optparse.gemspec | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ea6844b9558e5c..dcd980f8743822 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -426,7 +426,8 @@ # class OptionParser # The version string - OptionParser::Version = "0.7.0.dev.2" + VERSION = "0.7.0.dev.2" + Version = VERSION # for compatibility # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 6ea6b883956dc0..885b0ec3803b6e 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -3,7 +3,7 @@ name = File.basename(__FILE__, ".gemspec") version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*OptionParser::Version\s*=\s*"(.*)"/ =~ line and break $1 + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 end rescue nil end From 9facc6e9603d5edf1f7a07756c71eaf3c05bf732 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 12:20:46 +0900 Subject: [PATCH 0699/2435] [ruby/optparse] Bump up v0.7.0 https://github.com/ruby/optparse/commit/a394ca4878 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index dcd980f8743822..ef707e404b44df 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -426,7 +426,7 @@ # class OptionParser # The version string - VERSION = "0.7.0.dev.2" + VERSION = "0.7.0" Version = VERSION # for compatibility # :stopdoc: From 5fbc0bcf5fc903d509821a51900f4ffab6b7f037 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 03:22:55 +0000 Subject: [PATCH 0700/2435] Update default gems list at 9facc6e9603d5edf1f7a07756c71ea [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e04477c26c98ca..0c437ace4fc1fb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -194,7 +194,7 @@ The following default gems are updated. * json 2.15.2 * net-http 0.7.0 * openssl 4.0.0.pre -* optparse 0.7.0.dev.2 +* optparse 0.7.0 * pp 0.6.3 * prism 1.6.0 * psych 5.2.6 From d3d6f19eb5aa1cd466002adb03f150ab9af38f7e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 12:20:14 +0900 Subject: [PATCH 0701/2435] [ruby/optparse] Bump up to v0.8.0 v0.8.0 is mistake of release workflow. This version is same as v0.7.0 https://github.com/ruby/optparse/commit/9a467d10d4 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ef707e404b44df..0a3d04b09a6813 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -426,7 +426,7 @@ # class OptionParser # The version string - VERSION = "0.7.0" + VERSION = "0.8.0" Version = VERSION # for compatibility # :stopdoc: From d5c05585923bca11f07ff19edccd1f8e67620610 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 03:35:05 +0000 Subject: [PATCH 0702/2435] Update default gems list at d3d6f19eb5aa1cd466002adb03f150 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 0c437ace4fc1fb..4d637d72f46f63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -194,7 +194,7 @@ The following default gems are updated. * json 2.15.2 * net-http 0.7.0 * openssl 4.0.0.pre -* optparse 0.7.0 +* optparse 0.8.0 * pp 0.6.3 * prism 1.6.0 * psych 5.2.6 From 0044bac4de603b4b4d4815cc27167e71ee11039f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 13:34:23 +0900 Subject: [PATCH 0703/2435] [ruby/resolv] v0.6.3 https://github.com/ruby/resolv/commit/31a393e96c --- lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 73410f1324b7b1..fce5092d0e9cb7 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -34,7 +34,7 @@ class Resolv - VERSION = "0.6.2" + VERSION = "0.6.3" ## # Looks up the first IP address for +name+. From 8b88e7f8386941787b137543e488d00138b9dbfe Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 04:36:25 +0000 Subject: [PATCH 0704/2435] Update default gems list at 0044bac4de603b4b4d4815cc27167e [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 4d637d72f46f63..15b4d500dcb501 100644 --- a/NEWS.md +++ b/NEWS.md @@ -198,7 +198,7 @@ The following default gems are updated. * pp 0.6.3 * prism 1.6.0 * psych 5.2.6 -* resolv 0.6.2 +* resolv 0.6.3 * stringio 3.1.8.dev * strscan 3.1.6.dev * timeout 0.4.4 From 3941164c63ab77ac05228cc3afbff6230b7aceec Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 13:44:53 +0900 Subject: [PATCH 0705/2435] [ruby/win32-registry] v0.1.1 https://github.com/ruby/win32-registry/commit/6d8a43a890 --- ext/win32/win32-registry.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec index a690dd5525ed14..9b65af4a5b9976 100644 --- a/ext/win32/win32-registry.gemspec +++ b/ext/win32/win32-registry.gemspec @@ -1,7 +1,7 @@ # frozen_string_literal: true Gem::Specification.new do |spec| spec.name = "win32-registry" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["U.Nakamura"] spec.email = ["usa@garbagecollect.jp"] From 2b6dc5bf6f38057eafc22030184859732802b798 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 04:47:15 +0000 Subject: [PATCH 0706/2435] Update default gems list at 3941164c63ab77ac05228cc3afbff6 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 15b4d500dcb501..6cb52e27169074 100644 --- a/NEWS.md +++ b/NEWS.md @@ -176,7 +176,7 @@ releases. The following default gem is added. -* win32-registry 0.1.0 +* win32-registry 0.1.1 The following default gems are updated. From 307b625b5968895055fe7be1e009c8d8f769e8cd Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 31 Oct 2025 13:53:42 +0900 Subject: [PATCH 0707/2435] [ruby/digest] v3.2.1 https://github.com/ruby/digest/commit/687e7cc1e1 --- ext/digest/lib/digest/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/digest/lib/digest/version.rb b/ext/digest/lib/digest/version.rb index 854106165cab52..691323f1099fe0 100644 --- a/ext/digest/lib/digest/version.rb +++ b/ext/digest/lib/digest/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Digest - VERSION = "3.2.0" + VERSION = "3.2.1" end From a6379032ee98bc43fb68ce7a6c186f3512558ce0 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 31 Oct 2025 04:55:24 +0000 Subject: [PATCH 0708/2435] Update default gems list at 307b625b5968895055fe7be1e009c8 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 6cb52e27169074..abaf585eabf609 100644 --- a/NEWS.md +++ b/NEWS.md @@ -183,6 +183,7 @@ The following default gems are updated. * RubyGems 4.0.0.dev * bundler 4.0.0.dev * date 3.5.0 +* digest 3.2.1 * english 0.8.1 * erb 5.1.3 * etc 1.4.6 From 981ee02c7c664f19b983662d618d5e6bd87d1739 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 21 Nov 2017 20:30:00 +0900 Subject: [PATCH 0709/2435] Fix performance problem with /k/i and /s/i (Close k-takata/Onigmo#97) E.g. For the pattern `/----k/i`, optimization was totally turned off. Make it possible to use the characters before `k` (i.e. `----`) for optimization. https://github.com/k-takata/Onigmo/commit/9c13de8d0684ebde97e3709d7693997c81ca374b --- regcomp.c | 67 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/regcomp.c b/regcomp.c index e389e6f1209af5..27878405cd24b6 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4217,7 +4217,7 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, { OnigDistance i, len; int clen, flen, n, j, k; - UChar *p, buf[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM][ONIGENC_MBC_CASE_FOLD_MAXLEN]; + UChar *p, buf[ONIGENC_MBC_CASE_FOLD_MAXLEN]; OnigCaseFoldCodeItem items[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM]; OnigEncoding enc = reg->enc; @@ -4299,7 +4299,7 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, { OnigDistance i, len; int clen, flen, n, j, k; - UChar *p, buf[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM][ONIGENC_MBC_CASE_FOLD_MAXLEN]; + UChar *p, buf[ONIGENC_MBC_CASE_FOLD_MAXLEN]; OnigCaseFoldCodeItem items[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM]; OnigEncoding enc = reg->enc; @@ -4307,6 +4307,34 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, if (len < ONIG_CHAR_TABLE_SIZE) { for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) skip[i] = (UChar )(len + 1); + if (ignore_case) { + for (i = 0; i < len; i += clen) { + p = s + i; + n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, + p, end, items); + clen = enclen(enc, p, end); + if (p + clen > end) + clen = (int )(end - p); + + for (j = 0; j < n; j++) { + if ((items[j].code_len != 1) || (items[j].byte_len != clen)) { + /* Different length isn't supported. Stop optimization at here. */ + end = p; + goto endcheck; + } + flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf); + if (flen != clen) { + /* Different length isn't supported. Stop optimization at here. */ + end = p; + goto endcheck; + } + } + } +endcheck: + ; + } + + len = end - s; n = 0; for (i = 0; i < len; i += clen) { p = s + i; @@ -4317,17 +4345,11 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, if (p + clen > end) clen = (int )(end - p); - for (j = 0; j < n; j++) { - if ((items[j].code_len != 1) || (items[j].byte_len != clen)) - return 1; /* different length isn't supported. */ - flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf[j]); - if (flen != clen) - return 1; /* different length isn't supported. */ - } for (j = 0; j < clen; j++) { skip[s[i + j]] = (UChar )(len - i - j); for (k = 0; k < n; k++) { - skip[buf[k][j]] = (UChar )(len - i - j); + ONIGENC_CODE_TO_MBC(enc, items[k].code[0], buf); + skip[buf[j]] = (UChar )(len - i - j); } } } @@ -4369,7 +4391,7 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, } # endif } - return 0; + return (int)len; } #endif /* USE_SUNDAY_QUICK_SEARCH */ @@ -5342,7 +5364,6 @@ optimize_node_left(Node* node, NodeOptInfo* opt, OptEnv* env) static int set_optimize_exact_info(regex_t* reg, OptExactInfo* e) { - int r; int allow_reverse; if (e->len == 0) return 0; @@ -5357,15 +5378,18 @@ set_optimize_exact_info(regex_t* reg, OptExactInfo* e) if (e->ignore_case > 0) { if (e->len >= 3 || (e->len >= 2 && allow_reverse)) { - r = set_bm_skip(reg->exact, reg->exact_end, reg, + e->len = set_bm_skip(reg->exact, reg->exact_end, reg, reg->map, &(reg->int_map), 1); - if (r == 0) { + reg->exact_end = reg->exact + e->len; + if (e->len >= 3) { reg->optimize = (allow_reverse != 0 ? ONIG_OPTIMIZE_EXACT_BM_IC : ONIG_OPTIMIZE_EXACT_BM_NOT_REV_IC); } - else { + else if (e->len > 0) { reg->optimize = ONIG_OPTIMIZE_EXACT_IC; } + else + return 0; } else { reg->optimize = ONIG_OPTIMIZE_EXACT_IC; @@ -5373,15 +5397,10 @@ set_optimize_exact_info(regex_t* reg, OptExactInfo* e) } else { if (e->len >= 3 || (e->len >= 2 && allow_reverse)) { - r = set_bm_skip(reg->exact, reg->exact_end, reg, - reg->map, &(reg->int_map), 0); - if (r == 0) { - reg->optimize = (allow_reverse != 0 - ? ONIG_OPTIMIZE_EXACT_BM : ONIG_OPTIMIZE_EXACT_BM_NOT_REV); - } - else { - reg->optimize = ONIG_OPTIMIZE_EXACT; - } + set_bm_skip(reg->exact, reg->exact_end, reg, + reg->map, &(reg->int_map), 0); + reg->optimize = (allow_reverse != 0 + ? ONIG_OPTIMIZE_EXACT_BM : ONIG_OPTIMIZE_EXACT_BM_NOT_REV); } else { reg->optimize = ONIG_OPTIMIZE_EXACT; From bcea1129f4cac556befd3aa6f0f5580a9f0b1eb5 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Thu, 24 Jan 2019 19:05:57 +0900 Subject: [PATCH 0710/2435] Revert "[tune] implicit-anchor optimization" This reverts commit 282338f88a8bf0807a7a1d21b06f78abe9de8fac. It seems that the commit didn't improve the performance. Revert it to fix k-takata/Onigmo#100. https://github.com/k-takata/Onigmo/commit/cef834cb3a6e278fa252f52b704c65175a970ac0 --- regcomp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regcomp.c b/regcomp.c index 27878405cd24b6..12ad5d79a65571 100644 --- a/regcomp.c +++ b/regcomp.c @@ -5254,7 +5254,7 @@ optimize_node_left(Node* node, NodeOptInfo* opt, OptEnv* env) r = optimize_node_left(qn->target, &nopt, env); if (r) break; - if (/*qn->lower == 0 &&*/ IS_REPEAT_INFINITE(qn->upper)) { + if (qn->lower == 0 && IS_REPEAT_INFINITE(qn->upper)) { if (env->mmd.max == 0 && NTYPE(qn->target) == NT_CANY && qn->greedy) { if (IS_MULTILINE(env->options)) From bfbbcf34557b0aad4f5ed045df3e4e86b11c9a8c Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Fri, 25 Jan 2019 18:54:41 +0900 Subject: [PATCH 0711/2435] [Bug #13671] Fix that "ss" in look-behind causes syntax error Fixes k-takata/Onigmo#92. This fix was ported from oniguruma: https://github.com/kkos/oniguruma/commit/257082dac8c6019198b56324012f0bd1830ff4ba https://github.com/k-takata/Onigmo/commit/b1a5445fbeba97b3e94a733c2ce11c033453af73 --- regcomp.c | 37 ++++++++++++++++++------------- spec/ruby/language/regexp_spec.rb | 2 +- test/ruby/test_regexp.rb | 23 +++++++++++++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/regcomp.c b/regcomp.c index 12ad5d79a65571..22dbe5f9d50b6e 100644 --- a/regcomp.c +++ b/regcomp.c @@ -3301,6 +3301,14 @@ setup_subexp_call(Node* node, ScanEnv* env) } #endif +#define IN_ALT (1<<0) +#define IN_NOT (1<<1) +#define IN_REPEAT (1<<2) +#define IN_VAR_REPEAT (1<<3) +#define IN_CALL (1<<4) +#define IN_RECCALL (1<<5) +#define IN_LOOK_BEHIND (1<<6) + /* divide different length alternatives in look-behind. (?<=A|B) ==> (?<=A)|(?<=B) (? (?s; end = sn->end; if (start >= end) return 0; + is_in_look_behind = (state & IN_LOOK_BEHIND) != 0; + r = 0; top_root = root = prev_node = snode = NULL_NODE; alt_num = 1; @@ -3630,7 +3643,7 @@ expand_case_fold_string(Node* node, regex_t* reg) len = enclen(reg->enc, p, end); varlen = is_case_fold_variable_len(n, items, len); - if (n == 0 || varlen == 0) { + if (n == 0 || varlen == 0 || is_in_look_behind) { if (IS_NULL(snode)) { if (IS_NULL(root) && IS_NOT_NULL(prev_node)) { onig_node_free(top_root); @@ -3889,13 +3902,6 @@ setup_comb_exp_check(Node* node, int state, ScanEnv* env) } #endif -#define IN_ALT (1<<0) -#define IN_NOT (1<<1) -#define IN_REPEAT (1<<2) -#define IN_VAR_REPEAT (1<<3) -#define IN_CALL (1<<4) -#define IN_RECCALL (1<<5) - /* setup_tree does the following work. 1. check empty loop. (set qn->target_empty_info) 2. expand ignore-case in char class. @@ -3937,7 +3943,7 @@ setup_tree(Node* node, regex_t* reg, int state, ScanEnv* env) case NT_STR: if (IS_IGNORECASE(reg->options) && !NSTRING_IS_RAW(node)) { - r = expand_case_fold_string(node, reg); + r = expand_case_fold_string(node, reg, state); } break; @@ -4180,7 +4186,7 @@ setup_tree(Node* node, regex_t* reg, int state, ScanEnv* env) if (r < 0) return r; if (r > 0) return ONIGERR_INVALID_LOOK_BEHIND_PATTERN; if (NTYPE(node) != NT_ANCHOR) goto restart; - r = setup_tree(an->target, reg, state, env); + r = setup_tree(an->target, reg, (state | IN_LOOK_BEHIND), env); if (r != 0) return r; r = setup_look_behind(node, reg, env); } @@ -4193,7 +4199,8 @@ setup_tree(Node* node, regex_t* reg, int state, ScanEnv* env) if (r < 0) return r; if (r > 0) return ONIGERR_INVALID_LOOK_BEHIND_PATTERN; if (NTYPE(node) != NT_ANCHOR) goto restart; - r = setup_tree(an->target, reg, (state | IN_NOT), env); + r = setup_tree(an->target, reg, (state | IN_NOT | IN_LOOK_BEHIND), + env); if (r != 0) return r; r = setup_look_behind(node, reg, env); } diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index 0cd9584549b801..dbf341b19ea526 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -112,7 +112,7 @@ /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] end - ruby_bug "#13671", ""..."3.6" do # https://bugs.ruby-lang.org/issues/13671 + ruby_bug "#13671", ""..."3.5" do # https://bugs.ruby-lang.org/issues/13671 it "handles a lookbehind with ss characters" do r = Regexp.new("(? Date: Fri, 25 Jan 2019 18:56:31 +0900 Subject: [PATCH 0712/2435] Fix lgtm.com warnings * Multiplication result may overflow 'int' before it is converted to 'OnigDistance'. * Comparison is always true because code <= 122. * This statement makes ExprStmt unreachable. * Empty block without comment https://github.com/k-takata/Onigmo/commit/387ad616c3cb9370f99d2b11198c2135fa07030f --- enc/unicode.c | 2 +- regcomp.c | 9 +++------ regexec.c | 28 ++++++---------------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/enc/unicode.c b/enc/unicode.c index cbfc6cdf589681..1c4428a3beb4b9 100644 --- a/enc/unicode.c +++ b/enc/unicode.c @@ -682,7 +682,7 @@ onigenc_unicode_case_map(OnigCaseFoldType* flagP, *pp += codepoint_length; if (code <= 'z') { /* ASCII comes first */ - if (code >= 'a' && code <= 'z') { + if (code >= 'a' /*&& code <= 'z'*/) { if (flags & ONIGENC_CASE_UPCASE) { MODIFIED; if (flags & ONIGENC_CASE_FOLD_TURKISH_AZERI && code == 'i') diff --git a/regcomp.c b/regcomp.c index 22dbe5f9d50b6e..664806f085ee0e 100644 --- a/regcomp.c +++ b/regcomp.c @@ -2803,14 +2803,11 @@ get_head_value_node(Node* node, int exact, regex_t* reg) case NT_STR: { StrNode* sn = NSTR(node); - if (sn->end <= sn->s) break; - if (exact != 0 && - !NSTRING_IS_RAW(node) && IS_IGNORECASE(reg->options)) { - } - else { + if (exact == 0 || + NSTRING_IS_RAW(node) || !IS_IGNORECASE(reg->options)) { n = node; } } @@ -5078,7 +5075,7 @@ optimize_node_left(Node* node, NodeOptInfo* opt, OptEnv* env) if (NSTRING_IS_DONT_GET_OPT_INFO(node)) { int n = onigenc_strlen(env->enc, sn->s, sn->end); - max = ONIGENC_MBC_MAXLEN_DIST(env->enc) * (OnigDistance)n; + max = (OnigDistance )ONIGENC_MBC_MAXLEN_DIST(env->enc) * (OnigDistance)n; } else { concat_opt_exact_info_str(&opt->exb, sn->s, sn->end, diff --git a/regexec.c b/regexec.c index b8d174ec8ed472..e5e5f948a97bb1 100644 --- a/regexec.c +++ b/regexec.c @@ -2742,7 +2742,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, /* default behavior: return first-matching result. */ goto finish; - NEXT; CASE(OP_EXACT1) MOP_IN(OP_EXACT1); DATA_ENSURE(1); @@ -3316,40 +3315,36 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, if (ON_STR_BEGIN(s) || !ONIGENC_IS_MBC_WORD(encode, sprev, end)) { MOP_OUT; JUMP; - } + } } goto fail; - NEXT; CASE(OP_ASCII_WORD_BEGIN) MOP_IN(OP_ASCII_WORD_BEGIN); if (DATA_ENSURE_CHECK1 && ONIGENC_IS_MBC_ASCII_WORD(encode, s, end)) { if (ON_STR_BEGIN(s) || !ONIGENC_IS_MBC_ASCII_WORD(encode, sprev, end)) { MOP_OUT; JUMP; - } + } } goto fail; - NEXT; CASE(OP_WORD_END) MOP_IN(OP_WORD_END); if (!ON_STR_BEGIN(s) && ONIGENC_IS_MBC_WORD(encode, sprev, end)) { if (ON_STR_END(s) || !ONIGENC_IS_MBC_WORD(encode, s, end)) { MOP_OUT; JUMP; - } + } } goto fail; - NEXT; CASE(OP_ASCII_WORD_END) MOP_IN(OP_ASCII_WORD_END); if (!ON_STR_BEGIN(s) && ONIGENC_IS_MBC_ASCII_WORD(encode, sprev, end)) { if (ON_STR_END(s) || !ONIGENC_IS_MBC_ASCII_WORD(encode, s, end)) { MOP_OUT; JUMP; - } + } } goto fail; - NEXT; #endif CASE(OP_BEGIN_BUF) MOP_IN(OP_BEGIN_BUF); @@ -3379,10 +3374,9 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, #endif && !ON_STR_END(s)) { MOP_OUT; - JUMP; + JUMP; } goto fail; - NEXT; CASE(OP_END_LINE) MOP_IN(OP_END_LINE); if (ON_STR_END(s)) { @@ -3398,10 +3392,9 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, } else if (ONIGENC_IS_MBC_NEWLINE_EX(encode, s, str, end, option, 1)) { MOP_OUT; - JUMP; + JUMP; } goto fail; - NEXT; CASE(OP_SEMI_END_BUF) MOP_IN(OP_SEMI_END_BUF); if (ON_STR_END(s)) { @@ -3433,7 +3426,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, #endif } goto fail; - NEXT; CASE(OP_BEGIN_POSITION) MOP_IN(OP_BEGIN_POSITION); if (s != msa->gpos) @@ -3499,12 +3491,10 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_BACKREF1) MOP_IN(OP_BACKREF1); mem = 1; goto backref; - NEXT; CASE(OP_BACKREF2) MOP_IN(OP_BACKREF2); mem = 2; goto backref; - NEXT; CASE(OP_BACKREFN) MOP_IN(OP_BACKREFN); GET_MEMNUM_INC(mem, p); @@ -3934,7 +3924,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, STACK_GET_REPEAT(mem, stkp); si = GET_STACK_INDEX(stkp); goto repeat_inc; - NEXT; CASE(OP_REPEAT_INC_NG) MOP_IN(OP_REPEAT_INC_NG); GET_MEMNUM_INC(mem, p); /* mem: OP_REPEAT ID */ @@ -3970,7 +3959,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, STACK_GET_REPEAT(mem, stkp); si = GET_STACK_INDEX(stkp); goto repeat_inc_ng; - NEXT; CASE(OP_PUSH_POS) MOP_IN(OP_PUSH_POS); STACK_PUSH_POS(s, sprev, pkeep); @@ -3995,7 +3983,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_FAIL_POS) MOP_IN(OP_FAIL_POS); STACK_POP_TIL_POS_NOT; goto fail; - NEXT; CASE(OP_PUSH_STOP_BT) MOP_IN(OP_PUSH_STOP_BT); STACK_PUSH_STOP_BT; @@ -4036,7 +4023,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_FAIL_LOOK_BEHIND_NOT) MOP_IN(OP_FAIL_LOOK_BEHIND_NOT); STACK_POP_TIL_LOOK_BEHIND_NOT; goto fail; - NEXT; CASE(OP_PUSH_ABSENT_POS) MOP_IN(OP_PUSH_ABSENT_POS); /* Save the absent-start-pos and the original end-pos. */ @@ -4098,7 +4084,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, #endif STACK_POP_TIL_ABSENT; goto fail; - NEXT; #ifdef USE_SUBEXP_CALL CASE(OP_CALL) MOP_IN(OP_CALL); @@ -4128,7 +4113,6 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_FINISH) goto finish; - NEXT; CASE(OP_FAIL) if (0) { From 54b963956b65f8333886e6afe4fb6d73e250148f Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Fri, 25 Jan 2019 18:58:59 +0900 Subject: [PATCH 0713/2435] Avoid negative character Better fix for k-takata/Onigmo#107. https://github.com/k-takata/Onigmo/commit/85393e4a63223b538529e7095255ce1153c09cff --- enc/unicode.c | 6 ++---- regenc.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/enc/unicode.c b/enc/unicode.c index 1c4428a3beb4b9..07497cdbe46731 100644 --- a/enc/unicode.c +++ b/enc/unicode.c @@ -687,10 +687,8 @@ onigenc_unicode_case_map(OnigCaseFoldType* flagP, MODIFIED; if (flags & ONIGENC_CASE_FOLD_TURKISH_AZERI && code == 'i') code = I_WITH_DOT_ABOVE; - else { - code -= 'a'; - code += 'A'; - } + else + code -= 'a' - 'A'; } } else if (code >= 'A' && code <= 'Z') { diff --git a/regenc.c b/regenc.c index 0afdf22cb7bdc1..823aacc28e615b 100644 --- a/regenc.c +++ b/regenc.c @@ -984,7 +984,7 @@ onigenc_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const if (code >= 'a' && code <= 'z' && (flags & ONIGENC_CASE_UPCASE)) { flags |= ONIGENC_CASE_MODIFIED; - code += 'A' - 'a'; + code -= 'a' - 'A'; } else if (code >= 'A' && code <= 'Z' && (flags & (ONIGENC_CASE_DOWNCASE | ONIGENC_CASE_FOLD))) { @@ -1013,7 +1013,7 @@ onigenc_single_byte_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar if (code >= 'a' && code <= 'z' && (flags & ONIGENC_CASE_UPCASE)) { flags |= ONIGENC_CASE_MODIFIED; - code += 'A' - 'a'; + code -= 'a' - 'A'; } else if (code >= 'A' && code <= 'Z' && (flags & (ONIGENC_CASE_DOWNCASE | ONIGENC_CASE_FOLD))) { From a89246834d8d2b60ece05b5ee34a012973b53df6 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Mon, 28 Jan 2019 18:52:39 +0900 Subject: [PATCH 0714/2435] Fix initialization of the table for quick search This fixes k-takata/Onigmo#120. The commit k-takata/Onigmo@9c13de8d0684ebde97e3709d7693997c81ca374b was insufficient. https://github.com/k-takata/Onigmo/commit/1de602ddff140d91419e3f86dd35c81d7bd2d8e7 --- regcomp.c | 4 ++-- test/ruby/test_regexp.rb | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/regcomp.c b/regcomp.c index 664806f085ee0e..3b738b1a673292 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4309,8 +4309,6 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, len = end - s; if (len < ONIG_CHAR_TABLE_SIZE) { - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) skip[i] = (UChar )(len + 1); - if (ignore_case) { for (i = 0; i < len; i += clen) { p = s + i; @@ -4339,6 +4337,8 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, } len = end - s; + for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) + skip[i] = (UChar )(len + 1); n = 0; for (i = 0; i < len; i += clen) { p = s + i; diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index b69c148305f37e..2d7a67dd549c61 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1743,6 +1743,10 @@ def test_conditional_expression assert_raise(RegexpError, bug12418){ Regexp.new('(0?0|(?(5)||)|(?(5)||))?') } end + def test_quick_search + assert_match_at('(?i) *TOOKY', 'Mozilla/5.0 (Linux; Android 4.0.3; TOOKY', [[34, 40]]) # Issue #120 + end + def test_ss_in_look_behind assert_match_at("(?i:ss)", "ss", [[0, 2]]) assert_match_at("(?i:ss)", "Ss", [[0, 2]]) From ba74fcd6e1079dd4c8482c56675ffa9d20fed7ac Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 29 Jan 2019 18:59:19 +0900 Subject: [PATCH 0715/2435] Remove old code for BMH search Remove the code for Boyer-Moore-Horspool search. Now we are using Sunday's quick search. https://github.com/k-takata/Onigmo/commit/3d9072419a1578b742a422412d004fd8a54209fd --- regcomp.c | 84 --------------------- regexec.c | 214 ------------------------------------------------------ regint.h | 1 - 3 files changed, 299 deletions(-) diff --git a/regcomp.c b/regcomp.c index 3b738b1a673292..0ecf162556b777 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4213,89 +4213,6 @@ setup_tree(Node* node, regex_t* reg, int state, ScanEnv* env) return r; } -#ifndef USE_SUNDAY_QUICK_SEARCH -/* set skip map for Boyer-Moore search */ -static int -set_bm_skip(UChar* s, UChar* end, regex_t* reg, - UChar skip[], int** int_skip, int ignore_case) -{ - OnigDistance i, len; - int clen, flen, n, j, k; - UChar *p, buf[ONIGENC_MBC_CASE_FOLD_MAXLEN]; - OnigCaseFoldCodeItem items[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM]; - OnigEncoding enc = reg->enc; - - len = end - s; - if (len < ONIG_CHAR_TABLE_SIZE) { - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) skip[i] = (UChar )len; - - n = 0; - for (i = 0; i < len - 1; i += clen) { - p = s + i; - if (ignore_case) - n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, - p, end, items); - clen = enclen(enc, p, end); - if (p + clen > end) - clen = (int )(end - p); - - for (j = 0; j < n; j++) { - if ((items[j].code_len != 1) || (items[j].byte_len != clen)) - return 1; /* different length isn't supported. */ - flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf[j]); - if (flen != clen) - return 1; /* different length isn't supported. */ - } - for (j = 0; j < clen; j++) { - skip[s[i + j]] = (UChar )(len - 1 - i - j); - for (k = 0; k < n; k++) { - skip[buf[k][j]] = (UChar )(len - 1 - i - j); - } - } - } - } - else { -# if OPT_EXACT_MAXLEN < ONIG_CHAR_TABLE_SIZE - /* This should not happen. */ - return ONIGERR_TYPE_BUG; -# else - if (IS_NULL(*int_skip)) { - *int_skip = (int* )xmalloc(sizeof(int) * ONIG_CHAR_TABLE_SIZE); - if (IS_NULL(*int_skip)) return ONIGERR_MEMORY; - } - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) (*int_skip)[i] = (int )len; - - n = 0; - for (i = 0; i < len - 1; i += clen) { - p = s + i; - if (ignore_case) - n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, - p, end, items); - clen = enclen(enc, p, end); - if (p + clen > end) - clen = (int )(end - p); - - for (j = 0; j < n; j++) { - if ((items[j].code_len != 1) || (items[j].byte_len != clen)) - return 1; /* different length isn't supported. */ - flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf[j]); - if (flen != clen) - return 1; /* different length isn't supported. */ - } - for (j = 0; j < clen; j++) { - (*int_skip)[s[i + j]] = (int )(len - 1 - i - j); - for (k = 0; k < n; k++) { - (*int_skip)[buf[k][j]] = (int )(len - 1 - i - j); - } - } - } -# endif - } - return 0; -} - -#else /* USE_SUNDAY_QUICK_SEARCH */ - /* set skip map for Sunday's quick search */ static int set_bm_skip(UChar* s, UChar* end, regex_t* reg, @@ -4397,7 +4314,6 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, } return (int)len; } -#endif /* USE_SUNDAY_QUICK_SEARCH */ typedef struct { OnigDistance min; /* min byte length */ diff --git a/regexec.c b/regexec.c index e5e5f948a97bb1..eec3e236631805 100644 --- a/regexec.c +++ b/regexec.c @@ -4377,219 +4377,6 @@ slow_search_backward_ic(OnigEncoding enc, int case_fold_flag, return (UChar* )NULL; } -#ifndef USE_SUNDAY_QUICK_SEARCH -/* Boyer-Moore-Horspool search applied to a multibyte string */ -static UChar* -bm_search_notrev(regex_t* reg, const UChar* target, const UChar* target_end, - const UChar* text, const UChar* text_end, - const UChar* text_range) -{ - const UChar *s, *se, *t, *p, *end; - const UChar *tail; - ptrdiff_t skip, tlen1; - -# ifdef ONIG_DEBUG_SEARCH - fprintf(stderr, "bm_search_notrev: text: %"PRIuPTR" (%p), text_end: %"PRIuPTR" (%p), text_range: %"PRIuPTR" (%p)\n", - (uintptr_t )text, text, (uintptr_t )text_end, text_end, (uintptr_t )text_range, text_range); -# endif - - tail = target_end - 1; - tlen1 = tail - target; - end = text_range; - if (end + tlen1 > text_end) - end = text_end - tlen1; - - s = text; - - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = se = s + tlen1; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )s; - p--; t--; - } - skip = reg->map[*se]; - t = s; - do { - s += enclen(reg->enc, s, end); - } while ((s - t) < skip && s < end); - } - } - else { -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = se = s + tlen1; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )s; - p--; t--; - } - skip = reg->int_map[*se]; - t = s; - do { - s += enclen(reg->enc, s, end); - } while ((s - t) < skip && s < end); - } -# endif - } - - return (UChar* )NULL; -} - -/* Boyer-Moore-Horspool search */ -static UChar* -bm_search(regex_t* reg, const UChar* target, const UChar* target_end, - const UChar* text, const UChar* text_end, const UChar* text_range) -{ - const UChar *s, *t, *p, *end; - const UChar *tail; - -# ifdef ONIG_DEBUG_SEARCH - fprintf(stderr, "bm_search: text: %"PRIuPTR" (%p), text_end: %"PRIuPTR" (%p), text_range: %"PRIuPTR" (%p)\n", - (uintptr_t )text, text, (uintptr_t )text_end, text_end, (uintptr_t )text_range, text_range); -# endif - - end = text_range + (target_end - target) - 1; - if (end > text_end) - end = text_end; - - tail = target_end - 1; - s = text + (target_end - target) - 1; - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = s; - t = tail; -# ifdef ONIG_DEBUG_SEARCH - fprintf(stderr, "bm_search_loop: pos: %"PRIdPTR" %s\n", - (intptr_t )(s - text), s); -# endif - while (*p == *t) { - if (t == target) return (UChar* )p; - p--; t--; - } - s += reg->map[*s]; - } - } - else { /* see int_map[] */ -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = s; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )p; - p--; t--; - } - s += reg->int_map[*s]; - } -# endif - } - return (UChar* )NULL; -} - -/* Boyer-Moore-Horspool search applied to a multibyte string (ignore case) */ -static UChar* -bm_search_notrev_ic(regex_t* reg, const UChar* target, const UChar* target_end, - const UChar* text, const UChar* text_end, - const UChar* text_range) -{ - const UChar *s, *se, *t, *end; - const UChar *tail; - ptrdiff_t skip, tlen1; - OnigEncoding enc = reg->enc; - int case_fold_flag = reg->case_fold_flag; - -# ifdef ONIG_DEBUG_SEARCH - fprintf(stderr, "bm_search_notrev_ic: text: %d (%p), text_end: %d (%p), text_range: %d (%p)\n", - (int )text, text, (int )text_end, text_end, (int )text_range, text_range); -# endif - - tail = target_end - 1; - tlen1 = tail - target; - end = text_range; - if (end + tlen1 > text_end) - end = text_end - tlen1; - - s = text; - - if (IS_NULL(reg->int_map)) { - while (s < end) { - se = s + tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - s, se + 1)) - return (UChar* )s; - skip = reg->map[*se]; - t = s; - do { - s += enclen(reg->enc, s, end); - } while ((s - t) < skip && s < end); - } - } - else { -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - se = s + tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - s, se + 1)) - return (UChar* )s; - skip = reg->int_map[*se]; - t = s; - do { - s += enclen(reg->enc, s, end); - } while ((s - t) < skip && s < end); - } -# endif - } - - return (UChar* )NULL; -} - -/* Boyer-Moore-Horspool search (ignore case) */ -static UChar* -bm_search_ic(regex_t* reg, const UChar* target, const UChar* target_end, - const UChar* text, const UChar* text_end, const UChar* text_range) -{ - const UChar *s, *p, *end; - const UChar *tail; - OnigEncoding enc = reg->enc; - int case_fold_flag = reg->case_fold_flag; - -# ifdef ONIG_DEBUG_SEARCH - fprintf(stderr, "bm_search_ic: text: %d (%p), text_end: %d (%p), text_range: %d (%p)\n", - (int )text, text, (int )text_end, text_end, (int )text_range, text_range); -# endif - - end = text_range + (target_end - target) - 1; - if (end > text_end) - end = text_end; - - tail = target_end - 1; - s = text + (target_end - target) - 1; - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = s - (target_end - target) + 1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - p, s + 1)) - return (UChar* )p; - s += reg->map[*s]; - } - } - else { /* see int_map[] */ -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = s - (target_end - target) + 1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - p, s + 1)) - return (UChar* )p; - s += reg->int_map[*s]; - } -# endif - } - return (UChar* )NULL; -} - -#else /* USE_SUNDAY_QUICK_SEARCH */ - /* Sunday's quick search applied to a multibyte string */ static UChar* bm_search_notrev(regex_t* reg, const UChar* target, const UChar* target_end, @@ -4808,7 +4595,6 @@ bm_search_ic(regex_t* reg, const UChar* target, const UChar* target_end, } return (UChar* )NULL; } -#endif /* USE_SUNDAY_QUICK_SEARCH */ #ifdef USE_INT_MAP_BACKWARD static int diff --git a/regint.h b/regint.h index 75abfba235790c..9924e5f62ab5f3 100644 --- a/regint.h +++ b/regint.h @@ -86,7 +86,6 @@ /* #define USE_OP_PUSH_OR_JUMP_EXACT */ #define USE_QTFR_PEEK_NEXT #define USE_ST_LIBRARY -#define USE_SUNDAY_QUICK_SEARCH #define INIT_MATCH_STACK_SIZE 160 #define DEFAULT_MATCH_STACK_LIMIT_SIZE 0 /* unlimited */ From 961683bcb10ad7e470c7956104f34ef9220b6de4 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Fri, 31 Oct 2025 20:06:33 +0900 Subject: [PATCH 0716/2435] Run .so init functions in namespaces to be loaded --- load.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/load.c b/load.c index 9a1ff53f5415da..d1d6969039b849 100644 --- a/load.c +++ b/load.c @@ -1212,17 +1212,19 @@ load_ext(VALUE path, VALUE fname) return (VALUE)dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); } -static bool -run_static_ext_init(rb_vm_t *vm, const char *feature) +static VALUE +run_static_ext_init(VALUE vm_ptr, VALUE feature_value) { + rb_vm_t *vm = (rb_vm_t *)vm_ptr; + const char *feature = RSTRING_PTR(feature_value); st_data_t key = (st_data_t)feature; st_data_t init_func; if (vm->static_ext_inits && st_delete(vm->static_ext_inits, &key, &init_func)) { ((void (*)(void))init_func)(); - return true; + return Qtrue; } - return false; + return Qfalse; } static int @@ -1331,7 +1333,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa else if (!*ftptr) { result = TAG_RETURN; } - else if (found == 's' && run_static_ext_init(th->vm, RSTRING_PTR(path))) { + else if (found == 's' && RTEST(rb_vm_call_cfunc_in_namespace(Qnil, run_static_ext_init, (VALUE)th->vm, path, path, ns))) { result = TAG_RETURN; } else if (RTEST(rb_hash_aref(realpaths, From 65168b7eafd30fd3958d71761fa155bba00cdec5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Fri, 31 Oct 2025 20:07:28 +0900 Subject: [PATCH 0717/2435] Specify RUBY_DEBUG flag in the right way --- namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index 4e5a4e9bc42491..847aff9f872965 100644 --- a/namespace.c +++ b/namespace.c @@ -1084,7 +1084,7 @@ Init_Namespace(void) if (rb_namespace_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); -#ifdef RUBY_DEBUG +#if RUBY_DEBUG > 0 rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); rb_define_global_function("dump_classext", rb_f_dump_classext, 1); From 6bdb2027f64df8e6f59273488ea01ed1614bcf76 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 31 Oct 2025 23:52:23 +0900 Subject: [PATCH 0718/2435] [ruby/json] Fix memory leak when exception is raised during JSON generation part 2 Commit https://github.com/ruby/json/commit/44df509dc2de fixed it for StandardError, but other exceptions and jumps are also possible. Use rb_ensure() to release FBuffer instead of rb_rescue(). A reproducer: o = Object.new def o.to_json(a) = throw :a a = ["make heap allocation"*100, o] 10.times do 100_000.times do catch(:a) { JSON(a) } end puts `ps -o rss= -p #{$$}` end https://github.com/ruby/json/commit/9b7b648ecd --- ext/json/fbuffer/fbuffer.h | 5 +---- ext/json/generator/generator.c | 18 +++++------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index d5fd8ac6d71f78..7d57a87b14ff5e 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -283,13 +283,10 @@ static VALUE fbuffer_finalize(FBuffer *fb) { if (fb->io) { fbuffer_flush(fb); - fbuffer_free(fb); rb_io_flush(fb->io); return fb->io; } else { - VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); - fbuffer_free(fb); - return result; + return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); } } diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index d04c8a90797b7d..72155abe523358 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1444,16 +1444,14 @@ static VALUE generate_json_try(VALUE d) data->func(data->buffer, data, data->obj); - return Qnil; + return fbuffer_finalize(data->buffer); } -static VALUE generate_json_rescue(VALUE d, VALUE exc) +static VALUE generate_json_ensure(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); - rb_exc_raise(exc); - return Qundef; } @@ -1474,9 +1472,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, .obj = obj, .func = func }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - - return fbuffer_finalize(&buffer); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* call-seq: @@ -1522,9 +1518,7 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) .obj = obj, .func = generate_json }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - - return fbuffer_finalize(&buffer); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) @@ -2030,9 +2024,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) .obj = obj, .func = generate_json, }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - - return fbuffer_finalize(&buffer); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* From 2c88500c0825c6f3f0671d1718e660c85eff691d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 31 Oct 2025 13:54:02 -0400 Subject: [PATCH 0719/2435] ZJIT: Simplify some profiling APIs (#15017) --- zjit/src/hir.rs | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 94becee9c8796a..5a609670f4b9ba 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2126,23 +2126,6 @@ impl Function { self.push_insn(block, Insn::GuardType { val, guard_type, state }) } - fn likely_is_fixnum(&self, val: InsnId, profiled_type: ProfiledType) -> bool { - self.is_a(val, types::Fixnum) || profiled_type.is_fixnum() - } - - fn coerce_to_fixnum(&mut self, block: BlockId, val: InsnId, state: InsnId) -> InsnId { - if self.is_a(val, types::Fixnum) { return val; } - self.push_insn(block, Insn::GuardType { val, guard_type: types::Fixnum, state }) - } - - fn arguments_likely_fixnums(&mut self, left: InsnId, right: InsnId, state: InsnId) -> bool { - let frame_state = self.frame_state(state); - let iseq_insn_idx = frame_state.insn_idx; - let left_profiled_type = self.profiled_type_of_at(left, iseq_insn_idx).unwrap_or_default(); - let right_profiled_type = self.profiled_type_of_at(right, iseq_insn_idx).unwrap_or_default(); - self.likely_is_fixnum(left, left_profiled_type) && self.likely_is_fixnum(right, right_profiled_type) - } - fn count_fancy_call_features(&mut self, block: BlockId, ci_flags: c_uint) { use Counter::*; if 0 != ci_flags & VM_CALL_ARGS_SPLAT { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_splat)); } @@ -2161,14 +2144,14 @@ impl Function { self.push_insn_id(block, orig_insn_id); return; } - if self.arguments_likely_fixnums(left, right, state) { + if self.likely_a(left, types::Fixnum, state) && self.likely_a(right, types::Fixnum, state) { if bop == BOP_NEQ { // For opt_neq, the interpreter checks that both neq and eq are unchanged. self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_EQ }, state }); } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop }, state }); - let left = self.coerce_to_fixnum(block, left, state); - let right = self.coerce_to_fixnum(block, right, state); + let left = self.coerce_to(block, left, types::Fixnum, state); + let right = self.coerce_to(block, right, types::Fixnum, state); let result = self.push_insn(block, f(left, right)); self.make_equal_to(orig_insn_id, result); self.insn_types[result.0] = self.infer_type(result); @@ -2567,8 +2550,8 @@ impl Function { let high_is_fix = self.is_a(high, types::Fixnum); if low_is_fix || high_is_fix { - let low_fix = self.coerce_to_fixnum(block, low, state); - let high_fix = self.coerce_to_fixnum(block, high, state); + let low_fix = self.coerce_to(block, low, types::Fixnum, state); + let high_fix = self.coerce_to(block, high, types::Fixnum, state); let replacement = self.push_insn(block, Insn::NewRangeFixnum { low: low_fix, high: high_fix, flag, state }); self.make_equal_to(insn_id, replacement); self.insn_types[replacement.0] = self.infer_type(replacement); From 980e18496e1aafc642b199d24c81ab4a8afb3abb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 31 Oct 2025 11:08:13 -0700 Subject: [PATCH 0720/2435] namespace.c: Fix -Wunused-function warnings --- namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index 847aff9f872965..0f0230b5fdbd9a 100644 --- a/namespace.c +++ b/namespace.c @@ -876,7 +876,7 @@ Init_enable_namespace(void) } } -#ifdef RUBY_DEBUG +#if RUBY_DEBUG > 0 /* :nodoc: */ static VALUE From ab01fcc5123205cdff6e566c2b686e7ab3ed383f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 31 Oct 2025 12:47:55 -0700 Subject: [PATCH 0721/2435] ZJIT: Let AssemblerPanicHook write into stderr (#15019) --- zjit/src/backend/lir.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index a90e80bf4187b1..584251de802bf2 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2290,7 +2290,7 @@ pub struct AssemblerPanicHook { impl AssemblerPanicHook { /// Maximum number of lines [`Self::dump_asm`] is allowed to dump by default. /// When --zjit-dump-lir is given, this limit is ignored. - const MAX_DUMP_LINES: usize = 40; + const MAX_DUMP_LINES: usize = 10; /// Install a panic hook to dump Assembler with insn_idx on dev builds. /// This returns shared references to the previous hook and insn_idx. @@ -2340,12 +2340,12 @@ impl AssemblerPanicHook { (insn_idx.saturating_sub(Self::MAX_DUMP_LINES / 2), insn_idx.saturating_add(Self::MAX_DUMP_LINES / 2)) }; - println!("Failed to compile LIR at insn_idx={insn_idx}:"); + eprintln!("Failed to compile LIR at insn_idx={insn_idx}:"); for (idx, line) in lines.iter().enumerate().filter(|(idx, _)| (min_idx..=max_idx).contains(idx)) { if idx == insn_idx && line.starts_with(" ") { - println!("{BOLD_BEGIN}=>{}{BOLD_END}", &line[" ".len()..]); + eprintln!("{BOLD_BEGIN}=>{}{BOLD_END}", &line[" ".len()..]); } else { - println!("{line}"); + eprintln!("{line}"); } } } From 7688b05098af501642b1930ac1091dbb6241285e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 31 Oct 2025 15:48:59 -0400 Subject: [PATCH 0722/2435] ZJIT: Optimize VM_METHOD_TYPE_ALIAS (#15018) Just loop until you find a non-alias. --- zjit/src/hir.rs | 20 +++++++-- zjit/src/hir/opt_tests.rs | 87 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5a609670f4b9ba..955d33a90621e6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2276,7 +2276,11 @@ impl Function { // Load an overloaded cme if applicable. See vm_search_cc(). // It allows you to use a faster ISEQ if possible. cme = unsafe { rb_check_overloaded_cme(cme, ci) }; - let def_type = unsafe { get_cme_def_type(cme) }; + let mut def_type = unsafe { get_cme_def_type(cme) }; + while def_type == VM_METHOD_TYPE_ALIAS { + cme = unsafe { rb_aliased_callable_method_entry(cme) }; + def_type = unsafe { get_cme_def_type(cme) }; + } if def_type == VM_METHOD_TYPE_ISEQ { // TODO(max): Allow non-iseq; cache cme // Only specialize positional-positional calls @@ -2453,7 +2457,11 @@ impl Function { // Load an overloaded cme if applicable. See vm_search_cc(). // It allows you to use a faster ISEQ if possible. cme = unsafe { rb_check_overloaded_cme(cme, ci) }; - let def_type = unsafe { get_cme_def_type(cme) }; + let mut def_type = unsafe { get_cme_def_type(cme) }; + while def_type == VM_METHOD_TYPE_ALIAS { + cme = unsafe { rb_aliased_callable_method_entry(cme) }; + def_type = unsafe { get_cme_def_type(cme) }; + } self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; } @@ -2810,13 +2818,17 @@ impl Function { }; // Do method lookup - let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; + let mut method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; if method.is_null() { return Err(()); } // Filter for C methods - let def_type = unsafe { get_cme_def_type(method) }; + let mut def_type = unsafe { get_cme_def_type(method) }; + while def_type == VM_METHOD_TYPE_ALIAS { + method = unsafe { rb_aliased_callable_method_entry(method) }; + def_type = unsafe { get_cme_def_type(method) }; + } if def_type != VM_METHOD_TYPE_CFUNC { return Err(()); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 52471220234e8c..8a9acb59bb2527 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -638,6 +638,93 @@ mod hir_opt_tests { "); } + #[test] + fn test_optimize_send_without_block_to_aliased_iseq() { + eval(" + def foo = 1 + alias bar foo + alias baz bar + def test = baz + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_send_without_block_to_aliased_cfunc() { + eval(" + alias bar itself + alias baz bar + def test = baz + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_optimize_send_to_aliased_cfunc() { + eval(" + class C < Array + alias fun_new_map map + end + def test(o) = o.fun_new_map {|e| e } + test C.new; test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:BasicObject = GetLocal l0, EP@3 + PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v25:ArraySubclass[class_exact:C] = GuardType v13, ArraySubclass[class_exact:C] + v26:BasicObject = CCallWithFrame fun_new_map@0x1038, v25, block=0x1040 + v16:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v26 + "); + } + #[test] fn test_optimize_nonexistent_top_level_call() { eval(" From 4fc9ad5264af5f866520511a58ce57ea3af099cb Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 31 Oct 2025 08:51:03 -0700 Subject: [PATCH 0723/2435] Remove always true conditonals in vm_populate_cc --- vm_insnhelper.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index cbed6143297b81..1f6eb9222c297b 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2156,17 +2156,7 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid) RB_DEBUG_COUNTER_INC(cc_not_found_in_ccs); - const rb_callable_method_entry_t *cme; - - if (ccs) { - cme = ccs->cme; - cme = UNDEFINED_METHOD_ENTRY_P(cme) ? NULL : cme; - - VM_ASSERT(cme == rb_callable_method_entry(klass, mid)); - } - else { - cme = rb_callable_method_entry(klass, mid); - } + const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); VM_ASSERT(cme == NULL || IMEMO_TYPE_P(cme, imemo_ment)); @@ -2180,9 +2170,9 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid) METHOD_ENTRY_CACHED_SET((struct rb_callable_method_entry_struct *)cme); - if (ccs == NULL) { - VM_ASSERT(cc_tbl); + VM_ASSERT(cc_tbl); + { VALUE ccs_obj; if (UNLIKELY(rb_managed_id_table_lookup(cc_tbl, mid, &ccs_obj))) { ccs = (struct rb_class_cc_entries *)ccs_obj; From b931199d458fe24167be51157867ec9e6fa12eaa Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 31 Oct 2025 08:08:57 -0700 Subject: [PATCH 0724/2435] Avoid duping cc table when cme == NULL --- vm_insnhelper.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 1f6eb9222c297b..27177d9b13ef75 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2143,17 +2143,6 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid) { ASSERT_vm_locking(); - VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); - const VALUE original_cc_table = cc_tbl; - struct rb_class_cc_entries *ccs = NULL; - - if (!cc_tbl) { - cc_tbl = rb_vm_cc_table_create(1); - } - else if (rb_multi_ractor_p()) { - cc_tbl = rb_vm_cc_table_dup(cc_tbl); - } - RB_DEBUG_COUNTER_INC(cc_not_found_in_ccs); const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); @@ -2166,12 +2155,23 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid) return &vm_empty_cc; } + VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); + const VALUE original_cc_table = cc_tbl; + if (!cc_tbl) { + // Is this possible after rb_callable_method_entry ? + cc_tbl = rb_vm_cc_table_create(1); + } + else if (rb_multi_ractor_p()) { + cc_tbl = rb_vm_cc_table_dup(cc_tbl); + } + VM_ASSERT(cme == rb_callable_method_entry(klass, mid)); METHOD_ENTRY_CACHED_SET((struct rb_callable_method_entry_struct *)cme); VM_ASSERT(cc_tbl); + struct rb_class_cc_entries *ccs = NULL; { VALUE ccs_obj; if (UNLIKELY(rb_managed_id_table_lookup(cc_tbl, mid, &ccs_obj))) { From cc8cfbcd8592cb003c58558ce63c117461a5ef32 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 31 Oct 2025 21:09:25 +0000 Subject: [PATCH 0725/2435] ZJIT: Standardize variable name for callable method entry (#15021) --- zjit/src/hir.rs | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 955d33a90621e6..55bec186512799 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2677,9 +2677,9 @@ impl Function { self.infer_types(); } - fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { + fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, cme: *const rb_callable_method_entry_struct, state: InsnId) { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme }, state }); } /// Optimize SendWithoutBlock that land in a C method to a direct CCall without @@ -2715,19 +2715,19 @@ impl Function { }; // Do method lookup - let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; - if method.is_null() { + let cme: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; + if cme.is_null() { return Err(()); } // Filter for C methods - let def_type = unsafe { get_cme_def_type(method) }; + let def_type = unsafe { get_cme_def_type(cme) }; if def_type != VM_METHOD_TYPE_CFUNC { return Err(()); } // Find the `argc` (arity) of the C method, which describes the parameters it expects - let cfunc = unsafe { get_cme_def_body_cfunc(method) }; + let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; let cfunc_argc = unsafe { get_mct_argc(cfunc) }; match cfunc_argc { 0.. => { @@ -2747,7 +2747,7 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } @@ -2769,7 +2769,7 @@ impl Function { cd, cfunc, args: cfunc_args, - cme: method, + cme, name: method_id, state, return_type: types::BasicObject, @@ -2818,23 +2818,23 @@ impl Function { }; // Do method lookup - let mut method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; - if method.is_null() { + let mut cme: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) }; + if cme.is_null() { return Err(()); } // Filter for C methods - let mut def_type = unsafe { get_cme_def_type(method) }; + let mut def_type = unsafe { get_cme_def_type(cme) }; while def_type == VM_METHOD_TYPE_ALIAS { - method = unsafe { rb_aliased_callable_method_entry(method) }; - def_type = unsafe { get_cme_def_type(method) }; + cme = unsafe { rb_aliased_callable_method_entry(cme) }; + def_type = unsafe { get_cme_def_type(cme) }; } if def_type != VM_METHOD_TYPE_CFUNC { return Err(()); } // Find the `argc` (arity) of the C method, which describes the parameters it expects - let cfunc = unsafe { get_cme_def_body_cfunc(method) }; + let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; let cfunc_argc = unsafe { get_mct_argc(cfunc) }; match cfunc_argc { 0.. => { @@ -2854,14 +2854,14 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } - let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + let props = ZJITState::get_method_annotations().get_cfunc_properties(cme); if props.is_none() && get_option!(stats) { - count_not_annotated_cfunc(fun, block, method); + count_not_annotated_cfunc(fun, block, cme); } let props = props.unwrap_or_default(); @@ -2897,13 +2897,13 @@ impl Function { fun.make_equal_to(send_insn_id, ccall); } else { if get_option!(stats) { - count_not_inlined_cfunc(fun, block, method); + count_not_inlined_cfunc(fun, block, cme); } let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, - cme: method, + cme, name: method_id, state, return_type, @@ -2923,7 +2923,7 @@ impl Function { if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { fun.count_fancy_call_features(block, ci_flags); } else { - fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); @@ -2935,9 +2935,9 @@ impl Function { } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); - let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + let props = ZJITState::get_method_annotations().get_cfunc_properties(cme); if props.is_none() && get_option!(stats) { - count_not_annotated_cfunc(fun, block, method); + count_not_annotated_cfunc(fun, block, cme); } let props = props.unwrap_or_default(); @@ -2956,7 +2956,7 @@ impl Function { // No inlining; emit a call if get_option!(stats) { - count_not_inlined_cfunc(fun, block, method); + count_not_inlined_cfunc(fun, block, cme); } let return_type = props.return_type; let elidable = props.elidable; @@ -2964,7 +2964,7 @@ impl Function { cfunc, recv, args, - cme: method, + cme, name: method_id, state, return_type, From 8db30094fce210cd193b571d7c4b7e5702ed1ca9 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 31 Oct 2025 18:58:21 -0400 Subject: [PATCH 0726/2435] ZJIT: Fix incorrect elision of call to BasicObject#!= rb_obj_not_equal() uses rb_funcall(), so it's not `no_gc`, `leaf`, nor `elidable`. --- zjit/src/cruby_methods.rs | 2 +- zjit/src/hir/opt_tests.rs | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index bd3409ff07ce5c..0af3be1819dac7 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -221,7 +221,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cInteger, "^", inline_integer_xor); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 8a9acb59bb2527..d0b3203ac186e6 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -502,6 +502,52 @@ mod hir_opt_tests { "); } + #[test] + fn neq_with_side_effect_not_elided () { + let result = eval(" + class CustomEq + attr_reader :count + + def ==(o) + @count = @count.to_i + 1 + self.equal?(o) + end + end + + def test(object) + # intentionally unused, but also can't assign to underscore + object != object + nil + end + + custom = CustomEq.new + test(custom) + test(custom) + + custom.count + "); + assert_eq!(VALUE::fixnum_from_usize(2), result); + assert_snapshot!(hir_string("test"), @r" + fn test@:13: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(CustomEq@0x1000, !=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(CustomEq@0x1000) + v28:HeapObject[class_exact:CustomEq] = GuardType v9, HeapObject[class_exact:CustomEq] + v29:BoolExact = CCallWithFrame !=@0x1038, v28, v9 + v19:NilClass = Const Value(nil) + CheckInterrupts + Return v19 + "); + } + #[test] fn test_replace_guard_if_known_fixnum() { eval(" From 17a7b4e03170599c942d2a7f1bbac6247aa3bd56 Mon Sep 17 00:00:00 2001 From: Sam Westerman Date: Sat, 1 Nov 2025 00:18:01 +0000 Subject: [PATCH 0727/2435] [DOC] Fix typo in `Hash#compare_by_identity` docs --- hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hash.c b/hash.c index 603cab76db33d9..97a303c4d5851c 100644 --- a/hash.c +++ b/hash.c @@ -4649,7 +4649,7 @@ rb_hash_compact_bang(VALUE hash) * returns +self+: * * By default, two keys are considered to be the same key - * if and only if they are _equal_ objects (per method #==): + * if and only if they are _equal_ objects (per method #eql?): * * h = {} * h['x'] = 0 From 8c1900528b527e5948fb167182937196ff611600 Mon Sep 17 00:00:00 2001 From: git Date: Sat, 1 Nov 2025 06:50:27 +0000 Subject: [PATCH 0728/2435] Update bundled gems list as of 2025-10-31 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index abaf585eabf609..fb700d88e17964 100644 --- a/NEWS.md +++ b/NEWS.md @@ -161,7 +161,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.15.0 +* rdoc 6.15.1 * win32ole 1.9.2 * irb 1.15.2 * reline 0.6.2 diff --git a/gems/bundled_gems b/gems/bundled_gems index 19ed86f4534994..563f5e762750be 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.15.0 https://github.com/ruby/rdoc ac2a6fbf62b584a8325a665a9e7b368388bc7df6 +rdoc 6.15.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.2 https://github.com/ruby/irb d43c3d764ae439706aa1b26a3ec299cc45eaed5b reline 0.6.2 https://github.com/ruby/reline From 6e2e7a335546275edf15a1617663d5a1c03c6188 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 09:24:18 +0100 Subject: [PATCH 0729/2435] [ruby/json] parser.c: Extract `json_parse_number` https://github.com/ruby/json/commit/2681b23b87 --- ext/json/parser/parser.c | 184 ++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index ca8f501539c322..565f8e020386b1 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1022,6 +1022,95 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig return Qfalse; } +static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig *config, bool negative, const char *start) +{ + bool integer = true; + + // Variables for Ryu optimization - extract digits during parsing + uint64_t mantissa = 0; + int mantissa_digits = 0; + int32_t exponent = 0; + int decimal_point_pos = -1; + + const char first_digit = *state->cursor; + + // Parse integer part and extract mantissa digits + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + mantissa = mantissa * 10 + (*state->cursor - '0'); + mantissa_digits++; + state->cursor++; + } + + if (RB_UNLIKELY(first_digit == '0' && mantissa_digits > 1 || negative && mantissa_digits == 0)) { + raise_parse_error_at("invalid number: %s", state, start); + } + + // Parse fractional part + if ((state->cursor < state->end) && (*state->cursor == '.')) { + integer = false; + decimal_point_pos = mantissa_digits; // Remember position of decimal point + state->cursor++; + + if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { + raise_parse_error_at("invalid number: %s", state, start); + } + + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + mantissa = mantissa * 10 + (*state->cursor - '0'); + mantissa_digits++; + state->cursor++; + } + } + + // Parse exponent + if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { + integer = false; + state->cursor++; + + bool negative_exponent = false; + if ((state->cursor < state->end) && ((*state->cursor == '-') || (*state->cursor == '+'))) { + negative_exponent = (*state->cursor == '-'); + state->cursor++; + } + + if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { + raise_parse_error_at("invalid number: %s", state, start); + } + + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + exponent = exponent * 10 + (*state->cursor - '0'); + state->cursor++; + } + + if (negative_exponent) { + exponent = -exponent; + } + } + + if (integer) { + return json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor); + } + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) { + exponent -= (mantissa_digits - decimal_point_pos); + } + + return json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor); +} + +static inline VALUE json_parse_positive_number(JSON_ParserState *state, JSON_ParserConfig *config) +{ + return json_parse_number(state, config, false, state->cursor); +} + +static inline VALUE json_parse_negative_number(JSON_ParserState *state, JSON_ParserConfig *config) +{ + const char *start = state->cursor; + state->cursor++; + return json_parse_number(state, config, true, start); +} + static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) { json_eat_whitespace(state); @@ -1072,7 +1161,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) raise_parse_error("unexpected token %s", state); break; - case '-': + case '-': { // Note: memcmp with a small power of two compile to an integer comparison if ((state->end - state->cursor >= 9) && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { if (config->allow_nan) { @@ -1082,95 +1171,12 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) raise_parse_error("unexpected token %s", state); } } - // Fallthrough - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - bool integer = true; - - // Variables for Ryu optimization - extract digits during parsing - uint64_t mantissa = 0; - int mantissa_digits = 0; - int32_t exponent = 0; - bool negative = false; - int decimal_point_pos = -1; - - // /\A-?(0|[1-9]\d*)(\.\d+)?([Ee][-+]?\d+)?/ - const char *start = state->cursor; - - // Handle optional negative sign - if (*state->cursor == '-') { - negative = true; - state->cursor++; - if (state->cursor >= state->end || !rb_isdigit(*state->cursor)) { - raise_parse_error_at("invalid number: %s", state, start); - } - } - - // Parse integer part and extract mantissa digits - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - mantissa = mantissa * 10 + (*state->cursor - '0'); - mantissa_digits++; - state->cursor++; - } - - if (RB_UNLIKELY(start[0] == '0' && mantissa_digits > 1)) { - raise_parse_error_at("invalid number: %s", state, start); - } else if (RB_UNLIKELY(mantissa_digits > 1 && negative && start[1] == '0')) { - raise_parse_error_at("invalid number: %s", state, start); - } - - // Parse fractional part - if ((state->cursor < state->end) && (*state->cursor == '.')) { - integer = false; - decimal_point_pos = mantissa_digits; // Remember position of decimal point - state->cursor++; - - if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { - raise_parse_error_at("invalid number: %s", state, start); - } - - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - mantissa = mantissa * 10 + (*state->cursor - '0'); - mantissa_digits++; - state->cursor++; - } - } - - // Parse exponent - if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { - integer = false; - state->cursor++; - - bool negative_exponent = false; - if ((state->cursor < state->end) && ((*state->cursor == '-') || (*state->cursor == '+'))) { - negative_exponent = (*state->cursor == '-'); - state->cursor++; - } - - if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { - raise_parse_error_at("invalid number: %s", state, start); - } - - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - exponent = exponent * 10 + (*state->cursor - '0'); - state->cursor++; - } - - if (negative_exponent) { - exponent = -exponent; - } - } - - if (integer) { - return json_push_value(state, config, json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor)); - } - - // Adjust exponent based on decimal point position - if (decimal_point_pos >= 0) { - exponent -= (mantissa_digits - decimal_point_pos); - } - - return json_push_value(state, config, json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor)); + return json_push_value(state, config, json_parse_negative_number(state, config)); + break; } + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + return json_push_value(state, config, json_parse_positive_number(state, config)); + break; case '"': { // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} return json_parse_string(state, config, false); From a6bdf52bc9005f4c8709736588fca53787ad3d78 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 09:42:24 +0100 Subject: [PATCH 0730/2435] [ruby/json] parser.c: Extract json_parse_digits https://github.com/ruby/json/commit/1bf405ecc6 --- ext/json/parser/parser.c | 49 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 565f8e020386b1..d69cc28a92ff46 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1022,24 +1022,28 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig return Qfalse; } +static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator) +{ + const char *start = state->cursor; + while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { + *accumulator = *accumulator * 10 + (*state->cursor - '0'); + state->cursor++; + } + return (int)(state->cursor - start); +} + static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig *config, bool negative, const char *start) { bool integer = true; + const char first_digit = *state->cursor; // Variables for Ryu optimization - extract digits during parsing - uint64_t mantissa = 0; - int mantissa_digits = 0; int32_t exponent = 0; int decimal_point_pos = -1; - - const char first_digit = *state->cursor; + uint64_t mantissa = 0; // Parse integer part and extract mantissa digits - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - mantissa = mantissa * 10 + (*state->cursor - '0'); - mantissa_digits++; - state->cursor++; - } + int mantissa_digits = json_parse_digits(state, &mantissa); if (RB_UNLIKELY(first_digit == '0' && mantissa_digits > 1 || negative && mantissa_digits == 0)) { raise_parse_error_at("invalid number: %s", state, start); @@ -1051,19 +1055,16 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig decimal_point_pos = mantissa_digits; // Remember position of decimal point state->cursor++; - if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { - raise_parse_error_at("invalid number: %s", state, start); - } + int fractional_digits = json_parse_digits(state, &mantissa); + mantissa_digits += fractional_digits; - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - mantissa = mantissa * 10 + (*state->cursor - '0'); - mantissa_digits++; - state->cursor++; + if (RB_UNLIKELY(!fractional_digits)) { + raise_parse_error_at("invalid number: %s", state, start); } } // Parse exponent - if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { + if ((state->cursor < state->end) && ((rb_tolower(*state->cursor) == 'e'))) { integer = false; state->cursor++; @@ -1073,18 +1074,14 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig state->cursor++; } - if (state->cursor == state->end || !rb_isdigit(*state->cursor)) { - raise_parse_error_at("invalid number: %s", state, start); - } + uint64_t abs_exponent = 0; + int exponent_digits = json_parse_digits(state, &abs_exponent); - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - exponent = exponent * 10 + (*state->cursor - '0'); - state->cursor++; + if (RB_UNLIKELY(!exponent_digits)) { + raise_parse_error_at("invalid number: %s", state, start); } - if (negative_exponent) { - exponent = -exponent; - } + exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent); } if (integer) { From b3d5c96613f38f7741e829403872be11491e42b6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 10:27:15 +0100 Subject: [PATCH 0731/2435] [ruby/json] parser.c: Introduce `peek()` and `eos()` helpers Encapsulate pointer arithmetic to reduce possibility of mistakes. https://github.com/ruby/json/commit/8b39407225 --- ext/json/parser/parser.c | 154 +++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 69 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index d69cc28a92ff46..90460f3a2a008b 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -406,6 +406,18 @@ typedef struct JSON_ParserStateStruct { int current_nesting; } JSON_ParserState; +static inline bool eos(JSON_ParserState *state) { + return state->cursor >= state->end; +} + +static inline char peek(JSON_ParserState *state) +{ + if (RB_UNLIKELY(eos(state))) { + return 0; + } + return *state->cursor; +} + static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out) { const char *cursor = state->cursor; @@ -571,7 +583,7 @@ json_eat_comments(JSON_ParserState *state) raise_parse_error_at("unexpected end of input, expected closing '*/'", state, state->end); } else { state->cursor++; - if (state->cursor < state->end && *state->cursor == '/') { + if (peek(state) == '/') { state->cursor++; break; } @@ -591,11 +603,12 @@ json_eat_comments(JSON_ParserState *state) static inline void json_eat_whitespace(JSON_ParserState *state) { - while (state->cursor < state->end && RB_UNLIKELY(whitespace[(unsigned char)*state->cursor])) { - if (RB_LIKELY(*state->cursor != '/')) { - state->cursor++; - } else { + unsigned char cursor; + while (RB_UNLIKELY(whitespace[cursor = (unsigned char)peek(state)])) { + if (RB_UNLIKELY(cursor == '/')) { json_eat_comments(state); + } else { + state->cursor++; } } } @@ -980,7 +993,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) #endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */ #endif /* HAVE_SIMD */ - while (state->cursor < state->end) { + while (!eos(state)) { if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) { return 1; } @@ -1025,8 +1038,10 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator) { const char *start = state->cursor; - while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) { - *accumulator = *accumulator * 10 + (*state->cursor - '0'); + char next_char; + + while (rb_isdigit(next_char = peek(state))) { + *accumulator = *accumulator * 10 + (next_char - '0'); state->cursor++; } return (int)(state->cursor - start); @@ -1050,7 +1065,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig } // Parse fractional part - if ((state->cursor < state->end) && (*state->cursor == '.')) { + if (peek(state) == '.') { integer = false; decimal_point_pos = mantissa_digits; // Remember position of decimal point state->cursor++; @@ -1064,13 +1079,14 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig } // Parse exponent - if ((state->cursor < state->end) && ((rb_tolower(*state->cursor) == 'e'))) { + if (rb_tolower(peek(state)) == 'e') { integer = false; state->cursor++; bool negative_exponent = false; - if ((state->cursor < state->end) && ((*state->cursor == '-') || (*state->cursor == '+'))) { - negative_exponent = (*state->cursor == '-'); + const char next_char = peek(state); + if (next_char == '-' || next_char == '+') { + negative_exponent = next_char == '-'; state->cursor++; } @@ -1111,11 +1127,8 @@ static inline VALUE json_parse_negative_number(JSON_ParserState *state, JSON_Par static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) { json_eat_whitespace(state); - if (state->cursor >= state->end) { - raise_parse_error("unexpected end of input", state); - } - switch (*state->cursor) { + switch (peek(state)) { case 'n': if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "null", 4) == 0)) { state->cursor += 4; @@ -1184,7 +1197,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) json_eat_whitespace(state); long stack_head = state->stack->head; - if ((state->cursor < state->end) && (*state->cursor == ']')) { + if (peek(state) == ']') { state->cursor++; return json_push_value(state, config, json_decode_array(state, config, 0)); } else { @@ -1199,26 +1212,26 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) while (true) { json_eat_whitespace(state); - if (state->cursor < state->end) { - if (*state->cursor == ']') { - state->cursor++; - long count = state->stack->head - stack_head; - state->current_nesting--; - state->in_array--; - return json_push_value(state, config, json_decode_array(state, config, count)); - } + const char next_char = peek(state); - if (*state->cursor == ',') { - state->cursor++; - if (config->allow_trailing_comma) { - json_eat_whitespace(state); - if ((state->cursor < state->end) && (*state->cursor == ']')) { - continue; - } + if (RB_LIKELY(next_char == ',')) { + state->cursor++; + if (config->allow_trailing_comma) { + json_eat_whitespace(state); + if (peek(state) == ']') { + continue; } - json_parse_any(state, config); - continue; } + json_parse_any(state, config); + continue; + } + + if (next_char == ']') { + state->cursor++; + long count = state->stack->head - stack_head; + state->current_nesting--; + state->in_array--; + return json_push_value(state, config, json_decode_array(state, config, count)); } raise_parse_error("expected ',' or ']' after array value", state); @@ -1232,7 +1245,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) json_eat_whitespace(state); long stack_head = state->stack->head; - if ((state->cursor < state->end) && (*state->cursor == '}')) { + if (peek(state) == '}') { state->cursor++; return json_push_value(state, config, json_decode_object(state, config, 0)); } else { @@ -1241,13 +1254,13 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); } - if (*state->cursor != '"') { + if (peek(state) != '"') { raise_parse_error("expected object key, got %s", state); } json_parse_string(state, config, true); json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { + if (peek(state) != ':') { raise_parse_error("expected ':' after object key", state); } state->cursor++; @@ -1258,46 +1271,45 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) while (true) { json_eat_whitespace(state); - if (state->cursor < state->end) { - if (*state->cursor == '}') { - state->cursor++; - state->current_nesting--; - size_t count = state->stack->head - stack_head; + const char next_char = peek(state); + if (next_char == '}') { + state->cursor++; + state->current_nesting--; + size_t count = state->stack->head - stack_head; - // Temporary rewind cursor in case an error is raised - const char *final_cursor = state->cursor; - state->cursor = object_start_cursor; - VALUE object = json_decode_object(state, config, count); - state->cursor = final_cursor; + // Temporary rewind cursor in case an error is raised + const char *final_cursor = state->cursor; + state->cursor = object_start_cursor; + VALUE object = json_decode_object(state, config, count); + state->cursor = final_cursor; - return json_push_value(state, config, object); - } + return json_push_value(state, config, object); + } - if (*state->cursor == ',') { - state->cursor++; - json_eat_whitespace(state); + if (next_char == ',') { + state->cursor++; + json_eat_whitespace(state); - if (config->allow_trailing_comma) { - if ((state->cursor < state->end) && (*state->cursor == '}')) { - continue; - } + if (config->allow_trailing_comma) { + if (peek(state) == '}') { + continue; } + } - if (*state->cursor != '"') { - raise_parse_error("expected object key, got: %s", state); - } - json_parse_string(state, config, true); + if (RB_UNLIKELY(peek(state) != '"')) { + raise_parse_error("expected object key, got: %s", state); + } + json_parse_string(state, config, true); - json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { - raise_parse_error("expected ':' after object key, got: %s", state); - } - state->cursor++; + json_eat_whitespace(state); + if (RB_UNLIKELY(peek(state) != ':')) { + raise_parse_error("expected ':' after object key, got: %s", state); + } + state->cursor++; - json_parse_any(state, config); + json_parse_any(state, config); - continue; - } + continue; } raise_parse_error("expected ',' or '}' after object value, got: %s", state); @@ -1305,6 +1317,10 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) break; } + case 0: + raise_parse_error("unexpected end of input", state); + break; + default: raise_parse_error("unexpected character: %s", state); break; @@ -1316,7 +1332,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) static void json_ensure_eof(JSON_ParserState *state) { json_eat_whitespace(state); - if (state->cursor != state->end) { + if (!eos(state)) { raise_parse_error("unexpected token at end of stream %s", state); } } From 1942cb219ab50c55b198e4a6173b4d09f3fced84 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 31 Oct 2025 08:31:11 +0100 Subject: [PATCH 0732/2435] [ruby/json] Add test coverage for T_BIGNUM parsing https://github.com/ruby/json/commit/f0150e2944 --- test/json/json_parser_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 9d387cb808925a..30188c4ebdea32 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -131,6 +131,12 @@ def test_parse_numbers capture_output { assert_equal(Float::INFINITY, parse("23456789012E666")) } end + def test_parse_bignum + bignum = Integer('1234567890' * 10) + assert_equal(bignum, JSON.parse(bignum.to_s)) + assert_equal(bignum.to_f, JSON.parse(bignum.to_s + ".0")) + end + def test_parse_bigdecimals assert_equal(BigDecimal, JSON.parse('{"foo": 9.01234567890123456789}', decimal_class: BigDecimal)["foo"].class) assert_equal(BigDecimal("0.901234567890123456789E1"),JSON.parse('{"foo": 9.01234567890123456789}', decimal_class: BigDecimal)["foo"] ) From bca8fce78f588c1119c56945b99dd29d95a67a0c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 10:54:03 +0100 Subject: [PATCH 0733/2435] [ruby/json] parser.c: Introduce `rest()` helper https://github.com/ruby/json/commit/11f4e7b7be --- ext/json/parser/parser.c | 74 +++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 90460f3a2a008b..3bd654dc3b8e71 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -406,6 +406,10 @@ typedef struct JSON_ParserStateStruct { int current_nesting; } JSON_ParserState; +static inline ssize_t rest(JSON_ParserState *state) { + return state->end - state->cursor; +} + static inline bool eos(JSON_ParserState *state) { return state->cursor >= state->end; } @@ -564,39 +568,39 @@ static const bool whitespace[256] = { static void json_eat_comments(JSON_ParserState *state) { - if (state->cursor + 1 < state->end) { - switch (state->cursor[1]) { - case '/': { - state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); - if (!state->cursor) { - state->cursor = state->end; - } else { - state->cursor++; - } - break; + const char *start = state->cursor; + state->cursor++; + + switch (peek(state)) { + case '/': { + state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); + if (!state->cursor) { + state->cursor = state->end; + } else { + state->cursor++; } - case '*': { - state->cursor += 2; - while (true) { - state->cursor = memchr(state->cursor, '*', state->end - state->cursor); - if (!state->cursor) { - raise_parse_error_at("unexpected end of input, expected closing '*/'", state, state->end); - } else { - state->cursor++; - if (peek(state) == '/') { - state->cursor++; - break; - } - } + break; + } + case '*': { + state->cursor++; + + while (true) { + const char *next_match = memchr(state->cursor, '*', state->end - state->cursor); + if (!next_match) { + raise_parse_error_at("unterminated comment, expected closing '*/'", state, start); + } + + state->cursor = next_match + 1; + if (peek(state) == '/') { + state->cursor++; + break; } - break; } - default: - raise_parse_error("unexpected token %s", state); - break; + break; } - } else { - raise_parse_error("unexpected token %s", state); + default: + raise_parse_error_at("unexpected token %s", state, start); + break; } } @@ -1130,7 +1134,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) switch (peek(state)) { case 'n': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "null", 4) == 0)) { + if (rest(state) >= 4 && (memcmp(state->cursor, "null", 4) == 0)) { state->cursor += 4; return json_push_value(state, config, Qnil); } @@ -1138,7 +1142,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) raise_parse_error("unexpected token %s", state); break; case 't': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "true", 4) == 0)) { + if (rest(state) >= 4 && (memcmp(state->cursor, "true", 4) == 0)) { state->cursor += 4; return json_push_value(state, config, Qtrue); } @@ -1147,7 +1151,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) break; case 'f': // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 5) && (memcmp(state->cursor + 1, "alse", 4) == 0)) { + if (rest(state) >= 5 && (memcmp(state->cursor + 1, "alse", 4) == 0)) { state->cursor += 5; return json_push_value(state, config, Qfalse); } @@ -1156,7 +1160,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) break; case 'N': // Note: memcmp with a small power of two compile to an integer comparison - if (config->allow_nan && (state->end - state->cursor >= 3) && (memcmp(state->cursor + 1, "aN", 2) == 0)) { + if (config->allow_nan && rest(state) >= 3 && (memcmp(state->cursor + 1, "aN", 2) == 0)) { state->cursor += 3; return json_push_value(state, config, CNaN); } @@ -1164,7 +1168,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) raise_parse_error("unexpected token %s", state); break; case 'I': - if (config->allow_nan && (state->end - state->cursor >= 8) && (memcmp(state->cursor, "Infinity", 8) == 0)) { + if (config->allow_nan && rest(state) >= 8 && (memcmp(state->cursor, "Infinity", 8) == 0)) { state->cursor += 8; return json_push_value(state, config, CInfinity); } @@ -1173,7 +1177,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) break; case '-': { // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 9) && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { + if (rest(state) >= 9 && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { if (config->allow_nan) { state->cursor += 9; return json_push_value(state, config, CMinusInfinity); From 5ce27bef014d394e1c478de78165edaf9af122aa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 1 Nov 2025 13:47:59 +0900 Subject: [PATCH 0734/2435] Flush NEWS.md only when NEW is not given Split flushing NEWS and bumping up versions --- defs/gmake.mk | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index c7eaca91a54992..4069a4b8c9127c 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -538,34 +538,53 @@ spec/%/ spec/%_spec.rb: programs exts PHONY ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) matz: up - $(eval OLD := $(MAJOR).$(MINOR).0) + +matz: OLD := $(MAJOR).$(MINOR).0 ifdef NEW - $(eval MAJOR := $(word 1,$(subst ., ,$(NEW)))) - $(eval MINOR := $(word 2,$(subst ., ,$(NEW)))) +matz: MAJOR := $(word 1,$(subst ., ,$(NEW))) +matz: MINOR := $(word 2,$(subst ., ,$(NEW))) +matz: .WAIT bump_news else - $(eval MINOR := $(shell expr $(MINOR) + 1)) +matz: MINOR := $(shell expr $(MINOR) + 1) +matz: .WAIT reset_news endif - $(eval override NEW := $(MAJOR).$(MINOR).0) - $(eval message := Development of $(NEW) started.) - $(eval files := include/ruby/version.h include/ruby/internal/abi.h) + +matz: .WAIT bump_headers +matz: override NEW := $(MAJOR).$(MINOR).0 +matz: files := include/ruby/version.h include/ruby/internal/abi.h +matz: message := Development of $(NEW) started. + +flush_news: $(GIT_IN_SRC) mv -f NEWS.md doc/NEWS/NEWS-$(OLD).md $(GIT_IN_SRC) commit -m "[DOC] Flush NEWS.md" + +.PHONY: flush_news reset_news bump_news bump_headers + +bump_headers: sed -i~ \ -e "s/^\(#define RUBY_API_VERSION_MAJOR\) .*/\1 $(MAJOR)/" \ -e "s/^\(#define RUBY_API_VERSION_MINOR\) .*/\1 $(MINOR)/" \ -e "s/^\(#define RUBY_ABI_VERSION\) .*/\1 0/" \ $(files:%=$(srcdir)/%) - $(GIT_IN_SRC) add $(files) + +reset_news: flush_news $(BASERUBY) -C $(srcdir) -p -00 \ - -e 'BEGIN {old, new = ARGV.shift(2); STDOUT.reopen("NEWS.md")}' \ + -e 'BEGIN {old, new = ARGV.shift(2); STDOUT.reopen(ARGV.shift)}' \ -e 'case $$.' \ -e 'when 1; $$_.sub!(/Ruby \K[0-9.]+/, new)' \ -e 'when 2; $$_.sub!(/\*\*\K[0-9.]+(?=\*\*)/, old)' \ -e 'end' \ -e 'next if /^[\[ *]/ =~ $$_' \ -e '$$_.sub!(/\n{2,}\z/, "\n\n")' \ - $(OLD) $(NEW) doc/NEWS/NEWS-$(OLD).md - $(GIT_IN_SRC) add NEWS.md + $(OLD) $(NEW) NEWS.md doc/NEWS/NEWS-$(OLD).md + +bump_news: + $(BASERUBY) -C $(srcdir) -p -i \ + -e 'BEGIN {new = ARGV.shift; print gets("").sub(/Ruby \K[0-9.]+/, new)}' \ + $(NEW) NEWS.md + +matz: + $(GIT_IN_SRC) add NEWS.md $(files) $(GIT_IN_SRC) commit -m "$(message)" tags: From db5708045037a159458de741b46e9c47fe430284 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 1 Nov 2025 19:10:19 +0900 Subject: [PATCH 0735/2435] [DOC] How to use `make matz` --- defs/gmake.mk | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index 4069a4b8c9127c..3dcfe9f639a244 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -537,8 +537,13 @@ spec/%/ spec/%_spec.rb: programs exts PHONY ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) -matz: up +# `make matz`: bump up the MINOR; +# Copying NEWS.md to doc/NEWS/, and empty the details in NEWS.md. +# +# `make matz NEW=x.y`: bump up to x.y.0; +# Just update the version in the title of NEWS.md. +matz: up matz: OLD := $(MAJOR).$(MINOR).0 ifdef NEW matz: MAJOR := $(word 1,$(subst ., ,$(NEW))) From babf50e33bb0d9e1f3c37d11c1cfdc50c4f5bc7e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 11:06:32 +0100 Subject: [PATCH 0736/2435] [ruby/json] Use SWAR for parsing integers on little endian machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: https://github.com/ruby/json/pull/878 ``` == Parsing float parsing (2251051 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 23.000 i/100ms Calculating ------------------------------------- after 214.382 (± 0.5%) i/s (4.66 ms/i) - 1.081k in 5.042555s Comparison: before: 189.5 i/s after: 214.4 i/s - 1.13x faster ``` https://github.com/ruby/json/commit/6348ff0891 Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 3bd654dc3b8e71..e591ca2c5a9f85 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1039,11 +1039,61 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig return Qfalse; } +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +// From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/ +// Additional References: +// https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +// http://0x80.pl/notesen/2014-10-12-parsing-decimal-numbers-part-1-swar.html +static inline uint64_t decode_8digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return val; +} + +static inline uint64_t decode_4digits_unrolled(uint32_t val) { + const uint32_t mask = 0x000000FF; + const uint32_t mul1 = 100; + val -= 0x30303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = ((val & mask) * mul1) + (((val >> 16) & mask)); + return val; +} +#endif + static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator) { const char *start = state->cursor; - char next_char; +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + while (rest(state) >= 8) { + uint64_t next_8bytes; + memcpy(&next_8bytes, state->cursor, sizeof(uint64_t)); + + // From: https://github.com/simdjson/simdjson/blob/32b301893c13d058095a07d9868edaaa42ee07aa/include/simdjson/generic/numberparsing.h#L333 + // Branchless version of: http://0x80.pl/articles/swar-digits-validate.html + uint64_t match = (next_8bytes & 0xF0F0F0F0F0F0F0F0) | (((next_8bytes + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4); + + if (match == 0x3333333333333333) { // 8 consecutive digits + *accumulator = (*accumulator * 100000000) + decode_8digits_unrolled(next_8bytes); + state->cursor += 8; + continue; + } + + if ((match & 0xFFFFFFFF) == 0x33333333) { // 4 consecutive digits + *accumulator = (*accumulator * 10000) + decode_4digits_unrolled((uint32_t)next_8bytes); + state->cursor += 4; + break; + } + + break; + } +#endif + + char next_char; while (rb_isdigit(next_char = peek(state))) { *accumulator = *accumulator * 10 + (next_char - '0'); state->cursor++; From 33a026fedd43fa5472937e77834397742fed6180 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 1 Nov 2025 11:38:16 +0100 Subject: [PATCH 0737/2435] Fix the description and logic for the Ractor.make_shareable(Proc) test --- bootstraptest/test_ractor.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index fef55ffd688003..a437faeac1814f 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1187,16 +1187,19 @@ def /(other) [a.frozen?, a[0].frozen?] == [true, false] } -# Ractor.make_shareable(a_proc) is not supported now. -assert_equal 'true', %q{ - pr = Proc.new{} +# Ractor.make_shareable(a_proc) requires a shareable receiver +assert_equal '[:ok, :error]', %q{ + pr1 = nil.instance_exec { Proc.new{} } + pr2 = Proc.new{} - begin - Ractor.make_shareable(pr) - rescue Ractor::Error - true - else - false + [pr1, pr2].map do |pr| + begin + Ractor.make_shareable(pr) + rescue Ractor::Error + :error + else + :ok + end end } From 94287b1e18edee04a0fd646fde84fe1178ce46d1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 1 Nov 2025 11:41:23 +0100 Subject: [PATCH 0738/2435] Make the expectation more precise in Ractor.make_shareable(Proc) test --- bootstraptest/test_ractor.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index a437faeac1814f..f008bc82a4add1 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1188,15 +1188,15 @@ def /(other) } # Ractor.make_shareable(a_proc) requires a shareable receiver -assert_equal '[:ok, :error]', %q{ +assert_equal '[:ok, "Proc\'s self is not shareable:"]', %q{ pr1 = nil.instance_exec { Proc.new{} } pr2 = Proc.new{} [pr1, pr2].map do |pr| begin Ractor.make_shareable(pr) - rescue Ractor::Error - :error + rescue Ractor::Error => e + e.message[/^.+?:/] else :ok end From ed7229eac8a5678211be8f1468af778d7beebf5c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 12:15:04 +0100 Subject: [PATCH 0739/2435] [ruby/json] parser.c: Use SWAR to skip consecutive spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: https://github.com/ruby/json/pull/881 If we encounter a newline, it is likely that the document is pretty printed, hence that the newline is followed by multiple spaces. In such case we can use SWAR to count up to eight consecutive spaces at once. ``` == Parsing activitypub.json (58160 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 1.118k i/100ms Calculating ------------------------------------- after 11.223k (± 0.7%) i/s (89.10 μs/i) - 57.018k in 5.080522s Comparison: before: 10834.4 i/s after: 11223.4 i/s - 1.04x faster == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 118.000 i/100ms Calculating ------------------------------------- after 1.188k (± 1.0%) i/s (841.62 μs/i) - 6.018k in 5.065355s Comparison: before: 1094.8 i/s after: 1188.2 i/s - 1.09x faster == Parsing citm_catalog.json (1727030 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 58.000 i/100ms Calculating ------------------------------------- after 570.506 (± 3.7%) i/s (1.75 ms/i) - 2.900k in 5.091529s Comparison: before: 419.6 i/s after: 570.5 i/s - 1.36x faster == Parsing float parsing (2251051 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 22.000 i/100ms Calculating ------------------------------------- after 212.010 (± 1.9%) i/s (4.72 ms/i) - 1.078k in 5.086885s Comparison: before: 189.4 i/s after: 212.0 i/s - 1.12x faster ``` https://github.com/ruby/json/commit/b3fd7b26be Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 46 ++++++++++++++++++++++++++++------------ ext/json/simd/simd.h | 3 +-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index e591ca2c5a9f85..01234b5a860b0a 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -557,14 +557,6 @@ static uint32_t unescape_unicode(JSON_ParserState *state, const unsigned char *p static const rb_data_type_t JSON_ParserConfig_type; -static const bool whitespace[256] = { - [' '] = 1, - ['\t'] = 1, - ['\n'] = 1, - ['\r'] = 1, - ['/'] = 1, -}; - static void json_eat_comments(JSON_ParserState *state) { @@ -607,12 +599,38 @@ json_eat_comments(JSON_ParserState *state) static inline void json_eat_whitespace(JSON_ParserState *state) { - unsigned char cursor; - while (RB_UNLIKELY(whitespace[cursor = (unsigned char)peek(state)])) { - if (RB_UNLIKELY(cursor == '/')) { - json_eat_comments(state); - } else { - state->cursor++; + while (true) { + switch (peek(state)) { + case ' ': + state->cursor++; + break; + case '\n': + state->cursor++; + + // Heuristic: if we see a newline, there is likely consecutive spaces after it. +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + while (rest(state) > 8) { + uint64_t chunk; + memcpy(&chunk, state->cursor, sizeof(uint64_t)); + size_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT; + + state->cursor += consecutive_spaces; + if (consecutive_spaces != 8) { + break; + } + } +#endif + break; + case '\t': + case '\r': + state->cursor++; + break; + case '/': + json_eat_comments(state); + break; + + default: + return; } } } diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 3abbdb020958a8..2aa6c3d046a764 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -4,8 +4,6 @@ typedef enum { SIMD_SSE2 } SIMD_Implementation; -#ifdef JSON_ENABLE_SIMD - #ifdef __clang__ # if __has_builtin(__builtin_ctzll) # define HAVE_BUILTIN_CTZLL 1 @@ -54,6 +52,7 @@ static inline int trailing_zeros(int input) #define FORCE_INLINE #endif +#ifdef JSON_ENABLE_SIMD #define SIMD_MINIMUM_THRESHOLD 6 From a97f4c627f2bd1fcc777f1d0314284210b43262f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 13:05:18 +0100 Subject: [PATCH 0740/2435] [ruby/json] parser.c: Appease GCC warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ../../../../../../ext/json/ext/parser/parser.c:1142:40: warning: suggest parentheses around ‘&&’ within ‘||’ [-Wparentheses] 1142 | if (RB_UNLIKELY(first_digit == '0' && mantissa_digits > 1 || negative && mantissa_digits == 0)) { | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ ``` https://github.com/ruby/json/commit/ded62a5122 --- ext/json/parser/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 01234b5a860b0a..555652f42582b1 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1132,7 +1132,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig // Parse integer part and extract mantissa digits int mantissa_digits = json_parse_digits(state, &mantissa); - if (RB_UNLIKELY(first_digit == '0' && mantissa_digits > 1 || negative && mantissa_digits == 0)) { + if (RB_UNLIKELY((first_digit == '0' && mantissa_digits > 1) || (negative && mantissa_digits == 0))) { raise_parse_error_at("invalid number: %s", state, start); } From af0597f018c91d4a30090431d7f9b426efa91459 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 14:57:33 +0100 Subject: [PATCH 0741/2435] [ruby/json] Enable JSON_DEBUG for parser/extconf.rb https://github.com/ruby/json/commit/3ea744ad67 --- ext/json/parser/extconf.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index de5d5758b46c42..dc1c8952c6e6cd 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'mkmf' +$defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby From 5384136eb578b3a4504f744ff5623239426df8d5 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 1 Nov 2025 14:57:33 +0100 Subject: [PATCH 0742/2435] [ruby/json] Enable JSON_DEBUG for parser/extconf.rb https://github.com/ruby/json/commit/82b030f294 --- ext/json/parser/parser.c | 24 +++++++++++++++++------- ext/json/simd/simd.h | 12 ++++++++++++ test/json/json_parser_test.rb | 4 ++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 555652f42582b1..8206716d705cb8 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -612,12 +612,14 @@ json_eat_whitespace(JSON_ParserState *state) while (rest(state) > 8) { uint64_t chunk; memcpy(&chunk, state->cursor, sizeof(uint64_t)); - size_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT; + if (chunk == 0x2020202020202020) { + state->cursor += 8; + continue; + } + uint32_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT; state->cursor += consecutive_spaces; - if (consecutive_spaces != 8) { - break; - } + break; } #endif break; @@ -1101,13 +1103,21 @@ static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulat continue; } - if ((match & 0xFFFFFFFF) == 0x33333333) { // 4 consecutive digits + uint32_t consecutive_digits = trailing_zeros64(match ^ 0x3333333333333333) / CHAR_BIT; + + if (consecutive_digits >= 4) { *accumulator = (*accumulator * 10000) + decode_4digits_unrolled((uint32_t)next_8bytes); state->cursor += 4; - break; + consecutive_digits -= 4; + } + + while (consecutive_digits) { + *accumulator = *accumulator * 10 + (*state->cursor - '0'); + consecutive_digits--; + state->cursor++; } - break; + return (int)(state->cursor - start); } #endif diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 2aa6c3d046a764..0abe4fad658813 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -1,3 +1,7 @@ +#ifdef JSON_DEBUG +#include +#endif + typedef enum { SIMD_NONE, SIMD_NEON, @@ -18,6 +22,10 @@ typedef enum { static inline uint32_t trailing_zeros64(uint64_t input) { +#ifdef JSON_DEBUG + assert(input > 0); // __builtin_ctz(0) is undefined behavior +#endif + #if HAVE_BUILTIN_CTZLL return __builtin_ctzll(input); #else @@ -33,6 +41,10 @@ static inline uint32_t trailing_zeros64(uint64_t input) static inline int trailing_zeros(int input) { +#ifdef JSON_DEBUG + assert(input > 0); // __builtin_ctz(0) is undefined behavior +#endif + #if HAVE_BUILTIN_CTZLL return __builtin_ctz(input); #else diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 30188c4ebdea32..bab16a6fc21dbc 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -804,6 +804,10 @@ def test_parse_leading_slash end end + def test_parse_whitespace_after_newline + assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]") + end + private def assert_equal_float(expected, actual, delta = 1e-2) From 390d77ba00f9e8f18a5408b404365db0b25fbf37 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 30 Oct 2025 18:18:36 -0400 Subject: [PATCH 0743/2435] Fix memory leak in String#encode when StringValue raises The following script leaks memory: 10.times do 100_000.times do "\ufffd".encode(Encoding::US_ASCII, fallback: proc { Object.new }) rescue end puts `ps -o rss= -p #{$$}` end Before: 450244 887748 1325124 1762756 2200260 2637508 3075012 3512516 3950020 4387524 After: 12236 12364 12748 13004 13388 13516 13772 13772 13772 13772 --- test/ruby/test_string.rb | 30 ++++++++++++++++++++++++++++++ transcode.c | 9 +++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index adbfe328cccfbf..99ab85ddbe9faa 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3807,6 +3807,36 @@ class MyError < StandardError; end end end + def test_encode_fallback_not_string_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { Object.new } + RUBY + "proc" => <<~RUBY, + fallback = proc { Object.new } + RUBY + "method" => <<~RUBY, + def my_method(_str) = Object.new + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = Object.new + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue TypeError + end + RUBY + end + end + private def assert_bytesplice_result(expected, s, *args) diff --git a/transcode.c b/transcode.c index 20b92b66f7ae21..86e828c47911a3 100644 --- a/transcode.c +++ b/transcode.c @@ -2360,7 +2360,13 @@ transcode_loop_fallback_try(VALUE a) { struct transcode_loop_fallback_args *args = (struct transcode_loop_fallback_args *)a; - return args->fallback_func(args->fallback, args->rep); + VALUE ret = args->fallback_func(args->fallback, args->rep); + + if (!UNDEF_P(ret) && !NIL_P(ret)) { + StringValue(ret); + } + + return ret; } static void @@ -2428,7 +2434,6 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos, } if (!UNDEF_P(rep) && !NIL_P(rep)) { - StringValue(rep); ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep), RSTRING_LEN(rep), rb_enc_name(rb_enc_get(rep))); if ((int)ret == -1) { From 48c7f349f68846e10d60ae77ad299a38ee014479 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 1 Nov 2025 20:14:42 +0100 Subject: [PATCH 0744/2435] Fix rescue in test_ractor.rb --- bootstraptest/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index f008bc82a4add1..25af87b610ef83 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1297,7 +1297,7 @@ class C s = 'str' trap(:INT){p s} }.join - rescue => Ractor::RemoteError + rescue Ractor::RemoteError a << :ok end } From a122d7a58e91ed6cd531e906cb398688d7cc8b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=BCrst?= Date: Mon, 27 Oct 2025 21:17:06 +0900 Subject: [PATCH 0745/2435] Add regression test for bug 21559. --- test/test_unicode_normalize.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_unicode_normalize.rb b/test/test_unicode_normalize.rb index 98f46551d33374..120ff01cd48c50 100644 --- a/test/test_unicode_normalize.rb +++ b/test/test_unicode_normalize.rb @@ -210,6 +210,11 @@ def test_us_ascii assert_equal true, ascii_string.unicode_normalized?(:nfkd) end + def test_bug_21559 + str = "s\u{1611e}\u{323}\u{1611e}\u{307}\u{1611f}" + assert_equal str.unicode_normalize(:nfd), str.unicode_normalize(:nfc).unicode_normalize(:nfd) + end + def test_canonical_ordering a = "\u03B1\u0313\u0300\u0345" a_unordered1 = "\u03B1\u0345\u0313\u0300" From 83a943b5948efbe5a2a6de9fa425482c51e536fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=BCrst?= Date: Mon, 27 Oct 2025 21:22:50 +0900 Subject: [PATCH 0746/2435] Add test for Unicode normalization of Gurung Khema. --- test/test_unicode_normalize.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_unicode_normalize.rb b/test/test_unicode_normalize.rb index 120ff01cd48c50..dd06d271310ad9 100644 --- a/test/test_unicode_normalize.rb +++ b/test/test_unicode_normalize.rb @@ -215,6 +215,10 @@ def test_bug_21559 assert_equal str.unicode_normalize(:nfd), str.unicode_normalize(:nfc).unicode_normalize(:nfd) end + def test_gurung_khema + assert_equal "\u{16121 16121 16121 16121 16121 1611E}", "\u{1611E 16121 16121 16121 16121 16121}".unicode_normalize + end + def test_canonical_ordering a = "\u03B1\u0313\u0300\u0345" a_unordered1 = "\u03B1\u0345\u0313\u0300" From e4c8e3544237b8c0efba6b945173dc66552d641c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=BCrst?= Date: Sun, 2 Nov 2025 09:18:32 +0900 Subject: [PATCH 0747/2435] Remove recursion in UnicodeNormalize.nfc_one, fixing bug 21159. --- lib/unicode_normalize/normalize.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index 7872f8a0bcffdd..611f43ef2aa0ad 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -111,17 +111,22 @@ def self.nfc_one(string) start = nfd_string[0] last_class = CLASS_TABLE[start]-1 accents = '' + result = '' nfd_string[1..-1].each_char do |accent| accent_class = CLASS_TABLE[accent] if last_class1 # TODO: change from recursion to loop - hangul_comp_one(start+accents) + hangul_comp_one(result+start+accents) end def self.normalize(string, form = :nfc) From 22496e2fe6ea4d354d039927a33458dcc1e852eb Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 2 Nov 2025 13:33:06 +0900 Subject: [PATCH 0748/2435] Update bundled irb and repl_type_completor version (#15030) --- gems/bundled_gems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 563f5e762750be..80c93ea007b704 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -34,14 +34,14 @@ drb 2.2.3 https://github.com/ruby/drb nkf 0.2.0 https://github.com/ruby/nkf syslog 0.3.0 https://github.com/ruby/syslog csv 3.3.5 https://github.com/ruby/csv -repl_type_completor 0.1.11 https://github.com/ruby/repl_type_completor 25108aa8d69ddaba0b5da3feff1c0035371524b2 +repl_type_completor 0.1.12 https://github.com/ruby/repl_type_completor ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger rdoc 6.15.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole -irb 1.15.2 https://github.com/ruby/irb d43c3d764ae439706aa1b26a3ec299cc45eaed5b +irb 1.15.3 https://github.com/ruby/irb reline 0.6.2 https://github.com/ruby/reline readline 0.0.4 https://github.com/ruby/readline fiddle 1.1.8 https://github.com/ruby/fiddle From 2c7fd0734c53fabcb6ad5bc8965384f90bd29a2a Mon Sep 17 00:00:00 2001 From: git Date: Sun, 2 Nov 2025 04:34:24 +0000 Subject: [PATCH 0749/2435] [DOC] Update bundled gems list at 22496e2fe6ea4d354d039927a33458 --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index fb700d88e17964..6804fd1bc55f69 100644 --- a/NEWS.md +++ b/NEWS.md @@ -163,7 +163,7 @@ The following bundled gems are promoted from default gems. * logger 1.7.0 * rdoc 6.15.1 * win32ole 1.9.2 -* irb 1.15.2 +* irb 1.15.3 * reline 0.6.2 * readline 0.0.4 * fiddle 1.1.8 @@ -228,7 +228,7 @@ The following bundled gems are updated. * drb 2.2.3 * syslog 0.3.0 * csv 3.3.5 -* repl_type_completor 0.1.11 +* repl_type_completor 0.1.12 ## Supported platforms From f827b116295aa37539aa55483eedea97c31aae17 Mon Sep 17 00:00:00 2001 From: Alejandro Exojo Date: Sun, 2 Nov 2025 09:21:20 +0100 Subject: [PATCH 0750/2435] [ruby/erb] Fix typo in documentation (https://github.com/ruby/erb/pull/91) https://github.com/ruby/erb/commit/6bceee7d6e --- lib/erb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb.rb b/lib/erb.rb index 884a63035c1564..d3bbc4979c9375 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -467,7 +467,7 @@ # ``` # # You can give `trim_mode: '>'` to suppress the trailing newline -# for each line that ends with `'%<'` (regardless of its beginning): +# for each line that ends with `'%>'` (regardless of its beginning): # # ``` # ERB.new(template, trim_mode: '>').result.lines.each {|line| puts line.inspect } From bd51b20c50a9a05c9fcea8025b223892eed4165b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=BCrst?= Date: Sun, 2 Nov 2025 19:22:22 +0900 Subject: [PATCH 0751/2435] minor code allignment (related to bug 21559) --- lib/unicode_normalize/normalize.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index 611f43ef2aa0ad..0447df8de7010a 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -117,7 +117,7 @@ def self.nfc_one(string) if last_class Date: Sat, 1 Nov 2025 10:49:14 -0400 Subject: [PATCH 0752/2435] Fix string allocation when slot size < 40 bytes We need to allocate at least sizeof(struct RString) when the string is embedded on garbage collectors that support slot sizes less than 40 bytes. --- string.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/string.c b/string.c index fa6ce7f3ec1d07..2a250cc69da130 100644 --- a/string.c +++ b/string.c @@ -242,7 +242,9 @@ rb_str_reembeddable_p(VALUE str) static inline size_t rb_str_embed_size(long capa) { - return offsetof(struct RString, as.embed.ary) + capa; + size_t size = offsetof(struct RString, as.embed.ary) + capa; + if (size < sizeof(struct RString)) size = sizeof(struct RString); + return size; } size_t From 2380f69f6875709ee4c37095cea4e6163d9faac1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 1 Nov 2025 10:51:04 -0400 Subject: [PATCH 0753/2435] Fix array allocation when slot size < 40 bytes We need to allocate at least sizeof(struct RArray) when the array is embedded on garbage collectors that support slot sizes less than 40 bytes. --- array.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/array.c b/array.c index 12f45e2cbb421a..b71123532d19cc 100644 --- a/array.c +++ b/array.c @@ -194,7 +194,9 @@ ary_embed_capa(VALUE ary) static size_t ary_embed_size(long capa) { - return offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); + size_t size = offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); + if (size < sizeof(struct RArray)) size = sizeof(struct RArray); + return size; } static bool From 37c7153668b31edbf56ec6227a7dc30cdcc45e4f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 1 Nov 2025 11:17:46 -0400 Subject: [PATCH 0754/2435] Make rb_str_embed_size aware of termlen --- string.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/string.c b/string.c index 2a250cc69da130..8b18ad97709712 100644 --- a/string.c +++ b/string.c @@ -240,9 +240,9 @@ rb_str_reembeddable_p(VALUE str) } static inline size_t -rb_str_embed_size(long capa) +rb_str_embed_size(long capa, long termlen) { - size_t size = offsetof(struct RString, as.embed.ary) + capa; + size_t size = offsetof(struct RString, as.embed.ary) + capa + termlen; if (size < sizeof(struct RString)) size = sizeof(struct RString); return size; } @@ -252,28 +252,30 @@ rb_str_size_as_embedded(VALUE str) { size_t real_size; if (STR_EMBED_P(str)) { - real_size = rb_str_embed_size(RSTRING(str)->len) + TERM_LEN(str); + size_t capa = RSTRING(str)->len; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } /* if the string is not currently embedded, but it can be embedded, how * much space would it require */ else if (rb_str_reembeddable_p(str)) { - real_size = rb_str_embed_size(RSTRING(str)->as.heap.aux.capa) + TERM_LEN(str); + size_t capa = RSTRING(str)->as.heap.aux.capa; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } else { real_size = sizeof(struct RString); } - if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) { - real_size += sizeof(st_index_t); - } - return real_size; } static inline bool STR_EMBEDDABLE_P(long len, long termlen) { - return rb_gc_size_allocatable_p(rb_str_embed_size(len + termlen)); + return rb_gc_size_allocatable_p(rb_str_embed_size(len, termlen)); } static VALUE str_replace_shared_without_enc(VALUE str2, VALUE str); @@ -1006,7 +1008,7 @@ must_not_null(const char *ptr) static inline VALUE str_alloc_embed(VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); + size_t size = rb_str_embed_size(capa, 0); RUBY_ASSERT(size > 0); RUBY_ASSERT(rb_gc_size_allocatable_p(size)); @@ -1883,7 +1885,7 @@ str_replace(VALUE str, VALUE str2) static inline VALUE ec_str_alloc_embed(struct rb_execution_context_struct *ec, VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); + size_t size = rb_str_embed_size(capa, 0); RUBY_ASSERT(size > 0); RUBY_ASSERT(rb_gc_size_allocatable_p(size)); From ee7ce9e6d71c7707d13541e9baa3a59745ac639b Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Sun, 2 Nov 2025 23:38:02 +0900 Subject: [PATCH 0755/2435] Update power_assert to 3.0.0 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 80c93ea007b704..3ff5e3f6b013f4 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -7,7 +7,7 @@ # if `revision` is not given, "v"+`version` or `version` will be used. minitest 5.26.0 https://github.com/minitest/minitest -power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a +power_assert 3.0.0 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml From 11583f53b172ead33355330c1445964d5af47f46 Mon Sep 17 00:00:00 2001 From: git Date: Sun, 2 Nov 2025 14:39:07 +0000 Subject: [PATCH 0756/2435] [DOC] Update bundled gems list at ee7ce9e6d71c7707d13541e9baa3a5 --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 6804fd1bc55f69..ab9c0c32b1f958 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,6 +213,7 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.26.0 +* power_assert 3.0.0 * rake 13.3.1 * test-unit 3.7.0 * rexml 3.4.4 From 5153a2dcb3f22cbfa1037764d491646ed5b127d5 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 30 Oct 2025 10:29:33 +0100 Subject: [PATCH 0757/2435] [ruby/json] Invoke `as_json` callback for strings with invalid encoding Fix: https://github.com/ruby/json/issues/873 This allow users to encode binary strings if they so wish. e.g. they can use Base64 or similar, or chose to replace invalid characters with something else. https://github.com/ruby/json/commit/b1b16c416f --- ext/json/generator/generator.c | 237 ++++++++++++++++++++------------- test/json/json_coder_test.rb | 65 +++++++++ 2 files changed, 207 insertions(+), 95 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 72155abe523358..024a8572726098 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -996,13 +996,12 @@ static inline VALUE vstate_get(struct generate_json_data *data) return data->vstate; } -struct hash_foreach_arg { - VALUE hash; - struct generate_json_data *data; - int first_key_type; - bool first; - bool mixed_keys_encountered; -}; +static VALUE +json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) +{ + VALUE proc_args[2] = {object, is_key}; + return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); +} static VALUE convert_string_subclass(VALUE key) @@ -1019,6 +1018,129 @@ convert_string_subclass(VALUE key) return key_to_s; } +static bool enc_utf8_compatible_p(int enc_idx) +{ + if (enc_idx == usascii_encindex) return true; + if (enc_idx == utf8_encindex) return true; + return false; +} + +static VALUE encode_json_string_try(VALUE str) +{ + return rb_funcall(str, i_encode, 1, Encoding_UTF_8); +} + +static VALUE encode_json_string_rescue(VALUE str, VALUE exception) +{ + raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); + return Qundef; +} + +static inline bool valid_json_string_p(VALUE str) +{ + int coderange = rb_enc_str_coderange(str); + + if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) { + return true; + } + + if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) { + return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str)); + } + + return false; +} + +static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (RB_LIKELY(valid_json_string_p(str))) { + return str; + } + + if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) { + VALUE coerced_str = json_call_as_json(data->state, str, Qfalse); + if (coerced_str != str) { + if (RB_TYPE_P(coerced_str, T_STRING)) { + if (!valid_json_string_p(coerced_str)) { + raise_generator_error(str, "source sequence is illegal/malformed utf-8"); + } + } else { + // as_json could return another type than T_STRING + if (is_key) { + raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str)); + } + } + + return coerced_str; + } + } + + if (RB_ENCODING_GET_INLINED(str) == binary_encindex) { + VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); + switch (rb_enc_str_coderange(utf8_string)) { + case ENC_CODERANGE_7BIT: + return utf8_string; + case ENC_CODERANGE_VALID: + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. + // TODO: Raise in 3.0.0 + rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); + return utf8_string; + break; + } + } + + return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); +} + +static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + fbuffer_append_char(buffer, '"'); + + long len; + search_state search; + search.buffer = buffer; + RSTRING_GETMEM(obj, search.ptr, len); + search.cursor = search.ptr; + search.end = search.ptr + len; + +#ifdef HAVE_SIMD + search.matches_mask = 0; + search.has_matches = false; + search.chunk_base = NULL; +#endif /* HAVE_SIMD */ + + switch (rb_enc_str_coderange(obj)) { + case ENC_CODERANGE_7BIT: + case ENC_CODERANGE_VALID: + if (RB_UNLIKELY(data->state->ascii_only)) { + convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); + } else if (RB_UNLIKELY(data->state->script_safe)) { + convert_UTF8_to_script_safe_JSON(&search); + } else { + convert_UTF8_to_JSON(&search); + } + break; + default: + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + break; + } + fbuffer_append_char(buffer, '"'); +} + +static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + obj = ensure_valid_encoding(data, obj, false, false); + raw_generate_json_string(buffer, data, obj); +} + +struct hash_foreach_arg { + VALUE hash; + struct generate_json_data *data; + int first_key_type; + bool first; + bool mixed_keys_encountered; +}; + NOINLINE() static void json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) @@ -1035,13 +1157,6 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) } } -static VALUE -json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) -{ - VALUE proc_args[2] = {object, is_key}; - return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); -} - static int json_object_i(VALUE key, VALUE val, VALUE _arg) { @@ -1107,8 +1222,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) break; } + key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true); + if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) { - generate_json_string(buffer, data, key_to_s); + raw_generate_json_string(buffer, data, key_to_s); } else { generate_json(buffer, data, key_to_s); } @@ -1191,85 +1308,6 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data fbuffer_append_char(buffer, ']'); } -static inline int enc_utf8_compatible_p(int enc_idx) -{ - if (enc_idx == usascii_encindex) return 1; - if (enc_idx == utf8_encindex) return 1; - return 0; -} - -static VALUE encode_json_string_try(VALUE str) -{ - return rb_funcall(str, i_encode, 1, Encoding_UTF_8); -} - -static VALUE encode_json_string_rescue(VALUE str, VALUE exception) -{ - raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); - return Qundef; -} - -static inline VALUE ensure_valid_encoding(VALUE str) -{ - int encindex = RB_ENCODING_GET(str); - VALUE utf8_string; - if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) { - if (encindex == binary_encindex) { - utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); - switch (rb_enc_str_coderange(utf8_string)) { - case ENC_CODERANGE_7BIT: - return utf8_string; - case ENC_CODERANGE_VALID: - // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. - // TODO: Raise in 3.0.0 - rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); - return utf8_string; - break; - } - } - - str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); - } - return str; -} - -static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) -{ - obj = ensure_valid_encoding(obj); - - fbuffer_append_char(buffer, '"'); - - long len; - search_state search; - search.buffer = buffer; - RSTRING_GETMEM(obj, search.ptr, len); - search.cursor = search.ptr; - search.end = search.ptr + len; - -#ifdef HAVE_SIMD - search.matches_mask = 0; - search.has_matches = false; - search.chunk_base = NULL; -#endif /* HAVE_SIMD */ - - switch (rb_enc_str_coderange(obj)) { - case ENC_CODERANGE_7BIT: - case ENC_CODERANGE_VALID: - if (RB_UNLIKELY(data->state->ascii_only)) { - convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); - } else if (RB_UNLIKELY(data->state->script_safe)) { - convert_UTF8_to_script_safe_JSON(&search); - } else { - convert_UTF8_to_JSON(&search); - } - break; - default: - raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); - break; - } - fbuffer_append_char(buffer, '"'); -} - static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE tmp; @@ -1408,7 +1446,16 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU break; case T_STRING: if (klass != rb_cString) goto general; - generate_json_string(buffer, data, obj); + + if (RB_LIKELY(valid_json_string_p(obj))) { + raw_generate_json_string(buffer, data, obj); + } else if (as_json_called) { + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + } else { + obj = ensure_valid_encoding(data, obj, false, false); + as_json_called = true; + goto start; + } break; case T_SYMBOL: generate_json_symbol(buffer, data, obj); diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index fb9d7b30a59a11..83b89a3b13dea9 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -67,6 +67,71 @@ def test_json_coder_dump_NaN_or_Infinity_loop assert_include error.message, "NaN not allowed in JSON" end + def test_json_coder_string_invalid_encoding + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.dup + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.bytes + end + + assert_equal "[255]", coder.dump("\xFF") + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "Array not allowed as object key in JSON", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + [object].pack("m") + end + + assert_equal '"/w==\\n"', coder.dump("\xFF") + assert_equal 1, calls + + assert_equal '{"/w==\\n":1}', coder.dump({ "\xFF" => 1 }) + assert_equal 2, calls + end + def test_nesting_recovery coder = JSON::Coder.new ary = [] From bc77a11bd845e4c3d79b990c196fedca37e9ab0b Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Fri, 31 Oct 2025 23:25:43 +0900 Subject: [PATCH 0758/2435] Add basic namespace tests --- test/ruby/test_namespace.rb | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 5a014ad7148a51..af0d6173f24f7b 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -695,4 +695,76 @@ def test_root_and_main_methods pat = "_ruby_ns_*."+RbConfig::CONFIG["DLEXT"] File.unlink(*Dir.glob(pat, base: tmp).map {|so| "#{tmp}/#{so}"}) end + + def test_basic_namespace_detections + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + ns = Namespace.new + code = <<~EOC + NS1 = Namespace.current + class Foo + NS2 = Namespace.current + NS2_proc = ->(){ NS2 } + NS3_proc = ->(){ Namespace.current } + + def ns4 = Namespace.current + def self.ns5 = NS2 + def self.ns6 = Namespace.current + def self.ns6_proc = ->(){ Namespace.current } + + def self.yield_block = yield + def self.call_block(&b) = b.call + end + NS9 = Foo.new.ns4 + EOC + ns.eval(code) + outer = Namespace.current + assert_equal ns, ns::NS1 # on TOP frame + assert_equal ns, ns::Foo::NS2 # on CLASS frame + assert_equal ns, ns::Foo::NS2_proc.call # proc -> a const on CLASS + assert_equal ns, ns::Foo::NS3_proc.call # proc -> the current + assert_equal ns, ns::Foo.new.ns4 # instance method -> the current + assert_equal ns, ns::Foo.ns5 # singleton method -> a const on CLASS + assert_equal ns, ns::Foo.ns6 # singleton method -> the current + assert_equal ns, ns::Foo.ns6_proc.call # method returns a proc -> the current + + assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields + assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block + assert_equal ns, ns::NS9 # on TOP frame, referring a class in the current + end; + end + + def test_loading_extension_libs_in_main_namespace + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require "prism" + require "optparse" + require "date" + require "time" + require "delegate" + require "singleton" + require "pp" + require "fileutils" + require "tempfile" + require "tmpdir" + require "json" + require "psych" + require "yaml" + require "zlib" + require "open3" + require "ipaddr" + require "net/http" + require "openssl" + require "socket" + require "uri" + require "digest" + require "erb" + require "stringio" + require "monitor" + require "timeout" + require "securerandom" + expected = 1 + assert_equal expected, 1 + end; + end end From e89eecceafea3f8834c6fc4cdd74a7812591cae5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Fri, 31 Oct 2025 23:38:16 +0900 Subject: [PATCH 0759/2435] No need to call rb_define_class/module_under_id Classes/modules defined in a namespace are defined under ::Object as usual (as without namespaces), and it'll be set into the const_tbl of ::Object. In namespaces, namespace objects' const_tbl is equal to the one of ::Object. So constants of ::Object are just equal to constants of the namespace. That means, top level classes/modules in a namespace can be referred as namespace::KlassName without calling rb_define_class_under_id(). --- class.c | 14 ++------------ test/ruby/test_namespace.rb | 4 ++++ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/class.c b/class.c index 5e50a281d9a9fe..a4249425a13a93 100644 --- a/class.c +++ b/class.c @@ -1605,13 +1605,8 @@ VALUE rb_define_class(const char *name, VALUE super) { VALUE klass; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_class_id_under(ns->ns_object, id, super); - } if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(klass, T_CLASS)) { @@ -1723,13 +1718,8 @@ VALUE rb_define_module(const char *name) { VALUE module; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_module_id_under(ns->ns_object, id); - } if (rb_const_defined(rb_cObject, id)) { module = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(module, T_MODULE)) { diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index af0d6173f24f7b..2b7dd04ac7f933 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -715,6 +715,7 @@ def self.ns6_proc = ->(){ Namespace.current } def self.yield_block = yield def self.call_block(&b) = b.call end + FOO_NAME = Foo.name NS9 = Foo.new.ns4 EOC ns.eval(code) @@ -731,6 +732,9 @@ def self.call_block(&b) = b.call assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block assert_equal ns, ns::NS9 # on TOP frame, referring a class in the current + + assert_equal "Foo", ns::FOO_NAME + assert_equal "Foo", ns::Foo.name end; end From bb62a1cf8dffdbaf2b97486e781600023ff1356c Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 08:00:18 +0900 Subject: [PATCH 0760/2435] Add flag to ignore EXPERIMENTAL warnings --- test/ruby/test_namespace.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 2b7dd04ac7f933..0525e0f2839c1c 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -183,7 +183,7 @@ def test_proc_defined_in_namespace_refers_module_in_namespace pend unless Namespace.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; ns1 = Namespace.new ns1.require(File.join("#{here}", 'namespace/proc_callee')) From 12303e2e2e9634dda5e2b5eec7ae7e2296c9f229 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 08:00:56 +0900 Subject: [PATCH 0761/2435] Fix use of inappropriate debug flag --- namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namespace.c b/namespace.c index 0f0230b5fdbd9a..3498a4cc4ae433 100644 --- a/namespace.c +++ b/namespace.c @@ -1084,7 +1084,7 @@ Init_Namespace(void) if (rb_namespace_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); -#if RUBY_DEBUG > 0 +#if RUBY_DEBUG rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); rb_define_global_function("dump_classext", rb_f_dump_classext, 1); From fa4c04a7f75cef4f5e3c1a92b54eba8316c159a5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 08:01:35 +0900 Subject: [PATCH 0762/2435] Use CFUNC namespace only for IFUNC frames, its behavior should be unchanged --- test/ruby/test_namespace.rb | 23 ++++++++++- vm.c | 81 ++++++++++++++++++++++++------------- vm_dump.c | 45 +++++++++++---------- 3 files changed, 97 insertions(+), 52 deletions(-) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 0525e0f2839c1c..6df960f7305b80 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -711,12 +711,26 @@ def ns4 = Namespace.current def self.ns5 = NS2 def self.ns6 = Namespace.current def self.ns6_proc = ->(){ Namespace.current } + def self.ns7 + res = [] + [1,2].chunk{ it.even? }.each do |bool, members| + res << Namespace.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") + end + res + end def self.yield_block = yield def self.call_block(&b) = b.call end FOO_NAME = Foo.name - NS9 = Foo.new.ns4 + + module Kernel + def foo_namespace = Namespace.current + module_function :foo_namespace + end + + NS_X = Foo.new.ns4 + NS_Y = foo_namespace EOC ns.eval(code) outer = Namespace.current @@ -729,9 +743,14 @@ def self.call_block(&b) = b.call assert_equal ns, ns::Foo.ns6 # singleton method -> the current assert_equal ns, ns::Foo.ns6_proc.call # method returns a proc -> the current + # a block after CFUNC/IFUNC in a method -> the current + assert_equal ["#{ns.object_id}:false:1", "#{ns.object_id}:true:2"], ns::Foo.ns7 + assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block - assert_equal ns, ns::NS9 # on TOP frame, referring a class in the current + + assert_equal ns, ns::NS_X # on TOP frame, referring a class in the current + assert_equal ns, ns::NS_Y # on TOP frame, referring Kernel method defined by a CFUNC method assert_equal "Foo", ns::FOO_NAME assert_equal "Foo", ns::Foo.name diff --git a/vm.c b/vm.c index c11ac9f7a10510..32785dbcc8cbca 100644 --- a/vm.c +++ b/vm.c @@ -113,39 +113,62 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre // rb_vmdebug_namespace_env_dump_raw() simulates this function const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ - const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; + const rb_control_frame_t *cfp = current_cfp; + + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { + ep = VM_EP_LEP(current_cfp->ep); + /** + * Returns CFUNC frame only in this case. + * + * Usually CFUNC frame doesn't represent the current namespace and it should operate + * the caller namespace. See the example: + * + * # in the main namespace + * module Kernel + * def foo = "foo" + * module_function :foo + * end + * + * In the case above, `module_function` is defined in the root namespace. + * If `module_function` worked in the root namespace, `Kernel#foo` is invisible + * from it and it causes NameError: undefined method `foo` for module `Kernel`. + * + * But in cases of IFUNC (blocks written in C), IFUNC doesn't have its own namespace + * and its local env frame will be CFUNC frame. + * For example, `Enumerator#chunk` calls IFUNC blocks, written as `chunk_i` function. + * + * [1].chunk{ it.even? }.each{ ... } + * + * Before calling the Ruby block `{ it.even? }`, `#chunk` calls `chunk_i` as IFUNC + * to iterate the array's members (it's just like `#each`). + * We expect that `chunk_i` works as expected by the implementation of `#chunk` + * without any overwritten definitions from namespaces. + * So the definitions on IFUNC frames should be equal to the caller CFUNC. + */ + VM_ASSERT(VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)); + return ep; + } - while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { - while (!VM_ENV_LOCAL_P(ep)) { - ep = VM_ENV_PREV_EP(ep); - } - while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { - if (!cfp) { - cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); - VM_NAMESPACE_ASSERT(cfp, "Failed to search cfp from ep"); - VM_NAMESPACE_ASSERT(cfp->ep == ep, "Searched cfp's ep is not equal to ep"); - } - if (!cfp) { - return NULL; - } - VM_NAMESPACE_ASSERT(cfp->ep, "cfp->ep == NULL"); - VM_NAMESPACE_ASSERT(cfp->ep == ep, "cfp->ep != ep"); + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - VM_NAMESPACE_ASSERT(!VM_FRAME_FINISHED_P(cfp), "CFUNC frame should not FINISHED"); + VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); + VM_NAMESPACE_ASSERT(cfp < eocfp, "CFUNC should have a valid caller frame"); + if (!cfp || cfp >= eocfp) { + return NULL; + } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - if (cfp >= eocfp) { - return NULL; - } - VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); - ep = cfp->ep; - if (!ep) { - return NULL; - } + VM_NAMESPACE_ASSERT(cfp->ep, "CFUNC should have a valid caller frame with env"); + ep = cfp->ep; + if (!ep) { + return NULL; } - checkpoint_cfp = cfp; - cfp = NULL; } + + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + } + return ep; } @@ -3096,7 +3119,7 @@ current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_fram VM_NAMESPACE_ASSERT(lep, "lep should be valid"); VM_NAMESPACE_ASSERT(rb_namespace_available(), "namespace should be available here"); - if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD)) { + if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CFUNC)) { cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); VM_NAMESPACE_ASSERT(cme, "cme should be valid"); VM_NAMESPACE_ASSERT(cme->def, "cme->def shold be valid"); diff --git a/vm_dump.c b/vm_dump.c index 0fcef846db8704..2ed1f955b3445e 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -421,39 +421,42 @@ rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_con // See VM_EP_RUBY_LEP for the original logic const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ - const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; + const rb_control_frame_t *cfp = current_cfp, *checkpoint_cfp = current_cfp; kprintf("-- Namespace detection information " "-----------------------------------------\n"); namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); - while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { while (!VM_ENV_LOCAL_P(ep)) { ep = VM_ENV_PREV_EP(ep); namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); } - while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { - if (!cfp) { - cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, ep); - } - if (!cfp) { - goto stop; - } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - if (cfp >= eocfp) { - kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); - goto stop; - } - ep = cfp->ep; - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); - if (!ep) { - goto stop; - } + goto stop; + } + + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + if (!cfp) { + goto stop; + } + if (cfp >= eocfp) { + kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); + goto stop; + } + ep = cfp->ep; + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + if (!ep) { + goto stop; } - checkpoint_cfp = cfp; - cfp = NULL; } + + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + } + stop: kprintf("\n"); return true; From 0116dd5e0caaf8136f867b3037ce6d8dbf376232 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 08:03:33 +0900 Subject: [PATCH 0763/2435] Make Namespace.root visible not only for debugging There are many APIs that expects application codes overwrite global methods. For example, warn() expects Warning.warn() is overwritten to hook warning messages. If we enable namespace, Warning.warn defined in the app code is visible only in the namespace, and invisible from warn() defined in the root namespace. So we have to enable users to overwrite Warning.warn in the root namespace. This is ugly and temporal workaround. We need to define better APIs to enable users to hook such behaviors in the different way from defining global methods. --- namespace.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/namespace.c b/namespace.c index 3498a4cc4ae433..2ae91025455e80 100644 --- a/namespace.c +++ b/namespace.c @@ -1084,14 +1084,13 @@ Init_Namespace(void) if (rb_namespace_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); -#if RUBY_DEBUG rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); - rb_define_global_function("dump_classext", rb_f_dump_classext, 1); - rb_define_method(rb_cNamespace, "root?", rb_namespace_root_p, 0); rb_define_method(rb_cNamespace, "main?", rb_namespace_main_p, 0); - rb_define_method(rb_cNamespace, "user?", rb_namespace_user_p, 0); + +#if RUBY_DEBUG + rb_define_global_function("dump_classext", rb_f_dump_classext, 1); #endif } From 89fa15b6f69ebb5c9d71aab5bf19d5ac2b67b05b Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 09:41:36 +0900 Subject: [PATCH 0764/2435] Fix incorrect RUBY_DEBUG range --- namespace.c | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/namespace.c b/namespace.c index 2ae91025455e80..0efc75e28bf009 100644 --- a/namespace.c +++ b/namespace.c @@ -876,8 +876,6 @@ Init_enable_namespace(void) } } -#if RUBY_DEBUG > 0 - /* :nodoc: */ static VALUE rb_namespace_s_root(VALUE recv) @@ -892,6 +890,24 @@ rb_namespace_s_main(VALUE recv) return main_namespace->ns_object; } +/* :nodoc: */ +static VALUE +rb_namespace_root_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_ROOT_P(ns)); +} + +/* :nodoc: */ +static VALUE +rb_namespace_main_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_MAIN_P(ns)); +} + +#if RUBY_DEBUG + static const char * classname(VALUE klass) { @@ -1027,30 +1043,6 @@ rb_f_dump_classext(VALUE recv, VALUE klass) return res; } -/* :nodoc: */ -static VALUE -rb_namespace_root_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_ROOT_P(ns)); -} - -/* :nodoc: */ -static VALUE -rb_namespace_main_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_MAIN_P(ns)); -} - -/* :nodoc: */ -static VALUE -rb_namespace_user_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_USER_P(ns)); -} - #endif /* RUBY_DEBUG */ /* From c83a249f777acf2995a0597127a03967f2b92b5e Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 1 Nov 2025 10:43:22 +0900 Subject: [PATCH 0765/2435] pend on Windows for timeouts --- test/ruby/test_namespace.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 6df960f7305b80..daa4e9e0ba3881 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -758,6 +758,7 @@ def foo_namespace = Namespace.current end def test_loading_extension_libs_in_main_namespace + pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; require "prism" From 4a3d8346a6d0e068508631541f6bc43e8b154ea1 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 2 Nov 2025 17:21:03 +0000 Subject: [PATCH 0766/2435] [DOC] Tweaks for String#to_f --- string.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/string.c b/string.c index 8b18ad97709712..472159ffe470e1 100644 --- a/string.c +++ b/string.c @@ -7071,7 +7071,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * '3.14159'.to_f # => 3.14159 * '1.234e-2'.to_f # => 0.01234 * - * Characters past a leading valid number (in the given +base+) are ignored: + * Characters past a leading valid number are ignored: * * '3.14 (pi to two places)'.to_f # => 3.14 * @@ -7079,6 +7079,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * * 'abcdef'.to_f # => 0.0 * + * See {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE From 35a5e5513377d0d1f13c3ab15966a5c51b24066c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 10:07:28 +0100 Subject: [PATCH 0767/2435] [ruby/json] Centralize macro definitions https://github.com/ruby/json/commit/1576ea7d47 --- ext/json/fbuffer/fbuffer.h | 50 +------------------- ext/json/generator/generator.c | 34 ++++---------- ext/json/json.h | 85 ++++++++++++++++++++++++++++++++++ ext/json/parser/parser.c | 49 +------------------- ext/json/simd/simd.h | 28 ++++------- 5 files changed, 106 insertions(+), 140 deletions(-) create mode 100644 ext/json/json.h diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index 7d57a87b14ff5e..ecba61465e46c0 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -1,55 +1,9 @@ #ifndef _FBUFFER_H_ #define _FBUFFER_H_ -#include "ruby.h" -#include "ruby/encoding.h" +#include "../json.h" #include "../vendor/jeaiii-ltoa.h" -/* shims */ -/* This is the fallback definition from Ruby 3.4 */ - -#ifndef RBIMPL_STDBOOL_H -#if defined(__cplusplus) -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include -# endif -#elif defined(HAVE_STDBOOL_H) -# include -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - -#ifndef NOINLINE -#if defined(__has_attribute) && __has_attribute(noinline) -#define NOINLINE() __attribute__((noinline)) -#else -#define NOINLINE() -#endif -#endif - -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr -#endif - -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - -#ifndef MAYBE_UNUSED -# define MAYBE_UNUSED(x) x -#endif - -#ifdef RUBY_DEBUG -#ifndef JSON_DEBUG -#define JSON_DEBUG RUBY_DEBUG -#endif -#endif - enum fbuffer_type { FBUFFER_HEAP_ALLOCATED = 0, FBUFFER_STACK_ALLOCATED = 1, @@ -290,4 +244,4 @@ static VALUE fbuffer_finalize(FBuffer *fb) } } -#endif +#endif // _FBUFFER_H_ diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 024a8572726098..56aa636ef87cc0 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1,4 +1,4 @@ -#include "ruby.h" +#include "../json.h" #include "../fbuffer/fbuffer.h" #include "../vendor/fpconv.c" @@ -36,10 +36,6 @@ typedef struct JSON_Generator_StateStruct { bool strict; } JSON_Generator_State; -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(cond) (cond) -#endif - static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8; static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode; @@ -85,10 +81,7 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d static int usascii_encindex, utf8_encindex, binary_encindex; -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_generator_error_str(VALUE invalid_object, VALUE str) +NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str) { rb_enc_associate_index(str, utf8_encindex); VALUE exc = rb_exc_new_str(eGeneratorError, str); @@ -96,13 +89,10 @@ static void raise_generator_error_str(VALUE invalid_object, VALUE str) rb_exc_raise(exc); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif #ifdef RBIMPL_ATTR_FORMAT RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) #endif -static void raise_generator_error(VALUE invalid_object, const char *fmt, ...) +NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -137,13 +127,7 @@ typedef struct _search_state { #endif /* HAVE_SIMD */ } search_state; -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif - -static inline FORCE_INLINE void search_flush(search_state *search) +static ALWAYS_INLINE() void search_flush(search_state *search) { // Do not remove this conditional without profiling, specifically escape-heavy text. // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush). @@ -186,7 +170,7 @@ static inline unsigned char search_escape_basic(search_state *search) return 0; } -static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search) +static ALWAYS_INLINE() void escape_UTF8_char_basic(search_state *search) { const unsigned char ch = (unsigned char)*search->ptr; switch (ch) { @@ -273,7 +257,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) #ifdef HAVE_SIMD -static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) +static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) { // Flush the buffer so everything up until the last 'len' characters are unflushed. search_flush(search); @@ -296,7 +280,7 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi #ifdef HAVE_SIMD_NEON -static inline FORCE_INLINE unsigned char neon_next_match(search_state *search) +static ALWAYS_INLINE() unsigned char neon_next_match(search_state *search) { uint64_t mask = search->matches_mask; uint32_t index = trailing_zeros64(mask) >> 2; @@ -410,7 +394,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search) #ifdef HAVE_SIMD_SSE2 -static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) +static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search) { int mask = search->matches_mask; int index = trailing_zeros(mask); @@ -434,7 +418,7 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) #define TARGET_SSE2 #endif -static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(search_state *search) +static inline TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { // There are more matches if search->matches_mask > 0. diff --git a/ext/json/json.h b/ext/json/json.h new file mode 100644 index 00000000000000..873440527dec60 --- /dev/null +++ b/ext/json/json.h @@ -0,0 +1,85 @@ +#ifndef _JSON_H_ +#define _JSON_H_ + +#include "ruby.h" +#include "ruby/encoding.h" + +#if defined(RUBY_DEBUG) && RUBY_DEBUG +# define JSON_ASSERT RUBY_ASSERT +#else +# ifdef JSON_DEBUG +# include +# define JSON_ASSERT(x) assert(x) +# else +# define JSON_ASSERT(x) +# endif +#endif + +/* shims */ + +#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG +# define INT64T2NUM(x) LL2NUM(x) +# define UINT64T2NUM(x) ULL2NUM(x) +#elif SIZEOF_UINT64_T == SIZEOF_LONG +# define INT64T2NUM(x) LONG2NUM(x) +# define UINT64T2NUM(x) ULONG2NUM(x) +#else +# error No uint64_t conversion +#endif + +/* This is the fallback definition from Ruby 3.4 */ +#ifndef RBIMPL_STDBOOL_H +#if defined(__cplusplus) +# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) +# include +# endif +#elif defined(HAVE_STDBOOL_H) +# include +#elif !defined(HAVE__BOOL) +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)+0) +# define __bool_true_false_are_defined +#endif +#endif + +#ifndef NORETURN +#define NORETURN(x) x +#endif + +#ifndef NOINLINE +#if defined(__has_attribute) && __has_attribute(noinline) +#define NOINLINE(x) __attribute__((noinline)) x +#else +#define NOINLINE(x) x +#endif +#endif + +#ifndef ALWAYS_INLINE +#if defined(__has_attribute) && __has_attribute(always_inline) +#define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x +#else +#define ALWAYS_INLINE(x) inline x +#endif +#endif + +#ifndef RB_UNLIKELY +#define RB_UNLIKELY(expr) expr +#endif + +#ifndef RB_LIKELY +#define RB_LIKELY(expr) expr +#endif + +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + +#ifdef RUBY_DEBUG +#ifndef JSON_DEBUG +#define JSON_DEBUG RUBY_DEBUG +#endif +#endif + +#endif // _JSON_H_ \ No newline at end of file diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 8206716d705cb8..d9b578f2453e09 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1,46 +1,7 @@ -#include "ruby.h" -#include "ruby/encoding.h" +#include "../json.h" #include "../vendor/ryu.h" - -/* shims */ -/* This is the fallback definition from Ruby 3.4 */ - -#ifndef RBIMPL_STDBOOL_H -#if defined(__cplusplus) -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include -# endif -#elif defined(HAVE_STDBOOL_H) -# include -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - -#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG -# define INT64T2NUM(x) LL2NUM(x) -# define UINT64T2NUM(x) ULL2NUM(x) -#elif SIZEOF_UINT64_T == SIZEOF_LONG -# define INT64T2NUM(x) LONG2NUM(x) -# define UINT64T2NUM(x) ULONG2NUM(x) -#else -# error No uint64_t conversion -#endif - #include "../simd/simd.h" -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr -#endif - -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - static VALUE mJSON, eNestingError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity; @@ -985,17 +946,11 @@ static const bool string_scan_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif - #ifdef HAVE_SIMD static SIMD_Implementation simd_impl = SIMD_NONE; #endif /* HAVE_SIMD */ -static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) +static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) { #ifdef HAVE_SIMD #if defined(HAVE_SIMD_NEON) diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 0abe4fad658813..194baee51c3475 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -1,6 +1,4 @@ -#ifdef JSON_DEBUG -#include -#endif +#include "../json.h" typedef enum { SIMD_NONE, @@ -22,9 +20,7 @@ typedef enum { static inline uint32_t trailing_zeros64(uint64_t input) { -#ifdef JSON_DEBUG - assert(input > 0); // __builtin_ctz(0) is undefined behavior -#endif + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior #if HAVE_BUILTIN_CTZLL return __builtin_ctzll(input); @@ -41,9 +37,7 @@ static inline uint32_t trailing_zeros64(uint64_t input) static inline int trailing_zeros(int input) { -#ifdef JSON_DEBUG - assert(input > 0); // __builtin_ctz(0) is undefined behavior -#endif + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior #if HAVE_BUILTIN_CTZLL return __builtin_ctz(input); @@ -58,12 +52,6 @@ static inline int trailing_zeros(int input) #endif } -#if (defined(__GNUC__ ) || defined(__clang__)) -#define FORCE_INLINE __attribute__((always_inline)) -#else -#define FORCE_INLINE -#endif - #ifdef JSON_ENABLE_SIMD #define SIMD_MINIMUM_THRESHOLD 6 @@ -81,14 +69,14 @@ static inline SIMD_Implementation find_simd_implementation(void) #define HAVE_SIMD_NEON 1 // See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon -static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches) +static ALWAYS_INLINE() uint64_t neon_match_mask(uint8x16_t matches) { const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); return mask & 0x8888888888888888ull; } -static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr) +static ALWAYS_INLINE() uint64_t compute_chunk_mask_neon(const char *ptr) { uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); @@ -101,7 +89,7 @@ static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr) return neon_match_mask(needs_escape); } -static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) +static ALWAYS_INLINE() int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) { while (*ptr + sizeof(uint8x16_t) <= end) { uint64_t chunk_mask = compute_chunk_mask_neon(*ptr); @@ -148,7 +136,7 @@ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) #define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) #define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) -static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *ptr) +static inline TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char *ptr) { __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 @@ -159,7 +147,7 @@ static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *p return _mm_movemask_epi8(needs_escape); } -static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +static inline TARGET_SSE2 ALWAYS_INLINE() int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) { while (*ptr + sizeof(__m128i) <= end) { int chunk_mask = compute_chunk_mask_sse2(*ptr); From edebd83ea99651a47d4592f2cef12618da3a0736 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 10:46:20 +0100 Subject: [PATCH 0768/2435] [ruby/json] parser.c: Skip checking for escape sequences in `rstring_cache_fetch` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caller already know if the string contains escape sequences so this check is redundant. Also stop calling `rstring_cache_fetch` from `json_string_unescape` as we know it won't match anyways. ``` == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 122.000 i/100ms Calculating ------------------------------------- after 1.226k (± 0.3%) i/s (815.85 μs/i) - 6.222k in 5.076282s Comparison: before: 1206.2 i/s after: 1225.7 i/s - 1.02x faster ``` https://github.com/ruby/json/commit/b8cdf3282d Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 25 ------------------------- test/json/json_parser_test.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index d9b578f2453e09..68ff6d7ac357fa 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -122,12 +122,6 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rstring = build_interned_string(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { @@ -174,12 +168,6 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rsymbol = build_symbol(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { @@ -652,19 +640,6 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c int unescape_len; char buf[4]; - if (is_name && state->in_array) { - VALUE cached_key; - if (RB_UNLIKELY(symbolize)) { - cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); - } else { - cached_key = rstring_cache_fetch(&state->name_cache, string, bufferSize); - } - - if (RB_LIKELY(cached_key)) { - return cached_key; - } - } - VALUE result = rb_str_buf_new(bufferSize); rb_enc_associate_index(result, utf8_encindex); buffer = RSTRING_PTR(result); diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index bab16a6fc21dbc..6315c3e667be85 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -344,6 +344,18 @@ def test_parse_big_integers assert_equal orig, parse(json5) end + def test_parse_escaped_key + doc = { + "test\r1" => 1, + "entries" => [ + "test\t2" => 2, + "test\n3" => 3, + ] + } + + assert_equal doc, parse(JSON.generate(doc)) + end + def test_parse_duplicate_key expected = {"a" => 2} expected_sym = {a: 2} From ea0a411f2552ec89c7121ceeee7e23fbafc7bab6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 11:10:56 +0100 Subject: [PATCH 0769/2435] [ruby/json] parser.c: simplify sorted insert loop in rstring_cache_fetch https://github.com/ruby/json/commit/31453b8e95 Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 68ff6d7ac357fa..caa4f9fa0589f3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -105,17 +105,15 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, entry); + int cmp = rstring_cache_cmp(str, length, entry); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; @@ -125,11 +123,7 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon VALUE rstring = build_interned_string(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rstring); + rvalue_cache_insert_at(cache, low, rstring); } return rstring; } @@ -151,17 +145,15 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); + int cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; @@ -171,11 +163,7 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon VALUE rsymbol = build_symbol(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rsymbol); + rvalue_cache_insert_at(cache, low, rsymbol); } return rsymbol; } From 0832e954c9ef181563be0e70ba089ed0a8c0d02e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 11:32:58 +0100 Subject: [PATCH 0770/2435] [ruby/json] parser.c: Extract `json_string_cacheable_p` We can share that logic between the two functions. https://github.com/ruby/json/commit/ac580458e0 --- ext/json/parser/parser.c | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index caa4f9fa0589f3..1e83dad9157cfd 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -90,19 +90,6 @@ static inline int rstring_cache_cmp(const char *str, const long length, VALUE rs static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } - - if (RB_UNLIKELY(!rb_isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; - } - int low = 0; int high = cache->length - 1; @@ -130,19 +117,6 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const long length) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } - - if (RB_UNLIKELY(!rb_isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; - } - int low = 0; int high = cache->length - 1; @@ -600,11 +574,20 @@ static inline VALUE build_string(const char *start, const char *end, bool intern return result; } +static inline bool json_string_cacheable_p(const char *string, size_t length) +{ + // We mostly want to cache strings that are likely to be repeated. + // Simple heuristics: + // - Common names aren't likely to be very long. So we just don't cache names above an arbitrary threshold. + // - If the first character isn't a letter, we're much less likely to see this string again. + return length <= JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH && rb_isalpha(string[0]); +} + static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) { size_t bufferSize = stringEnd - string; - if (is_name && state->in_array) { + if (is_name && state->in_array && RB_LIKELY(json_string_cacheable_p(string, bufferSize))) { VALUE cached_key; if (RB_UNLIKELY(symbolize)) { cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); From 52a17bbe6d778d56cc600f73f107c1992350f877 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 11:45:43 +0100 Subject: [PATCH 0771/2435] [ruby/json] parser.c: use `rb_str_to_interned_str` over `rb_funcall` https://github.com/ruby/json/commit/21284ea649 --- ext/json/parser/extconf.rb | 1 + ext/json/parser/parser.c | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index dc1c8952c6e6cd..cda385767c3cc7 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -3,6 +3,7 @@ $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 +have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby have_func("strnlen", "string.h") # Missing on Solaris 10 diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 1e83dad9157cfd..25eeb89e773f1b 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -16,7 +16,7 @@ static int utf8_encindex; #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby -void +static void rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) { long index = 0; @@ -33,6 +33,12 @@ rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) #define rb_hash_new_capa(n) rb_hash_new() #endif +#ifndef HAVE_RB_STR_TO_INTERNED_STR +static VALUE rb_str_to_interned_str(VALUE str) +{ + return rb_funcall(rb_str_freeze(str), i_uminus, 0); +} +#endif /* name cache */ @@ -703,7 +709,7 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c if (symbolize) { result = rb_str_intern(result); } else if (intern) { - result = rb_funcall(rb_str_freeze(result), i_uminus, 0); + result = rb_str_to_interned_str(result); } return result; @@ -1310,6 +1316,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) } raise_parse_error("unreachable: %s", state); + return Qundef; } static void json_ensure_eof(JSON_ParserState *state) From c49e4db680895735643d1001b2a0e4f405f49021 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 14:10:02 +0100 Subject: [PATCH 0772/2435] [ruby/json] parser.c: Always inline `json_eat_whitespace` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` == Parsing activitypub.json (58160 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 1.174k i/100ms Calculating ------------------------------------- after 11.756k (± 0.9%) i/s (85.06 μs/i) - 59.874k in 5.093438s Comparison: before: 11078.6 i/s after: 11756.1 i/s - 1.06x faster == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 130.000 i/100ms Calculating ------------------------------------- after 1.340k (± 0.3%) i/s (746.06 μs/i) - 6.760k in 5.043432s Comparison: before: 1191.1 i/s after: 1340.4 i/s - 1.13x faster == Parsing citm_catalog.json (1727030 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 68.000 i/100ms Calculating ------------------------------------- after 689.451 (± 1.6%) i/s (1.45 ms/i) - 3.468k in 5.031470s Comparison: before: 630.3 i/s after: 689.5 i/s - 1.09x faster == Parsing float parsing (2251051 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 27.000 i/100ms Calculating ------------------------------------- after 248.265 (± 0.8%) i/s (4.03 ms/i) - 1.242k in 5.003185s Comparison: before: 232.7 i/s after: 248.3 i/s - 1.07x faster ``` https://github.com/ruby/json/commit/043880f6ab Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 25eeb89e773f1b..20754249e2ca39 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -513,7 +513,7 @@ json_eat_comments(JSON_ParserState *state) } } -static inline void +static ALWAYS_INLINE() void json_eat_whitespace(JSON_ParserState *state) { while (true) { From 4740b3d7aab216e83f069c4d2f26edfa3aeb1709 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 3 Nov 2025 07:39:00 -0800 Subject: [PATCH 0773/2435] [ruby/json] Fix check_dependency --- ext/json/generator/depend | 1 + ext/json/parser/depend | 1 + 2 files changed, 2 insertions(+) diff --git a/ext/json/generator/depend b/ext/json/generator/depend index aee4ab94ebcce8..3ba4acfdd26156 100644 --- a/ext/json/generator/depend +++ b/ext/json/generator/depend @@ -178,6 +178,7 @@ generator.o: $(hdrdir)/ruby/ruby.h generator.o: $(hdrdir)/ruby/st.h generator.o: $(hdrdir)/ruby/subst.h generator.o: $(srcdir)/../fbuffer/fbuffer.h +generator.o: $(srcdir)/../json.h generator.o: $(srcdir)/../simd/simd.h generator.o: $(srcdir)/../vendor/fpconv.c generator.o: $(srcdir)/../vendor/jeaiii-ltoa.h diff --git a/ext/json/parser/depend b/ext/json/parser/depend index b780afb5f94546..d4737b1dfb5b63 100644 --- a/ext/json/parser/depend +++ b/ext/json/parser/depend @@ -175,6 +175,7 @@ parser.o: $(hdrdir)/ruby/ruby.h parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h +parser.o: $(srcdir)/../json.h parser.o: $(srcdir)/../simd/simd.h parser.o: $(srcdir)/../vendor/ryu.h parser.o: parser.c From 2f9e0d355ef4db3157a401da8bf3b8da4c811d6b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 16:55:42 +0100 Subject: [PATCH 0774/2435] [ruby/json] Fix duplicate 'inline' declaration specifier Followup: https://github.com/ruby/json/pull/889 https://github.com/ruby/json/commit/591510392a --- ext/json/generator/generator.c | 2 +- ext/json/simd/simd.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 56aa636ef87cc0..8930213b939a0d 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -418,7 +418,7 @@ static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search) #define TARGET_SSE2 #endif -static inline TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search) +static TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { // There are more matches if search->matches_mask > 0. diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 194baee51c3475..a4f917fd0ae518 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -136,7 +136,7 @@ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) #define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) #define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) -static inline TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char *ptr) +static TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char *ptr) { __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 @@ -147,7 +147,7 @@ static inline TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char return _mm_movemask_epi8(needs_escape); } -static inline TARGET_SSE2 ALWAYS_INLINE() int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +static TARGET_SSE2 ALWAYS_INLINE() int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) { while (*ptr + sizeof(__m128i) <= end) { int chunk_mask = compute_chunk_mask_sse2(*ptr); From 505fcf5dcfb59e91ed97e770b166793e44845bd8 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 18:02:10 +0100 Subject: [PATCH 0775/2435] [ruby/json] ext/json/ext/json.h: Add missing newline at end of file https://github.com/ruby/json/commit/3bc1787bd4 --- ext/json/json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/json.h b/ext/json/json.h index 873440527dec60..20e7101de7b0da 100644 --- a/ext/json/json.h +++ b/ext/json/json.h @@ -82,4 +82,4 @@ typedef unsigned char _Bool; #endif #endif -#endif // _JSON_H_ \ No newline at end of file +#endif // _JSON_H_ From 16af72790837ffb10c87ec23f99a6c519abc21e3 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 3 Nov 2025 14:30:59 -0500 Subject: [PATCH 0776/2435] Avoid taking vm barrier in heap_prepare() (#14425) We can avoid taking this barrier if we're not incremental marking or lazy sweeping. I found this was taking a significant amount of samples when profiling `Psych.load` in multiple ractors due to the vm barrier. With this change, we get significant improvements in ractor benchmarks that allocate lots of objects. -- Psych.load benchmark -- ``` Before: After: r: itr: time r: itr: time 0 #1: 960ms 0 #1: 943ms 0 #2: 979ms 0 #2: 939ms 0 #3: 968ms 0 #3: 948ms 0 #4: 963ms 0 #4: 946ms 0 #5: 964ms 0 #5: 944ms 1 #1: 947ms 1 #1: 940ms 1 #2: 950ms 1 #2: 947ms 1 #3: 962ms 1 #3: 950ms 1 #4: 947ms 1 #4: 945ms 1 #5: 947ms 1 #5: 943ms 2 #1: 1131ms 2 #1: 1005ms 2 #2: 1153ms 2 #2: 996ms 2 #3: 1155ms 2 #3: 1003ms 2 #4: 1205ms 2 #4: 1012ms 2 #5: 1179ms 2 #5: 1012ms 4 #1: 1555ms 4 #1: 1209ms 4 #2: 1509ms 4 #2: 1244ms 4 #3: 1529ms 4 #3: 1254ms 4 #4: 1512ms 4 #4: 1267ms 4 #5: 1513ms 4 #5: 1245ms 6 #1: 2122ms 6 #1: 1584ms 6 #2: 2080ms 6 #2: 1532ms 6 #3: 2079ms 6 #3: 1476ms 6 #4: 2021ms 6 #4: 1463ms 6 #5: 1999ms 6 #5: 1461ms 8 #1: 2741ms 8 #1: 1630ms 8 #2: 2711ms 8 #2: 1632ms 8 #3: 2688ms 8 #3: 1654ms 8 #4: 2641ms 8 #4: 1684ms 8 #5: 2656ms 8 #5: 1752ms ``` --- gc/default/default.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 0a9945cdac98b2..e0a5aade85f223 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -985,6 +985,9 @@ total_final_slots_count(rb_objspace_t *objspace) #define GC_INCREMENTAL_SWEEP_SLOT_COUNT 2048 #define GC_INCREMENTAL_SWEEP_POOL_SLOT_COUNT 1024 #define is_lazy_sweeping(objspace) (GC_ENABLE_LAZY_SWEEP && has_sweeping_pages(objspace)) +/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */ +#define needs_continue_sweeping(objspace, heap) \ + ((heap)->free_pages == NULL && is_lazy_sweeping(objspace)) #if SIZEOF_LONG == SIZEOF_VOIDP # define obj_id_to_ref(objid) ((objid) ^ FIXNUM_FLAG) /* unset FIXNUM_FLAG */ @@ -2022,7 +2025,10 @@ static void gc_continue(rb_objspace_t *objspace, rb_heap_t *heap) { unsigned int lock_lev; - gc_enter(objspace, gc_enter_event_continue, &lock_lev); + bool needs_gc = is_incremental_marking(objspace) || needs_continue_sweeping(objspace, heap); + if (!needs_gc) return; + + gc_enter(objspace, gc_enter_event_continue, &lock_lev); // takes vm barrier, try to avoid /* Continue marking if in incremental marking. */ if (is_incremental_marking(objspace)) { @@ -2031,9 +2037,7 @@ gc_continue(rb_objspace_t *objspace, rb_heap_t *heap) } } - /* Continue sweeping if in lazy sweeping or the previous incremental - * marking finished and did not yield a free page. */ - if (heap->free_pages == NULL && is_lazy_sweeping(objspace)) { + if (needs_continue_sweeping(objspace, heap)) { gc_sweep_continue(objspace, heap); } From 8117600232161bdea403481ad2b9b66d36856c1a Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Mon, 3 Nov 2025 13:19:09 -0700 Subject: [PATCH 0777/2435] ZJIT: Implement include_p for opt_(new|dup)array_send YARV insns (#14885) These just call to the C functions that do the optimized test but this avoids the side exit. See https://github.com/ruby/ruby/pull/12123 for the original CRuby/YJIT implementation. --- test/ruby/test_zjit.rb | 18 ++++++++++++ zjit/src/codegen.rs | 46 ++++++++++++++++++++++++++++++ zjit/src/hir.rs | 58 +++++++++++++++++++++++++++++++++++++ zjit/src/hir/tests.rs | 65 +++++++++++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 2 ++ 5 files changed, 188 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 6805d91406be5c..f12ac19af59537 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -957,6 +957,24 @@ def test }, insns: [:opt_new], call_threshold: 2 end + def test_opt_newarray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_duparray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + def test_new_hash_empty assert_compiles '{}', %q{ def test = {} diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d4ed6304cb5577..72d111ac8a97f7 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -450,6 +450,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), + Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), + &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } @@ -1328,6 +1330,50 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm_ccall!(asm, rb_jit_array_len, array) } +fn gen_array_include( + jit: &JITState, + asm: &mut Assembler, + elements: Vec, + target: Opnd, + state: &FrameState, +) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + let num: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); + + // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack. + // The elements are at the bottom of the virtual stack, followed by the target. + // Get a pointer to the first element on the Ruby stack. + let stack_bottom = state.stack().len() - elements.len() - 1; + let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32)); + + unsafe extern "C" { + fn rb_vm_opt_newarray_include_p(ec: EcPtr, num: c_long, elts: *const VALUE, target: VALUE) -> VALUE; + } + asm.ccall( + rb_vm_opt_newarray_include_p as *const u8, + vec![EC, num.into(), elements_ptr, target], + ) +} + +fn gen_dup_array_include( + jit: &JITState, + asm: &mut Assembler, + ary: VALUE, + target: Opnd, + state: &FrameState, +) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + unsafe extern "C" { + fn rb_vm_opt_duparray_include_p(ec: EcPtr, ary: VALUE, target: VALUE) -> VALUE; + } + asm.ccall( + rb_vm_opt_duparray_include_p as *const u8, + vec![EC, ary.into(), target], + ) +} + /// Compile a new hash instruction fn gen_new_hash( jit: &mut JITState, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 55bec186512799..ce164f37d618a7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -457,6 +457,7 @@ impl PtrPrintMap { #[derive(Debug, Clone, Copy)] pub enum SideExitReason { UnknownNewarraySend(vm_opt_newarray_send_type), + UnknownDuparraySend(u64), UnknownSpecialVariable(u64), UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), @@ -548,6 +549,7 @@ impl std::fmt::Display for SideExitReason { SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK) => write!(f, "UnknownNewarraySend(PACK)"), SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK_BUFFER) => write!(f, "UnknownNewarraySend(PACK_BUFFER)"), SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_INCLUDE_P) => write!(f, "UnknownNewarraySend(INCLUDE_P)"), + SideExitReason::UnknownDuparraySend(method_id) => write!(f, "UnknownDuparraySend({})", method_id), SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"), SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"), SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())), @@ -616,6 +618,8 @@ pub enum Insn { NewRangeFixnum { low: InsnId, high: InsnId, flag: RangeType, state: InsnId }, ArrayDup { val: InsnId, state: InsnId }, ArrayMax { elements: Vec, state: InsnId }, + ArrayInclude { elements: Vec, target: InsnId, state: InsnId }, + DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId }, /// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`. ArrayExtend { left: InsnId, right: InsnId, state: InsnId }, /// Push `val` onto `array`, where `array` is already `Array`. @@ -988,6 +992,18 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } + Insn::ArrayInclude { elements, target, .. } => { + write!(f, "ArrayInclude")?; + let mut prefix = " "; + for element in elements { + write!(f, "{prefix}{element}")?; + prefix = ", "; + } + write!(f, " | {target}") + } + Insn::DupArrayInclude { ary, target, .. } => { + write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target) + } Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") } Insn::HashDup { val, .. } => { write!(f, "HashDup {val}") } Insn::HashAref { hash, key, .. } => { write!(f, "HashAref {hash}, {key}")} @@ -1789,6 +1805,8 @@ impl Function { &ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) }, &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, + &ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) }, + &DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, @@ -1923,6 +1941,8 @@ impl Function { Insn::GetConstantPath { .. } => types::BasicObject, Insn::IsBlockGiven => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, + Insn::ArrayInclude { .. } => types::BoolExact, + Insn::DupArrayInclude { .. } => types::BoolExact, Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, @@ -3211,6 +3231,15 @@ impl Function { worklist.extend(elements); worklist.push_back(state); } + &Insn::ArrayInclude { ref elements, target, state } => { + worklist.extend(elements); + worklist.push_back(target); + worklist.push_back(state); + } + &Insn::DupArrayInclude { target, state, .. } => { + worklist.push_back(target); + worklist.push_back(state); + } &Insn::NewRange { low, high, state, .. } | &Insn::NewRangeFixnum { low, high, state, .. } => { worklist.push_back(low); @@ -4448,6 +4477,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); let (bop, insn) = match method { VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), + VM_OPT_NEWARRAY_SEND_INCLUDE_P => { + let target = elements[elements.len() - 1]; + let array_elements = elements[..elements.len() - 1].to_vec(); + (BOP_INCLUDE_P, Insn::ArrayInclude { elements: array_elements, target, state: exit_id }) + }, _ => { // Unknown opcode; side-exit into the interpreter fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownNewarraySend(method) }); @@ -4468,6 +4502,30 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let insn_id = fun.push_insn(block, Insn::ArrayDup { val, state: exit_id }); state.stack_push(insn_id); } + YARVINSN_opt_duparray_send => { + let ary = get_arg(pc, 0); + let method_id = get_arg(pc, 1).as_u64(); + let argc = get_arg(pc, 2).as_usize(); + if argc != 1 { + break; + } + let target = state.stack_pop()?; + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let bop = match method_id { + x if x == ID!(include_p).0 => BOP_INCLUDE_P, + _ => { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownDuparraySend(method_id) }); + break; + }, + }; + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, ARRAY_REDEFINED_OP_FLAG) } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }) }); + break; + } + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }, state: exit_id }); + let insn_id = fun.push_insn(block, Insn::DupArrayInclude { ary, target, state: exit_id }); + state.stack_push(insn_id); + } YARVINSN_newhash => { let count = get_arg(pc, 0).as_usize(); assert!(count % 2 == 0, "newhash count should be even"); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index c58e14ad4e0735..fe67779d85a808 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2023,7 +2023,70 @@ pub mod hir_build_tests { Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(INCLUDE_P) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33) + v30:BoolExact = ArrayInclude v15, v16 | v16 + PatchPoint NoEPEscape(test) + v35:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v37:ArrayExact = ArrayDup v35 + v39:BasicObject = SendWithoutBlock v14, :puts, v37 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_opt_duparray_send_include_p() { + eval(" + def test(x) + [:a, :b].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33) + v15:BoolExact = DupArrayInclude VALUE(0x1000) | v9 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_opt_duparray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) + end + end + def test(x) + [:a, :b].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:9: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33)) "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index e69f1d95884564..2d7139eec6c053 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -128,6 +128,7 @@ make_counters! { // exit_: Side exits reasons exit_compile_error, exit_unknown_newarray_send, + exit_unknown_duparray_send, exit_unhandled_tailcall, exit_unhandled_splat, exit_unhandled_kwarg, @@ -366,6 +367,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { use crate::stats::Counter::*; match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, + UnknownDuparraySend(_) => exit_unknown_duparray_send, UnhandledCallType(Tailcall) => exit_unhandled_tailcall, UnhandledCallType(Splat) => exit_unhandled_splat, UnhandledCallType(Kwarg) => exit_unhandled_kwarg, From 4001e81a8eb04ac1b7653b05762bcdcb364760e1 Mon Sep 17 00:00:00 2001 From: Max Leopold Date: Mon, 3 Nov 2025 21:46:26 +0100 Subject: [PATCH 0778/2435] ZJIT: Inline String#bytesize (#15033) Inline the `String#bytesize` function and remove the C call. --- test/ruby/test_zjit.rb | 10 ++++++++++ zjit/src/codegen.rs | 9 +++++++++ zjit/src/cruby.rs | 1 + zjit/src/cruby_methods.rs | 23 ++++++++++++++++++++++- zjit/src/hir.rs | 11 ++++++++++- zjit/src/hir/opt_tests.rs | 33 +++------------------------------ zjit/src/stats.rs | 2 ++ 7 files changed, 57 insertions(+), 32 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index f12ac19af59537..de2d1e61528e86 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2568,6 +2568,16 @@ def test(str) }, call_threshold: 2 end + def test_string_bytesize_multibyte + assert_compiles '4', %q{ + def test(s) + s.bytesize + end + + test("💎") + }, call_threshold: 2 + end + def test_nil_value_nil_opt_with_guard assert_compiles 'true', %q{ def test(val) = val.nil? diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 72d111ac8a97f7..7cd677bde3d5b3 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -403,6 +403,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), &Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)), &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), + &Insn::BoxFixnum { val, state } => gen_box_fixnum(jit, asm, opnd!(val), &function.frame_state(state)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), @@ -1595,6 +1596,14 @@ fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse)) } +fn gen_box_fixnum(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, state: &FrameState) -> lir::Opnd { + // Load the value, then test for overflow and tag it + let val = asm.load(val); + let shifted = asm.lshift(val, Opnd::UImm(1)); + asm.jo(side_exit(jit, state, BoxFixnumOverflow)); + asm.or(shifted, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)) +} + fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 89488fd2559e1c..631acbd8635686 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1376,6 +1376,7 @@ pub(crate) mod ids { name: freeze name: minusat content: b"-@" name: aref content: b"[]" + name: len name: _as_heap } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0af3be1819dac7..37d75f45974e2a 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -195,7 +195,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); annotate!(rb_mKernel, "===", inline_eqq); - annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cString, "bytesize", inline_string_bytesize); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "getbyte", inline_string_getbyte); @@ -305,6 +305,27 @@ fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins None } + +fn inline_string_bytesize(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if args.is_empty() && fun.likely_a(recv, types::String, state) { + let recv = fun.coerce_to(block, recv, types::String, state); + let len = fun.push_insn(block, hir::Insn::LoadField { + recv, + id: ID!(len), + offset: RUBY_OFFSET_RSTRING_LEN as i32, + return_type: types::CInt64, + }); + + let result = fun.push_insn(block, hir::Insn::BoxFixnum { + val: len, + state, + }); + + return Some(result); + } + None +} + fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[index] = args else { return None; }; if fun.likely_a(index, types::Fixnum, state) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ce164f37d618a7..136f7b452fcd38 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -478,6 +478,7 @@ pub enum SideExitReason { BlockParamProxyNotIseqOrIfunc, StackOverflow, FixnumModByZero, + BoxFixnumOverflow, } #[derive(Debug, Clone, Copy)] @@ -655,6 +656,8 @@ pub enum Insn { IsBitNotEqual { left: InsnId, right: InsnId }, /// Convert a C `bool` to a Ruby `Qtrue`/`Qfalse`. Same as `RBOOL` macro. BoxBool { val: InsnId }, + /// Convert a C `long` to a Ruby `Fixnum`. Side exit on overflow. + BoxFixnum { val: InsnId, state: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, @@ -924,6 +927,7 @@ impl Insn { Insn::NewRangeFixnum { .. } => false, Insn::StringGetbyteFixnum { .. } => false, Insn::IsBlockGiven => false, + Insn::BoxFixnum { .. } => false, _ => true, } } @@ -1057,6 +1061,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsBitEqual { left, right } => write!(f, "IsBitEqual {left}, {right}"), Insn::IsBitNotEqual { left, right } => write!(f, "IsBitNotEqual {left}, {right}"), Insn::BoxBool { val } => write!(f, "BoxBool {val}"), + Insn::BoxFixnum { val, .. } => write!(f, "BoxFixnum {val}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") } @@ -1696,6 +1701,7 @@ impl Function { &IsBitEqual { left, right } => IsBitEqual { left: find!(left), right: find!(right) }, &IsBitNotEqual { left, right } => IsBitNotEqual { left: find!(left), right: find!(right) }, &BoxBool { val } => BoxBool { val: find!(val) }, + &BoxFixnum { val, state } => BoxFixnum { val: find!(val), state: find!(state) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, &IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) }, @@ -1888,6 +1894,7 @@ impl Function { Insn::IsBitEqual { .. } => types::CBool, Insn::IsBitNotEqual { .. } => types::CBool, Insn::BoxBool { .. } => types::BoolExact, + Insn::BoxFixnum { .. } => types::Fixnum, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, @@ -3281,7 +3288,8 @@ impl Function { | &Insn::GuardNotFrozen { val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } - | &Insn::ToNewArray { val, state } => { + | &Insn::ToNewArray { val, state } + | &Insn::BoxFixnum { val, state } => { worklist.push_back(val); worklist.push_back(state); } @@ -3759,6 +3767,7 @@ impl Function { } } Insn::BoxBool { val } => self.assert_subtype(insn_id, val, types::CBool), + Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64), Insn::SetGlobal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::GetIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), Insn::SetIvar { self_val, val, .. } => { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index d0b3203ac186e6..07f80c06824b71 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2686,34 +2686,6 @@ mod hir_opt_tests { "); } - #[test] - fn string_bytesize_simple() { - eval(" - def test = 'abc'.bytesize - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - v24:Fixnum = CCall bytesize@0x1040, v12 - CheckInterrupts - Return v24 - "); - } - #[test] fn dont_replace_get_constant_path_with_empty_ic() { eval(" @@ -7161,7 +7133,7 @@ mod hir_opt_tests { } #[test] - fn test_specialize_string_bytesize() { + fn test_inline_string_bytesize() { eval(r#" def test(s) s.bytesize @@ -7182,8 +7154,9 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact + v24:CInt64 = LoadField v23, :len@0x1038 + v25:Fixnum = BoxFixnum v24 IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall bytesize@0x1038, v23 CheckInterrupts Return v25 "); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 2d7139eec6c053..35af2b1d9d3bea 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -139,6 +139,7 @@ make_counters! { exit_fixnum_sub_overflow, exit_fixnum_mult_overflow, exit_fixnum_mod_by_zero, + exit_box_fixnum_overflow, exit_guard_type_failure, exit_guard_type_not_failure, exit_guard_bit_equals_failure, @@ -378,6 +379,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { FixnumSubOverflow => exit_fixnum_sub_overflow, FixnumMultOverflow => exit_fixnum_mult_overflow, FixnumModByZero => exit_fixnum_mod_by_zero, + BoxFixnumOverflow => exit_box_fixnum_overflow, GuardType(_) => exit_guard_type_failure, GuardTypeNot(_) => exit_guard_type_not_failure, GuardBitEquals(_) => exit_guard_bit_equals_failure, From bac6a25ad3b6ed02ece11a2790cec3a2e6f8c8c9 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 2 Nov 2025 21:47:42 -0800 Subject: [PATCH 0779/2435] [ruby/rubygems] Remove open-ended and prerelease dependency warnings when building gems In general, rubygems should provide mechanism and not policy. Pessimistic versioning is not universally better, and in many cases, it can cause more problems than it solves. Rubygems should not be warning against open-ended versioning when building gems. The majority of the default gems with dependencies do not use pessimistic versioning, which indicates that Ruby itself recognizes that open-ended versioning is generally better. In some cases, depending on a prerelease gem is the only choice other than not releasing a gem. If you are building an extension gem for a feature in a prerelease version of another gem, then depending on the prerelease version is the only way to ensure a compatible dependency is installed. https://github.com/ruby/rubygems/commit/beba8dd065 --- lib/rubygems/specification_policy.rb | 36 ------------------------- test/rubygems/test_gem_specification.rb | 22 +-------------- 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index d79ee7df9252ae..e5008a24dbf4a6 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -190,9 +190,6 @@ def validate_duplicate_dependencies # :nodoc: ## # Checks that the gem does not depend on itself. - # Checks that dependencies use requirements as we recommend. Warnings are - # issued when dependencies are open-ended or overly strict for semantic - # versioning. def validate_dependencies # :nodoc: warning_messages = [] @@ -200,39 +197,6 @@ def validate_dependencies # :nodoc: if dep.name == @specification.name # warn on self reference warning_messages << "Self referencing dependency is unnecessary and strongly discouraged." end - - prerelease_dep = dep.requirements_list.any? do |req| - Gem::Requirement.new(req).prerelease? - end - - warning_messages << "prerelease dependency on #{dep} is not recommended" if - prerelease_dep && !@specification.version.prerelease? - - open_ended = dep.requirement.requirements.all? do |op, version| - !version.prerelease? && [">", ">="].include?(op) - end - - next unless open_ended - op, dep_version = dep.requirement.requirements.first - - segments = dep_version.segments - - base = segments.first 2 - - recommendation = if [">", ">="].include?(op) && segments == [0] - " use a bounded requirement, such as \"~> x.y\"" - else - bugfix = if op == ">" - ", \"> #{dep_version}\"" - elsif op == ">=" && base != segments - ", \">= #{dep_version}\"" - end - - " if #{dep.name} is semantically versioned, use:\n" \ - " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}" - end - - warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n" end if warning_messages.any? warning_messages.each {|warning_message| warning warning_message } diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index af351f4d2e1fc0..3a325c439c1cd8 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -2671,27 +2671,7 @@ def test_validate_dependencies @a1.validate end - expected = <<-EXPECTED -#{w}: prerelease dependency on b (>= 1.0.rc1) is not recommended -#{w}: prerelease dependency on c (>= 2.0.rc2, development) is not recommended -#{w}: open-ended dependency on i (>= 1.2) is not recommended - if i is semantically versioned, use: - add_runtime_dependency "i", "~> 1.2" -#{w}: open-ended dependency on j (>= 1.2.3) is not recommended - if j is semantically versioned, use: - add_runtime_dependency "j", "~> 1.2", ">= 1.2.3" -#{w}: open-ended dependency on k (> 1.2) is not recommended - if k is semantically versioned, use: - add_runtime_dependency "k", "~> 1.2", "> 1.2" -#{w}: open-ended dependency on l (> 1.2.3) is not recommended - if l is semantically versioned, use: - add_runtime_dependency "l", "~> 1.2", "> 1.2.3" -#{w}: open-ended dependency on o (>= 0) is not recommended - use a bounded requirement, such as "~> x.y" -#{w}: See https://guides.rubygems.org/specification-reference/ for help - EXPECTED - - assert_equal expected, @ui.error, "warning" + assert_equal "", @ui.error, "warning" end end From 2c2eaa3103e5cf1cbfc2b16d9db975a9b8a0399a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 31 Oct 2025 14:49:24 -0700 Subject: [PATCH 0780/2435] [ruby/rubygems] Fix constants in TAR to be frozen I would like to use the tar implementation inside a Ractor, but two of the constants are not frozen. This patch freezes the constants so we can use it in a Ractor. https://github.com/ruby/rubygems/commit/0ff4790f4c --- lib/rubygems/package/tar_header.rb | 8 ++++---- test/rubygems/test_gem_package_tar_header.rb | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index 0ebcbd789d4038..dd20d65080ff00 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -56,7 +56,7 @@ class Gem::Package::TarHeader ## # Pack format for a tar header - PACK_FORMAT = "a100" + # name + PACK_FORMAT = ("a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid @@ -71,12 +71,12 @@ class Gem::Package::TarHeader "a32" + # gname "a8" + # devmajor "a8" + # devminor - "a155" # prefix + "a155").freeze # prefix ## # Unpack format for a tar header - UNPACK_FORMAT = "A100" + # name + UNPACK_FORMAT = ("A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid @@ -91,7 +91,7 @@ class Gem::Package::TarHeader "A32" + # gname "A8" + # devmajor "A8" + # devminor - "A155" # prefix + "A155").freeze # prefix attr_reader(*FIELDS) diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb index a3f95bb7704f91..34f92967e9905b 100644 --- a/test/rubygems/test_gem_package_tar_header.rb +++ b/test/rubygems/test_gem_package_tar_header.rb @@ -26,6 +26,25 @@ def setup @tar_header = Gem::Package::TarHeader.new header end + def test_decode_in_ractor + new_header = Ractor.new(@tar_header.to_s) do |str| + Gem::Package::TarHeader.from StringIO.new str + end.value + + assert_headers_equal @tar_header, new_header + end if defined?(Ractor) && Ractor.instance_methods.include?(:value) + + def test_encode_in_ractor + header_bytes = @tar_header.to_s + + new_header = Ractor.new(header_bytes) do |str| + header = Gem::Package::TarHeader.from StringIO.new str + header.to_s + end.value + + assert_headers_equal header_bytes, new_header + end if defined?(Ractor) && Ractor.instance_methods.include?(:value) + def test_self_from io = TempIO.new @tar_header.to_s From 6695a3b3333069ce220aa4732c75fb75efe90383 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 3 Nov 2025 18:45:00 -0600 Subject: [PATCH 0781/2435] [ruby/stringio] [DOC] Tweaks for StringIO#eof? (https://github.com/ruby/stringio/pull/160) https://github.com/ruby/stringio/commit/5034156245 --- ext/stringio/stringio.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index b96010dfbf3f96..9133c24266884c 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -699,10 +699,18 @@ strio_to_read(VALUE self) * call-seq: * eof? -> true or false * - * Returns +true+ if positioned at end-of-stream, +false+ otherwise; - * see {Position}[rdoc-ref:IO@Position]. + * Returns whether +self+ is positioned at end-of-stream: + * + * strio = StringIO.new('foo') + * strio.pos # => 0 + * strio.eof? # => false + * strio.read # => "foo" + * strio.pos # => 3 + * strio.eof? # => true + * strio.close_read + * strio.eof? # Raises IOError: not opened for reading * - * Raises IOError if the stream is not opened for reading. + * Related: StringIO#pos. */ static VALUE strio_eof(VALUE self) From 0d210f4d39c1cff71f34dbe9938345897f3e5eb1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 3 Nov 2025 18:46:51 -0600 Subject: [PATCH 0782/2435] [ruby/stringio] [DOC] Tweaks for StringIO#external_encoding (https://github.com/ruby/stringio/pull/161) https://github.com/ruby/stringio/commit/92656f5c66 --- ext/stringio/stringio.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 9133c24266884c..cf3e06a71f130e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -2047,12 +2047,20 @@ strio_truncate(VALUE self, VALUE len) } /* - * call-seq: - * strio.external_encoding => encoding + * call-seq: + * external_encoding -> encoding or nil + * + * Returns an Encoding object that represents the encoding of the string; + * see {Encoding}[rdoc-ref:Encoding]: + * + * strio = StringIO.new('foo') + * strio.external_encoding # => # + * + * Returns +nil+ if +self+ has no string and is in write mode: + * + * strio = StringIO.new(nil, 'w+') + * strio.external_encoding # => nil * - * Returns the Encoding object that represents the encoding of the file. - * If the stream is write mode and no encoding is specified, returns - * +nil+. */ static VALUE From 9ca940757315e53d1eaddc83071b1b4581f8f578 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 3 Nov 2025 16:49:25 -0800 Subject: [PATCH 0783/2435] ZJIT: Implement register spill (#14936) --- zjit/src/asm/x86_64/mod.rs | 7 -- zjit/src/asm/x86_64/tests.rs | 18 ++- zjit/src/backend/arm64/mod.rs | 187 ++++++++++++++++++++++++----- zjit/src/backend/lir.rs | 213 ++++++++++++++++++++++----------- zjit/src/backend/x86_64/mod.rs | 157 +++++++++++++++++++----- 5 files changed, 431 insertions(+), 151 deletions(-) diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index 9d3bf18dcdab41..cfedca4540361d 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -779,13 +779,6 @@ pub fn imul(cb: &mut CodeBlock, opnd0: X86Opnd, opnd1: X86Opnd) { write_rm(cb, false, true, opnd0, opnd1, None, &[0x0F, 0xAF]); } - // Flip the operands to handle this case. This instruction has weird encoding restrictions. - (X86Opnd::Mem(_), X86Opnd::Reg(_)) => { - //REX.W + 0F AF /rIMUL r64, r/m64 - // Quadword register := Quadword register * r/m64. - write_rm(cb, false, true, opnd1, opnd0, None, &[0x0F, 0xAF]); - } - _ => unreachable!() } } diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs index 0f867259466de9..d574bdb0341066 100644 --- a/zjit/src/asm/x86_64/tests.rs +++ b/zjit/src/asm/x86_64/tests.rs @@ -228,22 +228,28 @@ fn test_cqo() { fn test_imul() { let cb1 = compile(|cb| imul(cb, RAX, RBX)); let cb2 = compile(|cb| imul(cb, RDX, mem_opnd(64, RAX, 0))); - // Operands flipped for encoding since multiplication is commutative - let cb3 = compile(|cb| imul(cb, mem_opnd(64, RAX, 0), RDX)); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @r" 0x0: imul rax, rbx 0x0: imul rdx, qword ptr [rax] - 0x0: imul rdx, qword ptr [rax] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @r" 480fafc3 480faf10 - 480faf10 "); } +#[test] +#[should_panic] +fn test_imul_mem_reg() { + // imul doesn't have (Mem, Reg) encoding. Since multiplication is communicative, imul() could + // swap operands. However, x86_scratch_split may need to move the result to the output operand, + // which can be complicated if the assembler may sometimes change the result operand. + // So x86_scratch_split should be responsible for that swap, not the assembler. + compile(|cb| imul(cb, mem_opnd(64, RAX, 0), RDX)); +} + #[test] fn test_jge_label() { let cb = compile(|cb| { diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index d762b14c911503..acf0576f9c80be 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -79,6 +79,9 @@ impl From for A64Opnd { Opnd::Mem(Mem { base: MemBase::VReg(_), .. }) => { panic!("attempted to lower an Opnd::Mem with a MemBase::VReg base") }, + Opnd::Mem(Mem { base: MemBase::Stack { .. }, .. }) => { + panic!("attempted to lower an Opnd::Mem with a MemBase::Stack base") + }, Opnd::VReg { .. } => panic!("attempted to lower an Opnd::VReg"), Opnd::Value(_) => panic!("attempted to lower an Opnd::Value"), Opnd::None => panic!( @@ -203,6 +206,7 @@ pub const ALLOC_REGS: &[Reg] = &[ /// [`Assembler::arm64_scratch_split`] or [`Assembler::new_with_scratch_reg`]. const SCRATCH0_OPND: Opnd = Opnd::Reg(X15_REG); const SCRATCH1_OPND: Opnd = Opnd::Reg(X17_REG); +const SCRATCH2_OPND: Opnd = Opnd::Reg(X14_REG); impl Assembler { /// Special register for intermediate processing in arm64_emit. It should be used only by arm64_emit. @@ -690,22 +694,129 @@ impl Assembler { /// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so this /// splits them and uses scratch registers for it. fn arm64_scratch_split(self) -> Assembler { - let mut asm = Assembler::new_with_asm(&self); + /// If opnd is Opnd::Mem with a too large disp, make the disp smaller using lea. + fn split_large_disp(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd { + match opnd { + Opnd::Mem(Mem { num_bits, disp, .. }) if !mem_disp_fits_bits(disp) => { + asm.lea_into(scratch_opnd, opnd); + Opnd::mem(num_bits, scratch_opnd, 0) + } + _ => opnd, + } + } + + /// If opnd is Opnd::Mem with MemBase::Stack, lower it to Opnd::Mem with MemBase::Reg, and split a large disp. + fn split_stack_membase(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd, stack_state: &StackState) -> Opnd { + let opnd = split_only_stack_membase(asm, opnd, scratch_opnd, stack_state); + split_large_disp(asm, opnd, scratch_opnd) + } + + /// split_stack_membase but without split_large_disp. This should be used only by lea. + fn split_only_stack_membase(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd, stack_state: &StackState) -> Opnd { + if let Opnd::Mem(Mem { base: stack_membase @ MemBase::Stack { .. }, disp: opnd_disp, num_bits: opnd_num_bits }) = opnd { + let base = Opnd::Mem(stack_state.stack_membase_to_mem(stack_membase)); + let base = split_large_disp(asm, base, scratch_opnd); + asm.load_into(scratch_opnd, base); + Opnd::Mem(Mem { base: MemBase::Reg(scratch_opnd.unwrap_reg().reg_no), disp: opnd_disp, num_bits: opnd_num_bits }) + } else { + opnd + } + } + + /// If opnd is Opnd::Mem, lower it to scratch_opnd. You should use this when `opnd` is read by the instruction, not written. + fn split_memory_read(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd { + if let Opnd::Mem(_) = opnd { + let opnd = split_large_disp(asm, opnd, scratch_opnd); + let scratch_opnd = opnd.num_bits().map(|num_bits| scratch_opnd.with_num_bits(num_bits)).unwrap_or(scratch_opnd); + asm.load_into(scratch_opnd, opnd); + scratch_opnd + } else { + opnd + } + } + + /// If opnd is Opnd::Mem, set scratch_reg to *opnd. Return Some(Opnd::Mem) if it needs to be written back from scratch_reg. + fn split_memory_write(opnd: &mut Opnd, scratch_opnd: Opnd) -> Option { + if let Opnd::Mem(_) = opnd { + let mem_opnd = opnd.clone(); + *opnd = opnd.num_bits().map(|num_bits| scratch_opnd.with_num_bits(num_bits)).unwrap_or(scratch_opnd); + Some(mem_opnd) + } else { + None + } + } + + // Prepare StackState to lower MemBase::Stack + let stack_state = StackState::new(self.stack_base_idx); + + let mut asm_local = Assembler::new_with_asm(&self); + let asm = &mut asm_local; asm.accept_scratch_reg = true; let mut iterator = self.insns.into_iter().enumerate().peekable(); while let Some((_, mut insn)) = iterator.next() { match &mut insn { - &mut Insn::Mul { out, .. } => { + Insn::Add { left, right, out } | + Insn::Sub { left, right, out } | + Insn::And { left, right, out } | + Insn::Or { left, right, out } | + Insn::Xor { left, right, out } | + Insn::CSelZ { truthy: left, falsy: right, out } | + Insn::CSelNZ { truthy: left, falsy: right, out } | + Insn::CSelE { truthy: left, falsy: right, out } | + Insn::CSelNE { truthy: left, falsy: right, out } | + Insn::CSelL { truthy: left, falsy: right, out } | + Insn::CSelLE { truthy: left, falsy: right, out } | + Insn::CSelG { truthy: left, falsy: right, out } | + Insn::CSelGE { truthy: left, falsy: right, out } => { + *left = split_memory_read(asm, *left, SCRATCH0_OPND); + *right = split_memory_read(asm, *right, SCRATCH1_OPND); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + + asm.push_insn(insn); + + if let Some(mem_out) = mem_out { + let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND); + asm.store(mem_out, SCRATCH0_OPND); + } + } + Insn::Mul { left, right, out } => { + *left = split_memory_read(asm, *left, SCRATCH0_OPND); + *right = split_memory_read(asm, *right, SCRATCH1_OPND); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + let reg_out = out.clone(); + asm.push_insn(insn); + if let Some(mem_out) = mem_out { + let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND); + asm.store(mem_out, SCRATCH0_OPND); + }; + // If the next instruction is JoMul if matches!(iterator.peek(), Some((_, Insn::JoMul(_)))) { // Produce a register that is all zeros or all ones // Based on the sign bit of the 64-bit mul result - asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: out, shift: Opnd::UImm(63) }); + asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: reg_out, shift: Opnd::UImm(63) }); + } + } + Insn::RShift { opnd, out, .. } => { + *opnd = split_memory_read(asm, *opnd, SCRATCH0_OPND); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + + asm.push_insn(insn); + + if let Some(mem_out) = mem_out { + let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND); + asm.store(mem_out, SCRATCH0_OPND); } } + Insn::Cmp { left, right } | + Insn::Test { left, right } => { + *left = split_memory_read(asm, *left, SCRATCH0_OPND); + *right = split_memory_read(asm, *right, SCRATCH1_OPND); + asm.push_insn(insn); + } // For compile_exits, support splitting simple C arguments here Insn::CCall { opnds, .. } if !opnds.is_empty() => { for (i, opnd) in opnds.iter().enumerate() { @@ -714,16 +825,32 @@ impl Assembler { *opnds = vec![]; asm.push_insn(insn); } - &mut Insn::Lea { opnd, out } => { - match (opnd, out) { - // Split here for compile_exits - (Opnd::Mem(_), Opnd::Mem(_)) => { - asm.lea_into(SCRATCH0_OPND, opnd); - asm.store(out, SCRATCH0_OPND); - } - _ => { - asm.push_insn(insn); + Insn::Lea { opnd, out } => { + *opnd = split_only_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + + asm.push_insn(insn); + + if let Some(mem_out) = mem_out { + let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND); + asm.store(mem_out, SCRATCH0_OPND); + } + } + Insn::Load { opnd, out } | + Insn::LoadInto { opnd, dest: out } => { + *opnd = split_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state); + *out = split_stack_membase(asm, *out, SCRATCH1_OPND, &stack_state); + + if let Opnd::Mem(_) = out { + // If NATIVE_STACK_PTR is used as a source for Store, it's handled as xzr, storeing zero. + // To save the content of NATIVE_STACK_PTR, we need to load it into another register first. + if *opnd == NATIVE_STACK_PTR { + asm.load_into(SCRATCH0_OPND, NATIVE_STACK_PTR); + *opnd = SCRATCH0_OPND; } + asm.store(*out, *opnd); + } else { + asm.push_insn(insn); } } &mut Insn::IncrCounter { mem, value } => { @@ -741,31 +868,24 @@ impl Assembler { asm.cmp(SCRATCH1_OPND, 0.into()); asm.jne(label); } - &mut Insn::Store { dest, src } => { - let Opnd::Mem(Mem { num_bits: dest_num_bits, disp: dest_disp, .. }) = dest else { - panic!("Insn::Store destination must be Opnd::Mem: {dest:?}, {src:?}"); - }; - - // Split dest using a scratch register if necessary. - let dest = if mem_disp_fits_bits(dest_disp) { - dest - } else { - asm.lea_into(SCRATCH0_OPND, dest); - Opnd::mem(dest_num_bits, SCRATCH0_OPND, 0) - }; - - asm.store(dest, src); + Insn::Store { dest, .. } => { + *dest = split_stack_membase(asm, *dest, SCRATCH0_OPND, &stack_state); + asm.push_insn(insn); } - &mut Insn::Mov { dest, src } => { + Insn::Mov { dest, src } => { + *src = split_stack_membase(asm, *src, SCRATCH0_OPND, &stack_state); + *dest = split_large_disp(asm, *dest, SCRATCH1_OPND); match dest { - Opnd::Reg(_) => asm.load_into(dest, src), - Opnd::Mem(_) => asm.store(dest, src), + Opnd::Reg(_) => asm.load_into(*dest, *src), + Opnd::Mem(_) => asm.store(*dest, *src), _ => asm.push_insn(insn), } } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { for (dst, src) in Self::resolve_parallel_moves(moves, Some(SCRATCH0_OPND)).unwrap() { + let src = split_stack_membase(asm, src, SCRATCH1_OPND, &stack_state); + let dst = split_large_disp(asm, dst, SCRATCH2_OPND); match dst { Opnd::Reg(_) => asm.load_into(dst, src), Opnd::Mem(_) => asm.store(dst, src), @@ -779,7 +899,7 @@ impl Assembler { } } - asm + asm_local } /// Emit platform-specific machine code @@ -1157,10 +1277,11 @@ impl Assembler { load_effective_address(cb, Self::EMIT_OPND, src_base_reg_no, src_disp); A64Opnd::new_mem(dest.rm_num_bits(), Self::EMIT_OPND, 0) }; + let dst = A64Opnd::Reg(Self::EMIT_REG.with_num_bits(src_num_bits)); match src_num_bits { - 64 | 32 => ldur(cb, Self::EMIT_OPND, src_mem), - 16 => ldurh(cb, Self::EMIT_OPND, src_mem), - 8 => ldurb(cb, Self::EMIT_OPND, src_mem), + 64 | 32 => ldur(cb, dst, src_mem), + 16 => ldurh(cb, dst, src_mem), + 8 => ldurb(cb, dst, src_mem), num_bits => panic!("unexpected num_bits: {num_bits}") }; Self::EMIT_REG diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 584251de802bf2..66e89a1304d715 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -28,8 +28,12 @@ pub static JIT_PRESERVED_REGS: &[Opnd] = &[CFP, SP, EC]; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum MemBase { + /// Register: Every Opnd::Mem should have MemBase::Reg as of emit. Reg(u8), + /// Virtual register: Lowered to MemBase::Reg or MemBase::Stack in alloc_regs. VReg(usize), + /// Stack slot: Lowered to MemBase::Reg in scratch_split. + Stack { stack_idx: usize, num_bits: u8 }, } // Memory location @@ -55,6 +59,8 @@ impl fmt::Display for Mem { match self.base { MemBase::Reg(reg_no) => write!(f, "{}", mem_base_reg(reg_no))?, MemBase::VReg(idx) => write!(f, "v{idx}")?, + MemBase::Stack { stack_idx, num_bits } if num_bits == 64 => write!(f, "Stack[{stack_idx}]")?, + MemBase::Stack { stack_idx, num_bits } => write!(f, "Stack{num_bits}[{stack_idx}]")?, } if self.disp != 0 { let sign = if self.disp > 0 { '+' } else { '-' }; @@ -1143,6 +1149,81 @@ impl LiveRange { } } +/// StackState manages which stack slots are used by which VReg +pub struct StackState { + /// The maximum number of spilled VRegs at a time + stack_size: usize, + /// Map from index at the C stack for spilled VRegs to Some(vreg_idx) if allocated + stack_slots: Vec>, + /// Copy of Assembler::stack_base_idx. Used for calculating stack slot offsets. + stack_base_idx: usize, +} + +impl StackState { + /// Initialize a stack allocator + pub(super) fn new(stack_base_idx: usize) -> Self { + StackState { + stack_size: 0, + stack_slots: vec![], + stack_base_idx, + } + } + + /// Allocate a stack slot for a given vreg_idx + fn alloc_stack(&mut self, vreg_idx: usize) -> Opnd { + for stack_idx in 0..self.stack_size { + if self.stack_slots[stack_idx].is_none() { + self.stack_slots[stack_idx] = Some(vreg_idx); + return Opnd::mem(64, NATIVE_BASE_PTR, self.stack_idx_to_disp(stack_idx)); + } + } + // Every stack slot is in use. Allocate a new stack slot. + self.stack_size += 1; + self.stack_slots.push(Some(vreg_idx)); + Opnd::mem(64, NATIVE_BASE_PTR, self.stack_idx_to_disp(self.stack_slots.len() - 1)) + } + + /// Deallocate a stack slot for a given disp + fn dealloc_stack(&mut self, disp: i32) { + let stack_idx = self.disp_to_stack_idx(disp); + if self.stack_slots[stack_idx].is_some() { + self.stack_slots[stack_idx] = None; + } + } + + /// Convert the `disp` of a stack slot operand to the stack index + fn disp_to_stack_idx(&self, disp: i32) -> usize { + (-disp / SIZEOF_VALUE_I32) as usize - self.stack_base_idx - 1 + } + + /// Convert a stack index to the `disp` of the stack slot + fn stack_idx_to_disp(&self, stack_idx: usize) -> i32 { + (self.stack_base_idx + stack_idx + 1) as i32 * -SIZEOF_VALUE_I32 + } + + /// Convert Mem to MemBase::Stack + fn mem_to_stack_membase(&self, mem: Mem) -> MemBase { + match mem { + Mem { base: MemBase::Reg(reg_no), disp, num_bits } if NATIVE_BASE_PTR.unwrap_reg().reg_no == reg_no => { + let stack_idx = self.disp_to_stack_idx(disp); + MemBase::Stack { stack_idx, num_bits } + } + _ => unreachable!(), + } + } + + /// Convert MemBase::Stack to Mem + pub(super) fn stack_membase_to_mem(&self, membase: MemBase) -> Mem { + match membase { + MemBase::Stack { stack_idx, num_bits } => { + let disp = self.stack_idx_to_disp(stack_idx); + Mem { base: MemBase::Reg(NATIVE_BASE_PTR.unwrap_reg().reg_no), disp, num_bits } + } + _ => unreachable!(), + } + } +} + /// RegisterPool manages which registers are used by which VReg struct RegisterPool { /// List of registers that can be allocated @@ -1155,45 +1236,54 @@ struct RegisterPool { /// The number of live registers. /// Provides a quick way to query `pool.filter(|r| r.is_some()).count()` live_regs: usize, + + /// Fallback to let StackState allocate stack slots when RegisterPool runs out of registers. + stack_state: StackState, } impl RegisterPool { /// Initialize a register pool - fn new(regs: Vec) -> Self { + fn new(regs: Vec, stack_base_idx: usize) -> Self { let pool = vec![None; regs.len()]; RegisterPool { regs, pool, live_regs: 0, + stack_state: StackState::new(stack_base_idx), } } /// Mutate the pool to indicate that the register at the index /// has been allocated and is live. - fn alloc_reg(&mut self, vreg_idx: usize) -> Option { + fn alloc_opnd(&mut self, vreg_idx: usize) -> Opnd { for (reg_idx, reg) in self.regs.iter().enumerate() { if self.pool[reg_idx].is_none() { self.pool[reg_idx] = Some(vreg_idx); self.live_regs += 1; - return Some(*reg); + return Opnd::Reg(*reg); } } - None + self.stack_state.alloc_stack(vreg_idx) } /// Allocate a specific register - fn take_reg(&mut self, reg: &Reg, vreg_idx: usize) -> Reg { + fn take_reg(&mut self, reg: &Reg, vreg_idx: usize) -> Opnd { let reg_idx = self.regs.iter().position(|elem| elem.reg_no == reg.reg_no) .unwrap_or_else(|| panic!("Unable to find register: {}", reg.reg_no)); assert_eq!(self.pool[reg_idx], None, "register already allocated for VReg({:?})", self.pool[reg_idx]); self.pool[reg_idx] = Some(vreg_idx); self.live_regs += 1; - *reg + Opnd::Reg(*reg) } // Mutate the pool to indicate that the given register is being returned // as it is no longer used by the instruction that previously held it. - fn dealloc_reg(&mut self, reg: &Reg) { + fn dealloc_opnd(&mut self, opnd: &Opnd) { + if let Opnd::Mem(Mem { disp, .. }) = *opnd { + return self.stack_state.dealloc_stack(disp); + } + + let reg = opnd.unwrap_reg(); let reg_idx = self.regs.iter().position(|elem| elem.reg_no == reg.reg_no) .unwrap_or_else(|| panic!("Unable to find register: {}", reg.reg_no)); if self.pool[reg_idx].is_some() { @@ -1428,61 +1518,40 @@ impl Assembler /// registers because their output is used as the operand on a subsequent /// instruction. This is our implementation of the linear scan algorithm. pub(super) fn alloc_regs(mut self, regs: Vec) -> Result { - // Dump live registers for register spill debugging. - fn dump_live_regs(insns: Vec, live_ranges: Vec, num_regs: usize, spill_index: usize) { - // Convert live_ranges to live_regs: the number of live registers at each index - let mut live_regs: Vec = vec![]; - for insn_idx in 0..insns.len() { - let live_count = live_ranges.iter().filter(|range| - match (range.start, range.end) { - (Some(start), Some(end)) => start <= insn_idx && insn_idx <= end, - _ => false, - } - ).count(); - live_regs.push(live_count); - } - - // Dump insns along with live registers - for (insn_idx, insn) in insns.iter().enumerate() { - eprint!("{:3} ", if spill_index == insn_idx { "==>" } else { "" }); - for reg in 0..=num_regs { - eprint!("{:1}", if reg < live_regs[insn_idx] { "|" } else { "" }); - } - eprintln!(" [{:3}] {:?}", insn_idx, insn); - } - } - // First, create the pool of registers. - let mut pool = RegisterPool::new(regs.clone()); + let mut pool = RegisterPool::new(regs.clone(), self.stack_base_idx); - // Mapping between VReg and allocated VReg for each VReg index. - // None if no register has been allocated for the VReg. - let mut reg_mapping: Vec> = vec![None; self.live_ranges.len()]; + // Mapping between VReg and register or stack slot for each VReg index. + // None if no register or stack slot has been allocated for the VReg. + let mut vreg_opnd: Vec> = vec![None; self.live_ranges.len()]; // List of registers saved before a C call, paired with the VReg index. let mut saved_regs: Vec<(Reg, usize)> = vec![]; + // Remember the indexes of Insn::FrameSetup to update the stack size later + let mut frame_setup_idxs: Vec = vec![]; + // live_ranges is indexed by original `index` given by the iterator. let mut asm = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); while let Some((index, mut insn)) = iterator.next() { + // Remember the index of FrameSetup to bump slot_count when we know the max number of spilled VRegs. + if let Insn::FrameSetup { .. } = insn { + frame_setup_idxs.push(asm.insns.len()); + } + let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { (Insn::ParallelMov { .. }, Some(Insn::CCall { .. })) | (Insn::CCall { .. }, _) if !pool.is_empty() => { // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { - let new_reg = if let Some(new_reg) = pool.alloc_reg(vreg_idx) { - new_reg - } else { - debug!("spilling VReg is not implemented yet, can't evacuate C_RET_REG on CCall"); - return Err(CompileError::RegisterSpillOnCCall); - }; - asm.mov(Opnd::Reg(new_reg), C_RET_OPND); - pool.dealloc_reg(&C_RET_REG); - reg_mapping[vreg_idx] = Some(new_reg); + let new_opnd = pool.alloc_opnd(vreg_idx); + asm.mov(new_opnd, C_RET_OPND); + pool.dealloc_opnd(&Opnd::Reg(C_RET_REG)); + vreg_opnd[vreg_idx] = Some(new_opnd); } true @@ -1501,8 +1570,8 @@ impl Assembler // uses this operand. If it is, we can return the allocated // register to the pool. if live_ranges[idx].end() == index { - if let Some(reg) = reg_mapping[idx] { - pool.dealloc_reg(®); + if let Some(opnd) = vreg_opnd[idx] { + pool.dealloc_opnd(&opnd); } else { unreachable!("no register allocated for insn {:?}", insn); } @@ -1520,7 +1589,7 @@ impl Assembler // Save live registers for &(reg, _) in saved_regs.iter() { asm.cpush(Opnd::Reg(reg)); - pool.dealloc_reg(®); + pool.dealloc_opnd(&Opnd::Reg(reg)); } // On x86_64, maintain 16-byte stack alignment if cfg!(target_arch = "x86_64") && saved_regs.len() % 2 == 1 { @@ -1560,7 +1629,7 @@ impl Assembler if let Some(Opnd::VReg{ idx, .. }) = opnd_iter.next() { if live_ranges[*idx].end() == index { - if let Some(reg) = reg_mapping[*idx] { + if let Some(Opnd::Reg(reg)) = vreg_opnd[*idx] { out_reg = Some(pool.take_reg(®, vreg_idx)); } } @@ -1569,23 +1638,7 @@ impl Assembler // Allocate a new register for this instruction if one is not // already allocated. - if out_reg.is_none() { - out_reg = match pool.alloc_reg(vreg_idx) { - Some(reg) => Some(reg), - None => { - if get_option!(debug) { - let mut insns = asm.insns; - insns.push(insn); - for (_, insn) in iterator.by_ref() { - insns.push(insn); - } - dump_live_regs(insns, live_ranges, regs.len(), index); - } - debug!("Register spill not supported"); - return Err(CompileError::RegisterSpillOnAlloc); - } - }; - } + let out_opnd = out_reg.unwrap_or_else(|| pool.alloc_opnd(vreg_idx)); // Set the output operand on the instruction let out_num_bits = Opnd::match_num_bits_iter(insn.opnd_iter()); @@ -1594,9 +1647,9 @@ impl Assembler // output operand on this instruction because the live range // extends beyond the index of the instruction. let out = insn.out_opnd_mut().unwrap(); - let reg = out_reg.unwrap().with_num_bits(out_num_bits); - reg_mapping[out.vreg_idx()] = Some(reg); - *out = Opnd::Reg(reg); + let out_opnd = out_opnd.with_num_bits(out_num_bits); + vreg_opnd[out.vreg_idx()] = Some(out_opnd); + *out = out_opnd; } // Replace VReg and Param operands by their corresponding register @@ -1604,11 +1657,15 @@ impl Assembler while let Some(opnd) = opnd_iter.next() { match *opnd { Opnd::VReg { idx, num_bits } => { - *opnd = Opnd::Reg(reg_mapping[idx].unwrap()).with_num_bits(num_bits); + *opnd = vreg_opnd[idx].unwrap().with_num_bits(num_bits); }, Opnd::Mem(Mem { base: MemBase::VReg(idx), disp, num_bits }) => { - let base = MemBase::Reg(reg_mapping[idx].unwrap().reg_no); - *opnd = Opnd::Mem(Mem { base, disp, num_bits }); + *opnd = match vreg_opnd[idx].unwrap() { + Opnd::Reg(reg) => Opnd::Mem(Mem { base: MemBase::Reg(reg.reg_no), disp, num_bits }), + // If the base is spilled, lower it to MemBase::Stack, which scratch_split will lower to MemBase::Reg. + Opnd::Mem(mem) => Opnd::Mem(Mem { base: pool.stack_state.mem_to_stack_membase(mem), disp, num_bits }), + _ => unreachable!(), + } } _ => {}, } @@ -1618,8 +1675,8 @@ impl Assembler // register if let Some(idx) = vreg_idx { if live_ranges[idx].end() == index { - if let Some(reg) = reg_mapping[idx] { - pool.dealloc_reg(®); + if let Some(opnd) = vreg_opnd[idx] { + pool.dealloc_opnd(&opnd); } else { unreachable!("no register allocated for insn {:?}", insn); } @@ -1671,6 +1728,16 @@ impl Assembler } } + // Extend the stack space for spilled operands + for frame_setup_idx in frame_setup_idxs { + match &mut asm.insns[frame_setup_idx] { + Insn::FrameSetup { slot_count, .. } => { + *slot_count += pool.stack_state.stack_size; + } + _ => unreachable!(), + } + } + assert!(pool.is_empty(), "Expected all registers to be returned to the pool"); Ok(asm) } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 14c0df8dd02e04..1d5d90a856c92e 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -1,4 +1,4 @@ -use std::mem::take; +use std::mem::{self, take}; use crate::asm::*; use crate::asm::x86_64::*; @@ -97,13 +97,13 @@ pub const ALLOC_REGS: &[Reg] = &[ RCX_REG, R8_REG, R9_REG, - R10_REG, RAX_REG, ]; /// Special scratch register for intermediate processing. It should be used only by /// [`Assembler::x86_scratch_split`] or [`Assembler::new_with_scratch_reg`]. const SCRATCH0_OPND: Opnd = Opnd::Reg(R11_REG); +const SCRATCH1_OPND: Opnd = Opnd::Reg(R10_REG); impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. @@ -395,13 +395,13 @@ impl Assembler { /// without requiring more registers to be available in the register /// allocator. So we just use the SCRATCH0_OPND register temporarily to hold /// the value before we immediately use it. - fn split_64bit_immediate(asm: &mut Assembler, opnd: Opnd) -> Opnd { + fn split_64bit_immediate(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd { match opnd { Opnd::Imm(value) => { // 32-bit values will be sign-extended if imm_num_bits(value) > 32 { - asm.mov(SCRATCH0_OPND, opnd); - SCRATCH0_OPND + asm.mov(scratch_opnd, opnd); + scratch_opnd } else { opnd } @@ -409,8 +409,8 @@ impl Assembler { Opnd::UImm(value) => { // 32-bit values will be sign-extended if imm_num_bits(value as i64) > 32 { - asm.mov(SCRATCH0_OPND, opnd); - SCRATCH0_OPND + asm.mov(scratch_opnd, opnd); + scratch_opnd } else { Opnd::Imm(value as i64) } @@ -419,23 +419,102 @@ impl Assembler { } } - let mut asm = Assembler::new_with_asm(&self); + /// If a given operand is Opnd::Mem and it uses MemBase::Stack, lower it to MemBase::Reg using a scratch regsiter. + fn split_stack_membase(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd, stack_state: &StackState) -> Opnd { + if let Opnd::Mem(Mem { base: stack_membase @ MemBase::Stack { .. }, disp, num_bits }) = opnd { + let base = Opnd::Mem(stack_state.stack_membase_to_mem(stack_membase)); + asm.load_into(scratch_opnd, base); + Opnd::Mem(Mem { base: MemBase::Reg(scratch_opnd.unwrap_reg().reg_no), disp, num_bits }) + } else { + opnd + } + } + + /// If opnd is Opnd::Mem, set scratch_reg to *opnd. Return Some(Opnd::Mem) if it needs to be written back from scratch_reg. + fn split_memory_write(opnd: &mut Opnd, scratch_opnd: Opnd) -> Option { + if let Opnd::Mem(_) = opnd { + let mem_opnd = opnd.clone(); + *opnd = opnd.num_bits().map(|num_bits| scratch_opnd.with_num_bits(num_bits)).unwrap_or(scratch_opnd); + Some(mem_opnd) + } else { + None + } + } + + /// If both opnd and other are Opnd::Mem, split opnd with scratch_opnd. + fn split_if_both_memory(asm: &mut Assembler, opnd: Opnd, other: Opnd, scratch_opnd: Opnd) -> Opnd { + if let (Opnd::Mem(_), Opnd::Mem(_)) = (opnd, other) { + asm.load_into(scratch_opnd.with_num_bits(opnd.rm_num_bits()), opnd); + scratch_opnd.with_num_bits(opnd.rm_num_bits()) + } else { + opnd + } + } + + /// Move src to dst, splitting it with scratch_opnd if it's a Mem-to-Mem move. Skip it if dst == src. + fn asm_mov(asm: &mut Assembler, dst: Opnd, src: Opnd, scratch_opnd: Opnd) { + if dst != src { + if let (Opnd::Mem(_), Opnd::Mem(_)) = (dst, src) { + asm.mov(scratch_opnd, src); + asm.mov(dst, scratch_opnd); + } else { + asm.mov(dst, src); + } + } + } + + // Prepare StackState to lower MemBase::Stack + let stack_state = StackState::new(self.stack_base_idx); + + let mut asm_local = Assembler::new_with_asm(&self); + let asm = &mut asm_local; asm.accept_scratch_reg = true; let mut iterator = self.insns.into_iter().enumerate().peekable(); while let Some((_, mut insn)) = iterator.next() { match &mut insn { - Insn::Add { right, .. } | - Insn::Sub { right, .. } | - Insn::Mul { right, .. } | - Insn::And { right, .. } | - Insn::Or { right, .. } | - Insn::Xor { right, .. } | - Insn::Test { right, .. } => { - *right = split_64bit_immediate(&mut asm, *right); + Insn::Add { left, right, out } | + Insn::Sub { left, right, out } | + Insn::And { left, right, out } | + Insn::Or { left, right, out } | + Insn::Xor { left, right, out } => { + *left = split_stack_membase(asm, *left, SCRATCH0_OPND, &stack_state); + *left = split_if_both_memory(asm, *left, *right, SCRATCH0_OPND); + *right = split_stack_membase(asm, *right, SCRATCH1_OPND, &stack_state); + *right = split_64bit_immediate(asm, *right, SCRATCH1_OPND); + + let (out, left) = (*out, *left); + asm.push_insn(insn); + asm_mov(asm, out, left, SCRATCH0_OPND); + } + Insn::Mul { left, right, out } => { + *left = split_stack_membase(asm, *left, SCRATCH0_OPND, &stack_state); + *left = split_if_both_memory(asm, *left, *right, SCRATCH0_OPND); + *right = split_stack_membase(asm, *right, SCRATCH1_OPND, &stack_state); + *right = split_64bit_immediate(asm, *right, SCRATCH1_OPND); + + // imul doesn't have (Mem, Reg) encoding. Swap left and right in that case. + if let (Opnd::Mem(_), Opnd::Reg(_)) = (&left, &right) { + mem::swap(left, right); + } + + let (out, left) = (*out, *left); asm.push_insn(insn); + asm_mov(asm, out, left, SCRATCH0_OPND); } + &mut Insn::Not { opnd, out } | + &mut Insn::LShift { opnd, out, .. } | + &mut Insn::RShift { opnd, out, .. } | + &mut Insn::URShift { opnd, out, .. } => { + asm.push_insn(insn); + asm_mov(asm, out, opnd, SCRATCH0_OPND); + } + Insn::Test { left, right } | Insn::Cmp { left, right } => { + *left = split_stack_membase(asm, *left, SCRATCH1_OPND, &stack_state); + *right = split_stack_membase(asm, *right, SCRATCH0_OPND, &stack_state); + *right = split_if_both_memory(asm, *right, *left, SCRATCH0_OPND); + let num_bits = match right { Opnd::Imm(value) => Some(imm_num_bits(*value)), Opnd::UImm(value) => Some(uimm_num_bits(*value)), @@ -450,7 +529,7 @@ impl Assembler { // directly in the instruction. let use_imm = num_bits.is_some() && left.num_bits() == num_bits && num_bits.unwrap() < 64; if !use_imm { - *right = split_64bit_immediate(&mut asm, *right); + *right = split_64bit_immediate(asm, *right, SCRATCH0_OPND); } asm.push_insn(insn); } @@ -462,16 +541,19 @@ impl Assembler { *opnds = vec![]; asm.push_insn(insn); } - &mut Insn::Lea { opnd, out } => { - match (opnd, out) { - // Split here for compile_exits - (Opnd::Mem(_), Opnd::Mem(_)) => { - asm.lea_into(SCRATCH0_OPND, opnd); - asm.store(out, SCRATCH0_OPND); - } - _ => { - asm.push_insn(insn); - } + Insn::CSelZ { out, .. } | + Insn::CSelNZ { out, .. } | + Insn::CSelE { out, .. } | + Insn::CSelNE { out, .. } | + Insn::CSelL { out, .. } | + Insn::CSelLE { out, .. } | + Insn::CSelG { out, .. } | + Insn::CSelGE { out, .. } | + Insn::Lea { out, .. } => { + let mem_out = split_memory_write(out, SCRATCH0_OPND); + asm.push_insn(insn); + if let Some(mem_out) = mem_out { + asm.store(mem_out, SCRATCH0_OPND); } } Insn::LeaJumpTarget { target, out } => { @@ -480,6 +562,15 @@ impl Assembler { asm.mov(*out, SCRATCH0_OPND); } } + Insn::Load { out, opnd } | + Insn::LoadInto { dest: out, opnd } => { + *opnd = split_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + asm.push_insn(insn); + if let Some(mem_out) = mem_out { + asm.store(mem_out, SCRATCH0_OPND.with_num_bits(mem_out.rm_num_bits())); + } + } // Convert Opnd::const_ptr into Opnd::Mem. This split is done here to give // a register for compile_exits. &mut Insn::IncrCounter { mem, value } => { @@ -487,17 +578,19 @@ impl Assembler { asm.load_into(SCRATCH0_OPND, mem); asm.incr_counter(Opnd::mem(64, SCRATCH0_OPND, 0), value); } + &mut Insn::Mov { dest, src } => { + asm_mov(asm, dest, src, SCRATCH0_OPND); + } // Resolve ParallelMov that couldn't be handled without a scratch register. Insn::ParallelMov { moves } => { for (dst, src) in Self::resolve_parallel_moves(&moves, Some(SCRATCH0_OPND)).unwrap() { - asm.mov(dst, src) + asm_mov(asm, dst, src, SCRATCH0_OPND); } } // Handle various operand combinations for spills on compile_exits. &mut Insn::Store { dest, src } => { - let Opnd::Mem(Mem { num_bits, .. }) = dest else { - panic!("Unexpected Insn::Store destination in x86_scratch_split: {dest:?}"); - }; + let num_bits = dest.rm_num_bits(); + let dest = split_stack_membase(asm, dest, SCRATCH1_OPND, &stack_state); let src = match src { Opnd::Reg(_) => src, @@ -541,7 +634,7 @@ impl Assembler { } } - asm + asm_local } /// Emit platform-specific machine code From be495013a798891945669f940301bd9914d1519d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 3 Nov 2025 18:58:37 -0600 Subject: [PATCH 0784/2435] [ruby/stringio] [DOC] Doc for StringIO#getbyte (https://github.com/ruby/stringio/pull/162) https://github.com/ruby/stringio/commit/95a7dd592c --- doc/stringio/getbyte.rdoc | 29 +++++++++++++++++++++++++++++ ext/stringio/stringio.c | 6 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 doc/stringio/getbyte.rdoc diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc new file mode 100644 index 00000000000000..48c334b5252a58 --- /dev/null +++ b/doc/stringio/getbyte.rdoc @@ -0,0 +1,29 @@ +Reads and returns the next integer byte (not character) from the stream: + + s = 'foo' + s.bytes # => [102, 111, 111] + strio = StringIO.new(s) + strio.getbyte # => 102 + strio.getbyte # => 111 + strio.getbyte # => 111 + +Returns +nil+ if at end-of-stream: + + strio.eof? # => true + strio.getbyte # => nil + +Returns a byte, not a character: + + s = 'тест' + s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + strio = StringIO.new(s) + strio.getbyte # => 209 + strio.getbyte # => 130 + + s = 'こんにちは' + s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + strio = StringIO.new(s) + strio.getbyte # => 227 + strio.getbyte # => 129 + +Related: StringIO.getc. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index cf3e06a71f130e..5f8dc047e83dc4 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -990,10 +990,10 @@ strio_getc(VALUE self) /* * call-seq: - * getbyte -> byte or nil + * getbyte -> integer or nil + * + * :include: stringio/getbyte.rdoc * - * Reads and returns the next 8-bit byte from the stream; - * see {Byte IO}[rdoc-ref:IO@Byte+IO]. */ static VALUE strio_getbyte(VALUE self) From 136157e772ab2b2ea08555d0ad821da7dc2bde96 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 4 Nov 2025 10:01:59 +0900 Subject: [PATCH 0785/2435] Revert "[ruby/rubygems] Fix constants in TAR to be frozen" This reverts commit 2c2eaa3103e5cf1cbfc2b16d9db975a9b8a0399a. --- lib/rubygems/package/tar_header.rb | 8 ++++---- test/rubygems/test_gem_package_tar_header.rb | 19 ------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index dd20d65080ff00..0ebcbd789d4038 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -56,7 +56,7 @@ class Gem::Package::TarHeader ## # Pack format for a tar header - PACK_FORMAT = ("a100" + # name + PACK_FORMAT = "a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid @@ -71,12 +71,12 @@ class Gem::Package::TarHeader "a32" + # gname "a8" + # devmajor "a8" + # devminor - "a155").freeze # prefix + "a155" # prefix ## # Unpack format for a tar header - UNPACK_FORMAT = ("A100" + # name + UNPACK_FORMAT = "A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid @@ -91,7 +91,7 @@ class Gem::Package::TarHeader "A32" + # gname "A8" + # devmajor "A8" + # devminor - "A155").freeze # prefix + "A155" # prefix attr_reader(*FIELDS) diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb index 34f92967e9905b..a3f95bb7704f91 100644 --- a/test/rubygems/test_gem_package_tar_header.rb +++ b/test/rubygems/test_gem_package_tar_header.rb @@ -26,25 +26,6 @@ def setup @tar_header = Gem::Package::TarHeader.new header end - def test_decode_in_ractor - new_header = Ractor.new(@tar_header.to_s) do |str| - Gem::Package::TarHeader.from StringIO.new str - end.value - - assert_headers_equal @tar_header, new_header - end if defined?(Ractor) && Ractor.instance_methods.include?(:value) - - def test_encode_in_ractor - header_bytes = @tar_header.to_s - - new_header = Ractor.new(header_bytes) do |str| - header = Gem::Package::TarHeader.from StringIO.new str - header.to_s - end.value - - assert_headers_equal header_bytes, new_header - end if defined?(Ractor) && Ractor.instance_methods.include?(:value) - def test_self_from io = TempIO.new @tar_header.to_s From 15e64bd2e69899682a6dd62620c02a074a00498c Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 3 Nov 2025 19:09:30 -0600 Subject: [PATCH 0786/2435] [ruby/stringio] [DOC] Doc for StringIO#gets (https://github.com/ruby/stringio/pull/164) https://github.com/ruby/stringio/commit/10e991e31d --- doc/stringio/gets.rdoc | 98 +++++++++++++++++++++++++++++++++++++++++ ext/stringio/stringio.c | 5 +-- 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 doc/stringio/gets.rdoc diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc new file mode 100644 index 00000000000000..892c3feb53a9bf --- /dev/null +++ b/doc/stringio/gets.rdoc @@ -0,0 +1,98 @@ +Reads and returns a line from the stream; +returns +nil+ if at end-of-stream. + +Side effects: + +- Increments stream position by the number of bytes read. +- Assigns the return value to global variable $_. + +With no arguments given, reads a line using the default record separator +(global variable $/,* whose initial value is "\n"): + + strio = StringIO.new(TEXT) + strio.pos # => 0 + strio.gets # => "First line\n" + strio.pos # => 11 + $_ # => "First line\n" + strio.gets # => "Second line\n" + strio.read # => "\nFourth line\nFifth line\n" + strio.eof? # => true + strio.gets # => nil + + strio = StringIO.new('тест') # Four 2-byte characters. + strio.pos # => 0 + strio.gets # => "тест" + strio.pos # => 8 + +Argument +sep+ + +With only string argument +sep+ given, reads a line using that string as the record separator: + + strio = StringIO.new(TEXT) + strio.gets(' ') # => "First " + strio.gets(' ') # => "line\nSecond " + strio.gets(' ') # => "line\n\nFourth " + +Argument +limit+ + +With only integer argument +limit+ given, +reads a line using the default record separator; +limits the size (in characters) of each line to the given limit: + + strio = StringIO.new(TEXT) + strio.gets(10) # => "First line" + strio.gets(10) # => "\n" + strio.gets(10) # => "Second lin" + strio.gets(10) # => "e\n" + +Arguments +sep+ and +limit+ + +With arguments +sep+ and +limit+ both given, honors both: + + strio = StringIO.new(TEXT) + strio.gets(' ', 10) # => "First " + strio.gets(' ', 10) # => "line\nSecon" + strio.gets(' ', 10) # => "d " + +Position + +As stated above, method +gets+ reads and returns the next line in the stream. + +In the examples above each +strio+ object starts with its position at beginning-of-stream; +but in other cases the position may be anywhere: + + strio = StringIO.new(TEXT) + strio.pos = 12 + strio.gets # => "econd line\n" + +The position need not be at a character boundary: + + strio = StringIO.new('тест') # Four 2-byte characters. + strio.pos = 2 # At beginning of second character. + strio.gets # => "ест" + strio.pos = 3 # In middle of second character. + strio.gets # => "\xB5ст" + +Special Record Separators + +Like some methods in class IO, method +gets+ honors two special record separators; +see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values]: + + strio = StringIO.new(TEXT) + strio.gets('') # Read "paragraph" (up to empty line). + # => "First line\nSecond line\n\n" + + strio = StringIO.new(TEXT) + strio.gets(nil) # "Slurp": read all. + # => "First line\nSecond line\n\nFourth line\nFifth line\n" + +Keyword Argument +chomp+ + +With keyword argument +chomp+ given as +true+ (the default is +false+), +removes the trailing newline (if any) from the returned line: + + strio = StringIO.new(TEXT) + strio.gets # => "First line\n" + strio.gets(chomp: true) # => "Second line" + +Related: StringIO.each_line. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 5f8dc047e83dc4..d66768a2c50279 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1428,9 +1428,8 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) * gets(limit, chomp: false) -> string or nil * gets(sep, limit, chomp: false) -> string or nil * - * Reads and returns a line from the stream; - * assigns the return value to $_; - * see {Line IO}[rdoc-ref:IO@Line+IO]. + * :include: stringio/gets.rdoc + * */ static VALUE strio_gets(int argc, VALUE *argv, VALUE self) From 0eac75df46bd702dfadb730306dcad5e6b72c55e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 02:08:43 +0000 Subject: [PATCH 0787/2435] Bump gacts/run-and-post-run in /.github/actions/setup/directories Bumps [gacts/run-and-post-run](https://github.com/gacts/run-and-post-run) from 1.4.2 to 1.4.3. - [Release notes](https://github.com/gacts/run-and-post-run/releases) - [Commits](https://github.com/gacts/run-and-post-run/compare/d803f6920adc9a47eeac4cb6c93dbc2e2890c684...81b6ce503cde93862cec047c54652e45c5dca991) --- updated-dependencies: - dependency-name: gacts/run-and-post-run dependency-version: 1.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 0120f1213082c3..c0fc75ad7d9b2f 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -175,7 +175,7 @@ runs: echo final='rmdir ${{ inputs.builddir }}' >> $GITHUB_OUTPUT - name: clean - uses: gacts/run-and-post-run@d803f6920adc9a47eeac4cb6c93dbc2e2890c684 # v1.4.2 + uses: gacts/run-and-post-run@81b6ce503cde93862cec047c54652e45c5dca991 # v1.4.3 with: working-directory: post: | From 447809658afa851b6523397103c2fce74c4d5562 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Feb 2025 17:12:25 +0900 Subject: [PATCH 0788/2435] [ruby/io-wait] Select packging files by pathspecs https://github.com/ruby/io-wait/commit/c66a90f5b1 --- ext/io/wait/io-wait.gemspec | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 1554dcdb304bef..44e6b65142e2d3 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -15,20 +15,20 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject do |f| - File.identical?(f, __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) - end - end + jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby' + dir, gemspec = File.split(__FILE__) + excludes = [ + *%w[:^/.git* :^/Gemfile* :^/Rakefile* :^/bin/ :^/test/ :^/rakelib/ :^*.java], + *(jruby ? %w[:^/ext/io] : %w[:^/ext/java]), + ":(exclude,literal,top)#{gemspec}" + ] + files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") + + spec.files = files spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] - jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby' - spec.files.delete_if do |f| - f.end_with?(".java") or - f.start_with?("ext/") && (jruby ^ f.start_with?("ext/java/")) - end if jruby spec.platform = 'java' spec.files << "lib/io/wait.jar" From 397bb12778dd8940572e3fc4156085f1ca57f056 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Tue, 4 Nov 2025 14:34:31 +0900 Subject: [PATCH 0789/2435] [ruby/uri] Re-allow consecutive, leading and trailing dots in EMAIL_REGEXP Effectively reverts commit https://github.com/ruby/uri/commit/788274b180d6 and https://github.com/ruby/uri/commit/0abac721d8fe. EMAIL_REGEXP was mostly drawn from WHATWG HTML LS. This spec states that it intentionally violates RFC 5322 to provide a practical regex for validation. > This requirement is a willful violation of RFC 5322, which defines a > syntax for email addresses that is simultaneously too strict (before the > "@" character), too vague (after the "@" character), and too lax > (allowing comments, whitespace characters, and quoted strings in manners > unfamiliar to most users) to be of practical use here. The allowing of consecutive dot s(`a..a@`) and leading/trailing dots (`.a@`, `a.@`) is not the only derivation from RFC 5322. If a truly RFC 5322-compliant regexp is needed, tt should be organized under a different name, since too much departure from the original EMAIL_REGEXP must be introduced. https://github.com/ruby/uri/commit/c551d7020b --- lib/uri/mailto.rb | 6 +----- test/uri/test_mailto.rb | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/uri/mailto.rb b/lib/uri/mailto.rb index f747b79ec753b3..cb8024f301fc47 100644 --- a/lib/uri/mailto.rb +++ b/lib/uri/mailto.rb @@ -52,11 +52,7 @@ class MailTo < Generic HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ # practical regexp for email address # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address - EMAIL_REGEXP = %r[\A#{ - atext = %q[(?:[a-zA-Z0-9!\#$%&'*+\/=?^_`{|}~-]+)] - }(?:\.#{atext})*@#{ - label = %q[(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)] - }(?:\.#{label})*\z] + EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ # :startdoc: # diff --git a/test/uri/test_mailto.rb b/test/uri/test_mailto.rb index 59bb5ded09b9a9..6cd33529784796 100644 --- a/test/uri/test_mailto.rb +++ b/test/uri/test_mailto.rb @@ -145,29 +145,23 @@ def test_check_to u.to = 'a@valid.com' assert_equal(u.to, 'a@valid.com') - # Invalid emails - assert_raise(URI::InvalidComponentError) do - u.to = '#1@mail.com' - end + # Intentionally allowed violations of RFC 5322 + u.to = 'a..a@valid.com' + assert_equal(u.to, 'a..a@valid.com') - assert_raise(URI::InvalidComponentError) do - u.to = '@invalid.email' - end + u.to = 'hello.@valid.com' + assert_equal(u.to, 'hello.@valid.com') - assert_raise(URI::InvalidComponentError) do - u.to = '.hello@invalid.email' - end - - assert_raise(URI::InvalidComponentError) do - u.to = 'hello.@invalid.email' - end + u.to = '.hello@valid.com' + assert_equal(u.to, '.hello@valid.com') + # Invalid emails assert_raise(URI::InvalidComponentError) do - u.to = 'n.@invalid.email' + u.to = '#1@mail.com' end assert_raise(URI::InvalidComponentError) do - u.to = 'n..t@invalid.email' + u.to = '@invalid.email' end # Invalid host emails From 83c2e3b92e25b766615600913cdca8174128f262 Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Tue, 4 Nov 2025 15:52:23 +0900 Subject: [PATCH 0790/2435] [ruby/uri] v1.1.1 https://github.com/ruby/uri/commit/f1b05c89ab --- lib/uri/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/version.rb b/lib/uri/version.rb index 03d6080fd33b0f..1f810602eb3faf 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION = '1.1.0'.freeze + VERSION = '1.1.1'.freeze VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end From 55257b50383bba389dbcfbc7379541e8093da0c7 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 4 Nov 2025 06:54:54 +0000 Subject: [PATCH 0791/2435] Update default gems list at 83c2e3b92e25b766615600913cdca8 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index ab9c0c32b1f958..8917a7d753ac56 100644 --- a/NEWS.md +++ b/NEWS.md @@ -203,7 +203,7 @@ The following default gems are updated. * stringio 3.1.8.dev * strscan 3.1.6.dev * timeout 0.4.4 -* uri 1.1.0 +* uri 1.1.1 * weakref 0.1.4 * zlib 3.2.2 From 157ae44b1e5f44d04e3dcb5cad4653e0f56e889d Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 4 Nov 2025 09:03:35 +0100 Subject: [PATCH 0792/2435] [ruby/json] Extract `JSON_CPU_LITTLE_ENDIAN_64BITS` definition Only apply these definitions on 64 bits archs, as it's unclear if they have performance benefits or compatibility issues on 32bit archs. https://github.com/ruby/json/commit/ddad00b746 --- ext/json/json.h | 7 +++++++ ext/json/parser/parser.c | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ext/json/json.h b/ext/json/json.h index 20e7101de7b0da..01fe0cd034e7e3 100644 --- a/ext/json/json.h +++ b/ext/json/json.h @@ -3,6 +3,7 @@ #include "ruby.h" #include "ruby/encoding.h" +#include #if defined(RUBY_DEBUG) && RUBY_DEBUG # define JSON_ASSERT RUBY_ASSERT @@ -82,4 +83,10 @@ typedef unsigned char _Bool; #endif #endif +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX +#define JSON_CPU_LITTLE_ENDIAN_64BITS 1 +#else +#define JSON_CPU_LITTLE_ENDIAN_64BITS 0 +#endif + #endif // _JSON_H_ diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 20754249e2ca39..0f4d31097bf8bb 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -323,7 +323,7 @@ typedef struct JSON_ParserStateStruct { int current_nesting; } JSON_ParserState; -static inline ssize_t rest(JSON_ParserState *state) { +static inline size_t rest(JSON_ParserState *state) { return state->end - state->cursor; } @@ -525,7 +525,7 @@ json_eat_whitespace(JSON_ParserState *state) state->cursor++; // Heuristic: if we see a newline, there is likely consecutive spaces after it. -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#if JSON_CPU_LITTLE_ENDIAN_64BITS while (rest(state) > 8) { uint64_t chunk; memcpy(&chunk, state->cursor, sizeof(uint64_t)); @@ -966,7 +966,7 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig return Qfalse; } -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#if JSON_CPU_LITTLE_ENDIAN_64BITS // From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/ // Additional References: // https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ @@ -995,8 +995,8 @@ static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulat { const char *start = state->cursor; -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - while (rest(state) >= 8) { +#if JSON_CPU_LITTLE_ENDIAN_64BITS + while (rest(state) >= sizeof(uint64_t)) { uint64_t next_8bytes; memcpy(&next_8bytes, state->cursor, sizeof(uint64_t)); From 7c924013630699321d51438f3754b291d8d4a37e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 4 Nov 2025 09:08:05 +0100 Subject: [PATCH 0793/2435] [ruby/json] Micro-optimize `rstring_cache_fetch` Closes: https://github.com/ruby/json/pull/888 - Mark it as `inline`. - Use `RSTRING_GETMEM`, instead of `RSTRING_LEN` and `RSTRING_PTR`. - Use an inlinable version of `memcmp`. ``` == Parsing activitypub.json (58160 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Comparison: before: 11766.6 i/s after: 12272.1 i/s - 1.04x faster == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Comparison: before: 1333.2 i/s after: 1422.0 i/s - 1.07x faster == Parsing citm_catalog.json (1727030 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Comparison: before: 656.3 i/s after: 673.1 i/s - 1.03x faster == Parsing float parsing (2251051 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Comparison: before: 276.8 i/s after: 276.4 i/s - same-ish: difference falls within error ``` https://github.com/ruby/json/commit/a67d1a1af4 Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 43 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 0f4d31097bf8bb..3d88a399e746af 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -84,17 +84,52 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring cache->entries[index] = rstring; } -static inline int rstring_cache_cmp(const char *str, const long length, VALUE rstring) +#if JSON_CPU_LITTLE_ENDIAN_64BITS && defined(__has_builtin) && __has_builtin(__builtin_bswap64) +static ALWAYS_INLINE() int rstring_cache_memcmp(const char *str, const char *rptr, const long length) { - long rstring_length = RSTRING_LEN(rstring); + // The libc memcmp has numerous complex optimizations, but in this particular case, + // we know the string is small (JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH), so being able to + // inline a simpler memcmp outperforms calling the libc version. + long i = 0; + + for (; i + 8 <= length; i += 8) { + uint64_t a, b; + memcpy(&a, str + i, 8); + memcpy(&b, rptr + i, 8); + if (a != b) { + a = __builtin_bswap64(a); + b = __builtin_bswap64(b); + return (a < b) ? -1 : 1; + } + } + + for (; i < length; i++) { + if (str[i] != rptr[i]) { + return (str[i] < rptr[i]) ? -1 : 1; + } + } + + return 0; +} +#else +#define rstring_cache_memcmp memcmp +#endif + +static ALWAYS_INLINE() int rstring_cache_cmp(const char *str, const long length, VALUE rstring) +{ + const char *rstring_ptr; + long rstring_length; + + RSTRING_GETMEM(rstring, rstring_ptr, rstring_length); + if (length == rstring_length) { - return memcmp(str, RSTRING_PTR(rstring), length); + return rstring_cache_memcmp(str, rstring_ptr, length); } else { return (int)(length - rstring_length); } } -static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +static ALWAYS_INLINE() VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) { int low = 0; int high = cache->length - 1; From f1776e8f176f50bd9ab16ca399d1a9c8b7d6eeb7 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 4 Nov 2025 12:06:46 +0100 Subject: [PATCH 0794/2435] [ruby/json] Tentative fix for RHEL8 compiler ``` parser.c:87:77: error: missing binary operator before token "(" #if JSON_CPU_LITTLE_ENDIAN_64BITS && defined(__has_builtin) && __has_builtin(__builtin_bswap64) ``` https://github.com/ruby/json/commit/fce1c7e84a --- ext/json/parser/parser.c | 9 ++++++--- ext/json/simd/simd.h | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 3d88a399e746af..9df04ce0079fad 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -84,7 +84,11 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring cache->entries[index] = rstring; } -#if JSON_CPU_LITTLE_ENDIAN_64BITS && defined(__has_builtin) && __has_builtin(__builtin_bswap64) +#define rstring_cache_memcmp memcmp + +#if JSON_CPU_LITTLE_ENDIAN_64BITS +#if __has_builtin(__builtin_bswap64) +#undef rstring_cache_memcmp static ALWAYS_INLINE() int rstring_cache_memcmp(const char *str, const char *rptr, const long length) { // The libc memcmp has numerous complex optimizations, but in this particular case, @@ -111,8 +115,7 @@ static ALWAYS_INLINE() int rstring_cache_memcmp(const char *str, const char *rpt return 0; } -#else -#define rstring_cache_memcmp memcmp +#endif #endif static ALWAYS_INLINE() int rstring_cache_cmp(const char *str, const long length, VALUE rstring) diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index a4f917fd0ae518..c9e3b3ec7c9f28 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -6,6 +6,10 @@ typedef enum { SIMD_SSE2 } SIMD_Implementation; +#ifndef __has_builtin // Optional of course. + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. +#endif + #ifdef __clang__ # if __has_builtin(__builtin_ctzll) # define HAVE_BUILTIN_CTZLL 1 From cdcb490d2bceb4ff8d6fce349047a2b722e6ae87 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Nov 2025 19:18:07 +0900 Subject: [PATCH 0795/2435] Tweak ENC_TRANS_SO_D It corresponds to TRANSSODIR, that contains `$(arch)`, so should contain it as well. --- enc/Makefile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enc/Makefile.in b/enc/Makefile.in index ce93fdd22d2350..17888f8f9b5e10 100644 --- a/enc/Makefile.in +++ b/enc/Makefile.in @@ -24,8 +24,8 @@ OBJEXT = @OBJEXT@ LIBEXT = @LIBEXT@ EXEEXT = @EXEEXT@ TIMESTAMPDIR = $(EXTOUT)/.timestamp -ENC_TRANS_D = $(TIMESTAMPDIR)/.enc-trans.time -ENC_TRANS_SO_D = $(TIMESTAMPDIR)/.enc-trans.so.time +ENC_TRANS_D = $(TIMESTAMPDIR)/enc-trans.time +ENC_TRANS_SO_D = $(TIMESTAMPDIR)/enc-trans-$(arch).time BUILTIN_ENCS = enc/ascii.c enc/us_ascii.c\ enc/unicode.c enc/utf_8.c From 29847070f00184d7c0a97f8e1f18f5b4a9775076 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Nov 2025 18:00:22 +0900 Subject: [PATCH 0796/2435] [ruby/io-wait] bump up to 0.3.3 https://github.com/ruby/io-wait/commit/57bc0b752b --- ext/io/wait/io-wait.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 44e6b65142e2d3..e16d7f10c349b2 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.3.2" +_VERSION = "0.3.3" Gem::Specification.new do |spec| spec.name = "io-wait" From ee74b97e521b9027c21390c969d51225ba417f09 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 4 Nov 2025 16:16:28 +0000 Subject: [PATCH 0797/2435] Update default gems list at 29847070f00184d7c0a97f8e1f18f5 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 8917a7d753ac56..0aa4aa63b36dac 100644 --- a/NEWS.md +++ b/NEWS.md @@ -191,7 +191,7 @@ The following default gems are updated. * fileutils 1.8.0 * io-console 0.8.1 * io-nonblock 0.3.2 -* io-wait 0.3.2 +* io-wait 0.3.3 * json 2.15.2 * net-http 0.7.0 * openssl 4.0.0.pre From 14f6f7051b9fd226dc209961563879b9cf18759e Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 30 Oct 2025 11:30:51 -0700 Subject: [PATCH 0798/2435] Fix rb_gc_impl_checking_shareable for modular GC This implements it the same as the other modular GC functions --- gc.c | 5 +++-- gc/gc_impl.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index 53cfe839d5b912..a8320246d03d89 100644 --- a/gc.c +++ b/gc.c @@ -620,6 +620,7 @@ typedef struct gc_function_map { void (*config_set)(void *objspace_ptr, VALUE hash); void (*stress_set)(void *objspace_ptr, VALUE flag); VALUE (*stress_get)(void *objspace_ptr); + bool (*checking_shareable)(void *objspace_ptr); // Object allocation VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); size_t (*obj_slot_size)(VALUE obj); @@ -794,6 +795,7 @@ ruby_modular_gc_init(void) load_modular_gc_func(config_get); load_modular_gc_func(stress_set); load_modular_gc_func(stress_get); + load_modular_gc_func(checking_shareable); // Object allocation load_modular_gc_func(new_obj); load_modular_gc_func(obj_slot_size); @@ -874,6 +876,7 @@ ruby_modular_gc_init(void) # define rb_gc_impl_config_set rb_gc_functions.config_set # define rb_gc_impl_stress_set rb_gc_functions.stress_set # define rb_gc_impl_stress_get rb_gc_functions.stress_get +# define rb_gc_impl_checking_shareable rb_gc_functions.checking_shareable // Object allocation # define rb_gc_impl_new_obj rb_gc_functions.new_obj # define rb_gc_impl_obj_slot_size rb_gc_functions.obj_slot_size @@ -2804,8 +2807,6 @@ mark_m_tbl(void *objspace, struct rb_id_table *tbl) } } -bool rb_gc_impl_checking_shareable(void *objspace_ptr); // in defaut/deafult.c - bool rb_gc_checking_shareable(void) { diff --git a/gc/gc_impl.h b/gc/gc_impl.h index 3250483639e775..2c05fe6cff0671 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -54,6 +54,7 @@ GC_IMPL_FN void rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag); GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); +GC_IMPL_FN bool rb_gc_impl_checking_shareable(void *objspace_ptr); // Object allocation GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); From b6f00701cbcee1ca791384b571e1e4057749834a Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 4 Nov 2025 12:46:00 -0500 Subject: [PATCH 0799/2435] [DOC] Mention on top of `vm_*.c` files the VM translation unit they're in (#15048) vm_method.c already mentions it. --- vm_args.c | 2 +- vm_eval.c | 2 +- vm_insnhelper.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vm_args.c b/vm_args.c index 777bcd21b33edc..5952b32f1fdb36 100644 --- a/vm_args.c +++ b/vm_args.c @@ -1,6 +1,6 @@ /********************************************************************** - vm_args.c - process method call arguments. + vm_args.c - process method call arguments. Included into vm.c. $Author$ diff --git a/vm_eval.c b/vm_eval.c index b791cd4990b00f..cd3f1bbafa4c57 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1,6 +1,6 @@ /********************************************************************** - vm_eval.c - + vm_eval.c - Included into vm.c. $Author$ created at: Sat May 24 16:02:32 JST 2008 diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 27177d9b13ef75..e3ae25b176c749 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1,6 +1,6 @@ /********************************************************************** - vm_insnhelper.c - instruction helper functions. + vm_insnhelper.c - instruction helper functions. Included into vm.c. $Author$ From 36cd985db4609b87575b08b9bff17504266de28b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 02:24:32 +0900 Subject: [PATCH 0800/2435] [ruby/strscan] Remove no longer used variable Since https://github.com/ruby/strscan/commit/92961cde2b42. https://github.com/ruby/strscan/commit/911f9c682a --- ext/strscan/strscan.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index bc543f62b1aa1e..d1b5c5e1d3a812 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -30,7 +30,6 @@ extern size_t onig_region_memsize(const struct re_registers *regs); static VALUE StringScanner; static VALUE ScanError; -static ID id_byteslice; static int usascii_encindex, utf8_encindex, binary_encindex; @@ -2287,8 +2286,6 @@ Init_strscan(void) ID id_scanerr = rb_intern("ScanError"); VALUE tmp; - id_byteslice = rb_intern("byteslice"); - usascii_encindex = rb_usascii_encindex(); utf8_encindex = rb_utf8_encindex(); binary_encindex = rb_ascii8bit_encindex(); From 480080b5bf8a8c262b65469348836555955a5852 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 27 Oct 2025 11:59:39 +0100 Subject: [PATCH 0801/2435] [ruby/stringio] fix: (jruby) failing to clean buffer's code-range same bug as: https://github.com/jruby/jruby/issues/9035 https://github.com/ruby/stringio/commit/65b144b175 --- test/stringio/test_stringio.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 70bab8afe2a0a2..fe890406074654 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -1064,6 +1064,20 @@ def test_coderange_after_overwrite assert_predicate(s.string, :ascii_only?) end + def test_coderange_after_read_into_buffer + s = StringIO.new("01234567890".b) + + buf = "¿Cómo estás? Ça va bien?" + assert_not_predicate(buf, :ascii_only?) + + assert_predicate(s.string, :ascii_only?) + + s.read(10, buf) + + assert_predicate(buf, :ascii_only?) + assert_equal '0123456789', buf + end + require "objspace" if ObjectSpace.respond_to?(:dump) && ObjectSpace.dump(eval(%{"test"})).include?('"chilled":true') # Ruby 3.4+ chilled strings def test_chilled_string From 091a1cd880f2f03085c408c5d8fe4b543eee009b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 04:06:05 +0900 Subject: [PATCH 0802/2435] Remove tests for obsolete StringScanner methods ruby/strscan#168 --- spec/ruby/library/stringscanner/clear_spec.rb | 18 ---- spec/ruby/library/stringscanner/empty_spec.rb | 18 ---- spec/ruby/library/stringscanner/eos_spec.rb | 17 +++- .../library/stringscanner/get_byte_spec.rb | 85 +++++++++++++++++- .../library/stringscanner/getbyte_spec.rb | 21 ----- spec/ruby/library/stringscanner/peek_spec.rb | 39 ++++++++- spec/ruby/library/stringscanner/peep_spec.rb | 18 ---- .../library/stringscanner/rest_size_spec.rb | 18 +++- spec/ruby/library/stringscanner/rest_spec.rb | 21 ----- .../library/stringscanner/restsize_spec.rb | 18 ---- spec/ruby/library/stringscanner/shared/eos.rb | 17 ---- .../library/stringscanner/shared/get_byte.rb | 87 ------------------- .../ruby/library/stringscanner/shared/peek.rb | 39 --------- .../library/stringscanner/shared/rest_size.rb | 18 ---- .../library/stringscanner/shared/terminate.rb | 8 -- .../library/stringscanner/terminate_spec.rb | 8 +- 16 files changed, 157 insertions(+), 293 deletions(-) delete mode 100644 spec/ruby/library/stringscanner/clear_spec.rb delete mode 100644 spec/ruby/library/stringscanner/empty_spec.rb delete mode 100644 spec/ruby/library/stringscanner/getbyte_spec.rb delete mode 100644 spec/ruby/library/stringscanner/peep_spec.rb delete mode 100644 spec/ruby/library/stringscanner/restsize_spec.rb delete mode 100644 spec/ruby/library/stringscanner/shared/eos.rb delete mode 100644 spec/ruby/library/stringscanner/shared/get_byte.rb delete mode 100644 spec/ruby/library/stringscanner/shared/peek.rb delete mode 100644 spec/ruby/library/stringscanner/shared/rest_size.rb delete mode 100644 spec/ruby/library/stringscanner/shared/terminate.rb diff --git a/spec/ruby/library/stringscanner/clear_spec.rb b/spec/ruby/library/stringscanner/clear_spec.rb deleted file mode 100644 index 7ae089704a101a..00000000000000 --- a/spec/ruby/library/stringscanner/clear_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/terminate' -require 'strscan' - -describe "StringScanner#clear" do - it_behaves_like :strscan_terminate, :clear - - it "warns in verbose mode that the method is obsolete" do - s = StringScanner.new("abc") - -> { - s.clear - }.should complain(/clear.*obsolete.*terminate/, verbose: true) - - -> { - s.clear - }.should_not complain(verbose: false) - end -end diff --git a/spec/ruby/library/stringscanner/empty_spec.rb b/spec/ruby/library/stringscanner/empty_spec.rb deleted file mode 100644 index d9449bea6ea899..00000000000000 --- a/spec/ruby/library/stringscanner/empty_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/eos' -require 'strscan' - -describe "StringScanner#empty?" do - it_behaves_like :strscan_eos, :empty? - - it "warns in verbose mode that the method is obsolete" do - s = StringScanner.new("abc") - -> { - s.empty? - }.should complain(/empty?.*obsolete.*eos?/, verbose: true) - - -> { - s.empty? - }.should_not complain(verbose: false) - end -end diff --git a/spec/ruby/library/stringscanner/eos_spec.rb b/spec/ruby/library/stringscanner/eos_spec.rb index b58ee1e4737dd5..03c2804e5b647f 100644 --- a/spec/ruby/library/stringscanner/eos_spec.rb +++ b/spec/ruby/library/stringscanner/eos_spec.rb @@ -1,7 +1,20 @@ require_relative '../../spec_helper' -require_relative 'shared/eos' require 'strscan' describe "StringScanner#eos?" do - it_behaves_like :strscan_eos, :eos? + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns true if the scan pointer is at the end of the string" do + @s.terminate + @s.should.eos? + + s = StringScanner.new('') + s.should.eos? + end + + it "returns false if the scan pointer is not at the end of the string" do + @s.should_not.eos? + end end diff --git a/spec/ruby/library/stringscanner/get_byte_spec.rb b/spec/ruby/library/stringscanner/get_byte_spec.rb index 29e2f557de1914..b3c2b7f678edd6 100644 --- a/spec/ruby/library/stringscanner/get_byte_spec.rb +++ b/spec/ruby/library/stringscanner/get_byte_spec.rb @@ -1,7 +1,88 @@ +# encoding: binary require_relative '../../spec_helper' -require_relative 'shared/get_byte' require 'strscan' describe "StringScanner#get_byte" do - it_behaves_like :strscan_get_byte, :get_byte + it "scans one byte and returns it" do + s = StringScanner.new('abc5.') + s.get_byte.should == 'a' + s.get_byte.should == 'b' + s.get_byte.should == 'c' + s.get_byte.should == '5' + s.get_byte.should == '.' + end + + it "is not multi-byte character sensitive" do + s = StringScanner.new("\244\242") + s.get_byte.should == "\244" + s.get_byte.should == "\242" + end + + it "returns nil at the end of the string" do + # empty string case + s = StringScanner.new('') + s.get_byte.should == nil + s.get_byte.should == nil + + # non-empty string case + s = StringScanner.new('a') + s.get_byte # skip one + s.get_byte.should == nil + end + + describe "#[] successive call with a capture group name" do + # https://github.com/ruby/strscan/issues/139 + ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil" do + s = StringScanner.new("This is a test") + s.get_byte + s.should.matched? + s[:a].should be_nil + end + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError" do + s = StringScanner.new("This is a test") + s.get_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + + it "returns a matching character when given Integer index" do + s = StringScanner.new("This is a test") + s.get_byte + s[0].should == "T" + end + + # https://github.com/ruby/strscan/issues/135 + ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + s.exist?(/(?This)/) + s.should.matched? + s[:a].should == "This" + + s.get_byte + s.should.matched? + s[:a].should be_nil + end + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + s.exist?(/(?This)/) + s.should.matched? + s[:a].should == "This" + + s.get_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + end end diff --git a/spec/ruby/library/stringscanner/getbyte_spec.rb b/spec/ruby/library/stringscanner/getbyte_spec.rb deleted file mode 100644 index e0659a5829a4a7..00000000000000 --- a/spec/ruby/library/stringscanner/getbyte_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/get_byte' -require_relative 'shared/extract_range' -require 'strscan' - -describe "StringScanner#getbyte" do - it_behaves_like :strscan_get_byte, :getbyte - - it "warns in verbose mode that the method is obsolete" do - s = StringScanner.new("abc") - -> { - s.getbyte - }.should complain(/getbyte.*obsolete.*get_byte/, verbose: true) - - -> { - s.getbyte - }.should_not complain(verbose: false) - end - - it_behaves_like :extract_range, :getbyte -end diff --git a/spec/ruby/library/stringscanner/peek_spec.rb b/spec/ruby/library/stringscanner/peek_spec.rb index cbb5630ff9ff03..d490abecf9661e 100644 --- a/spec/ruby/library/stringscanner/peek_spec.rb +++ b/spec/ruby/library/stringscanner/peek_spec.rb @@ -1,7 +1,42 @@ require_relative '../../spec_helper' -require_relative 'shared/peek' require 'strscan' describe "StringScanner#peek" do - it_behaves_like :strscan_peek, :peek + before :each do + @s = StringScanner.new('This is a test') + end + + it "returns at most the specified number of bytes from the current position" do + @s.peek(4).should == "This" + @s.pos.should == 0 + @s.pos = 5 + @s.peek(2).should == "is" + @s.peek(1000).should == "is a test" + + s = StringScanner.new("été") + s.peek(2).should == "é" + end + + it "returns an empty string when the passed argument is zero" do + @s.peek(0).should == "" + end + + it "raises a ArgumentError when the passed argument is negative" do + -> { @s.peek(-2) }.should raise_error(ArgumentError) + end + + it "raises a RangeError when the passed argument is a Bignum" do + -> { @s.peek(bignum_value) }.should raise_error(RangeError) + end + + it "returns an instance of String when passed a String subclass" do + cls = Class.new(String) + sub = cls.new("abc") + + s = StringScanner.new(sub) + + ch = s.peek(1) + ch.should_not be_kind_of(cls) + ch.should be_an_instance_of(String) + end end diff --git a/spec/ruby/library/stringscanner/peep_spec.rb b/spec/ruby/library/stringscanner/peep_spec.rb deleted file mode 100644 index bf6d579325aa2c..00000000000000 --- a/spec/ruby/library/stringscanner/peep_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/peek' -require 'strscan' - -describe "StringScanner#peep" do - it_behaves_like :strscan_peek, :peep - - it "warns in verbose mode that the method is obsolete" do - s = StringScanner.new("abc") - -> { - s.peep(1) - }.should complain(/peep.*obsolete.*peek/, verbose: true) - - -> { - s.peep(1) - }.should_not complain(verbose: false) - end -end diff --git a/spec/ruby/library/stringscanner/rest_size_spec.rb b/spec/ruby/library/stringscanner/rest_size_spec.rb index e62e3a8f8c2d36..a5e971631a5303 100644 --- a/spec/ruby/library/stringscanner/rest_size_spec.rb +++ b/spec/ruby/library/stringscanner/rest_size_spec.rb @@ -1,7 +1,21 @@ require_relative '../../spec_helper' -require_relative 'shared/rest_size' require 'strscan' describe "StringScanner#rest_size" do - it_behaves_like :strscan_rest_size, :rest_size + before :each do + @s = StringScanner.new('This is a test') + end + + it "returns the length of the rest of the string" do + @s.rest_size.should == 14 + @s.scan(/This/) + @s.rest_size.should == 10 + @s.terminate + @s.rest_size.should == 0 + end + + it "is equivalent to rest.size" do + @s.scan(/This/) + @s.rest_size.should == @s.rest.size + end end diff --git a/spec/ruby/library/stringscanner/rest_spec.rb b/spec/ruby/library/stringscanner/rest_spec.rb index 67072f880de2ce..25dcaf30ce4165 100644 --- a/spec/ruby/library/stringscanner/rest_spec.rb +++ b/spec/ruby/library/stringscanner/rest_spec.rb @@ -25,24 +25,3 @@ it_behaves_like :extract_range_matched, :rest end - -describe "StringScanner#rest?" do - before :each do - @s = StringScanner.new("This is a test") - end - - it "returns true if there is more data in the string" do - @s.rest?.should be_true - @s.scan(/This/) - @s.rest?.should be_true - end - - it "returns false if there is no more data in the string" do - @s.terminate - @s.rest?.should be_false - end - - it "is the opposite of eos?" do - @s.rest?.should_not == @s.eos? - end -end diff --git a/spec/ruby/library/stringscanner/restsize_spec.rb b/spec/ruby/library/stringscanner/restsize_spec.rb deleted file mode 100644 index 710520afae4772..00000000000000 --- a/spec/ruby/library/stringscanner/restsize_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/rest_size' -require 'strscan' - -describe "StringScanner#restsize" do - it_behaves_like :strscan_rest_size, :restsize - - it "warns in verbose mode that the method is obsolete" do - s = StringScanner.new("abc") - -> { - s.restsize - }.should complain(/restsize.*obsolete.*rest_size/, verbose: true) - - -> { - s.restsize - }.should_not complain(verbose: false) - end -end diff --git a/spec/ruby/library/stringscanner/shared/eos.rb b/spec/ruby/library/stringscanner/shared/eos.rb deleted file mode 100644 index ea04c764a28921..00000000000000 --- a/spec/ruby/library/stringscanner/shared/eos.rb +++ /dev/null @@ -1,17 +0,0 @@ -describe :strscan_eos, shared: true do - before :each do - @s = StringScanner.new("This is a test") - end - - it "returns true if the scan pointer is at the end of the string" do - @s.terminate - @s.send(@method).should be_true - - s = StringScanner.new('') - s.send(@method).should be_true - end - - it "returns false if the scan pointer is not at the end of the string" do - @s.send(@method).should be_false - end -end diff --git a/spec/ruby/library/stringscanner/shared/get_byte.rb b/spec/ruby/library/stringscanner/shared/get_byte.rb deleted file mode 100644 index 1f7378d5c6e72a..00000000000000 --- a/spec/ruby/library/stringscanner/shared/get_byte.rb +++ /dev/null @@ -1,87 +0,0 @@ -# encoding: binary -require 'strscan' - -describe :strscan_get_byte, shared: true do - it "scans one byte and returns it" do - s = StringScanner.new('abc5.') - s.send(@method).should == 'a' - s.send(@method).should == 'b' - s.send(@method).should == 'c' - s.send(@method).should == '5' - s.send(@method).should == '.' - end - - it "is not multi-byte character sensitive" do - s = StringScanner.new("\244\242") - s.send(@method).should == "\244" - s.send(@method).should == "\242" - end - - it "returns nil at the end of the string" do - # empty string case - s = StringScanner.new('') - s.send(@method).should == nil - s.send(@method).should == nil - - # non-empty string case - s = StringScanner.new('a') - s.send(@method) # skip one - s.send(@method).should == nil - end - - describe "#[] successive call with a capture group name" do - # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes - version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" - it "returns nil" do - s = StringScanner.new("This is a test") - s.send(@method) - s.should.matched? - s[:a].should be_nil - end - end - end - version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" - it "raises IndexError" do - s = StringScanner.new("This is a test") - s.send(@method) - s.should.matched? - -> { s[:a] }.should raise_error(IndexError) - end - end - - it "returns a matching character when given Integer index" do - s = StringScanner.new("This is a test") - s.send(@method) - s[0].should == "T" - end - - # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes - version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" - it "ignores the previous matching with Regexp" do - s = StringScanner.new("This is a test") - s.exist?(/(?This)/) - s.should.matched? - s[:a].should == "This" - - s.send(@method) - s.should.matched? - s[:a].should be_nil - end - end - end - version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" - it "ignores the previous matching with Regexp" do - s = StringScanner.new("This is a test") - s.exist?(/(?This)/) - s.should.matched? - s[:a].should == "This" - - s.send(@method) - s.should.matched? - -> { s[:a] }.should raise_error(IndexError) - end - end - end -end diff --git a/spec/ruby/library/stringscanner/shared/peek.rb b/spec/ruby/library/stringscanner/shared/peek.rb deleted file mode 100644 index 4c757866c19370..00000000000000 --- a/spec/ruby/library/stringscanner/shared/peek.rb +++ /dev/null @@ -1,39 +0,0 @@ -describe :strscan_peek, shared: true do - before :each do - @s = StringScanner.new('This is a test') - end - - it "returns at most the specified number of bytes from the current position" do - @s.send(@method, 4).should == "This" - @s.pos.should == 0 - @s.pos = 5 - @s.send(@method, 2).should == "is" - @s.send(@method, 1000).should == "is a test" - - s = StringScanner.new("été") - s.send(@method, 2).should == "é" - end - - it "returns an empty string when the passed argument is zero" do - @s.send(@method, 0).should == "" - end - - it "raises a ArgumentError when the passed argument is negative" do - -> { @s.send(@method, -2) }.should raise_error(ArgumentError) - end - - it "raises a RangeError when the passed argument is a Bignum" do - -> { @s.send(@method, bignum_value) }.should raise_error(RangeError) - end - - it "returns an instance of String when passed a String subclass" do - cls = Class.new(String) - sub = cls.new("abc") - - s = StringScanner.new(sub) - - ch = s.send(@method, 1) - ch.should_not be_kind_of(cls) - ch.should be_an_instance_of(String) - end -end diff --git a/spec/ruby/library/stringscanner/shared/rest_size.rb b/spec/ruby/library/stringscanner/shared/rest_size.rb deleted file mode 100644 index 4c4f49e45c0037..00000000000000 --- a/spec/ruby/library/stringscanner/shared/rest_size.rb +++ /dev/null @@ -1,18 +0,0 @@ -describe :strscan_rest_size, shared: true do - before :each do - @s = StringScanner.new('This is a test') - end - - it "returns the length of the rest of the string" do - @s.send(@method).should == 14 - @s.scan(/This/) - @s.send(@method).should == 10 - @s.terminate - @s.send(@method).should == 0 - end - - it "is equivalent to rest.size" do - @s.scan(/This/) - @s.send(@method).should == @s.rest.size - end -end diff --git a/spec/ruby/library/stringscanner/shared/terminate.rb b/spec/ruby/library/stringscanner/shared/terminate.rb deleted file mode 100644 index bf41d097e25806..00000000000000 --- a/spec/ruby/library/stringscanner/shared/terminate.rb +++ /dev/null @@ -1,8 +0,0 @@ -describe :strscan_terminate, shared: true do - it "set the scan pointer to the end of the string and clear matching data." do - s = StringScanner.new('This is a test') - s.send(@method) - s.bol?.should be_false - s.eos?.should be_true - end -end diff --git a/spec/ruby/library/stringscanner/terminate_spec.rb b/spec/ruby/library/stringscanner/terminate_spec.rb index 7943efe0f916df..3cff5c010c4c7a 100644 --- a/spec/ruby/library/stringscanner/terminate_spec.rb +++ b/spec/ruby/library/stringscanner/terminate_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' -require_relative 'shared/terminate' require 'strscan' describe "StringScanner#terminate" do - it_behaves_like :strscan_terminate, :terminate + it "set the scan pointer to the end of the string and clear matching data." do + s = StringScanner.new('This is a test') + s.terminate + s.should_not.bol? + s.should.eos? + end end From e9e5a4a4541eb2612fd8e5621edd15d964751d06 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 02:17:34 +0900 Subject: [PATCH 0803/2435] [ruby/strscan] Remove methods have been obsolete over two decades https://github.com/ruby/strscan/commit/1387def685 --- ext/strscan/strscan.c | 113 ------------------------------------------ 1 file changed, 113 deletions(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index d1b5c5e1d3a812..8842bc8e3e7cd3 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -96,7 +96,6 @@ static VALUE strscan_init_copy _((VALUE vself, VALUE vorig)); static VALUE strscan_s_mustc _((VALUE self)); static VALUE strscan_terminate _((VALUE self)); -static VALUE strscan_clear _((VALUE self)); static VALUE strscan_get_string _((VALUE self)); static VALUE strscan_set_string _((VALUE self, VALUE str)); static VALUE strscan_concat _((VALUE self, VALUE str)); @@ -118,15 +117,11 @@ static VALUE strscan_search_full _((VALUE self, VALUE re, static void adjust_registers_to_matched _((struct strscanner *p)); static VALUE strscan_getch _((VALUE self)); static VALUE strscan_get_byte _((VALUE self)); -static VALUE strscan_getbyte _((VALUE self)); static VALUE strscan_peek _((VALUE self, VALUE len)); -static VALUE strscan_peep _((VALUE self, VALUE len)); static VALUE strscan_scan_base10_integer _((VALUE self)); static VALUE strscan_unscan _((VALUE self)); static VALUE strscan_bol_p _((VALUE self)); static VALUE strscan_eos_p _((VALUE self)); -static VALUE strscan_empty_p _((VALUE self)); -static VALUE strscan_rest_p _((VALUE self)); static VALUE strscan_matched_p _((VALUE self)); static VALUE strscan_matched _((VALUE self)); static VALUE strscan_matched_size _((VALUE self)); @@ -384,21 +379,6 @@ strscan_terminate(VALUE self) return self; } -/* - * call-seq: - * clear -> self - * - * This method is obsolete; use the equivalent method StringScanner#terminate. - */ - - /* :nodoc: */ -static VALUE -strscan_clear(VALUE self) -{ - rb_warning("StringScanner#clear is obsolete; use #terminate instead"); - return strscan_terminate(self); -} - /* * :markup: markdown * :include: strscan/link_refs.txt @@ -1217,22 +1197,6 @@ strscan_get_byte(VALUE self) adjust_register_position(p, p->regs.end[0])); } -/* - * call-seq: - * getbyte - * - * Equivalent to #get_byte. - * This method is obsolete; use #get_byte instead. - */ - - /* :nodoc: */ -static VALUE -strscan_getbyte(VALUE self) -{ - rb_warning("StringScanner#getbyte is obsolete; use #get_byte instead"); - return strscan_get_byte(self); -} - /* * :markup: markdown * :include: strscan/link_refs.txt @@ -1268,22 +1232,6 @@ strscan_peek(VALUE self, VALUE vlen) return extract_beg_len(p, p->curr, len); } -/* - * call-seq: - * peep - * - * Equivalent to #peek. - * This method is obsolete; use #peek instead. - */ - - /* :nodoc: */ -static VALUE -strscan_peep(VALUE self, VALUE vlen) -{ - rb_warning("StringScanner#peep is obsolete; use #peek instead"); - return strscan_peek(self, vlen); -} - static VALUE strscan_parse_integer(struct strscanner *p, int base, long len) { @@ -1523,45 +1471,6 @@ strscan_eos_p(VALUE self) return EOS_P(p) ? Qtrue : Qfalse; } -/* - * call-seq: - * empty? - * - * Equivalent to #eos?. - * This method is obsolete, use #eos? instead. - */ - - /* :nodoc: */ -static VALUE -strscan_empty_p(VALUE self) -{ - rb_warning("StringScanner#empty? is obsolete; use #eos? instead"); - return strscan_eos_p(self); -} - -/* - * call-seq: - * rest? - * - * Returns true if and only if there is more data in the string. See #eos?. - * This method is obsolete; use #eos? instead. - * - * s = StringScanner.new('test string') - * # These two are opposites - * s.eos? # => false - * s.rest? # => true - */ - - /* :nodoc: */ -static VALUE -strscan_rest_p(VALUE self) -{ - struct strscanner *p; - - GET_SCANNER(self, p); - return EOS_P(p) ? Qfalse : Qtrue; -} - /* * :markup: markdown * :include: strscan/link_refs.txt @@ -2052,22 +1961,6 @@ strscan_rest_size(VALUE self) return INT2FIX(i); } -/* - * call-seq: - * restsize - * - * s.restsize is equivalent to s.rest_size. - * This method is obsolete; use #rest_size instead. - */ - - /* :nodoc: */ -static VALUE -strscan_restsize(VALUE self) -{ - rb_warning("StringScanner#restsize is obsolete; use #rest_size instead"); - return strscan_rest_size(self); -} - #define INSPECT_LENGTH 5 /* @@ -2308,7 +2201,6 @@ Init_strscan(void) rb_define_singleton_method(StringScanner, "must_C_version", strscan_s_mustc, 0); rb_define_method(StringScanner, "reset", strscan_reset, 0); rb_define_method(StringScanner, "terminate", strscan_terminate, 0); - rb_define_method(StringScanner, "clear", strscan_clear, 0); rb_define_method(StringScanner, "string", strscan_get_string, 0); rb_define_method(StringScanner, "string=", strscan_set_string, 1); rb_define_method(StringScanner, "concat", strscan_concat, 1); @@ -2333,11 +2225,9 @@ Init_strscan(void) rb_define_method(StringScanner, "getch", strscan_getch, 0); rb_define_method(StringScanner, "get_byte", strscan_get_byte, 0); - rb_define_method(StringScanner, "getbyte", strscan_getbyte, 0); rb_define_method(StringScanner, "scan_byte", strscan_scan_byte, 0); rb_define_method(StringScanner, "peek", strscan_peek, 1); rb_define_method(StringScanner, "peek_byte", strscan_peek_byte, 0); - rb_define_method(StringScanner, "peep", strscan_peep, 1); rb_define_private_method(StringScanner, "scan_base10_integer", strscan_scan_base10_integer, 0); rb_define_private_method(StringScanner, "scan_base16_integer", strscan_scan_base16_integer, 0); @@ -2347,8 +2237,6 @@ Init_strscan(void) rb_define_method(StringScanner, "beginning_of_line?", strscan_bol_p, 0); rb_alias(StringScanner, rb_intern("bol?"), rb_intern("beginning_of_line?")); rb_define_method(StringScanner, "eos?", strscan_eos_p, 0); - rb_define_method(StringScanner, "empty?", strscan_empty_p, 0); - rb_define_method(StringScanner, "rest?", strscan_rest_p, 0); rb_define_method(StringScanner, "matched?", strscan_matched_p, 0); rb_define_method(StringScanner, "matched", strscan_matched, 0); @@ -2362,7 +2250,6 @@ Init_strscan(void) rb_define_method(StringScanner, "rest", strscan_rest, 0); rb_define_method(StringScanner, "rest_size", strscan_rest_size, 0); - rb_define_method(StringScanner, "restsize", strscan_restsize, 0); rb_define_method(StringScanner, "inspect", strscan_inspect, 0); From f1f2dfebe8a3ed770e3263fb9379d1fb51f85feb Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 4 Nov 2025 14:46:01 -0500 Subject: [PATCH 0804/2435] Release VM lock before running finalizers (#15050) We shouldn't run any ruby code with the VM lock held. --- gc.c | 1 + gc/default/default.c | 22 ++++++++++++++-------- test/ruby/test_gc.rb | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/gc.c b/gc.c index a8320246d03d89..d1e542de2c6ecb 100644 --- a/gc.c +++ b/gc.c @@ -290,6 +290,7 @@ rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void saved.finished = 0; saved.final = Qundef; + ASSERT_vm_unlocking(); rb_ractor_ignore_belonging(true); EC_PUSH_TAG(ec); enum ruby_tag_type state = EC_EXEC_TAG(); diff --git a/gc/default/default.c b/gc/default/default.c index e0a5aade85f223..6045cec59887a0 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2763,24 +2763,27 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) RBASIC(obj)->flags |= FL_FINALIZE; - int lev = RB_GC_VM_LOCK(); + unsigned int lev = RB_GC_VM_LOCK(); if (st_lookup(finalizer_table, obj, &data)) { table = (VALUE)data; + VALUE dup_table = rb_ary_dup(table); + RB_GC_VM_UNLOCK(lev); /* 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)) { - RB_GC_VM_UNLOCK(lev); + VALUE recv = RARRAY_AREF(dup_table, i); + if (rb_equal(recv, block)) { // can't be called with VM lock held return recv; } } } + lev = RB_GC_VM_LOCK(); + RB_GC_GUARD(dup_table); rb_ary_push(table, block); } @@ -2841,8 +2844,8 @@ get_final(long i, void *data) return RARRAY_AREF(table, i + 1); } -static void -run_final(rb_objspace_t *objspace, VALUE zombie) +static unsigned int +run_final(rb_objspace_t *objspace, VALUE zombie, unsigned int lev) { if (RZOMBIE(zombie)->dfree) { RZOMBIE(zombie)->dfree(RZOMBIE(zombie)->data); @@ -2853,7 +2856,9 @@ run_final(rb_objspace_t *objspace, VALUE zombie) FL_UNSET(zombie, FL_FINALIZE); st_data_t table; if (st_delete(finalizer_table, &key, &table)) { + RB_GC_VM_UNLOCK(lev); rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table); + lev = RB_GC_VM_LOCK(); } else { rb_bug("FL_FINALIZE flag is set, but finalizers are not found"); @@ -2862,6 +2867,7 @@ run_final(rb_objspace_t *objspace, VALUE zombie) else { GC_ASSERT(!st_lookup(finalizer_table, key, NULL)); } + return lev; } static void @@ -2874,9 +2880,9 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) next_zombie = RZOMBIE(zombie)->next; page = GET_HEAP_PAGE(zombie); - int lev = RB_GC_VM_LOCK(); + unsigned int lev = RB_GC_VM_LOCK(); - run_final(objspace, zombie); + lev = run_final(objspace, zombie, lev); { GC_ASSERT(BUILTIN_TYPE(zombie) == T_ZOMBIE); GC_ASSERT(page->heap->final_slots_count > 0); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index a8a937f078a9da..7695fd33cf9945 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -914,4 +914,25 @@ def test_old_to_young_reference assert_include ObjectSpace.dump(young_obj), '"old":true' end end + + def test_finalizer_not_run_with_vm_lock + assert_ractor(<<~'RUBY') + Thread.new do + loop do + Encoding.list.each do |enc| + enc.names + end + end + end + + o = Object.new + ObjectSpace.define_finalizer(o, proc do + sleep 0.5 # finalizer shouldn't be run with VM lock, otherwise this context switch will crash + end) + o = nil + 4.times do + GC.start + end + RUBY + end end From 962aa14f240f43ca3bf3516432f7c3a6fbd1d3ff Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 4 Nov 2025 13:29:14 -0700 Subject: [PATCH 0805/2435] ZJIT: Add test to reproduce binarytrees crash (#15054) --- test/ruby/test_zjit.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index de2d1e61528e86..ae1af5c2c038b6 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -3164,6 +3164,37 @@ def test(define) }, call_threshold: 2 end + def test_regression_cfp_sp_set_correctly_before_leaf_gc_call + omit 'reproduction for known, unresolved ZJIT bug' + + assert_compiles ':ok', %q{ + def check(l, r) + return 1 unless l + 1 + check(*l) + check(*r) + end + + def tree(depth) + # This duparray is our leaf-gc target. + return [nil, nil] unless depth > 0 + + # Modify the local and pass it to the following calls. + depth -= 1 + [tree(depth), tree(depth)] + end + + def test + GC.stress = true + 2.times do + t = tree(11) + check(*t) + end + :ok + end + + test + }, call_threshold: 14, num_profiles: 5 + end + private # Assert that every method call in `test_script` can be compiled by ZJIT From fffa4671a4cfaea6e6eb2bc6a5dde14ad1a5a400 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 13:33:32 -0800 Subject: [PATCH 0806/2435] [ruby/strscan] Resurrect a method that has not been obsolete (https://github.com/ruby/strscan/pull/169) Partially revert https://github.com/ruby/strscan/pull/168 because strscan_rest_p did not have `rb_warning("StringScanner#rest? is obsolete")`. It is actively used by the latest tzinfo.gem, and we shouldn't remove it without deprecating it. https://github.com/ruby/strscan/commit/f3fdf21189 --- ext/strscan/strscan.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 8842bc8e3e7cd3..e2b827c63c9a3f 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -122,6 +122,7 @@ static VALUE strscan_scan_base10_integer _((VALUE self)); static VALUE strscan_unscan _((VALUE self)); static VALUE strscan_bol_p _((VALUE self)); static VALUE strscan_eos_p _((VALUE self)); +static VALUE strscan_rest_p _((VALUE self)); static VALUE strscan_matched_p _((VALUE self)); static VALUE strscan_matched _((VALUE self)); static VALUE strscan_matched_size _((VALUE self)); @@ -1471,6 +1472,29 @@ strscan_eos_p(VALUE self) return EOS_P(p) ? Qtrue : Qfalse; } +/* + * call-seq: + * rest? + * + * Returns true if and only if there is more data in the string. See #eos?. + * This method is obsolete; use #eos? instead. + * + * s = StringScanner.new('test string') + * # These two are opposites + * s.eos? # => false + * s.rest? # => true + */ + + /* :nodoc: */ +static VALUE +strscan_rest_p(VALUE self) +{ + struct strscanner *p; + + GET_SCANNER(self, p); + return EOS_P(p) ? Qfalse : Qtrue; +} + /* * :markup: markdown * :include: strscan/link_refs.txt @@ -2237,6 +2261,7 @@ Init_strscan(void) rb_define_method(StringScanner, "beginning_of_line?", strscan_bol_p, 0); rb_alias(StringScanner, rb_intern("bol?"), rb_intern("beginning_of_line?")); rb_define_method(StringScanner, "eos?", strscan_eos_p, 0); + rb_define_method(StringScanner, "rest?", strscan_rest_p, 0); rb_define_method(StringScanner, "matched?", strscan_matched_p, 0); rb_define_method(StringScanner, "matched", strscan_matched, 0); From 7a0d730ee320e8b7a46d8fd4719a1ec709fd958c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 13:39:51 -0800 Subject: [PATCH 0807/2435] Resurrect tests for StringScanner#rest? that has not been obsolete. Partially reverting https://github.com/ruby/ruby/pull/15049. --- spec/ruby/library/stringscanner/rest_spec.rb | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/ruby/library/stringscanner/rest_spec.rb b/spec/ruby/library/stringscanner/rest_spec.rb index 25dcaf30ce4165..67072f880de2ce 100644 --- a/spec/ruby/library/stringscanner/rest_spec.rb +++ b/spec/ruby/library/stringscanner/rest_spec.rb @@ -25,3 +25,24 @@ it_behaves_like :extract_range_matched, :rest end + +describe "StringScanner#rest?" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns true if there is more data in the string" do + @s.rest?.should be_true + @s.scan(/This/) + @s.rest?.should be_true + end + + it "returns false if there is no more data in the string" do + @s.terminate + @s.rest?.should be_false + end + + it "is the opposite of eos?" do + @s.rest?.should_not == @s.eos? + end +end From a0376eb2ccc8a893905d270c5363b73ccfcacd2d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 13:56:35 -0800 Subject: [PATCH 0808/2435] ZJIT: Fix --zjit-mem-size and add --zjit-exec-mem-size (#15041) ZJIT: Fix --zjit-mem-size and resurrect --zjit-exec-mem-size --- zjit/src/options.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/zjit/src/options.rs b/zjit/src/options.rs index f6471b5461497f..cd3a6439719b3f 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -117,7 +117,7 @@ impl Default for Options { /// description in a separate line if the option name is too long. 80-char limit --> | (any character beyond this `|` column fails the test) pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-mem-size=num", - "Max amount of memory that ZJIT can use (in MiB)."), + "Max amount of memory that ZJIT can use in MiB (default: 128)."), ("--zjit-call-threshold=num", "Number of calls to trigger JIT (default: 30)."), ("--zjit-num-profiles=num", @@ -175,6 +175,10 @@ const DUMP_LIR_ALL: &[DumpLIR] = &[ DumpLIR::scratch_split, ]; +/// Mamximum value for --zjit-mem-size/--zjit-exec-mem-size in MiB. +/// We set 1TiB just to avoid overflow. We could make it smaller. +const MAX_MEM_MIB: usize = 1024 * 1024; + /// Macro to dump LIR if --zjit-dump-lir is specified macro_rules! asm_dump { ($asm:expr, $target:ident) => { @@ -257,17 +261,19 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { ("", "") => {}, // Simply --zjit ("mem-size", _) => match opt_val.parse::() { - Ok(n) => { - // Reject 0 or too large values that could overflow. - // The upper bound is 1 TiB but we could make it smaller. - if n == 0 || n > 1024 * 1024 { - return None - } + Ok(n) if (1..=MAX_MEM_MIB).contains(&n) => { + // Convert from MiB to bytes internally for convenience + options.mem_bytes = n * 1024 * 1024; + } + _ => return None, + }, + ("exec-mem-size", _) => match opt_val.parse::() { + Ok(n) if (1..=MAX_MEM_MIB).contains(&n) => { // Convert from MiB to bytes internally for convenience options.exec_mem_bytes = n * 1024 * 1024; } - Err(_) => return None, + _ => return None, }, ("call-threshold", _) => match opt_val.parse() { From 554a78daabbfeb8d8a128d4600f1cc02287cdcd1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 17:57:25 -0600 Subject: [PATCH 0809/2435] [ruby/stringio] [DOC] Doc for StringIO.getc (https://github.com/ruby/stringio/pull/163) https://github.com/ruby/stringio/commit/a126fe252f --- doc/stringio/getc.rdoc | 34 ++++++++++++++++++++++++++++++++++ ext/stringio/stringio.c | 6 +++--- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 doc/stringio/getc.rdoc diff --git a/doc/stringio/getc.rdoc b/doc/stringio/getc.rdoc new file mode 100644 index 00000000000000..c021789c911b8d --- /dev/null +++ b/doc/stringio/getc.rdoc @@ -0,0 +1,34 @@ +Reads and returns the next character (or byte; see below) from the stream: + + strio = StringIO.new('foo') + strio.getc # => "f" + strio.getc # => "o" + strio.getc # => "o" + +Returns +nil+ if at end-of-stream: + + strio.eof? # => true + strio.getc # => nil + +Returns characters, not bytes: + + strio = StringIO.new('тест') + strio.getc # => "т" + strio.getc # => "е" + + strio = StringIO.new('こんにちは') + strio.getc # => "こ" + strio.getc # => "ん" + +In each of the examples above, the stream is positioned at the beginning of a character; +in other cases that need not be true: + + strio = StringIO.new('こんにちは') # Five 3-byte characters. + strio.pos = 3 # => 3 # At beginning of second character; returns character. + strio.getc # => "ん" + strio.pos = 4 # => 4 # At second byte of second character; returns byte. + strio.getc # => "\x82" + strio.pos = 5 # => 5 # At third byte of second character; returns byte. + strio.getc # => "\x93" + +Related: StringIO.getbyte. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d66768a2c50279..1ceda9dcf01093 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -964,10 +964,10 @@ strio_each_byte(VALUE self) /* * call-seq: - * getc -> character or nil + * getc -> character, byte, or nil + * + * :include: stringio/getc.rdoc * - * Reads and returns the next character from the stream; - * see {Character IO}[rdoc-ref:IO@Character+IO]. */ static VALUE strio_getc(VALUE self) From 9c0f2729c07d553f613d6a65144bb4ce0376e948 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:02:38 -0600 Subject: [PATCH 0810/2435] [ruby/stringio] [DOC] Tweaks for StringIO#internal_encoding (https://github.com/ruby/stringio/pull/166) https://github.com/ruby/stringio/commit/5eeb61df34 --- ext/stringio/stringio.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 1ceda9dcf01093..f61815c657d3e4 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -2071,10 +2071,9 @@ strio_external_encoding(VALUE self) /* * call-seq: - * strio.internal_encoding => encoding + * internal_encoding -> nil * - * Returns the Encoding of the internal string if conversion is - * specified. Otherwise returns +nil+. + * Returns +nil+; for compatibility with IO. */ static VALUE From e22d9abad3da623e376a067f98ea62a94ff00887 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:04:54 -0600 Subject: [PATCH 0811/2435] [ruby/stringio] [DOC] Tweaks for StringIO#fileno (https://github.com/ruby/stringio/pull/168) https://github.com/ruby/stringio/commit/9f10c7ae86 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index f61815c657d3e4..f6a639046f12e3 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -434,7 +434,7 @@ strio_false(VALUE self) } /* - * Returns +nil+. Just for compatibility to IO. + * Returns +nil+; for compatibility with IO. */ static VALUE strio_nil(VALUE self) From d5acffba82a2a79a83e0e5dbfa2036f06d497245 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:05:06 -0600 Subject: [PATCH 0812/2435] [ruby/stringio] [DOC] Tweaks for StringIO#fsync (https://github.com/ruby/stringio/pull/170) https://github.com/ruby/stringio/commit/da338d7e5d --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index f6a639046f12e3..ffe2ef205f19dd 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -454,7 +454,7 @@ strio_self(VALUE self) } /* - * Returns 0. Just for compatibility to IO. + * Returns 0; for compatibility with IO. */ static VALUE strio_0(VALUE self) From 00b5b3c5637ab26c806400cc7c679d7f9bbd1a2b Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:08:17 -0600 Subject: [PATCH 0813/2435] [ruby/stringio] [DOC] Tweaks for StringIO#isatty (https://github.com/ruby/stringio/pull/167) https://github.com/ruby/stringio/commit/94303ace95 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index ffe2ef205f19dd..6ad526c8e2545a 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -424,7 +424,7 @@ strio_s_new(int argc, VALUE *argv, VALUE klass) } /* - * Returns +false+. Just for compatibility to IO. + * Returns +false+; for compatibility with IO. */ static VALUE strio_false(VALUE self) From be905b2e581540dc2c51a54aed537b19955b7bb0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:08:36 -0600 Subject: [PATCH 0814/2435] [ruby/stringio] [DOC] Tweaks for StringIO#flush (https://github.com/ruby/stringio/pull/169) https://github.com/ruby/stringio/commit/bef6541b55 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 6ad526c8e2545a..c5e64a5c0f6808 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -444,7 +444,7 @@ strio_nil(VALUE self) } /* - * Returns an object itself. Just for compatibility to IO. + * Returns +self+; for compatibility with IO. */ static VALUE strio_self(VALUE self) From bd3b44cb0a341878abe0edf65d01b1a48c93f088 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 16:09:13 -0800 Subject: [PATCH 0815/2435] ZJIT: Use a shared trampoline across all ISEQs (#15042) --- vm.c | 69 ++++++++++++++++++++++------------ vm_core.h | 1 + vm_exec.h | 19 ++++++++-- zjit.h | 6 ++- zjit/src/backend/arm64/mod.rs | 30 +++++++++------ zjit/src/backend/lir.rs | 15 +++++++- zjit/src/backend/x86_64/mod.rs | 10 ++++- zjit/src/codegen.rs | 27 ++++++------- zjit/src/cruby.rs | 6 +-- zjit/src/state.rs | 21 +++++++---- 10 files changed, 133 insertions(+), 71 deletions(-) diff --git a/vm.c b/vm.c index 32785dbcc8cbca..f0aebf08a38694 100644 --- a/vm.c +++ b/vm.c @@ -503,7 +503,7 @@ rb_yjit_threshold_hit(const rb_iseq_t *iseq, uint64_t entry_calls) #define rb_yjit_threshold_hit(iseq, entry_calls) false #endif -#if USE_YJIT || USE_ZJIT +#if USE_YJIT // Generate JIT code that supports the following kinds of ISEQ entries: // * The first ISEQ on vm_exec (e.g.
, or Ruby methods/blocks // called by a C method). The current frame has VM_FRAME_FLAG_FINISH. @@ -513,13 +513,32 @@ rb_yjit_threshold_hit(const rb_iseq_t *iseq, uint64_t entry_calls) // The current frame doesn't have VM_FRAME_FLAG_FINISH. The current // vm_exec does NOT stop whether JIT code returns Qundef or not. static inline rb_jit_func_t -jit_compile(rb_execution_context_t *ec) +yjit_compile(rb_execution_context_t *ec) { const rb_iseq_t *iseq = ec->cfp->iseq; struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); + // Increment the ISEQ's call counter and trigger JIT compilation if not compiled + if (body->jit_entry == NULL) { + body->jit_entry_calls++; + if (rb_yjit_threshold_hit(iseq, body->jit_entry_calls)) { + rb_yjit_compile_iseq(iseq, ec, false); + } + } + return body->jit_entry; +} +#else +# define yjit_compile(ec) ((rb_jit_func_t)0) +#endif + #if USE_ZJIT - if (body->jit_entry == NULL && rb_zjit_enabled_p) { +static inline rb_jit_func_t +zjit_compile(rb_execution_context_t *ec) +{ + const rb_iseq_t *iseq = ec->cfp->iseq; + struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); + + if (body->jit_entry == NULL) { body->jit_entry_calls++; // At profile-threshold, rewrite some of the YARV instructions @@ -533,38 +552,38 @@ jit_compile(rb_execution_context_t *ec) rb_zjit_compile_iseq(iseq, false); } } -#endif - -#if USE_YJIT - // Increment the ISEQ's call counter and trigger JIT compilation if not compiled - if (body->jit_entry == NULL && rb_yjit_enabled_p) { - body->jit_entry_calls++; - if (rb_yjit_threshold_hit(iseq, body->jit_entry_calls)) { - rb_yjit_compile_iseq(iseq, ec, false); - } - } -#endif return body->jit_entry; } +#else +# define zjit_compile(ec) ((rb_jit_func_t)0) +#endif -// Execute JIT code compiled by jit_compile() +// Execute JIT code compiled by yjit_compile() or zjit_compile() static inline VALUE jit_exec(rb_execution_context_t *ec) { - rb_jit_func_t func = jit_compile(ec); - if (func) { - // Call the JIT code - return func(ec, ec->cfp); - } - else { +#if USE_YJIT + if (rb_yjit_enabled_p) { + rb_jit_func_t func = yjit_compile(ec); + if (func) { + return func(ec, ec->cfp); + } return Qundef; } -} -#else -# define jit_compile(ec) ((rb_jit_func_t)0) -# define jit_exec(ec) Qundef #endif +#if USE_ZJIT + void *zjit_entry = rb_zjit_entry; + if (zjit_entry) { + rb_jit_func_t func = zjit_compile(ec); + if (func) { + return ((rb_zjit_func_t)zjit_entry)(ec, ec->cfp, func); + } + } +#endif + return Qundef; +} + #if USE_YJIT // Generate JIT code that supports the following kind of ISEQ entry: // * The first ISEQ pushed by vm_exec_handle_exception. The frame would diff --git a/vm_core.h b/vm_core.h index e8e6a6a3a6b3f9..ded0280387b834 100644 --- a/vm_core.h +++ b/vm_core.h @@ -398,6 +398,7 @@ enum rb_builtin_attr { }; typedef VALUE (*rb_jit_func_t)(struct rb_execution_context_struct *, struct rb_control_frame_struct *); +typedef VALUE (*rb_zjit_func_t)(struct rb_execution_context_struct *, struct rb_control_frame_struct *, rb_jit_func_t); struct rb_iseq_constant_body { enum rb_iseq_type type; diff --git a/vm_exec.h b/vm_exec.h index c3b7d4e48882c3..033a48f1e7683c 100644 --- a/vm_exec.h +++ b/vm_exec.h @@ -175,11 +175,22 @@ default: \ // Run the JIT from the interpreter #define JIT_EXEC(ec, val) do { \ - rb_jit_func_t func; \ /* don't run tailcalls since that breaks FINISH */ \ - if (UNDEF_P(val) && GET_CFP() != ec->cfp && (func = jit_compile(ec))) { \ - val = func(ec, ec->cfp); \ - if (ec->tag->state) THROW_EXCEPTION(val); \ + if (UNDEF_P(val) && GET_CFP() != ec->cfp) { \ + rb_zjit_func_t zjit_entry; \ + if (rb_yjit_enabled_p) { \ + rb_jit_func_t func = yjit_compile(ec); \ + if (func) { \ + val = func(ec, ec->cfp); \ + if (ec->tag->state) THROW_EXCEPTION(val); \ + } \ + } \ + else if ((zjit_entry = rb_zjit_entry)) { \ + rb_jit_func_t func = zjit_compile(ec); \ + if (func) { \ + val = zjit_entry(ec, ec->cfp, func); \ + } \ + } \ } \ } while (0) diff --git a/zjit.h b/zjit.h index 7b3e410c91c4b0..47240846ff1db0 100644 --- a/zjit.h +++ b/zjit.h @@ -10,7 +10,7 @@ #endif #if USE_ZJIT -extern bool rb_zjit_enabled_p; +extern void *rb_zjit_entry; extern uint64_t rb_zjit_call_threshold; extern uint64_t rb_zjit_profile_threshold; void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception); @@ -29,7 +29,7 @@ void rb_zjit_before_ractor_spawn(void); void rb_zjit_tracing_invalidate_all(void); void rb_zjit_invalidate_no_singleton_class(VALUE klass); #else -#define rb_zjit_enabled_p false +#define rb_zjit_entry 0 static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception) {} static inline void rb_zjit_profile_insn(uint32_t insn, rb_execution_context_t *ec) {} static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {} @@ -42,4 +42,6 @@ static inline void rb_zjit_tracing_invalidate_all(void) {} static inline void rb_zjit_invalidate_no_singleton_class(VALUE klass) {} #endif // #if USE_ZJIT +#define rb_zjit_enabled_p (rb_zjit_entry != 0) + #endif // #ifndef ZJIT_H diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index acf0576f9c80be..532570d732341e 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1428,17 +1428,25 @@ impl Assembler { } }, Insn::CCall { fptr, .. } => { - // The offset to the call target in bytes - let src_addr = cb.get_write_ptr().raw_ptr(cb) as i64; - let dst_addr = *fptr as i64; - - // Use BL if the offset is short enough to encode as an immediate. - // Otherwise, use BLR with a register. - if b_offset_fits_bits((dst_addr - src_addr) / 4) { - bl(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); - } else { - emit_load_value(cb, Self::EMIT_OPND, dst_addr as u64); - blr(cb, Self::EMIT_OPND); + match fptr { + Opnd::UImm(fptr) => { + // The offset to the call target in bytes + let src_addr = cb.get_write_ptr().raw_ptr(cb) as i64; + let dst_addr = *fptr as i64; + + // Use BL if the offset is short enough to encode as an immediate. + // Otherwise, use BLR with a register. + if b_offset_fits_bits((dst_addr - src_addr) / 4) { + bl(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); + } else { + emit_load_value(cb, Self::EMIT_OPND, dst_addr as u64); + blr(cb, Self::EMIT_OPND); + } + } + Opnd::Reg(_) => { + blr(cb, fptr.into()); + } + _ => unreachable!("unsupported ccall fptr: {fptr:?}") } }, Insn::CRet { .. } => { diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 66e89a1304d715..e2f75e01c8fcba 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -386,7 +386,9 @@ pub enum Insn { // C function call with N arguments (variadic) CCall { opnds: Vec, - fptr: *const u8, + /// The function pointer to be called. This should be Opnd::const_ptr + /// (Opnd::UImm) in most cases. gen_entry_trampoline() uses Opnd::Reg. + fptr: Opnd, /// Optional PosMarker to remember the start address of the C call. /// It's embedded here to insert the PosMarker after push instructions /// that are split from this CCall on alloc_regs(). @@ -1989,11 +1991,20 @@ impl Assembler { pub fn ccall(&mut self, fptr: *const u8, opnds: Vec) -> Opnd { let canary_opnd = self.set_stack_canary(); let out = self.new_vreg(Opnd::match_num_bits(&opnds)); + let fptr = Opnd::const_ptr(fptr); self.push_insn(Insn::CCall { fptr, opnds, start_marker: None, end_marker: None, out }); self.clear_stack_canary(canary_opnd); out } + /// Call a C function stored in a register + pub fn ccall_reg(&mut self, fptr: Opnd, num_bits: u8) -> Opnd { + assert!(matches!(fptr, Opnd::Reg(_)), "ccall_reg must be called with Opnd::Reg: {fptr:?}"); + let out = self.new_vreg(num_bits); + self.push_insn(Insn::CCall { fptr, opnds: vec![], start_marker: None, end_marker: None, out }); + out + } + /// Call a C function with PosMarkers. This is used for recording the start and end /// addresses of the C call and rewriting it with a different function address later. pub fn ccall_with_pos_markers( @@ -2005,7 +2016,7 @@ impl Assembler { ) -> Opnd { let out = self.new_vreg(Opnd::match_num_bits(&opnds)); self.push_insn(Insn::CCall { - fptr, + fptr: Opnd::const_ptr(fptr), opnds, start_marker: Some(Rc::new(start_marker)), end_marker: Some(Rc::new(end_marker)), diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 1d5d90a856c92e..aea25ca2a46d35 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -863,7 +863,15 @@ impl Assembler { // C function call Insn::CCall { fptr, .. } => { - call_ptr(cb, RAX, *fptr); + match fptr { + Opnd::UImm(fptr) => { + call_ptr(cb, RAX, *fptr as *const u8); + } + Opnd::Reg(_) => { + call(cb, fptr.into()); + } + _ => unreachable!("unsupported ccall fptr: {fptr:?}") + } }, Insn::CRet(opnd) => { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7cd677bde3d5b3..01212ac88cdbfa 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -106,8 +106,7 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, jit_exception: boo } // Always mark the code region executable if asm.compile() has been used. - // We need to do this even if code_ptr is None because, whether gen_entry() - // fails or not, gen_iseq() may have already used asm.compile(). + // We need to do this even if code_ptr is None because gen_iseq() may have already used asm.compile(). cb.mark_all_executable(); code_ptr.map_or(std::ptr::null(), |ptr| ptr.raw_ptr(cb)) @@ -131,10 +130,7 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool) debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq, 0)); })?; - // Compile an entry point to the JIT code - gen_entry(cb, iseq, start_ptr).inspect_err(|err| { - debug!("{err:?}: gen_entry failed: {}", iseq_get_location(iseq, 0)); - }) + Ok(start_ptr) } /// Stub a branch for a JIT-to-JIT call @@ -170,14 +166,16 @@ fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) { }; } -/// Compile a JIT entry -fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result { +/// Compile a shared JIT entry trampoline +pub fn gen_entry_trampoline(cb: &mut CodeBlock) -> Result { // Set up registers for CFP, EC, SP, and basic block arguments let mut asm = Assembler::new(); - gen_entry_prologue(&mut asm, iseq); + gen_entry_prologue(&mut asm); - // Jump to the first block using a call instruction - asm.ccall(function_ptr.raw_ptr(cb), vec![]); + // Jump to the first block using a call instruction. This trampoline is used + // as rb_zjit_func_t in jit_exec(), which takes (EC, CFP, rb_jit_func_t). + // So C_ARG_OPNDS[2] is rb_jit_func_t, which is (EC, CFP) -> VALUE. + asm.ccall_reg(C_ARG_OPNDS[2], VALUE_BITS); // Restore registers for CFP, EC, and SP after use asm_comment!(asm, "return to the interpreter"); @@ -190,8 +188,7 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result let start_ptr = code_ptr.raw_addr(cb); let end_ptr = cb.get_write_ptr().raw_addr(cb); let code_size = end_ptr - start_ptr; - let iseq_name = iseq_get_location(iseq, 0); - register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size); + register_with_perf("ZJIT entry trampoline".into(), start_ptr, code_size); } Ok(code_ptr) } @@ -990,8 +987,8 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd } /// Compile an interpreter entry block to be inserted into an ISEQ -fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { - asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); +fn gen_entry_prologue(asm: &mut Assembler) { + asm_comment!(asm, "ZJIT entry trampoline"); // Save the registers we'll use for CFP, EP, SP asm.frame_setup(lir::JIT_PRESERVED_REGS); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 631acbd8635686..db47385bc88321 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1071,7 +1071,7 @@ pub use manual_defs::*; pub mod test_utils { use std::{ptr::null, sync::Once}; - use crate::{options::{rb_zjit_call_threshold, rb_zjit_prepare_options, set_call_threshold, DEFAULT_CALL_THRESHOLD}, state::{rb_zjit_enabled_p, ZJITState}}; + use crate::{options::{rb_zjit_call_threshold, rb_zjit_prepare_options, set_call_threshold, DEFAULT_CALL_THRESHOLD}, state::{rb_zjit_entry, ZJITState}}; use super::*; @@ -1114,10 +1114,10 @@ pub mod test_utils { } // Set up globals for convenience - ZJITState::init(); + let zjit_entry = ZJITState::init(); // Enable zjit_* instructions - unsafe { rb_zjit_enabled_p = true; } + unsafe { rb_zjit_entry = zjit_entry; } } /// Make sure the Ruby VM is set up and run a given callback with rb_protect() diff --git a/zjit/src/state.rs b/zjit/src/state.rs index c0e9e0b77ca909..3cb60cffcb6c52 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -1,6 +1,6 @@ //! Runtime state of ZJIT. -use crate::codegen::{gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline}; +use crate::codegen::{gen_entry_trampoline, gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline}; use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, EcPtr, Qnil, rb_vm_insn_addr2opcode, rb_profile_frames, VALUE, VM_INSTRUCTION_SIZE, size_t, rb_gc_mark}; use crate::cruby_methods; use crate::invariants::Invariants; @@ -9,14 +9,16 @@ use crate::options::get_option; use crate::stats::{Counters, InsnCounters, SideExitLocations}; use crate::virtualmem::CodePtr; use std::collections::HashMap; +use std::ptr::null; +/// Shared trampoline to enter ZJIT. Not null when ZJIT is enabled. #[allow(non_upper_case_globals)] #[unsafe(no_mangle)] -pub static mut rb_zjit_enabled_p: bool = false; +pub static mut rb_zjit_entry: *const u8 = null(); /// Like rb_zjit_enabled_p, but for Rust code. pub fn zjit_enabled_p() -> bool { - unsafe { rb_zjit_enabled_p } + unsafe { rb_zjit_entry != null() } } /// Global state needed for code generation @@ -65,8 +67,8 @@ pub struct ZJITState { static mut ZJIT_STATE: Option = None; impl ZJITState { - /// Initialize the ZJIT globals - pub fn init() { + /// Initialize the ZJIT globals. Return the address of the JIT entry trampoline. + pub fn init() -> *const u8 { let mut cb = { use crate::options::*; use crate::virtualmem::*; @@ -79,6 +81,7 @@ impl ZJITState { CodeBlock::new(mem_block.clone(), get_option!(dump_disasm)) }; + let entry_trampoline = gen_entry_trampoline(&mut cb).unwrap().raw_ptr(&cb); let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap(); let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap(); @@ -114,6 +117,8 @@ impl ZJITState { let code_ptr = gen_exit_trampoline_with_counter(cb, exit_trampoline).unwrap(); ZJITState::get_instance().exit_trampoline_with_counter = code_ptr; } + + entry_trampoline } /// Return true if zjit_state has been initialized @@ -252,7 +257,7 @@ pub extern "C" fn rb_zjit_init() { let result = std::panic::catch_unwind(|| { // Initialize ZJIT states cruby::ids::init(); - ZJITState::init(); + let zjit_entry = ZJITState::init(); // Install a panic hook for ZJIT rb_bug_panic_hook(); @@ -261,8 +266,8 @@ pub extern "C" fn rb_zjit_init() { unsafe { rb_vm_insn_count = 0; } // ZJIT enabled and initialized successfully - assert!(unsafe{ !rb_zjit_enabled_p }); - unsafe { rb_zjit_enabled_p = true; } + assert!(unsafe{ rb_zjit_entry == null() }); + unsafe { rb_zjit_entry = zjit_entry; } }); if result.is_err() { From d24bb1e76155374c82d03e3287f41247a2f04dce Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 4 Nov 2025 18:09:10 -0600 Subject: [PATCH 0816/2435] [ruby/stringio] [DOC] Tweaks for StringIO#string= (https://github.com/ruby/stringio/pull/172) https://github.com/ruby/stringio/commit/17ae4daf9a --- ext/stringio/stringio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index c5e64a5c0f6808..39e4be58538f9f 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -514,7 +514,7 @@ strio_get_string(VALUE self) * call-seq: * string = other_string -> other_string * - * Assigns the underlying string as +other_string+, and sets position to zero; + * Replaces the stored string with +other_string+, and sets the position to zero; * returns +other_string+: * * StringIO.open('foo') do |strio| @@ -528,7 +528,7 @@ strio_get_string(VALUE self) * "foo" * "bar" * - * Related: StringIO#string (returns the underlying string). + * Related: StringIO#string (returns the stored string). */ static VALUE strio_set_string(VALUE self, VALUE string) From 13f1b432d27e4823b8b2f60588eeefe7656ccdc0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 10:54:45 +0900 Subject: [PATCH 0817/2435] [ruby/strscan] [DOC] Remove the statement `rest?` is obsolete `eos?` is opposite, cannot be used instead of `rest?`. https://github.com/ruby/strscan/commit/bee8cc547b --- ext/strscan/strscan.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index e2b827c63c9a3f..164d4c65d061a5 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -1477,7 +1477,6 @@ strscan_eos_p(VALUE self) * rest? * * Returns true if and only if there is more data in the string. See #eos?. - * This method is obsolete; use #eos? instead. * * s = StringScanner.new('test string') * # These two are opposites From 033ba3c8812cc4027a44e8339803e4256e9a7e33 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 4 Nov 2025 21:36:55 -0500 Subject: [PATCH 0818/2435] Don't run global variable hook functions with VM lock held (#15053) We can't run arbitrary ruby code with the VM lock held. --- variable.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/variable.c b/variable.c index bab423e95029f7..f6cfef7b0725e6 100644 --- a/variable.c +++ b/variable.c @@ -1010,18 +1010,21 @@ rb_gvar_set(ID id, VALUE val) VALUE retval; struct rb_global_entry *entry; const rb_namespace_t *ns = rb_current_namespace(); + bool use_namespace_tbl = false; RB_VM_LOCKING() { entry = rb_global_entry(id); if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { + use_namespace_tbl = true; rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); retval = val; // TODO: think about trace } - else { - retval = rb_gvar_set_entry(entry, val); - } + } + + if (!use_namespace_tbl) { + retval = rb_gvar_set_entry(entry, val); } return retval; } @@ -1037,28 +1040,36 @@ rb_gvar_get(ID id) { VALUE retval, gvars, key; const rb_namespace_t *ns = rb_current_namespace(); + bool use_namespace_tbl = false; + struct rb_global_entry *entry; + struct rb_global_variable *var; // TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist) RB_VM_LOCKING() { - struct rb_global_entry *entry = rb_global_entry(id); - struct rb_global_variable *var = entry->var; + entry = rb_global_entry(id); + var = entry->var; if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { + use_namespace_tbl = true; gvars = ns->gvar_tbl; key = rb_id2sym(entry->id); if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached retval = rb_hash_aref(gvars, key); } else { - retval = (*var->getter)(entry->id, var->data); - if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) { - retval = rb_funcall(retval, rb_intern("clone"), 0); + RB_VM_UNLOCK(); + { + retval = (*var->getter)(entry->id, var->data); + if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) { + retval = rb_funcall(retval, rb_intern("clone"), 0); + } } + RB_VM_LOCK(); rb_hash_aset(gvars, key, retval); } } - else { - retval = (*var->getter)(entry->id, var->data); - } + } + if (!use_namespace_tbl) { + retval = (*var->getter)(entry->id, var->data); } return retval; } @@ -1159,6 +1170,7 @@ rb_alias_variable(ID name1, ID name2) else if ((entry1 = (struct rb_global_entry *)data1)->var != entry2->var) { struct rb_global_variable *var = entry1->var; if (var->block_trace) { + RB_VM_UNLOCK(); rb_raise(rb_eRuntimeError, "can't alias in tracer"); } var->counter--; From 9cfe949d4e04cd06404a5bdb2005990d3860307b Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 4 Nov 2025 21:57:44 -0500 Subject: [PATCH 0819/2435] ZJIT: Fallback counter rename: s/fancy/complex/ Kokubun bought up that "complex" is a more fitting name for what these counters count. Thanks! Also: - make the SendFallbackReason enum name consistent with the counter name - rewrite the printout prompt in zjit.rb --- zjit.rb | 4 ++-- zjit/src/hir.rs | 40 +++++++++++++++++++-------------------- zjit/src/hir/opt_tests.rs | 22 ++++++++++----------- zjit/src/stats.rs | 32 +++++++++++++++---------------- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/zjit.rb b/zjit.rb index 72a6e586513b56..cfe8bcd6e2cfb1 100644 --- a/zjit.rb +++ b/zjit.rb @@ -165,9 +165,9 @@ def stats_string print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) # Show most popular unsupported call features. Because each call can - # use multiple fancy features, a decrease in this number does not + # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. - print_counters_with_prefix(prefix: 'fancy_arg_pass_', prompt: 'popular unsupported argument-parameter features', buf:, stats:, limit: 10) + print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 136f7b452fcd38..09e293a0f6e05e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -579,7 +579,7 @@ pub enum SendFallbackReason { BmethodNonIseqProc, /// The call has at least one feature on the caller or callee side that the optimizer does not /// support. - FancyFeatureUse, + ComplexArgPass, /// Initial fallback reason for every instruction, which should be mutated to /// a more actionable reason when an attempt to specialize the instruction fails. NotOptimizedInstruction(ruby_vminsn_type), @@ -1417,12 +1417,12 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq }; use Counter::*; - if unsafe { rb_get_iseq_flags_has_rest(iseq) } { count_failure(fancy_arg_pass_param_rest) } - if unsafe { rb_get_iseq_flags_has_opt(iseq) } { count_failure(fancy_arg_pass_param_opt) } - if unsafe { rb_get_iseq_flags_has_kw(iseq) } { count_failure(fancy_arg_pass_param_kw) } - if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { count_failure(fancy_arg_pass_param_kwrest) } - if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(fancy_arg_pass_param_block) } - if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(fancy_arg_pass_param_forwardable) } + if unsafe { rb_get_iseq_flags_has_rest(iseq) } { count_failure(complex_arg_pass_param_rest) } + if unsafe { rb_get_iseq_flags_has_opt(iseq) } { count_failure(complex_arg_pass_param_opt) } + if unsafe { rb_get_iseq_flags_has_kw(iseq) } { count_failure(complex_arg_pass_param_kw) } + if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { count_failure(complex_arg_pass_param_kwrest) } + if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(complex_arg_pass_param_block) } + if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(complex_arg_pass_param_forwardable) } can_send } @@ -2153,16 +2153,16 @@ impl Function { self.push_insn(block, Insn::GuardType { val, guard_type, state }) } - fn count_fancy_call_features(&mut self, block: BlockId, ci_flags: c_uint) { + fn count_complex_call_features(&mut self, block: BlockId, ci_flags: c_uint) { use Counter::*; - if 0 != ci_flags & VM_CALL_ARGS_SPLAT { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_splat)); } - if 0 != ci_flags & VM_CALL_ARGS_BLOCKARG { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_blockarg)); } - if 0 != ci_flags & VM_CALL_KWARG { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_kwarg)); } - if 0 != ci_flags & VM_CALL_KW_SPLAT { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_kw_splat)); } - if 0 != ci_flags & VM_CALL_TAILCALL { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_tailcall)); } - if 0 != ci_flags & VM_CALL_SUPER { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_super)); } - if 0 != ci_flags & VM_CALL_ZSUPER { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_zsuper)); } - if 0 != ci_flags & VM_CALL_FORWARDING { self.push_insn(block, Insn::IncrCounter(fancy_arg_pass_caller_forwarding)); } + if 0 != ci_flags & VM_CALL_ARGS_SPLAT { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_splat)); } + if 0 != ci_flags & VM_CALL_ARGS_BLOCKARG { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_blockarg)); } + if 0 != ci_flags & VM_CALL_KWARG { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_kwarg)); } + if 0 != ci_flags & VM_CALL_KW_SPLAT { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_kw_splat)); } + if 0 != ci_flags & VM_CALL_TAILCALL { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_tailcall)); } + if 0 != ci_flags & VM_CALL_SUPER { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_super)); } + if 0 != ci_flags & VM_CALL_ZSUPER { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_zsuper)); } + if 0 != ci_flags & VM_CALL_FORWARDING { self.push_insn(block, Insn::IncrCounter(complex_arg_pass_caller_forwarding)); } } fn try_rewrite_fixnum_op(&mut self, block: BlockId, orig_insn_id: InsnId, f: &dyn Fn(InsnId, InsnId) -> Insn, bop: u32, left: InsnId, right: InsnId, state: InsnId) { @@ -2314,7 +2314,7 @@ impl Function { // TODO(max): Handle other kinds of parameter passing let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; if !can_direct_send(self, block, iseq) { - self.set_dynamic_send_reason(insn_id, FancyFeatureUse); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -2340,7 +2340,7 @@ impl Function { let iseq = unsafe { *capture.code.iseq.as_ref() }; if !can_direct_send(self, block, iseq) { - self.set_dynamic_send_reason(insn_id, FancyFeatureUse); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } // Can't pass a block to a block for now @@ -2876,7 +2876,7 @@ impl Function { // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { - fun.count_fancy_call_features(block, ci_flags); + fun.count_complex_call_features(block, ci_flags); return Err(()); } @@ -2948,7 +2948,7 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { - fun.count_fancy_call_features(block, ci_flags); + fun.count_complex_call_features(block, ci_flags); } else { fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 07f80c06824b71..97b93e3d7b05f3 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2549,7 +2549,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) - IncrCounter fancy_arg_pass_param_opt + IncrCounter complex_arg_pass_param_opt v12:BasicObject = SendWithoutBlock v6, :foo, v10 CheckInterrupts Return v12 @@ -2633,7 +2633,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) - IncrCounter fancy_arg_pass_param_rest + IncrCounter complex_arg_pass_param_rest v12:BasicObject = SendWithoutBlock v6, :foo, v10 CheckInterrupts Return v12 @@ -2940,9 +2940,9 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) - IncrCounter fancy_arg_pass_param_opt - IncrCounter fancy_arg_pass_param_kw - IncrCounter fancy_arg_pass_param_block + IncrCounter complex_arg_pass_param_opt + IncrCounter complex_arg_pass_param_kw + IncrCounter complex_arg_pass_param_block v18:BasicObject = SendWithoutBlock v43, :initialize CheckInterrupts CheckInterrupts @@ -7222,7 +7222,7 @@ mod hir_opt_tests { } #[test] - fn counting_fancy_feature_use_for_fallback() { + fn counting_complex_feature_use_for_fallback() { eval(" define_method(:fancy) { |_a, *_b, kw: 100, **kw_rest, &block| } def test = fancy(1) @@ -7239,10 +7239,10 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) - IncrCounter fancy_arg_pass_param_rest - IncrCounter fancy_arg_pass_param_kw - IncrCounter fancy_arg_pass_param_kwrest - IncrCounter fancy_arg_pass_param_block + IncrCounter complex_arg_pass_param_rest + IncrCounter complex_arg_pass_param_kw + IncrCounter complex_arg_pass_param_kwrest + IncrCounter complex_arg_pass_param_block v12:BasicObject = SendWithoutBlock v6, :fancy, v10 CheckInterrupts Return v12 @@ -7266,7 +7266,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - IncrCounter fancy_arg_pass_param_forwardable + IncrCounter complex_arg_pass_param_forwardable v11:BasicObject = SendWithoutBlock v6, :forwardable CheckInterrupts Return v11 diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 35af2b1d9d3bea..30a09669940679 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -177,7 +177,7 @@ make_counters! { send_fallback_ccall_with_frame_too_many_args, // The call has at least one feature on the caller or callee side // that the optimizer does not support. - send_fallback_fancy_call_feature, + send_fallback_one_or_more_complex_arg_pass, send_fallback_bmethod_non_iseq_proc, send_fallback_obj_to_string_not_string, send_fallback_not_optimized_instruction, @@ -257,22 +257,22 @@ make_counters! { unspecialized_send_def_type_null, // Unsupported parameter features - fancy_arg_pass_param_rest, - fancy_arg_pass_param_opt, - fancy_arg_pass_param_kw, - fancy_arg_pass_param_kwrest, - fancy_arg_pass_param_block, - fancy_arg_pass_param_forwardable, + complex_arg_pass_param_rest, + complex_arg_pass_param_opt, + complex_arg_pass_param_kw, + complex_arg_pass_param_kwrest, + complex_arg_pass_param_block, + complex_arg_pass_param_forwardable, // Unsupported caller side features - fancy_arg_pass_caller_splat, - fancy_arg_pass_caller_blockarg, - fancy_arg_pass_caller_kwarg, - fancy_arg_pass_caller_kw_splat, - fancy_arg_pass_caller_tailcall, - fancy_arg_pass_caller_super, - fancy_arg_pass_caller_zsuper, - fancy_arg_pass_caller_forwarding, + complex_arg_pass_caller_splat, + complex_arg_pass_caller_blockarg, + complex_arg_pass_caller_kwarg, + complex_arg_pass_caller_kw_splat, + complex_arg_pass_caller_tailcall, + complex_arg_pass_caller_super, + complex_arg_pass_caller_zsuper, + complex_arg_pass_caller_forwarding, // Writes to the VM frame vm_write_pc_count, @@ -427,7 +427,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, SendPolymorphic => send_fallback_send_polymorphic, SendNoProfiles => send_fallback_send_no_profiles, - FancyFeatureUse => send_fallback_fancy_call_feature, + ComplexArgPass => send_fallback_one_or_more_complex_arg_pass, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, From b919eb56ee7cfe2431d9b2301e347d4d503846cd Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 4 Nov 2025 22:03:36 -0500 Subject: [PATCH 0820/2435] ZJIT: Count caller side features for `complex_arg_pass` After 34b0ac68b31, we use a fallback instead of side exit for splats. Count splats under `send_fallback_one_or_more_complex_arg_pass`. --- zjit/src/hir.rs | 10 ++++++++- zjit/src/hir/opt_tests.rs | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 09e293a0f6e05e..b26d2ffa047c30 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2290,6 +2290,8 @@ impl Function { // do not optimize into a `SendWithoutBlockDirect`. let flags = unsafe { rb_vm_ci_flag(ci) }; if unspecializable_call_type(flags) { + self.count_complex_call_features(block, flags); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } @@ -2770,6 +2772,8 @@ impl Function { // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding if unspecializable_call_type(ci_flags) { + fun.count_complex_call_features(block, ci_flags); + fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); } @@ -2877,6 +2881,7 @@ impl Function { // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { fun.count_complex_call_features(block, ci_flags); + fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); } @@ -2948,7 +2953,10 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { - fun.count_complex_call_features(block, ci_flags); + // TODO(alan): Add fun.count_complex_call_features() here without double + // counting splat + fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); + return Err(()); } else { fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 97b93e3d7b05f3..6fc890f385a46c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4815,6 +4815,7 @@ mod hir_opt_tests { v14:ArrayExact = NewArray GuardBlockParamProxy l0 v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + IncrCounter complex_arg_pass_caller_blockarg v19:BasicObject = Send v14, 0x1008, :map, v17 CheckInterrupts Return v19 @@ -6841,6 +6842,51 @@ mod hir_opt_tests { "); } + #[test] + fn test_splat() { + eval(" + def foo = itself + + def test + # Use a local to inhibit compile.c peephole optimization to ensure callsites have VM_CALL_ARGS_SPLAT + empty = [] + foo(*empty) + ''.display(*empty) + itself(*empty) + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + v18:ArrayExact = ToArray v14 + IncrCounter complex_arg_pass_caller_splat + v20:BasicObject = SendWithoutBlock v8, :foo, v18 + v23:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v25:StringExact = StringCopy v23 + PatchPoint NoEPEscape(test) + v29:ArrayExact = ToArray v14 + IncrCounter complex_arg_pass_caller_splat + v31:BasicObject = SendWithoutBlock v25, :display, v29 + PatchPoint NoEPEscape(test) + v37:ArrayExact = ToArray v14 + IncrCounter complex_arg_pass_caller_splat + v39:BasicObject = SendWithoutBlock v8, :itself, v37 + CheckInterrupts + Return v39 + "); + } + #[test] fn test_inline_symbol_to_sym() { eval(r#" From d43533a974cfc105a91ba488d779c75036e49bbd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 17:19:10 -0800 Subject: [PATCH 0821/2435] ZJIT: Split LShift in arm64_scratch_split --- zjit/src/backend/arm64/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 532570d732341e..b3827ae75d4062 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -800,6 +800,7 @@ impl Assembler { asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: reg_out, shift: Opnd::UImm(63) }); } } + Insn::LShift { opnd, out, .. } | Insn::RShift { opnd, out, .. } => { *opnd = split_memory_read(asm, *opnd, SCRATCH0_OPND); let mem_out = split_memory_write(out, SCRATCH0_OPND); @@ -2690,4 +2691,24 @@ mod tests { "); assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae10302aae2030faa100080d200023fd6"); } + + #[test] + fn test_split_spilled_lshift() { + let (mut asm, mut cb) = setup_asm(); + + let opnd_vreg = asm.load(1.into()); + let out_vreg = asm.lshift(opnd_vreg, Opnd::UImm(1)); + asm.mov(C_RET_OPND, out_vreg); + asm.compile_with_num_regs(&mut cb, 0); // spill every VReg + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x16, #1 + 0x4: stur x16, [x29, #-8] + 0x8: ldur x15, [x29, #-8] + 0xc: lsl x15, x15, #1 + 0x10: stur x15, [x29, #-8] + 0x14: ldur x0, [x29, #-8] + "); + assert_snapshot!(cb.hexdump(), @"300080d2b0831ff8af835ff8eff97fd3af831ff8a0835ff8"); + } } From 53f1fc25462dceb93ffd9a481865e8dab5d5fb41 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 4 Nov 2025 17:50:52 -0800 Subject: [PATCH 0822/2435] ZJIT: Allow Store with 8-bit Opnd::Mem --- zjit/src/asm/arm64/inst/load_store.rs | 6 ++++++ zjit/src/asm/arm64/mod.rs | 16 +++++++++++++++- zjit/src/backend/arm64/mod.rs | 22 +++++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/zjit/src/asm/arm64/inst/load_store.rs b/zjit/src/asm/arm64/inst/load_store.rs index e27909ae355d10..e78edd56752aae 100644 --- a/zjit/src/asm/arm64/inst/load_store.rs +++ b/zjit/src/asm/arm64/inst/load_store.rs @@ -124,6 +124,12 @@ impl LoadStore { pub fn sturh(rt: u8, rn: u8, imm9: i16) -> Self { Self { rt, rn, idx: Index::None, imm9, opc: Opc::STR, size: Size::Size16 } } + + /// STURB (store register, byte, unscaled) + /// + pub fn sturb(rt: u8, rn: u8, imm9: i16) -> Self { + Self { rt, rn, idx: Index::None, imm9, opc: Opc::STR, size: Size::Size8 } + } } /// diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index ffb83cf16abd03..a4459117312f89 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -1026,7 +1026,21 @@ pub fn sturh(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { LoadStore::sturh(rt.reg_no, rn.base_reg_no, rn.disp as i16).into() }, - _ => panic!("Invalid operand combination to stur instruction.") + _ => panic!("Invalid operand combination to sturh instruction: {rt:?}, {rn:?}") + }; + + cb.write_bytes(&bytes); +} + +pub fn sturb(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { + let bytes: [u8; 4] = match (rt, rn) { + (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { + assert!(rn.num_bits == 8); + assert!(mem_disp_fits_bits(rn.disp), "Expected displacement {} to be 9 bits or less", rn.disp); + + LoadStore::sturb(rt.reg_no, rn.base_reg_no, rn.disp as i16).into() + }, + _ => panic!("Invalid operand combination to sturb instruction: {rt:?}, {rn:?}") }; cb.write_bytes(&bytes); diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index b3827ae75d4062..428d4bff779084 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1298,7 +1298,8 @@ impl Assembler { match dest.rm_num_bits() { 64 | 32 => stur(cb, src, dest.into()), 16 => sturh(cb, src, dest.into()), - num_bits => panic!("unexpected dest num_bits: {} (src: {:#?}, dest: {:#?})", num_bits, src, dest), + 8 => sturb(cb, src, dest.into()), + num_bits => panic!("unexpected dest num_bits: {} (src: {:?}, dest: {:?})", num_bits, src, dest), } }, Insn::Load { opnd, out } | @@ -1675,6 +1676,7 @@ mod tests { static TEMP_REGS: [Reg; 5] = [X1_REG, X9_REG, X10_REG, X14_REG, X15_REG]; fn setup_asm() -> (Assembler, CodeBlock) { + crate::options::rb_zjit_prepare_options(); // Allow `get_option!` in Assembler (Assembler::new(), CodeBlock::new_dummy()) } @@ -2600,9 +2602,22 @@ mod tests { assert_snapshot!(cb.hexdump(), @"200500b1010400b1"); } + #[test] + fn test_store_spilled_byte() { + let (mut asm, mut cb) = setup_asm(); + + asm.store(Opnd::mem(8, C_RET_OPND, 0), Opnd::mem(8, C_RET_OPND, 8)); + asm.compile_with_num_regs(&mut cb, 0); // spill every VReg + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: ldurb w16, [x0, #8] + 0x4: sturb w16, [x0] + "); + assert_snapshot!(cb.hexdump(), @"1080403810000038"); + } + #[test] fn test_ccall_resolve_parallel_moves_no_cycle() { - crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); asm.ccall(0 as _, vec![ @@ -2620,7 +2635,6 @@ mod tests { #[test] fn test_ccall_resolve_parallel_moves_single_cycle() { - crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // x0 and x1 form a cycle @@ -2643,7 +2657,6 @@ mod tests { #[test] fn test_ccall_resolve_parallel_moves_two_cycles() { - crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // x0 and x1 form a cycle, and x2 and rcx form another cycle @@ -2670,7 +2683,6 @@ mod tests { #[test] fn test_ccall_resolve_parallel_moves_large_cycle() { - crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // x0, x1, and x2 form a cycle From 49b06f40af04f1bad0e6a129386311bd9f27a9a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 11:56:21 +0900 Subject: [PATCH 0823/2435] Use the exception class mentioned in the doc Instead of an undocumented constant. --- spec/ruby/library/stringscanner/unscan_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/ruby/library/stringscanner/unscan_spec.rb b/spec/ruby/library/stringscanner/unscan_spec.rb index df0ea433674a29..b7b4876b8afa6a 100644 --- a/spec/ruby/library/stringscanner/unscan_spec.rb +++ b/spec/ruby/library/stringscanner/unscan_spec.rb @@ -21,8 +21,8 @@ @s.pos.should == pos end - it "raises a ScanError when the previous match had failed" do - -> { @s.unscan }.should raise_error(ScanError) - -> { @s.scan(/\d/); @s.unscan }.should raise_error(ScanError) + it "raises a StringScanner::Error when the previous match had failed" do + -> { @s.unscan }.should raise_error(StringScanner::Error) + -> { @s.scan(/\d/); @s.unscan }.should raise_error(StringScanner::Error) end end From c85ef2ca9c5c96f3a02bc8b1a20c0f570737994b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 13:46:28 +0900 Subject: [PATCH 0824/2435] [ruby/strscan] ISO C90 forbids mixed declarations and code Cannot use C99 syntax, as far as supporting Ruby 2.6 and earlier. https://github.com/ruby/strscan/commit/f6d178fda5 --- ext/strscan/strscan.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 164d4c65d061a5..da7b51ec32de83 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -1274,7 +1274,7 @@ static VALUE strscan_scan_base10_integer(VALUE self) { char *ptr; - long len = 0; + long len = 0, remaining_len; struct strscanner *p; GET_SCANNER(self, p); @@ -1284,7 +1284,7 @@ strscan_scan_base10_integer(VALUE self) ptr = CURPTR(p); - long remaining_len = S_RESTLEN(p); + remaining_len = S_RESTLEN(p); if (remaining_len <= 0) { return Qnil; @@ -1311,7 +1311,7 @@ static VALUE strscan_scan_base16_integer(VALUE self) { char *ptr; - long len = 0; + long len = 0, remaining_len; struct strscanner *p; GET_SCANNER(self, p); @@ -1321,7 +1321,7 @@ strscan_scan_base16_integer(VALUE self) ptr = CURPTR(p); - long remaining_len = S_RESTLEN(p); + remaining_len = S_RESTLEN(p); if (remaining_len <= 0) { return Qnil; From f8e9bccd03f3e62e2f25cc08d5d5c6861347a0fe Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 12:07:53 +0900 Subject: [PATCH 0825/2435] [ruby/strscan] Deprecate undocumented toplevel constant `ScanError` https://github.com/ruby/strscan/commit/b4ddc3a2a6 --- ext/strscan/extconf.rb | 1 + ext/strscan/strscan.c | 11 ++++++++++- test/strscan/test_stringscanner.rb | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index 3c311d2364b7d7..d2e9343cbced7f 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -4,6 +4,7 @@ $INCFLAGS << " -I$(top_srcdir)" if $extmk have_func("onig_region_memsize(NULL)") have_func("rb_reg_onig_match", "ruby/re.h") + have_func("rb_deprecate_constant") create_makefile 'strscan' else File.write('Makefile', dummy_makefile("").join) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index da7b51ec32de83..4746afd5178d7c 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -24,6 +24,14 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #define STRSCAN_VERSION "3.1.6.dev" + +#ifdef HAVE_RB_DEPRECATE_CONSTANT +/* In ruby 3.0, defined but exposed in external headers */ +extern void rb_deprecate_constant(VALUE mod, const char *name); +#else +# define rb_deprecate_constant(mod, name) ((void)0) +#endif + /* ======================================================================= Data Type Definitions ======================================================================= */ @@ -1604,7 +1612,7 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name (const unsigned char* )name_end, regs); if (num >= 1) { - return num; + return num; } } rb_enc_raise(enc, rb_eIndexError, "undefined group name reference: %.*s", @@ -2210,6 +2218,7 @@ Init_strscan(void) ScanError = rb_define_class_under(StringScanner, "Error", rb_eStandardError); if (!rb_const_defined(rb_cObject, id_scanerr)) { rb_const_set(rb_cObject, id_scanerr, ScanError); + rb_deprecate_constant(rb_cObject, "ScanError"); } tmp = rb_str_new2(STRSCAN_VERSION); rb_obj_freeze(tmp); diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb index 085a9113132b75..8218e5b6bebc85 100644 --- a/test/strscan/test_stringscanner.rb +++ b/test/strscan/test_stringscanner.rb @@ -875,7 +875,7 @@ def test_unscan assert_equal({}, s.named_captures) assert_equal("te", s.scan(/../)) assert_equal(nil, s.scan(/\d/)) - assert_raise(ScanError) { s.unscan } + assert_raise(StringScanner::Error) { s.unscan } end def test_rest From f8d1291162d45db18f51f0a8e0e27bb1f98b60ae Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 4 Nov 2025 15:00:18 +0100 Subject: [PATCH 0826/2435] Support passing a #to_str object to Pathname.new for compatibility * See https://github.com/ruby/pathname/pull/57#issuecomment-3485646510 --- pathname_builtin.rb | 3 ++- test/pathname/test_pathname.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 16ed219ec38c06..1dedf5e08df546 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -217,7 +217,8 @@ class Pathname def initialize(path) unless String === path path = path.to_path if path.respond_to? :to_path - raise TypeError unless String === path + path = path.to_str if path.respond_to? :to_str + raise TypeError, "Pathname.new requires a String, #to_path or #to_str" unless String === path end if path.include?("\0") diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index e80473e5a3eea0..e2d6a09fb2f0c0 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -484,6 +484,10 @@ def test_initialize assert_equal('a', p1.to_s) p2 = Pathname.new(p1) assert_equal(p1, p2) + + obj = Object.new + def obj.to_str; "a/b"; end + assert_equal("a/b", Pathname.new(obj).to_s) end def test_initialize_nul From 26cb69f7d173fe5c9c5e6e4dddfd135212987701 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 31 Oct 2025 00:02:21 +0900 Subject: [PATCH 0827/2435] sync_default_gems.rb: fix release check on case-sensitive filesystems This fixes it for the English gem. --- tool/sync_default_gems.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index f1a28f402a7e83..d927086d3278b6 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -370,13 +370,11 @@ def sync_default_gems(gem) def check_prerelease_version(gem) return if ["rubygems", "mmtk", "cgi"].include?(gem) - gem = gem.downcase - require "net/https" require "json" require "uri" - uri = URI("https://rubygems.org/api/v1/versions/#{gem}/latest.json") + uri = URI("https://rubygems.org/api/v1/versions/#{gem.downcase}/latest.json") response = Net::HTTP.get(uri) latest_version = JSON.parse(response)["version"] From 348adb8fb46c815b1ec16f05d1beceef270ef0ec Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 28 Oct 2025 21:34:41 +0900 Subject: [PATCH 0828/2435] sync_default_gems.rb: simplify rewriting commit message Use "git commit --amend" instead of "git filter-branch" since we only need to handle one commit at HEAD. --- tool/sync_default_gems.rb | 54 ++++++++++++----------------- tool/test/test_sync_default_gems.rb | 10 ++---- 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index d927086d3278b6..620539bcc97209 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -415,18 +415,19 @@ def ignore_file_pattern_for(gem) Regexp.union(*patterns) end - def message_filter(repo, sha, input: ARGF) + def message_filter(repo, sha, log) unless repo.count("/") == 1 and /\A\S+\z/ =~ repo raise ArgumentError, "invalid repository: #{repo}" end unless /\A\h{10,40}\z/ =~ sha raise ArgumentError, "invalid commit-hash: #{sha}" end - log = input.read - log.delete!("\r") - log << "\n" if !log.end_with?("\n") repo_url = "https://github.com/#{repo}" + # Log messages generated by GitHub web UI have inconsistent line endings + log = log.delete("\r") + log << "\n" if !log.end_with?("\n") + # Split the subject from the log message according to git conventions. # SPECIAL TREAT: when the first line ends with a dot `.` (which is not # obeying the conventions too), takes only that line. @@ -457,7 +458,7 @@ def message_filter(repo, sha, input: ARGF) else log = commit_url end - puts subject, "\n", log + "#{subject}\n\n#{log}" end def log_format(format, args, &block) @@ -648,6 +649,21 @@ def pickup_commit(gem, sha, edit) `git commit --amend --no-edit --all` end + # Update commit message to include links to the original commit + puts "Update commit message: #{sha}" + repo, = REPOSITORIES[gem] + headers, orig = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) + message = message_filter(repo, sha, orig) + IO.popen(%W[git commit --amend --no-edit -F -], "r+b") {|io| + io.write(message) + io.close_write + io.read + } + unless $?.success? + puts "Failed to modify commit message of #{sha}" + return nil + end + return true end @@ -684,32 +700,13 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil) puts "----" failed_commits = [] - - require 'shellwords' - filter = [ - ENV.fetch('RUBY', 'ruby').shellescape, - File.realpath(__FILE__).shellescape, - "--message-filter", - ] commits.each do |sha, subject| puts "Pick #{sha} from #{repo}." case pickup_commit(gem, sha, edit) when false - next + # skipped when nil failed_commits << sha - next - end - - puts "Update commit message: #{sha}" - - # Run this script itself (tool/sync_default_gems.rb --message-filter) as a message filter - IO.popen({"FILTER_BRANCH_SQUELCH_WARNING" => "1"}, - %W[git filter-branch -f --msg-filter #{[filter, repo, sha].join(' ')} -- HEAD~1..HEAD], - &:read) - unless $?.success? - puts "Failed to modify commit message of #{sha}" - break end end @@ -799,13 +796,6 @@ def update_default_gems(gem, release: false) next unless pattern =~ name or pattern =~ gem printf "%-15s https://github.com/%s\n", name, gem end - when "--message-filter" - ARGV.shift - if ARGV.size < 2 - abort "usage: #{$0} --message-filter repository commit-hash [input...]" - end - message_filter(*ARGV.shift(2)) - exit when "rdoc-ref" ARGV.shift pattern = ARGV.empty? ? %w[*.c *.rb *.rdoc] : ARGV diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index adbde66fbff132..b0c9638862a3d3 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -19,14 +19,8 @@ def assert_message_filter(expected, trailers, input, repo = "ruby/test", sha = " expected.concat(trailers.map {_1+"\n"}) end - out, err = capture_output do - SyncDefaultGems.message_filter(repo, sha, input: StringIO.new(input, "r")) - end - - all_assertions do |a| - a.for("error") {assert_empty err} - a.for("result") {assert_pattern_list(expected, out)} - end + out = SyncDefaultGems.message_filter(repo, sha, input) + assert_pattern_list(expected, out) end def test_subject_only From b722631b481314023b9fa2f3fd16fa9ab0b4bf9c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Oct 2025 19:34:22 +0900 Subject: [PATCH 0829/2435] sync_default_gems.rb: use declarative mapping rules No behavior change is intended by this change. --- tool/sync_default_gems.rb | 646 +++++++++++++++------------- tool/test/test_sync_default_gems.rb | 11 +- 2 files changed, 348 insertions(+), 309 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 620539bcc97209..9e3a2532e0c1c3 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -4,6 +4,7 @@ require 'fileutils' require "rbconfig" +require "find" module SyncDefaultGems include FileUtils @@ -11,76 +12,289 @@ module SyncDefaultGems module_function - REPOSITORIES = { - "io-console": 'ruby/io-console', - "io-nonblock": 'ruby/io-nonblock', - "io-wait": 'ruby/io-wait', - "net-http": "ruby/net-http", - "net-protocol": "ruby/net-protocol", - "open-uri": "ruby/open-uri", - "win32-registry": "ruby/win32-registry", - English: "ruby/English", - cgi: "ruby/cgi", - date: 'ruby/date', - delegate: "ruby/delegate", - did_you_mean: "ruby/did_you_mean", - digest: "ruby/digest", - erb: "ruby/erb", - error_highlight: "ruby/error_highlight", - etc: 'ruby/etc', - fcntl: 'ruby/fcntl', - fileutils: 'ruby/fileutils', - find: "ruby/find", - forwardable: "ruby/forwardable", - ipaddr: 'ruby/ipaddr', - json: 'ruby/json', - mmtk: ['ruby/mmtk', "main"], - open3: "ruby/open3", - openssl: "ruby/openssl", - optparse: "ruby/optparse", - pp: "ruby/pp", - prettyprint: "ruby/prettyprint", - prism: ["ruby/prism", "main"], - psych: 'ruby/psych', - resolv: "ruby/resolv", - rubygems: 'ruby/rubygems', - securerandom: "ruby/securerandom", - shellwords: "ruby/shellwords", - singleton: "ruby/singleton", - stringio: 'ruby/stringio', - strscan: 'ruby/strscan', - syntax_suggest: ["ruby/syntax_suggest", "main"], - tempfile: "ruby/tempfile", - time: "ruby/time", - timeout: "ruby/timeout", - tmpdir: "ruby/tmpdir", - tsort: "ruby/tsort", - un: "ruby/un", - uri: "ruby/uri", - weakref: "ruby/weakref", - yaml: "ruby/yaml", - zlib: 'ruby/zlib', - }.transform_keys(&:to_s) + # upstream: "owner/repo" + # branch: "branch_name" + # mappings: [ ["path_in_upstream", "path_in_ruby"], ... ] + # NOTE: path_in_ruby is assumed to be "owned" by this gem, and the contents + # will be removed before sync + # exclude: [ "fnmatch_pattern_after_mapping", ... ] + Repository = Data.define(:upstream, :branch, :mappings, :exclude) do + def excluded?(newpath) + p = newpath + until p == "." + return true if exclude.any? {|pat| File.fnmatch?(pat, p, File::FNM_PATHNAME|File::FNM_EXTGLOB)} + p = File.dirname(p) + end + false + end + + def rewrite_for_ruby(path) + newpath = mappings.find do |src, dst| + if path == src || path.start_with?(src + "/") + break path.sub(src, dst) + end + end + return nil unless newpath + return nil if excluded?(newpath) + newpath + end + end CLASSICAL_DEFAULT_BRANCH = "master" + def repo((upstream, branch), mappings, exclude: []) + branch ||= CLASSICAL_DEFAULT_BRANCH + exclude += ["ext/**/depend"] + Repository.new(upstream:, branch:, mappings:, exclude:) + end + + def lib((upstream, branch), gemspec_in_subdir: false) + _org, name = upstream.split("/") + gemspec_dst = gemspec_in_subdir ? "lib/#{name}/#{name}.gemspec" : "lib/#{name}.gemspec" + repo([upstream, branch], [ + ["lib/#{name}.rb", "lib/#{name}.rb"], + ["lib/#{name}", "lib/#{name}"], + ["test/test_#{name}.rb", "test/test_#{name}.rb"], + ["test/#{name}", "test/#{name}"], + ["#{name}.gemspec", gemspec_dst], + ]) + end + + REPOSITORIES = { + "io-console": repo("ruby/io-console", [ + ["ext/io/console", "ext/io/console"], + ["test/io/console", "test/io/console"], + ["lib/io/console", "ext/io/console/lib/console"], + ["io-console.gemspec", "ext/io/console/io-console.gemspec"], + ]), + "io-nonblock": repo("ruby/io-nonblock", [ + ["ext/io/nonblock", "ext/io/nonblock"], + ["test/io/nonblock", "test/io/nonblock"], + ["io-nonblock.gemspec", "ext/io/nonblock/io-nonblock.gemspec"], + ]), + "io-wait": repo("ruby/io-wait", [ + ["ext/io/wait", "ext/io/wait"], + ["test/io/wait", "test/io/wait"], + ["io-wait.gemspec", "ext/io/wait/io-wait.gemspec"], + ]), + "net-http": repo("ruby/net-http", [ + ["lib/net/http.rb", "lib/net/http.rb"], + ["lib/net/http", "lib/net/http"], + ["test/net/http", "test/net/http"], + ["net-http.gemspec", "lib/net/http/net-http.gemspec"], + ]), + "net-protocol": repo("ruby/net-protocol", [ + ["lib/net/protocol.rb", "lib/net/protocol.rb"], + ["test/net/protocol", "test/net/protocol"], + ["net-protocol.gemspec", "lib/net/net-protocol.gemspec"], + ]), + "open-uri": lib("ruby/open-uri"), + "win32-registry": repo("ruby/win32-registry", [ + ["lib/win32/registry.rb", "ext/win32/lib/win32/registry.rb"], + ["test/win32/test_registry.rb", "test/win32/test_registry.rb"], + ["win32-registry.gemspec", "ext/win32/win32-registry.gemspec"], + ]), + English: lib("ruby/English"), + cgi: repo("ruby/cgi", [ + ["ext/cgi", "ext/cgi"], + ["lib/cgi/escape.rb", "lib/cgi/escape.rb"], + ["test/cgi/test_cgi_escape.rb", "test/cgi/test_cgi_escape.rb"], + ["test/cgi/update_env.rb", "test/cgi/update_env.rb"], + ]), + date: repo("ruby/date", [ + ["doc/date", "doc/date"], + ["ext/date", "ext/date"], + ["lib", "ext/date/lib"], + ["test/date", "test/date"], + ["date.gemspec", "ext/date/date.gemspec"], + ], exclude: [ + "ext/date/lib/date_core.bundle", + ]), + delegate: lib("ruby/delegate"), + did_you_mean: repo("ruby/did_you_mean", [ + ["lib/did_you_mean.rb", "lib/did_you_mean.rb"], + ["lib/did_you_mean", "lib/did_you_mean"], + ["test", "test/did_you_mean"], + ["did_you_mean.gemspec", "lib/did_you_mean/did_you_mean.gemspec"], + ], exclude: [ + "test/did_you_mean/lib", + "test/did_you_mean/tree_spell/test_explore.rb", + ]), + digest: repo("ruby/digest", [ + ["ext/digest/lib/digest/sha2", "ext/digest/sha2/lib/sha2"], + ["ext/digest", "ext/digest"], + ["lib/digest.rb", "ext/digest/lib/digest.rb"], + ["lib/digest/version.rb", "ext/digest/lib/digest/version.rb"], + ["lib/digest/sha2.rb", "ext/digest/sha2/lib/sha2.rb"], + ["test/digest", "test/digest"], + ["digest.gemspec", "ext/digest/digest.gemspec"], + ]), + erb: repo("ruby/erb", [ + ["ext/erb", "ext/erb"], + ["lib/erb", "lib/erb"], + ["lib/erb.rb", "lib/erb.rb"], + ["test/erb", "test/erb"], + ["erb.gemspec", "lib/erb/erb.gemspec"], + ["libexec/erb", "libexec/erb"], + ]), + error_highlight: repo("ruby/error_highlight", [ + ["lib/error_highlight.rb", "lib/error_highlight.rb"], + ["lib/error_highlight", "lib/error_highlight"], + ["test", "test/error_highlight"], + ["error_highlight.gemspec", "lib/error_highlight/error_highlight.gemspec"], + ]), + etc: repo("ruby/etc", [ + ["ext/etc", "ext/etc"], + ["test/etc", "test/etc"], + ["etc.gemspec", "ext/etc/etc.gemspec"], + ]), + fcntl: repo("ruby/fcntl", [ + ["ext/fcntl", "ext/fcntl"], + ["fcntl.gemspec", "ext/fcntl/fcntl.gemspec"], + ]), + fileutils: lib("ruby/fileutils"), + find: lib("ruby/find"), + forwardable: lib("ruby/forwardable", gemspec_in_subdir: true), + ipaddr: lib("ruby/ipaddr"), + json: repo("ruby/json", [ + ["ext/json/ext", "ext/json"], + ["test/json", "test/json"], + ["lib", "ext/json/lib"], + ["json.gemspec", "ext/json/json.gemspec"], + ], exclude: [ + "ext/json/lib/json/ext/.keep", + "ext/json/lib/json/pure.rb", + "ext/json/lib/json/pure", + "ext/json/lib/json/truffle_ruby", + "test/json/lib", + "ext/json/extconf.rb", + ]), + mmtk: repo(["ruby/mmtk", "main"], [ + ["gc/mmtk", "gc/mmtk"], + ]), + open3: lib("ruby/open3", gemspec_in_subdir: true).tap { + it.exclude << "lib/open3/jruby_windows.rb" + }, + openssl: repo("ruby/openssl", [ + ["ext/openssl", "ext/openssl"], + ["lib", "ext/openssl/lib"], + ["test/openssl", "test/openssl"], + ["sample", "sample/openssl"], + ["openssl.gemspec", "ext/openssl/openssl.gemspec"], + ["History.md", "ext/openssl/History.md"], + ], exclude: [ + "test/openssl/envutil.rb", + "ext/openssl/depend", + ]), + optparse: lib("ruby/optparse", gemspec_in_subdir: true).tap { + it.mappings << ["doc/optparse", "doc/optparse"] + }, + pp: lib("ruby/pp"), + prettyprint: lib("ruby/prettyprint"), + prism: repo(["ruby/prism", "main"], [ + ["ext/prism", "prism"], + ["lib/prism.rb", "lib/prism.rb"], + ["lib/prism", "lib/prism"], + ["test/prism", "test/prism"], + ["src", "prism"], + ["prism.gemspec", "lib/prism/prism.gemspec"], + ["include/prism", "prism"], + ["include/prism.h", "prism/prism.h"], + ["config.yml", "prism/config.yml"], + ["templates", "prism/templates"], + ], exclude: [ + "prism/templates/{javascript,java,rbi,sig}", + "test/prism/snapshots_test.rb", + "test/prism/snapshots", + "prism/extconf.rb", + "prism/srcs.mk*", + ]), + psych: repo("ruby/psych", [ + ["ext/psych", "ext/psych"], + ["lib", "ext/psych/lib"], + ["test/psych", "test/psych"], + ["psych.gemspec", "ext/psych/psych.gemspec"], + ], exclude: [ + "ext/psych/lib/org", + "ext/psych/lib/psych.jar", + "ext/psych/lib/psych_jars.rb", + "ext/psych/lib/psych.{bundle,so}", + "ext/psych/lib/2.*", + "ext/psych/yaml/LICENSE", + "ext/psych/.gitignore", + ]), + resolv: repo("ruby/resolv", [ + ["lib/resolv.rb", "lib/resolv.rb"], + ["test/resolv", "test/resolv"], + ["resolv.gemspec", "lib/resolv.gemspec"], + ["ext/win32/resolv/lib/resolv.rb", "ext/win32/lib/win32/resolv.rb"], + ["ext/win32/resolv", "ext/win32/resolv"], + ]), + rubygems: repo("ruby/rubygems", [ + ["lib/rubygems.rb", "lib/rubygems.rb"], + ["lib/rubygems", "lib/rubygems"], + ["test/rubygems", "test/rubygems"], + ["bundler/lib/bundler.rb", "lib/bundler.rb"], + ["bundler/lib/bundler", "lib/bundler"], + ["bundler/exe/bundle", "libexec/bundle"], + ["bundler/exe/bundler", "libexec/bundler"], + ["bundler/bundler.gemspec", "lib/bundler/bundler.gemspec"], + ["bundler/spec", "spec/bundler"], + *["bundle", "parallel_rspec", "rspec"].map {|binstub| + ["bundler/bin/#{binstub}", "spec/bin/#{binstub}"] + }, + *%w[dev_gems test_gems rubocop_gems standard_gems].flat_map {|gemfile| + ["rb.lock", "rb"].map do |ext| + ["tool/bundler/#{gemfile}.#{ext}", "tool/bundler/#{gemfile}.#{ext}"] + end + }, + ], exclude: [ + "spec/bundler/bin", + "spec/bundler/support/artifice/vcr_cassettes", + "spec/bundler/support/artifice/used_cassettes.txt", + "lib/{bundler,rubygems}/**/{COPYING,LICENSE,README}{,.{md,txt,rdoc}}", + ]), + securerandom: lib("ruby/securerandom"), + shellwords: lib("ruby/shellwords"), + singleton: lib("ruby/singleton"), + stringio: repo("ruby/stringio", [ + ["ext/stringio", "ext/stringio"], + ["test/stringio", "test/stringio"], + ["stringio.gemspec", "ext/stringio/stringio.gemspec"], + ], exclude: [ + "ext/stringio/README.md", + ]), + strscan: repo("ruby/strscan", [ + ["ext/strscan", "ext/strscan"], + ["lib", "ext/strscan/lib"], + ["test/strscan", "test/strscan"], + ["strscan.gemspec", "ext/strscan/strscan.gemspec"], + ["doc/strscan", "doc/strscan"], + ], exclude: [ + "ext/strscan/regenc.h", + "ext/strscan/regint.h", + ]), + syntax_suggest: lib(["ruby/syntax_suggest", "main"], gemspec_in_subdir: true), + tempfile: lib("ruby/tempfile"), + time: lib("ruby/time"), + timeout: lib("ruby/timeout"), + tmpdir: lib("ruby/tmpdir"), + tsort: lib("ruby/tsort"), + un: lib("ruby/un"), + uri: lib("ruby/uri", gemspec_in_subdir: true), + weakref: lib("ruby/weakref"), + yaml: lib("ruby/yaml", gemspec_in_subdir: true), + zlib: repo("ruby/zlib", [ + ["ext/zlib", "ext/zlib"], + ["test/zlib", "test/zlib"], + ["zlib.gemspec", "ext/zlib/zlib.gemspec"], + ]), + }.transform_keys(&:to_s) + # Allow synchronizing commits up to this FETCH_DEPTH. We've historically merged PRs # with about 250 commits to ruby/ruby, so we use this depth for ruby/ruby in general. FETCH_DEPTH = 500 - class << REPOSITORIES - def [](gem) - repo, branch = super(gem) - return repo, branch || CLASSICAL_DEFAULT_BRANCH - end - - def each_pair - super do |gem, (repo, branch)| - yield gem, [repo, branch || CLASSICAL_DEFAULT_BRANCH] - end - end - end - def pipe_readlines(args, rs: "\0", chomp: true) IO.popen(args) do |f| f.readlines(rs, chomp: chomp) @@ -88,7 +302,7 @@ def pipe_readlines(args, rs: "\0", chomp: true) end def porcelain_status(*pattern) - pipe_readlines(%W"git status --porcelain -z --" + pattern) + pipe_readlines(%W"git status --porcelain --no-renames -z --" + pattern) end def replace_rdoc_ref(file) @@ -117,247 +331,61 @@ def replace_rdoc_ref_all result.inject(false) {|changed, file| changed | replace_rdoc_ref(file)} end - # We usually don't use this. Please consider using #sync_default_gems_with_commits instead. - def sync_default_gems(gem) - repo, = REPOSITORIES[gem] - puts "Sync #{repo}" + def rubygems_do_fixup + gemspec_content = File.readlines("lib/bundler/bundler.gemspec").map do |line| + next if line =~ /LICENSE\.md/ - upstream = File.join("..", "..", repo) + line.gsub("bundler.gemspec", "lib/bundler/bundler.gemspec") + end.compact.join + File.write("lib/bundler/bundler.gemspec", gemspec_content) - case gem - when "rubygems" - rm_rf(%w[lib/rubygems lib/rubygems.rb test/rubygems]) - cp_r(Dir.glob("#{upstream}/lib/rubygems*"), "lib") - cp_r("#{upstream}/test/rubygems", "test") - rm_rf(%w[lib/bundler lib/bundler.rb libexec/bundler libexec/bundle spec/bundler tool/bundler/*]) - cp_r(Dir.glob("#{upstream}/bundler/lib/bundler*"), "lib") - cp_r(Dir.glob("#{upstream}/bundler/exe/bundle*"), "libexec") - - gemspec_content = File.readlines("#{upstream}/bundler/bundler.gemspec").map do |line| - next if line =~ /LICENSE\.md/ - - line.gsub("bundler.gemspec", "lib/bundler/bundler.gemspec") - end.compact.join - File.write("lib/bundler/bundler.gemspec", gemspec_content) - - cp_r("#{upstream}/bundler/spec", "spec/bundler") - rm_rf("spec/bundler/bin") - - ["bundle", "parallel_rspec", "rspec"].each do |binstub| - content = File.read("#{upstream}/bundler/bin/#{binstub}").gsub("../spec", "../bundler") - File.write("spec/bin/#{binstub}", content) - chmod("+x", "spec/bin/#{binstub}") - end + ["bundle", "parallel_rspec", "rspec"].each do |binstub| + path = "spec/bin/#{binstub}" + next unless File.exist?(path) + content = File.read(path).gsub("../spec", "../bundler") + File.write(path, content) + chmod("+x", path) + end + end - %w[dev_gems test_gems rubocop_gems standard_gems].each do |gemfile| - ["rb.lock", "rb"].each do |ext| - cp_r("#{upstream}/tool/bundler/#{gemfile}.#{ext}", "tool/bundler") + # We usually don't use this. Please consider using #sync_default_gems_with_commits instead. + def sync_default_gems(gem) + config = REPOSITORIES[gem] + puts "Sync #{config.upstream}" + + upstream = File.join("..", "..", config.upstream) + + config.mappings.each do |src, dst| + rm_rf(dst) + end + + copied = Set.new + config.mappings.each do |src, dst| + prefix = File.join(upstream, src) + # Maybe mapping needs to be updated? + next unless File.exist?(prefix) + Find.find(prefix) do |path| + next if File.directory?(path) + if copied.add?(path) + newpath = config.rewrite_for_ruby(path.sub(%r{\A#{Regexp.escape(upstream)}/}, "")) + next unless newpath + mkdir_p(File.dirname(newpath)) + cp(path, newpath) end end - rm_rf Dir.glob("spec/bundler/support/artifice/{vcr_cassettes,used_cassettes.txt}") - rm_rf Dir.glob("lib/{bundler,rubygems}/**/{COPYING,LICENSE,README}{,.{md,txt,rdoc}}") - when "json" - rm_rf(%w[ext/json lib/json test/json]) - cp_r("#{upstream}/ext/json/ext", "ext/json") - cp_r("#{upstream}/test/json", "test/json") - rm_rf("test/json/lib") - cp_r("#{upstream}/lib", "ext/json") - cp_r("#{upstream}/json.gemspec", "ext/json") - rm_rf(%w[ext/json/lib/json/pure.rb ext/json/lib/json/pure ext/json/lib/json/truffle_ruby/]) - json_files = Dir.glob("ext/json/lib/json/ext/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) } - rm_rf(json_files - Dir.glob("ext/json/lib/json/ext/**/*.rb") - Dir.glob("ext/json/lib/json/ext/**/depend")) - `git checkout ext/json/extconf.rb ext/json/generator/depend ext/json/parser/depend ext/json/depend benchmark/` - when "psych" - rm_rf(%w[ext/psych test/psych]) - cp_r("#{upstream}/ext/psych", "ext") - cp_r("#{upstream}/lib", "ext/psych") - cp_r("#{upstream}/test/psych", "test") - rm_rf(%w[ext/psych/lib/org ext/psych/lib/psych.jar ext/psych/lib/psych_jars.rb]) - rm_rf(%w[ext/psych/lib/psych.{bundle,so} ext/psych/lib/2.*]) - rm_rf(["ext/psych/yaml/LICENSE"]) - cp_r("#{upstream}/psych.gemspec", "ext/psych") - `git checkout ext/psych/depend ext/psych/.gitignore` - when "stringio" - rm_rf(%w[ext/stringio test/stringio]) - cp_r("#{upstream}/ext/stringio", "ext") - cp_r("#{upstream}/test/stringio", "test") - cp_r("#{upstream}/stringio.gemspec", "ext/stringio") - `git checkout ext/stringio/depend ext/stringio/README.md` - when "io-console" - rm_rf(%w[ext/io/console test/io/console]) - cp_r("#{upstream}/ext/io/console", "ext/io") - cp_r("#{upstream}/test/io/console", "test/io") - mkdir_p("ext/io/console/lib") - cp_r("#{upstream}/lib/io/console", "ext/io/console/lib") - rm_rf("ext/io/console/lib/console/ffi") - cp_r("#{upstream}/io-console.gemspec", "ext/io/console") - `git checkout ext/io/console/depend` - when "io-nonblock" - rm_rf(%w[ext/io/nonblock test/io/nonblock]) - cp_r("#{upstream}/ext/io/nonblock", "ext/io") - cp_r("#{upstream}/test/io/nonblock", "test/io") - cp_r("#{upstream}/io-nonblock.gemspec", "ext/io/nonblock") - `git checkout ext/io/nonblock/depend` - when "io-wait" - rm_rf(%w[ext/io/wait test/io/wait]) - cp_r("#{upstream}/ext/io/wait", "ext/io") - cp_r("#{upstream}/test/io/wait", "test/io") - cp_r("#{upstream}/io-wait.gemspec", "ext/io/wait") - `git checkout ext/io/wait/depend` - when "etc" - rm_rf(%w[ext/etc test/etc]) - cp_r("#{upstream}/ext/etc", "ext") - cp_r("#{upstream}/test/etc", "test") - cp_r("#{upstream}/etc.gemspec", "ext/etc") - `git checkout ext/etc/depend` - when "date" - rm_rf(%w[ext/date test/date]) - cp_r("#{upstream}/doc/date", "doc") - cp_r("#{upstream}/ext/date", "ext") - cp_r("#{upstream}/lib", "ext/date") - cp_r("#{upstream}/test/date", "test") - cp_r("#{upstream}/date.gemspec", "ext/date") - `git checkout ext/date/depend` - rm_rf(["ext/date/lib/date_core.bundle"]) - when "zlib" - rm_rf(%w[ext/zlib test/zlib]) - cp_r("#{upstream}/ext/zlib", "ext") - cp_r("#{upstream}/test/zlib", "test") - cp_r("#{upstream}/zlib.gemspec", "ext/zlib") - `git checkout ext/zlib/depend` - when "fcntl" - rm_rf(%w[ext/fcntl]) - cp_r("#{upstream}/ext/fcntl", "ext") - cp_r("#{upstream}/fcntl.gemspec", "ext/fcntl") - `git checkout ext/fcntl/depend` - when "strscan" - rm_rf(%w[ext/strscan test/strscan]) - cp_r("#{upstream}/ext/strscan", "ext") - cp_r("#{upstream}/lib", "ext/strscan") - cp_r("#{upstream}/test/strscan", "test") - cp_r("#{upstream}/strscan.gemspec", "ext/strscan") - begin - cp_r("#{upstream}/doc/strscan", "doc") - rescue Errno::ENOENT + end + + porcelain_status().each do |line| + /\A(?.)(?.) (?.*)\z/ =~ line or raise + if config.excluded?(path) + puts "Restoring excluded file: #{path}" + IO.popen(%W"git checkout --" + [path], "rb", &:read) end - rm_rf(%w["ext/strscan/regenc.h ext/strscan/regint.h"]) - `git checkout ext/strscan/depend` - when "cgi" - rm_rf(%w[lib/cgi.rb lib/cgi ext/cgi test/cgi]) - cp_r("#{upstream}/ext/cgi", "ext") - mkdir_p("lib/cgi") - cp_r("#{upstream}/lib/cgi/escape.rb", "lib/cgi") - mkdir_p("test/cgi") - cp_r("#{upstream}/test/cgi/test_cgi_escape.rb", "test/cgi") - cp_r("#{upstream}/test/cgi/update_env.rb", "test/cgi") - rm_rf("lib/cgi/escape.jar") - `git checkout lib/cgi.rb lib/cgi/util.rb ext/cgi/escape/depend` - when "openssl" - rm_rf(%w[ext/openssl test/openssl]) - cp_r("#{upstream}/ext/openssl", "ext") - cp_r("#{upstream}/lib", "ext/openssl") - cp_r("#{upstream}/test/openssl", "test") - rm_rf("test/openssl/envutil.rb") - cp_r("#{upstream}/openssl.gemspec", "ext/openssl") - cp_r("#{upstream}/History.md", "ext/openssl") - `git checkout ext/openssl/depend` - when "net-protocol" - rm_rf(%w[lib/net/protocol.rb lib/net/net-protocol.gemspec test/net/protocol]) - cp_r("#{upstream}/lib/net/protocol.rb", "lib/net") - cp_r("#{upstream}/test/net/protocol", "test/net") - cp_r("#{upstream}/net-protocol.gemspec", "lib/net") - when "net-http" - rm_rf(%w[lib/net/http.rb lib/net/http test/net/http]) - cp_r("#{upstream}/lib/net/http.rb", "lib/net") - cp_r("#{upstream}/lib/net/http", "lib/net") - cp_r("#{upstream}/test/net/http", "test/net") - cp_r("#{upstream}/net-http.gemspec", "lib/net/http") - when "did_you_mean" - rm_rf(%w[lib/did_you_mean lib/did_you_mean.rb test/did_you_mean]) - cp_r(Dir.glob("#{upstream}/lib/did_you_mean*"), "lib") - cp_r("#{upstream}/did_you_mean.gemspec", "lib/did_you_mean") - cp_r("#{upstream}/test", "test/did_you_mean") - rm_rf("test/did_you_mean/lib") - rm_rf(%w[test/did_you_mean/tree_spell/test_explore.rb]) - when "erb" - rm_rf(%w[lib/erb* test/erb libexec/erb]) - cp_r("#{upstream}/lib/erb.rb", "lib") - cp_r("#{upstream}/test/erb", "test") - cp_r("#{upstream}/erb.gemspec", "lib/erb") - cp_r("#{upstream}/libexec/erb", "libexec") - when "digest" - rm_rf(%w[ext/digest test/digest]) - cp_r("#{upstream}/ext/digest", "ext") - mkdir_p("ext/digest/lib/digest") - cp_r("#{upstream}/lib/digest.rb", "ext/digest/lib/") - cp_r("#{upstream}/lib/digest/version.rb", "ext/digest/lib/digest/") - mkdir_p("ext/digest/sha2/lib") - cp_r("#{upstream}/lib/digest/sha2.rb", "ext/digest/sha2/lib") - move("ext/digest/lib/digest/sha2", "ext/digest/sha2/lib") - cp_r("#{upstream}/test/digest", "test") - cp_r("#{upstream}/digest.gemspec", "ext/digest") - `git checkout ext/digest/depend ext/digest/*/depend` - when "optparse" - sync_lib gem, upstream - rm_rf(%w[doc/optparse]) - mkdir_p("doc/optparse") - cp_r("#{upstream}/doc/optparse", "doc") - when "error_highlight" - rm_rf(%w[lib/error_highlight lib/error_highlight.rb test/error_highlight]) - cp_r(Dir.glob("#{upstream}/lib/error_highlight*"), "lib") - cp_r("#{upstream}/error_highlight.gemspec", "lib/error_highlight") - cp_r("#{upstream}/test", "test/error_highlight") - when "open3" - sync_lib gem, upstream - rm_rf("lib/open3/jruby_windows.rb") - when "syntax_suggest" - sync_lib gem, upstream - rm_rf(%w[spec/syntax_suggest libexec/syntax_suggest]) - cp_r("#{upstream}/spec", "spec/syntax_suggest") - cp_r("#{upstream}/exe/syntax_suggest", "libexec/syntax_suggest") - when "prism" - rm_rf(%w[test/prism prism]) - - cp_r("#{upstream}/ext/prism", "prism") - cp_r("#{upstream}/lib/.", "lib") - cp_r("#{upstream}/test/prism", "test") - cp_r("#{upstream}/src/.", "prism") - - cp_r("#{upstream}/prism.gemspec", "lib/prism") - cp_r("#{upstream}/include/prism/.", "prism") - cp_r("#{upstream}/include/prism.h", "prism") - - cp_r("#{upstream}/config.yml", "prism/") - cp_r("#{upstream}/templates", "prism/") - rm_rf("prism/templates/javascript") - rm_rf("prism/templates/java") - rm_rf("prism/templates/rbi") - rm_rf("prism/templates/sig") - - rm("test/prism/snapshots_test.rb") - rm_rf("test/prism/snapshots") - - rm("prism/extconf.rb") - `git checkout prism/srcs.mk*` - when "resolv" - rm_rf(%w[lib/resolv.* ext/win32/resolv test/resolv ext/win32/lib/win32/resolv.rb]) - cp_r("#{upstream}/lib/resolv.rb", "lib") - cp_r("#{upstream}/resolv.gemspec", "lib") - cp_r("#{upstream}/ext/win32/resolv", "ext/win32") - move("ext/win32/resolv/lib/resolv.rb", "ext/win32/lib/win32") - rm_rf("ext/win32/resolv/lib") # Clean up empty directory - cp_r("#{upstream}/test/resolv", "test") - `git checkout ext/win32/resolv/depend` - when "win32-registry" - rm_rf(%w[ext/win32/lib/win32/registry.rb test/win32/test_registry.rb]) - cp_r("#{upstream}/lib/win32/registry.rb", "ext/win32/lib/win32") - cp_r("#{upstream}/test/win32/test_registry.rb", "test/win32") - cp_r("#{upstream}/win32-registry.gemspec", "ext/win32") - when "mmtk" - rm_rf("gc/mmtk") - cp_r("#{upstream}/gc/mmtk", "gc") - else - sync_lib gem, upstream + end + + # RubyGems/Bundler needs special care + if gem == "rubygems" + rubygems_do_fixup end check_prerelease_version(gem) @@ -649,11 +677,12 @@ def pickup_commit(gem, sha, edit) `git commit --amend --no-edit --all` end + # Update commit message to include links to the original commit puts "Update commit message: #{sha}" - repo, = REPOSITORIES[gem] + config = REPOSITORIES[gem] headers, orig = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) - message = message_filter(repo, sha, orig) + message = message_filter(config.upstream, sha, orig) IO.popen(%W[git commit --amend --no-edit -F -], "r+b") {|io| io.write(message) io.close_write @@ -672,7 +701,8 @@ def pickup_commit(gem, sha, edit) # @param ranges [Array] "before..after". Note that it will NOT sync "before" (but commits after that). # @param edit [TrueClass] Set true if you want to resolve conflicts. Obviously, update-default-gem.sh doesn't use this. def sync_default_gems_with_commits(gem, ranges, edit: nil) - repo, default_branch = REPOSITORIES[gem] + config = REPOSITORIES[gem] + repo, default_branch = config.upstream, config.branch puts "Sync #{repo} with commit history." # Fetch the repository to be synchronized @@ -739,9 +769,9 @@ def sync_lib(repo, upstream = nil) end def update_default_gems(gem, release: false) - - repository, default_branch = REPOSITORIES[gem] - author, repository = repository.split('/') + config = REPOSITORIES[gem] + author, repository = config.upstream.split('/') + default_branch = config.branch puts "Update #{author}/#{repository}" @@ -792,9 +822,9 @@ def update_default_gems(gem, release: false) when "list" ARGV.shift pattern = Regexp.new(ARGV.join('|')) - REPOSITORIES.each_pair do |name, (gem)| - next unless pattern =~ name or pattern =~ gem - printf "%-15s https://github.com/%s\n", name, gem + REPOSITORIES.each do |gem, config| + next unless pattern =~ gem or pattern =~ config.upstream + printf "%-15s https://github.com/%s\n", gem, config.upstream end when "rdoc-ref" ARGV.shift diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index b0c9638862a3d3..741fff973558e3 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -109,7 +109,16 @@ def setup git(*%W"config --global log.showSignature true") end @target = "sync-test" - SyncDefaultGems::REPOSITORIES[@target] = ["ruby/#{@target}", "default"] + SyncDefaultGems::REPOSITORIES[@target] = SyncDefaultGems.repo( + ["ruby/#{@target}", "default"], + [ + ["lib", "lib"], + ["test", "test"], + ], + exclude: [ + "test/fixtures/*", + ], + ) @sha = {} @origdir = Dir.pwd Dir.chdir(@testdir) From 85e0f8c8783f5366c9c848efbf37f72beb17f574 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Oct 2025 23:16:14 +0900 Subject: [PATCH 0830/2435] sync_default_gems.rb: update paths and then do cherry-pick Currently, we try to git cherry-pick the upstream commit and then resolve merge conflicts in the working tree with the help of Git's rename detection. By the nature of heuristics, it does not work reliably when the upstream adds or removes files. Instead, first prepare temporary commit objects with uninteresting files removed and file paths adjusted for ruby/ruby, and then cherry-pick it. The cherry-pick should succeed as long as the mapping rules are correct, the upstream does not contain a funny merge that strictly depends on merge order, and there are no local changes in ruby/ruby. --- tool/sync_default_gems.rb | 293 ++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 152 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 9e3a2532e0c1c3..2b2dfcbbb0a1b0 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -5,6 +5,7 @@ require 'fileutils' require "rbconfig" require "find" +require "tempfile" module SyncDefaultGems include FileUtils @@ -331,6 +332,10 @@ def replace_rdoc_ref_all result.inject(false) {|changed, file| changed | replace_rdoc_ref(file)} end + def replace_rdoc_ref_all_full + Dir.glob("**/*.{c,rb,rdoc}").inject(false) {|changed, file| changed | replace_rdoc_ref(file)} + end + def rubygems_do_fixup gemspec_content = File.readlines("lib/bundler/bundler.gemspec").map do |line| next if line =~ /LICENSE\.md/ @@ -419,30 +424,6 @@ def check_prerelease_version(gem) puts "#{gem}-#{spec.version} is not latest version of rubygems.org" if spec.version.to_s != latest_version end - def ignore_file_pattern_for(gem) - patterns = [] - - # Common patterns - patterns << %r[\A(?: - [^/]+ # top-level entries - |\.git.* - |bin/.* - |ext/.*\.java - |rakelib/.* - |test/(?:lib|fixtures)/.* - |tool/(?!bundler/).* - )\z]mx - - # Gem-specific patterns - case gem - when nil - end&.tap do |pattern| - patterns << pattern - end - - Regexp.union(*patterns) - end - def message_filter(repo, sha, log) unless repo.count("/") == 1 and /\A\S+\z/ =~ repo raise ArgumentError, "invalid repository: #{repo}" @@ -523,28 +504,9 @@ def commits_in_ranges(gem, repo, default_branch, ranges) #++ def resolve_conflicts(gem, sha, edit) - # Skip this commit if everything has been removed as `ignored_paths`. + # Discover unmerged files: any unstaged changes changes = porcelain_status() - if changes.empty? - puts "Skip empty commit #{sha}" - return false - end - - # We want to skip - # DD: deleted by both - # DU: deleted by us - deleted = changes.grep(/^D[DU] /) {$'} - system(*%W"git rm -f --", *deleted) unless deleted.empty? - - # Import UA: added by them - added = changes.grep(/^UA /) {$'} - system(*%W"git add --", *added) unless added.empty? - - # Discover unmerged files - # AU: unmerged, added by us - # UU: unmerged, both modified - # AA: unmerged, both added - conflict = changes.grep(/\A(?:A[AU]|UU) /) {$'} + conflict = changes.grep(/\A(?:.[^ ?]) /) {$'} # If -e option is given, open each conflicted file with an editor unless conflict.empty? if edit @@ -565,134 +527,159 @@ def resolve_conflicts(gem, sha, edit) return true end - def preexisting?(base, file) - system(*%w"git cat-file -e", "#{base}:#{file}", err: File::NULL) - end - - def filter_pickup_files(changed, ignore_file_pattern, base) - toplevels = {} - remove = [] - ignore = [] - changed = changed.reject do |f| - case - when toplevels.fetch(top = f[%r[\A[^/]+(?=/|\z)]m]) { - remove << top if toplevels[top] = !preexisting?(base, top) - } - # Remove any new top-level directories. - true - when ignore_file_pattern.match?(f) - # Forcibly reset any changes matching ignore_file_pattern. - (preexisting?(base, f) ? ignore : remove) << f - end + def collect_cacheinfo(tree) + cacheinfo = pipe_readlines(%W"git ls-tree -r -t -z #{tree}").filter_map do |line| + fields, path = line.split("\t", 2) + mode, type, object = fields.split(" ", 3) + next unless type == "blob" + [mode, type, object, path] end - return changed, remove, ignore end - def pickup_files(gem, changed, picked) - # Forcibly remove any files that we don't want to copy to this - # repository. - - ignore_file_pattern = ignore_file_pattern_for(gem) - - base = picked ? "HEAD~" : "HEAD" - changed, remove, ignore = filter_pickup_files(changed, ignore_file_pattern, base) + def rewrite_cacheinfo(gem, blobs) + config = REPOSITORIES[gem] + rewritten = [] + ignored = blobs.dup + ignored.delete_if do |mode, type, object, path| + newpath = config.rewrite_for_ruby(path) + next unless newpath + rewritten << [mode, type, object, newpath] + end + [rewritten, ignored] + end - unless remove.empty? - puts "Remove added files: #{remove.join(', ')}" - system(*%w"git rm -fr --", *remove) - if picked - system(*%w"git commit --amend --no-edit --", *remove, %i[out err] => File::NULL) - end - end + def make_commit_info(gem, sha) + config = REPOSITORIES[gem] + headers, orig = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) + /^author (?.+?) <(?.*?)> (?.+?)$/ =~ headers or + raise "unable to parse author info for commit #{sha}" + author = { + "GIT_AUTHOR_NAME" => author_name, + "GIT_AUTHOR_EMAIL" => author_email, + "GIT_AUTHOR_DATE" => author_date, + } + message = message_filter(config.upstream, sha, orig) + [author, message] + end - unless ignore.empty? - puts "Reset ignored files: #{ignore.join(', ')}" - system(*%W"git rm -r --", *ignore) - ignore.each {|f| system(*%W"git checkout -f", base, "--", f)} + def fixup_commit(gem, commit) + wt = File.join("tmp", "sync_default_gems-fixup-worktree") + if File.directory?(wt) + IO.popen(%W"git -C #{wt} clean -xdf", "rb", &:read) + IO.popen(%W"git -C #{wt} reset --hard #{commit}", "rb", &:read) + else + IO.popen(%W"git worktree remove --force #{wt}", "rb", err: File::NULL, &:read) + IO.popen(%W"git worktree add --detach #{wt} #{commit}", "rb", &:read) end + raise "git worktree prepare failed for commit #{commit}" unless $?.success? - if changed.empty? - return nil + Dir.chdir(wt) do + if gem == "rubygems" + rubygems_do_fixup + end + replace_rdoc_ref_all_full end - return changed + IO.popen(%W"git -C #{wt} add -u", "rb", &:read) + IO.popen(%W"git -C #{wt} commit --amend --no-edit", "rb", &:read) + IO.popen(%W"git -C #{wt} rev-parse HEAD", "rb", &:read).chomp end - def pickup_commit(gem, sha, edit) - # Attempt to cherry-pick a commit - result = IO.popen(%W"git cherry-pick #{sha}", "rb", &:read) - picked = $?.success? - if result =~ /nothing\ to\ commit/ - `git reset` - puts "Skip empty commit #{sha}" - return false - end + def make_and_fixup_commit(gem, original_commit, cacheinfo, parent: nil, message: nil, author: nil) + tree = Tempfile.create("sync_default_gems-#{gem}-index") do |f| + File.unlink(f.path) + IO.popen({"GIT_INDEX_FILE" => f.path}, + %W"git update-index --index-info", "wb", out: IO::NULL) do |io| + cacheinfo.each do |mode, type, object, path| + io.puts("#{mode} #{type} #{object}\t#{path}") + end + end + raise "git update-index failed" unless $?.success? - # Skip empty commits - if result.empty? - return false + IO.popen({"GIT_INDEX_FILE" => f.path}, %W"git write-tree --missing-ok", "rb", &:read).chomp end - if picked - changed = pipe_readlines(%w"git diff-tree --name-only -r -z HEAD~..HEAD --") - else - changed = pipe_readlines(%w"git diff --name-only -r -z HEAD --") - end + args = ["-m", message || "Rewriten commit for #{original_commit}"] + args += ["-p", parent] if parent + commit = IO.popen({**author}, %W"git commit-tree #{tree}" + args, "rb", &:read).chomp - # Pick up files to merge. - unless changed = pickup_files(gem, changed, picked) - puts "Skip commit #{sha} only for tools or toplevel" - if picked - `git reset --hard HEAD~` - else - `git cherry-pick --abort` - end - return false - end + # Apply changes that require a working tree + commit = fixup_commit(gem, commit) - # If the cherry-pick attempt failed, try to resolve conflicts. - # Skip the commit, if it contains unresolved conflicts or no files to pick up. - unless picked or resolve_conflicts(gem, sha, edit) - system(*%w"git --no-pager diff") if !picked && !edit # If failed, show `git diff` unless editing - `git reset` && `git checkout .` && `git clean -fd` # Clean up un-committed diffs - return picked || nil # Fail unless cherry-picked - end + commit + end - # Commit cherry-picked commit - if picked - system(*%w"git commit --amend --no-edit") - elsif porcelain_status().empty? - system(*%w"git cherry-pick --skip") + def rewrite_commit(gem, sha) + config = REPOSITORIES[gem] + author, message = make_commit_info(gem, sha) + new_blobs = collect_cacheinfo("#{sha}") + new_rewritten, new_ignored = rewrite_cacheinfo(gem, new_blobs) + + headers, orig_message = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) + first_parent = headers[/^parent (.{40})$/, 1] + unless first_parent + # Root commit, first time to sync this repo + return make_and_fixup_commit(gem, sha, new_rewritten, message: message, author: author) + end + + old_blobs = collect_cacheinfo(first_parent) + old_rewritten, old_ignored = rewrite_cacheinfo(gem, old_blobs) + if old_ignored != new_ignored + paths = (old_ignored + new_ignored - (old_ignored & new_ignored)) + .map {|*_, path| path}.uniq + puts "\e\[1mIgnoring file changes not in mappings: #{paths.join(" ")}\e\[0m" + end + changed_paths = (old_rewritten + new_rewritten - (old_rewritten & new_rewritten)) + .map {|*_, path| path}.uniq + if changed_paths.empty? + puts "Skip commit only for tools or toplevel" return false - else - system(*%w"git cherry-pick --continue --no-edit") - end or return nil - - # Amend the commit if RDoc references need to be replaced - head = log_format('%H', %W"-1 HEAD", &:read).chomp - system(*%w"git reset --quiet HEAD~ --") - amend = replace_rdoc_ref_all - system(*%W"git reset --quiet #{head} --") - if amend - `git commit --amend --no-edit --all` end + # Build commit objects from "cacheinfo" + new_parent = make_and_fixup_commit(gem, first_parent, old_rewritten) + new_commit = make_and_fixup_commit(gem, sha, new_rewritten, parent: new_parent, message: message, author: author) + puts "Created a temporary commit for cherry-pick: #{new_commit}" + new_commit + end - # Update commit message to include links to the original commit - puts "Update commit message: #{sha}" + def pickup_commit(gem, sha, edit) config = REPOSITORIES[gem] - headers, orig = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) - message = message_filter(config.upstream, sha, orig) - IO.popen(%W[git commit --amend --no-edit -F -], "r+b") {|io| - io.write(message) - io.close_write - io.read - } + + rewritten = rewrite_commit(gem, sha) + + # No changes remaining after rewriting + return false unless rewritten + + # Attempt to cherry-pick a commit + result = IO.popen(%W"git cherry-pick #{rewritten}", "rb", err: [:child, :out], &:read) unless $?.success? - puts "Failed to modify commit message of #{sha}" - return nil + if result =~ /The previous cherry-pick is now empty/ + system(*%w"git cherry-pick --skip") + puts "Skip empty commit #{sha}" + return false + end + + # If the cherry-pick attempt failed, try to resolve conflicts. + # Skip the commit, if it contains unresolved conflicts or no files to pick up. + unless resolve_conflicts(gem, sha, edit) + system(*%w"git --no-pager diff") if !edit # If failed, show `git diff` unless editing + `git reset` && `git checkout .` && `git clean -fd` # Clean up un-committed diffs + return nil # Fail unless cherry-picked + end + + # Commit cherry-picked commit + if porcelain_status().empty? + system(*%w"git cherry-pick --skip") + return false + else + system(*%w"git cherry-pick --continue --no-edit") + return nil unless $?.success? + end end + new_head = IO.popen(%W"git rev-parse HEAD", "rb", &:read).chomp + puts "Committed cherry-pick as #{new_head}" return true end @@ -727,22 +714,24 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil) puts "Try to pick these commits:" puts commits.map{|commit| commit.join(": ")} - puts "----" failed_commits = [] commits.each do |sha, subject| - puts "Pick #{sha} from #{repo}." + puts "----" + puts "Pick #{sha} #{subject}" case pickup_commit(gem, sha, edit) when false # skipped when nil - failed_commits << sha + failed_commits << [sha, subject] end end unless failed_commits.empty? puts "---- failed commits ----" - puts failed_commits + failed_commits.each do |sha, subject| + puts "#{sha} #{subject}" + end return false end return true From f979ef1fb34569cfa34f00691591feac58b27842 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 28 Oct 2025 21:55:09 +0900 Subject: [PATCH 0831/2435] sync_default_gems.rb: gracefully handle merge commits Find interesting commits by following parents instead of relying on "git log". If we encounter a merge commit that may contain a conflict resolution, fall back to cherry-picking the merge commit as a whole rather than replaying each individual commit. The sync commit will include a shortlog for the squashed commits in that case. --- tool/sync_default_gems.rb | 88 +++++++++++++++++++---------- tool/test/test_sync_default_gems.rb | 29 ++++++++++ 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 2b2dfcbbb0a1b0..03f9625bfa5cd9 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -424,7 +424,7 @@ def check_prerelease_version(gem) puts "#{gem}-#{spec.version} is not latest version of rubygems.org" if spec.version.to_s != latest_version end - def message_filter(repo, sha, log) + def message_filter(repo, sha, log, context: nil) unless repo.count("/") == 1 and /\A\S+\z/ =~ repo raise ArgumentError, "invalid repository: #{repo}" end @@ -458,14 +458,15 @@ def message_filter(repo, sha, log) end end commit_url = "#{repo_url}/commit/#{sha[0,10]}\n" + sync_note = context ? "#{commit_url}\n#{context}" : commit_url if log and !log.empty? log.sub!(/(?<=\n)\n+\z/, '') # drop empty lines at the last conv[log] log.sub!(/(?:(\A\s*)|\s*\n)(?=((?i:^Co-authored-by:.*\n?)+)?\Z)/) { - ($~.begin(1) ? "" : "\n\n") + commit_url + ($~.begin(2) ? "\n" : "") + ($~.begin(1) ? "" : "\n\n") + sync_note + ($~.begin(2) ? "\n" : "") } else - log = commit_url + log = sync_note end "#{subject}\n\n#{log}" end @@ -475,27 +476,51 @@ def log_format(format, args, &block) log --no-show-signature --format=#{format}] + args, "rb", &block) end - # Returns commit list as array of [commit_hash, subject]. - def commits_in_ranges(gem, repo, default_branch, ranges) - # If -a is given, discover all commits since the last picked commit - if ranges == true - pattern = "https://github\.com/#{Regexp.quote(repo)}/commit/([0-9a-f]+)$" - log = log_format('%B', %W"-E --grep=#{pattern} -n1 --", &:read) - ranges = ["#{log[%r[#{pattern}\n\s*(?i:co-authored-by:.*)*\s*\Z], 1]}..#{gem}/#{default_branch}"] - end + def commits_in_range(upto, exclude, toplevel:) + args = [upto, *exclude.map {|s|"^#{s}"}] + log_format('%H,%P,%s', %W"--first-parent" + args) do |f| + f.read.split("\n").reverse.flat_map {|commit| + hash, parents, subject = commit.split(',', 3) + parents = parents.split + + # Non-merge commit + if parents.size <= 1 + puts "#{hash} #{subject}" + next [[hash, subject]] + end - # Parse a given range with git log - ranges.flat_map do |range| - unless range.include?("..") - range = "#{range}~1..#{range}" - end + # Clean 2-parent merge commit: follow the other parent as long as it + # contains no potentially-non-clean merges + if parents.size == 2 && + IO.popen(%W"git diff-tree --remerge-diff #{hash}", "rb", &:read).empty? + puts "\e[2mChecking the other parent of #{hash} #{subject}\e[0m" + ret = catch(:quit) { + commits_in_range(parents[1], exclude + [parents[0]], toplevel: false) + } + next ret if ret + end - log_format('%H,%s', %W"#{range} --") do |f| - f.read.split("\n").reverse.map{|commit| commit.split(',', 2)} - end + unless toplevel + puts "\e[1mMerge commit with possible conflict resolution #{hash} #{subject}\e[0m" + throw :quit + end + + puts "#{hash} #{subject} " \ + "\e[1m[merge commit with possible conflicts, will do a squash merge]\e[0m" + [[hash, subject]] + } end end + # Returns commit list as array of [commit_hash, subject, sync_note]. + def commits_in_ranges(ranges) + ranges.flat_map do |range| + exclude, upto = range.include?("..") ? range.split("..", 2) : ["#{range}~1", range] + puts "Looking for commits in range #{exclude}..#{upto}" + commits_in_range(upto, exclude.empty? ? [] : [exclude], toplevel: true) + end.uniq + end + #-- # Following methods used by sync_default_gems_with_commits return # true: success @@ -558,7 +583,12 @@ def make_commit_info(gem, sha) "GIT_AUTHOR_EMAIL" => author_email, "GIT_AUTHOR_DATE" => author_date, } - message = message_filter(config.upstream, sha, orig) + context = nil + if /^parent (?.{40})\nparent .{40}$/ =~ headers + # Squashing a merge commit: keep authorship information + context = IO.popen(%W"git shortlog #{first_parent}..#{sha} --", "rb", &:read) + end + message = message_filter(config.upstream, sha, orig, context: context) [author, message] end @@ -683,9 +713,8 @@ def pickup_commit(gem, sha, edit) return true end - # NOTE: This method is also used by GitHub ruby/git.ruby-lang.org's bin/update-default-gem.sh # @param gem [String] A gem name, also used as a git remote name. REPOSITORIES converts it to the appropriate GitHub repository. - # @param ranges [Array] "before..after". Note that it will NOT sync "before" (but commits after that). + # @param ranges [Array, true] "commit", "before..after", or true. Note that it will NOT sync "before" (but commits after that). # @param edit [TrueClass] Set true if you want to resolve conflicts. Obviously, update-default-gem.sh doesn't use this. def sync_default_gems_with_commits(gem, ranges, edit: nil) config = REPOSITORIES[gem] @@ -700,21 +729,18 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil) end system(*%W"git fetch --no-tags --depth=#{FETCH_DEPTH} #{gem} #{default_branch}") - commits = commits_in_ranges(gem, repo, default_branch, ranges) - - # Ignore Merge commits and already-merged commits. - commits.delete_if do |sha, subject| - subject.start_with?("Merge", "Auto Merge") + # If -a is given, discover all commits since the last picked commit + if ranges == true + pattern = "https://github\.com/#{Regexp.quote(repo)}/commit/([0-9a-f]+)$" + log = log_format('%B', %W"-E --grep=#{pattern} -n1 --", &:read) + ranges = ["#{log[%r[#{pattern}\n\s*(?i:co-authored-by:.*)*\s*\Z], 1]}..#{gem}/#{default_branch}"] end - + commits = commits_in_ranges(ranges) if commits.empty? puts "No commits to pick" return true end - puts "Try to pick these commits:" - puts commits.map{|commit| commit.join(": ")} - failed_commits = [] commits.each do |sha, subject| puts "----" diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index 741fff973558e3..f50be036fe16fd 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -317,5 +317,34 @@ def test_delete_after_conflict assert_equal(":ok\n""Should.be_merged\n", File.read("src/lib/common.rb"), out) assert_not_operator(File, :exist?, "src/lib/bad.rb", out) end + + def test_squash_merge + # 2---. <- branch + # / \ + # 1---3---3'<- merge commit with conflict resolution + File.write("#@target/lib/conflict.rb", "# 1\n") + git(*%W"add lib/conflict.rb", chdir: @target) + git(*%W"commit -q -m", "Add conflict.rb", chdir: @target) + + git(*%W"checkout -q -b branch", chdir: @target) + File.write("#@target/lib/conflict.rb", "# 2\n") + File.write("#@target/lib/new.rb", "# new\n") + git(*%W"add lib/conflict.rb lib/new.rb", chdir: @target) + git(*%W"commit -q -m", "Commit in branch", chdir: @target) + + git(*%W"checkout -q default", chdir: @target) + File.write("#@target/lib/conflict.rb", "# 3\n") + git(*%W"add lib/conflict.rb", chdir: @target) + git(*%W"commit -q -m", "Commit in default", chdir: @target) + + # How can I suppress "Auto-merging ..." message from git merge? + git(*%W"merge -X ours -m", "Merge commit", "branch", chdir: @target, out: IO::NULL) + + out = assert_sync() + assert_equal("# 3\n", File.read("src/lib/conflict.rb"), out) + subject, body = top_commit("src", format: "%B").split("\n\n", 2) + assert_equal("[ruby/#@target] Merge commit", subject, out) + assert_includes(body, "Commit in branch", out) + end end if /darwin|linux/ =~ RUBY_PLATFORM end From 2172057f1b645e5a874898c8a83f5fa7f3ff6fcb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 5 Nov 2025 15:43:17 +0900 Subject: [PATCH 0832/2435] Use Ruby 3.4 for sync_default_gems.rb ``` tool/sync_default_gems.rb:177:in `block in ': undefined local variable or method `it' for SyncDefaultGems:Module (NameError) it.exclude << "lib/open3/jruby_windows.rb" ^^ from :90:in `tap' from tool/sync_default_gems.rb:176:in `' from tool/sync_default_gems.rb:10:in `
' ``` --- .github/workflows/sync_default_gems.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 3a811be18a3187..2f0b4d8865eb4f 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -32,6 +32,11 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + with: + ruby-version: '3.4' + bundler: none + - name: Run tool/sync_default_gems.rb id: sync run: | From 27b1500e7061a1dea35cf03a0254788075e9c9fd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 15:31:23 +0900 Subject: [PATCH 0833/2435] [ruby/strscan] [DOC] Add document of StringScanner::Error https://github.com/ruby/strscan/commit/16ec901356 --- ext/strscan/strscan.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 4746afd5178d7c..4fe10e1d138996 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -2190,6 +2190,13 @@ strscan_named_captures(VALUE self) Ruby Interface ======================================================================= */ +/* + * Document-class: StringScanner::Error + * + * The error class for StringScanner. + * See StringScanner#unscan. + */ + /* * Document-class: StringScanner * From 439ca0432e669f7cc4f884bcbafeef53aa948b93 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 14:30:42 +0900 Subject: [PATCH 0834/2435] [ruby/strscan] Deprecate constant `Id` `$Id$` is for RCS, CVS, and SVN; no information with GIT. https://github.com/ruby/strscan/commit/9e3db14fa2 --- ext/strscan/strscan.c | 1 + test/strscan/test_stringscanner.rb | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 4fe10e1d138996..8fa41e646bdf30 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -2233,6 +2233,7 @@ Init_strscan(void) tmp = rb_str_new2("$Id$"); rb_obj_freeze(tmp); rb_const_set(StringScanner, rb_intern("Id"), tmp); + rb_deprecate_constant(StringScanner, "Id"); rb_define_alloc_func(StringScanner, strscan_s_allocate); rb_define_private_method(StringScanner, "initialize", strscan_initialize, -1); diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb index 8218e5b6bebc85..dd3663ea6a2c90 100644 --- a/test/strscan/test_stringscanner.rb +++ b/test/strscan/test_stringscanner.rb @@ -107,11 +107,6 @@ def test_const_Version assert_equal(true, StringScanner::Version.frozen?) end - def test_const_Id - assert_instance_of(String, StringScanner::Id) - assert_equal(true, StringScanner::Id.frozen?) - end - def test_inspect str = 'test string'.dup s = create_string_scanner(str, false) From ae7415c27ec08dfc74346e6bf0ffc2e4636332c2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 19:04:12 +0900 Subject: [PATCH 0835/2435] [ruby/strscan] [DOC] no doc for internal methods https://github.com/ruby/strscan/commit/5614095d9c --- ext/strscan/strscan.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 8fa41e646bdf30..d63897dc61f35d 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -1278,6 +1278,7 @@ strscan_must_ascii_compat(VALUE str) rb_must_asciicompat(str); } +/* :nodoc: */ static VALUE strscan_scan_base10_integer(VALUE self) { @@ -1315,6 +1316,7 @@ strscan_scan_base10_integer(VALUE self) return strscan_parse_integer(p, 10, len); } +/* :nodoc: */ static VALUE strscan_scan_base16_integer(VALUE self) { From 946d2d036faee1eb1e37cb4c2bcad9feff9b5780 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 5 Nov 2025 15:11:05 +0900 Subject: [PATCH 0836/2435] Suppressing unused warnings --- tool/sync_default_gems.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 03f9625bfa5cd9..b2509f0062b0b5 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -381,7 +381,7 @@ def sync_default_gems(gem) end porcelain_status().each do |line| - /\A(?.)(?.) (?.*)\z/ =~ line or raise + /\A(?:.)(?:.) (?.*)\z/ =~ line or raise if config.excluded?(path) puts "Restoring excluded file: #{path}" IO.popen(%W"git checkout --" + [path], "rb", &:read) @@ -553,7 +553,7 @@ def resolve_conflicts(gem, sha, edit) end def collect_cacheinfo(tree) - cacheinfo = pipe_readlines(%W"git ls-tree -r -t -z #{tree}").filter_map do |line| + pipe_readlines(%W"git ls-tree -r -t -z #{tree}").filter_map do |line| fields, path = line.split("\t", 2) mode, type, object = fields.split(" ", 3) next unless type == "blob" @@ -640,12 +640,11 @@ def make_and_fixup_commit(gem, original_commit, cacheinfo, parent: nil, message: end def rewrite_commit(gem, sha) - config = REPOSITORIES[gem] author, message = make_commit_info(gem, sha) new_blobs = collect_cacheinfo("#{sha}") new_rewritten, new_ignored = rewrite_cacheinfo(gem, new_blobs) - headers, orig_message = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) + headers, _ = IO.popen(%W[git cat-file commit #{sha}], "rb", &:read).split("\n\n", 2) first_parent = headers[/^parent (.{40})$/, 1] unless first_parent # Root commit, first time to sync this repo @@ -674,8 +673,6 @@ def rewrite_commit(gem, sha) end def pickup_commit(gem, sha, edit) - config = REPOSITORIES[gem] - rewritten = rewrite_commit(gem, sha) # No changes remaining after rewriting From af6a6a2a62cec1e363b622cc801aa745e71977df Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Thu, 6 Nov 2025 00:35:37 +0900 Subject: [PATCH 0837/2435] Update power_assert to 3.0.1 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 3ff5e3f6b013f4..01929b290b2186 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -7,7 +7,7 @@ # if `revision` is not given, "v"+`version` or `version` will be used. minitest 5.26.0 https://github.com/minitest/minitest -power_assert 3.0.0 https://github.com/ruby/power_assert +power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml From 57040636df10cf46d8ad0c294dbbe3b5756e8c21 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 5 Nov 2025 15:36:26 +0000 Subject: [PATCH 0838/2435] Update bundled gems list as of 2025-11-05 --- NEWS.md | 5 +++-- gems/bundled_gems | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0aa4aa63b36dac..b22cf5c290e9ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,9 +213,9 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.26.0 -* power_assert 3.0.0 +* power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.0 +* test-unit 3.7.1 * rexml 3.4.4 * net-ftp 0.3.9 * net-imap 0.5.12 @@ -223,6 +223,7 @@ The following bundled gems are updated. * matrix 0.4.3 * prime 0.1.4 * rbs 3.9.5 +* typeprof 0.31.0 * debug 1.11.0 * base64 0.3.0 * bigdecimal 3.3.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 01929b290b2186..114e2f4e6c2de5 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.26.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.0 https://github.com/test-unit/test-unit +test-unit 3.7.1 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp @@ -19,7 +19,7 @@ net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime rbs 3.9.5 https://github.com/ruby/rbs -typeprof 0.30.1 https://github.com/ruby/typeprof +typeprof 0.31.0 https://github.com/ruby/typeprof debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m From 54907db8f3daa6d096e78e7eb78e515842c47789 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 5 Nov 2025 12:42:08 -0500 Subject: [PATCH 0839/2435] Fix ractor move of object with generic ivars (#15056) This bug was happening only when the `id2ref` table exists. We need to replace the generic fields before replacing the object id of the newly moved object. Fixes [Bug #21664] --- bootstraptest/test_ractor.rb | 13 +++++++++++++ ractor.c | 5 ++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 25af87b610ef83..834eb627ef0e90 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1969,6 +1969,19 @@ def ==(o) roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj } +# move object with generic ivars and existing id2ref table +# [Bug #21664] +assert_equal 'ok', %q{ + obj = [1] + obj.instance_variable_set("@field", :ok) + ObjectSpace._id2ref(obj.object_id) # build id2ref table + + ractor = Ractor.new { Ractor.receive } + ractor.send(obj, move: true) + obj = ractor.value + obj.instance_variable_get("@field") +} + # copy object with complex generic ivars assert_equal 'ok', %q{ # Make Array too_complex diff --git a/ractor.c b/ractor.c index 70594443d6ffbf..48fbf7cfb94404 100644 --- a/ractor.c +++ b/ractor.c @@ -1973,13 +1973,12 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data) rb_gc_writebarrier_remember(data->replacement); void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c - - rb_gc_obj_id_moved(data->replacement); - if (UNLIKELY(rb_obj_exivar_p(obj))) { rb_replace_generic_ivar(data->replacement, obj); } + rb_gc_obj_id_moved(data->replacement); + VALUE flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_PROMOTED); // Avoid mutations using bind_call, etc. From d3d2357a6c87328769ca7496bf8525e8072b1834 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 09:57:59 -0800 Subject: [PATCH 0840/2435] ZJIT: Run ruby-bench CI for macOS arm64 as well (#15040) --- .github/workflows/zjit-macos.yml | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index f687672ac7a9e3..85c4302737a9b2 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -150,6 +150,58 @@ jobs: working-directory: if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + # Separated from `make` job to avoid making it a required status check for now + ruby-bench: + strategy: + matrix: + include: + # Test --call-threshold=2 with 2 iterations in total + - ruby_opts: '--zjit-call-threshold=2' + bench_opts: '--warmup=1 --bench=1' + configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow + + runs-on: macos-14 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') + )}} + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: ./.github/actions/setup/macos + + - uses: ./.github/actions/setup/directories + with: + srcdir: src + builddir: build + makeup: true + + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix="$(pwd)/install" ${{ matrix.configure }} + + - run: make install + + - name: Checkout ruby-bench + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: ruby/ruby-bench + path: ruby-bench + + - name: Run ruby-bench + run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + working-directory: ruby-bench + + - uses: ./.github/actions/slack + with: + label: ruby-bench ${{ matrix.bench_opts }} ${{ matrix.ruby_opts }} + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} + defaults: run: working-directory: build From 7334244e001910ed1fc224cb7a59c7e9c7ae8d53 Mon Sep 17 00:00:00 2001 From: Sam Partington Date: Wed, 5 Nov 2025 18:00:57 +0000 Subject: [PATCH 0841/2435] [ruby/erb] Fix tag shown in example of ERB expression tag and execution tag (https://github.com/ruby/erb/pull/92) These were the wrong way around. https://github.com/ruby/erb/commit/50a5cd76fe --- lib/erb.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index d3bbc4979c9375..b1f4e9361b6384 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -60,7 +60,7 @@ # \ERB supports tags of three kinds: # # - [Expression tags][expression tags]: -# each begins with `'<%'`, ends with `'%>'`; contains a Ruby expression; +# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression; # in the result, the value of the expression replaces the entire tag: # # template = 'The magic word is <%= magic_word %>.' @@ -77,7 +77,7 @@ # ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." # # - [Execution tags][execution tags]: -# each begins with `'<%='`, ends with `'%>'`; contains Ruby code to be executed: +# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed: # # template = '<% File.write("t.txt", "Some stuff.") %>' # ERB.new(template).result From 242d8edbebc34f88313748f9fedf14367c6d409b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:23:35 -0800 Subject: [PATCH 0842/2435] Extend timeout for unstable tests https://github.com/ruby/ruby/actions/runs/19111531630/job/54609629054 --- test/ruby/test_autoload.rb | 6 ++++++ test/test_extlibs.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 607f0e3355efe9..7b6a0b5712556d 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -613,4 +613,10 @@ module SomeNamespace RUBY end end + + private + + def assert_separately(*args, **kwargs) + super(*args, **{ timeout: 60 }.merge(kwargs)) + end end diff --git a/test/test_extlibs.rb b/test/test_extlibs.rb index 8969c3c50fb4d7..122eca3f5c5f29 100644 --- a/test/test_extlibs.rb +++ b/test/test_extlibs.rb @@ -10,7 +10,7 @@ def self.check_existence(ext, add_msg = nil) add_msg = ". #{add_msg}" if add_msg log = "#{@extdir}/#{ext}/mkmf.log" define_method("test_existence_of_#{ext}") do - assert_separately([], <<-"end;", ignore_stderr: true) # do + assert_separately([], <<-"end;", ignore_stderr: true, timeout: 60) # do log = #{log.dump} msg = proc { "extension library `#{ext}' is not found#{add_msg}\n" << From df290e11d928e67b0ffdf3cc767e0d4ad6f01b29 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:28:18 -0800 Subject: [PATCH 0843/2435] Skip an unstable IO test for mswin https://github.com/ruby/ruby/actions/runs/19107764906/job/54596244201 --- bootstraptest/test_io.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstraptest/test_io.rb b/bootstraptest/test_io.rb index 4e5d6d59c9bd36..4081769a8c05ac 100644 --- a/bootstraptest/test_io.rb +++ b/bootstraptest/test_io.rb @@ -85,7 +85,7 @@ ARGF.set_encoding "foo" } -/freebsd/ =~ RUBY_PLATFORM or +/(freebsd|mswin)/ =~ RUBY_PLATFORM or 10.times do assert_normal_exit %q{ at_exit { p :foo } From d327eb6046ad9dc0bb6c24ceb23ce69061011164 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Wed, 5 Nov 2025 11:30:00 -0700 Subject: [PATCH 0844/2435] ZJIT: Track guard shape exit ratio (#15052) new ZJIT stats excerpt from liquid-runtime: ``` vm_read_from_parent_iseq_local_count: 10,909,753 guard_type_count: 45,109,441 guard_type_exit_ratio: 4.3% guard_shape_count: 15,272,133 guard_shape_exit_ratio: 20.1% code_region_bytes: 3,899,392 ``` lobsters ``` guard_type_count: 71,765,580 guard_type_exit_ratio: 4.3% guard_shape_count: 21,872,560 guard_shape_exit_ratio: 8.0% ``` railsbench ``` guard_type_count: 117,661,124 guard_type_exit_ratio: 0.7% guard_shape_count: 28,032,665 guard_shape_exit_ratio: 5.1% ``` shipit ``` guard_type_count: 106,195,615 guard_type_exit_ratio: 3.5% guard_shape_count: 33,672,673 guard_shape_exit_ratio: 10.1% ``` --- zjit.rb | 5 ++++- zjit/src/codegen.rs | 1 + zjit/src/stats.rs | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index cfe8bcd6e2cfb1..5156c72f43410d 100644 --- a/zjit.rb +++ b/zjit.rb @@ -152,6 +152,7 @@ def stats_string stats = self.stats stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 + stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) @@ -206,6 +207,8 @@ def stats_string :guard_type_count, :guard_type_exit_ratio, + :guard_shape_count, + :guard_shape_exit_ratio, :code_region_bytes, :side_exit_count, @@ -242,7 +245,7 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) case key when :ratio_in_zjit value = '%0.1f%%' % value - when :guard_type_exit_ratio + when :guard_type_exit_ratio, :guard_shape_exit_ratio value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 01212ac88cdbfa..364b9225fe07e6 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -964,6 +964,7 @@ fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: Opnd, right: } fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: ShapeId, state: &FrameState) -> Opnd { + gen_incr_counter(asm, Counter::guard_shape_count); let shape_id_offset = unsafe { rb_shape_id_offset() }; let val = asm.load(val); let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, val, shape_id_offset); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 30a09669940679..aea28db28b8804 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -286,6 +286,7 @@ make_counters! { // The number of times we ran a dynamic check guard_type_count, + guard_shape_count, } /// Increase a counter by a specified amount From bf0331b907ae7f25b9464081682218ab74e3ccb6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:44:40 -0800 Subject: [PATCH 0845/2435] ZJIT: Add zjit_alloc_bytes and total_mem_bytes stats (#15059) --- zjit.rb | 3 +++ zjit/src/stats.rs | 7 +++++-- zjit/src/virtualmem.rs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zjit.rb b/zjit.rb index 5156c72f43410d..3ee9880a5dde29 100644 --- a/zjit.rb +++ b/zjit.rb @@ -211,6 +211,9 @@ def stats_string :guard_shape_exit_ratio, :code_region_bytes, + :zjit_alloc_bytes, + :total_mem_bytes, + :side_exit_count, :total_insn_count, :vm_insn_count, diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index aea28db28b8804..12e6e3aa8d0f87 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -550,7 +550,10 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> } // Memory usage stats - set_stat_usize!(hash, "code_region_bytes", ZJITState::get_code_block().mapped_region_size()); + let code_region_bytes = ZJITState::get_code_block().mapped_region_size(); + set_stat_usize!(hash, "code_region_bytes", code_region_bytes); + set_stat_usize!(hash, "zjit_alloc_bytes", zjit_alloc_bytes()); + set_stat_usize!(hash, "total_mem_bytes", code_region_bytes + zjit_alloc_bytes()); // End of default stats. Every counter beyond this is provided only for --zjit-stats. if !get_option!(stats) { @@ -645,7 +648,7 @@ pub fn with_time_stat(counter: Counter, func: F) -> R where F: FnOnce() -> } /// The number of bytes ZJIT has allocated on the Rust heap. -pub fn zjit_alloc_size() -> usize { +pub fn zjit_alloc_bytes() -> usize { jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) } diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 2717dfcf225f23..770fbfba47d1c8 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -5,7 +5,7 @@ use std::ptr::NonNull; use crate::cruby::*; -use crate::stats::zjit_alloc_size; +use crate::stats::zjit_alloc_bytes; pub type VirtualMem = VirtualMemory; @@ -199,7 +199,7 @@ impl VirtualMemory { // Ignore zjit_alloc_size() if self.memory_limit_bytes is None for testing let mut required_region_bytes = page_addr + page_size - start as usize; if self.memory_limit_bytes.is_some() { - required_region_bytes += zjit_alloc_size(); + required_region_bytes += zjit_alloc_bytes(); } assert!((start..=whole_region_end).contains(&mapped_region_end)); From 4f56abbb0a3e25972b246fa516718520e5cd27e9 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Wed, 5 Nov 2025 12:08:28 -0700 Subject: [PATCH 0846/2435] ZJIT: Don't side-exit on VM_CALL_KWARG just SendWithoutBlock (#15065) --- zjit/src/hir.rs | 6 +++--- zjit/src/hir/opt_tests.rs | 10 ++++++++-- zjit/src/hir/tests.rs | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b26d2ffa047c30..9762a87dd454cd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2286,7 +2286,7 @@ impl Function { }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site - // If the call site info indicates that the `Function` has `VM_CALL_ARGS_SPLAT` set, then + // If the call site info indicates that the `Function` has overly complex arguments, then // do not optimize into a `SendWithoutBlockDirect`. let flags = unsafe { rb_vm_ci_flag(ci) }; if unspecializable_call_type(flags) { @@ -4265,13 +4265,13 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { /// If we can't handle the type of send (yet), bail out. fn unhandled_call_type(flags: u32) -> Result<(), CallType> { - if (flags & VM_CALL_KWARG) != 0 { return Err(CallType::Kwarg); } if (flags & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } Ok(()) } -/// If a given call uses splatting or block arguments, then we won't specialize. +/// If a given call uses overly complex arguments, then we won't specialize. fn unspecializable_call_type(flags: u32) -> bool { + ((flags & VM_CALL_KWARG) != 0) || ((flags & VM_CALL_ARGS_SPLAT) != 0) || ((flags & VM_CALL_ARGS_BLOCKARG) != 0) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 6fc890f385a46c..f824351eca7551 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2659,7 +2659,10 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + IncrCounter complex_arg_pass_caller_kwarg + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 "); } @@ -2682,7 +2685,10 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + IncrCounter complex_arg_pass_caller_kwarg + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index fe67779d85a808..a8738a07157b16 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1572,7 +1572,9 @@ pub mod hir_build_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v13:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) + v15:BasicObject = SendWithoutBlock v8, :foo, v13 + CheckInterrupts + Return v15 "); } From 02267417da32bf480f7050ff2ab182076aa0ad83 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 5 Nov 2025 15:01:17 -0500 Subject: [PATCH 0847/2435] ZJIT: Profile specific objects for invokeblock (#15051) I made a special kind of `ProfiledType` that looks at specific objects, not just their classes/shapes (https://github.com/ruby/ruby/pull/15051). Then I profiled some of our benchmarks. For lobsters: ``` Top-6 invokeblock handler (100.0% of total 1,064,155): megamorphic: 494,931 (46.5%) monomorphic_iseq: 337,171 (31.7%) polymorphic: 113,381 (10.7%) monomorphic_ifunc: 52,260 ( 4.9%) monomorphic_other: 38,970 ( 3.7%) no_profiles: 27,442 ( 2.6%) ``` For railsbench: ``` Top-6 invokeblock handler (100.0% of total 2,529,104): monomorphic_iseq: 834,452 (33.0%) megamorphic: 818,347 (32.4%) polymorphic: 632,273 (25.0%) monomorphic_ifunc: 224,243 ( 8.9%) monomorphic_other: 19,595 ( 0.8%) no_profiles: 194 ( 0.0%) ``` For shipit: ``` Top-6 invokeblock handler (100.0% of total 2,104,148): megamorphic: 1,269,889 (60.4%) polymorphic: 411,475 (19.6%) no_profiles: 173,367 ( 8.2%) monomorphic_other: 118,619 ( 5.6%) monomorphic_iseq: 84,891 ( 4.0%) monomorphic_ifunc: 45,907 ( 2.2%) ``` Seems like a monomorphic case for a specific ISEQ actually isn't a bad way of going about this, at least to start... --- insns.def | 1 + vm_insnhelper.c | 12 ++++++++ zjit.c | 4 +++ zjit.rb | 1 + zjit/bindgen/src/main.rs | 2 ++ zjit/src/cruby_bindings.inc.rs | 54 +++++++++++++++++++--------------- zjit/src/distribution.rs | 8 +++++ zjit/src/hir.rs | 28 ++++++++++++++++++ zjit/src/profile.rs | 30 +++++++++++++++++++ zjit/src/stats.rs | 7 +++++ 10 files changed, 124 insertions(+), 23 deletions(-) diff --git a/insns.def b/insns.def index ce358da28575ed..8c16a67717bbb0 100644 --- a/insns.def +++ b/insns.def @@ -1137,6 +1137,7 @@ invokeblock // attr bool handles_sp = true; // attr rb_snum_t sp_inc = sp_inc_of_invokeblock(cd->ci); // attr rb_snum_t comptime_sp_inc = sp_inc_of_invokeblock(ci); +// attr bool zjit_profile = true; { VALUE bh = VM_BLOCK_HANDLER_NONE; val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index e3ae25b176c749..e1ec5e63ec9653 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5497,6 +5497,12 @@ vm_invoke_proc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, return vm_invoke_block(ec, reg_cfp, calling, ci, is_lambda, block_handler); } +enum rb_block_handler_type +rb_vm_block_handler_type(VALUE block_handler) +{ + return vm_block_handler_type(block_handler); +} + static inline VALUE vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci, @@ -6059,6 +6065,12 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv } } +VALUE +rb_vm_get_block_handler(rb_control_frame_t *reg_cfp) +{ + return VM_CF_BLOCK_HANDLER(reg_cfp); +} + static VALUE vm_invokeblock_i(struct rb_execution_context_struct *ec, struct rb_control_frame_struct *reg_cfp, diff --git a/zjit.c b/zjit.c index fac087a605f4d1..72e6fe14241ef4 100644 --- a/zjit.c +++ b/zjit.c @@ -301,6 +301,10 @@ rb_zjit_class_has_default_allocator(VALUE klass) return alloc == rb_class_allocate_instance; } + +VALUE rb_vm_get_block_handler(rb_control_frame_t *reg_cfp); +enum rb_block_handler_type rb_vm_block_handler_type(VALUE block_handler); + // Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them. VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key); diff --git a/zjit.rb b/zjit.rb index 3ee9880a5dde29..c59cce41dd7d70 100644 --- a/zjit.rb +++ b/zjit.rb @@ -164,6 +164,7 @@ def stats_string print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can # use multiple complex features, a decrease in this number does not diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 76f04f4369e3da..bbb3b54d6c873c 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -399,6 +399,8 @@ fn main() { .allowlist_function("rb_yarv_str_eql_internal") .allowlist_function("rb_str_neq_internal") .allowlist_function("rb_yarv_ary_entry_internal") + .allowlist_function("rb_vm_get_block_handler") + .allowlist_function("rb_vm_block_handler_type") .allowlist_function("rb_FL_TEST") .allowlist_function("rb_FL_TEST_RAW") .allowlist_function("rb_RB_TYPE_P") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index eb8ffcd1580e8c..dc9d0d144c1259 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -571,6 +571,11 @@ pub struct rb_captured_block__bindgen_ty_1 { pub val: __BindgenUnionField, pub bindgen_union_field: u64, } +pub const block_handler_type_iseq: rb_block_handler_type = 0; +pub const block_handler_type_ifunc: rb_block_handler_type = 1; +pub const block_handler_type_symbol: rb_block_handler_type = 2; +pub const block_handler_type_proc: rb_block_handler_type = 3; +pub type rb_block_handler_type = u32; pub const block_type_iseq: rb_block_type = 0; pub const block_type_ifunc: rb_block_type = 1; pub const block_type_symbol: rb_block_type = 2; @@ -1047,29 +1052,30 @@ pub const YARVINSN_zjit_send: ruby_vminsn_type = 219; pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 220; pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 221; pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 222; -pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 223; -pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 224; -pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 225; -pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 226; -pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 227; -pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 228; -pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 229; -pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 230; -pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 231; -pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 232; -pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 233; -pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 236; -pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 237; -pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 238; -pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 239; -pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 240; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 241; -pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 242; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 243; -pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 244; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 245; +pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 223; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 224; +pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 225; +pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 226; +pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 227; +pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 228; +pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 229; +pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 230; +pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 231; +pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 232; +pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 244; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 245; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 246; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), @@ -1333,6 +1339,8 @@ unsafe extern "C" { pub fn rb_zjit_class_initialized_p(klass: VALUE) -> bool; pub fn rb_zjit_class_get_alloc_func(klass: VALUE) -> rb_alloc_func_t; pub fn rb_zjit_class_has_default_allocator(klass: VALUE) -> bool; + pub fn rb_vm_get_block_handler(reg_cfp: *mut rb_control_frame_t) -> VALUE; + pub fn rb_vm_block_handler_type(block_handler: VALUE) -> rb_block_handler_type; pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; diff --git a/zjit/src/distribution.rs b/zjit/src/distribution.rs index 7a496ffd8dfc09..2c6ffb3ae6fff0 100644 --- a/zjit/src/distribution.rs +++ b/zjit/src/distribution.rs @@ -114,10 +114,18 @@ impl Distributi self.kind == DistributionKind::Monomorphic } + pub fn is_polymorphic(&self) -> bool { + self.kind == DistributionKind::Polymorphic + } + pub fn is_skewed_polymorphic(&self) -> bool { self.kind == DistributionKind::SkewedPolymorphic } + pub fn is_megamorphic(&self) -> bool { + self.kind == DistributionKind::Megamorphic + } + pub fn is_skewed_megamorphic(&self) -> bool { self.kind == DistributionKind::SkewedMegamorphic } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9762a87dd454cd..449047d0dfd72c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4407,6 +4407,34 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // profiled cfp->self. if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable { profiles.profile_self(&exit_state, self_param); + } else if opcode == YARVINSN_invokeblock || opcode == YARVINSN_trace_invokeblock { + if get_option!(stats) { + let iseq_insn_idx = exit_state.insn_idx; + if let Some(operand_types) = profiles.payload.profile.get_operand_types(iseq_insn_idx) { + if let [self_type_distribution] = &operand_types[..] { + let summary = TypeDistributionSummary::new(&self_type_distribution); + if summary.is_monomorphic() { + let obj = summary.bucket(0).class(); + let bh_type = unsafe { rb_vm_block_handler_type(obj) }; + if bh_type == block_handler_type_iseq { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_iseq)); + } else if bh_type == block_handler_type_ifunc { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_ifunc)); + } else { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_other)); + } + } else if summary.is_skewed_polymorphic() || summary.is_polymorphic() { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_polymorphic)); + } else if summary.is_skewed_megamorphic() || summary.is_megamorphic() { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_megamorphic)); + } else { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_no_profiles)); + } + } else { + fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_no_profiles)); + } + } + } } else { profiles.profile_stack(&exit_state); } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 08fdf3eb97a090..c58999668e59cf 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -43,6 +43,10 @@ impl Profiler { fn peek_at_self(&self) -> VALUE { unsafe { rb_get_cfp_self(self.cfp) } } + + fn peek_at_block_handler(&self) -> VALUE { + unsafe { rb_vm_get_block_handler(self.cfp) } + } } /// API called from zjit_* instruction. opcode is the bare (non-zjit_*) instruction. @@ -83,6 +87,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_length => profile_operands(profiler, profile, 1), YARVINSN_opt_size => profile_operands(profiler, profile, 1), YARVINSN_opt_succ => profile_operands(profiler, profile, 1), + YARVINSN_invokeblock => profile_block_handler(profiler, profile), YARVINSN_opt_send_without_block | YARVINSN_send => { let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr(); let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -135,6 +140,17 @@ fn profile_self(profiler: &mut Profiler, profile: &mut IseqProfile) { types[0].observe(ty); } +fn profile_block_handler(profiler: &mut Profiler, profile: &mut IseqProfile) { + let types = &mut profile.opnd_types[profiler.insn_idx]; + if types.is_empty() { + types.resize(1, TypeDistribution::new()); + } + let obj = profiler.peek_at_block_handler(); + let ty = ProfiledType::object(obj); + unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + types[0].observe(ty); +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Flags(u32); @@ -147,6 +163,8 @@ impl Flags { const IS_T_OBJECT: u32 = 1 << 2; /// Object is a struct with embedded fields const IS_STRUCT_EMBEDDED: u32 = 1 << 3; + /// Set if the ProfiledType is used for profiling specific objects, not just classes/shapes + const IS_OBJECT_PROFILING: u32 = 1 << 4; pub fn none() -> Self { Self(Self::NONE) } @@ -155,6 +173,7 @@ impl Flags { pub fn is_embedded(self) -> bool { (self.0 & Self::IS_EMBEDDED) != 0 } pub fn is_t_object(self) -> bool { (self.0 & Self::IS_T_OBJECT) != 0 } pub fn is_struct_embedded(self) -> bool { (self.0 & Self::IS_STRUCT_EMBEDDED) != 0 } + pub fn is_object_profiling(self) -> bool { (self.0 & Self::IS_OBJECT_PROFILING) != 0 } } /// opt_send_without_block/opt_plus/... should store: @@ -182,6 +201,14 @@ impl Default for ProfiledType { } impl ProfiledType { + /// Profile the object itself + fn object(obj: VALUE) -> Self { + let mut flags = Flags::none(); + flags.0 |= Flags::IS_OBJECT_PROFILING; + Self { class: obj, shape: INVALID_SHAPE_ID, flags } + } + + /// Profile the class and shape of the given object fn new(obj: VALUE) -> Self { if obj == Qfalse { return Self { class: unsafe { rb_cFalseClass }, @@ -251,6 +278,9 @@ impl ProfiledType { } pub fn is_string(&self) -> bool { + if self.flags.is_object_profiling() { + panic!("should not call is_string on object-profiled ProfiledType"); + } // Fast paths for immediates and exact-class if self.flags.is_immediate() { return false; diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 12e6e3aa8d0f87..fbfac7b42990ee 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -287,6 +287,13 @@ make_counters! { // The number of times we ran a dynamic check guard_type_count, guard_shape_count, + + invokeblock_handler_monomorphic_iseq, + invokeblock_handler_monomorphic_ifunc, + invokeblock_handler_monomorphic_other, + invokeblock_handler_polymorphic, + invokeblock_handler_megamorphic, + invokeblock_handler_no_profiles, } /// Increase a counter by a specified amount From 00c9a21ccef939cdad9b6abef2cd90c9dbd8c1c6 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Wed, 5 Nov 2025 18:29:38 -0700 Subject: [PATCH 0848/2435] [DOC] Update glossary (#15070) --- doc/contributing/glossary.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/contributing/glossary.md b/doc/contributing/glossary.md index fb5570f5e2562b..3ec9796147ddc7 100644 --- a/doc/contributing/glossary.md +++ b/doc/contributing/glossary.md @@ -4,15 +4,17 @@ Just a list of acronyms I've run across in the Ruby source code and their meanin | Term | Definition | | --- | -----------| +| `bmethod` | Method defined by `define_method() {}` (a Block that runs as a Method). | | `BIN` | Basic Instruction Name. Used as a macro to reference the YARV instruction. Converts pop into YARVINSN_pop. | | `bop` | Basic Operator. Relates to methods like `Integer` plus and minus which can be optimized as long as they haven't been redefined. | | `cc` | Call Cache. An inline cache structure for the call site. Stored in the `cd` | | `cd` | Call Data. A data structure that points at the `ci` and the `cc`. `iseq` objects points at the `cd`, and access call information and call caches via this structure | | CFG | Control Flow Graph. Representation of the program where all control-flow and data dependencies have been made explicit by unrolling the stack and local variables. | | `cfp`| Control Frame Pointer. Represents a Ruby stack frame. Calling a method pushes a new frame (cfp), returning pops a frame. Points at the `pc`, `sp`, `ep`, and the corresponding `iseq`| -| `ci` | Call Information. Refers to an `rb_callinfo` struct. Contains call information about the call site, including number of parameters to be passed, whether it they are keyword arguments or not, etc. Used in conjunction with the `cc` and `cd`. | +| `ci` | Call Information. Refers to an `rb_callinfo` struct. Contains call information about the call site, including number of parameters to be passed, whether they are keyword arguments or not, etc. Used in conjunction with the `cc` and `cd`. | +| `cme` | Callable Method Entry. Refers to the `rb_callable_method_entry_t` struct, the internal representation of a Ruby method that has `defined_class` and `owner` set and is ready for dispatch. | | `cref` | Class reference. A structure pointing to the class reference where `klass_or_self`, visibility scope, and refinements are stored. It also stores a pointer to the next class in the hierarchy referenced by `rb_cref_struct * next`. The Class reference is lexically scoped. | -| CRuby | Implementation of Ruby written in C | +| CRuby | Reference implementation of Ruby written in C | | `cvar` | Class Variable. Refers to a Ruby class variable like `@@foo` | | `dvar` | Dynamic Variable. Used by the parser to refer to local variables that are defined outside of the current lexical scope. For example `def foo; bar = 1; -> { p bar }; end` the "bar" inside the block is a `dvar` | | `ec` | Execution Context. The top level VM context, points at the current `cfp` | @@ -36,9 +38,10 @@ Just a list of acronyms I've run across in the Ruby source code and their meanin | `snt` | Shared Native Thread. OS thread on which many ruby threads can run. Ruby threads from different ractors can even run on the same SNT. Ruby threads can switch SNTs when they context switch. SNTs are used in the M:N threading model. By default, non-main ractors use this model. | `dnt` | Dedicated Native Thread. OS thread on which only one ruby thread can run. The ruby thread always runs on that same OS thread. DNTs are used in the 1:1 threading model. By default, the main ractor uses this model. | `sp` | Stack Pointer. The top of the stack. The VM executes instructions in the `iseq` and instructions will push and pop values on the stack. The VM updates the `sp` on the `cfp` to point at the top of the stack| +| ST table | ST table is the main C implementation of a hash (smaller Ruby hashes may be backed by AR tables). | | `svar` | Special Variable. Refers to special local variables like `$~` and `$_`. See the `getspecial` instruction in `insns.def` | | `VALUE` | VALUE is a pointer to a ruby object from the Ruby C code. | -| VM | Virtual Machine. In MRI's case YARV (Yet Another Ruby VM) +| VM | Virtual Machine. In MRI's case YARV (Yet Another Ruby VM) | WB | Write Barrier. To do with GC write barriers | | WC | Wild Card. As seen in instructions like `getlocal_WC_0`. It means this instruction takes a "wild card" for the parameter (in this case an index for a local) | | YARV | Yet Another Ruby VM. The virtual machine that CRuby uses | From 6014ed99680fd3beb013dd8564f81f6c2f2a9f34 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 2 Nov 2025 13:45:26 -0500 Subject: [PATCH 0849/2435] Remove dead rb_hash_dump --- hash.c | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/hash.c b/hash.c index 97a303c4d5851c..a8be0bad75f675 100644 --- a/hash.c +++ b/hash.c @@ -492,37 +492,6 @@ RHASH_AR_TABLE_BOUND(VALUE h) #if HASH_DEBUG #define hash_verify(hash) hash_verify_(hash, __FILE__, __LINE__) -void -rb_hash_dump(VALUE hash) -{ - rb_obj_info_dump(hash); - - if (RHASH_AR_TABLE_P(hash)) { - unsigned i, bound = RHASH_AR_TABLE_BOUND(hash); - - fprintf(stderr, " size:%u bound:%u\n", - RHASH_AR_TABLE_SIZE(hash), bound); - - for (i=0; ikey; - v = pair->val; - fprintf(stderr, " %d key:%s val:%s hint:%02x\n", i, - rb_raw_obj_info(b1, 0x100, k), - rb_raw_obj_info(b2, 0x100, v), - ar_hint(hash, i)); - } - else { - fprintf(stderr, " %d empty\n", i); - } - } - } -} - static VALUE hash_verify_(VALUE hash, const char *file, int line) { From 8c95c9d5ae6ad9b4920e06669d900f9df4cc2711 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 19:20:13 +0900 Subject: [PATCH 0850/2435] Dispatch by platform at load `RUBY_PLATFORM` should be invariant within the same process. --- ext/socket/lib/socket.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 1e30861efaeb3f..f01616bf5aea76 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -1569,13 +1569,16 @@ def self.unix_server_socket(path) end end - class << self - private - - def unix_socket_abstract_name?(path) - /linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path + if RUBY_PLATFORM.include?("linux") + def self.unix_socket_abstract_name?(path) + path.empty? or path.start_with?("\0") + end + else + def self.unix_socket_abstract_name?(path) + false end end + private_class_method :unix_socket_abstract_name? # creates a UNIX socket server on _path_. # It calls the block for each socket accepted. From 89056f4a86e51226f72d88e8eb40084910e57819 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 5 Nov 2025 19:25:46 +0900 Subject: [PATCH 0851/2435] [DOC] Stop documentation for internals Stop documentation for undocumented private constants and private class methods. And align private method definition styles. Also `Socket.tcp_with_fast_fallback` looks private as well as `Socket.tcp_without_fast_fallback`. --- ext/socket/lib/socket.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index f01616bf5aea76..9862c92c0b6591 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -599,6 +599,7 @@ def accept_nonblock(exception: true) __accept_nonblock(exception) end + # :stopdoc: RESOLUTION_DELAY = 0.05 private_constant :RESOLUTION_DELAY @@ -616,6 +617,7 @@ def accept_nonblock(exception: true) IPV6_ADDRESS_FORMAT = /\A(?i:(?:(?:[0-9A-F]{1,4}:){7}(?:[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){6}(?:[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){5}(?:(?::[0-9A-F]{1,4}){1,2}|:(?:[0-9A-F]{1,4}:){1,4}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){4}(?:(?::[0-9A-F]{1,4}){1,3}|:(?:[0-9A-F]{1,4}:){1,3}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){3}(?:(?::[0-9A-F]{1,4}){1,4}|:(?:[0-9A-F]{1,4}:){1,2}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){2}(?:(?::[0-9A-F]{1,4}){1,5}|:(?:[0-9A-F]{1,4}:)[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){1}(?:(?::[0-9A-F]{1,4}){1,6}|:(?:[0-9A-F]{1,4}:){0,5}[0-9A-F]{1,4}|:)|(?:::(?:[0-9A-F]{1,4}:){0,7}[0-9A-F]{1,4}|::)))(?:%.+)?\z/ private_constant :IPV6_ADDRESS_FORMAT + # :startdoc: # :call-seq: # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } @@ -680,6 +682,7 @@ def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: ni end end + # :stopdoc: def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) if local_host || local_port local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout) @@ -923,6 +926,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connecting_socket.close end end + private_class_method :tcp_with_fast_fallback def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) last_error = nil @@ -1106,7 +1110,6 @@ def any_unresolved_family? end private_constant :HostnameResolutionStore - # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) sockets = [] begin @@ -1139,9 +1142,7 @@ def self.ip_sockets_port0(ai_list, reuseaddr) end sockets end - class << self - private :ip_sockets_port0 - end + private_class_method :ip_sockets_port0 def self.tcp_server_sockets_port0(host) ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE) @@ -1569,6 +1570,7 @@ def self.unix_server_socket(path) end end + # :stopdoc: if RUBY_PLATFORM.include?("linux") def self.unix_socket_abstract_name?(path) path.empty? or path.start_with?("\0") @@ -1579,6 +1581,7 @@ def self.unix_socket_abstract_name?(path) end end private_class_method :unix_socket_abstract_name? + # :startdoc: # creates a UNIX socket server on _path_. # It calls the block for each socket accepted. From 2612915c3489c76fcb3828eefd3b29fdf30a9a5e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 24 Jun 2025 19:07:18 +0900 Subject: [PATCH 0852/2435] [ruby/openssl] digest: refactor tests for name aliases Use explicit strings instead of relying on OpenSSL::ASN1::ObjectId methods. It is reduced to just SHA-256 because testing other algorithms does not improve test coverage for ruby/openssl. https://github.com/ruby/openssl/commit/dcfd2e7b97 --- test/openssl/test_digest.rb | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 5b4eb3c74c440a..567cebf4b72b8b 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -62,8 +62,17 @@ def test_digest_constants end def test_digest_by_oid_and_name - check_digest(OpenSSL::ASN1::ObjectId.new("MD5")) - check_digest(OpenSSL::ASN1::ObjectId.new("SHA1")) + # SHA256 + o1 = OpenSSL::Digest.digest("SHA256", "") + o2 = OpenSSL::Digest.digest("sha256", "") + assert_equal(o1, o2) + o3 = OpenSSL::Digest.digest("2.16.840.1.101.3.4.2.1", "") + assert_equal(o1, o3) + + # An alias for SHA256 recognized by EVP_get_digestbyname(), but not by + # EVP_MD_fetch() + o4 = OpenSSL::Digest.digest("RSA-SHA256", "") + assert_equal(o1, o4) end def encode16(str) @@ -109,13 +118,6 @@ def test_sha3 assert_equal(s512, OpenSSL::Digest.hexdigest('SHA3-512', "")) end - def test_digest_by_oid_and_name_sha2 - check_digest(OpenSSL::ASN1::ObjectId.new("SHA224")) - check_digest(OpenSSL::ASN1::ObjectId.new("SHA256")) - check_digest(OpenSSL::ASN1::ObjectId.new("SHA384")) - check_digest(OpenSSL::ASN1::ObjectId.new("SHA512")) - end - def test_openssl_digest assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5") @@ -132,17 +134,6 @@ def test_digests assert_include digests, "sha256" assert_include digests, "sha512" end - - private - - def check_digest(oid) - d = OpenSSL::Digest.new(oid.sn) - assert_not_nil(d) - d = OpenSSL::Digest.new(oid.ln) - assert_not_nil(d) - d = OpenSSL::Digest.new(oid.oid) - assert_not_nil(d) - end end end From 18ab5023b63af88eddb083e89e19224e9f25af4d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 3 Nov 2025 18:31:09 +0900 Subject: [PATCH 0853/2435] [ruby/openssl] digest: raise DigestError for unsupported algorithm name We generally raise OpenSSL::OpenSSLError or its subclass for errors originating from the OpenSSL library, which may include extra details appended by ossl_raise(). https://github.com/ruby/openssl/commit/9427a05ce5 --- ext/openssl/ossl_digest.c | 5 +++-- test/openssl/test_digest.rb | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index 329de6c1bab078..80cc041734cae0 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -56,8 +56,9 @@ ossl_evp_get_digestbyname(VALUE obj) md = EVP_get_digestbyobj(oid); ASN1_OBJECT_free(oid); } - if(!md) - ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%"PRIsVALUE").", obj); + if (!md) + ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, + obj); } else { EVP_MD_CTX *ctx; diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 567cebf4b72b8b..b5dc56928de584 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -10,6 +10,12 @@ def setup @d2 = OpenSSL::Digest::MD5.new end + def test_initialize + assert_raise(OpenSSL::Digest::DigestError) { + OpenSSL::Digest.new("no such algorithm") + } + end + def test_digest null_hex = "d41d8cd98f00b204e9800998ecf8427e" null_bin = [null_hex].pack("H*") From 26751e40857be6faf91d0c87362ebae769f51faa Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 3 Nov 2025 18:26:28 +0900 Subject: [PATCH 0854/2435] [ruby/openssl] cipher: raise CipherError for unsupported algorithm name Raise OpenSSL::Cipher::CipherError instead of ArgumentError or RuntimeError for consistency. https://github.com/ruby/openssl/commit/78601c9c34 --- ext/openssl/ossl_cipher.c | 10 +++------- test/openssl/test_cipher.rb | 5 ++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index f449c63b695b8b..075e92c67f63a4 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -65,8 +65,8 @@ ossl_evp_get_cipherbyname(VALUE obj) StringValueCStr(obj); cipher = EVP_get_cipherbyname(RSTRING_PTR(obj)); if (!cipher) - ossl_raise(rb_eArgError, - "unsupported cipher algorithm: %"PRIsVALUE, obj); + ossl_raise(eCipherError, "unsupported cipher algorithm: %"PRIsVALUE, + obj); return cipher; } @@ -114,17 +114,13 @@ ossl_cipher_initialize(VALUE self, VALUE str) { EVP_CIPHER_CTX *ctx; const EVP_CIPHER *cipher; - char *name; - name = StringValueCStr(str); GetCipherInit(self, ctx); if (ctx) { ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } + cipher = ossl_evp_get_cipherbyname(str); AllocCipher(self, ctx); - if (!(cipher = EVP_get_cipherbyname(name))) { - ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%"PRIsVALUE")", str); - } if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) ossl_raise(eCipherError, NULL); diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 10858b353559e1..30c9a59d4172d5 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -112,6 +112,9 @@ def test_initialize cipher = OpenSSL::Cipher.new("DES-EDE3-CBC") assert_raise(RuntimeError) { cipher.__send__(:initialize, "DES-EDE3-CBC") } assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final } + assert_raise(OpenSSL::Cipher::CipherError) { + OpenSSL::Cipher.new("no such algorithm") + } end def test_ctr_if_exists @@ -368,7 +371,7 @@ def test_aes_keywrap_pad begin cipher = OpenSSL::Cipher.new("id-aes192-wrap-pad").encrypt - rescue OpenSSL::Cipher::CipherError, RuntimeError + rescue OpenSSL::Cipher::CipherError omit "id-aes192-wrap-pad is not supported: #$!" end cipher.key = kek From 10d2311e136212549d36f90ec7cb86108e682088 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 24 Jun 2025 19:31:31 +0900 Subject: [PATCH 0855/2435] [ruby/openssl] digest: use EVP_MD_fetch() if available With the introduction of OpenSSL 3 providers, newly implemented algorithms do not necessarily have a corresponding NID assigned. To use such an algorithm, it has to be "fetched" from providers using the new EVP_*_fetch() functions. For digest algorithms, we have to use EVP_MD_fetch() instead of the existing EVP_get_digestbyname(). However, it is not a drop-in replacement because: - EVP_MD_fetch() does not support all algorithm name aliases recognized by EVP_get_digestbyname(). - Both return an EVP_MD, but the one returned by EVP_MD_fetch() is sometimes reference counted and the user has to explicitly release it with EVP_MD_free(). So, keep using EVP_get_digestbyname() for all OpenSSL versions for now, and fall back to EVP_MD_fetch() if it fails. In the latter case, prepare a T_DATA object to manage the fetched EVP_MD's lifetime. https://github.com/ruby/openssl/commit/9fc2179403 --- ext/openssl/ossl_cipher.c | 4 +- ext/openssl/ossl_digest.c | 78 ++++++++++++++++++++++++++----------- ext/openssl/ossl_digest.h | 10 ++++- ext/openssl/ossl_hmac.c | 10 ++++- ext/openssl/ossl_kdf.c | 8 ++-- ext/openssl/ossl_ns_spki.c | 8 ++-- ext/openssl/ossl_ocsp.c | 23 +++++------ ext/openssl/ossl_pkcs12.c | 4 +- ext/openssl/ossl_pkcs7.c | 12 ++++-- ext/openssl/ossl_pkey.c | 20 +++++----- ext/openssl/ossl_pkey_rsa.c | 17 ++++---- ext/openssl/ossl_ts.c | 31 +++++++++------ ext/openssl/ossl_x509cert.c | 13 +++---- ext/openssl/ossl_x509crl.c | 13 +++---- ext/openssl/ossl_x509req.c | 13 +++---- test/openssl/test_digest.rb | 7 ++++ 16 files changed, 163 insertions(+), 108 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 075e92c67f63a4..89d62fad894400 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -264,7 +264,7 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) { EVP_CIPHER_CTX *ctx; const EVP_MD *digest; - VALUE vpass, vsalt, viter, vdigest; + VALUE vpass, vsalt, viter, vdigest, md_holder; unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL; int iter; @@ -279,7 +279,7 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) iter = NIL_P(viter) ? 2048 : NUM2INT(viter); if (iter <= 0) rb_raise(rb_eArgError, "iterations must be a positive integer"); - digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_get_digestbyname(vdigest); + digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_md_fetch(vdigest, &md_holder); GetCipher(self, ctx); EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt, (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index 80cc041734cae0..e2f1af7e61aa8f 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -21,6 +21,7 @@ */ static VALUE cDigest; static VALUE eDigestError; +static ID id_md_holder; static VALUE ossl_digest_alloc(VALUE klass); @@ -38,35 +39,62 @@ static const rb_data_type_t ossl_digest_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; +#ifdef OSSL_USE_PROVIDER +static void +ossl_evp_md_free(void *ptr) +{ + // This is safe to call against const EVP_MD * returned by + // EVP_get_digestbyname() + EVP_MD_free(ptr); +} + +static const rb_data_type_t ossl_evp_md_holder_type = { + "OpenSSL/EVP_MD", + { + .dfree = ossl_evp_md_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +#endif + /* * Public */ const EVP_MD * -ossl_evp_get_digestbyname(VALUE obj) +ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder) { - const EVP_MD *md; - ASN1_OBJECT *oid = NULL; - - if (RB_TYPE_P(obj, T_STRING)) { - const char *name = StringValueCStr(obj); - - md = EVP_get_digestbyname(name); - if (!md) { - oid = OBJ_txt2obj(name, 0); - md = EVP_get_digestbyobj(oid); - ASN1_OBJECT_free(oid); - } - if (!md) - ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, - obj); - } else { + *holder = Qnil; + if (rb_obj_is_kind_of(obj, cDigest)) { EVP_MD_CTX *ctx; - GetDigest(obj, ctx); - - md = EVP_MD_CTX_get0_md(ctx); + EVP_MD *md = (EVP_MD *)EVP_MD_CTX_get0_md(ctx); +#ifdef OSSL_USE_PROVIDER + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + if (!EVP_MD_up_ref(md)) + ossl_raise(eDigestError, "EVP_MD_up_ref"); + RTYPEDDATA_DATA(*holder) = md; +#endif + return md; } + const char *name = StringValueCStr(obj); + EVP_MD *md = (EVP_MD *)EVP_get_digestbyname(name); + if (!md) { + ASN1_OBJECT *oid = OBJ_txt2obj(name, 0); + md = (EVP_MD *)EVP_get_digestbyobj(oid); + ASN1_OBJECT_free(oid); + } +#ifdef OSSL_USE_PROVIDER + if (!md) { + ossl_clear_error(); + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + md = EVP_MD_fetch(NULL, name, NULL); + RTYPEDDATA_DATA(*holder) = md; + } +#endif + if (!md) + ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, + obj); return md; } @@ -76,6 +104,9 @@ ossl_digest_new(const EVP_MD *md) VALUE ret; EVP_MD_CTX *ctx; + // NOTE: This does not set id_md_holder because this function should + // only be called from ossl_engine.c, which will not use any + // reference-counted digests. ret = ossl_digest_alloc(cDigest); ctx = EVP_MD_CTX_new(); if (!ctx) @@ -122,10 +153,10 @@ ossl_digest_initialize(int argc, VALUE *argv, VALUE self) { EVP_MD_CTX *ctx; const EVP_MD *md; - VALUE type, data; + VALUE type, data, md_holder; rb_scan_args(argc, argv, "11", &type, &data); - md = ossl_evp_get_digestbyname(type); + md = ossl_evp_md_fetch(type, &md_holder); if (!NIL_P(data)) StringValue(data); TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx); @@ -137,6 +168,7 @@ ossl_digest_initialize(int argc, VALUE *argv, VALUE self) if (!EVP_DigestInit_ex(ctx, md, NULL)) ossl_raise(eDigestError, "Digest initialization failed"); + rb_ivar_set(self, id_md_holder, md_holder); if (!NIL_P(data)) return ossl_digest_update(self, data); return self; @@ -443,4 +475,6 @@ Init_ossl_digest(void) rb_define_method(cDigest, "block_length", ossl_digest_block_length, 0); rb_define_method(cDigest, "name", ossl_digest_name, 0); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } diff --git a/ext/openssl/ossl_digest.h b/ext/openssl/ossl_digest.h index 588a0c6f578a63..9c3bb2b149ede9 100644 --- a/ext/openssl/ossl_digest.h +++ b/ext/openssl/ossl_digest.h @@ -10,7 +10,15 @@ #if !defined(_OSSL_DIGEST_H_) #define _OSSL_DIGEST_H_ -const EVP_MD *ossl_evp_get_digestbyname(VALUE); +/* + * Gets EVP_MD from a String or an OpenSSL::Digest instance (discouraged, but + * still supported for compatibility). A holder object is created if the EVP_MD + * is a "fetched" algorithm. + */ +const EVP_MD *ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder); +/* + * This is meant for OpenSSL::Engine#digest. EVP_MD must not be a fetched one. + */ VALUE ossl_digest_new(const EVP_MD *); void Init_ossl_digest(void); diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index b30482757997b6..250b427bdcc8f6 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -23,6 +23,7 @@ */ static VALUE cHMAC; static VALUE eHMACError; +static ID id_md_holder; /* * Public @@ -94,19 +95,22 @@ ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) { EVP_MD_CTX *ctx; EVP_PKEY *pkey; + const EVP_MD *md; + VALUE md_holder; GetHMAC(self, ctx); StringValue(key); + md = ossl_evp_md_fetch(digest, &md_holder); pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, (unsigned char *)RSTRING_PTR(key), RSTRING_LENINT(key)); if (!pkey) ossl_raise(eHMACError, "EVP_PKEY_new_raw_private_key"); - if (EVP_DigestSignInit(ctx, NULL, ossl_evp_get_digestbyname(digest), - NULL, pkey) != 1) { + if (EVP_DigestSignInit(ctx, NULL, md, NULL, pkey) != 1) { EVP_PKEY_free(pkey); ossl_raise(eHMACError, "EVP_DigestSignInit"); } + rb_ivar_set(self, id_md_holder, md_holder); /* Decrement reference counter; EVP_MD_CTX still keeps it */ EVP_PKEY_free(pkey); @@ -300,4 +304,6 @@ Init_ossl_hmac(void) rb_define_method(cHMAC, "hexdigest", ossl_hmac_hexdigest, 0); rb_define_alias(cHMAC, "inspect", "hexdigest"); rb_define_alias(cHMAC, "to_s", "hexdigest"); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index f349939a80475e..e7429a76881c7d 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -35,7 +35,7 @@ static VALUE mKDF, eKDF; static VALUE kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) { - VALUE pass, salt, opts, kwargs[4], str; + VALUE pass, salt, opts, kwargs[4], str, md_holder; static ID kwargs_ids[4]; int iters, len; const EVP_MD *md; @@ -53,7 +53,7 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) salt = StringValue(kwargs[0]); iters = NUM2INT(kwargs[1]); len = NUM2INT(kwargs[2]); - md = ossl_evp_get_digestbyname(kwargs[3]); + md = ossl_evp_md_fetch(kwargs[3], &md_holder); str = rb_str_new(0, len); if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), @@ -172,7 +172,7 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) static VALUE kdf_hkdf(int argc, VALUE *argv, VALUE self) { - VALUE ikm, salt, info, opts, kwargs[4], str; + VALUE ikm, salt, info, opts, kwargs[4], str, md_holder; static ID kwargs_ids[4]; int saltlen, ikmlen, infolen; size_t len; @@ -197,7 +197,7 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) len = (size_t)NUM2LONG(kwargs[2]); if (len > LONG_MAX) rb_raise(rb_eArgError, "length must be non-negative"); - md = ossl_evp_get_digestbyname(kwargs[3]); + md = ossl_evp_md_fetch(kwargs[3], &md_holder); str = rb_str_new(NULL, (long)len); pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index ffed3a64a602f6..51ec8532c3ee8c 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -283,13 +283,13 @@ ossl_spki_sign(VALUE self, VALUE key, VALUE digest) NETSCAPE_SPKI *spki; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); GetSPKI(self, spki); - if (!NETSCAPE_SPKI_sign(spki, pkey, md)) { - ossl_raise(eSPKIError, NULL); - } + if (!NETSCAPE_SPKI_sign(spki, pkey, md)) + ossl_raise(eSPKIError, "NETSCAPE_SPKI_sign"); return self; } diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 5a3a71cae0d83e..84d38760e5c0de 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -369,7 +369,7 @@ ossl_ocspreq_get_certid(VALUE self) static VALUE ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) { - VALUE signer_cert, signer_key, certs, flags, digest; + VALUE signer_cert, signer_key, certs, flags, digest, md_holder; OCSP_REQUEST *req; X509 *signer; EVP_PKEY *key; @@ -384,10 +384,7 @@ ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) flg = NUM2INT(flags); - if (NIL_P(digest)) - md = NULL; - else - md = ossl_evp_get_digestbyname(digest); + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) flg |= OCSP_NOCERTS; else @@ -395,7 +392,8 @@ ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) ret = OCSP_request_sign(req, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); - if (!ret) ossl_raise(eOCSPError, NULL); + if (!ret) + ossl_raise(eOCSPError, "OCSP_request_sign"); return self; } @@ -1000,7 +998,7 @@ ossl_ocspbres_find_response(VALUE self, VALUE target) static VALUE ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) { - VALUE signer_cert, signer_key, certs, flags, digest; + VALUE signer_cert, signer_key, certs, flags, digest, md_holder; OCSP_BASICRESP *bs; X509 *signer; EVP_PKEY *key; @@ -1015,10 +1013,7 @@ ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) flg = NUM2INT(flags); - if (NIL_P(digest)) - md = NULL; - else - md = ossl_evp_get_digestbyname(digest); + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) flg |= OCSP_NOCERTS; else @@ -1026,7 +1021,8 @@ ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) ret = OCSP_basic_sign(bs, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); - if (!ret) ossl_raise(eOCSPError, NULL); + if (!ret) + ossl_raise(eOCSPError, "OCSP_basic_sign"); return self; } @@ -1460,10 +1456,11 @@ ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self) else { X509 *x509s, *x509i; const EVP_MD *md; + VALUE md_holder; x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ - md = !NIL_P(digest) ? ossl_evp_get_digestbyname(digest) : NULL; + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); newid = OCSP_cert_to_id(md, x509s, x509i); if (!newid) diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index 0b7469e673f77c..f76e1625f596a2 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -271,7 +271,7 @@ static VALUE pkcs12_set_mac(int argc, VALUE *argv, VALUE self) { PKCS12 *p12; - VALUE pass, salt, iter, md_name; + VALUE pass, salt, iter, md_name, md_holder = Qnil; int iter_i = 0; const EVP_MD *md_type = NULL; @@ -285,7 +285,7 @@ pkcs12_set_mac(int argc, VALUE *argv, VALUE self) if (!NIL_P(iter)) iter_i = NUM2INT(iter); if (!NIL_P(md_name)) - md_type = ossl_evp_get_digestbyname(md_name); + md_type = ossl_evp_md_fetch(md_name, &md_holder); if (!PKCS12_set_mac(p12, RSTRING_PTR(pass), RSTRING_LENINT(pass), !NIL_P(salt) ? (unsigned char *)RSTRING_PTR(salt) : NULL, diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 910ef9665c7919..070844d2eb2b00 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -68,6 +68,7 @@ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; static VALUE ePKCS7Error; +static ID id_md_holder; static void ossl_pkcs7_free(void *ptr) @@ -968,14 +969,15 @@ ossl_pkcs7si_initialize(VALUE self, VALUE cert, VALUE key, VALUE digest) EVP_PKEY *pkey; X509 *x509; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); GetPKCS7si(self, p7si); - if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, md))) { - ossl_raise(ePKCS7Error, NULL); - } + if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, md))) + ossl_raise(ePKCS7Error, "PKCS7_SIGNER_INFO_set"); + rb_ivar_set(self, id_md_holder, md_holder); return self; } @@ -1161,4 +1163,6 @@ Init_ossl_pkcs7(void) DefPKCS7Const(BINARY); DefPKCS7Const(NOATTR); DefPKCS7Const(NOSMIMECAP); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 37c132ef2ea680..2da3abeb1fa7fb 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -1111,7 +1111,7 @@ static VALUE ossl_pkey_sign(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, data, options, sig; + VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; @@ -1121,7 +1121,7 @@ ossl_pkey_sign(int argc, VALUE *argv, VALUE self) pkey = GetPrivPKeyPtr(self); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_MD_CTX_new(); @@ -1190,7 +1190,7 @@ static VALUE ossl_pkey_verify(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, data, options; + VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; @@ -1200,7 +1200,7 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); @@ -1269,7 +1269,7 @@ static VALUE ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, data, options, sig; + VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; size_t outlen; @@ -1278,7 +1278,7 @@ ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self) GetPKey(self, pkey); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); @@ -1345,7 +1345,7 @@ static VALUE ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, data, options; + VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state, ret; @@ -1354,7 +1354,7 @@ ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); @@ -1408,7 +1408,7 @@ static VALUE ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; - VALUE digest, sig, options, out; + VALUE digest, sig, options, out, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state; @@ -1418,7 +1418,7 @@ ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "21", &digest, &sig, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index b2983d3b53cfc2..0f16a9bc687692 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -349,7 +349,7 @@ ossl_rsa_to_der(VALUE self) static VALUE ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) { - VALUE digest, data, options, kwargs[2], signature; + VALUE digest, data, options, kwargs[2], signature, mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; @@ -370,11 +370,11 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else salt_len = NUM2INT(kwargs[0]); - mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); pkey = GetPrivPKeyPtr(self); buf_len = EVP_PKEY_size(pkey); - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); signature = rb_str_new(NULL, (long)buf_len); @@ -436,7 +436,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) static VALUE ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) { - VALUE digest, signature, data, options, kwargs[2]; + VALUE digest, signature, data, options, kwargs[2], mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; @@ -456,10 +456,10 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else salt_len = NUM2INT(kwargs[0]); - mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); GetPKey(self, pkey); - md = ossl_evp_get_digestbyname(digest); + md = ossl_evp_md_fetch(digest, &md_holder); StringValue(signature); StringValue(data); @@ -485,17 +485,16 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) result = EVP_DigestVerifyFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), RSTRING_LEN(signature)); + EVP_MD_CTX_free(md_ctx); switch (result) { case 0: ossl_clear_error(); - EVP_MD_CTX_free(md_ctx); return Qfalse; case 1: - EVP_MD_CTX_free(md_ctx); return Qtrue; default: - goto err; + ossl_raise(eRSAError, "EVP_DigestVerifyFinal"); } err: diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index c7d2bd271b94c4..3c505b64a9f6a5 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -1155,9 +1155,14 @@ ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, time_t *sec, long *usec) } static VALUE -ossl_evp_get_digestbyname_i(VALUE arg) +ossl_evp_md_fetch_i(VALUE args_) { - return (VALUE)ossl_evp_get_digestbyname(arg); + VALUE *args = (VALUE *)args_, md_holder; + const EVP_MD *md; + + md = ossl_evp_md_fetch(args[1], &md_holder); + rb_ary_push(args[0], md_holder); + return (VALUE)md; } static VALUE @@ -1193,7 +1198,8 @@ ossl_obj2bio_i(VALUE arg) static VALUE ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) { - VALUE serial_number, def_policy_id, gen_time, additional_certs, allowed_digests; + VALUE serial_number, def_policy_id, gen_time, additional_certs, + allowed_digests, allowed_digests_tmp = Qnil; VALUE str; STACK_OF(X509) *inter_certs; VALUE tsresp, ret = Qnil; @@ -1270,16 +1276,18 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) allowed_digests = ossl_tsfac_get_allowed_digests(self); if (rb_obj_is_kind_of(allowed_digests, rb_cArray)) { - int i; - VALUE rbmd; - const EVP_MD *md; - - for (i = 0; i < RARRAY_LEN(allowed_digests); i++) { - rbmd = rb_ary_entry(allowed_digests, i); - md = (const EVP_MD *)rb_protect(ossl_evp_get_digestbyname_i, rbmd, &status); + allowed_digests_tmp = rb_ary_new_capa(RARRAY_LEN(allowed_digests)); + for (long i = 0; i < RARRAY_LEN(allowed_digests); i++) { + VALUE args[] = { + allowed_digests_tmp, + rb_ary_entry(allowed_digests, i), + }; + const EVP_MD *md = (const EVP_MD *)rb_protect(ossl_evp_md_fetch_i, + (VALUE)args, &status); if (status) goto end; - TS_RESP_CTX_add_md(ctx, md); + if (!TS_RESP_CTX_add_md(ctx, md)) + goto end; } } @@ -1293,6 +1301,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) response = TS_RESP_create_response(ctx, req_bio); BIO_free(req_bio); + RB_GC_GUARD(allowed_digests_tmp); if (!response) { err_msg = "Error during response generation"; diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 30e3c617531bde..c7653031b4bc1f 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -535,17 +535,14 @@ ossl_x509_sign(VALUE self, VALUE key, VALUE digest) X509 *x509; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); GetX509(self, x509); - if (!X509_sign(x509, pkey, md)) { - ossl_raise(eX509CertError, NULL); - } + if (!X509_sign(x509, pkey, md)) + ossl_raise(eX509CertError, "X509_sign"); return self; } diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 52174d1711487b..b9ee5f05692b52 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -349,17 +349,14 @@ ossl_x509crl_sign(VALUE self, VALUE key, VALUE digest) X509_CRL *crl; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; GetX509CRL(self, crl); pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } - if (!X509_CRL_sign(crl, pkey, md)) { - ossl_raise(eX509CRLError, NULL); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); + if (!X509_CRL_sign(crl, pkey, md)) + ossl_raise(eX509CRLError, "X509_CRL_sign"); return self; } diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index b4c29f877e8536..eae57969241954 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -312,17 +312,14 @@ ossl_x509req_sign(VALUE self, VALUE key, VALUE digest) X509_REQ *req; EVP_PKEY *pkey; const EVP_MD *md; + VALUE md_holder; GetX509Req(self, req); pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ - if (NIL_P(digest)) { - md = NULL; /* needed for some key types, e.g. Ed25519 */ - } else { - md = ossl_evp_get_digestbyname(digest); - } - if (!X509_REQ_sign(req, pkey, md)) { - ossl_raise(eX509ReqError, NULL); - } + /* NULL needed for some key types, e.g. Ed25519 */ + md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); + if (!X509_REQ_sign(req, pkey, md)) + ossl_raise(eX509ReqError, "X509_REQ_sign"); return self; } diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index b5dc56928de584..2ef84cfa4c7d2a 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -124,6 +124,13 @@ def test_sha3 assert_equal(s512, OpenSSL::Digest.hexdigest('SHA3-512', "")) end + def test_fetched_evp_md + # Pre-NIST Keccak is an example of a digest algorithm that doesn't have an + # NID and requires dynamic allocation of EVP_MD + hex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + assert_equal(hex, OpenSSL::Digest.hexdigest("KECCAK-256", "")) + end if openssl?(3, 2, 0) + def test_openssl_digest assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5") From 57aaf86bdbdaacb66ebbd29d1e2551d87167cbfe Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 9 Aug 2025 18:36:49 +0900 Subject: [PATCH 0856/2435] [ruby/openssl] cipher: use EVP_CIPHER_fetch() if available Likewise, use EVP_MD_fetch() if it is available. This adds support for AES-GCM-SIV with OpenSSL 3.2 or later. https://github.com/ruby/openssl/commit/0e565a215e --- ext/openssl/ossl_cipher.c | 70 +++++++++++++++++++++++++++---------- ext/openssl/ossl_cipher.h | 11 +++++- ext/openssl/ossl_pkcs7.c | 17 +++++---- ext/openssl/ossl_pkey.c | 8 ++--- test/openssl/test_cipher.rb | 18 ++++++++++ 5 files changed, 95 insertions(+), 29 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 89d62fad894400..a52518291124b3 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -33,7 +33,7 @@ static VALUE cCipher; static VALUE eCipherError; static VALUE eAuthTagError; -static ID id_auth_tag_len, id_key_set; +static ID id_auth_tag_len, id_key_set, id_cipher_holder; static VALUE ossl_cipher_alloc(VALUE klass); static void ossl_cipher_free(void *ptr); @@ -46,30 +46,58 @@ static const rb_data_type_t ossl_cipher_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; +#ifdef OSSL_USE_PROVIDER +static void +ossl_evp_cipher_free(void *ptr) +{ + // This is safe to call against const EVP_CIPHER * returned by + // EVP_get_cipherbyname() + EVP_CIPHER_free(ptr); +} + +static const rb_data_type_t ossl_evp_cipher_holder_type = { + "OpenSSL/EVP_CIPHER", + { + .dfree = ossl_evp_cipher_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +#endif + /* * PUBLIC */ const EVP_CIPHER * -ossl_evp_get_cipherbyname(VALUE obj) +ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder) { + *holder = Qnil; if (rb_obj_is_kind_of(obj, cCipher)) { - EVP_CIPHER_CTX *ctx; - - GetCipher(obj, ctx); - - return EVP_CIPHER_CTX_cipher(ctx); + EVP_CIPHER_CTX *ctx; + GetCipher(obj, ctx); + EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_CIPHER_CTX_cipher(ctx); +#ifdef OSSL_USE_PROVIDER + *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); + if (!EVP_CIPHER_up_ref(cipher)) + ossl_raise(eCipherError, "EVP_CIPHER_up_ref"); + RTYPEDDATA_DATA(*holder) = cipher; +#endif + return cipher; } - else { - const EVP_CIPHER *cipher; - StringValueCStr(obj); - cipher = EVP_get_cipherbyname(RSTRING_PTR(obj)); - if (!cipher) - ossl_raise(eCipherError, "unsupported cipher algorithm: %"PRIsVALUE, - obj); - - return cipher; + const char *name = StringValueCStr(obj); + EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_get_cipherbyname(name); +#ifdef OSSL_USE_PROVIDER + if (!cipher) { + ossl_clear_error(); + *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); + cipher = EVP_CIPHER_fetch(NULL, name, NULL); + RTYPEDDATA_DATA(*holder) = cipher; } +#endif + if (!cipher) + ossl_raise(eCipherError, "unsupported cipher algorithm: %"PRIsVALUE, + obj); + return cipher; } VALUE @@ -78,6 +106,9 @@ ossl_cipher_new(const EVP_CIPHER *cipher) VALUE ret; EVP_CIPHER_CTX *ctx; + // NOTE: This does not set id_cipher_holder because this function should + // only be called from ossl_engine.c, which will not use any + // reference-counted ciphers. ret = ossl_cipher_alloc(cCipher); AllocCipher(ret, ctx); if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) @@ -114,15 +145,17 @@ ossl_cipher_initialize(VALUE self, VALUE str) { EVP_CIPHER_CTX *ctx; const EVP_CIPHER *cipher; + VALUE cipher_holder; GetCipherInit(self, ctx); if (ctx) { ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } - cipher = ossl_evp_get_cipherbyname(str); + cipher = ossl_evp_cipher_fetch(str, &cipher_holder); AllocCipher(self, ctx); if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, "EVP_CipherInit_ex"); + rb_ivar_set(self, id_cipher_holder, cipher_holder); return self; } @@ -1106,4 +1139,5 @@ Init_ossl_cipher(void) id_auth_tag_len = rb_intern_const("auth_tag_len"); id_key_set = rb_intern_const("key_set"); + id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } diff --git a/ext/openssl/ossl_cipher.h b/ext/openssl/ossl_cipher.h index 12da68ca3e9cd5..fba63a140f0b6d 100644 --- a/ext/openssl/ossl_cipher.h +++ b/ext/openssl/ossl_cipher.h @@ -10,7 +10,16 @@ #if !defined(_OSSL_CIPHER_H_) #define _OSSL_CIPHER_H_ -const EVP_CIPHER *ossl_evp_get_cipherbyname(VALUE); +/* + * Gets EVP_CIPHER from a String or an OpenSSL::Digest instance (discouraged, + * but still supported for compatibility). A holder object is created if the + * EVP_CIPHER is a "fetched" algorithm. + */ +const EVP_CIPHER *ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder); +/* + * This is meant for OpenSSL::Engine#cipher. EVP_CIPHER must not be a fetched + * one. + */ VALUE ossl_cipher_new(const EVP_CIPHER *); void Init_ossl_cipher(void); diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 070844d2eb2b00..0fcae1971cfa4b 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -68,7 +68,7 @@ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; static VALUE ePKCS7Error; -static ID id_md_holder; +static ID id_md_holder, id_cipher_holder; static void ossl_pkcs7_free(void *ptr) @@ -313,7 +313,7 @@ ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass) static VALUE ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) { - VALUE certs, data, cipher, flags; + VALUE certs, data, cipher, flags, cipher_holder; STACK_OF(X509) *x509s; BIO *in; const EVP_CIPHER *ciph; @@ -327,7 +327,7 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) "cipher must be specified. Before version 3.3, " \ "the default cipher was RC2-40-CBC."); } - ciph = ossl_evp_get_cipherbyname(cipher); + ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); flg = NIL_P(flags) ? 0 : NUM2INT(flags); ret = NewPKCS7(cPKCS7); in = ossl_obj2bio(&data); @@ -344,6 +344,7 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) BIO_free(in); SetPKCS7(ret, p7); ossl_pkcs7_set_data(ret, data); + rb_ivar_set(ret, id_cipher_holder, cipher_holder); sk_X509_pop_free(x509s, X509_free); return ret; @@ -536,11 +537,14 @@ static VALUE ossl_pkcs7_set_cipher(VALUE self, VALUE cipher) { PKCS7 *pkcs7; + const EVP_CIPHER *ciph; + VALUE cipher_holder; GetPKCS7(self, pkcs7); - if (!PKCS7_set_cipher(pkcs7, ossl_evp_get_cipherbyname(cipher))) { - ossl_raise(ePKCS7Error, NULL); - } + ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); + if (!PKCS7_set_cipher(pkcs7, ciph)) + ossl_raise(ePKCS7Error, "PKCS7_set_cipher"); + rb_ivar_set(self, id_cipher_holder, cipher_holder); return cipher; } @@ -1165,4 +1169,5 @@ Init_ossl_pkcs7(void) DefPKCS7Const(NOSMIMECAP); id_md_holder = rb_intern_const("EVP_MD_holder"); + id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 2da3abeb1fa7fb..fea4989d4b26c0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -814,14 +814,14 @@ VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; - VALUE cipher, pass; + VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (!NIL_P(cipher)) { - enc = ossl_evp_get_cipherbyname(cipher); + enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); pass = ossl_pem_passwd_value(pass); } @@ -849,7 +849,7 @@ static VALUE do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; - VALUE cipher, pass; + VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; @@ -860,7 +860,7 @@ do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) * TODO: EncryptedPrivateKeyInfo actually has more options. * Should they be exposed? */ - enc = ossl_evp_get_cipherbyname(cipher); + enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); pass = ossl_pem_passwd_value(pass); } diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 30c9a59d4172d5..93766cfc88ce17 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -345,6 +345,24 @@ def test_aes_ocb_tag_len end if has_cipher?("aes-128-ocb") + def test_aes_gcm_siv + # RFC 8452 Appendix C.1., 8th example + key = ["01000000000000000000000000000000"].pack("H*") + iv = ["030000000000000000000000"].pack("H*") + aad = ["01"].pack("H*") + pt = ["0200000000000000"].pack("H*") + ct = ["1e6daba35669f4273b0a1a2560969cdf790d99759abd1508"].pack("H*") + tag = ["3b0a1a2560969cdf790d99759abd1508"].pack("H*") + ct_without_tag = ct.byteslice(0, ct.bytesize - tag.bytesize) + + cipher = new_encryptor("aes-128-gcm-siv", key: key, iv: iv, auth_data: aad) + assert_equal ct_without_tag, cipher.update(pt) << cipher.final + assert_equal tag, cipher.auth_tag + cipher = new_decryptor("aes-128-gcm-siv", key: key, iv: iv, auth_tag: tag, + auth_data: aad) + assert_equal pt, cipher.update(ct_without_tag) << cipher.final + end if openssl?(3, 2, 0) + def test_aes_gcm_key_iv_order_issue pt = "[ruby/openssl#49]" cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt From d73eba51853fde74624984b3063bd648b664e3e3 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sat, 1 Nov 2025 21:45:03 +0100 Subject: [PATCH 0857/2435] [ruby/prism] Reject `def f a, (b) = 1` Fixes [#Bug 21660], followup to https://github.com/ruby/prism/pull/3674 https://github.com/ruby/prism/commit/fb445a49e5 Co-Authored-By: tomoya ishida --- prism/prism.c | 40 ++++--------------- ...endless_method_command_call_parameters.txt | 19 +++++---- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 6a77dd0febd5d3..03b12e9db82d34 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14621,18 +14621,6 @@ update_parameter_state(pm_parser_t *parser, pm_token_t *token, pm_parameters_ord return true; } -/** - * Ensures that after parsing a parameter, the next token is not `=`. - * Some parameters like `def(* = 1)` cannot become optional. When no parens - * are present like in `def * = 1`, this creates ambiguity with endless method definitions. - */ -static inline void -refute_optional_parameter(pm_parser_t *parser) { - if (match1(parser, PM_TOKEN_EQUAL)) { - pm_parser_err_previous(parser, PM_ERR_DEF_ENDLESS_PARAMETERS); - } -} - /** * Parse a list of parameters on a method definition. */ @@ -14685,10 +14673,6 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; } - if (!uses_parentheses) { - refute_optional_parameter(parser); - } - pm_block_parameter_node_t *param = pm_block_parameter_node_create(parser, &name, &operator); if (repeated) { pm_node_flag_set_repeated_parameter((pm_node_t *)param); @@ -14710,10 +14694,6 @@ parse_parameters( bool succeeded = update_parameter_state(parser, &parser->current, &order); parser_lex(parser); - if (!uses_parentheses) { - refute_optional_parameter(parser); - } - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL; pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous); @@ -14895,10 +14875,6 @@ parse_parameters( context_pop(parser); pm_parameters_node_keywords_append(params, param); - if (!uses_parentheses) { - refute_optional_parameter(parser); - } - // If parsing the value of the parameter resulted in error recovery, // then we can put a missing node in its place and stop parsing the // parameters entirely now. @@ -14930,10 +14906,6 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS; } - if (!uses_parentheses) { - refute_optional_parameter(parser); - } - pm_node_t *param = (pm_node_t *) pm_rest_parameter_node_create(parser, &operator, &name); if (repeated) { pm_node_flag_set_repeated_parameter(param); @@ -14982,10 +14954,6 @@ parse_parameters( } } - if (!uses_parentheses) { - refute_optional_parameter(parser); - } - if (params->keyword_rest == NULL) { pm_parameters_node_keyword_rest_set(params, param); } else { @@ -19586,6 +19554,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t rparen; pm_parameters_node_t *params; + bool accept_endless_def = true; switch (parser->current.type) { case PM_TOKEN_PARENTHESIS_LEFT: { parser_lex(parser); @@ -19621,6 +19590,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b rparen = not_provided(parser); params = parse_parameters(parser, PM_BINDING_POWER_DEFINED, false, false, true, true, false, (uint16_t) (depth + 1)); + // Reject `def * = 1` and similar. We have to specifically check + // for them because they create ambiguity with optional arguments. + accept_endless_def = false; + context_pop(parser); break; } @@ -19642,6 +19615,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (token_is_setter_name(&name)) { pm_parser_err_token(parser, &name, PM_ERR_DEF_ENDLESS_SETTER); } + if (!accept_endless_def) { + pm_parser_err_previous(parser, PM_ERR_DEF_ENDLESS_PARAMETERS); + } equal = parser->previous; context_push(parser, PM_CONTEXT_DEF); diff --git a/test/prism/errors/endless_method_command_call_parameters.txt b/test/prism/errors/endless_method_command_call_parameters.txt index 94c4f88fc835df..5dc92ce7f9f0d9 100644 --- a/test/prism/errors/endless_method_command_call_parameters.txt +++ b/test/prism/errors/endless_method_command_call_parameters.txt @@ -1,24 +1,27 @@ def f x: = 1 - ^~ could not parse the endless method parameters + ^ could not parse the endless method parameters def f ... = 1 - ^~~ could not parse the endless method parameters + ^ could not parse the endless method parameters def f * = 1 - ^ could not parse the endless method parameters + ^ could not parse the endless method parameters def f ** = 1 - ^~ could not parse the endless method parameters + ^ could not parse the endless method parameters def f & = 1 - ^ could not parse the endless method parameters + ^ could not parse the endless method parameters def f *a = 1 - ^ could not parse the endless method parameters + ^ could not parse the endless method parameters def f **a = 1 - ^ could not parse the endless method parameters + ^ could not parse the endless method parameters def f &a = 1 - ^ could not parse the endless method parameters + ^ could not parse the endless method parameters + +def f a, (b) = 1 + ^ could not parse the endless method parameters From 16b1aa4e4ab1b81914c58eae8b2f31c963b4bd4c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 2 Dec 2024 23:23:20 +0900 Subject: [PATCH 0858/2435] [ruby/openssl] pkey: unify error classes into PKeyError Remove the following subclasses of OpenSSL::PKey::PKeyError and make them aliases of it. - OpenSSL::PKey::DHError - OpenSSL::PKey::DSAError - OpenSSL::PKey::ECError - OpenSSL::PKey::RSAError Historically, methods defined on OpenSSL::PKey and OpenSSL::PKey::PKey raise OpenSSL::PKey::PKeyError, while methods on the subclasses raise their respective exception classes. However, this distinction is not particularly useful since all those exception classes represent the same kind of errors from the underlying EVP_PKEY API. I think this convention comes from the fact that OpenSSL::PKey::{DH, DSA,RSA} originally wrapped the corresponding OpenSSL structs DH, DSA, and RSA, before they were unified to wrap EVP_PKEY, way back in 2002. OpenSSL::PKey::EC::Group::Error and OpenSSL::PKey::EC::Point::Error are out of scope of this change, as they are not subclasses of OpenSSL::PKey::PKeyError and do not represent errors from the EVP_PKEY API. https://github.com/ruby/openssl/commit/e74ff3e272 --- ext/openssl/lib/openssl/pkey.rb | 84 ++++++++++++++------------------- ext/openssl/ossl_pkey.c | 11 ++++- ext/openssl/ossl_pkey_dh.c | 34 +++++-------- ext/openssl/ossl_pkey_dsa.c | 23 +++------ ext/openssl/ossl_pkey_ec.c | 49 +++++++++---------- ext/openssl/ossl_pkey_rsa.c | 31 +++++------- test/openssl/test_pkey.rb | 7 +++ test/openssl/test_pkey_dh.rb | 2 +- test/openssl/test_pkey_dsa.rb | 4 +- test/openssl/test_pkey_ec.rb | 12 +++-- test/openssl/test_pkey_rsa.rb | 10 ++-- 11 files changed, 121 insertions(+), 146 deletions(-) diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb index 3d1e8885ca22f6..39871e15dde854 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -7,6 +7,9 @@ require_relative 'marshal' module OpenSSL::PKey + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + DHError = PKeyError + class DH include OpenSSL::Marshal @@ -102,7 +105,7 @@ def compute_key(pub_bn) # puts dh0.pub_key == dh.pub_key #=> false def generate_key! if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 - raise DHError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \ + raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \ "use OpenSSL::PKey.generate_key instead" end @@ -147,6 +150,9 @@ def new(*args, &blk) # :nodoc: end end + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + DSAError = PKeyError + class DSA include OpenSSL::Marshal @@ -242,13 +248,9 @@ def new(*args, &blk) # :nodoc: # sig = dsa.sign_raw(nil, digest) # p dsa.verify_raw(nil, sig, digest) #=> true def syssign(string) - q or raise OpenSSL::PKey::DSAError, "incomplete DSA" - private? or raise OpenSSL::PKey::DSAError, "Private DSA key needed!" - begin - sign_raw(nil, string) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::DSAError, $!.message - end + q or raise PKeyError, "incomplete DSA" + private? or raise PKeyError, "Private DSA key needed!" + sign_raw(nil, string) end # :call-seq: @@ -266,12 +268,13 @@ def syssign(string) # A \DSA signature value. def sysverify(digest, sig) verify_raw(nil, sig, digest) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::DSAError, $!.message end end if defined?(EC) + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + ECError = PKeyError + class EC include OpenSSL::Marshal @@ -282,8 +285,6 @@ class EC # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_sign_asn1(data) sign_raw(nil, data) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::ECError, $!.message end # :call-seq: @@ -293,8 +294,6 @@ def dsa_sign_asn1(data) # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_verify_asn1(data, sig) verify_raw(nil, sig, data) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::ECError, $!.message end # :call-seq: @@ -334,6 +333,9 @@ def to_bn(conversion_form = group.point_conversion_form) end end + # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. + RSAError = PKeyError + class RSA include OpenSSL::Marshal @@ -407,15 +409,11 @@ def new(*args, &blk) # :nodoc: # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def private_encrypt(string, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - private? or raise OpenSSL::PKey::RSAError, "private key needed." - begin - sign_raw(nil, string, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + private? or raise PKeyError, "private key needed." + sign_raw(nil, string, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -430,14 +428,10 @@ def private_encrypt(string, padding = PKCS1_PADDING) # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def public_decrypt(string, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - begin - verify_recover(nil, string, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + verify_recover(nil, string, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -452,14 +446,10 @@ def public_decrypt(string, padding = PKCS1_PADDING) # Deprecated in version 3.0. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def public_encrypt(data, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - begin - encrypt(data, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + encrypt(data, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end # :call-seq: @@ -473,15 +463,11 @@ def public_encrypt(data, padding = PKCS1_PADDING) # Deprecated in version 3.0. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def private_decrypt(data, padding = PKCS1_PADDING) - n or raise OpenSSL::PKey::RSAError, "incomplete RSA" - private? or raise OpenSSL::PKey::RSAError, "private key needed." - begin - decrypt(data, { - "rsa_padding_mode" => translate_padding_mode(padding), - }) - rescue OpenSSL::PKey::PKeyError - raise OpenSSL::PKey::RSAError, $!.message - end + n or raise PKeyError, "incomplete RSA" + private? or raise PKeyError, "private key needed." + decrypt(data, { + "rsa_padding_mode" => translate_padding_mode(padding), + }) end PKCS1_PADDING = 1 @@ -500,7 +486,7 @@ def private_decrypt(data, padding = PKCS1_PADDING) when PKCS1_OAEP_PADDING "oaep" else - raise OpenSSL::PKey::PKeyError, "unsupported padding mode" + raise PKeyError, "unsupported padding mode" end end end diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index fea4989d4b26c0..2d66c6ce625d34 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -1718,7 +1718,16 @@ Init_ossl_pkey(void) /* Document-class: OpenSSL::PKey::PKeyError * - *Raised when errors occur during PKey#sign or PKey#verify. + * Raised when errors occur during PKey#sign or PKey#verify. + * + * Before version 4.0.0, OpenSSL::PKey::PKeyError had the following + * subclasses. These subclasses have been removed and the constants are + * now defined as aliases of OpenSSL::PKey::PKeyError. + * + * * OpenSSL::PKey::DHError + * * OpenSSL::PKey::DSAError + * * OpenSSL::PKey::ECError + * * OpenSSL::PKey::RSAError */ ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 561007fec8b058..79509bef3d2f82 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -22,14 +22,13 @@ GetPKeyDH((obj), _pkey); \ (dh) = EVP_PKEY_get0_DH(_pkey); \ if ((dh) == NULL) \ - ossl_raise(eDHError, "failed to get DH from EVP_PKEY"); \ + ossl_raise(ePKeyError, "failed to get DH from EVP_PKEY"); \ } while (0) /* * Classes */ VALUE cDH; -static VALUE eDHError; /* * Private @@ -94,7 +93,7 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) #else dh = DH_new(); if (!dh) - ossl_raise(eDHError, "DH_new"); + ossl_raise(ePKeyError, "DH_new"); goto legacy; #endif } @@ -114,12 +113,12 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, Qnil); BIO_free(in); if (!pkey) - ossl_raise(eDHError, "could not parse pkey"); + ossl_raise(ePKeyError, "could not parse pkey"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DH) { EVP_PKEY_free(pkey); - rb_raise(eDHError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -130,7 +129,7 @@ ossl_dh_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); - ossl_raise(eDHError, "EVP_PKEY_assign_DH"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -152,7 +151,7 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) dh = DHparams_dup(dh_other); if (!dh) - ossl_raise(eDHError, "DHparams_dup"); + ossl_raise(ePKeyError, "DHparams_dup"); DH_get0_key(dh_other, &pub, &priv); if (pub) { @@ -162,7 +161,7 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) if (!pub2 || (priv && !priv2)) { BN_clear_free(pub2); BN_clear_free(priv2); - ossl_raise(eDHError, "BN_dup"); + ossl_raise(ePKeyError, "BN_dup"); } DH_set0_key(dh, pub2, priv2); } @@ -171,7 +170,7 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); - ossl_raise(eDHError, "EVP_PKEY_assign_DH"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -250,11 +249,11 @@ ossl_dh_export(VALUE self) GetDH(self, dh); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); } if (!PEM_write_bio_DHparams(out, dh)) { BIO_free(out); - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); @@ -284,11 +283,11 @@ ossl_dh_to_der(VALUE self) GetDH(self, dh); if((len = i2d_DHparams(dh, NULL)) <= 0) - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_DHparams(dh, &p) < 0) - ossl_raise(eDHError, NULL); + ossl_raise(ePKeyError, NULL); ossl_str_adjust(str, p); return str; @@ -315,7 +314,7 @@ ossl_dh_check_params(VALUE self) GetPKey(self, pkey); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) - ossl_raise(eDHError, "EVP_PKEY_CTX_new"); + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); ret = EVP_PKEY_param_check(pctx); EVP_PKEY_CTX_free(pctx); #else @@ -364,13 +363,6 @@ Init_ossl_dh(void) ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); #endif - /* Document-class: OpenSSL::PKey::DHError - * - * Generic exception that is raised if an operation on a DH PKey - * fails unexpectedly or in case an instantiation of an instance of DH - * fails due to non-conformant input data. - */ - eDHError = rb_define_class_under(mPKey, "DHError", ePKeyError); /* Document-class: OpenSSL::PKey::DH * * An implementation of the Diffie-Hellman key exchange protocol based on diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index cb38786b560c0a..34e1c7052165be 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -22,7 +22,7 @@ GetPKeyDSA((obj), _pkey); \ (dsa) = EVP_PKEY_get0_DSA(_pkey); \ if ((dsa) == NULL) \ - ossl_raise(eDSAError, "failed to get DSA from EVP_PKEY"); \ + ossl_raise(ePKeyError, "failed to get DSA from EVP_PKEY"); \ } while (0) static inline int @@ -43,7 +43,6 @@ DSA_PRIVATE(VALUE obj, OSSL_3_const DSA *dsa) * Classes */ VALUE cDSA; -static VALUE eDSAError; /* * Private @@ -105,7 +104,7 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) #else dsa = DSA_new(); if (!dsa) - ossl_raise(eDSAError, "DSA_new"); + ossl_raise(ePKeyError, "DSA_new"); goto legacy; #endif } @@ -125,12 +124,12 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) - ossl_raise(eDSAError, "Neither PUB key nor PRIV key"); + ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DSA) { EVP_PKEY_free(pkey); - rb_raise(eDSAError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -141,7 +140,7 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa); - ossl_raise(eDSAError, "EVP_PKEY_assign_DSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -164,13 +163,13 @@ ossl_dsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_DSAPrivateKey, (char *)dsa); if (!dsa_new) - ossl_raise(eDSAError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa_new) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa_new); - ossl_raise(eDSAError, "EVP_PKEY_assign_DSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; @@ -341,14 +340,6 @@ Init_ossl_dsa(void) ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); #endif - /* Document-class: OpenSSL::PKey::DSAError - * - * Generic exception that is raised if an operation on a DSA PKey - * fails unexpectedly or in case an instantiation of an instance of DSA - * fails due to non-conformant input data. - */ - eDSAError = rb_define_class_under(mPKey, "DSAError", ePKeyError); - /* Document-class: OpenSSL::PKey::DSA * * DSA, the Digital Signature Algorithm, is specified in NIST's diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 8c97297a56193e..c063450a4f2ef3 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -23,7 +23,7 @@ static const rb_data_type_t ossl_ec_point_type; GetPKeyEC(obj, _pkey); \ (key) = EVP_PKEY_get0_EC_KEY(_pkey); \ if ((key) == NULL) \ - ossl_raise(eECError, "failed to get EC_KEY from EVP_PKEY"); \ + ossl_raise(ePKeyError, "failed to get EC_KEY from EVP_PKEY"); \ } while (0) #define GetECGroup(obj, group) do { \ @@ -43,7 +43,6 @@ static const rb_data_type_t ossl_ec_point_type; } while (0) VALUE cEC; -static VALUE eECError; static VALUE cEC_GROUP; static VALUE eEC_GROUP; static VALUE cEC_POINT; @@ -71,20 +70,20 @@ ec_key_new_from_group(VALUE arg) GetECGroup(arg, group); if (!(ec = EC_KEY_new())) - ossl_raise(eECError, NULL); + ossl_raise(ePKeyError, NULL); if (!EC_KEY_set_group(ec, group)) { EC_KEY_free(ec); - ossl_raise(eECError, NULL); + ossl_raise(ePKeyError, NULL); } } else { int nid = OBJ_sn2nid(StringValueCStr(arg)); if (nid == NID_undef) - ossl_raise(eECError, "invalid curve name"); + ossl_raise(ePKeyError, "invalid curve name"); if (!(ec = EC_KEY_new_by_curve_name(nid))) - ossl_raise(eECError, NULL); + ossl_raise(ePKeyError, NULL); EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); @@ -114,12 +113,12 @@ ossl_ec_key_s_generate(VALUE klass, VALUE arg) if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(obj) = pkey; if (!EC_KEY_generate_key(ec)) - ossl_raise(eECError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return obj; } @@ -154,7 +153,7 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) "without arguments; pkeys are immutable with OpenSSL 3.0"); #else if (!(ec = EC_KEY_new())) - ossl_raise(eECError, "EC_KEY_new"); + ossl_raise(ePKeyError, "EC_KEY_new"); goto legacy; #endif } @@ -178,7 +177,7 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_EC) { EVP_PKEY_free(pkey); - rb_raise(eECError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -188,7 +187,7 @@ static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -209,12 +208,12 @@ ossl_ec_key_initialize_copy(VALUE self, VALUE other) ec_new = EC_KEY_dup(ec); if (!ec_new) - ossl_raise(eECError, "EC_KEY_dup"); + ossl_raise(ePKeyError, "EC_KEY_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec_new) != 1) { EC_KEY_free(ec_new); - ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; @@ -263,7 +262,7 @@ ossl_ec_key_set_group(VALUE self, VALUE group_v) GetECGroup(group_v, group); if (EC_KEY_set_group(ec, group) != 1) - ossl_raise(eECError, "EC_KEY_set_group"); + ossl_raise(ePKeyError, "EC_KEY_set_group"); return group_v; #endif @@ -313,7 +312,7 @@ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) break; /* fallthrough */ default: - ossl_raise(eECError, "EC_KEY_set_private_key"); + ossl_raise(ePKeyError, "EC_KEY_set_private_key"); } return private_key; @@ -364,7 +363,7 @@ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) break; /* fallthrough */ default: - ossl_raise(eECError, "EC_KEY_set_public_key"); + ossl_raise(ePKeyError, "EC_KEY_set_public_key"); } return public_key; @@ -468,7 +467,7 @@ ossl_ec_key_export(int argc, VALUE *argv, VALUE self) GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) - ossl_raise(eECError, "can't export - no public key set"); + ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(argc, argv, self, 0); else @@ -496,7 +495,7 @@ ossl_ec_key_to_der(VALUE self) GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) - ossl_raise(eECError, "can't export - no public key set"); + ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(0, NULL, self, 1); else @@ -525,7 +524,7 @@ static VALUE ossl_ec_key_generate_key(VALUE self) GetEC(self, ec); if (EC_KEY_generate_key(ec) != 1) - ossl_raise(eECError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return self; #endif @@ -550,18 +549,18 @@ static VALUE ossl_ec_key_check_key(VALUE self) GetEC(self, ec); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) - ossl_raise(eECError, "EVP_PKEY_CTX_new"); + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EC_KEY_get0_private_key(ec) != NULL) { if (EVP_PKEY_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); - ossl_raise(eECError, "EVP_PKEY_check"); + ossl_raise(ePKeyError, "EVP_PKEY_check"); } } else { if (EVP_PKEY_public_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); - ossl_raise(eECError, "EVP_PKEY_public_check"); + ossl_raise(ePKeyError, "EVP_PKEY_public_check"); } } @@ -571,7 +570,7 @@ static VALUE ossl_ec_key_check_key(VALUE self) GetEC(self, ec); if (EC_KEY_check_key(ec) != 1) - ossl_raise(eECError, "EC_KEY_check_key"); + ossl_raise(ePKeyError, "EC_KEY_check_key"); #endif return Qtrue; @@ -1108,7 +1107,7 @@ static VALUE ossl_ec_group_to_string(VALUE self, int format) if (i != 1) { BIO_free(out); - ossl_raise(eECError, NULL); + ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); @@ -1536,8 +1535,6 @@ void Init_ossl_ec(void) ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); #endif - eECError = rb_define_class_under(mPKey, "ECError", ePKeyError); - /* * Document-class: OpenSSL::PKey::EC * diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 0f16a9bc687692..1a719d3cb96458 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -22,7 +22,7 @@ GetPKeyRSA((obj), _pkey); \ (rsa) = EVP_PKEY_get0_RSA(_pkey); \ if ((rsa) == NULL) \ - ossl_raise(eRSAError, "failed to get RSA from EVP_PKEY"); \ + ossl_raise(ePKeyError, "failed to get RSA from EVP_PKEY"); \ } while (0) static inline int @@ -44,7 +44,6 @@ RSA_PRIVATE(VALUE obj, OSSL_3_const RSA *rsa) * Classes */ VALUE cRSA; -static VALUE eRSAError; /* * Private @@ -98,7 +97,7 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) #else rsa = RSA_new(); if (!rsa) - ossl_raise(eRSAError, "RSA_new"); + ossl_raise(ePKeyError, "RSA_new"); goto legacy; #endif } @@ -121,12 +120,12 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) - ossl_raise(eRSAError, "Neither PUB key nor PRIV key"); + ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_RSA) { EVP_PKEY_free(pkey); - rb_raise(eRSAError, "incorrect pkey type: %s", OBJ_nid2sn(type)); + rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -137,7 +136,7 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) { EVP_PKEY_free(pkey); RSA_free(rsa); - ossl_raise(eRSAError, "EVP_PKEY_assign_RSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; return self; @@ -160,12 +159,12 @@ ossl_rsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_RSAPrivateKey, (char *)rsa); if (!rsa_new) - ossl_raise(eRSAError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa_new) != 1) { RSA_free(rsa_new); - ossl_raise(eRSAError, "EVP_PKEY_assign_RSA"); + ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; @@ -320,7 +319,7 @@ ossl_rsa_to_der(VALUE self) * Signs _data_ using the Probabilistic Signature Scheme (RSA-PSS) and returns * the calculated signature. * - * RSAError will be raised if an error occurs. + * PKeyError will be raised if an error occurs. * * See #verify_pss for the verification operation. * @@ -407,7 +406,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) err: EVP_MD_CTX_free(md_ctx); - ossl_raise(eRSAError, NULL); + ossl_raise(ePKeyError, NULL); } /* @@ -417,7 +416,7 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) * Verifies _data_ using the Probabilistic Signature Scheme (RSA-PSS). * * The return value is +true+ if the signature is valid, +false+ otherwise. - * RSAError will be raised if an error occurs. + * PKeyError will be raised if an error occurs. * * See #sign_pss for the signing operation and an example code. * @@ -499,7 +498,7 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) err: EVP_MD_CTX_free(md_ctx); - ossl_raise(eRSAError, NULL); + ossl_raise(ePKeyError, NULL); } /* @@ -543,14 +542,6 @@ Init_ossl_rsa(void) ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); #endif - /* Document-class: OpenSSL::PKey::RSAError - * - * Generic exception that is raised if an operation on an RSA PKey - * fails unexpectedly or in case an instantiation of an instance of RSA - * fails due to non-conformant input data. - */ - eRSAError = rb_define_class_under(mPKey, "RSAError", ePKeyError); - /* Document-class: OpenSSL::PKey::RSA * * RSA is an asymmetric public key algorithm that has been formalized in diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 0943a7737db707..88299888f046bf 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -314,4 +314,11 @@ def test_to_text rsa = Fixtures.pkey("rsa-1") assert_include rsa.to_text, "publicExponent" end + + def test_legacy_error_classes + assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DSAError) + assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DHError) + assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::ECError) + assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::RSAError) + end end diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index 6ca5b1f5f8cd40..cd13283a2a7dce 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -140,7 +140,7 @@ def test_params_ok? # AWS-LC automatically does parameter checks on the parsed params. if aws_lc? - assert_raise(OpenSSL::PKey::DHError) { + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh0.p + 1), OpenSSL::ASN1::Integer(dh0.g) diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index ef0fdf9182fb1b..1ec0bf0b4d4bf8 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -97,7 +97,7 @@ def test_sign_verify_raw sig = key.syssign(digest) assert_equal true, key.sysverify(digest, sig) assert_equal false, key.sysverify(digest, invalid_sig) - assert_sign_verify_false_or_error{ key.sysverify(digest, malformed_sig) } + assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, digest) assert_equal false, key.verify_raw(nil, invalid_sig, digest) assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) } @@ -148,7 +148,7 @@ def test_DSAPrivateKey_encrypted cipher = OpenSSL::Cipher.new("aes-128-cbc") exported = orig.to_pem(cipher, "abcdef\0\1") assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") - assert_raise(OpenSSL::PKey::DSAError) { + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::DSA.new(exported, "abcdef") } end diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index 58857cb038b10b..df91a1be255f07 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -54,7 +54,9 @@ def test_builtin_curves end def test_generate - assert_raise(OpenSSL::PKey::ECError) { OpenSSL::PKey::EC.generate("non-existent") } + assert_raise(OpenSSL::PKey::PKeyError) { + OpenSSL::PKey::EC.generate("non-existent") + } g = OpenSSL::PKey::EC::Group.new("prime256v1") ec = OpenSSL::PKey::EC.generate(g) assert_equal(true, ec.private?) @@ -65,7 +67,7 @@ def test_generate def test_generate_key ec = OpenSSL::PKey::EC.new("prime256v1") assert_equal false, ec.private? - assert_raise(OpenSSL::PKey::ECError) { ec.to_der } + assert_raise(OpenSSL::PKey::PKeyError) { ec.to_der } ec.generate_key! assert_equal true, ec.private? assert_nothing_raised { ec.to_der } @@ -109,13 +111,13 @@ def test_check_key assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(ec_key_data) } else key4 = OpenSSL::PKey.read(ec_key_data) - assert_raise(OpenSSL::PKey::ECError) { key4.check_key } + assert_raise(OpenSSL::PKey::PKeyError) { key4.check_key } end # EC#private_key= is deprecated in 3.0 and won't work on OpenSSL 3.0 if !openssl?(3, 0, 0) key2.private_key += 1 - assert_raise(OpenSSL::PKey::ECError) { key2.check_key } + assert_raise(OpenSSL::PKey::PKeyError) { key2.check_key } end end @@ -269,7 +271,7 @@ def test_ECPrivateKey_encrypted cipher = OpenSSL::Cipher.new("aes-128-cbc") exported = p256.to_pem(cipher, "abcdef\0\1") assert_same_ec p256, OpenSSL::PKey::EC.new(exported, "abcdef\0\1") - assert_raise(OpenSSL::PKey::ECError) { + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::EC.new(exported, "abcdef") } end diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 90dd0481edfc67..86f51cf4385a01 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -9,8 +9,8 @@ def test_no_private_exp rsa = Fixtures.pkey("rsa-1") key.set_key(rsa.n, rsa.e, nil) key.set_factors(rsa.p, rsa.q) - assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt("foo") } - assert_raise(OpenSSL::PKey::RSAError){ key.private_decrypt("foo") } + assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt("foo") } + assert_raise(OpenSSL::PKey::PKeyError){ key.private_decrypt("foo") } end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0 def test_private @@ -180,7 +180,7 @@ def test_sign_verify_raw_legacy # Failure cases assert_raise(ArgumentError){ key.private_encrypt() } assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) } - assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) } + assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt(plain0, 666) } end @@ -231,7 +231,7 @@ def test_sign_verify_pss key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256") end - assert_raise(OpenSSL::PKey::RSAError) { + assert_raise(OpenSSL::PKey::PKeyError) { key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA256") } end @@ -373,7 +373,7 @@ def test_RSAPrivateKey_encrypted cipher = OpenSSL::Cipher.new("aes-128-cbc") exported = rsa.to_pem(cipher, "abcdef\0\1") assert_same_rsa rsa, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1") - assert_raise(OpenSSL::PKey::RSAError) { + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::RSA.new(exported, "abcdef") } end From 87ae631b406ee6996558544f9781abbac697f7a9 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 6 Nov 2025 22:38:08 +0900 Subject: [PATCH 0859/2435] [ruby/openssl] pkey/rsa: fix usage of eRSAError This is a follow-up to commit https://github.com/ruby/openssl/commit/e74ff3e2722f, which missed the line added in a different PR. https://github.com/ruby/openssl/commit/1b01d19456 --- ext/openssl/ossl_pkey_rsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 1a719d3cb96458..ed65121acc8849 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -493,7 +493,7 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) case 1: return Qtrue; default: - ossl_raise(eRSAError, "EVP_DigestVerifyFinal"); + ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); } err: From f7e7443aaa9bdebefbdbfbd57cdda3a0d7febfdd Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 7 Nov 2025 00:45:10 +0900 Subject: [PATCH 0860/2435] Adjust OpenSSL specs for digest algorithm lookup https://github.com/ruby/openssl/pull/958 changed the common logic for digest algorithm lookup: - If the argument is neither an OpenSSL::Digest instance nor a String, it is now implicitly converted to String with #to_str. This is consistent with algorithm name lookup logic in ruby/openssl for pkeys and ciphers. - If the name is not recognized, OpenSSL::Digest::DigestError is raised instead of RuntimeError. Update the specs accordingly: - Remove specs that expect #to_str not to be called. - Relax regexps matching TypeError messages. - Expect OpenSSL::Digest::DigestError instead of RuntimeError for ruby/openssl 4.0.0 and later. --- .../library/openssl/digest/initialize_spec.rb | 16 ++++++--------- .../library/openssl/kdf/pbkdf2_hmac_spec.rb | 20 +++++++------------ 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/spec/ruby/library/openssl/digest/initialize_spec.rb b/spec/ruby/library/openssl/digest/initialize_spec.rb index 1cd0409c4d838c..b5911716ca8c14 100644 --- a/spec/ruby/library/openssl/digest/initialize_spec.rb +++ b/spec/ruby/library/openssl/digest/initialize_spec.rb @@ -23,18 +23,14 @@ OpenSSL::Digest.new("sha512").name.should == "SHA512" end - it "throws an error when called with an unknown digest" do - -> { OpenSSL::Digest.new("wd40") }.should raise_error(RuntimeError, /Unsupported digest algorithm \(wd40\)/) + version_is OpenSSL::VERSION, "4.0.0" do + it "throws an error when called with an unknown digest" do + -> { OpenSSL::Digest.new("wd40") }.should raise_error(OpenSSL::Digest::DigestError, /wd40/) + end end it "cannot be called with a symbol" do - -> { OpenSSL::Digest.new(:SHA1) }.should raise_error(TypeError, /wrong argument type Symbol/) - end - - it "does not call #to_str on the argument" do - name = mock("digest name") - name.should_not_receive(:to_str) - -> { OpenSSL::Digest.new(name) }.should raise_error(TypeError, /wrong argument type/) + -> { OpenSSL::Digest.new(:SHA1) }.should raise_error(TypeError) end end @@ -62,7 +58,7 @@ end it "cannot be called with a digest class" do - -> { OpenSSL::Digest.new(OpenSSL::Digest::SHA1) }.should raise_error(TypeError, /wrong argument type Class/) + -> { OpenSSL::Digest.new(OpenSSL::Digest::SHA1) }.should raise_error(TypeError) end context "when called without an initial String argument" do diff --git a/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb index 40f85972759a5a..1112972060e18e 100644 --- a/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb +++ b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb @@ -107,21 +107,15 @@ it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest" do -> { OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: Object.new) - }.should raise_error(TypeError, "wrong argument type Object (expected OpenSSL/Digest)") + }.should raise_error(TypeError) end - it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest, it does not try to call #to_str" do - hash = mock("hash") - hash.should_not_receive(:to_str) - -> { - OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash) - }.should raise_error(TypeError, "wrong argument type MockObject (expected OpenSSL/Digest)") - end - - it "raises a RuntimeError for unknown digest algorithms" do - -> { - OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40") - }.should raise_error(RuntimeError, /Unsupported digest algorithm \(wd40\)/) + version_is OpenSSL::VERSION, "4.0.0" do + it "raises a OpenSSL::Digest::DigestError for unknown digest algorithms" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40") + }.should raise_error(OpenSSL::Digest::DigestError, /wd40/) + end end it "treats salt as a required keyword" do From 1cca3efa5a1a348cab93a6dc2486bc9a71519d39 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 6 Nov 2025 12:40:46 -0500 Subject: [PATCH 0861/2435] ZJIT: Use interpreter inline cache in setinstancevariable (#14925) We have both `SetIvar` and `SetInstanceVariable`. The former is a purely dynamic fallback that we can inline `attr_accessor`/`attr_writer` into, whereas the latter comes straight from the interpreter's `setinstancevariable` opcode. --- zjit/src/codegen.rs | 10 ++++++++++ zjit/src/hir.rs | 16 +++++++++++----- zjit/src/hir/opt_tests.rs | 2 +- zjit/src/hir/tests.rs | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 364b9225fe07e6..d7612d3e503ccf 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -426,6 +426,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)), Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), + Insn::SetInstanceVariable { self_val, id, ic, val, state } => no_output!(gen_set_instance_variable(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)), @@ -840,6 +841,15 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); } +/// Emit an uncached instance variable store using the interpreter inline cache +fn gen_set_instance_variable(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_constant_cache, val: Opnd, state: &FrameState) { + gen_incr_counter(asm, Counter::dynamic_setivar_count); + // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. + gen_prepare_non_leaf_call(jit, asm, state); + let iseq = Opnd::Value(jit.iseq.into()); + asm_ccall!(asm, rb_vm_setinstancevariable, iseq, recv, id.0.into(), val, Opnd::const_ptr(ic)); +} + fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); asm_ccall!(asm, rb_vm_getclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 449047d0dfd72c..e0a2e0fdff1802 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -675,6 +675,8 @@ pub enum Insn { GetIvar { self_val: InsnId, id: ID, state: InsnId }, /// Set `self_val`'s instance variable `id` to `val` SetIvar { self_val: InsnId, id: ID, val: InsnId, state: InsnId }, + /// Set `self_val`'s instance variable `id` to `val` using the interpreter inline cache + SetInstanceVariable { self_val: InsnId, id: ID, ic: *const iseq_inline_constant_cache, val: InsnId, state: InsnId }, /// Check whether an instance variable exists on `self_val` DefinedIvar { self_val: InsnId, id: ID, pushval: VALUE, state: InsnId }, @@ -866,7 +868,7 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::SetInstanceVariable { .. } => false, _ => true, } } @@ -1193,6 +1195,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), + Insn::SetInstanceVariable { self_val, id, val, .. } => write!(f, "SetInstanceVariable {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()), &Insn::GetLocal { level, ep_offset, use_sp: true, rest_param } => write!(f, "GetLocal l{level}, SP@{}{}", ep_offset + 1, if rest_param { ", *" } else { "" }), @@ -1817,6 +1820,7 @@ impl Function { &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, + &SetInstanceVariable { self_val, id, ic, val, state } => SetInstanceVariable { self_val: find!(self_val), id, ic, val: find!(val), state }, &GetClassVar { id, ic, state } => GetClassVar { id, ic, state }, &SetClassVar { id, val, ic, state } => SetClassVar { id, val: find!(val), ic, state }, &SetLocal { val, ep_offset, level } => SetLocal { val: find!(val), ep_offset, level }, @@ -1870,7 +1874,8 @@ impl Function { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } => + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } + | Insn::SetInstanceVariable { .. } => panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -3379,7 +3384,8 @@ impl Function { worklist.push_back(self_val); worklist.push_back(state); } - &Insn::SetIvar { self_val, val, state, .. } => { + &Insn::SetIvar { self_val, val, state, .. } + | &Insn::SetInstanceVariable { self_val, val, state, .. } => { worklist.push_back(self_val); worklist.push_back(val); worklist.push_back(state); @@ -5089,13 +5095,13 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_setinstancevariable => { let id = ID(get_arg(pc, 0).as_u64()); - // ic is in arg 1 + let ic = get_arg(pc, 1).as_ptr(); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_setivar // TODO: We only really need this if self_val is a class/module fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state: exit_id }); let val = state.stack_pop()?; - fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, val, state: exit_id }); + fun.push_insn(block, Insn::SetInstanceVariable { self_val: self_param, id, ic, val, state: exit_id }); } YARVINSN_getclassvariable => { let id = ID(get_arg(pc, 0).as_u64()); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index f824351eca7551..9b757433e1b032 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3198,7 +3198,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 + SetInstanceVariable v6, :@foo, v10 CheckInterrupts Return v10 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index a8738a07157b16..af3fd3de9153d0 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2185,7 +2185,7 @@ pub mod hir_build_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 + SetInstanceVariable v6, :@foo, v10 CheckInterrupts Return v10 "); From ff6c728acb81e734c3f5a955e82535179a4f6c89 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 6 Nov 2025 10:21:18 -0800 Subject: [PATCH 0862/2435] ZJIT: Restore dropped_bytes after temporary OOM (#15069) --- zjit/src/asm/mod.rs | 8 ++++++++ zjit/src/backend/lir.rs | 10 +++++++++- zjit/src/codegen.rs | 5 +++++ zjit/src/virtualmem.rs | 7 +++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index dca2b7b0cf018a..86176c0ec9bae5 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -208,6 +208,14 @@ impl CodeBlock { self.dropped_bytes } + /// Set dropped_bytes to false if the current zjit_alloc_bytes() + code_region_size + /// + page_size is below --zjit-mem-size. + pub fn update_dropped_bytes(&mut self) { + if self.mem_block.borrow().can_allocate() { + self.dropped_bytes = false; + } + } + /// Allocate a new label with a given name pub fn new_label(&mut self, name: String) -> Label { assert!(!name.contains(' '), "use underscores in label names, not spaces"); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index e2f75e01c8fcba..69b030608be776 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1750,7 +1750,15 @@ impl Assembler #[cfg(feature = "disasm")] let start_addr = cb.get_write_ptr(); let alloc_regs = Self::get_alloc_regs(); - let ret = self.compile_with_regs(cb, alloc_regs); + let had_dropped_bytes = cb.has_dropped_bytes(); + let ret = self.compile_with_regs(cb, alloc_regs).inspect_err(|err| { + // If we use too much memory to compile the Assembler, it would set cb.dropped_bytes = true. + // To avoid failing future compilation by cb.has_dropped_bytes(), attempt to reset dropped_bytes with + // the current zjit_alloc_bytes() which may be decreased after self is dropped in compile_with_regs(). + if *err == CompileError::OutOfMemory && !had_dropped_bytes { + cb.update_dropped_bytes(); + } + }); #[cfg(feature = "disasm")] if get_option!(dump_disasm) { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d7612d3e503ccf..6a7707dd5a5a17 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -220,6 +220,11 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> R /// Compile an ISEQ into machine code fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Result { + // If we ran out of code region, we shouldn't attempt to generate new code. + if cb.has_dropped_bytes() { + return Err(CompileError::OutOfMemory); + } + // Convert ISEQ into optimized High-level IR if not given let function = match function { Some(function) => function, diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 770fbfba47d1c8..9741a7b13867d5 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -258,6 +258,13 @@ impl VirtualMemory { Ok(()) } + /// Return true if write_byte() can allocate a new page + pub fn can_allocate(&self) -> bool { + let memory_usage_bytes = self.mapped_region_bytes + zjit_alloc_bytes(); + let memory_limit_bytes = self.memory_limit_bytes.unwrap_or(self.region_size_bytes); + memory_usage_bytes + self.page_size_bytes < memory_limit_bytes + } + /// Make all the code in the region executable. Call this at the end of a write session. /// See [Self] for usual usage flow. pub fn mark_all_executable(&mut self) { From c6c92bdce3ad2714a3da5f1b1d7f083b2b579be3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 6 Nov 2025 10:50:58 -0800 Subject: [PATCH 0863/2435] zjit-macos.yml: Unset MAKEFLAGS before ruby-bench (#15084) --- .github/workflows/zjit-macos.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 85c4302737a9b2..ea9e60b1b48770 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -186,6 +186,10 @@ jobs: - run: make install + # setup/directories set MAKEFLAGS=-j4 for macOS, which randomly fails sqlite3.gem builds + - name: Unset MAKEFLAGS + run: echo "MAKEFLAGS=" >> "$GITHUB_ENV" + - name: Checkout ruby-bench uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: From 38d31dc49bb3b94cd1577117c52210c9035ddf1e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 6 Nov 2025 15:07:02 -0500 Subject: [PATCH 0864/2435] ZJIT: Untag block handler (#15085) Storing the tagged block handler in profiles is not GC-safe (nice catch, Kokubun). Store the untagged block handler instead. Fix bug in https://github.com/ruby/ruby/pull/15051 --- vm_insnhelper.c | 30 ++++++++++++++++++++++-------- zjit.c | 3 +-- zjit/bindgen/src/main.rs | 3 +-- zjit/src/cruby_bindings.inc.rs | 8 +------- zjit/src/hir.rs | 5 ++--- zjit/src/profile.rs | 2 +- 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index e1ec5e63ec9653..63dcaba8a33bf5 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5497,12 +5497,6 @@ vm_invoke_proc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, return vm_invoke_block(ec, reg_cfp, calling, ci, is_lambda, block_handler); } -enum rb_block_handler_type -rb_vm_block_handler_type(VALUE block_handler) -{ - return vm_block_handler_type(block_handler); -} - static inline VALUE vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci, @@ -6065,10 +6059,30 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv } } +// Return the untagged block handler: +// * If it's an ISEQ or an IFUNC, fetch it from its rb_captured_block +// * If it's a PROC or SYMBOL, return it as is +static VALUE +rb_vm_untag_block_handler(VALUE block_handler) +{ + switch (vm_block_handler_type(block_handler)) { + case block_handler_type_iseq: + case block_handler_type_ifunc: { + struct rb_captured_block *captured = VM_TAGGED_PTR_REF(block_handler, 0x03); + return captured->code.val; + } + case block_handler_type_proc: + case block_handler_type_symbol: + return block_handler; + default: + rb_bug("rb_vm_untag_block_handler: unreachable"); + } +} + VALUE -rb_vm_get_block_handler(rb_control_frame_t *reg_cfp) +rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp) { - return VM_CF_BLOCK_HANDLER(reg_cfp); + return rb_vm_untag_block_handler(VM_CF_BLOCK_HANDLER(reg_cfp)); } static VALUE diff --git a/zjit.c b/zjit.c index 72e6fe14241ef4..d1f192801a2ec8 100644 --- a/zjit.c +++ b/zjit.c @@ -302,8 +302,7 @@ rb_zjit_class_has_default_allocator(VALUE klass) } -VALUE rb_vm_get_block_handler(rb_control_frame_t *reg_cfp); -enum rb_block_handler_type rb_vm_block_handler_type(VALUE block_handler); +VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp); // Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them. VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self); diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index bbb3b54d6c873c..95209375dcfd71 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -399,8 +399,7 @@ fn main() { .allowlist_function("rb_yarv_str_eql_internal") .allowlist_function("rb_str_neq_internal") .allowlist_function("rb_yarv_ary_entry_internal") - .allowlist_function("rb_vm_get_block_handler") - .allowlist_function("rb_vm_block_handler_type") + .allowlist_function("rb_vm_get_untagged_block_handler") .allowlist_function("rb_FL_TEST") .allowlist_function("rb_FL_TEST_RAW") .allowlist_function("rb_RB_TYPE_P") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index dc9d0d144c1259..86239588292adb 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -571,11 +571,6 @@ pub struct rb_captured_block__bindgen_ty_1 { pub val: __BindgenUnionField, pub bindgen_union_field: u64, } -pub const block_handler_type_iseq: rb_block_handler_type = 0; -pub const block_handler_type_ifunc: rb_block_handler_type = 1; -pub const block_handler_type_symbol: rb_block_handler_type = 2; -pub const block_handler_type_proc: rb_block_handler_type = 3; -pub type rb_block_handler_type = u32; pub const block_type_iseq: rb_block_type = 0; pub const block_type_ifunc: rb_block_type = 1; pub const block_type_symbol: rb_block_type = 2; @@ -1339,8 +1334,7 @@ unsafe extern "C" { pub fn rb_zjit_class_initialized_p(klass: VALUE) -> bool; pub fn rb_zjit_class_get_alloc_func(klass: VALUE) -> rb_alloc_func_t; pub fn rb_zjit_class_has_default_allocator(klass: VALUE) -> bool; - pub fn rb_vm_get_block_handler(reg_cfp: *mut rb_control_frame_t) -> VALUE; - pub fn rb_vm_block_handler_type(block_handler: VALUE) -> rb_block_handler_type; + pub fn rb_vm_get_untagged_block_handler(reg_cfp: *mut rb_control_frame_t) -> VALUE; pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e0a2e0fdff1802..07ffe4b00a7f5e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4421,10 +4421,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let summary = TypeDistributionSummary::new(&self_type_distribution); if summary.is_monomorphic() { let obj = summary.bucket(0).class(); - let bh_type = unsafe { rb_vm_block_handler_type(obj) }; - if bh_type == block_handler_type_iseq { + if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 } { fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_iseq)); - } else if bh_type == block_handler_type_ifunc { + } else if unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1 } { fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_ifunc)); } else { fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_other)); diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index c58999668e59cf..3366fe8e58db17 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -45,7 +45,7 @@ impl Profiler { } fn peek_at_block_handler(&self) -> VALUE { - unsafe { rb_vm_get_block_handler(self.cfp) } + unsafe { rb_vm_get_untagged_block_handler(self.cfp) } } } From 2998c8d6b99ec49925ebea42198b29c3e27b34a7 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 6 Nov 2025 16:32:20 -0500 Subject: [PATCH 0865/2435] ns_subclasses refcount accesses need to be atomic (#15083) We were seeing errors like: ``` * thread #8, stop reason = EXC_BAD_ACCESS (code=1, address=0x803) * frame #0: 0x00000001001fe944 ruby`rb_st_lookup(tab=0x00000000000007fb, key=1, value=0x00000001305b7490) at st.c:1066:22 frame #1: 0x000000010002d658 ruby`remove_class_from_subclasses [inlined] class_get_subclasses_for_ns(tbl=0x00000000000007fb, ns_id=1) at class.c:604:9 frame #2: 0x000000010002d650 ruby`remove_class_from_subclasses(tbl=0x00000000000007fb, ns_id=1, klass=4754039232) at class.c:620:34 frame #3: 0x000000010002c8a8 ruby`rb_class_classext_free_subclasses(ext=0x000000011b5ce1d8, klass=4754039232, replacing=) at class.c:700:9 frame #4: 0x000000010002c760 ruby`rb_class_classext_free(klass=4754039232, ext=0x000000011b5ce1d8, is_prime=true) at class.c:105:5 frame #5: 0x00000001000e770c ruby`classext_free(ext=, is_prime=, namespace=, arg=) at gc.c:1231:5 [artificial] frame #6: 0x000000010002d178 ruby`rb_class_classext_foreach(klass=, func=(ruby`classext_free at gc.c:1228), arg=0x00000001305b75c0) at class.c:518:5 frame #7: 0x00000001000e745c ruby`rb_gc_obj_free(objspace=0x000000012500c400, obj=4754039232) at gc.c:1282:9 frame #8: 0x00000001000e70d4 ruby`gc_sweep_plane(objspace=0x000000012500c400, heap=, p=4754039232, bitset=4095, ctx=0x00000001305b76e8) at default.c:3482:21 frame #9: 0x00000001000e6e9c ruby`gc_sweep_page(objspace=0x000000012500c400, heap=0x000000012500c540, ctx=0x00000001305b76e8) at default.c:3567:13 frame #10: 0x00000001000e51d0 ruby`gc_sweep_step(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:3848:9 frame #11: 0x00000001000e1880 ruby`gc_continue [inlined] gc_sweep_continue(objspace=0x000000012500c400, sweep_heap=0x000000012500c540) at default.c:3931:13 frame #12: 0x00000001000e1754 ruby`gc_continue(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2037:9 frame #13: 0x00000001000e10bc ruby`newobj_cache_miss [inlined] heap_prepare(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2056:5 frame #14: 0x00000001000e1074 ruby`newobj_cache_miss [inlined] heap_next_free_page(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2280:9 frame #15: 0x00000001000e106c ruby`newobj_cache_miss(objspace=0x000000012500c400, cache=0x0000600001b00300, heap_idx=2, vm_locked=false) at default.c:2387:38 frame #16: 0x00000001000e0d28 ruby`newobj_alloc(objspace=, cache=, heap_idx=, vm_locked=) at default.c:2411:15 [artificial] frame #17: 0x00000001000d7214 ruby`newobj_of [inlined] rb_gc_impl_new_obj(objspace_ptr=, cache_ptr=, klass=, flags=, wb_protected=, alloc_size=) at default.c:2490:15 frame #18: 0x00000001000d719c ruby`newobj_of(cr=, klass=4313971728, flags=258, wb_protected=, size=) at gc.c:995:17 frame #19: 0x00000001000d73ec ruby`rb_wb_protected_newobj_of(ec=, klass=, flags=, size=) at gc.c:1044:12 [artificial] frame #20: 0x0000000100032d34 ruby`class_alloc0(type=, klass=4313971728, namespaceable=) at class.c:803:5 ``` --- class.c | 2 +- internal/class.h | 12 ++++++------ test/ruby/test_class.rb | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/class.c b/class.c index a4249425a13a93..04c66ce8d54962 100644 --- a/class.c +++ b/class.c @@ -690,7 +690,7 @@ rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass, bool replacin } VM_ASSERT( rb_ns_subclasses_ref_count(anchor->ns_subclasses) > 0, - "ns_subclasses refcount (%p) %ld", anchor->ns_subclasses, rb_ns_subclasses_ref_count(anchor->ns_subclasses)); + "ns_subclasses refcount (%p) %d", anchor->ns_subclasses, rb_ns_subclasses_ref_count(anchor->ns_subclasses)); st_delete(tbl, &ns_id, NULL); rb_ns_subclasses_ref_dec(anchor->ns_subclasses); xfree(anchor); diff --git a/internal/class.h b/internal/class.h index 138620dd6f0469..f182211705e1f5 100644 --- a/internal/class.h +++ b/internal/class.h @@ -28,29 +28,29 @@ #endif struct rb_ns_subclasses { - long refcount; + rb_atomic_t refcount; struct st_table *tbl; }; typedef struct rb_ns_subclasses rb_ns_subclasses_t; -static inline long +static inline rb_atomic_t rb_ns_subclasses_ref_count(rb_ns_subclasses_t *ns_sub) { - return ns_sub->refcount; + return ATOMIC_LOAD_RELAXED(ns_sub->refcount); } static inline rb_ns_subclasses_t * rb_ns_subclasses_ref_inc(rb_ns_subclasses_t *ns_sub) { - ns_sub->refcount++; + RUBY_ATOMIC_FETCH_ADD(ns_sub->refcount, 1); return ns_sub; } static inline void rb_ns_subclasses_ref_dec(rb_ns_subclasses_t *ns_sub) { - ns_sub->refcount--; - if (ns_sub->refcount == 0) { + rb_atomic_t was = RUBY_ATOMIC_FETCH_SUB(ns_sub->refcount, 1); + if (was == 1) { st_free_table(ns_sub->tbl); xfree(ns_sub); } diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index f40817e7a1ef54..cb05751da16c16 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -887,4 +887,19 @@ def test_method_table_assignment_just_after_class_init class C; end end; end + + def test_subclasses_refcount_in_ractors + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + rs = [] + 8.times do + rs << Ractor.new do + 5_000.times do + Class.new + end + end + end + rs.each(&:join) + end; + end end From 9343017673bc4587e45737044bfa70bbe83b9b7e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 6 Nov 2025 14:14:30 -0800 Subject: [PATCH 0866/2435] ZJIT: Fix an incomplete comment (#15088) --- zjit/src/codegen.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6a7707dd5a5a17..f90c4605a26050 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2163,7 +2163,8 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result Result { let (mut asm, scratch_reg) = Assembler::new_with_scratch_reg(); asm_comment!(asm, "function_stub_hit trampoline"); From 844132ae8ec28bf64871c0897a0618b801e647f6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 6 Nov 2025 14:14:47 -0800 Subject: [PATCH 0867/2435] ZJIT: Remove obsolete register spill counters (#15089) --- zjit/src/stats.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index fbfac7b42990ee..e1d7c692ed52e0 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -331,8 +331,6 @@ pub enum CompileError { IseqStackTooLarge, ExceptionHandler, OutOfMemory, - RegisterSpillOnAlloc, - RegisterSpillOnCCall, ParseError(ParseError), JitToJitOptional, } @@ -347,8 +345,6 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { IseqStackTooLarge => compile_error_iseq_stack_too_large, ExceptionHandler => compile_error_exception_handler, OutOfMemory => compile_error_out_of_memory, - RegisterSpillOnAlloc => compile_error_register_spill_on_alloc, - RegisterSpillOnCCall => compile_error_register_spill_on_ccall, JitToJitOptional => compile_error_jit_to_jit_optional, ParseError(parse_error) => match parse_error { StackUnderflow(_) => compile_error_parse_stack_underflow, From cf4a034d59913fb71a7dd1b052164984be4a3d14 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 6 Nov 2025 09:29:28 -0800 Subject: [PATCH 0868/2435] Use rb_set_memsize for constant cache tables These were converted to a set in c0417bd094abcc68be913ce49a430df7cefbcd44 --- vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm.c b/vm.c index f0aebf08a38694..5b706dc6c5e227 100644 --- a/vm.c +++ b/vm.c @@ -3445,7 +3445,7 @@ size_t rb_vm_memsize_workqueue(struct ccan_list_head *workqueue); // vm_trace.c static enum rb_id_table_iterator_result vm_memsize_constant_cache_i(ID id, VALUE ics, void *size) { - *((size_t *) size) += rb_st_memsize((st_table *) ics); + *((size_t *) size) += rb_set_memsize((set_table *) ics); return ID_TABLE_CONTINUE; } From f19b29d1dbb2918dee112b0df0cba2ca1165a399 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 5 Nov 2025 12:02:07 +0900 Subject: [PATCH 0869/2435] Add namespace tests about global variables, toplevel methods --- test/ruby/test_namespace.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index daa4e9e0ba3881..4241ce62ff4a92 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -700,8 +700,13 @@ def test_basic_namespace_detections assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; ns = Namespace.new + $gvar1 = 'bar' code = <<~EOC NS1 = Namespace.current + $gvar1 = 'foo' + + def toplevel = $gvar1 + class Foo NS2 = Namespace.current NS2_proc = ->(){ NS2 } @@ -721,6 +726,9 @@ def self.ns7 def self.yield_block = yield def self.call_block(&b) = b.call + + def self.gvar1 = $gvar1 + def self.call_toplevel = toplevel end FOO_NAME = Foo.name @@ -749,6 +757,10 @@ def foo_namespace = Namespace.current assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block + assert_equal 'foo', ns::Foo.gvar1 # method refers gvar + assert_equal 'bar', $gvar1 # gvar value out of the ns + assert_equal 'foo', ns::Foo.call_toplevel # toplevel method referring gvar + assert_equal ns, ns::NS_X # on TOP frame, referring a class in the current assert_equal ns, ns::NS_Y # on TOP frame, referring Kernel method defined by a CFUNC method From 50b9d9d355b2c64101ebdd2f2de0e9010bb050dc Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 5 Nov 2025 13:52:45 +0900 Subject: [PATCH 0870/2435] rename namespace.c (and others) to box.c --- namespace.c => box.c | 0 doc/{namespace.md => box.h} | 0 ext/-test-/{namespace => box}/yay1/extconf.rb | 0 ext/-test-/{namespace => box}/yay1/yay1.c | 0 ext/-test-/{namespace => box}/yay1/yay1.def | 0 ext/-test-/{namespace => box}/yay1/yay1.h | 0 ext/-test-/{namespace => box}/yay2/extconf.rb | 0 ext/-test-/{namespace => box}/yay2/yay2.c | 0 ext/-test-/{namespace => box}/yay2/yay2.def | 0 ext/-test-/{namespace => box}/yay2/yay2.h | 0 internal/{namespace.h => box.h} | 0 test/-ext-/{namespace => box}/test_load_ext.rb | 0 test/ruby/{namespace => box}/a.1_1_0.rb | 0 test/ruby/{namespace => box}/a.1_2_0.rb | 0 test/ruby/{namespace => box}/a.rb | 0 test/ruby/{namespace => box}/autoloading.rb | 0 test/ruby/{namespace => box}/blank.rb | 0 test/ruby/{namespace => box}/blank1.rb | 0 test/ruby/{namespace => box}/blank2.rb | 0 test/ruby/{namespace => box}/call_proc.rb | 0 test/ruby/{namespace => box}/call_toplevel.rb | 0 test/ruby/{namespace => box}/consts.rb | 0 test/ruby/{namespace => box}/current.rb | 0 test/ruby/{namespace => box}/define_toplevel.rb | 0 test/ruby/{namespace => box}/global_vars.rb | 0 test/ruby/{namespace => box}/instance_variables.rb | 0 test/ruby/{namespace => box}/line_splitter.rb | 0 test/ruby/{namespace => box}/load_path.rb | 0 test/ruby/{namespace => box}/ns.rb | 0 test/ruby/{namespace => box}/open_class_with_include.rb | 0 test/ruby/{namespace => box}/proc_callee.rb | 0 test/ruby/{namespace => box}/proc_caller.rb | 0 test/ruby/{namespace => box}/procs.rb | 0 test/ruby/{namespace => box}/raise.rb | 0 test/ruby/{namespace => box}/returns_proc.rb | 0 test/ruby/{namespace => box}/singleton_methods.rb | 0 test/ruby/{namespace => box}/string_ext.rb | 0 test/ruby/{namespace => box}/string_ext_caller.rb | 0 test/ruby/{namespace => box}/string_ext_calling.rb | 0 test/ruby/{namespace => box}/string_ext_eval_caller.rb | 0 test/ruby/{namespace => box}/top_level.rb | 0 test/ruby/{test_namespace.rb => test_box.c} | 0 42 files changed, 0 insertions(+), 0 deletions(-) rename namespace.c => box.c (100%) rename doc/{namespace.md => box.h} (100%) rename ext/-test-/{namespace => box}/yay1/extconf.rb (100%) rename ext/-test-/{namespace => box}/yay1/yay1.c (100%) rename ext/-test-/{namespace => box}/yay1/yay1.def (100%) rename ext/-test-/{namespace => box}/yay1/yay1.h (100%) rename ext/-test-/{namespace => box}/yay2/extconf.rb (100%) rename ext/-test-/{namespace => box}/yay2/yay2.c (100%) rename ext/-test-/{namespace => box}/yay2/yay2.def (100%) rename ext/-test-/{namespace => box}/yay2/yay2.h (100%) rename internal/{namespace.h => box.h} (100%) rename test/-ext-/{namespace => box}/test_load_ext.rb (100%) rename test/ruby/{namespace => box}/a.1_1_0.rb (100%) rename test/ruby/{namespace => box}/a.1_2_0.rb (100%) rename test/ruby/{namespace => box}/a.rb (100%) rename test/ruby/{namespace => box}/autoloading.rb (100%) rename test/ruby/{namespace => box}/blank.rb (100%) rename test/ruby/{namespace => box}/blank1.rb (100%) rename test/ruby/{namespace => box}/blank2.rb (100%) rename test/ruby/{namespace => box}/call_proc.rb (100%) rename test/ruby/{namespace => box}/call_toplevel.rb (100%) rename test/ruby/{namespace => box}/consts.rb (100%) rename test/ruby/{namespace => box}/current.rb (100%) rename test/ruby/{namespace => box}/define_toplevel.rb (100%) rename test/ruby/{namespace => box}/global_vars.rb (100%) rename test/ruby/{namespace => box}/instance_variables.rb (100%) rename test/ruby/{namespace => box}/line_splitter.rb (100%) rename test/ruby/{namespace => box}/load_path.rb (100%) rename test/ruby/{namespace => box}/ns.rb (100%) rename test/ruby/{namespace => box}/open_class_with_include.rb (100%) rename test/ruby/{namespace => box}/proc_callee.rb (100%) rename test/ruby/{namespace => box}/proc_caller.rb (100%) rename test/ruby/{namespace => box}/procs.rb (100%) rename test/ruby/{namespace => box}/raise.rb (100%) rename test/ruby/{namespace => box}/returns_proc.rb (100%) rename test/ruby/{namespace => box}/singleton_methods.rb (100%) rename test/ruby/{namespace => box}/string_ext.rb (100%) rename test/ruby/{namespace => box}/string_ext_caller.rb (100%) rename test/ruby/{namespace => box}/string_ext_calling.rb (100%) rename test/ruby/{namespace => box}/string_ext_eval_caller.rb (100%) rename test/ruby/{namespace => box}/top_level.rb (100%) rename test/ruby/{test_namespace.rb => test_box.c} (100%) diff --git a/namespace.c b/box.c similarity index 100% rename from namespace.c rename to box.c diff --git a/doc/namespace.md b/doc/box.h similarity index 100% rename from doc/namespace.md rename to doc/box.h diff --git a/ext/-test-/namespace/yay1/extconf.rb b/ext/-test-/box/yay1/extconf.rb similarity index 100% rename from ext/-test-/namespace/yay1/extconf.rb rename to ext/-test-/box/yay1/extconf.rb diff --git a/ext/-test-/namespace/yay1/yay1.c b/ext/-test-/box/yay1/yay1.c similarity index 100% rename from ext/-test-/namespace/yay1/yay1.c rename to ext/-test-/box/yay1/yay1.c diff --git a/ext/-test-/namespace/yay1/yay1.def b/ext/-test-/box/yay1/yay1.def similarity index 100% rename from ext/-test-/namespace/yay1/yay1.def rename to ext/-test-/box/yay1/yay1.def diff --git a/ext/-test-/namespace/yay1/yay1.h b/ext/-test-/box/yay1/yay1.h similarity index 100% rename from ext/-test-/namespace/yay1/yay1.h rename to ext/-test-/box/yay1/yay1.h diff --git a/ext/-test-/namespace/yay2/extconf.rb b/ext/-test-/box/yay2/extconf.rb similarity index 100% rename from ext/-test-/namespace/yay2/extconf.rb rename to ext/-test-/box/yay2/extconf.rb diff --git a/ext/-test-/namespace/yay2/yay2.c b/ext/-test-/box/yay2/yay2.c similarity index 100% rename from ext/-test-/namespace/yay2/yay2.c rename to ext/-test-/box/yay2/yay2.c diff --git a/ext/-test-/namespace/yay2/yay2.def b/ext/-test-/box/yay2/yay2.def similarity index 100% rename from ext/-test-/namespace/yay2/yay2.def rename to ext/-test-/box/yay2/yay2.def diff --git a/ext/-test-/namespace/yay2/yay2.h b/ext/-test-/box/yay2/yay2.h similarity index 100% rename from ext/-test-/namespace/yay2/yay2.h rename to ext/-test-/box/yay2/yay2.h diff --git a/internal/namespace.h b/internal/box.h similarity index 100% rename from internal/namespace.h rename to internal/box.h diff --git a/test/-ext-/namespace/test_load_ext.rb b/test/-ext-/box/test_load_ext.rb similarity index 100% rename from test/-ext-/namespace/test_load_ext.rb rename to test/-ext-/box/test_load_ext.rb diff --git a/test/ruby/namespace/a.1_1_0.rb b/test/ruby/box/a.1_1_0.rb similarity index 100% rename from test/ruby/namespace/a.1_1_0.rb rename to test/ruby/box/a.1_1_0.rb diff --git a/test/ruby/namespace/a.1_2_0.rb b/test/ruby/box/a.1_2_0.rb similarity index 100% rename from test/ruby/namespace/a.1_2_0.rb rename to test/ruby/box/a.1_2_0.rb diff --git a/test/ruby/namespace/a.rb b/test/ruby/box/a.rb similarity index 100% rename from test/ruby/namespace/a.rb rename to test/ruby/box/a.rb diff --git a/test/ruby/namespace/autoloading.rb b/test/ruby/box/autoloading.rb similarity index 100% rename from test/ruby/namespace/autoloading.rb rename to test/ruby/box/autoloading.rb diff --git a/test/ruby/namespace/blank.rb b/test/ruby/box/blank.rb similarity index 100% rename from test/ruby/namespace/blank.rb rename to test/ruby/box/blank.rb diff --git a/test/ruby/namespace/blank1.rb b/test/ruby/box/blank1.rb similarity index 100% rename from test/ruby/namespace/blank1.rb rename to test/ruby/box/blank1.rb diff --git a/test/ruby/namespace/blank2.rb b/test/ruby/box/blank2.rb similarity index 100% rename from test/ruby/namespace/blank2.rb rename to test/ruby/box/blank2.rb diff --git a/test/ruby/namespace/call_proc.rb b/test/ruby/box/call_proc.rb similarity index 100% rename from test/ruby/namespace/call_proc.rb rename to test/ruby/box/call_proc.rb diff --git a/test/ruby/namespace/call_toplevel.rb b/test/ruby/box/call_toplevel.rb similarity index 100% rename from test/ruby/namespace/call_toplevel.rb rename to test/ruby/box/call_toplevel.rb diff --git a/test/ruby/namespace/consts.rb b/test/ruby/box/consts.rb similarity index 100% rename from test/ruby/namespace/consts.rb rename to test/ruby/box/consts.rb diff --git a/test/ruby/namespace/current.rb b/test/ruby/box/current.rb similarity index 100% rename from test/ruby/namespace/current.rb rename to test/ruby/box/current.rb diff --git a/test/ruby/namespace/define_toplevel.rb b/test/ruby/box/define_toplevel.rb similarity index 100% rename from test/ruby/namespace/define_toplevel.rb rename to test/ruby/box/define_toplevel.rb diff --git a/test/ruby/namespace/global_vars.rb b/test/ruby/box/global_vars.rb similarity index 100% rename from test/ruby/namespace/global_vars.rb rename to test/ruby/box/global_vars.rb diff --git a/test/ruby/namespace/instance_variables.rb b/test/ruby/box/instance_variables.rb similarity index 100% rename from test/ruby/namespace/instance_variables.rb rename to test/ruby/box/instance_variables.rb diff --git a/test/ruby/namespace/line_splitter.rb b/test/ruby/box/line_splitter.rb similarity index 100% rename from test/ruby/namespace/line_splitter.rb rename to test/ruby/box/line_splitter.rb diff --git a/test/ruby/namespace/load_path.rb b/test/ruby/box/load_path.rb similarity index 100% rename from test/ruby/namespace/load_path.rb rename to test/ruby/box/load_path.rb diff --git a/test/ruby/namespace/ns.rb b/test/ruby/box/ns.rb similarity index 100% rename from test/ruby/namespace/ns.rb rename to test/ruby/box/ns.rb diff --git a/test/ruby/namespace/open_class_with_include.rb b/test/ruby/box/open_class_with_include.rb similarity index 100% rename from test/ruby/namespace/open_class_with_include.rb rename to test/ruby/box/open_class_with_include.rb diff --git a/test/ruby/namespace/proc_callee.rb b/test/ruby/box/proc_callee.rb similarity index 100% rename from test/ruby/namespace/proc_callee.rb rename to test/ruby/box/proc_callee.rb diff --git a/test/ruby/namespace/proc_caller.rb b/test/ruby/box/proc_caller.rb similarity index 100% rename from test/ruby/namespace/proc_caller.rb rename to test/ruby/box/proc_caller.rb diff --git a/test/ruby/namespace/procs.rb b/test/ruby/box/procs.rb similarity index 100% rename from test/ruby/namespace/procs.rb rename to test/ruby/box/procs.rb diff --git a/test/ruby/namespace/raise.rb b/test/ruby/box/raise.rb similarity index 100% rename from test/ruby/namespace/raise.rb rename to test/ruby/box/raise.rb diff --git a/test/ruby/namespace/returns_proc.rb b/test/ruby/box/returns_proc.rb similarity index 100% rename from test/ruby/namespace/returns_proc.rb rename to test/ruby/box/returns_proc.rb diff --git a/test/ruby/namespace/singleton_methods.rb b/test/ruby/box/singleton_methods.rb similarity index 100% rename from test/ruby/namespace/singleton_methods.rb rename to test/ruby/box/singleton_methods.rb diff --git a/test/ruby/namespace/string_ext.rb b/test/ruby/box/string_ext.rb similarity index 100% rename from test/ruby/namespace/string_ext.rb rename to test/ruby/box/string_ext.rb diff --git a/test/ruby/namespace/string_ext_caller.rb b/test/ruby/box/string_ext_caller.rb similarity index 100% rename from test/ruby/namespace/string_ext_caller.rb rename to test/ruby/box/string_ext_caller.rb diff --git a/test/ruby/namespace/string_ext_calling.rb b/test/ruby/box/string_ext_calling.rb similarity index 100% rename from test/ruby/namespace/string_ext_calling.rb rename to test/ruby/box/string_ext_calling.rb diff --git a/test/ruby/namespace/string_ext_eval_caller.rb b/test/ruby/box/string_ext_eval_caller.rb similarity index 100% rename from test/ruby/namespace/string_ext_eval_caller.rb rename to test/ruby/box/string_ext_eval_caller.rb diff --git a/test/ruby/namespace/top_level.rb b/test/ruby/box/top_level.rb similarity index 100% rename from test/ruby/namespace/top_level.rb rename to test/ruby/box/top_level.rb diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_box.c similarity index 100% rename from test/ruby/test_namespace.rb rename to test/ruby/test_box.c From aaa1234702f0186b1cd7d2cea136eee20fc82153 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 5 Nov 2025 14:29:25 +0900 Subject: [PATCH 0871/2435] update referenced filenames from namespace to box --- box.c | 2 +- class.c | 2 +- common.mk | 2 +- depend | 606 ++++++++++++++++++++++---------------------- dln.c | 2 +- ext/coverage/depend | 2 +- ext/objspace/depend | 6 +- ext/ripper/depend | 2 +- ext/socket/depend | 30 +-- internal/class.h | 2 +- load.c | 2 +- variable.c | 2 +- vm.c | 4 +- vm_core.h | 2 +- 14 files changed, 333 insertions(+), 333 deletions(-) diff --git a/box.c b/box.c index 0efc75e28bf009..a9368ca7440d91 100644 --- a/box.c +++ b/box.c @@ -2,6 +2,7 @@ #include "eval_intern.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/eval.h" #include "internal/error.h" @@ -9,7 +10,6 @@ #include "internal/gc.h" #include "internal/hash.h" #include "internal/load.h" -#include "internal/namespace.h" #include "internal/st.h" #include "internal/variable.h" #include "iseq.h" diff --git a/class.c b/class.c index 04c66ce8d54962..8c3c91cf7a92b4 100644 --- a/class.c +++ b/class.c @@ -21,10 +21,10 @@ #include "debug_counter.h" #include "id_table.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/eval.h" #include "internal/hash.h" -#include "internal/namespace.h" #include "internal/object.h" #include "internal/string.h" #include "internal/variable.h" diff --git a/common.mk b/common.mk index 58a8da14021fff..fe8ef3b19efe43 100644 --- a/common.mk +++ b/common.mk @@ -119,6 +119,7 @@ COMMONOBJS = \ array.$(OBJEXT) \ ast.$(OBJEXT) \ bignum.$(OBJEXT) \ + box.$(OBJEXT) \ class.$(OBJEXT) \ compar.$(OBJEXT) \ compile.$(OBJEXT) \ @@ -146,7 +147,6 @@ COMMONOBJS = \ marshal.$(OBJEXT) \ math.$(OBJEXT) \ memory_view.$(OBJEXT) \ - namespace.$(OBJEXT) \ node.$(OBJEXT) \ node_dump.$(OBJEXT) \ numeric.$(OBJEXT) \ diff --git a/depend b/depend index c908bb6b0102b4..569f14a04da5bf 100644 --- a/depend +++ b/depend @@ -63,6 +63,7 @@ array.$(OBJEXT): $(top_srcdir)/internal/array.h array.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h array.$(OBJEXT): $(top_srcdir)/internal/bignum.h array.$(OBJEXT): $(top_srcdir)/internal/bits.h +array.$(OBJEXT): $(top_srcdir)/internal/box.h array.$(OBJEXT): $(top_srcdir)/internal/class.h array.$(OBJEXT): $(top_srcdir)/internal/compar.h array.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -71,7 +72,6 @@ array.$(OBJEXT): $(top_srcdir)/internal/fixnum.h array.$(OBJEXT): $(top_srcdir)/internal/gc.h array.$(OBJEXT): $(top_srcdir)/internal/hash.h array.$(OBJEXT): $(top_srcdir)/internal/imemo.h -array.$(OBJEXT): $(top_srcdir)/internal/namespace.h array.$(OBJEXT): $(top_srcdir)/internal/numeric.h array.$(OBJEXT): $(top_srcdir)/internal/object.h array.$(OBJEXT): $(top_srcdir)/internal/proc.h @@ -286,12 +286,12 @@ ast.$(OBJEXT): $(top_srcdir)/internal/array.h ast.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ast.$(OBJEXT): $(top_srcdir)/internal/bignum.h ast.$(OBJEXT): $(top_srcdir)/internal/bits.h +ast.$(OBJEXT): $(top_srcdir)/internal/box.h ast.$(OBJEXT): $(top_srcdir)/internal/compilers.h ast.$(OBJEXT): $(top_srcdir)/internal/complex.h ast.$(OBJEXT): $(top_srcdir)/internal/fixnum.h ast.$(OBJEXT): $(top_srcdir)/internal/gc.h ast.$(OBJEXT): $(top_srcdir)/internal/imemo.h -ast.$(OBJEXT): $(top_srcdir)/internal/namespace.h ast.$(OBJEXT): $(top_srcdir)/internal/numeric.h ast.$(OBJEXT): $(top_srcdir)/internal/parse.h ast.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -526,13 +526,13 @@ bignum.$(OBJEXT): $(top_srcdir)/internal/array.h bignum.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h bignum.$(OBJEXT): $(top_srcdir)/internal/bignum.h bignum.$(OBJEXT): $(top_srcdir)/internal/bits.h +bignum.$(OBJEXT): $(top_srcdir)/internal/box.h bignum.$(OBJEXT): $(top_srcdir)/internal/class.h bignum.$(OBJEXT): $(top_srcdir)/internal/compilers.h bignum.$(OBJEXT): $(top_srcdir)/internal/complex.h bignum.$(OBJEXT): $(top_srcdir)/internal/fixnum.h bignum.$(OBJEXT): $(top_srcdir)/internal/gc.h bignum.$(OBJEXT): $(top_srcdir)/internal/imemo.h -bignum.$(OBJEXT): $(top_srcdir)/internal/namespace.h bignum.$(OBJEXT): $(top_srcdir)/internal/numeric.h bignum.$(OBJEXT): $(top_srcdir)/internal/object.h bignum.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -729,6 +729,251 @@ bignum.$(OBJEXT): {$(VPATH)}thread_native.h bignum.$(OBJEXT): {$(VPATH)}util.h bignum.$(OBJEXT): {$(VPATH)}vm_core.h bignum.$(OBJEXT): {$(VPATH)}vm_opts.h +box.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +box.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +box.$(OBJEXT): $(CCAN_DIR)/list/list.h +box.$(OBJEXT): $(CCAN_DIR)/str/str.h +box.$(OBJEXT): $(hdrdir)/ruby/ruby.h +box.$(OBJEXT): $(top_srcdir)/internal/array.h +box.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +box.$(OBJEXT): $(top_srcdir)/internal/box.h +box.$(OBJEXT): $(top_srcdir)/internal/class.h +box.$(OBJEXT): $(top_srcdir)/internal/compilers.h +box.$(OBJEXT): $(top_srcdir)/internal/error.h +box.$(OBJEXT): $(top_srcdir)/internal/eval.h +box.$(OBJEXT): $(top_srcdir)/internal/file.h +box.$(OBJEXT): $(top_srcdir)/internal/gc.h +box.$(OBJEXT): $(top_srcdir)/internal/hash.h +box.$(OBJEXT): $(top_srcdir)/internal/imemo.h +box.$(OBJEXT): $(top_srcdir)/internal/load.h +box.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h +box.$(OBJEXT): $(top_srcdir)/internal/serial.h +box.$(OBJEXT): $(top_srcdir)/internal/set_table.h +box.$(OBJEXT): $(top_srcdir)/internal/st.h +box.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +box.$(OBJEXT): $(top_srcdir)/internal/string.h +box.$(OBJEXT): $(top_srcdir)/internal/variable.h +box.$(OBJEXT): $(top_srcdir)/internal/vm.h +box.$(OBJEXT): $(top_srcdir)/internal/warnings.h +box.$(OBJEXT): $(top_srcdir)/prism/ast.h +box.$(OBJEXT): $(top_srcdir)/prism/defines.h +box.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h +box.$(OBJEXT): $(top_srcdir)/prism/encoding.h +box.$(OBJEXT): $(top_srcdir)/prism/node.h +box.$(OBJEXT): $(top_srcdir)/prism/options.h +box.$(OBJEXT): $(top_srcdir)/prism/pack.h +box.$(OBJEXT): $(top_srcdir)/prism/parser.h +box.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h +box.$(OBJEXT): $(top_srcdir)/prism/prism.h +box.$(OBJEXT): $(top_srcdir)/prism/regexp.h +box.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h +box.$(OBJEXT): $(top_srcdir)/prism/version.h +box.$(OBJEXT): {$(VPATH)}assert.h +box.$(OBJEXT): {$(VPATH)}atomic.h +box.$(OBJEXT): {$(VPATH)}backward/2/assume.h +box.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +box.$(OBJEXT): {$(VPATH)}backward/2/bool.h +box.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +box.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +box.$(OBJEXT): {$(VPATH)}backward/2/limits.h +box.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +box.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +box.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +box.$(OBJEXT): {$(VPATH)}box.c +box.$(OBJEXT): {$(VPATH)}config.h +box.$(OBJEXT): {$(VPATH)}constant.h +box.$(OBJEXT): {$(VPATH)}darray.h +box.$(OBJEXT): {$(VPATH)}debug_counter.h +box.$(OBJEXT): {$(VPATH)}defines.h +box.$(OBJEXT): {$(VPATH)}encoding.h +box.$(OBJEXT): {$(VPATH)}eval_intern.h +box.$(OBJEXT): {$(VPATH)}id.h +box.$(OBJEXT): {$(VPATH)}id_table.h +box.$(OBJEXT): {$(VPATH)}intern.h +box.$(OBJEXT): {$(VPATH)}internal.h +box.$(OBJEXT): {$(VPATH)}internal/abi.h +box.$(OBJEXT): {$(VPATH)}internal/anyargs.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +box.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +box.$(OBJEXT): {$(VPATH)}internal/assume.h +box.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +box.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +box.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +box.$(OBJEXT): {$(VPATH)}internal/attr/const.h +box.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +box.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +box.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +box.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +box.$(OBJEXT): {$(VPATH)}internal/attr/error.h +box.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +box.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +box.$(OBJEXT): {$(VPATH)}internal/attr/format.h +box.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +box.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +box.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +box.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +box.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +box.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +box.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +box.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h +box.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +box.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +box.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +box.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +box.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +box.$(OBJEXT): {$(VPATH)}internal/cast.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +box.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +box.$(OBJEXT): {$(VPATH)}internal/config.h +box.$(OBJEXT): {$(VPATH)}internal/constant_p.h +box.$(OBJEXT): {$(VPATH)}internal/core.h +box.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +box.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +box.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +box.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +box.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +box.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +box.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +box.$(OBJEXT): {$(VPATH)}internal/core/robject.h +box.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +box.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +box.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +box.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +box.$(OBJEXT): {$(VPATH)}internal/ctype.h +box.$(OBJEXT): {$(VPATH)}internal/dllexport.h +box.$(OBJEXT): {$(VPATH)}internal/dosish.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/re.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/string.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h +box.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h +box.$(OBJEXT): {$(VPATH)}internal/error.h +box.$(OBJEXT): {$(VPATH)}internal/eval.h +box.$(OBJEXT): {$(VPATH)}internal/event.h +box.$(OBJEXT): {$(VPATH)}internal/fl_type.h +box.$(OBJEXT): {$(VPATH)}internal/gc.h +box.$(OBJEXT): {$(VPATH)}internal/glob.h +box.$(OBJEXT): {$(VPATH)}internal/globals.h +box.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +box.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +box.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +box.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +box.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +box.$(OBJEXT): {$(VPATH)}internal/has/extension.h +box.$(OBJEXT): {$(VPATH)}internal/has/feature.h +box.$(OBJEXT): {$(VPATH)}internal/has/warning.h +box.$(OBJEXT): {$(VPATH)}internal/intern/array.h +box.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +box.$(OBJEXT): {$(VPATH)}internal/intern/class.h +box.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +box.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +box.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +box.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +box.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +box.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +box.$(OBJEXT): {$(VPATH)}internal/intern/error.h +box.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +box.$(OBJEXT): {$(VPATH)}internal/intern/file.h +box.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +box.$(OBJEXT): {$(VPATH)}internal/intern/io.h +box.$(OBJEXT): {$(VPATH)}internal/intern/load.h +box.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +box.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +box.$(OBJEXT): {$(VPATH)}internal/intern/object.h +box.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +box.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +box.$(OBJEXT): {$(VPATH)}internal/intern/process.h +box.$(OBJEXT): {$(VPATH)}internal/intern/random.h +box.$(OBJEXT): {$(VPATH)}internal/intern/range.h +box.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +box.$(OBJEXT): {$(VPATH)}internal/intern/re.h +box.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +box.$(OBJEXT): {$(VPATH)}internal/intern/select.h +box.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +box.$(OBJEXT): {$(VPATH)}internal/intern/set.h +box.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +box.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +box.$(OBJEXT): {$(VPATH)}internal/intern/string.h +box.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +box.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +box.$(OBJEXT): {$(VPATH)}internal/intern/time.h +box.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +box.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +box.$(OBJEXT): {$(VPATH)}internal/interpreter.h +box.$(OBJEXT): {$(VPATH)}internal/iterator.h +box.$(OBJEXT): {$(VPATH)}internal/memory.h +box.$(OBJEXT): {$(VPATH)}internal/method.h +box.$(OBJEXT): {$(VPATH)}internal/module.h +box.$(OBJEXT): {$(VPATH)}internal/newobj.h +box.$(OBJEXT): {$(VPATH)}internal/scan_args.h +box.$(OBJEXT): {$(VPATH)}internal/special_consts.h +box.$(OBJEXT): {$(VPATH)}internal/static_assert.h +box.$(OBJEXT): {$(VPATH)}internal/stdalign.h +box.$(OBJEXT): {$(VPATH)}internal/stdbool.h +box.$(OBJEXT): {$(VPATH)}internal/stdckdint.h +box.$(OBJEXT): {$(VPATH)}internal/symbol.h +box.$(OBJEXT): {$(VPATH)}internal/value.h +box.$(OBJEXT): {$(VPATH)}internal/value_type.h +box.$(OBJEXT): {$(VPATH)}internal/variable.h +box.$(OBJEXT): {$(VPATH)}internal/warning_push.h +box.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +box.$(OBJEXT): {$(VPATH)}iseq.h +box.$(OBJEXT): {$(VPATH)}method.h +box.$(OBJEXT): {$(VPATH)}missing.h +box.$(OBJEXT): {$(VPATH)}node.h +box.$(OBJEXT): {$(VPATH)}onigmo.h +box.$(OBJEXT): {$(VPATH)}oniguruma.h +box.$(OBJEXT): {$(VPATH)}prism/ast.h +box.$(OBJEXT): {$(VPATH)}prism/diagnostic.h +box.$(OBJEXT): {$(VPATH)}prism/version.h +box.$(OBJEXT): {$(VPATH)}prism_compile.h +box.$(OBJEXT): {$(VPATH)}ruby_assert.h +box.$(OBJEXT): {$(VPATH)}ruby_atomic.h +box.$(OBJEXT): {$(VPATH)}rubyparser.h +box.$(OBJEXT): {$(VPATH)}shape.h +box.$(OBJEXT): {$(VPATH)}st.h +box.$(OBJEXT): {$(VPATH)}subst.h +box.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +box.$(OBJEXT): {$(VPATH)}thread_native.h +box.$(OBJEXT): {$(VPATH)}util.h +box.$(OBJEXT): {$(VPATH)}vm_core.h +box.$(OBJEXT): {$(VPATH)}vm_debug.h +box.$(OBJEXT): {$(VPATH)}vm_opts.h +box.$(OBJEXT): {$(VPATH)}vm_sync.h builtin.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h builtin.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h builtin.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -736,10 +981,10 @@ builtin.$(OBJEXT): $(CCAN_DIR)/str/str.h builtin.$(OBJEXT): $(hdrdir)/ruby/ruby.h builtin.$(OBJEXT): $(top_srcdir)/internal/array.h builtin.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +builtin.$(OBJEXT): $(top_srcdir)/internal/box.h builtin.$(OBJEXT): $(top_srcdir)/internal/compilers.h builtin.$(OBJEXT): $(top_srcdir)/internal/gc.h builtin.$(OBJEXT): $(top_srcdir)/internal/imemo.h -builtin.$(OBJEXT): $(top_srcdir)/internal/namespace.h builtin.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h builtin.$(OBJEXT): $(top_srcdir)/internal/serial.h builtin.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -966,13 +1211,13 @@ class.$(OBJEXT): $(CCAN_DIR)/str/str.h class.$(OBJEXT): $(hdrdir)/ruby/ruby.h class.$(OBJEXT): $(top_srcdir)/internal/array.h class.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +class.$(OBJEXT): $(top_srcdir)/internal/box.h class.$(OBJEXT): $(top_srcdir)/internal/class.h class.$(OBJEXT): $(top_srcdir)/internal/compilers.h class.$(OBJEXT): $(top_srcdir)/internal/eval.h class.$(OBJEXT): $(top_srcdir)/internal/gc.h class.$(OBJEXT): $(top_srcdir)/internal/hash.h class.$(OBJEXT): $(top_srcdir)/internal/imemo.h -class.$(OBJEXT): $(top_srcdir)/internal/namespace.h class.$(OBJEXT): $(top_srcdir)/internal/object.h class.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h class.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -1365,6 +1610,7 @@ compile.$(OBJEXT): $(top_srcdir)/internal/array.h compile.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h compile.$(OBJEXT): $(top_srcdir)/internal/bignum.h compile.$(OBJEXT): $(top_srcdir)/internal/bits.h +compile.$(OBJEXT): $(top_srcdir)/internal/box.h compile.$(OBJEXT): $(top_srcdir)/internal/class.h compile.$(OBJEXT): $(top_srcdir)/internal/compile.h compile.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -1376,7 +1622,6 @@ compile.$(OBJEXT): $(top_srcdir)/internal/gc.h compile.$(OBJEXT): $(top_srcdir)/internal/hash.h compile.$(OBJEXT): $(top_srcdir)/internal/imemo.h compile.$(OBJEXT): $(top_srcdir)/internal/io.h -compile.$(OBJEXT): $(top_srcdir)/internal/namespace.h compile.$(OBJEXT): $(top_srcdir)/internal/numeric.h compile.$(OBJEXT): $(top_srcdir)/internal/object.h compile.$(OBJEXT): $(top_srcdir)/internal/parse.h @@ -1631,6 +1876,7 @@ complex.$(OBJEXT): $(top_srcdir)/internal/array.h complex.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h complex.$(OBJEXT): $(top_srcdir)/internal/bignum.h complex.$(OBJEXT): $(top_srcdir)/internal/bits.h +complex.$(OBJEXT): $(top_srcdir)/internal/box.h complex.$(OBJEXT): $(top_srcdir)/internal/class.h complex.$(OBJEXT): $(top_srcdir)/internal/compilers.h complex.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -1638,7 +1884,6 @@ complex.$(OBJEXT): $(top_srcdir)/internal/fixnum.h complex.$(OBJEXT): $(top_srcdir)/internal/gc.h complex.$(OBJEXT): $(top_srcdir)/internal/imemo.h complex.$(OBJEXT): $(top_srcdir)/internal/math.h -complex.$(OBJEXT): $(top_srcdir)/internal/namespace.h complex.$(OBJEXT): $(top_srcdir)/internal/numeric.h complex.$(OBJEXT): $(top_srcdir)/internal/object.h complex.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -1845,11 +2090,11 @@ concurrent_set.$(OBJEXT): $(CCAN_DIR)/str/str.h concurrent_set.$(OBJEXT): $(hdrdir)/ruby/ruby.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/array.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/box.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/compilers.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/concurrent_set.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/gc.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/imemo.h -concurrent_set.$(OBJEXT): $(top_srcdir)/internal/namespace.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/serial.h concurrent_set.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -2051,13 +2296,13 @@ cont.$(OBJEXT): $(hdrdir)/ruby/ruby.h cont.$(OBJEXT): $(hdrdir)/ruby/version.h cont.$(OBJEXT): $(top_srcdir)/internal/array.h cont.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +cont.$(OBJEXT): $(top_srcdir)/internal/box.h cont.$(OBJEXT): $(top_srcdir)/internal/compilers.h cont.$(OBJEXT): $(top_srcdir)/internal/cont.h cont.$(OBJEXT): $(top_srcdir)/internal/error.h cont.$(OBJEXT): $(top_srcdir)/internal/eval.h cont.$(OBJEXT): $(top_srcdir)/internal/gc.h cont.$(OBJEXT): $(top_srcdir)/internal/imemo.h -cont.$(OBJEXT): $(top_srcdir)/internal/namespace.h cont.$(OBJEXT): $(top_srcdir)/internal/proc.h cont.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h cont.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -2294,11 +2539,11 @@ debug.$(OBJEXT): $(CCAN_DIR)/str/str.h debug.$(OBJEXT): $(hdrdir)/ruby/ruby.h debug.$(OBJEXT): $(top_srcdir)/internal/array.h debug.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +debug.$(OBJEXT): $(top_srcdir)/internal/box.h debug.$(OBJEXT): $(top_srcdir)/internal/class.h debug.$(OBJEXT): $(top_srcdir)/internal/compilers.h debug.$(OBJEXT): $(top_srcdir)/internal/gc.h debug.$(OBJEXT): $(top_srcdir)/internal/imemo.h -debug.$(OBJEXT): $(top_srcdir)/internal/namespace.h debug.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h debug.$(OBJEXT): $(top_srcdir)/internal/serial.h debug.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -2672,6 +2917,7 @@ dir.$(OBJEXT): $(hdrdir)/ruby/ruby.h dir.$(OBJEXT): $(hdrdir)/ruby/version.h dir.$(OBJEXT): $(top_srcdir)/internal/array.h dir.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +dir.$(OBJEXT): $(top_srcdir)/internal/box.h dir.$(OBJEXT): $(top_srcdir)/internal/class.h dir.$(OBJEXT): $(top_srcdir)/internal/compilers.h dir.$(OBJEXT): $(top_srcdir)/internal/dir.h @@ -2681,7 +2927,6 @@ dir.$(OBJEXT): $(top_srcdir)/internal/file.h dir.$(OBJEXT): $(top_srcdir)/internal/gc.h dir.$(OBJEXT): $(top_srcdir)/internal/imemo.h dir.$(OBJEXT): $(top_srcdir)/internal/io.h -dir.$(OBJEXT): $(top_srcdir)/internal/namespace.h dir.$(OBJEXT): $(top_srcdir)/internal/object.h dir.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h dir.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -2883,8 +3128,8 @@ dir.$(OBJEXT): {$(VPATH)}util.h dir.$(OBJEXT): {$(VPATH)}vm_core.h dir.$(OBJEXT): {$(VPATH)}vm_opts.h dln.$(OBJEXT): $(hdrdir)/ruby/ruby.h +dln.$(OBJEXT): $(top_srcdir)/internal/box.h dln.$(OBJEXT): $(top_srcdir)/internal/compilers.h -dln.$(OBJEXT): $(top_srcdir)/internal/namespace.h dln.$(OBJEXT): $(top_srcdir)/internal/warnings.h dln.$(OBJEXT): {$(VPATH)}assert.h dln.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -4613,6 +4858,7 @@ enumerator.$(OBJEXT): $(top_srcdir)/internal/array.h enumerator.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bignum.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bits.h +enumerator.$(OBJEXT): $(top_srcdir)/internal/box.h enumerator.$(OBJEXT): $(top_srcdir)/internal/class.h enumerator.$(OBJEXT): $(top_srcdir)/internal/compilers.h enumerator.$(OBJEXT): $(top_srcdir)/internal/enumerator.h @@ -4621,7 +4867,6 @@ enumerator.$(OBJEXT): $(top_srcdir)/internal/fixnum.h enumerator.$(OBJEXT): $(top_srcdir)/internal/gc.h enumerator.$(OBJEXT): $(top_srcdir)/internal/hash.h enumerator.$(OBJEXT): $(top_srcdir)/internal/imemo.h -enumerator.$(OBJEXT): $(top_srcdir)/internal/namespace.h enumerator.$(OBJEXT): $(top_srcdir)/internal/numeric.h enumerator.$(OBJEXT): $(top_srcdir)/internal/range.h enumerator.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -4830,6 +5075,7 @@ error.$(OBJEXT): $(hdrdir)/ruby/ruby.h error.$(OBJEXT): $(hdrdir)/ruby/version.h error.$(OBJEXT): $(top_srcdir)/internal/array.h error.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +error.$(OBJEXT): $(top_srcdir)/internal/box.h error.$(OBJEXT): $(top_srcdir)/internal/class.h error.$(OBJEXT): $(top_srcdir)/internal/compilers.h error.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -4839,7 +5085,6 @@ error.$(OBJEXT): $(top_srcdir)/internal/hash.h error.$(OBJEXT): $(top_srcdir)/internal/imemo.h error.$(OBJEXT): $(top_srcdir)/internal/io.h error.$(OBJEXT): $(top_srcdir)/internal/load.h -error.$(OBJEXT): $(top_srcdir)/internal/namespace.h error.$(OBJEXT): $(top_srcdir)/internal/object.h error.$(OBJEXT): $(top_srcdir)/internal/process.h error.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -5055,6 +5300,7 @@ eval.$(OBJEXT): $(hdrdir)/ruby/ruby.h eval.$(OBJEXT): $(hdrdir)/ruby/version.h eval.$(OBJEXT): $(top_srcdir)/internal/array.h eval.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +eval.$(OBJEXT): $(top_srcdir)/internal/box.h eval.$(OBJEXT): $(top_srcdir)/internal/class.h eval.$(OBJEXT): $(top_srcdir)/internal/compilers.h eval.$(OBJEXT): $(top_srcdir)/internal/cont.h @@ -5065,7 +5311,6 @@ eval.$(OBJEXT): $(top_srcdir)/internal/hash.h eval.$(OBJEXT): $(top_srcdir)/internal/imemo.h eval.$(OBJEXT): $(top_srcdir)/internal/inits.h eval.$(OBJEXT): $(top_srcdir)/internal/io.h -eval.$(OBJEXT): $(top_srcdir)/internal/namespace.h eval.$(OBJEXT): $(top_srcdir)/internal/object.h eval.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h eval.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -5536,6 +5781,7 @@ gc.$(OBJEXT): $(top_srcdir)/internal/array.h gc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h gc.$(OBJEXT): $(top_srcdir)/internal/bignum.h gc.$(OBJEXT): $(top_srcdir)/internal/bits.h +gc.$(OBJEXT): $(top_srcdir)/internal/box.h gc.$(OBJEXT): $(top_srcdir)/internal/class.h gc.$(OBJEXT): $(top_srcdir)/internal/compile.h gc.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -5549,7 +5795,6 @@ gc.$(OBJEXT): $(top_srcdir)/internal/gc.h gc.$(OBJEXT): $(top_srcdir)/internal/hash.h gc.$(OBJEXT): $(top_srcdir)/internal/imemo.h gc.$(OBJEXT): $(top_srcdir)/internal/io.h -gc.$(OBJEXT): $(top_srcdir)/internal/namespace.h gc.$(OBJEXT): $(top_srcdir)/internal/numeric.h gc.$(OBJEXT): $(top_srcdir)/internal/object.h gc.$(OBJEXT): $(top_srcdir)/internal/proc.h @@ -5811,12 +6056,12 @@ goruby.$(OBJEXT): $(top_srcdir)/internal/array.h goruby.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h goruby.$(OBJEXT): $(top_srcdir)/internal/bignum.h goruby.$(OBJEXT): $(top_srcdir)/internal/bits.h +goruby.$(OBJEXT): $(top_srcdir)/internal/box.h goruby.$(OBJEXT): $(top_srcdir)/internal/compilers.h goruby.$(OBJEXT): $(top_srcdir)/internal/complex.h goruby.$(OBJEXT): $(top_srcdir)/internal/fixnum.h goruby.$(OBJEXT): $(top_srcdir)/internal/gc.h goruby.$(OBJEXT): $(top_srcdir)/internal/imemo.h -goruby.$(OBJEXT): $(top_srcdir)/internal/namespace.h goruby.$(OBJEXT): $(top_srcdir)/internal/numeric.h goruby.$(OBJEXT): $(top_srcdir)/internal/parse.h goruby.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -6052,6 +6297,7 @@ hash.$(OBJEXT): $(top_srcdir)/internal/array.h hash.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h hash.$(OBJEXT): $(top_srcdir)/internal/bignum.h hash.$(OBJEXT): $(top_srcdir)/internal/bits.h +hash.$(OBJEXT): $(top_srcdir)/internal/box.h hash.$(OBJEXT): $(top_srcdir)/internal/class.h hash.$(OBJEXT): $(top_srcdir)/internal/compilers.h hash.$(OBJEXT): $(top_srcdir)/internal/cont.h @@ -6059,7 +6305,6 @@ hash.$(OBJEXT): $(top_srcdir)/internal/error.h hash.$(OBJEXT): $(top_srcdir)/internal/gc.h hash.$(OBJEXT): $(top_srcdir)/internal/hash.h hash.$(OBJEXT): $(top_srcdir)/internal/imemo.h -hash.$(OBJEXT): $(top_srcdir)/internal/namespace.h hash.$(OBJEXT): $(top_srcdir)/internal/object.h hash.$(OBJEXT): $(top_srcdir)/internal/proc.h hash.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -6304,11 +6549,11 @@ imemo.$(OBJEXT): $(CCAN_DIR)/str/str.h imemo.$(OBJEXT): $(hdrdir)/ruby/ruby.h imemo.$(OBJEXT): $(top_srcdir)/internal/array.h imemo.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +imemo.$(OBJEXT): $(top_srcdir)/internal/box.h imemo.$(OBJEXT): $(top_srcdir)/internal/class.h imemo.$(OBJEXT): $(top_srcdir)/internal/compilers.h imemo.$(OBJEXT): $(top_srcdir)/internal/gc.h imemo.$(OBJEXT): $(top_srcdir)/internal/imemo.h -imemo.$(OBJEXT): $(top_srcdir)/internal/namespace.h imemo.$(OBJEXT): $(top_srcdir)/internal/object.h imemo.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h imemo.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -6682,6 +6927,7 @@ io.$(OBJEXT): $(top_srcdir)/internal/array.h io.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h io.$(OBJEXT): $(top_srcdir)/internal/bignum.h io.$(OBJEXT): $(top_srcdir)/internal/bits.h +io.$(OBJEXT): $(top_srcdir)/internal/box.h io.$(OBJEXT): $(top_srcdir)/internal/class.h io.$(OBJEXT): $(top_srcdir)/internal/compilers.h io.$(OBJEXT): $(top_srcdir)/internal/encoding.h @@ -6691,7 +6937,6 @@ io.$(OBJEXT): $(top_srcdir)/internal/gc.h io.$(OBJEXT): $(top_srcdir)/internal/imemo.h io.$(OBJEXT): $(top_srcdir)/internal/inits.h io.$(OBJEXT): $(top_srcdir)/internal/io.h -io.$(OBJEXT): $(top_srcdir)/internal/namespace.h io.$(OBJEXT): $(top_srcdir)/internal/numeric.h io.$(OBJEXT): $(top_srcdir)/internal/object.h io.$(OBJEXT): $(top_srcdir)/internal/process.h @@ -6913,13 +7158,13 @@ io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/box.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/error.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/fixnum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/gc.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/imemo.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/io.h -io_buffer.$(OBJEXT): $(top_srcdir)/internal/namespace.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/numeric.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -7127,6 +7372,7 @@ iseq.$(OBJEXT): $(top_srcdir)/internal/array.h iseq.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h iseq.$(OBJEXT): $(top_srcdir)/internal/bignum.h iseq.$(OBJEXT): $(top_srcdir)/internal/bits.h +iseq.$(OBJEXT): $(top_srcdir)/internal/box.h iseq.$(OBJEXT): $(top_srcdir)/internal/class.h iseq.$(OBJEXT): $(top_srcdir)/internal/compile.h iseq.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -7138,7 +7384,6 @@ iseq.$(OBJEXT): $(top_srcdir)/internal/gc.h iseq.$(OBJEXT): $(top_srcdir)/internal/hash.h iseq.$(OBJEXT): $(top_srcdir)/internal/imemo.h iseq.$(OBJEXT): $(top_srcdir)/internal/io.h -iseq.$(OBJEXT): $(top_srcdir)/internal/namespace.h iseq.$(OBJEXT): $(top_srcdir)/internal/numeric.h iseq.$(OBJEXT): $(top_srcdir)/internal/parse.h iseq.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -7385,12 +7630,12 @@ jit.$(OBJEXT): $(hdrdir)/ruby/ruby.h jit.$(OBJEXT): $(top_srcdir)/internal/array.h jit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h jit.$(OBJEXT): $(top_srcdir)/internal/bits.h +jit.$(OBJEXT): $(top_srcdir)/internal/box.h jit.$(OBJEXT): $(top_srcdir)/internal/class.h jit.$(OBJEXT): $(top_srcdir)/internal/compilers.h jit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h jit.$(OBJEXT): $(top_srcdir)/internal/gc.h jit.$(OBJEXT): $(top_srcdir)/internal/imemo.h -jit.$(OBJEXT): $(top_srcdir)/internal/namespace.h jit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h jit.$(OBJEXT): $(top_srcdir)/internal/serial.h jit.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -7627,6 +7872,7 @@ load.$(OBJEXT): $(top_srcdir)/internal/array.h load.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h load.$(OBJEXT): $(top_srcdir)/internal/bignum.h load.$(OBJEXT): $(top_srcdir)/internal/bits.h +load.$(OBJEXT): $(top_srcdir)/internal/box.h load.$(OBJEXT): $(top_srcdir)/internal/compilers.h load.$(OBJEXT): $(top_srcdir)/internal/complex.h load.$(OBJEXT): $(top_srcdir)/internal/dir.h @@ -7638,7 +7884,6 @@ load.$(OBJEXT): $(top_srcdir)/internal/gc.h load.$(OBJEXT): $(top_srcdir)/internal/hash.h load.$(OBJEXT): $(top_srcdir)/internal/imemo.h load.$(OBJEXT): $(top_srcdir)/internal/load.h -load.$(OBJEXT): $(top_srcdir)/internal/namespace.h load.$(OBJEXT): $(top_srcdir)/internal/numeric.h load.$(OBJEXT): $(top_srcdir)/internal/parse.h load.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -8379,6 +8624,7 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/array.h marshal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h marshal.$(OBJEXT): $(top_srcdir)/internal/bignum.h marshal.$(OBJEXT): $(top_srcdir)/internal/bits.h +marshal.$(OBJEXT): $(top_srcdir)/internal/box.h marshal.$(OBJEXT): $(top_srcdir)/internal/class.h marshal.$(OBJEXT): $(top_srcdir)/internal/compilers.h marshal.$(OBJEXT): $(top_srcdir)/internal/encoding.h @@ -8387,7 +8633,6 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/fixnum.h marshal.$(OBJEXT): $(top_srcdir)/internal/gc.h marshal.$(OBJEXT): $(top_srcdir)/internal/hash.h marshal.$(OBJEXT): $(top_srcdir)/internal/imemo.h -marshal.$(OBJEXT): $(top_srcdir)/internal/namespace.h marshal.$(OBJEXT): $(top_srcdir)/internal/numeric.h marshal.$(OBJEXT): $(top_srcdir)/internal/object.h marshal.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -8777,11 +9022,11 @@ memory_view.$(OBJEXT): $(CCAN_DIR)/str/str.h memory_view.$(OBJEXT): $(hdrdir)/ruby/ruby.h memory_view.$(OBJEXT): $(top_srcdir)/internal/array.h memory_view.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/box.h memory_view.$(OBJEXT): $(top_srcdir)/internal/compilers.h memory_view.$(OBJEXT): $(top_srcdir)/internal/gc.h memory_view.$(OBJEXT): $(top_srcdir)/internal/hash.h memory_view.$(OBJEXT): $(top_srcdir)/internal/imemo.h -memory_view.$(OBJEXT): $(top_srcdir)/internal/namespace.h memory_view.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h memory_view.$(OBJEXT): $(top_srcdir)/internal/serial.h memory_view.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -8988,13 +9233,13 @@ miniinit.$(OBJEXT): $(top_srcdir)/internal/array.h miniinit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h miniinit.$(OBJEXT): $(top_srcdir)/internal/bignum.h miniinit.$(OBJEXT): $(top_srcdir)/internal/bits.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/box.h miniinit.$(OBJEXT): $(top_srcdir)/internal/compilers.h miniinit.$(OBJEXT): $(top_srcdir)/internal/complex.h miniinit.$(OBJEXT): $(top_srcdir)/internal/eval.h miniinit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h miniinit.$(OBJEXT): $(top_srcdir)/internal/gc.h miniinit.$(OBJEXT): $(top_srcdir)/internal/imemo.h -miniinit.$(OBJEXT): $(top_srcdir)/internal/namespace.h miniinit.$(OBJEXT): $(top_srcdir)/internal/numeric.h miniinit.$(OBJEXT): $(top_srcdir)/internal/parse.h miniinit.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -9243,251 +9488,6 @@ miniinit.$(OBJEXT): {$(VPATH)}vm_opts.h miniinit.$(OBJEXT): {$(VPATH)}warning.rb miniinit.$(OBJEXT): {$(VPATH)}yjit.rb miniinit.$(OBJEXT): {$(VPATH)}zjit.rb -namespace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h -namespace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h -namespace.$(OBJEXT): $(CCAN_DIR)/list/list.h -namespace.$(OBJEXT): $(CCAN_DIR)/str/str.h -namespace.$(OBJEXT): $(hdrdir)/ruby/ruby.h -namespace.$(OBJEXT): $(top_srcdir)/internal/array.h -namespace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h -namespace.$(OBJEXT): $(top_srcdir)/internal/class.h -namespace.$(OBJEXT): $(top_srcdir)/internal/compilers.h -namespace.$(OBJEXT): $(top_srcdir)/internal/error.h -namespace.$(OBJEXT): $(top_srcdir)/internal/eval.h -namespace.$(OBJEXT): $(top_srcdir)/internal/file.h -namespace.$(OBJEXT): $(top_srcdir)/internal/gc.h -namespace.$(OBJEXT): $(top_srcdir)/internal/hash.h -namespace.$(OBJEXT): $(top_srcdir)/internal/imemo.h -namespace.$(OBJEXT): $(top_srcdir)/internal/load.h -namespace.$(OBJEXT): $(top_srcdir)/internal/namespace.h -namespace.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h -namespace.$(OBJEXT): $(top_srcdir)/internal/serial.h -namespace.$(OBJEXT): $(top_srcdir)/internal/set_table.h -namespace.$(OBJEXT): $(top_srcdir)/internal/st.h -namespace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h -namespace.$(OBJEXT): $(top_srcdir)/internal/string.h -namespace.$(OBJEXT): $(top_srcdir)/internal/variable.h -namespace.$(OBJEXT): $(top_srcdir)/internal/vm.h -namespace.$(OBJEXT): $(top_srcdir)/internal/warnings.h -namespace.$(OBJEXT): $(top_srcdir)/prism/ast.h -namespace.$(OBJEXT): $(top_srcdir)/prism/defines.h -namespace.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h -namespace.$(OBJEXT): $(top_srcdir)/prism/encoding.h -namespace.$(OBJEXT): $(top_srcdir)/prism/node.h -namespace.$(OBJEXT): $(top_srcdir)/prism/options.h -namespace.$(OBJEXT): $(top_srcdir)/prism/pack.h -namespace.$(OBJEXT): $(top_srcdir)/prism/parser.h -namespace.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h -namespace.$(OBJEXT): $(top_srcdir)/prism/prism.h -namespace.$(OBJEXT): $(top_srcdir)/prism/regexp.h -namespace.$(OBJEXT): $(top_srcdir)/prism/static_literals.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h -namespace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h -namespace.$(OBJEXT): $(top_srcdir)/prism/version.h -namespace.$(OBJEXT): {$(VPATH)}assert.h -namespace.$(OBJEXT): {$(VPATH)}atomic.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/assume.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/attributes.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/bool.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/limits.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/long_long.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h -namespace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h -namespace.$(OBJEXT): {$(VPATH)}config.h -namespace.$(OBJEXT): {$(VPATH)}constant.h -namespace.$(OBJEXT): {$(VPATH)}darray.h -namespace.$(OBJEXT): {$(VPATH)}debug_counter.h -namespace.$(OBJEXT): {$(VPATH)}defines.h -namespace.$(OBJEXT): {$(VPATH)}encoding.h -namespace.$(OBJEXT): {$(VPATH)}eval_intern.h -namespace.$(OBJEXT): {$(VPATH)}id.h -namespace.$(OBJEXT): {$(VPATH)}id_table.h -namespace.$(OBJEXT): {$(VPATH)}intern.h -namespace.$(OBJEXT): {$(VPATH)}internal.h -namespace.$(OBJEXT): {$(VPATH)}internal/abi.h -namespace.$(OBJEXT): {$(VPATH)}internal/anyargs.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h -namespace.$(OBJEXT): {$(VPATH)}internal/assume.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/cold.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/const.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/error.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/format.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/pure.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/warning.h -namespace.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h -namespace.$(OBJEXT): {$(VPATH)}internal/cast.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h -namespace.$(OBJEXT): {$(VPATH)}internal/compiler_since.h -namespace.$(OBJEXT): {$(VPATH)}internal/config.h -namespace.$(OBJEXT): {$(VPATH)}internal/constant_p.h -namespace.$(OBJEXT): {$(VPATH)}internal/core.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rarray.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rclass.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rdata.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rfile.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rhash.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/robject.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rstring.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h -namespace.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h -namespace.$(OBJEXT): {$(VPATH)}internal/ctype.h -namespace.$(OBJEXT): {$(VPATH)}internal/dllexport.h -namespace.$(OBJEXT): {$(VPATH)}internal/dosish.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/re.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/string.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h -namespace.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h -namespace.$(OBJEXT): {$(VPATH)}internal/error.h -namespace.$(OBJEXT): {$(VPATH)}internal/eval.h -namespace.$(OBJEXT): {$(VPATH)}internal/event.h -namespace.$(OBJEXT): {$(VPATH)}internal/fl_type.h -namespace.$(OBJEXT): {$(VPATH)}internal/gc.h -namespace.$(OBJEXT): {$(VPATH)}internal/glob.h -namespace.$(OBJEXT): {$(VPATH)}internal/globals.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/attribute.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/builtin.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/extension.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/feature.h -namespace.$(OBJEXT): {$(VPATH)}internal/has/warning.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/array.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/class.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/compar.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/complex.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/cont.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/dir.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/enum.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/error.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/eval.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/file.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/hash.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/io.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/load.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/object.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/parse.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/proc.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/process.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/random.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/range.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/rational.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/re.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/select.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/set.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/signal.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/string.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/struct.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/thread.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/time.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/variable.h -namespace.$(OBJEXT): {$(VPATH)}internal/intern/vm.h -namespace.$(OBJEXT): {$(VPATH)}internal/interpreter.h -namespace.$(OBJEXT): {$(VPATH)}internal/iterator.h -namespace.$(OBJEXT): {$(VPATH)}internal/memory.h -namespace.$(OBJEXT): {$(VPATH)}internal/method.h -namespace.$(OBJEXT): {$(VPATH)}internal/module.h -namespace.$(OBJEXT): {$(VPATH)}internal/newobj.h -namespace.$(OBJEXT): {$(VPATH)}internal/scan_args.h -namespace.$(OBJEXT): {$(VPATH)}internal/special_consts.h -namespace.$(OBJEXT): {$(VPATH)}internal/static_assert.h -namespace.$(OBJEXT): {$(VPATH)}internal/stdalign.h -namespace.$(OBJEXT): {$(VPATH)}internal/stdbool.h -namespace.$(OBJEXT): {$(VPATH)}internal/stdckdint.h -namespace.$(OBJEXT): {$(VPATH)}internal/symbol.h -namespace.$(OBJEXT): {$(VPATH)}internal/value.h -namespace.$(OBJEXT): {$(VPATH)}internal/value_type.h -namespace.$(OBJEXT): {$(VPATH)}internal/variable.h -namespace.$(OBJEXT): {$(VPATH)}internal/warning_push.h -namespace.$(OBJEXT): {$(VPATH)}internal/xmalloc.h -namespace.$(OBJEXT): {$(VPATH)}iseq.h -namespace.$(OBJEXT): {$(VPATH)}method.h -namespace.$(OBJEXT): {$(VPATH)}missing.h -namespace.$(OBJEXT): {$(VPATH)}namespace.c -namespace.$(OBJEXT): {$(VPATH)}node.h -namespace.$(OBJEXT): {$(VPATH)}onigmo.h -namespace.$(OBJEXT): {$(VPATH)}oniguruma.h -namespace.$(OBJEXT): {$(VPATH)}prism/ast.h -namespace.$(OBJEXT): {$(VPATH)}prism/diagnostic.h -namespace.$(OBJEXT): {$(VPATH)}prism/version.h -namespace.$(OBJEXT): {$(VPATH)}prism_compile.h -namespace.$(OBJEXT): {$(VPATH)}ruby_assert.h -namespace.$(OBJEXT): {$(VPATH)}ruby_atomic.h -namespace.$(OBJEXT): {$(VPATH)}rubyparser.h -namespace.$(OBJEXT): {$(VPATH)}shape.h -namespace.$(OBJEXT): {$(VPATH)}st.h -namespace.$(OBJEXT): {$(VPATH)}subst.h -namespace.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h -namespace.$(OBJEXT): {$(VPATH)}thread_native.h -namespace.$(OBJEXT): {$(VPATH)}util.h -namespace.$(OBJEXT): {$(VPATH)}vm_core.h -namespace.$(OBJEXT): {$(VPATH)}vm_debug.h -namespace.$(OBJEXT): {$(VPATH)}vm_opts.h -namespace.$(OBJEXT): {$(VPATH)}vm_sync.h node.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h node.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h node.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -9495,11 +9495,11 @@ node.$(OBJEXT): $(CCAN_DIR)/str/str.h node.$(OBJEXT): $(hdrdir)/ruby/ruby.h node.$(OBJEXT): $(top_srcdir)/internal/array.h node.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +node.$(OBJEXT): $(top_srcdir)/internal/box.h node.$(OBJEXT): $(top_srcdir)/internal/compilers.h node.$(OBJEXT): $(top_srcdir)/internal/gc.h node.$(OBJEXT): $(top_srcdir)/internal/hash.h node.$(OBJEXT): $(top_srcdir)/internal/imemo.h -node.$(OBJEXT): $(top_srcdir)/internal/namespace.h node.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h node.$(OBJEXT): $(top_srcdir)/internal/serial.h node.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -9702,6 +9702,7 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/array.h node_dump.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h node_dump.$(OBJEXT): $(top_srcdir)/internal/bignum.h node_dump.$(OBJEXT): $(top_srcdir)/internal/bits.h +node_dump.$(OBJEXT): $(top_srcdir)/internal/box.h node_dump.$(OBJEXT): $(top_srcdir)/internal/class.h node_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h node_dump.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -9709,7 +9710,6 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/fixnum.h node_dump.$(OBJEXT): $(top_srcdir)/internal/gc.h node_dump.$(OBJEXT): $(top_srcdir)/internal/hash.h node_dump.$(OBJEXT): $(top_srcdir)/internal/imemo.h -node_dump.$(OBJEXT): $(top_srcdir)/internal/namespace.h node_dump.$(OBJEXT): $(top_srcdir)/internal/numeric.h node_dump.$(OBJEXT): $(top_srcdir)/internal/parse.h node_dump.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -9918,6 +9918,7 @@ numeric.$(OBJEXT): $(top_srcdir)/internal/array.h numeric.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h numeric.$(OBJEXT): $(top_srcdir)/internal/bignum.h numeric.$(OBJEXT): $(top_srcdir)/internal/bits.h +numeric.$(OBJEXT): $(top_srcdir)/internal/box.h numeric.$(OBJEXT): $(top_srcdir)/internal/class.h numeric.$(OBJEXT): $(top_srcdir)/internal/compilers.h numeric.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -9926,7 +9927,6 @@ numeric.$(OBJEXT): $(top_srcdir)/internal/fixnum.h numeric.$(OBJEXT): $(top_srcdir)/internal/gc.h numeric.$(OBJEXT): $(top_srcdir)/internal/hash.h numeric.$(OBJEXT): $(top_srcdir)/internal/imemo.h -numeric.$(OBJEXT): $(top_srcdir)/internal/namespace.h numeric.$(OBJEXT): $(top_srcdir)/internal/numeric.h numeric.$(OBJEXT): $(top_srcdir)/internal/object.h numeric.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -10137,6 +10137,7 @@ object.$(OBJEXT): $(top_srcdir)/internal/array.h object.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h object.$(OBJEXT): $(top_srcdir)/internal/bignum.h object.$(OBJEXT): $(top_srcdir)/internal/bits.h +object.$(OBJEXT): $(top_srcdir)/internal/box.h object.$(OBJEXT): $(top_srcdir)/internal/class.h object.$(OBJEXT): $(top_srcdir)/internal/compilers.h object.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -10145,7 +10146,6 @@ object.$(OBJEXT): $(top_srcdir)/internal/fixnum.h object.$(OBJEXT): $(top_srcdir)/internal/gc.h object.$(OBJEXT): $(top_srcdir)/internal/imemo.h object.$(OBJEXT): $(top_srcdir)/internal/inits.h -object.$(OBJEXT): $(top_srcdir)/internal/namespace.h object.$(OBJEXT): $(top_srcdir)/internal/numeric.h object.$(OBJEXT): $(top_srcdir)/internal/object.h object.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -10364,10 +10364,10 @@ pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h pack.$(OBJEXT): $(top_srcdir)/internal/array.h pack.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h pack.$(OBJEXT): $(top_srcdir)/internal/bits.h +pack.$(OBJEXT): $(top_srcdir)/internal/box.h pack.$(OBJEXT): $(top_srcdir)/internal/compilers.h pack.$(OBJEXT): $(top_srcdir)/internal/gc.h pack.$(OBJEXT): $(top_srcdir)/internal/imemo.h -pack.$(OBJEXT): $(top_srcdir)/internal/namespace.h pack.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h pack.$(OBJEXT): $(top_srcdir)/internal/serial.h pack.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -10576,6 +10576,7 @@ parse.$(OBJEXT): $(top_srcdir)/internal/array.h parse.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h parse.$(OBJEXT): $(top_srcdir)/internal/bignum.h parse.$(OBJEXT): $(top_srcdir)/internal/bits.h +parse.$(OBJEXT): $(top_srcdir)/internal/box.h parse.$(OBJEXT): $(top_srcdir)/internal/compile.h parse.$(OBJEXT): $(top_srcdir)/internal/compilers.h parse.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -10586,7 +10587,6 @@ parse.$(OBJEXT): $(top_srcdir)/internal/gc.h parse.$(OBJEXT): $(top_srcdir)/internal/hash.h parse.$(OBJEXT): $(top_srcdir)/internal/imemo.h parse.$(OBJEXT): $(top_srcdir)/internal/io.h -parse.$(OBJEXT): $(top_srcdir)/internal/namespace.h parse.$(OBJEXT): $(top_srcdir)/internal/numeric.h parse.$(OBJEXT): $(top_srcdir)/internal/parse.h parse.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -12040,6 +12040,7 @@ proc.$(OBJEXT): $(hdrdir)/ruby/ruby.h proc.$(OBJEXT): $(hdrdir)/ruby/version.h proc.$(OBJEXT): $(top_srcdir)/internal/array.h proc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +proc.$(OBJEXT): $(top_srcdir)/internal/box.h proc.$(OBJEXT): $(top_srcdir)/internal/class.h proc.$(OBJEXT): $(top_srcdir)/internal/compilers.h proc.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -12047,7 +12048,6 @@ proc.$(OBJEXT): $(top_srcdir)/internal/eval.h proc.$(OBJEXT): $(top_srcdir)/internal/gc.h proc.$(OBJEXT): $(top_srcdir)/internal/hash.h proc.$(OBJEXT): $(top_srcdir)/internal/imemo.h -proc.$(OBJEXT): $(top_srcdir)/internal/namespace.h proc.$(OBJEXT): $(top_srcdir)/internal/object.h proc.$(OBJEXT): $(top_srcdir)/internal/proc.h proc.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -12287,6 +12287,7 @@ process.$(OBJEXT): $(top_srcdir)/internal/array.h process.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h process.$(OBJEXT): $(top_srcdir)/internal/bignum.h process.$(OBJEXT): $(top_srcdir)/internal/bits.h +process.$(OBJEXT): $(top_srcdir)/internal/box.h process.$(OBJEXT): $(top_srcdir)/internal/class.h process.$(OBJEXT): $(top_srcdir)/internal/compilers.h process.$(OBJEXT): $(top_srcdir)/internal/dir.h @@ -12297,7 +12298,6 @@ process.$(OBJEXT): $(top_srcdir)/internal/gc.h process.$(OBJEXT): $(top_srcdir)/internal/hash.h process.$(OBJEXT): $(top_srcdir)/internal/imemo.h process.$(OBJEXT): $(top_srcdir)/internal/io.h -process.$(OBJEXT): $(top_srcdir)/internal/namespace.h process.$(OBJEXT): $(top_srcdir)/internal/numeric.h process.$(OBJEXT): $(top_srcdir)/internal/object.h process.$(OBJEXT): $(top_srcdir)/internal/process.h @@ -12518,6 +12518,7 @@ ractor.$(OBJEXT): $(top_srcdir)/internal/array.h ractor.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ractor.$(OBJEXT): $(top_srcdir)/internal/bignum.h ractor.$(OBJEXT): $(top_srcdir)/internal/bits.h +ractor.$(OBJEXT): $(top_srcdir)/internal/box.h ractor.$(OBJEXT): $(top_srcdir)/internal/compilers.h ractor.$(OBJEXT): $(top_srcdir)/internal/complex.h ractor.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -12525,7 +12526,6 @@ ractor.$(OBJEXT): $(top_srcdir)/internal/fixnum.h ractor.$(OBJEXT): $(top_srcdir)/internal/gc.h ractor.$(OBJEXT): $(top_srcdir)/internal/hash.h ractor.$(OBJEXT): $(top_srcdir)/internal/imemo.h -ractor.$(OBJEXT): $(top_srcdir)/internal/namespace.h ractor.$(OBJEXT): $(top_srcdir)/internal/numeric.h ractor.$(OBJEXT): $(top_srcdir)/internal/object.h ractor.$(OBJEXT): $(top_srcdir)/internal/ractor.h @@ -12750,11 +12750,11 @@ random.$(OBJEXT): $(top_srcdir)/internal/array.h random.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h random.$(OBJEXT): $(top_srcdir)/internal/bignum.h random.$(OBJEXT): $(top_srcdir)/internal/bits.h +random.$(OBJEXT): $(top_srcdir)/internal/box.h random.$(OBJEXT): $(top_srcdir)/internal/compilers.h random.$(OBJEXT): $(top_srcdir)/internal/fixnum.h random.$(OBJEXT): $(top_srcdir)/internal/gc.h random.$(OBJEXT): $(top_srcdir)/internal/imemo.h -random.$(OBJEXT): $(top_srcdir)/internal/namespace.h random.$(OBJEXT): $(top_srcdir)/internal/numeric.h random.$(OBJEXT): $(top_srcdir)/internal/random.h random.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -13159,13 +13159,13 @@ rational.$(OBJEXT): $(top_srcdir)/internal/array.h rational.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h rational.$(OBJEXT): $(top_srcdir)/internal/bignum.h rational.$(OBJEXT): $(top_srcdir)/internal/bits.h +rational.$(OBJEXT): $(top_srcdir)/internal/box.h rational.$(OBJEXT): $(top_srcdir)/internal/class.h rational.$(OBJEXT): $(top_srcdir)/internal/compilers.h rational.$(OBJEXT): $(top_srcdir)/internal/complex.h rational.$(OBJEXT): $(top_srcdir)/internal/fixnum.h rational.$(OBJEXT): $(top_srcdir)/internal/gc.h rational.$(OBJEXT): $(top_srcdir)/internal/imemo.h -rational.$(OBJEXT): $(top_srcdir)/internal/namespace.h rational.$(OBJEXT): $(top_srcdir)/internal/numeric.h rational.$(OBJEXT): $(top_srcdir)/internal/object.h rational.$(OBJEXT): $(top_srcdir)/internal/rational.h @@ -13370,13 +13370,13 @@ re.$(OBJEXT): $(hdrdir)/ruby/ruby.h re.$(OBJEXT): $(top_srcdir)/internal/array.h re.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h re.$(OBJEXT): $(top_srcdir)/internal/bits.h +re.$(OBJEXT): $(top_srcdir)/internal/box.h re.$(OBJEXT): $(top_srcdir)/internal/class.h re.$(OBJEXT): $(top_srcdir)/internal/compilers.h re.$(OBJEXT): $(top_srcdir)/internal/encoding.h re.$(OBJEXT): $(top_srcdir)/internal/gc.h re.$(OBJEXT): $(top_srcdir)/internal/hash.h re.$(OBJEXT): $(top_srcdir)/internal/imemo.h -re.$(OBJEXT): $(top_srcdir)/internal/namespace.h re.$(OBJEXT): $(top_srcdir)/internal/object.h re.$(OBJEXT): $(top_srcdir)/internal/ractor.h re.$(OBJEXT): $(top_srcdir)/internal/re.h @@ -14586,6 +14586,7 @@ ruby.$(OBJEXT): $(top_srcdir)/internal/array.h ruby.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ruby.$(OBJEXT): $(top_srcdir)/internal/bignum.h ruby.$(OBJEXT): $(top_srcdir)/internal/bits.h +ruby.$(OBJEXT): $(top_srcdir)/internal/box.h ruby.$(OBJEXT): $(top_srcdir)/internal/class.h ruby.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h ruby.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -14601,7 +14602,6 @@ ruby.$(OBJEXT): $(top_srcdir)/internal/io.h ruby.$(OBJEXT): $(top_srcdir)/internal/load.h ruby.$(OBJEXT): $(top_srcdir)/internal/loadpath.h ruby.$(OBJEXT): $(top_srcdir)/internal/missing.h -ruby.$(OBJEXT): $(top_srcdir)/internal/namespace.h ruby.$(OBJEXT): $(top_srcdir)/internal/numeric.h ruby.$(OBJEXT): $(top_srcdir)/internal/object.h ruby.$(OBJEXT): $(top_srcdir)/internal/parse.h @@ -15032,10 +15032,10 @@ scheduler.$(OBJEXT): $(CCAN_DIR)/str/str.h scheduler.$(OBJEXT): $(hdrdir)/ruby/ruby.h scheduler.$(OBJEXT): $(top_srcdir)/internal/array.h scheduler.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +scheduler.$(OBJEXT): $(top_srcdir)/internal/box.h scheduler.$(OBJEXT): $(top_srcdir)/internal/compilers.h scheduler.$(OBJEXT): $(top_srcdir)/internal/gc.h scheduler.$(OBJEXT): $(top_srcdir)/internal/imemo.h -scheduler.$(OBJEXT): $(top_srcdir)/internal/namespace.h scheduler.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h scheduler.$(OBJEXT): $(top_srcdir)/internal/serial.h scheduler.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -15242,12 +15242,12 @@ set.$(OBJEXT): $(hdrdir)/ruby/ruby.h set.$(OBJEXT): $(top_srcdir)/internal/array.h set.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h set.$(OBJEXT): $(top_srcdir)/internal/bits.h +set.$(OBJEXT): $(top_srcdir)/internal/box.h set.$(OBJEXT): $(top_srcdir)/internal/compilers.h set.$(OBJEXT): $(top_srcdir)/internal/error.h set.$(OBJEXT): $(top_srcdir)/internal/gc.h set.$(OBJEXT): $(top_srcdir)/internal/hash.h set.$(OBJEXT): $(top_srcdir)/internal/imemo.h -set.$(OBJEXT): $(top_srcdir)/internal/namespace.h set.$(OBJEXT): $(top_srcdir)/internal/proc.h set.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h set.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -15612,12 +15612,12 @@ shape.$(OBJEXT): $(hdrdir)/ruby/ruby.h shape.$(OBJEXT): $(hdrdir)/ruby/version.h shape.$(OBJEXT): $(top_srcdir)/internal/array.h shape.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +shape.$(OBJEXT): $(top_srcdir)/internal/box.h shape.$(OBJEXT): $(top_srcdir)/internal/class.h shape.$(OBJEXT): $(top_srcdir)/internal/compilers.h shape.$(OBJEXT): $(top_srcdir)/internal/error.h shape.$(OBJEXT): $(top_srcdir)/internal/gc.h shape.$(OBJEXT): $(top_srcdir)/internal/imemo.h -shape.$(OBJEXT): $(top_srcdir)/internal/namespace.h shape.$(OBJEXT): $(top_srcdir)/internal/object.h shape.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h shape.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -15827,12 +15827,12 @@ signal.$(OBJEXT): $(hdrdir)/ruby/ruby.h signal.$(OBJEXT): $(hdrdir)/ruby/version.h signal.$(OBJEXT): $(top_srcdir)/internal/array.h signal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +signal.$(OBJEXT): $(top_srcdir)/internal/box.h signal.$(OBJEXT): $(top_srcdir)/internal/compilers.h signal.$(OBJEXT): $(top_srcdir)/internal/error.h signal.$(OBJEXT): $(top_srcdir)/internal/eval.h signal.$(OBJEXT): $(top_srcdir)/internal/gc.h signal.$(OBJEXT): $(top_srcdir)/internal/imemo.h -signal.$(OBJEXT): $(top_srcdir)/internal/namespace.h signal.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h signal.$(OBJEXT): $(top_srcdir)/internal/serial.h signal.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -16594,6 +16594,7 @@ string.$(OBJEXT): $(top_srcdir)/internal/array.h string.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h string.$(OBJEXT): $(top_srcdir)/internal/bignum.h string.$(OBJEXT): $(top_srcdir)/internal/bits.h +string.$(OBJEXT): $(top_srcdir)/internal/box.h string.$(OBJEXT): $(top_srcdir)/internal/class.h string.$(OBJEXT): $(top_srcdir)/internal/compar.h string.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -16604,7 +16605,6 @@ string.$(OBJEXT): $(top_srcdir)/internal/fixnum.h string.$(OBJEXT): $(top_srcdir)/internal/gc.h string.$(OBJEXT): $(top_srcdir)/internal/hash.h string.$(OBJEXT): $(top_srcdir)/internal/imemo.h -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 @@ -16856,13 +16856,13 @@ struct.$(OBJEXT): $(hdrdir)/ruby/ruby.h struct.$(OBJEXT): $(hdrdir)/ruby/version.h struct.$(OBJEXT): $(top_srcdir)/internal/array.h struct.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +struct.$(OBJEXT): $(top_srcdir)/internal/box.h struct.$(OBJEXT): $(top_srcdir)/internal/class.h struct.$(OBJEXT): $(top_srcdir)/internal/compilers.h struct.$(OBJEXT): $(top_srcdir)/internal/error.h struct.$(OBJEXT): $(top_srcdir)/internal/gc.h struct.$(OBJEXT): $(top_srcdir)/internal/hash.h struct.$(OBJEXT): $(top_srcdir)/internal/imemo.h -struct.$(OBJEXT): $(top_srcdir)/internal/namespace.h struct.$(OBJEXT): $(top_srcdir)/internal/object.h struct.$(OBJEXT): $(top_srcdir)/internal/proc.h struct.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -17072,6 +17072,7 @@ symbol.$(OBJEXT): $(hdrdir)/ruby/ruby.h symbol.$(OBJEXT): $(hdrdir)/ruby/version.h symbol.$(OBJEXT): $(top_srcdir)/internal/array.h symbol.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +symbol.$(OBJEXT): $(top_srcdir)/internal/box.h symbol.$(OBJEXT): $(top_srcdir)/internal/class.h symbol.$(OBJEXT): $(top_srcdir)/internal/compilers.h symbol.$(OBJEXT): $(top_srcdir)/internal/concurrent_set.h @@ -17079,7 +17080,6 @@ symbol.$(OBJEXT): $(top_srcdir)/internal/error.h symbol.$(OBJEXT): $(top_srcdir)/internal/gc.h symbol.$(OBJEXT): $(top_srcdir)/internal/hash.h symbol.$(OBJEXT): $(top_srcdir)/internal/imemo.h -symbol.$(OBJEXT): $(top_srcdir)/internal/namespace.h symbol.$(OBJEXT): $(top_srcdir)/internal/object.h symbol.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h symbol.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -17298,6 +17298,7 @@ thread.$(OBJEXT): $(hdrdir)/ruby/version.h thread.$(OBJEXT): $(top_srcdir)/internal/array.h thread.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h thread.$(OBJEXT): $(top_srcdir)/internal/bits.h +thread.$(OBJEXT): $(top_srcdir)/internal/box.h thread.$(OBJEXT): $(top_srcdir)/internal/class.h thread.$(OBJEXT): $(top_srcdir)/internal/compilers.h thread.$(OBJEXT): $(top_srcdir)/internal/cont.h @@ -17307,7 +17308,6 @@ thread.$(OBJEXT): $(top_srcdir)/internal/gc.h thread.$(OBJEXT): $(top_srcdir)/internal/hash.h thread.$(OBJEXT): $(top_srcdir)/internal/imemo.h thread.$(OBJEXT): $(top_srcdir)/internal/io.h -thread.$(OBJEXT): $(top_srcdir)/internal/namespace.h thread.$(OBJEXT): $(top_srcdir)/internal/object.h thread.$(OBJEXT): $(top_srcdir)/internal/proc.h thread.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -17558,13 +17558,13 @@ time.$(OBJEXT): $(top_srcdir)/internal/array.h time.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h time.$(OBJEXT): $(top_srcdir)/internal/bignum.h time.$(OBJEXT): $(top_srcdir)/internal/bits.h +time.$(OBJEXT): $(top_srcdir)/internal/box.h time.$(OBJEXT): $(top_srcdir)/internal/compar.h time.$(OBJEXT): $(top_srcdir)/internal/compilers.h time.$(OBJEXT): $(top_srcdir)/internal/fixnum.h time.$(OBJEXT): $(top_srcdir)/internal/gc.h time.$(OBJEXT): $(top_srcdir)/internal/hash.h time.$(OBJEXT): $(top_srcdir)/internal/imemo.h -time.$(OBJEXT): $(top_srcdir)/internal/namespace.h time.$(OBJEXT): $(top_srcdir)/internal/numeric.h time.$(OBJEXT): $(top_srcdir)/internal/rational.h time.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -18138,6 +18138,7 @@ variable.$(OBJEXT): $(hdrdir)/ruby/ruby.h variable.$(OBJEXT): $(hdrdir)/ruby/version.h variable.$(OBJEXT): $(top_srcdir)/internal/array.h variable.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +variable.$(OBJEXT): $(top_srcdir)/internal/box.h variable.$(OBJEXT): $(top_srcdir)/internal/class.h variable.$(OBJEXT): $(top_srcdir)/internal/compilers.h variable.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -18145,7 +18146,6 @@ variable.$(OBJEXT): $(top_srcdir)/internal/eval.h variable.$(OBJEXT): $(top_srcdir)/internal/gc.h variable.$(OBJEXT): $(top_srcdir)/internal/hash.h variable.$(OBJEXT): $(top_srcdir)/internal/imemo.h -variable.$(OBJEXT): $(top_srcdir)/internal/namespace.h variable.$(OBJEXT): $(top_srcdir)/internal/object.h variable.$(OBJEXT): $(top_srcdir)/internal/re.h variable.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -18361,11 +18361,11 @@ version.$(OBJEXT): $(hdrdir)/ruby/ruby.h version.$(OBJEXT): $(hdrdir)/ruby/version.h version.$(OBJEXT): $(top_srcdir)/internal/array.h version.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +version.$(OBJEXT): $(top_srcdir)/internal/box.h version.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h version.$(OBJEXT): $(top_srcdir)/internal/compilers.h version.$(OBJEXT): $(top_srcdir)/internal/gc.h version.$(OBJEXT): $(top_srcdir)/internal/imemo.h -version.$(OBJEXT): $(top_srcdir)/internal/namespace.h version.$(OBJEXT): $(top_srcdir)/internal/parse.h version.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h version.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -18574,6 +18574,7 @@ vm.$(OBJEXT): $(top_srcdir)/internal/array.h vm.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm.$(OBJEXT): $(top_srcdir)/internal/bignum.h vm.$(OBJEXT): $(top_srcdir)/internal/bits.h +vm.$(OBJEXT): $(top_srcdir)/internal/box.h vm.$(OBJEXT): $(top_srcdir)/internal/class.h vm.$(OBJEXT): $(top_srcdir)/internal/compar.h vm.$(OBJEXT): $(top_srcdir)/internal/compile.h @@ -18589,7 +18590,6 @@ vm.$(OBJEXT): $(top_srcdir)/internal/hash.h vm.$(OBJEXT): $(top_srcdir)/internal/imemo.h vm.$(OBJEXT): $(top_srcdir)/internal/inits.h vm.$(OBJEXT): $(top_srcdir)/internal/missing.h -vm.$(OBJEXT): $(top_srcdir)/internal/namespace.h vm.$(OBJEXT): $(top_srcdir)/internal/numeric.h vm.$(OBJEXT): $(top_srcdir)/internal/object.h vm.$(OBJEXT): $(top_srcdir)/internal/parse.h @@ -18860,12 +18860,12 @@ vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/version.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/array.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/box.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/class.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/error.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/imemo.h -vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/namespace.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -19096,10 +19096,10 @@ vm_dump.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_dump.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/array.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +vm_dump.$(OBJEXT): $(top_srcdir)/internal/box.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/imemo.h -vm_dump.$(OBJEXT): $(top_srcdir)/internal/namespace.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -19330,10 +19330,10 @@ vm_sync.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_sync.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/array.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +vm_sync.$(OBJEXT): $(top_srcdir)/internal/box.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/imemo.h -vm_sync.$(OBJEXT): $(top_srcdir)/internal/namespace.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_sync.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -19541,12 +19541,12 @@ vm_trace.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/array.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/bits.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/box.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/class.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/gc.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/hash.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/imemo.h -vm_trace.$(OBJEXT): $(top_srcdir)/internal/namespace.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -19783,11 +19783,11 @@ weakmap.$(OBJEXT): $(CCAN_DIR)/str/str.h weakmap.$(OBJEXT): $(hdrdir)/ruby/ruby.h weakmap.$(OBJEXT): $(top_srcdir)/internal/array.h weakmap.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/box.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/imemo.h -weakmap.$(OBJEXT): $(top_srcdir)/internal/namespace.h weakmap.$(OBJEXT): $(top_srcdir)/internal/proc.h weakmap.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h weakmap.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -19987,6 +19987,7 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/array.h yjit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h yjit.$(OBJEXT): $(top_srcdir)/internal/bignum.h yjit.$(OBJEXT): $(top_srcdir)/internal/bits.h +yjit.$(OBJEXT): $(top_srcdir)/internal/box.h yjit.$(OBJEXT): $(top_srcdir)/internal/class.h yjit.$(OBJEXT): $(top_srcdir)/internal/compile.h yjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -19995,7 +19996,6 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h yjit.$(OBJEXT): $(top_srcdir)/internal/gc.h yjit.$(OBJEXT): $(top_srcdir)/internal/hash.h yjit.$(OBJEXT): $(top_srcdir)/internal/imemo.h -yjit.$(OBJEXT): $(top_srcdir)/internal/namespace.h yjit.$(OBJEXT): $(top_srcdir)/internal/numeric.h yjit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h yjit.$(OBJEXT): $(top_srcdir)/internal/serial.h diff --git a/dln.c b/dln.c index 5f6b1f0564c7b6..6bc4ef199e8479 100644 --- a/dln.c +++ b/dln.c @@ -25,8 +25,8 @@ static void dln_loaderror(const char *format, ...); #endif #include "dln.h" #include "internal.h" +#include "internal/box.h" #include "internal/compilers.h" -#include "internal/namespace.h" #ifdef HAVE_STDLIB_H # include diff --git a/ext/coverage/depend b/ext/coverage/depend index e042bac7c4dc8b..fb7f9a95af8e93 100644 --- a/ext/coverage/depend +++ b/ext/coverage/depend @@ -183,11 +183,11 @@ coverage.o: $(top_srcdir)/id_table.h coverage.o: $(top_srcdir)/internal.h coverage.o: $(top_srcdir)/internal/array.h coverage.o: $(top_srcdir)/internal/basic_operators.h +coverage.o: $(top_srcdir)/internal/box.h coverage.o: $(top_srcdir)/internal/compilers.h coverage.o: $(top_srcdir)/internal/gc.h coverage.o: $(top_srcdir)/internal/hash.h coverage.o: $(top_srcdir)/internal/imemo.h -coverage.o: $(top_srcdir)/internal/namespace.h coverage.o: $(top_srcdir)/internal/sanitizers.h coverage.o: $(top_srcdir)/internal/serial.h coverage.o: $(top_srcdir)/internal/set_table.h diff --git a/ext/objspace/depend b/ext/objspace/depend index 3c11511575ff52..bc1565aa49c367 100644 --- a/ext/objspace/depend +++ b/ext/objspace/depend @@ -182,10 +182,10 @@ object_tracing.o: $(top_srcdir)/id_table.h object_tracing.o: $(top_srcdir)/internal.h object_tracing.o: $(top_srcdir)/internal/array.h object_tracing.o: $(top_srcdir)/internal/basic_operators.h +object_tracing.o: $(top_srcdir)/internal/box.h object_tracing.o: $(top_srcdir)/internal/compilers.h object_tracing.o: $(top_srcdir)/internal/gc.h object_tracing.o: $(top_srcdir)/internal/imemo.h -object_tracing.o: $(top_srcdir)/internal/namespace.h object_tracing.o: $(top_srcdir)/internal/sanitizers.h object_tracing.o: $(top_srcdir)/internal/serial.h object_tracing.o: $(top_srcdir)/internal/set_table.h @@ -391,12 +391,12 @@ objspace.o: $(top_srcdir)/id_table.h objspace.o: $(top_srcdir)/internal.h objspace.o: $(top_srcdir)/internal/array.h objspace.o: $(top_srcdir)/internal/basic_operators.h +objspace.o: $(top_srcdir)/internal/box.h objspace.o: $(top_srcdir)/internal/class.h objspace.o: $(top_srcdir)/internal/compilers.h objspace.o: $(top_srcdir)/internal/gc.h objspace.o: $(top_srcdir)/internal/hash.h objspace.o: $(top_srcdir)/internal/imemo.h -objspace.o: $(top_srcdir)/internal/namespace.h objspace.o: $(top_srcdir)/internal/sanitizers.h objspace.o: $(top_srcdir)/internal/serial.h objspace.o: $(top_srcdir)/internal/set_table.h @@ -605,13 +605,13 @@ objspace_dump.o: $(top_srcdir)/id_table.h objspace_dump.o: $(top_srcdir)/internal.h objspace_dump.o: $(top_srcdir)/internal/array.h objspace_dump.o: $(top_srcdir)/internal/basic_operators.h +objspace_dump.o: $(top_srcdir)/internal/box.h objspace_dump.o: $(top_srcdir)/internal/class.h objspace_dump.o: $(top_srcdir)/internal/compilers.h objspace_dump.o: $(top_srcdir)/internal/gc.h objspace_dump.o: $(top_srcdir)/internal/hash.h objspace_dump.o: $(top_srcdir)/internal/imemo.h objspace_dump.o: $(top_srcdir)/internal/io.h -objspace_dump.o: $(top_srcdir)/internal/namespace.h objspace_dump.o: $(top_srcdir)/internal/sanitizers.h objspace_dump.o: $(top_srcdir)/internal/serial.h objspace_dump.o: $(top_srcdir)/internal/set_table.h diff --git a/ext/ripper/depend b/ext/ripper/depend index c8ba34962c6da2..0086708d23b0a6 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -584,6 +584,7 @@ ripper.o: $(top_srcdir)/internal/array.h ripper.o: $(top_srcdir)/internal/basic_operators.h ripper.o: $(top_srcdir)/internal/bignum.h ripper.o: $(top_srcdir)/internal/bits.h +ripper.o: $(top_srcdir)/internal/box.h ripper.o: $(top_srcdir)/internal/compile.h ripper.o: $(top_srcdir)/internal/compilers.h ripper.o: $(top_srcdir)/internal/complex.h @@ -594,7 +595,6 @@ ripper.o: $(top_srcdir)/internal/gc.h ripper.o: $(top_srcdir)/internal/hash.h ripper.o: $(top_srcdir)/internal/imemo.h ripper.o: $(top_srcdir)/internal/io.h -ripper.o: $(top_srcdir)/internal/namespace.h ripper.o: $(top_srcdir)/internal/numeric.h ripper.o: $(top_srcdir)/internal/parse.h ripper.o: $(top_srcdir)/internal/rational.h diff --git a/ext/socket/depend b/ext/socket/depend index 6f2d280bfd1d8f..3573dc45e2fa9f 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -197,12 +197,12 @@ ancdata.o: $(top_srcdir)/id_table.h ancdata.o: $(top_srcdir)/internal.h ancdata.o: $(top_srcdir)/internal/array.h ancdata.o: $(top_srcdir)/internal/basic_operators.h +ancdata.o: $(top_srcdir)/internal/box.h ancdata.o: $(top_srcdir)/internal/compilers.h ancdata.o: $(top_srcdir)/internal/error.h ancdata.o: $(top_srcdir)/internal/gc.h ancdata.o: $(top_srcdir)/internal/imemo.h ancdata.o: $(top_srcdir)/internal/io.h -ancdata.o: $(top_srcdir)/internal/namespace.h ancdata.o: $(top_srcdir)/internal/sanitizers.h ancdata.o: $(top_srcdir)/internal/serial.h ancdata.o: $(top_srcdir)/internal/set_table.h @@ -412,12 +412,12 @@ basicsocket.o: $(top_srcdir)/id_table.h basicsocket.o: $(top_srcdir)/internal.h basicsocket.o: $(top_srcdir)/internal/array.h basicsocket.o: $(top_srcdir)/internal/basic_operators.h +basicsocket.o: $(top_srcdir)/internal/box.h basicsocket.o: $(top_srcdir)/internal/compilers.h basicsocket.o: $(top_srcdir)/internal/error.h basicsocket.o: $(top_srcdir)/internal/gc.h basicsocket.o: $(top_srcdir)/internal/imemo.h basicsocket.o: $(top_srcdir)/internal/io.h -basicsocket.o: $(top_srcdir)/internal/namespace.h basicsocket.o: $(top_srcdir)/internal/sanitizers.h basicsocket.o: $(top_srcdir)/internal/serial.h basicsocket.o: $(top_srcdir)/internal/set_table.h @@ -627,12 +627,12 @@ constants.o: $(top_srcdir)/id_table.h constants.o: $(top_srcdir)/internal.h constants.o: $(top_srcdir)/internal/array.h constants.o: $(top_srcdir)/internal/basic_operators.h +constants.o: $(top_srcdir)/internal/box.h constants.o: $(top_srcdir)/internal/compilers.h constants.o: $(top_srcdir)/internal/error.h constants.o: $(top_srcdir)/internal/gc.h constants.o: $(top_srcdir)/internal/imemo.h constants.o: $(top_srcdir)/internal/io.h -constants.o: $(top_srcdir)/internal/namespace.h constants.o: $(top_srcdir)/internal/sanitizers.h constants.o: $(top_srcdir)/internal/serial.h constants.o: $(top_srcdir)/internal/set_table.h @@ -843,12 +843,12 @@ ifaddr.o: $(top_srcdir)/id_table.h ifaddr.o: $(top_srcdir)/internal.h ifaddr.o: $(top_srcdir)/internal/array.h ifaddr.o: $(top_srcdir)/internal/basic_operators.h +ifaddr.o: $(top_srcdir)/internal/box.h ifaddr.o: $(top_srcdir)/internal/compilers.h ifaddr.o: $(top_srcdir)/internal/error.h ifaddr.o: $(top_srcdir)/internal/gc.h ifaddr.o: $(top_srcdir)/internal/imemo.h ifaddr.o: $(top_srcdir)/internal/io.h -ifaddr.o: $(top_srcdir)/internal/namespace.h ifaddr.o: $(top_srcdir)/internal/sanitizers.h ifaddr.o: $(top_srcdir)/internal/serial.h ifaddr.o: $(top_srcdir)/internal/set_table.h @@ -1058,12 +1058,12 @@ init.o: $(top_srcdir)/id_table.h init.o: $(top_srcdir)/internal.h init.o: $(top_srcdir)/internal/array.h init.o: $(top_srcdir)/internal/basic_operators.h +init.o: $(top_srcdir)/internal/box.h init.o: $(top_srcdir)/internal/compilers.h init.o: $(top_srcdir)/internal/error.h init.o: $(top_srcdir)/internal/gc.h init.o: $(top_srcdir)/internal/imemo.h init.o: $(top_srcdir)/internal/io.h -init.o: $(top_srcdir)/internal/namespace.h init.o: $(top_srcdir)/internal/sanitizers.h init.o: $(top_srcdir)/internal/serial.h init.o: $(top_srcdir)/internal/set_table.h @@ -1273,12 +1273,12 @@ ipsocket.o: $(top_srcdir)/id_table.h ipsocket.o: $(top_srcdir)/internal.h ipsocket.o: $(top_srcdir)/internal/array.h ipsocket.o: $(top_srcdir)/internal/basic_operators.h +ipsocket.o: $(top_srcdir)/internal/box.h ipsocket.o: $(top_srcdir)/internal/compilers.h ipsocket.o: $(top_srcdir)/internal/error.h ipsocket.o: $(top_srcdir)/internal/gc.h ipsocket.o: $(top_srcdir)/internal/imemo.h ipsocket.o: $(top_srcdir)/internal/io.h -ipsocket.o: $(top_srcdir)/internal/namespace.h ipsocket.o: $(top_srcdir)/internal/sanitizers.h ipsocket.o: $(top_srcdir)/internal/serial.h ipsocket.o: $(top_srcdir)/internal/set_table.h @@ -1488,12 +1488,12 @@ option.o: $(top_srcdir)/id_table.h option.o: $(top_srcdir)/internal.h option.o: $(top_srcdir)/internal/array.h option.o: $(top_srcdir)/internal/basic_operators.h +option.o: $(top_srcdir)/internal/box.h option.o: $(top_srcdir)/internal/compilers.h option.o: $(top_srcdir)/internal/error.h option.o: $(top_srcdir)/internal/gc.h option.o: $(top_srcdir)/internal/imemo.h option.o: $(top_srcdir)/internal/io.h -option.o: $(top_srcdir)/internal/namespace.h option.o: $(top_srcdir)/internal/sanitizers.h option.o: $(top_srcdir)/internal/serial.h option.o: $(top_srcdir)/internal/set_table.h @@ -1703,12 +1703,12 @@ raddrinfo.o: $(top_srcdir)/id_table.h raddrinfo.o: $(top_srcdir)/internal.h raddrinfo.o: $(top_srcdir)/internal/array.h raddrinfo.o: $(top_srcdir)/internal/basic_operators.h +raddrinfo.o: $(top_srcdir)/internal/box.h raddrinfo.o: $(top_srcdir)/internal/compilers.h raddrinfo.o: $(top_srcdir)/internal/error.h raddrinfo.o: $(top_srcdir)/internal/gc.h raddrinfo.o: $(top_srcdir)/internal/imemo.h raddrinfo.o: $(top_srcdir)/internal/io.h -raddrinfo.o: $(top_srcdir)/internal/namespace.h raddrinfo.o: $(top_srcdir)/internal/sanitizers.h raddrinfo.o: $(top_srcdir)/internal/serial.h raddrinfo.o: $(top_srcdir)/internal/set_table.h @@ -1918,12 +1918,12 @@ socket.o: $(top_srcdir)/id_table.h socket.o: $(top_srcdir)/internal.h socket.o: $(top_srcdir)/internal/array.h socket.o: $(top_srcdir)/internal/basic_operators.h +socket.o: $(top_srcdir)/internal/box.h socket.o: $(top_srcdir)/internal/compilers.h socket.o: $(top_srcdir)/internal/error.h socket.o: $(top_srcdir)/internal/gc.h socket.o: $(top_srcdir)/internal/imemo.h socket.o: $(top_srcdir)/internal/io.h -socket.o: $(top_srcdir)/internal/namespace.h socket.o: $(top_srcdir)/internal/sanitizers.h socket.o: $(top_srcdir)/internal/serial.h socket.o: $(top_srcdir)/internal/set_table.h @@ -2133,12 +2133,12 @@ sockssocket.o: $(top_srcdir)/id_table.h sockssocket.o: $(top_srcdir)/internal.h sockssocket.o: $(top_srcdir)/internal/array.h sockssocket.o: $(top_srcdir)/internal/basic_operators.h +sockssocket.o: $(top_srcdir)/internal/box.h sockssocket.o: $(top_srcdir)/internal/compilers.h sockssocket.o: $(top_srcdir)/internal/error.h sockssocket.o: $(top_srcdir)/internal/gc.h sockssocket.o: $(top_srcdir)/internal/imemo.h sockssocket.o: $(top_srcdir)/internal/io.h -sockssocket.o: $(top_srcdir)/internal/namespace.h sockssocket.o: $(top_srcdir)/internal/sanitizers.h sockssocket.o: $(top_srcdir)/internal/serial.h sockssocket.o: $(top_srcdir)/internal/set_table.h @@ -2348,12 +2348,12 @@ tcpserver.o: $(top_srcdir)/id_table.h tcpserver.o: $(top_srcdir)/internal.h tcpserver.o: $(top_srcdir)/internal/array.h tcpserver.o: $(top_srcdir)/internal/basic_operators.h +tcpserver.o: $(top_srcdir)/internal/box.h tcpserver.o: $(top_srcdir)/internal/compilers.h tcpserver.o: $(top_srcdir)/internal/error.h tcpserver.o: $(top_srcdir)/internal/gc.h tcpserver.o: $(top_srcdir)/internal/imemo.h tcpserver.o: $(top_srcdir)/internal/io.h -tcpserver.o: $(top_srcdir)/internal/namespace.h tcpserver.o: $(top_srcdir)/internal/sanitizers.h tcpserver.o: $(top_srcdir)/internal/serial.h tcpserver.o: $(top_srcdir)/internal/set_table.h @@ -2563,12 +2563,12 @@ tcpsocket.o: $(top_srcdir)/id_table.h tcpsocket.o: $(top_srcdir)/internal.h tcpsocket.o: $(top_srcdir)/internal/array.h tcpsocket.o: $(top_srcdir)/internal/basic_operators.h +tcpsocket.o: $(top_srcdir)/internal/box.h tcpsocket.o: $(top_srcdir)/internal/compilers.h tcpsocket.o: $(top_srcdir)/internal/error.h tcpsocket.o: $(top_srcdir)/internal/gc.h tcpsocket.o: $(top_srcdir)/internal/imemo.h tcpsocket.o: $(top_srcdir)/internal/io.h -tcpsocket.o: $(top_srcdir)/internal/namespace.h tcpsocket.o: $(top_srcdir)/internal/sanitizers.h tcpsocket.o: $(top_srcdir)/internal/serial.h tcpsocket.o: $(top_srcdir)/internal/set_table.h @@ -2778,12 +2778,12 @@ udpsocket.o: $(top_srcdir)/id_table.h udpsocket.o: $(top_srcdir)/internal.h udpsocket.o: $(top_srcdir)/internal/array.h udpsocket.o: $(top_srcdir)/internal/basic_operators.h +udpsocket.o: $(top_srcdir)/internal/box.h udpsocket.o: $(top_srcdir)/internal/compilers.h udpsocket.o: $(top_srcdir)/internal/error.h udpsocket.o: $(top_srcdir)/internal/gc.h udpsocket.o: $(top_srcdir)/internal/imemo.h udpsocket.o: $(top_srcdir)/internal/io.h -udpsocket.o: $(top_srcdir)/internal/namespace.h udpsocket.o: $(top_srcdir)/internal/sanitizers.h udpsocket.o: $(top_srcdir)/internal/serial.h udpsocket.o: $(top_srcdir)/internal/set_table.h @@ -2993,12 +2993,12 @@ unixserver.o: $(top_srcdir)/id_table.h unixserver.o: $(top_srcdir)/internal.h unixserver.o: $(top_srcdir)/internal/array.h unixserver.o: $(top_srcdir)/internal/basic_operators.h +unixserver.o: $(top_srcdir)/internal/box.h unixserver.o: $(top_srcdir)/internal/compilers.h unixserver.o: $(top_srcdir)/internal/error.h unixserver.o: $(top_srcdir)/internal/gc.h unixserver.o: $(top_srcdir)/internal/imemo.h unixserver.o: $(top_srcdir)/internal/io.h -unixserver.o: $(top_srcdir)/internal/namespace.h unixserver.o: $(top_srcdir)/internal/sanitizers.h unixserver.o: $(top_srcdir)/internal/serial.h unixserver.o: $(top_srcdir)/internal/set_table.h @@ -3208,12 +3208,12 @@ unixsocket.o: $(top_srcdir)/id_table.h unixsocket.o: $(top_srcdir)/internal.h unixsocket.o: $(top_srcdir)/internal/array.h unixsocket.o: $(top_srcdir)/internal/basic_operators.h +unixsocket.o: $(top_srcdir)/internal/box.h unixsocket.o: $(top_srcdir)/internal/compilers.h unixsocket.o: $(top_srcdir)/internal/error.h unixsocket.o: $(top_srcdir)/internal/gc.h unixsocket.o: $(top_srcdir)/internal/imemo.h unixsocket.o: $(top_srcdir)/internal/io.h -unixsocket.o: $(top_srcdir)/internal/namespace.h unixsocket.o: $(top_srcdir)/internal/sanitizers.h unixsocket.o: $(top_srcdir)/internal/serial.h unixsocket.o: $(top_srcdir)/internal/set_table.h diff --git a/internal/class.h b/internal/class.h index f182211705e1f5..19393eb7c3eeaf 100644 --- a/internal/class.h +++ b/internal/class.h @@ -10,7 +10,7 @@ */ #include "id.h" #include "id_table.h" /* for struct rb_id_table */ -#include "internal/namespace.h" /* for rb_current_namespace */ +#include "internal/box.h" /* for rb_current_namespace */ #include "internal/serial.h" /* for rb_serial_t */ #include "internal/static_assert.h" #include "internal/variable.h" /* for rb_class_ivar_set */ diff --git a/load.c b/load.c index d1d6969039b849..c38a5fbd321672 100644 --- a/load.c +++ b/load.c @@ -5,13 +5,13 @@ #include "dln.h" #include "eval_intern.h" #include "internal.h" +#include "internal/box.h" #include "internal/dir.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/file.h" #include "internal/hash.h" #include "internal/load.h" -#include "internal/namespace.h" #include "internal/ruby_parser.h" #include "internal/thread.h" #include "internal/variable.h" diff --git a/variable.c b/variable.c index f6cfef7b0725e6..5a58f7ed6316d4 100644 --- a/variable.c +++ b/variable.c @@ -20,12 +20,12 @@ #include "id.h" #include "id_table.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/compilers.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/hash.h" -#include "internal/namespace.h" #include "internal/object.h" #include "internal/gc.h" #include "internal/re.h" diff --git a/vm.c b/vm.c index 5b706dc6c5e227..351951408d13e7 100644 --- a/vm.c +++ b/vm.c @@ -12,6 +12,7 @@ #include "eval_intern.h" #include "internal.h" +#include "internal/box.h" #include "internal/class.h" #include "internal/compile.h" #include "internal/cont.h" @@ -21,7 +22,6 @@ #include "internal/gc.h" #include "internal/inits.h" #include "internal/missing.h" -#include "internal/namespace.h" #include "internal/object.h" #include "internal/proc.h" #include "internal/re.h" @@ -98,7 +98,7 @@ rb_vm_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame } #if VM_CHECK_MODE > 0 -// ruby_namespace_crashed defined in internal/namespace.h +// ruby_namespace_crashed defined in internal/box.h #define VM_NAMESPACE_CRASHED() {ruby_namespace_crashed = true;} #define VM_NAMESPACE_ASSERT(expr, msg) \ if (!(expr)) { ruby_namespace_crashed = true; rb_bug(msg); } diff --git a/vm_core.h b/vm_core.h index ded0280387b834..599c7ada9fa9d1 100644 --- a/vm_core.h +++ b/vm_core.h @@ -118,7 +118,7 @@ extern int ruby_assert_critical_section_entered; #include "internal.h" #include "internal/array.h" #include "internal/basic_operators.h" -#include "internal/namespace.h" +#include "internal/box.h" #include "internal/sanitizers.h" #include "internal/serial.h" #include "internal/set_table.h" From d2a587c79156275f66035d60bcc69882be61a3e1 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 5 Nov 2025 16:09:45 +0900 Subject: [PATCH 0872/2435] renaming internal data structures and functions from namespace to box --- box.c | 589 ++++++++++++++++++++++---------------------- builtin.c | 2 +- class.c | 212 ++++++++-------- dln.c | 2 +- eval.c | 4 +- eval_intern.h | 10 +- gc.c | 20 +- inits.c | 2 +- insns.def | 4 +- internal/box.h | 72 +++--- internal/class.h | 176 ++++++------- internal/inits.h | 10 +- internal/variable.h | 4 +- iseq.c | 2 +- load.c | 269 ++++++++++---------- method.h | 2 +- mini_builtin.c | 2 +- proc.c | 22 +- ruby.c | 24 +- shape.c | 2 +- shape.h | 4 +- variable.c | 80 +++--- vm.c | 166 ++++++------- vm_core.h | 30 +-- vm_dump.c | 76 +++--- vm_eval.c | 16 +- vm_method.c | 4 +- 27 files changed, 900 insertions(+), 906 deletions(-) diff --git a/box.c b/box.c index a9368ca7440d91..f1290f60aa228e 100644 --- a/box.c +++ b/box.c @@ -24,7 +24,7 @@ VALUE rb_cNamespace = 0; VALUE rb_cNamespaceEntry = 0; VALUE rb_mNamespaceLoader = 0; -static rb_namespace_t root_namespace_data = { +static rb_box_t root_box_data = { /* Initialize values lazily in Init_namespace() */ (VALUE)NULL, 0, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, @@ -32,12 +32,12 @@ static rb_namespace_t root_namespace_data = { false, false }; -static rb_namespace_t * root_namespace = &root_namespace_data; -static rb_namespace_t * main_namespace = 0; +static rb_box_t * root_box = &root_box_data; +static rb_box_t * main_box = 0; static char *tmp_dir; static bool tmp_dir_has_dirsep; -#define NAMESPACE_TMP_PREFIX "_ruby_ns_" +#define BOX_TMP_PREFIX "_ruby_box_" #ifndef MAXPATHLEN # define MAXPATHLEN 1024 @@ -49,156 +49,156 @@ static bool tmp_dir_has_dirsep; # define DIRSEP "/" #endif -bool ruby_namespace_enabled = false; // extern -bool ruby_namespace_init_done = false; // extern -bool ruby_namespace_crashed = false; // extern, changed only in vm.c +bool ruby_box_enabled = false; // extern +bool ruby_box_init_done = false; // extern +bool ruby_box_crashed = false; // extern, changed only in vm.c VALUE rb_resolve_feature_path(VALUE klass, VALUE fname); -static VALUE rb_namespace_inspect(VALUE obj); +static VALUE rb_box_inspect(VALUE obj); void -rb_namespace_init_done(void) +rb_box_init_done(void) { - ruby_namespace_init_done = true; + ruby_box_init_done = true; } -const rb_namespace_t * -rb_root_namespace(void) +const rb_box_t * +rb_root_box(void) { - return root_namespace; + return root_box; } -const rb_namespace_t * -rb_main_namespace(void) +const rb_box_t * +rb_main_box(void) { - return main_namespace; + return main_box; } -const rb_namespace_t * -rb_current_namespace(void) +const rb_box_t * +rb_current_box(void) { /* - * If RUBY_NAMESPACE is not set, the root namespace is the only available one. + * If RUBY_NAMESPACE is not set, the root box is the only available one. * - * Until the main_namespace is not initialized, the root namespace is - * the only valid namespace. + * Until the main_box is not initialized, the root box is + * the only valid box. * This early return is to avoid accessing EC before its setup. */ - if (!main_namespace) - return root_namespace; + if (!main_box) + return root_box; - return rb_vm_current_namespace(GET_EC()); + return rb_vm_current_box(GET_EC()); } -const rb_namespace_t * -rb_loading_namespace(void) +const rb_box_t * +rb_loading_box(void) { - if (!main_namespace) - return root_namespace; + if (!main_box) + return root_box; - return rb_vm_loading_namespace(GET_EC()); + return rb_vm_loading_box(GET_EC()); } -const rb_namespace_t * -rb_current_namespace_in_crash_report(void) +const rb_box_t * +rb_current_box_in_crash_report(void) { - if (ruby_namespace_crashed) + if (ruby_box_crashed) return NULL; - return rb_current_namespace(); + return rb_current_box(); } -static long namespace_id_counter = 0; +static long box_id_counter = 0; static long -namespace_generate_id(void) +box_generate_id(void) { long id; RB_VM_LOCKING() { - id = ++namespace_id_counter; + id = ++box_id_counter; } return id; } static VALUE -namespace_main_to_s(VALUE obj) +box_main_to_s(VALUE obj) { return rb_str_new2("main"); } static void -namespace_entry_initialize(rb_namespace_t *ns) +box_entry_initialize(rb_box_t *box) { - const rb_namespace_t *root = rb_root_namespace(); + const rb_box_t *root = rb_root_box(); // These will be updated immediately - ns->ns_object = 0; - ns->ns_id = 0; - - ns->top_self = rb_obj_alloc(rb_cObject); - rb_define_singleton_method(ns->top_self, "to_s", namespace_main_to_s, 0); - rb_define_alias(rb_singleton_class(ns->top_self), "inspect", "to_s"); - ns->load_path = rb_ary_dup(root->load_path); - ns->expanded_load_path = rb_ary_dup(root->expanded_load_path); - ns->load_path_snapshot = rb_ary_new(); - ns->load_path_check_cache = 0; - ns->loaded_features = rb_ary_dup(root->loaded_features); - ns->loaded_features_snapshot = rb_ary_new(); - ns->loaded_features_index = st_init_numtable(); - ns->loaded_features_realpaths = rb_hash_dup(root->loaded_features_realpaths); - ns->loaded_features_realpath_map = rb_hash_dup(root->loaded_features_realpath_map); - ns->loading_table = st_init_strtable(); - ns->ruby_dln_libmap = rb_hash_new_with_size(0); - ns->gvar_tbl = rb_hash_new_with_size(0); - - ns->is_user = true; - ns->is_optional = true; + box->box_object = 0; + box->box_id = 0; + + box->top_self = rb_obj_alloc(rb_cObject); + rb_define_singleton_method(box->top_self, "to_s", box_main_to_s, 0); + rb_define_alias(rb_singleton_class(box->top_self), "inspect", "to_s"); + box->load_path = rb_ary_dup(root->load_path); + box->expanded_load_path = rb_ary_dup(root->expanded_load_path); + box->load_path_snapshot = rb_ary_new(); + box->load_path_check_cache = 0; + box->loaded_features = rb_ary_dup(root->loaded_features); + box->loaded_features_snapshot = rb_ary_new(); + box->loaded_features_index = st_init_numtable(); + box->loaded_features_realpaths = rb_hash_dup(root->loaded_features_realpaths); + box->loaded_features_realpath_map = rb_hash_dup(root->loaded_features_realpath_map); + box->loading_table = st_init_strtable(); + box->ruby_dln_libmap = rb_hash_new_with_size(0); + box->gvar_tbl = rb_hash_new_with_size(0); + + box->is_user = true; + box->is_optional = true; } void -rb_namespace_gc_update_references(void *ptr) +rb_box_gc_update_references(void *ptr) { - rb_namespace_t *ns = (rb_namespace_t *)ptr; - if (!ns) return; - - if (ns->ns_object) - ns->ns_object = rb_gc_location(ns->ns_object); - if (ns->top_self) - ns->top_self = rb_gc_location(ns->top_self); - ns->load_path = rb_gc_location(ns->load_path); - ns->expanded_load_path = rb_gc_location(ns->expanded_load_path); - ns->load_path_snapshot = rb_gc_location(ns->load_path_snapshot); - if (ns->load_path_check_cache) { - ns->load_path_check_cache = rb_gc_location(ns->load_path_check_cache); + rb_box_t *box = (rb_box_t *)ptr; + if (!box) return; + + if (box->box_object) + box->box_object = rb_gc_location(box->box_object); + if (box->top_self) + box->top_self = rb_gc_location(box->top_self); + box->load_path = rb_gc_location(box->load_path); + box->expanded_load_path = rb_gc_location(box->expanded_load_path); + box->load_path_snapshot = rb_gc_location(box->load_path_snapshot); + if (box->load_path_check_cache) { + box->load_path_check_cache = rb_gc_location(box->load_path_check_cache); } - ns->loaded_features = rb_gc_location(ns->loaded_features); - ns->loaded_features_snapshot = rb_gc_location(ns->loaded_features_snapshot); - ns->loaded_features_realpaths = rb_gc_location(ns->loaded_features_realpaths); - ns->loaded_features_realpath_map = rb_gc_location(ns->loaded_features_realpath_map); - ns->ruby_dln_libmap = rb_gc_location(ns->ruby_dln_libmap); - ns->gvar_tbl = rb_gc_location(ns->gvar_tbl); + box->loaded_features = rb_gc_location(box->loaded_features); + box->loaded_features_snapshot = rb_gc_location(box->loaded_features_snapshot); + box->loaded_features_realpaths = rb_gc_location(box->loaded_features_realpaths); + box->loaded_features_realpath_map = rb_gc_location(box->loaded_features_realpath_map); + box->ruby_dln_libmap = rb_gc_location(box->ruby_dln_libmap); + box->gvar_tbl = rb_gc_location(box->gvar_tbl); } void -rb_namespace_entry_mark(void *ptr) +rb_box_entry_mark(void *ptr) { - const rb_namespace_t *ns = (rb_namespace_t *)ptr; - if (!ns) return; - - rb_gc_mark(ns->ns_object); - rb_gc_mark(ns->top_self); - rb_gc_mark(ns->load_path); - rb_gc_mark(ns->expanded_load_path); - rb_gc_mark(ns->load_path_snapshot); - rb_gc_mark(ns->load_path_check_cache); - rb_gc_mark(ns->loaded_features); - rb_gc_mark(ns->loaded_features_snapshot); - rb_gc_mark(ns->loaded_features_realpaths); - rb_gc_mark(ns->loaded_features_realpath_map); - if (ns->loading_table) { - rb_mark_tbl(ns->loading_table); + const rb_box_t *box = (rb_box_t *)ptr; + if (!box) return; + + rb_gc_mark(box->box_object); + rb_gc_mark(box->top_self); + rb_gc_mark(box->load_path); + rb_gc_mark(box->expanded_load_path); + rb_gc_mark(box->load_path_snapshot); + rb_gc_mark(box->load_path_check_cache); + rb_gc_mark(box->loaded_features); + rb_gc_mark(box->loaded_features_snapshot); + rb_gc_mark(box->loaded_features_realpaths); + rb_gc_mark(box->loaded_features_realpath_map); + if (box->loading_table) { + rb_mark_tbl(box->loading_table); } - rb_gc_mark(ns->ruby_dln_libmap); - rb_gc_mark(ns->gvar_tbl); + rb_gc_mark(box->ruby_dln_libmap); + rb_gc_mark(box->gvar_tbl); } static int @@ -218,186 +218,185 @@ free_loaded_feature_index_i(st_data_t key, st_data_t value, st_data_t arg) } static void -namespace_root_free(void *ptr) +box_root_free(void *ptr) { - rb_namespace_t *ns = (rb_namespace_t *)ptr; - if (ns->loading_table) { - st_foreach(ns->loading_table, free_loading_table_entry, 0); - st_free_table(ns->loading_table); - ns->loading_table = 0; + rb_box_t *box = (rb_box_t *)ptr; + if (box->loading_table) { + st_foreach(box->loading_table, free_loading_table_entry, 0); + st_free_table(box->loading_table); + box->loading_table = 0; } - if (ns->loaded_features_index) { - st_foreach(ns->loaded_features_index, free_loaded_feature_index_i, 0); - st_free_table(ns->loaded_features_index); + if (box->loaded_features_index) { + st_foreach(box->loaded_features_index, free_loaded_feature_index_i, 0); + st_free_table(box->loaded_features_index); } } static void -namespace_entry_free(void *ptr) +box_entry_free(void *ptr) { - namespace_root_free(ptr); + box_root_free(ptr); xfree(ptr); } static size_t -namespace_entry_memsize(const void *ptr) +box_entry_memsize(const void *ptr) { - const rb_namespace_t *ns = (const rb_namespace_t *)ptr; - return sizeof(rb_namespace_t) + \ - rb_st_memsize(ns->loaded_features_index) + \ - rb_st_memsize(ns->loading_table); + const rb_box_t *box = (const rb_box_t *)ptr; + return sizeof(rb_box_t) + \ + rb_st_memsize(box->loaded_features_index) + \ + rb_st_memsize(box->loading_table); } -const rb_data_type_t rb_namespace_data_type = { +const rb_data_type_t rb_box_data_type = { "Namespace::Entry", { - rb_namespace_entry_mark, - namespace_entry_free, - namespace_entry_memsize, - rb_namespace_gc_update_references, + rb_box_entry_mark, + box_entry_free, + box_entry_memsize, + rb_box_gc_update_references, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers }; -const rb_data_type_t rb_root_namespace_data_type = { +const rb_data_type_t rb_root_box_data_type = { "Namespace::Root", { - rb_namespace_entry_mark, - namespace_root_free, - namespace_entry_memsize, - rb_namespace_gc_update_references, + rb_box_entry_mark, + box_root_free, + box_entry_memsize, + rb_box_gc_update_references, }, - &rb_namespace_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers + &rb_box_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers }; VALUE -rb_namespace_entry_alloc(VALUE klass) +rb_box_entry_alloc(VALUE klass) { - rb_namespace_t *entry; - VALUE obj = TypedData_Make_Struct(klass, rb_namespace_t, &rb_namespace_data_type, entry); - namespace_entry_initialize(entry); + rb_box_t *entry; + VALUE obj = TypedData_Make_Struct(klass, rb_box_t, &rb_box_data_type, entry); + box_entry_initialize(entry); return obj; } -static rb_namespace_t * -get_namespace_struct_internal(VALUE entry) +static rb_box_t * +get_box_struct_internal(VALUE entry) { - rb_namespace_t *sval; - TypedData_Get_Struct(entry, rb_namespace_t, &rb_namespace_data_type, sval); + rb_box_t *sval; + TypedData_Get_Struct(entry, rb_box_t, &rb_box_data_type, sval); return sval; } -rb_namespace_t * -rb_get_namespace_t(VALUE namespace) +rb_box_t * +rb_get_box_t(VALUE box) { VALUE entry; - ID id_namespace_entry; + ID id_box_entry; - VM_ASSERT(namespace); + VM_ASSERT(box); - if (NIL_P(namespace)) - return root_namespace; + if (NIL_P(box)) + return root_box; - VM_ASSERT(NAMESPACE_OBJ_P(namespace)); + VM_ASSERT(BOX_OBJ_P(box)); - CONST_ID(id_namespace_entry, "__namespace_entry__"); - entry = rb_attr_get(namespace, id_namespace_entry); - return get_namespace_struct_internal(entry); + CONST_ID(id_box_entry, "__box_entry__"); + entry = rb_attr_get(box, id_box_entry); + return get_box_struct_internal(entry); } VALUE -rb_get_namespace_object(rb_namespace_t *ns) +rb_get_box_object(rb_box_t *box) { - VM_ASSERT(ns && ns->ns_object); - return ns->ns_object; + VM_ASSERT(box && box->box_object); + return box->box_object; } /* * call-seq: - * Namespace.new -> new_namespace + * Namespace.new -> new_box * - * Returns a new Namespace object. + * Returns a new Ruby::Box object. */ static VALUE -namespace_initialize(VALUE namespace) +box_initialize(VALUE box_value) { - rb_namespace_t *ns; + rb_box_t *box; rb_classext_t *object_classext; VALUE entry; - ID id_namespace_entry; - CONST_ID(id_namespace_entry, "__namespace_entry__"); + ID id_box_entry; + CONST_ID(id_box_entry, "__box_entry__"); - if (!rb_namespace_available()) { + if (!rb_box_available()) { rb_raise(rb_eRuntimeError, "Namespace is disabled. Set RUBY_NAMESPACE=1 environment variable to use Namespace."); } entry = rb_class_new_instance_pass_kw(0, NULL, rb_cNamespaceEntry); - ns = get_namespace_struct_internal(entry); + box = get_box_struct_internal(entry); - ns->ns_object = namespace; - ns->ns_id = namespace_generate_id(); - rb_define_singleton_method(ns->load_path, "resolve_feature_path", rb_resolve_feature_path, 1); + box->box_object = box_value; + box->box_id = box_generate_id(); + rb_define_singleton_method(box->load_path, "resolve_feature_path", rb_resolve_feature_path, 1); - // Set the Namespace object unique/consistent from any namespaces to have just single - // constant table from any view of every (including main) namespace. - // If a code in the namespace adds a constant, the constant will be visible even from root/main. - RCLASS_SET_PRIME_CLASSEXT_WRITABLE(namespace, true); + // Set the Ruby::Box object unique/consistent from any boxes to have just single + // constant table from any view of every (including main) box. + // If a code in the box adds a constant, the constant will be visible even from root/main. + RCLASS_SET_PRIME_CLASSEXT_WRITABLE(box_value, true); // Get a clean constant table of Object even by writable one // because ns was just created, so it has not touched any constants yet. - object_classext = RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns); - RCLASS_SET_CONST_TBL(namespace, RCLASSEXT_CONST_TBL(object_classext), true); + object_classext = RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, box); + RCLASS_SET_CONST_TBL(box_value, RCLASSEXT_CONST_TBL(object_classext), true); - rb_ivar_set(namespace, id_namespace_entry, entry); + rb_ivar_set(box_value, id_box_entry, entry); - return namespace; + return box_value; } /* * call-seq: * Namespace.enabled? -> true or false * - * Returns +true+ if namespace is enabled. + * Returns +true+ if Ruby::Box is enabled. */ static VALUE -rb_namespace_s_getenabled(VALUE recv) +rb_box_s_getenabled(VALUE recv) { - return RBOOL(rb_namespace_available()); + return RBOOL(rb_box_available()); } /* * call-seq: - * Namespace.current -> namespace, nil or false + * Namespace.current -> box, nil or false * - * Returns the current namespace. - * Returns +nil+ if it is the built-in namespace. - * Returns +false+ if namespace is not enabled. + * Returns the current box. + * Returns +nil+ if Ruby Box is not enabled. */ static VALUE -rb_namespace_s_current(VALUE recv) +rb_box_s_current(VALUE recv) { - const rb_namespace_t *ns; + const rb_box_t *box; - if (!rb_namespace_available()) + if (!rb_box_available()) return Qnil; - ns = rb_vm_current_namespace(GET_EC()); - VM_ASSERT(ns && ns->ns_object); - return ns->ns_object; + box = rb_vm_current_box(GET_EC()); + VM_ASSERT(box && box->box_object); + return box->box_object; } /* * call-seq: * load_path -> array * - * Returns namespace local load path. + * Returns box local load path. */ static VALUE -rb_namespace_load_path(VALUE namespace) +rb_box_load_path(VALUE box) { - VM_ASSERT(NAMESPACE_OBJ_P(namespace)); - return rb_get_namespace_t(namespace)->load_path; + VM_ASSERT(BOX_OBJ_P(box)); + return rb_get_box_t(box)->load_path; } #ifdef _WIN32 @@ -480,12 +479,12 @@ system_tmpdir(void) /* end of copy */ static int -sprint_ext_filename(char *str, size_t size, long namespace_id, const char *prefix, const char *basename) +sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, const char *basename) { if (tmp_dir_has_dirsep) { - return snprintf(str, size, "%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, prefix, getpid(), namespace_id, basename); + return snprintf(str, size, "%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, prefix, getpid(), box_id, basename); } - return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), namespace_id, basename); + return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename); } #ifdef _WIN32 @@ -661,19 +660,19 @@ escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize } VALUE -rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) +rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) { char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN]; int copy_error, wrote; char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); - rb_namespace_t *ns = rb_get_namespace_t(namespace); + rb_box_t *box = rb_get_box_t(box_value); fname_without_suffix(fname_ptr, fname2, sizeof(fname2)); escaped_basename(src_path, fname2, basename, sizeof(basename)); - wrote = sprint_ext_filename(ext_path, sizeof(ext_path), ns->ns_id, NAMESPACE_TMP_PREFIX, basename); + wrote = sprint_ext_filename(ext_path, sizeof(ext_path), box->box_id, BOX_TMP_PREFIX, basename); if (wrote >= (int)sizeof(ext_path)) { - rb_bug("Extension file path in namespace was too long"); + rb_bug("Extension file path in the box was too long"); } copy_error = copy_ext_file(src_path, ext_path); if (copy_error) { @@ -683,7 +682,7 @@ rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) #else copy_ext_file_error(message, sizeof(message), copy_error, src_path, ext_path); #endif - rb_raise(rb_eLoadError, "can't prepare the extension file for namespaces (%s from %s): %s", ext_path, src_path, message); + rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %s): %s", ext_path, src_path, message); } // TODO: register the path to be clean-uped return rb_str_new_cstr(ext_path); @@ -694,40 +693,40 @@ rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) // And it requires calling dlclose before deleting it. static VALUE -rb_namespace_load(int argc, VALUE *argv, VALUE namespace) +rb_box_load(int argc, VALUE *argv, VALUE box) { VALUE fname, wrap; rb_scan_args(argc, argv, "11", &fname, &wrap); - rb_vm_frame_flag_set_ns_require(GET_EC()); + rb_vm_frame_flag_set_box_require(GET_EC()); VALUE args = rb_ary_new_from_args(2, fname, wrap); return rb_load_entrypoint(args); } static VALUE -rb_namespace_require(VALUE namespace, VALUE fname) +rb_box_require(VALUE box, VALUE fname) { - rb_vm_frame_flag_set_ns_require(GET_EC()); + rb_vm_frame_flag_set_box_require(GET_EC()); return rb_require_string(fname); } static VALUE -rb_namespace_require_relative(VALUE namespace, VALUE fname) +rb_box_require_relative(VALUE box, VALUE fname) { - rb_vm_frame_flag_set_ns_require(GET_EC()); + rb_vm_frame_flag_set_box_require(GET_EC()); return rb_require_relative_entrypoint(fname); } static void -initialize_root_namespace(void) +initialize_root_box(void) { - VALUE root_namespace, entry; - ID id_namespace_entry; + VALUE root_box, entry; + ID id_box_entry; rb_vm_t *vm = GET_VM(); - rb_namespace_t *root = (rb_namespace_t *)rb_root_namespace(); + rb_box_t *root = (rb_box_t *)rb_root_box(); root->load_path = rb_ary_new(); root->expanded_load_path = rb_ary_hidden_new(0); @@ -746,98 +745,98 @@ initialize_root_namespace(void) root->ruby_dln_libmap = rb_hash_new_with_size(0); root->gvar_tbl = rb_hash_new_with_size(0); - vm->root_namespace = root; + vm->root_box = root; - if (rb_namespace_available()) { - CONST_ID(id_namespace_entry, "__namespace_entry__"); + if (rb_box_available()) { + CONST_ID(id_box_entry, "__box_entry__"); - root_namespace = rb_obj_alloc(rb_cNamespace); - RCLASS_SET_PRIME_CLASSEXT_WRITABLE(root_namespace, true); - RCLASS_SET_CONST_TBL(root_namespace, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true); + root_box = rb_obj_alloc(rb_cNamespace); + RCLASS_SET_PRIME_CLASSEXT_WRITABLE(root_box, true); + RCLASS_SET_CONST_TBL(root_box, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true); - root->ns_id = namespace_generate_id(); - root->ns_object = root_namespace; + root->box_id = box_generate_id(); + root->box_object = root_box; - entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_root_namespace_data_type, root); - rb_ivar_set(root_namespace, id_namespace_entry, entry); + entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_root_box_data_type, root); + rb_ivar_set(root_box, id_box_entry, entry); } else { - root->ns_id = 1; - root->ns_object = Qnil; + root->box_id = 1; + root->box_object = Qnil; } } static VALUE -rb_namespace_eval(VALUE namespace, VALUE str) +rb_box_eval(VALUE box_value, VALUE str) { const rb_iseq_t *iseq; - const rb_namespace_t *ns; + const rb_box_t *box; StringValue(str); iseq = rb_iseq_compile_iseq(str, rb_str_new_cstr("eval")); VM_ASSERT(iseq); - ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + box = (const rb_box_t *)rb_get_box_t(box_value); - return rb_iseq_eval(iseq, ns); + return rb_iseq_eval(iseq, box); } -static int namespace_experimental_warned = 0; +static int box_experimental_warned = 0; void -rb_initialize_main_namespace(void) +rb_initialize_main_box(void) { - rb_namespace_t *ns; - VALUE main_ns; + rb_box_t *box; + VALUE main_box_value; rb_vm_t *vm = GET_VM(); - VM_ASSERT(rb_namespace_available()); + VM_ASSERT(rb_box_available()); - if (!namespace_experimental_warned) { + if (!box_experimental_warned) { rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, - "Namespace is experimental, and the behavior may change in the future!\n" - "See doc/namespace.md for known issues, etc."); - namespace_experimental_warned = 1; + "Ruby::Box is experimental, and the behavior may change in the future!\n" + "See doc/box.md for known issues, etc."); + box_experimental_warned = 1; } - main_ns = rb_class_new_instance(0, NULL, rb_cNamespace); - VM_ASSERT(NAMESPACE_OBJ_P(main_ns)); - ns = rb_get_namespace_t(main_ns); - ns->ns_object = main_ns; - ns->is_user = true; - ns->is_optional = false; + main_box_value = rb_class_new_instance(0, NULL, rb_cNamespace); + VM_ASSERT(BOX_OBJ_P(main_box_value)); + box = rb_get_box_t(main_box_value); + box->box_object = main_box_value; + box->is_user = true; + box->is_optional = false; - rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_ns); + rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_box_value); - vm->main_namespace = main_namespace = ns; + vm->main_box = main_box = box; // create the writable classext of ::Object explicitly to finalize the set of visible top-level constants - RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns); + RCLASS_EXT_WRITABLE_IN_BOX(rb_cObject, box); } static VALUE -rb_namespace_inspect(VALUE obj) +rb_box_inspect(VALUE obj) { - rb_namespace_t *ns; + rb_box_t *box; VALUE r; if (obj == Qfalse) { r = rb_str_new_cstr("#"); return r; } - ns = rb_get_namespace_t(obj); + box = rb_get_box_t(obj); r = rb_str_new_cstr("#ns_id), rb_intern("to_s"), 0)); - if (NAMESPACE_ROOT_P(ns)) { + rb_str_concat(r, rb_funcall(LONG2NUM(box->box_id), rb_intern("to_s"), 0)); + if (BOX_ROOT_P(box)) { rb_str_cat_cstr(r, ",root"); } - if (NAMESPACE_USER_P(ns)) { + if (BOX_USER_P(box)) { rb_str_cat_cstr(r, ",user"); } - if (NAMESPACE_MAIN_P(ns)) { + if (BOX_MAIN_P(box)) { rb_str_cat_cstr(r, ",main"); } - else if (NAMESPACE_OPTIONAL_P(ns)) { + else if (BOX_OPTIONAL_P(box)) { rb_str_cat_cstr(r, ",optional"); } rb_str_cat_cstr(r, ">"); @@ -845,65 +844,65 @@ rb_namespace_inspect(VALUE obj) } static VALUE -rb_namespace_loading_func(int argc, VALUE *argv, VALUE _self) +rb_box_loading_func(int argc, VALUE *argv, VALUE _self) { - rb_vm_frame_flag_set_ns_require(GET_EC()); + rb_vm_frame_flag_set_box_require(GET_EC()); return rb_call_super(argc, argv); } static void -namespace_define_loader_method(const char *name) +box_define_loader_method(const char *name) { - rb_define_private_method(rb_mNamespaceLoader, name, rb_namespace_loading_func, -1); - rb_define_singleton_method(rb_mNamespaceLoader, name, rb_namespace_loading_func, -1); + rb_define_private_method(rb_mNamespaceLoader, name, rb_box_loading_func, -1); + rb_define_singleton_method(rb_mNamespaceLoader, name, rb_box_loading_func, -1); } void -Init_root_namespace(void) +Init_root_box(void) { - root_namespace->loading_table = st_init_strtable(); + root_box->loading_table = st_init_strtable(); } void -Init_enable_namespace(void) +Init_enable_box(void) { const char *env = getenv("RUBY_NAMESPACE"); if (env && strlen(env) == 1 && env[0] == '1') { - ruby_namespace_enabled = true; + ruby_box_enabled = true; } else { - ruby_namespace_init_done = true; + ruby_box_init_done = true; } } /* :nodoc: */ static VALUE -rb_namespace_s_root(VALUE recv) +rb_box_s_root(VALUE recv) { - return root_namespace->ns_object; + return root_box->box_object; } /* :nodoc: */ static VALUE -rb_namespace_s_main(VALUE recv) +rb_box_s_main(VALUE recv) { - return main_namespace->ns_object; + return main_box->box_object; } /* :nodoc: */ static VALUE -rb_namespace_root_p(VALUE namespace) +rb_box_root_p(VALUE box_value) { - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_ROOT_P(ns)); + const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value); + return RBOOL(BOX_ROOT_P(box)); } /* :nodoc: */ static VALUE -rb_namespace_main_p(VALUE namespace) +rb_box_main_p(VALUE box_value) { - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_MAIN_P(ns)); + const rb_box_t *box = (const rb_box_t *)rb_get_box_t(box_value); + return RBOOL(BOX_MAIN_P(box)); } #if RUBY_DEBUG @@ -940,14 +939,14 @@ dump_classext_constants_i(ID mid, VALUE _val, void *data) } static void -dump_classext_i(rb_classext_t *ext, bool is_prime, VALUE _ns, void *data) +dump_classext_i(rb_classext_t *ext, bool is_prime, VALUE _recv, void *data) { char buf[4096]; struct rb_id_table *tbl; VALUE ary, res = (VALUE)data; - snprintf(buf, 4096, "Namespace %ld:%s classext %p\n", - RCLASSEXT_NS(ext)->ns_id, is_prime ? " prime" : "", (void *)ext); + snprintf(buf, 4096, "Ruby::Box %ld:%s classext %p\n", + RCLASSEXT_BOX(ext)->box_id, is_prime ? " prime" : "", (void *)ext); rb_str_cat_cstr(res, buf); snprintf(buf, 2048, " Super: %s\n", classname(RCLASSEXT_SUPER(ext))); @@ -989,13 +988,13 @@ rb_f_dump_classext(VALUE recv, VALUE klass) /* * The desired output String value is: * Class: 0x88800932 (String) [singleton] - * Prime classext namespace(2,main), readable(t), writable(f) + * Prime classext box(2,main), readable(t), writable(f) * Non-prime classexts: 3 - * Namespace 2: prime classext 0x88800933 + * Box 2: prime classext 0x88800933 * Super: Object * Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ... * Constants(12): FOO, Bar, ... - * Namespace 5: classext 0x88800934 + * Box 5: classext 0x88800934 * Super: Object * Methods(43): aaaaa, bbbb, cccc, dddd, eeeee, ffff, gggg, hhhhh, ... * Constants(12): FOO, Bar, ... @@ -1003,7 +1002,7 @@ rb_f_dump_classext(VALUE recv, VALUE klass) char buf[2048]; VALUE res; const rb_classext_t *ext; - const rb_namespace_t *ns; + const rb_box_t *box; st_table *classext_tbl; if (!(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE))) { @@ -1021,10 +1020,10 @@ rb_f_dump_classext(VALUE recv, VALUE klass) res = rb_str_new_cstr(buf); ext = RCLASS_EXT_PRIME(klass); - ns = RCLASSEXT_NS(ext); - snprintf(buf, 2048, "Prime classext namespace(%ld,%s), readable(%s), writable(%s)\n", - ns->ns_id, - NAMESPACE_ROOT_P(ns) ? "root" : (NAMESPACE_MAIN_P(ns) ? "main" : "optional"), + box = RCLASSEXT_BOX(ext); + snprintf(buf, 2048, "Prime classext box(%ld,%s), readable(%s), writable(%s)\n", + box->box_id, + BOX_ROOT_P(box) ? "root" : (BOX_MAIN_P(box) ? "main" : "optional"), RCLASS_PRIME_CLASSEXT_READABLE_P(klass) ? "t" : "f", RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass) ? "t" : "f"); rb_str_cat_cstr(res, buf); @@ -1046,54 +1045,54 @@ rb_f_dump_classext(VALUE recv, VALUE klass) #endif /* RUBY_DEBUG */ /* - * Document-class: Namespace + * Document-class: Ruby::Box * - * Namespace is designed to provide separated spaces in a Ruby + * Ruby::Box is designed to provide separated spaces in a Ruby * process, to isolate applications and libraries. - * See {Namespace}[rdoc-ref:namespace.md]. + * See {Ruby::Box}[rdoc-ref:box.md]. */ void -Init_Namespace(void) +Init_Box(void) { tmp_dir = system_tmpdir(); tmp_dir_has_dirsep = (strcmp(tmp_dir + (strlen(tmp_dir) - strlen(DIRSEP)), DIRSEP) == 0); rb_cNamespace = rb_define_class("Namespace", rb_cModule); - rb_define_method(rb_cNamespace, "initialize", namespace_initialize, 0); + rb_define_method(rb_cNamespace, "initialize", box_initialize, 0); /* :nodoc: */ rb_cNamespaceEntry = rb_define_class_under(rb_cNamespace, "Entry", rb_cObject); - rb_define_alloc_func(rb_cNamespaceEntry, rb_namespace_entry_alloc); + rb_define_alloc_func(rb_cNamespaceEntry, rb_box_entry_alloc); - initialize_root_namespace(); + initialize_root_box(); /* :nodoc: */ rb_mNamespaceLoader = rb_define_module_under(rb_cNamespace, "Loader"); - namespace_define_loader_method("require"); - namespace_define_loader_method("require_relative"); - namespace_define_loader_method("load"); + box_define_loader_method("require"); + box_define_loader_method("require_relative"); + box_define_loader_method("load"); - if (rb_namespace_available()) { + if (rb_box_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); - rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); - rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); - rb_define_method(rb_cNamespace, "root?", rb_namespace_root_p, 0); - rb_define_method(rb_cNamespace, "main?", rb_namespace_main_p, 0); + rb_define_singleton_method(rb_cNamespace, "root", rb_box_s_root, 0); + rb_define_singleton_method(rb_cNamespace, "main", rb_box_s_main, 0); + rb_define_method(rb_cNamespace, "root?", rb_box_root_p, 0); + rb_define_method(rb_cNamespace, "main?", rb_box_main_p, 0); #if RUBY_DEBUG rb_define_global_function("dump_classext", rb_f_dump_classext, 1); #endif } - rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); - rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_s_current, 0); + rb_define_singleton_method(rb_cNamespace, "enabled?", rb_box_s_getenabled, 0); + rb_define_singleton_method(rb_cNamespace, "current", rb_box_s_current, 0); - rb_define_method(rb_cNamespace, "load_path", rb_namespace_load_path, 0); - rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1); - rb_define_method(rb_cNamespace, "require", rb_namespace_require, 1); - rb_define_method(rb_cNamespace, "require_relative", rb_namespace_require_relative, 1); - rb_define_method(rb_cNamespace, "eval", rb_namespace_eval, 1); + rb_define_method(rb_cNamespace, "load_path", rb_box_load_path, 0); + rb_define_method(rb_cNamespace, "load", rb_box_load, -1); + rb_define_method(rb_cNamespace, "require", rb_box_require, 1); + rb_define_method(rb_cNamespace, "require_relative", rb_box_require_relative, 1); + rb_define_method(rb_cNamespace, "eval", rb_box_eval, 1); - rb_define_method(rb_cNamespace, "inspect", rb_namespace_inspect, 0); + rb_define_method(rb_cNamespace, "inspect", rb_box_inspect, 0); } diff --git a/builtin.c b/builtin.c index 657143a739f649..6cc9790466a9f1 100644 --- a/builtin.c +++ b/builtin.c @@ -51,7 +51,7 @@ load_with_builtin_functions(const char *feature_name, const struct rb_builtin_fu vm->builtin_function_table = NULL; // exec - rb_iseq_eval(rb_iseq_check(iseq), rb_root_namespace()); // builtin functions are loaded in the root namespace + rb_iseq_eval(rb_iseq_check(iseq), rb_root_box()); // builtin functions are loaded in the root box } void diff --git a/class.c b/class.c index 8c3c91cf7a92b4..16ebd3b88965ea 100644 --- a/class.c +++ b/class.c @@ -41,21 +41,21 @@ * 1: RUBY_FL_SINGLETON * This class is a singleton class. * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This class's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. + * This class's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. * 3: RCLASS_IS_INITIALIZED * Class has been initialized. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. */ /* Flags of T_ICLASS * * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This module's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * This module's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. */ /* Flags of T_MODULE @@ -66,12 +66,12 @@ * 1: * Ensures that RUBY_FL_SINGLETON is never set on a T_MODULE. See `rb_class_real`. * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE - * This module's prime classext is the only classext and writable from any namespaces. - * If unset, the prime classext is writable only from the root namespace. + * This module's prime classext is the only classext and writable from any boxes. + * If unset, the prime classext is writable only from the root box. * 3: RCLASS_IS_INITIALIZED * Module has been initialized. - * 4: RCLASS_NAMESPACEABLE - * Is a builtin class that may be namespaced. It larger than a normal class. + * 4: RCLASS_BOXABLE + * Is a builtin class that may be boxed. It larger than a normal class. * 5: RMODULE_IS_REFINEMENT * Module is used for refinements. */ @@ -150,15 +150,15 @@ iclass_free_orphan_classext(VALUE klass, rb_classext_t *ext) xfree(ext); } -struct rb_class_set_namespace_classext_args { +struct rb_class_set_box_classext_args { VALUE obj; rb_classext_t *ext; }; static int -rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) +rb_class_set_box_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) { - struct rb_class_set_namespace_classext_args *args = (struct rb_class_set_namespace_classext_args *)a; + struct rb_class_set_box_classext_args *args = (struct rb_class_set_box_classext_args *)a; if (existing) { if (LIKELY(BUILTIN_TYPE(args->obj) == T_ICLASS)) { @@ -175,14 +175,14 @@ rb_class_set_namespace_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, s } void -rb_class_set_namespace_classext(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext) +rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext) { - struct rb_class_set_namespace_classext_args args = { + struct rb_class_set_box_classext_args args = { .obj = obj, .ext = ext, }; - st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)ns->ns_object, rb_class_set_namespace_classext_update, (st_data_t)&args); + st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, rb_class_set_box_classext_update, (st_data_t)&args); } RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; @@ -289,12 +289,12 @@ duplicate_classext_const_tbl(struct rb_id_table *src, VALUE klass) } static VALUE -namespace_subclasses_tbl_key(const rb_namespace_t *ns) +box_subclasses_tbl_key(const rb_box_t *box) { - if (!ns){ + if (!box){ return 0; } - return (VALUE)ns->ns_id; + return (VALUE)box->box_id; } static void @@ -302,16 +302,16 @@ duplicate_classext_subclasses(rb_classext_t *orig, rb_classext_t *copy) { rb_subclass_anchor_t *anchor, *orig_anchor; rb_subclass_entry_t *head, *cur, *cdr, *entry, *first = NULL; - rb_ns_subclasses_t *ns_subclasses; + rb_box_subclasses_t *box_subclasses; struct st_table *tbl; if (RCLASSEXT_SUBCLASSES(orig)) { orig_anchor = RCLASSEXT_SUBCLASSES(orig); - ns_subclasses = orig_anchor->ns_subclasses; - tbl = ((rb_ns_subclasses_t *)ns_subclasses)->tbl; + box_subclasses = orig_anchor->box_subclasses; + tbl = ((rb_box_subclasses_t *)box_subclasses)->tbl; anchor = ZALLOC(rb_subclass_anchor_t); - anchor->ns_subclasses = rb_ns_subclasses_ref_inc(ns_subclasses); + anchor->box_subclasses = rb_box_subclasses_ref_inc(box_subclasses); head = ZALLOC(rb_subclass_entry_t); anchor->head = head; @@ -333,28 +333,28 @@ duplicate_classext_subclasses(rb_classext_t *orig, rb_classext_t *copy) cdr->prev = cur; cur->next = cdr; if (!first) { - VALUE ns_id = namespace_subclasses_tbl_key(RCLASSEXT_NS(copy)); + VALUE box_id = box_subclasses_tbl_key(RCLASSEXT_BOX(copy)); first = cdr; - st_insert(tbl, ns_id, (st_data_t)first); + st_insert(tbl, box_id, (st_data_t)first); } cur = cdr; entry = entry->next; } } - if (RCLASSEXT_NS_SUPER_SUBCLASSES(orig)) - RCLASSEXT_NS_SUPER_SUBCLASSES(copy) = rb_ns_subclasses_ref_inc(RCLASSEXT_NS_SUPER_SUBCLASSES(orig)); - if (RCLASSEXT_NS_MODULE_SUBCLASSES(orig)) - RCLASSEXT_NS_MODULE_SUBCLASSES(copy) = rb_ns_subclasses_ref_inc(RCLASSEXT_NS_MODULE_SUBCLASSES(orig)); + if (RCLASSEXT_BOX_SUPER_SUBCLASSES(orig)) + RCLASSEXT_BOX_SUPER_SUBCLASSES(copy) = rb_box_subclasses_ref_inc(RCLASSEXT_BOX_SUPER_SUBCLASSES(orig)); + if (RCLASSEXT_BOX_MODULE_SUBCLASSES(orig)) + RCLASSEXT_BOX_MODULE_SUBCLASSES(copy) = rb_box_subclasses_ref_inc(RCLASSEXT_BOX_MODULE_SUBCLASSES(orig)); } static void -class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_namespace_t *ns) +class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_box_t *box) { RUBY_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); rb_classext_t *src = RCLASS_EXT_PRIME(iclass); - rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(iclass, ns); + rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(iclass, box); int first_set = 0; if (ext) { @@ -364,7 +364,7 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n ext = ZALLOC(rb_classext_t); - RCLASSEXT_NS(ext) = ns; + RCLASSEXT_BOX(ext) = box; RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(src); @@ -383,7 +383,7 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n // RCLASSEXT_CALLABLE_M_TBL(ext) = NULL; // RCLASSEXT_CC_TBL(ext) = NULL; - // subclasses, namespace_super_subclasses_tbl, namespace_module_subclasses_tbl + // subclasses, box_super_subclasses_tbl, box_module_subclasses_tbl duplicate_classext_subclasses(src, ext); RCLASSEXT_SET_ORIGIN(ext, iclass, RCLASSEXT_ORIGIN(src)); @@ -392,23 +392,23 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n RCLASSEXT_SET_INCLUDER(ext, iclass, RCLASSEXT_INCLUDER(src)); - VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_BOXABLE)); - first_set = RCLASS_SET_NAMESPACE_CLASSEXT(iclass, ns, ext); + first_set = RCLASS_SET_BOX_CLASSEXT(iclass, box, ext); if (first_set) { RCLASS_SET_PRIME_CLASSEXT_WRITABLE(iclass, false); } } rb_classext_t * -rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace_t *ns) +rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *box) { VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); rb_classext_t *ext = ZALLOC(rb_classext_t); bool dup_iclass = RB_TYPE_P(klass, T_MODULE) ? true : false; - RCLASSEXT_NS(ext) = ns; + RCLASSEXT_BOX(ext) = box; RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(orig); @@ -433,7 +433,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace * so initially, it can be NULL and let it be created lazily. * RCLASSEXT_CALLABLE_M_TBL(ext) = NULL; * - * cc_tbl is for method inline cache, and method calls from different namespaces never occur on + * cc_tbl is for method inline cache, and method calls from different boxes never occur on * the same code, so the copied classext should have a different cc_tbl from the prime one. * RCLASSEXT_CC_TBL(copy) = NULL */ @@ -445,7 +445,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace RCLASSEXT_SET_ORIGIN(ext, klass, RCLASSEXT_ORIGIN(orig)); /* - * Members not copied to namespace classext values + * Members not copied to box's classext values * * refined_class * * as.class.allocator / as.singleton_class.attached_object * * includer @@ -463,7 +463,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace /* * ICLASS has the same m_tbl/const_tbl/cvc_tbl with the included module. * So the module's classext is copied, its tables should be also referred - * by the ICLASS's classext for the namespace. + * by the ICLASS's classext for the box. */ rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); rb_subclass_entry_t *subclass_entry = anchor->head; @@ -472,9 +472,9 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace iclass = subclass_entry->klass; if (RBASIC_CLASS(iclass) == klass) { // Is the subclass an ICLASS including this module into another class - // If so we need to re-associate it under our namespace with the new ext - VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_NAMESPACEABLE)); - class_duplicate_iclass_classext(iclass, ext, ns); + // If so we need to re-associate it under our box with the new ext + VM_ASSERT(FL_TEST_RAW(iclass, RCLASS_BOXABLE)); + class_duplicate_iclass_classext(iclass, ext, box); } } subclass_entry = subclass_entry->next; @@ -541,9 +541,9 @@ push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) { rb_subclass_entry_t *entry, *head; rb_subclass_anchor_t *anchor; - rb_ns_subclasses_t *ns_subclasses; + rb_box_subclasses_t *box_subclasses; struct st_table *tbl; - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); entry = ZALLOC(rb_subclass_entry_t); entry->klass = klass; @@ -551,9 +551,9 @@ push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) RB_VM_LOCKING() { anchor = RCLASS_WRITABLE_SUBCLASSES(super); VM_ASSERT(anchor); - ns_subclasses = (rb_ns_subclasses_t *)anchor->ns_subclasses; - VM_ASSERT(ns_subclasses); - tbl = ns_subclasses->tbl; + box_subclasses = (rb_box_subclasses_t *)anchor->box_subclasses; + VM_ASSERT(box_subclasses); + tbl = box_subclasses->tbl; VM_ASSERT(tbl); head = anchor->head; @@ -563,14 +563,14 @@ push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) } head->next = entry; entry->prev = head; - st_insert(tbl, namespace_subclasses_tbl_key(ns), (st_data_t)entry); + st_insert(tbl, box_subclasses_tbl_key(box), (st_data_t)entry); } if (is_module) { - RCLASS_WRITE_NS_MODULE_SUBCLASSES(klass, anchor->ns_subclasses); + RCLASS_WRITE_BOX_MODULE_SUBCLASSES(klass, anchor->box_subclasses); } else { - RCLASS_WRITE_NS_SUPER_SUBCLASSES(klass, anchor->ns_subclasses); + RCLASS_WRITE_BOX_SUPER_SUBCLASSES(klass, anchor->box_subclasses); } } @@ -598,10 +598,10 @@ rb_class_remove_subclass_head(VALUE klass) } static struct rb_subclass_entry * -class_get_subclasses_for_ns(struct st_table *tbl, VALUE ns_id) +class_get_subclasses_for_ns(struct st_table *tbl, VALUE box_id) { st_data_t value; - if (st_lookup(tbl, (st_data_t)ns_id, &value)) { + if (st_lookup(tbl, (st_data_t)box_id, &value)) { return (struct rb_subclass_entry *)value; } return NULL; @@ -615,9 +615,9 @@ remove_class_from_subclasses_replace_first_entry(st_data_t *key, st_data_t *valu } static void -remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) +remove_class_from_subclasses(struct st_table *tbl, VALUE box_id, VALUE klass) { - rb_subclass_entry_t *entry = class_get_subclasses_for_ns(tbl, ns_id); + rb_subclass_entry_t *entry = class_get_subclasses_for_ns(tbl, box_id); bool first_entry = true; while (entry) { if (entry->klass == klass) { @@ -632,11 +632,11 @@ remove_class_from_subclasses(struct st_table *tbl, VALUE ns_id, VALUE klass) if (first_entry) { if (next) { - st_update(tbl, ns_id, remove_class_from_subclasses_replace_first_entry, (st_data_t)next); + st_update(tbl, box_id, remove_class_from_subclasses_replace_first_entry, (st_data_t)next); } else { // no subclass entries in this ns after the deletion - st_delete(tbl, &ns_id, NULL); + st_delete(tbl, &box_id, NULL); } } @@ -655,32 +655,32 @@ void rb_class_remove_from_super_subclasses(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_ns_subclasses_t *ns_subclasses = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); + rb_box_subclasses_t *box_subclasses = RCLASSEXT_BOX_SUPER_SUBCLASSES(ext); - if (!ns_subclasses) return; - remove_class_from_subclasses(ns_subclasses->tbl, namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)), klass); - rb_ns_subclasses_ref_dec(ns_subclasses); - RCLASSEXT_NS_SUPER_SUBCLASSES(ext) = 0; + if (!box_subclasses) return; + remove_class_from_subclasses(box_subclasses->tbl, box_subclasses_tbl_key(RCLASSEXT_BOX(ext)), klass); + rb_box_subclasses_ref_dec(box_subclasses); + RCLASSEXT_BOX_SUPER_SUBCLASSES(ext) = 0; } void rb_class_remove_from_module_subclasses(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - rb_ns_subclasses_t *ns_subclasses = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); + rb_box_subclasses_t *box_subclasses = RCLASSEXT_BOX_MODULE_SUBCLASSES(ext); - if (!ns_subclasses) return; - remove_class_from_subclasses(ns_subclasses->tbl, namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)), klass); - rb_ns_subclasses_ref_dec(ns_subclasses); - RCLASSEXT_NS_MODULE_SUBCLASSES(ext) = 0; + if (!box_subclasses) return; + remove_class_from_subclasses(box_subclasses->tbl, box_subclasses_tbl_key(RCLASSEXT_BOX(ext)), klass); + rb_box_subclasses_ref_dec(box_subclasses); + RCLASSEXT_BOX_MODULE_SUBCLASSES(ext) = 0; } void rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass, bool replacing) { rb_subclass_anchor_t *anchor = RCLASSEXT_SUBCLASSES(ext); - struct st_table *tbl = anchor->ns_subclasses->tbl; - VALUE ns_id = namespace_subclasses_tbl_key(RCLASSEXT_NS(ext)); + struct st_table *tbl = anchor->box_subclasses->tbl; + VALUE box_id = box_subclasses_tbl_key(RCLASSEXT_BOX(ext)); rb_subclass_entry_t *next, *entry = anchor->head; while (entry) { @@ -689,21 +689,21 @@ rb_class_classext_free_subclasses(rb_classext_t *ext, VALUE klass, bool replacin entry = next; } VM_ASSERT( - rb_ns_subclasses_ref_count(anchor->ns_subclasses) > 0, - "ns_subclasses refcount (%p) %d", anchor->ns_subclasses, rb_ns_subclasses_ref_count(anchor->ns_subclasses)); - st_delete(tbl, &ns_id, NULL); - rb_ns_subclasses_ref_dec(anchor->ns_subclasses); + rb_box_subclasses_ref_count(anchor->box_subclasses) > 0, + "box_subclasses refcount (%p) %d", anchor->box_subclasses, rb_box_subclasses_ref_count(anchor->box_subclasses)); + st_delete(tbl, &box_id, NULL); + rb_box_subclasses_ref_dec(anchor->box_subclasses); xfree(anchor); - if (!replacing && RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) { - rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_SUPER_SUBCLASSES(ext); - remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); - rb_ns_subclasses_ref_dec(ns_sub); + if (!replacing && RCLASSEXT_BOX_SUPER_SUBCLASSES(ext)) { + rb_box_subclasses_t *box_sub = RCLASSEXT_BOX_SUPER_SUBCLASSES(ext); + remove_class_from_subclasses(box_sub->tbl, box_id, klass); + rb_box_subclasses_ref_dec(box_sub); } - if (!replacing && RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) { - rb_ns_subclasses_t *ns_sub = RCLASSEXT_NS_MODULE_SUBCLASSES(ext); - remove_class_from_subclasses(ns_sub->tbl, ns_id, klass); - rb_ns_subclasses_ref_dec(ns_sub); + if (!replacing && RCLASSEXT_BOX_MODULE_SUBCLASSES(ext)) { + rb_box_subclasses_t *box_sub = RCLASSEXT_BOX_MODULE_SUBCLASSES(ext); + remove_class_from_subclasses(box_sub->tbl, box_id, klass); + rb_box_subclasses_ref_dec(box_sub); } } @@ -766,19 +766,19 @@ class_switch_superclass(VALUE super, VALUE klass) * @note this function is not Class#allocate. */ static VALUE -class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) +class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) { - rb_ns_subclasses_t *ns_subclasses; + rb_box_subclasses_t *box_subclasses; rb_subclass_anchor_t *anchor; - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); - if (!ruby_namespace_init_done) { - namespaceable = true; + if (!ruby_box_init_done) { + boxable = true; } size_t alloc_size = sizeof(struct RClass_and_rb_classext_t); - if (namespaceable) { - alloc_size = sizeof(struct RClass_namespaceable); + if (boxable) { + alloc_size = sizeof(struct RClass_boxable); } // class_alloc is supposed to return a new object that is not promoted yet. @@ -787,18 +787,18 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) // // TODO: Note that this could cause memory leak. // If NEWOBJ_OF fails with out of memory, these buffers will leak. - ns_subclasses = ZALLOC(rb_ns_subclasses_t); - ns_subclasses->refcount = 1; - ns_subclasses->tbl = st_init_numtable(); + box_subclasses = ZALLOC(rb_box_subclasses_t); + box_subclasses->refcount = 1; + box_subclasses->tbl = st_init_numtable(); anchor = ZALLOC(rb_subclass_anchor_t); - anchor->ns_subclasses = ns_subclasses; + anchor->box_subclasses = box_subclasses; anchor->head = ZALLOC(rb_subclass_entry_t); RUBY_ASSERT(type == T_CLASS || type == T_ICLASS || type == T_MODULE); VALUE flags = type | FL_SHAREABLE; if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; - if (namespaceable) flags |= RCLASS_NAMESPACEABLE; + if (boxable) flags |= RCLASS_BOXABLE; NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size, 0); @@ -813,14 +813,14 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) RCLASS_SET_SUPER((VALUE)obj, 0); */ - if (namespaceable) { - ((struct RClass_namespaceable *)obj)->ns_classext_tbl = NULL; + if (boxable) { + ((struct RClass_boxable *)obj)->box_classext_tbl = NULL; } - RCLASS_PRIME_NS((VALUE)obj) = ns; - // Classes/Modules defined in user namespaces are - // writable directly because it exists only in a namespace. - RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, !namespaceable || NAMESPACE_USER_P(ns)); + RCLASS_PRIME_BOX((VALUE)obj) = box; + // Classes/Modules defined in user boxes are + // writable directly because it exists only in a box. + RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, !boxable || BOX_USER_P(box)); RCLASS_SET_ORIGIN((VALUE)obj, (VALUE)obj); RCLASS_SET_REFINED_CLASS((VALUE)obj, Qnil); @@ -872,9 +872,9 @@ class_clear_method_table(VALUE c) } static VALUE -class_boot_namespaceable(VALUE super, bool namespaceable) +class_boot_boxable(VALUE super, bool boxable) { - VALUE klass = class_alloc0(T_CLASS, rb_cClass, namespaceable); + VALUE klass = class_alloc0(T_CLASS, rb_cClass, boxable); // initialize method table prior to class_associate_super() // because class_associate_super() may cause GC and promote klass @@ -900,7 +900,7 @@ class_boot_namespaceable(VALUE super, bool namespaceable) VALUE rb_class_boot(VALUE super) { - return class_boot_namespaceable(super, false); + return class_boot_boxable(super, false); } static VALUE * @@ -1387,7 +1387,7 @@ static inline VALUE make_metaclass(VALUE klass) { VALUE super; - VALUE metaclass = class_boot_namespaceable(Qundef, FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)); + VALUE metaclass = class_boot_boxable(Qundef, FL_TEST_RAW(klass, RCLASS_BOXABLE)); FL_SET(metaclass, FL_SINGLETON); rb_singleton_class_attached(metaclass, klass); @@ -1423,7 +1423,7 @@ static inline VALUE make_singleton_class(VALUE obj) { VALUE orig_class = METACLASS_OF(obj); - VALUE klass = class_boot_namespaceable(orig_class, FL_TEST_RAW(orig_class, RCLASS_NAMESPACEABLE)); + VALUE klass = class_boot_boxable(orig_class, FL_TEST_RAW(orig_class, RCLASS_BOXABLE)); FL_SET(klass, FL_SINGLETON); RBASIC_SET_CLASS(obj, klass); diff --git a/dln.c b/dln.c index 6bc4ef199e8479..2549f031835844 100644 --- a/dln.c +++ b/dln.c @@ -389,7 +389,7 @@ dln_open(const char *file) # endif /* Load file */ - int mode = rb_namespace_available() ? RTLD_LAZY|RTLD_LOCAL : RTLD_LAZY|RTLD_GLOBAL; + int mode = rb_box_available() ? RTLD_LAZY|RTLD_LOCAL : RTLD_LAZY|RTLD_GLOBAL; handle = dlopen(file, mode); if (handle == NULL) { error = dln_strerror(); diff --git a/eval.c b/eval.c index 0ff59efd988975..4fbb0e799735d4 100644 --- a/eval.c +++ b/eval.c @@ -78,9 +78,9 @@ ruby_setup(void) #endif Init_BareVM(); rb_vm_encoded_insn_data_table_init(); - Init_enable_namespace(); + Init_enable_box(); Init_vm_objects(); - Init_root_namespace(); + Init_root_box(); Init_fstring_table(); EC_PUSH_TAG(GET_EC()); diff --git a/eval_intern.h b/eval_intern.h index 4ac950e23813cb..91808f1f29aa43 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -296,11 +296,11 @@ VALUE rb_vm_make_jump_tag_but_local_jump(enum ruby_tag_type state, VALUE val); rb_cref_t *rb_vm_cref(void); rb_cref_t *rb_vm_cref_replace_with_duplicated_cref(void); VALUE rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, VALUE block_handler, VALUE filename); -VALUE rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, VALUE filename, const rb_namespace_t *ns); -void rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec); -const rb_namespace_t *rb_vm_current_namespace(const rb_execution_context_t *ec); -const rb_namespace_t *rb_vm_caller_namespace(const rb_execution_context_t *ec); -const rb_namespace_t *rb_vm_loading_namespace(const rb_execution_context_t *ec); +VALUE rb_vm_call_cfunc_in_box(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, VALUE filename, const rb_box_t *box); +void rb_vm_frame_flag_set_box_require(const rb_execution_context_t *ec); +const rb_box_t *rb_vm_current_box(const rb_execution_context_t *ec); +const rb_box_t *rb_vm_caller_box(const rb_execution_context_t *ec); +const rb_box_t *rb_vm_loading_box(const rb_execution_context_t *ec); void rb_vm_set_progname(VALUE filename); VALUE rb_vm_cbase(void); diff --git a/gc.c b/gc.c index d1e542de2c6ecb..92ba02809abb90 100644 --- a/gc.c +++ b/gc.c @@ -1228,7 +1228,7 @@ struct classext_foreach_args { }; static void -classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) +classext_free(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg) { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; @@ -1236,7 +1236,7 @@ classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) } static void -classext_iclass_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) +classext_iclass_free(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg) { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; @@ -1918,8 +1918,8 @@ object_id(VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - // With namespaces, classes and modules have different fields - // in different namespaces, so we cannot store the object id + // With Ruby Box, classes and modules have different fields + // in different boxes, so we cannot store the object id // in fields. return class_object_id(obj); case T_IMEMO: @@ -2271,7 +2271,7 @@ rb_gc_after_updating_jit_code(void) } static void -classext_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) +classext_memsize(rb_classext_t *ext, bool prime, VALUE box_value, void *arg) { size_t *size = (size_t *)arg; size_t s = 0; @@ -2295,7 +2295,7 @@ classext_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) } static void -classext_superclasses_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) +classext_superclasses_memsize(rb_classext_t *ext, bool prime, VALUE box_value, void *arg) { size_t *size = (size_t *)arg; size_t array_size; @@ -3075,7 +3075,7 @@ struct gc_mark_classext_foreach_arg { }; static void -gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) +gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE box_value, void *arg) { struct gc_mark_classext_foreach_arg *foreach_arg = (struct gc_mark_classext_foreach_arg *)arg; rb_objspace_t *objspace = foreach_arg->objspace; @@ -3100,7 +3100,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *a } static void -gc_mark_classext_iclass(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) +gc_mark_classext_iclass(rb_classext_t *ext, bool prime, VALUE box_value, void *arg) { struct gc_mark_classext_foreach_arg *foreach_arg = (struct gc_mark_classext_foreach_arg *)arg; rb_objspace_t *objspace = foreach_arg->objspace; @@ -3774,7 +3774,7 @@ update_classext_values(rb_objspace_t *objspace, rb_classext_t *ext, bool is_icla } static void -update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) +update_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg) { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; rb_objspace_t *objspace = args->objspace; @@ -3798,7 +3798,7 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) } static void -update_iclass_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) +update_iclass_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg) { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; rb_objspace_t *objspace = args->objspace; diff --git a/inits.c b/inits.c index 5209e429f996e9..4569362b95b8b7 100644 --- a/inits.c +++ b/inits.c @@ -52,7 +52,7 @@ rb_call_inits(void) CALL(Time); CALL(Random); CALL(load); - CALL(Namespace); + CALL(Box); CALL(Proc); CALL(Binding); CALL(Math); diff --git a/insns.def b/insns.def index 8c16a67717bbb0..f282b5a8e7084c 100644 --- a/insns.def +++ b/insns.def @@ -803,13 +803,13 @@ defineclass (VALUE val) { VALUE klass = vm_find_or_create_class_by_id(id, flags, cbase, super); - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); rb_iseq_check(class_iseq); /* enter scope */ vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass, - GC_GUARDED_PTR(ns), + GC_GUARDED_PTR(box), (VALUE)vm_cref_push(ec, klass, NULL, FALSE, FALSE), ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(), ISEQ_BODY(class_iseq)->local_table_size, diff --git a/internal/box.h b/internal/box.h index c5a495fdf003ba..36844ebf19b24a 100644 --- a/internal/box.h +++ b/internal/box.h @@ -1,5 +1,5 @@ -#ifndef INTERNAL_NAMESPACE_H /*-*-C-*-vi:se ft=c:*/ -#define INTERNAL_NAMESPACE_H +#ifndef INTERNAL_BOX_H /*-*-C-*-vi:se ft=c:*/ +#define INTERNAL_BOX_H #include "ruby/ruby.h" /* for VALUE */ @@ -9,15 +9,15 @@ * Permission is hereby granted, to either redistribute and/or * modify this file, provided that the conditions mentioned in the * file COPYING are met. Consult the file for details. - * @brief Internal header for Namespace. + * @brief Internal header for Ruby Box. */ -struct rb_namespace_struct { +struct rb_box_struct { /* - * To retrieve Namespace object that provides #require and so on. - * That is used from load.c, etc., that uses rb_namespace_t internally. + * To retrieve Ruby::Box object that provides #require and so on. + * That is used from load.c, etc., that uses rb_box_t internally. */ - VALUE ns_object; - long ns_id; // namespace id to generate ext filenames + VALUE box_object; + long box_id; // box_id to generate ext filenames VALUE top_self; @@ -38,44 +38,44 @@ struct rb_namespace_struct { bool is_user; bool is_optional; }; -typedef struct rb_namespace_struct rb_namespace_t; +typedef struct rb_box_struct rb_box_t; -#define NAMESPACE_OBJ_P(obj) (rb_obj_class(obj) == rb_cNamespace) +#define BOX_OBJ_P(obj) (rb_obj_class(obj) == rb_cNamespace) -#define NAMESPACE_ROOT_P(ns) (ns && !ns->is_user) -#define NAMESPACE_USER_P(ns) (ns && ns->is_user) -#define NAMESPACE_OPTIONAL_P(ns) (ns && ns->is_optional) -#define NAMESPACE_MAIN_P(ns) (ns && ns->is_user && !ns->is_optional) +#define BOX_ROOT_P(box) (box && !box->is_user) +#define BOX_USER_P(box) (box && box->is_user) +#define BOX_OPTIONAL_P(box) (box && box->is_optional) +#define BOX_MAIN_P(box) (box && box->is_user && !box->is_optional) -#define NAMESPACE_METHOD_DEFINITION(mdef) (mdef ? mdef->ns : NULL) -#define NAMESPACE_METHOD_ENTRY(me) (me ? NAMESPACE_METHOD_DEFINITION(me->def) : NULL) -#define NAMESPACE_CC(cc) (cc ? NAMESPACE_METHOD_ENTRY(cc->cme_) : NULL) -#define NAMESPACE_CC_ENTRIES(ccs) (ccs ? NAMESPACE_METHOD_ENTRY(ccs->cme) : NULL) +#define BOX_METHOD_DEFINITION(mdef) (mdef ? mdef->ns : NULL) +#define BOX_METHOD_ENTRY(me) (me ? BOX_METHOD_DEFINITION(me->def) : NULL) +#define BOX_CC(cc) (cc ? BOX_METHOD_ENTRY(cc->cme_) : NULL) +#define BOX_CC_ENTRIES(ccs) (ccs ? BOX_METHOD_ENTRY(ccs->cme) : NULL) -RUBY_EXTERN bool ruby_namespace_enabled; -RUBY_EXTERN bool ruby_namespace_init_done; -RUBY_EXTERN bool ruby_namespace_crashed; +RUBY_EXTERN bool ruby_box_enabled; +RUBY_EXTERN bool ruby_box_init_done; +RUBY_EXTERN bool ruby_box_crashed; static inline bool -rb_namespace_available(void) +rb_box_available(void) { - return ruby_namespace_enabled; + return ruby_box_enabled; } -const rb_namespace_t * rb_root_namespace(void); -const rb_namespace_t * rb_main_namespace(void); -const rb_namespace_t * rb_current_namespace(void); -const rb_namespace_t * rb_loading_namespace(void); -const rb_namespace_t * rb_current_namespace_in_crash_report(void); +const rb_box_t * rb_root_box(void); +const rb_box_t * rb_main_box(void); +const rb_box_t * rb_current_box(void); +const rb_box_t * rb_loading_box(void); +const rb_box_t * rb_current_box_in_crash_report(void); -void rb_namespace_entry_mark(void *); -void rb_namespace_gc_update_references(void *ptr); +void rb_box_entry_mark(void *); +void rb_box_gc_update_references(void *ptr); -rb_namespace_t * rb_get_namespace_t(VALUE ns); -VALUE rb_get_namespace_object(rb_namespace_t *ns); +rb_box_t * rb_get_box_t(VALUE ns); +VALUE rb_get_box_object(rb_box_t *ns); -VALUE rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path); +VALUE rb_box_local_extension(VALUE box, VALUE fname, VALUE path); -void rb_initialize_main_namespace(void); -void rb_namespace_init_done(void); -#endif /* INTERNAL_NAMESPACE_H */ +void rb_initialize_main_box(void); +void rb_box_init_done(void); +#endif /* INTERNAL_BOX_H */ diff --git a/internal/class.h b/internal/class.h index 19393eb7c3eeaf..04d2849656222b 100644 --- a/internal/class.h +++ b/internal/class.h @@ -10,7 +10,7 @@ */ #include "id.h" #include "id_table.h" /* for struct rb_id_table */ -#include "internal/box.h" /* for rb_current_namespace */ +#include "internal/box.h" #include "internal/serial.h" /* for rb_serial_t */ #include "internal/static_assert.h" #include "internal/variable.h" /* for rb_class_ivar_set */ @@ -27,37 +27,37 @@ # undef RCLASS_SUPER #endif -struct rb_ns_subclasses { +struct rb_box_subclasses { rb_atomic_t refcount; struct st_table *tbl; }; -typedef struct rb_ns_subclasses rb_ns_subclasses_t; +typedef struct rb_box_subclasses rb_box_subclasses_t; static inline rb_atomic_t -rb_ns_subclasses_ref_count(rb_ns_subclasses_t *ns_sub) +rb_box_subclasses_ref_count(rb_box_subclasses_t *box_sub) { - return ATOMIC_LOAD_RELAXED(ns_sub->refcount); + return ATOMIC_LOAD_RELAXED(box_sub->refcount); } -static inline rb_ns_subclasses_t * -rb_ns_subclasses_ref_inc(rb_ns_subclasses_t *ns_sub) +static inline rb_box_subclasses_t * +rb_box_subclasses_ref_inc(rb_box_subclasses_t *box_sub) { - RUBY_ATOMIC_FETCH_ADD(ns_sub->refcount, 1); - return ns_sub; + RUBY_ATOMIC_FETCH_ADD(box_sub->refcount, 1); + return box_sub; } static inline void -rb_ns_subclasses_ref_dec(rb_ns_subclasses_t *ns_sub) +rb_box_subclasses_ref_dec(rb_box_subclasses_t *box_sub) { - rb_atomic_t was = RUBY_ATOMIC_FETCH_SUB(ns_sub->refcount, 1); + rb_atomic_t was = RUBY_ATOMIC_FETCH_SUB(box_sub->refcount, 1); if (was == 1) { - st_free_table(ns_sub->tbl); - xfree(ns_sub); + st_free_table(box_sub->tbl); + xfree(box_sub); } } struct rb_subclass_anchor { - rb_ns_subclasses_t *ns_subclasses; + rb_box_subclasses_t *box_subclasses; struct rb_subclass_entry *head; }; typedef struct rb_subclass_anchor rb_subclass_anchor_t; @@ -77,7 +77,7 @@ struct rb_cvar_class_tbl_entry { }; struct rb_classext_struct { - const rb_namespace_t *ns; + const rb_box_t *box; VALUE super; VALUE fields_obj; // Fields are either ivar or other internal properties stored inline struct rb_id_table *m_tbl; @@ -92,19 +92,19 @@ struct rb_classext_struct { */ struct rb_subclass_anchor *subclasses; /** - * The `ns_super_subclasses` points the `ns_subclasses` struct to retreive the subclasses - * of the super class in a specific namespace. + * The `box_super_subclasses` points the `box_subclasses` struct to retreive the subclasses + * of the super class in a specific box. * In compaction GCs, collecting a classext should trigger the deletion of a rb_subclass_entry * from the super's subclasses. But it may be prevented by the read barrier. * Fetching the super's subclasses for a ns is to avoid the read barrier in that process. */ - rb_ns_subclasses_t *ns_super_subclasses; + rb_box_subclasses_t *box_super_subclasses; /** - * In the case that this is an `ICLASS`, `ns_module_subclasses` points to the link + * In the case that this is an `ICLASS`, `box_module_subclasses` points to the link * in the module's `subclasses` list that indicates that the klass has been * included. Hopefully that makes sense. */ - rb_ns_subclasses_t *ns_module_subclasses; + rb_box_subclasses_t *box_module_subclasses; const VALUE origin_; const VALUE refined_class; @@ -138,7 +138,7 @@ struct RClass { struct RBasic basic; VALUE object_id; /* - * If ns_classext_tbl is NULL, then the prime classext is readable (because no other classext exists). + * If box_classext_tbl is NULL, then the prime classext is readable (because no other classext exists). * For the check whether writable or not, check flag RCLASS_PRIME_CLASSEXT_WRITABLE */ }; @@ -154,9 +154,9 @@ struct RClass_and_rb_classext_t { STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass_and_rb_classext_t) <= 4 * RVALUE_SIZE); #endif -struct RClass_namespaceable { +struct RClass_boxable { struct RClass_and_rb_classext_t base; - st_table *ns_classext_tbl; // ns_object -> (rb_classext_t *) + st_table *box_classext_tbl; // box_object -> (rb_classext_t *) }; static const uint16_t RCLASS_MAX_SUPERCLASS_DEPTH = ((uint16_t)-1); @@ -170,13 +170,13 @@ static inline void RCLASS_SET_PRIME_CLASSEXT_WRITABLE(VALUE obj, bool writable); #define RCLASS_EXT_PRIME(c) (&((struct RClass_and_rb_classext_t*)(c))->classext) #define RCLASS_EXT_PRIME_P(ext, c) (&((struct RClass_and_rb_classext_t*)(c))->classext == ext) -static inline rb_classext_t * RCLASS_EXT_READABLE_IN_NS(VALUE obj, const rb_namespace_t *ns); +static inline rb_classext_t * RCLASS_EXT_READABLE_IN_BOX(VALUE obj, const rb_box_t *box); static inline rb_classext_t * RCLASS_EXT_READABLE(VALUE obj); -static inline rb_classext_t * RCLASS_EXT_WRITABLE_IN_NS(VALUE obj, const rb_namespace_t *ns); +static inline rb_classext_t * RCLASS_EXT_WRITABLE_IN_BOX(VALUE obj, const rb_box_t *box); static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj); // Raw accessor -#define RCLASSEXT_NS(ext) (ext->ns) +#define RCLASSEXT_BOX(ext) (ext->box) #define RCLASSEXT_SUPER(ext) (ext->super) #define RCLASSEXT_FIELDS(ext) (ext->fields_obj ? ROBJECT_FIELDS(ext->fields_obj) : NULL) #define RCLASSEXT_FIELDS_OBJ(ext) (ext->fields_obj) @@ -188,8 +188,8 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj); #define RCLASSEXT_SUPERCLASS_DEPTH(ext) (ext->superclass_depth) #define RCLASSEXT_SUPERCLASSES(ext) (ext->superclasses) #define RCLASSEXT_SUBCLASSES(ext) (ext->subclasses) -#define RCLASSEXT_NS_SUPER_SUBCLASSES(ext) (ext->ns_super_subclasses) -#define RCLASSEXT_NS_MODULE_SUBCLASSES(ext) (ext->ns_module_subclasses) +#define RCLASSEXT_BOX_SUPER_SUBCLASSES(ext) (ext->box_super_subclasses) +#define RCLASSEXT_BOX_MODULE_SUBCLASSES(ext) (ext->box_module_subclasses) #define RCLASSEXT_ORIGIN(ext) (ext->origin_) #define RCLASSEXT_REFINED_CLASS(ext) (ext->refined_class) // class.allocator/singleton_class.attached_object are not accessed directly via RCLASSEXT_* @@ -206,7 +206,7 @@ static inline void RCLASSEXT_SET_ORIGIN(rb_classext_t *ext, VALUE klass, VALUE o static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE includer); /* Prime classext entry accessor for very specific reason */ -#define RCLASS_PRIME_NS(c) (RCLASS_EXT_PRIME(c)->ns) +#define RCLASS_PRIME_BOX(c) (RCLASS_EXT_PRIME(c)->box) // To invalidate CC by inserting&invalidating method entry into tables containing the target cme // See clear_method_cache_by_id_in_class() #define RCLASS_PRIME_FIELDS_OBJ(c) (RCLASS_EXT_PRIME(c)->fields_obj) @@ -218,7 +218,7 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE #define RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(c, tbl) (RCLASS_EXT_PRIME(c)->callable_m_tbl != tbl) #define RCLASS_CC_TBL_NOT_PRIME_P(c, tbl) (RCLASS_EXT_PRIME(c)->cc_tbl != tbl) -// Read accessor, regarding namespaces +// Read accessor, regarding box #define RCLASS_SUPER(c) (RCLASS_EXT_READABLE(c)->super) #define RCLASS_M_TBL(c) (RCLASS_EXT_READABLE(c)->m_tbl) #define RCLASS_CONST_TBL(c) (RCLASS_EXT_READABLE(c)->const_tbl) @@ -240,12 +240,12 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE #define RCLASS_SUPERCLASSES(c) (RCLASS_EXT_PRIME(c)->superclasses) #define RCLASS_SUPERCLASSES_WITH_SELF_P(c) (RCLASS_EXT_PRIME(c)->superclasses_with_self) -// namespaces don't make changes on these refined_class/attached_object/includer +// Ruby Box doesn't make changes on these refined_class/attached_object/includer #define RCLASS_REFINED_CLASS(c) (RCLASS_EXT_PRIME(c)->refined_class) #define RCLASS_ATTACHED_OBJECT(c) (RCLASS_EXT_PRIME(c)->as.singleton_class.attached_object) #define RCLASS_INCLUDER(c) (RCLASS_EXT_PRIME(c)->as.iclass.includer) -// max IV count and variation count are just hints, so they don't need to be per-namespace +// max IV count and variation count are just hints, so they don't need to be per-box #define RCLASS_MAX_IV_COUNT(ext) (RCLASS_EXT_PRIME(ext)->max_iv_count) #define RCLASS_VARIATION_COUNT(ext) (RCLASS_EXT_PRIME(ext)->variation_count) @@ -268,8 +268,8 @@ static inline void RCLASS_WRITE_CVC_TBL(VALUE klass, struct rb_id_table *table); static inline void RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool with_self); static inline void RCLASS_SET_SUBCLASSES(VALUE klass, rb_subclass_anchor_t *anchor); -static inline void RCLASS_WRITE_NS_SUPER_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses); -static inline void RCLASS_WRITE_NS_MODULE_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses); +static inline void RCLASS_WRITE_BOX_SUPER_SUBCLASSES(VALUE klass, rb_box_subclasses_t *box_subclasses); +static inline void RCLASS_WRITE_BOX_MODULE_SUBCLASSES(VALUE klass, rb_box_subclasses_t *box_subclasses); static inline void RCLASS_SET_ORIGIN(VALUE klass, VALUE origin); static inline void RCLASS_WRITE_ORIGIN(VALUE klass, VALUE origin); @@ -293,14 +293,14 @@ static inline void RCLASS_WRITE_CLASSPATH(VALUE klass, VALUE classpath, bool per #define RCLASS_PRIME_CLASSEXT_WRITABLE FL_USER2 #define RCLASS_IS_INITIALIZED FL_USER3 // 3 is RMODULE_IS_REFINEMENT for RMODULE -#define RCLASS_NAMESPACEABLE FL_USER4 +#define RCLASS_BOXABLE FL_USER4 static inline st_table * RCLASS_CLASSEXT_TBL(VALUE klass) { - if (FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)) { - struct RClass_namespaceable *ns_klass = (struct RClass_namespaceable *)klass; - return ns_klass->ns_classext_tbl; + if (FL_TEST_RAW(klass, RCLASS_BOXABLE)) { + struct RClass_boxable *box_klass = (struct RClass_boxable *)klass; + return box_klass->box_classext_tbl; } return NULL; } @@ -308,25 +308,25 @@ RCLASS_CLASSEXT_TBL(VALUE klass) static inline void RCLASS_SET_CLASSEXT_TBL(VALUE klass, st_table *tbl) { - RUBY_ASSERT(FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)); - struct RClass_namespaceable *ns_klass = (struct RClass_namespaceable *)klass; - ns_klass->ns_classext_tbl = tbl; + RUBY_ASSERT(FL_TEST_RAW(klass, RCLASS_BOXABLE)); + struct RClass_boxable *box_klass = (struct RClass_boxable *)klass; + box_klass->box_classext_tbl = tbl; } /* class.c */ -rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns); +rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_box_t *box); void rb_class_ensure_writable(VALUE obj); -void rb_class_set_namespace_classext(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext); +void rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext); static inline int -RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t *ext) +RCLASS_SET_BOX_CLASSEXT(VALUE obj, const rb_box_t *box, rb_classext_t *ext) { int first_set = 0; st_table *tbl = RCLASS_CLASSEXT_TBL(obj); - VM_ASSERT(NAMESPACE_USER_P(ns)); // non-prime classext is only for user namespace, with ns_object - VM_ASSERT(ns->ns_object); - VM_ASSERT(RCLASSEXT_NS(ext) == ns); + VM_ASSERT(BOX_USER_P(box)); // non-prime classext is only for user box, with box_object + VM_ASSERT(box->box_object); + VM_ASSERT(RCLASSEXT_BOX(ext) == box); if (!tbl) { tbl = st_init_numtable_with_size(1); RCLASS_SET_CLASSEXT_TBL(obj, tbl); @@ -335,28 +335,28 @@ RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t first_set = 1; } - rb_class_set_namespace_classext(obj, ns, ext); + rb_class_set_box_classext(obj, box, ext); return first_set; } -#define VM_ASSERT_NAMESPACEABLE_TYPE(klass) \ - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS), "%s is not namespaceable type", rb_type_str(BUILTIN_TYPE(klass))) +#define VM_ASSERT_BOXABLE_TYPE(klass) \ + VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS), "%s is not boxable type", rb_type_str(BUILTIN_TYPE(klass))) static inline bool RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) { VM_ASSERT(klass != 0, "klass should be a valid object"); - VM_ASSERT_NAMESPACEABLE_TYPE(klass); + VM_ASSERT_BOXABLE_TYPE(klass); // if the lookup table exists, then it means the prime classext is NOT directly readable. - return !FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE) || RCLASS_CLASSEXT_TBL(klass) == NULL; + return !FL_TEST_RAW(klass, RCLASS_BOXABLE) || RCLASS_CLASSEXT_TBL(klass) == NULL; } static inline bool RCLASS_PRIME_CLASSEXT_WRITABLE_P(VALUE klass) { VM_ASSERT(klass != 0, "klass should be a valid object"); - VM_ASSERT_NAMESPACEABLE_TYPE(klass); + VM_ASSERT_BOXABLE_TYPE(klass); return FL_TEST(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } @@ -364,7 +364,7 @@ static inline void RCLASS_SET_PRIME_CLASSEXT_WRITABLE(VALUE klass, bool writable) { VM_ASSERT(klass != 0, "klass should be a valid object"); - VM_ASSERT_NAMESPACEABLE_TYPE(klass); + VM_ASSERT_BOXABLE_TYPE(klass); if (writable) { FL_SET(klass, RCLASS_PRIME_CLASSEXT_WRITABLE); } @@ -374,12 +374,12 @@ RCLASS_SET_PRIME_CLASSEXT_WRITABLE(VALUE klass, bool writable) } static inline rb_classext_t * -RCLASS_EXT_TABLE_LOOKUP_INTERNAL(VALUE obj, const rb_namespace_t *ns) +RCLASS_EXT_TABLE_LOOKUP_INTERNAL(VALUE obj, const rb_box_t *box) { st_data_t classext_ptr; st_table *classext_tbl = RCLASS_CLASSEXT_TBL(obj); if (classext_tbl) { - if (rb_st_lookup(classext_tbl, (st_data_t)ns->ns_object, &classext_ptr)) { + if (rb_st_lookup(classext_tbl, (st_data_t)box->box_object, &classext_ptr)) { return (rb_classext_t *)classext_ptr; } } @@ -387,9 +387,9 @@ RCLASS_EXT_TABLE_LOOKUP_INTERNAL(VALUE obj, const rb_namespace_t *ns) } static inline rb_classext_t * -RCLASS_EXT_READABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) +RCLASS_EXT_READABLE_LOOKUP(VALUE obj, const rb_box_t *box) { - rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, ns); + rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, box); if (ext) return ext; // Classext for the ns not found. Refer the prime one instead. @@ -397,46 +397,46 @@ RCLASS_EXT_READABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) } static inline rb_classext_t * -RCLASS_EXT_READABLE_IN_NS(VALUE obj, const rb_namespace_t *ns) +RCLASS_EXT_READABLE_IN_BOX(VALUE obj, const rb_box_t *box) { - if (NAMESPACE_ROOT_P(ns) + if (BOX_ROOT_P(box) || RCLASS_PRIME_CLASSEXT_READABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } - return RCLASS_EXT_READABLE_LOOKUP(obj, ns); + return RCLASS_EXT_READABLE_LOOKUP(obj, box); } static inline rb_classext_t * RCLASS_EXT_READABLE(VALUE obj) { - const rb_namespace_t *ns; + const rb_box_t *box; if (RCLASS_PRIME_CLASSEXT_READABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } - // delay determining the current namespace to optimize for unmodified classes - ns = rb_current_namespace(); - if (NAMESPACE_ROOT_P(ns)) { + // delay determining the current box to optimize for unmodified classes + box = rb_current_box(); + if (BOX_ROOT_P(box)) { return RCLASS_EXT_PRIME(obj); } - return RCLASS_EXT_READABLE_LOOKUP(obj, ns); + return RCLASS_EXT_READABLE_LOOKUP(obj, box); } static inline rb_classext_t * -RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) +RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_box_t *box) { rb_classext_t *ext; int first_set = 0; - ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, ns); + ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, box); if (ext) return ext; RB_VM_LOCKING() { // re-check the classext is not created to avoid the multi-thread race - ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, ns); + ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, box); if (!ext) { - ext = rb_class_duplicate_classext(RCLASS_EXT_PRIME(obj), obj, ns); - first_set = RCLASS_SET_NAMESPACE_CLASSEXT(obj, ns, ext); + ext = rb_class_duplicate_classext(RCLASS_EXT_PRIME(obj), obj, box); + first_set = RCLASS_SET_BOX_CLASSEXT(obj, box, ext); if (first_set) { // TODO: are there any case that a class/module become non-writable after its birthtime? RCLASS_SET_PRIME_CLASSEXT_WRITABLE(obj, false); @@ -447,28 +447,28 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) } static inline rb_classext_t * -RCLASS_EXT_WRITABLE_IN_NS(VALUE obj, const rb_namespace_t *ns) +RCLASS_EXT_WRITABLE_IN_BOX(VALUE obj, const rb_box_t *box) { - if (NAMESPACE_ROOT_P(ns) + if (BOX_ROOT_P(box) || RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj)) { return RCLASS_EXT_PRIME(obj); } - return RCLASS_EXT_WRITABLE_LOOKUP(obj, ns); + return RCLASS_EXT_WRITABLE_LOOKUP(obj, box); } static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj) { - const rb_namespace_t *ns; + const rb_box_t *box; if (LIKELY(RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj))) { return RCLASS_EXT_PRIME(obj); } - // delay determining the current namespace to optimize for unmodified classes - ns = rb_current_namespace(); - if (NAMESPACE_ROOT_P(ns)) { + // delay determining the current box to optimize for unmodified classes + box = rb_current_box(); + if (BOX_ROOT_P(box)) { return RCLASS_EXT_PRIME(obj); } - return RCLASS_EXT_WRITABLE_LOOKUP(obj, ns); + return RCLASS_EXT_WRITABLE_LOOKUP(obj, box); } static inline void @@ -485,7 +485,7 @@ RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE includer) } /* class.c */ -typedef void rb_class_classext_foreach_callback_func(rb_classext_t *classext, bool is_prime, VALUE namespace, void *arg); +typedef void rb_class_classext_foreach_callback_func(rb_classext_t *classext, bool is_prime, VALUE box_value, void *arg); void rb_class_classext_foreach(VALUE klass, rb_class_classext_foreach_callback_func *func, void *arg); void rb_class_subclass_add(VALUE super, VALUE klass); void rb_class_remove_from_super_subclasses(VALUE); @@ -739,21 +739,21 @@ RCLASS_SET_SUBCLASSES(VALUE klass, struct rb_subclass_anchor *anchor) } static inline void -RCLASS_WRITE_NS_SUPER_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses) +RCLASS_WRITE_BOX_SUPER_SUBCLASSES(VALUE klass, rb_box_subclasses_t *box_subclasses) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - if (RCLASSEXT_NS_SUPER_SUBCLASSES(ext)) - rb_ns_subclasses_ref_dec(RCLASSEXT_NS_SUPER_SUBCLASSES(ext)); - RCLASSEXT_NS_SUPER_SUBCLASSES(ext) = rb_ns_subclasses_ref_inc(ns_subclasses); + if (RCLASSEXT_BOX_SUPER_SUBCLASSES(ext)) + rb_box_subclasses_ref_dec(RCLASSEXT_BOX_SUPER_SUBCLASSES(ext)); + RCLASSEXT_BOX_SUPER_SUBCLASSES(ext) = rb_box_subclasses_ref_inc(box_subclasses); } static inline void -RCLASS_WRITE_NS_MODULE_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses) +RCLASS_WRITE_BOX_MODULE_SUBCLASSES(VALUE klass, rb_box_subclasses_t *box_subclasses) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); - if (RCLASSEXT_NS_MODULE_SUBCLASSES(ext)) - rb_ns_subclasses_ref_dec(RCLASSEXT_NS_MODULE_SUBCLASSES(ext)); - RCLASSEXT_NS_MODULE_SUBCLASSES(ext) = rb_ns_subclasses_ref_inc(ns_subclasses); + if (RCLASSEXT_BOX_MODULE_SUBCLASSES(ext)) + rb_box_subclasses_ref_dec(RCLASSEXT_BOX_MODULE_SUBCLASSES(ext)); + RCLASSEXT_BOX_MODULE_SUBCLASSES(ext) = rb_box_subclasses_ref_inc(box_subclasses); } static inline void diff --git a/internal/inits.h b/internal/inits.h index c1cf3db94d664c..dee818285c75ad 100644 --- a/internal/inits.h +++ b/internal/inits.h @@ -9,6 +9,10 @@ * @brief Internal header aggregating init functions. */ +/* box.c */ +void Init_enable_box(void); +void Init_root_box(void); + /* class.c */ void Init_class_hierarchy(void); @@ -25,16 +29,10 @@ int Init_enc_set_filesystem_encoding(void); /* newline.c */ void Init_newline(void); -/* namespace.c */ -void Init_enable_namespace(void); - /* vm.c */ void Init_BareVM(void); void Init_vm_objects(void); -/* namespace.c */ -void Init_root_namespace(void); - /* vm_backtrace.c */ void Init_vm_backtrace(void); diff --git a/internal/variable.h b/internal/variable.h index 5e2bcceb61875c..ca5e189c904fe3 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -22,13 +22,13 @@ VALUE rb_search_class_path(VALUE); VALUE rb_attr_delete(VALUE, ID); void rb_autoload_str(VALUE mod, ID id, VALUE file); VALUE rb_autoload_at_p(VALUE, ID, int); -void rb_autoload_copy_table_for_namespace(st_table *, const rb_namespace_t *); +void rb_autoload_copy_table_for_box(st_table *, const rb_box_t *); NORETURN(VALUE rb_mod_const_missing(VALUE,VALUE)); rb_gvar_getter_t *rb_gvar_getter_function_of(ID); rb_gvar_setter_t *rb_gvar_setter_function_of(ID); void rb_gvar_readonly_setter(VALUE v, ID id, VALUE *_); void rb_gvar_ractor_local(const char *name); -void rb_gvar_namespace_ready(const char *name); +void rb_gvar_box_ready(const char *name); /** * Sets the name of a module. diff --git a/iseq.c b/iseq.c index df849d05215a6d..9bf56ad6557545 100644 --- a/iseq.c +++ b/iseq.c @@ -2003,7 +2003,7 @@ iseqw_eval(VALUE self) if (0 == ISEQ_BODY(iseq)->iseq_size) { rb_raise(rb_eTypeError, "attempt to evaluate dummy InstructionSequence"); } - return rb_iseq_eval(iseq, rb_current_namespace()); + return rb_iseq_eval(iseq, rb_current_box()); } /* diff --git a/load.c b/load.c index c38a5fbd321672..69f1d365f269a0 100644 --- a/load.c +++ b/load.c @@ -65,10 +65,10 @@ enum expand_type { string objects in $LOAD_PATH are frozen. */ static void -rb_construct_expanded_load_path(rb_namespace_t *ns, enum expand_type type, int *has_relative, int *has_non_cache) +rb_construct_expanded_load_path(rb_box_t *box, enum expand_type type, int *has_relative, int *has_non_cache) { - VALUE load_path = ns->load_path; - VALUE expanded_load_path = ns->expanded_load_path; + VALUE load_path = box->load_path; + VALUE expanded_load_path = box->expanded_load_path; VALUE snapshot; VALUE ary; long i; @@ -108,39 +108,39 @@ rb_construct_expanded_load_path(rb_namespace_t *ns, enum expand_type type, int * rb_ary_push(ary, rb_fstring(expanded_path)); } rb_ary_freeze(ary); - ns->expanded_load_path = ary; - snapshot = ns->load_path_snapshot; - load_path = ns->load_path; + box->expanded_load_path = ary; + snapshot = box->load_path_snapshot; + load_path = box->load_path; rb_ary_replace(snapshot, load_path); } static VALUE -get_expanded_load_path(rb_namespace_t *ns) +get_expanded_load_path(rb_box_t *box) { VALUE check_cache; const VALUE non_cache = Qtrue; - const VALUE load_path_snapshot = ns->load_path_snapshot; - const VALUE load_path = ns->load_path; + const VALUE load_path_snapshot = box->load_path_snapshot; + const VALUE load_path = box->load_path; if (!rb_ary_shared_with_p(load_path_snapshot, load_path)) { /* The load path was modified. Rebuild the expanded load path. */ int has_relative = 0, has_non_cache = 0; - rb_construct_expanded_load_path(ns, EXPAND_ALL, &has_relative, &has_non_cache); + rb_construct_expanded_load_path(box, EXPAND_ALL, &has_relative, &has_non_cache); if (has_relative) { - ns->load_path_check_cache = rb_dir_getwd_ospath(); + box->load_path_check_cache = rb_dir_getwd_ospath(); } else if (has_non_cache) { /* Non string object. */ - ns->load_path_check_cache = non_cache; + box->load_path_check_cache = non_cache; } else { - ns->load_path_check_cache = 0; + box->load_path_check_cache = 0; } } - else if ((check_cache = ns->load_path_check_cache) == non_cache) { + else if ((check_cache = box->load_path_check_cache) == non_cache) { int has_relative = 1, has_non_cache = 1; /* Expand only non-cacheable objects. */ - rb_construct_expanded_load_path(ns, EXPAND_NON_CACHE, + rb_construct_expanded_load_path(box, EXPAND_NON_CACHE, &has_relative, &has_non_cache); } else if (check_cache) { @@ -149,49 +149,49 @@ get_expanded_load_path(rb_namespace_t *ns) if (!rb_str_equal(check_cache, cwd)) { /* Current working directory or filesystem encoding was changed. Expand relative load path and non-cacheable objects again. */ - ns->load_path_check_cache = cwd; - rb_construct_expanded_load_path(ns, EXPAND_RELATIVE, + box->load_path_check_cache = cwd; + rb_construct_expanded_load_path(box, EXPAND_RELATIVE, &has_relative, &has_non_cache); } else { /* Expand only tilde (User HOME) and non-cacheable objects. */ - rb_construct_expanded_load_path(ns, EXPAND_HOME, + rb_construct_expanded_load_path(box, EXPAND_HOME, &has_relative, &has_non_cache); } } - return ns->expanded_load_path; + return box->expanded_load_path; } VALUE rb_get_expanded_load_path(void) { - return get_expanded_load_path((rb_namespace_t *)rb_loading_namespace()); + return get_expanded_load_path((rb_box_t *)rb_loading_box()); } static VALUE load_path_getter(ID _x, VALUE * _y) { - return rb_loading_namespace()->load_path; + return rb_loading_box()->load_path; } static VALUE get_LOADED_FEATURES(ID _x, VALUE *_y) { - return rb_loading_namespace()->loaded_features; + return rb_loading_box()->loaded_features; } static void -reset_loaded_features_snapshot(const rb_namespace_t *ns) +reset_loaded_features_snapshot(const rb_box_t *box) { - VALUE snapshot = ns->loaded_features_snapshot; - VALUE loaded_features = ns->loaded_features; + VALUE snapshot = box->loaded_features_snapshot; + VALUE loaded_features = box->loaded_features; rb_ary_replace(snapshot, loaded_features); } static struct st_table * -get_loaded_features_index_raw(const rb_namespace_t *ns) +get_loaded_features_index_raw(const rb_box_t *box) { - return ns->loaded_features_index; + return box->loaded_features_index; } static st_data_t @@ -212,7 +212,7 @@ is_rbext_path(VALUE feature_path) typedef rb_darray(long) feature_indexes_t; struct features_index_add_single_args { - const rb_namespace_t *ns; + const rb_box_t *box; VALUE offset; bool rb; }; @@ -221,7 +221,7 @@ static int features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t raw_args, int existing) { struct features_index_add_single_args *args = (struct features_index_add_single_args *)raw_args; - const rb_namespace_t *ns = args->ns; + const rb_box_t *box = args->box; VALUE offset = args->offset; bool rb = args->rb; @@ -229,7 +229,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r VALUE this_feature_index = *value; if (FIXNUM_P(this_feature_index)) { - VALUE loaded_features = ns->loaded_features; + VALUE loaded_features = box->loaded_features; VALUE this_feature_path = RARRAY_AREF(loaded_features, FIX2LONG(this_feature_index)); feature_indexes_t feature_indexes; @@ -249,7 +249,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r long pos = -1; if (rb) { - VALUE loaded_features = ns->loaded_features; + VALUE loaded_features = box->loaded_features; for (size_t i = 0; i < rb_darray_size(feature_indexes); ++i) { long idx = rb_darray_get(feature_indexes, i); VALUE this_feature_path = RARRAY_AREF(loaded_features, idx); @@ -281,7 +281,7 @@ features_index_add_single_callback(st_data_t *key, st_data_t *value, st_data_t r } static void -features_index_add_single(const rb_namespace_t *ns, const char* str, size_t len, VALUE offset, bool rb) +features_index_add_single(const rb_box_t *box, const char* str, size_t len, VALUE offset, bool rb) { struct st_table *features_index; st_data_t short_feature_key; @@ -289,10 +289,10 @@ features_index_add_single(const rb_namespace_t *ns, const char* str, size_t len, Check_Type(offset, T_FIXNUM); short_feature_key = feature_key(str, len); - features_index = get_loaded_features_index_raw(ns); + features_index = get_loaded_features_index_raw(box); struct features_index_add_single_args args = { - .ns = ns, + .box = box, .offset = offset, .rb = rb, }; @@ -309,7 +309,7 @@ features_index_add_single(const rb_namespace_t *ns, const char* str, size_t len, relies on for its fast lookup. */ static void -features_index_add(const rb_namespace_t *ns, VALUE feature, VALUE offset) +features_index_add(const rb_box_t *box, VALUE feature, VALUE offset) { RUBY_ASSERT(rb_ractor_main_p()); @@ -337,14 +337,14 @@ features_index_add(const rb_namespace_t *ns, VALUE feature, VALUE offset) if (p < feature_str) break; /* Now *p == '/'. We reach this point for every '/' in `feature`. */ - features_index_add_single(ns, p + 1, feature_end - p - 1, offset, false); + features_index_add_single(box, p + 1, feature_end - p - 1, offset, false); if (ext) { - features_index_add_single(ns, p + 1, ext - p - 1, offset, rb); + features_index_add_single(box, p + 1, ext - p - 1, offset, rb); } } - features_index_add_single(ns, feature_str, feature_end - feature_str, offset, false); + features_index_add_single(box, feature_str, feature_end - feature_str, offset, false); if (ext) { - features_index_add_single(ns, feature_str, ext - feature_str, offset, rb); + features_index_add_single(box, feature_str, ext - feature_str, offset, rb); } } @@ -359,19 +359,19 @@ loaded_features_index_clear_i(st_data_t key, st_data_t val, st_data_t arg) } static st_table * -get_loaded_features_index(const rb_namespace_t *ns) +get_loaded_features_index(const rb_box_t *box) { int i; - VALUE features = ns->loaded_features; - const VALUE snapshot = ns->loaded_features_snapshot; + VALUE features = box->loaded_features; + const VALUE snapshot = box->loaded_features_snapshot; if (!rb_ary_shared_with_p(snapshot, features)) { /* The sharing was broken; something (other than us in rb_provide_feature()) modified loaded_features. Rebuild the index. */ - st_foreach(ns->loaded_features_index, loaded_features_index_clear_i, 0); + st_foreach(box->loaded_features_index, loaded_features_index_clear_i, 0); - VALUE realpaths = ns->loaded_features_realpaths; - VALUE realpath_map = ns->loaded_features_realpath_map; + VALUE realpaths = box->loaded_features_realpaths; + VALUE realpath_map = box->loaded_features_realpath_map; VALUE previous_realpath_map = rb_hash_dup(realpath_map); rb_hash_clear(realpaths); rb_hash_clear(realpath_map); @@ -387,15 +387,15 @@ get_loaded_features_index(const rb_namespace_t *ns) as_str = rb_fstring(as_str); if (as_str != entry) rb_ary_store(features, i, as_str); - features_index_add(ns, as_str, INT2FIX(i)); + features_index_add(box, as_str, INT2FIX(i)); } /* The user modified $LOADED_FEATURES, so we should restore the changes. */ - if (!rb_ary_shared_with_p(features, ns->loaded_features)) { - rb_ary_replace(ns->loaded_features, features); + if (!rb_ary_shared_with_p(features, box->loaded_features)) { + rb_ary_replace(box->loaded_features, features); } - reset_loaded_features_snapshot(ns); + reset_loaded_features_snapshot(box); - features = ns->loaded_features_snapshot; + features = box->loaded_features_snapshot; long j = RARRAY_LEN(features); for (i = 0; i < j; i++) { VALUE as_str = rb_ary_entry(features, i); @@ -409,7 +409,7 @@ get_loaded_features_index(const rb_namespace_t *ns) rb_hash_aset(realpath_map, as_str, realpath); } } - return ns->loaded_features_index; + return box->loaded_features_index; } /* This searches `load_path` for a value such that @@ -494,7 +494,7 @@ loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f) * 'u': unsuffixed */ static int -rb_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) +rb_feature_p(const rb_box_t *box, const char *feature, const char *ext, int rb, int expanded, const char **fn) { VALUE features, this_feature_index = Qnil, v, p, load_path = 0; const char *f, *e; @@ -515,8 +515,8 @@ rb_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int elen = 0; type = 0; } - features = ns->loaded_features; - features_index = get_loaded_features_index(ns); + features = box->loaded_features; + features_index = get_loaded_features_index(box); key = feature_key(feature, strlen(feature)); /* We search `features` for an entry such that either @@ -564,7 +564,7 @@ rb_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int if ((n = RSTRING_LEN(v)) < len) continue; if (strncmp(f, feature, len) != 0) { if (expanded) continue; - if (!load_path) load_path = get_expanded_load_path((rb_namespace_t *)ns); + if (!load_path) load_path = get_expanded_load_path((rb_box_t *)box); if (!(p = loaded_feature_path(f, n, feature, len, type, load_path))) continue; expanded = 1; @@ -584,14 +584,14 @@ rb_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int } } - loading_tbl = ns->loading_table; + loading_tbl = box->loading_table; f = 0; if (!expanded && !rb_is_absolute_path(feature)) { struct loaded_feature_searching fs; fs.name = feature; fs.len = len; fs.type = type; - fs.load_path = load_path ? load_path : get_expanded_load_path((rb_namespace_t *)ns); + fs.load_path = load_path ? load_path : get_expanded_load_path((rb_box_t *)box); fs.result = 0; st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs); if ((f = fs.result) != 0) { @@ -646,7 +646,7 @@ rb_provided(const char *feature) } static int -feature_provided(rb_namespace_t *ns, const char *feature, const char **loading) +feature_provided(rb_box_t *box, const char *feature, const char **loading) { const char *ext = strrchr(feature, '.'); VALUE fullpath = 0; @@ -658,15 +658,15 @@ feature_provided(rb_namespace_t *ns, const char *feature, const char **loading) } if (ext && !strchr(ext, '/')) { if (IS_RBEXT(ext)) { - if (rb_feature_p(ns, feature, ext, TRUE, FALSE, loading)) return TRUE; + if (rb_feature_p(box, feature, ext, TRUE, FALSE, loading)) return TRUE; return FALSE; } else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { - if (rb_feature_p(ns, feature, ext, FALSE, FALSE, loading)) return TRUE; + if (rb_feature_p(box, feature, ext, FALSE, FALSE, loading)) return TRUE; return FALSE; } } - if (rb_feature_p(ns, feature, 0, TRUE, FALSE, loading)) + if (rb_feature_p(box, feature, 0, TRUE, FALSE, loading)) return TRUE; RB_GC_GUARD(fullpath); return FALSE; @@ -675,40 +675,40 @@ feature_provided(rb_namespace_t *ns, const char *feature, const char **loading) int rb_feature_provided(const char *feature, const char **loading) { - rb_namespace_t *ns = (rb_namespace_t *)rb_current_namespace(); - return feature_provided(ns, feature, loading); + rb_box_t *box = (rb_box_t *)rb_current_box(); + return feature_provided(box, feature, loading); } static void -rb_provide_feature(const rb_namespace_t *ns, VALUE feature) +rb_provide_feature(const rb_box_t *box, VALUE feature) { VALUE features; - features = ns->loaded_features; + features = box->loaded_features; if (OBJ_FROZEN(features)) { rb_raise(rb_eRuntimeError, "$LOADED_FEATURES is frozen; cannot append feature"); } feature = rb_fstring(feature); - get_loaded_features_index(ns); + get_loaded_features_index(box); // If loaded_features and loaded_features_snapshot share the same backing // array, pushing into it would cause the whole array to be copied. // To avoid this we first clear loaded_features_snapshot. - rb_ary_clear(ns->loaded_features_snapshot); + rb_ary_clear(box->loaded_features_snapshot); rb_ary_push(features, feature); - features_index_add(ns, feature, INT2FIX(RARRAY_LEN(features)-1)); - reset_loaded_features_snapshot(ns); + features_index_add(box, feature, INT2FIX(RARRAY_LEN(features)-1)); + reset_loaded_features_snapshot(box); } void rb_provide(const char *feature) { /* - * rb_provide() must use rb_current_namespace to store provided features - * in the current namespace's loaded_features, etc. + * rb_provide() must use rb_current_box to store provided features + * in the current box's loaded_features, etc. */ - rb_provide_feature(rb_current_namespace(), rb_fstring_cstr(feature)); + rb_provide_feature(rb_current_box(), rb_fstring_cstr(feature)); } NORETURN(static void load_failed(VALUE)); @@ -729,14 +729,14 @@ realpath_internal_cached(VALUE hash, VALUE path) static inline void load_iseq_eval(rb_execution_context_t *ec, VALUE fname) { - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); const rb_iseq_t *iseq = rb_iseq_load_iseq(fname); if (!iseq) { rb_execution_context_t *ec = GET_EC(); VALUE v = rb_vm_push_frame_fname(ec, fname); - VALUE realpath_map = ns->loaded_features_realpath_map; + VALUE realpath_map = box->loaded_features_realpath_map; if (rb_ruby_prism_p()) { pm_parse_result_t result = { 0 }; @@ -781,14 +781,14 @@ load_iseq_eval(rb_execution_context_t *ec, VALUE fname) } rb_exec_event_hook_script_compiled(ec, iseq, Qnil); - rb_iseq_eval(iseq, ns); + rb_iseq_eval(iseq, box); } static inline enum ruby_tag_type load_wrapping(rb_execution_context_t *ec, VALUE fname, VALUE load_wrapper) { enum ruby_tag_type state; - rb_namespace_t *ns; + rb_box_t *box; rb_thread_t *th = rb_ec_thread_ptr(ec); volatile VALUE wrapper = th->top_wrapper; volatile VALUE self = th->top_self; @@ -799,12 +799,12 @@ load_wrapping(rb_execution_context_t *ec, VALUE fname, VALUE load_wrapper) ec->errinfo = Qnil; /* ensure */ /* load in module as toplevel */ - if (NAMESPACE_OBJ_P(load_wrapper)) { - ns = rb_get_namespace_t(load_wrapper); - if (!ns->top_self) { - ns->top_self = rb_obj_clone(rb_vm_top_self()); + if (BOX_OBJ_P(load_wrapper)) { + box = rb_get_box_t(load_wrapper); + if (!box->top_self) { + box->top_self = rb_obj_clone(rb_vm_top_self()); } - th->top_self = ns->top_self; + th->top_self = box->top_self; } else { th->top_self = rb_obj_clone(rb_vm_top_self()); @@ -843,9 +843,9 @@ raise_load_if_failed(rb_execution_context_t *ec, enum ruby_tag_type state) static void rb_load_internal(VALUE fname, VALUE wrap) { - VALUE namespace; + VALUE box_value; rb_execution_context_t *ec = GET_EC(); - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); enum ruby_tag_type state = TAG_NONE; if (RTEST(wrap)) { if (!RB_TYPE_P(wrap, T_MODULE)) { @@ -853,9 +853,9 @@ rb_load_internal(VALUE fname, VALUE wrap) } state = load_wrapping(ec, fname, wrap); } - else if (NAMESPACE_OPTIONAL_P(ns)) { - namespace = ns->ns_object; - state = load_wrapping(ec, fname, namespace); + else if (BOX_OPTIONAL_P(box)) { + box_value = box->box_object; + state = load_wrapping(ec, fname, box_value); } else { load_iseq_eval(ec, fname); @@ -958,10 +958,10 @@ rb_f_load(int argc, VALUE *argv, VALUE _) } static char * -load_lock(const rb_namespace_t *ns, const char *ftptr, bool warn) +load_lock(const rb_box_t *box, const char *ftptr, bool warn) { st_data_t data; - st_table *loading_tbl = ns->loading_table; + st_table *loading_tbl = box->loading_table; if (!st_lookup(loading_tbl, (st_data_t)ftptr, &data)) { /* partial state */ @@ -1003,11 +1003,11 @@ release_thread_shield(st_data_t *key, st_data_t *value, st_data_t done, int exis } static void -load_unlock(const rb_namespace_t *ns, const char *ftptr, int done) +load_unlock(const rb_box_t *box, const char *ftptr, int done) { if (ftptr) { st_data_t key = (st_data_t)ftptr; - st_table *loading_tbl = ns->loading_table; + st_table *loading_tbl = box->loading_table; st_update(loading_tbl, key, release_thread_shield, done); } @@ -1055,8 +1055,6 @@ static VALUE rb_require_string_internal(VALUE fname, bool resurrect); VALUE rb_f_require(VALUE obj, VALUE fname) { - // const rb_namespace_t *ns = rb_loading_namespace(); - // printf("F:current loading ns: %ld\n", ns->ns_id); return rb_require_string(fname); } @@ -1086,10 +1084,10 @@ rb_f_require_relative(VALUE obj, VALUE fname) return rb_require_relative_entrypoint(fname); } -typedef int (*feature_func)(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn); +typedef int (*feature_func)(const rb_box_t *box, const char *feature, const char *ext, int rb, int expanded, const char **fn); static int -search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, feature_func rb_feature_p) +search_required(const rb_box_t *box, VALUE fname, volatile VALUE *path, feature_func rb_feature_p) { VALUE tmp; char *ext, *ftptr; @@ -1100,20 +1098,20 @@ search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, fea ext = strrchr(ftptr = RSTRING_PTR(fname), '.'); if (ext && !strchr(ext, '/')) { if (IS_RBEXT(ext)) { - if (rb_feature_p(ns, ftptr, ext, TRUE, FALSE, &loading)) { + if (rb_feature_p(box, ftptr, ext, TRUE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 'r'; } if ((tmp = rb_find_file(fname)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(ns, ftptr, ext, TRUE, TRUE, &loading) || loading) + if (!rb_feature_p(box, ftptr, ext, TRUE, TRUE, &loading) || loading) *path = tmp; return 'r'; } return 0; } else if (IS_SOEXT(ext)) { - if (rb_feature_p(ns, ftptr, ext, FALSE, FALSE, &loading)) { + if (rb_feature_p(box, ftptr, ext, FALSE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 's'; } @@ -1122,25 +1120,25 @@ search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, fea OBJ_FREEZE(tmp); if ((tmp = rb_find_file(tmp)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(ns, ftptr, ext, FALSE, TRUE, &loading) || loading) + if (!rb_feature_p(box, ftptr, ext, FALSE, TRUE, &loading) || loading) *path = tmp; return 's'; } } else if (IS_DLEXT(ext)) { - if (rb_feature_p(ns, ftptr, ext, FALSE, FALSE, &loading)) { + if (rb_feature_p(box, ftptr, ext, FALSE, FALSE, &loading)) { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 's'; } if ((tmp = rb_find_file(fname)) != 0) { ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (!rb_feature_p(ns, ftptr, ext, FALSE, TRUE, &loading) || loading) + if (!rb_feature_p(box, ftptr, ext, FALSE, TRUE, &loading) || loading) *path = tmp; return 's'; } } } - else if ((ft = rb_feature_p(ns, ftptr, 0, FALSE, FALSE, &loading)) == 'r') { + else if ((ft = rb_feature_p(box, ftptr, 0, FALSE, FALSE, &loading)) == 'r') { if (loading) *path = rb_filesystem_str_new_cstr(loading); return 'r'; } @@ -1174,7 +1172,7 @@ search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, fea if (ft) goto feature_present; ftptr = RSTRING_PTR(tmp); - return rb_feature_p(ns, ftptr, 0, FALSE, TRUE, 0); + return rb_feature_p(box, ftptr, 0, FALSE, TRUE, 0); default: if (ft) { @@ -1183,7 +1181,7 @@ search_required(const rb_namespace_t *ns, VALUE fname, volatile VALUE *path, fea /* fall through */ case loadable_ext_rb: ext = strrchr(ftptr = RSTRING_PTR(tmp), '.'); - if (rb_feature_p(ns, ftptr, ext, type == loadable_ext_rb, TRUE, &loading) && !loading) + if (rb_feature_p(box, ftptr, ext, type == loadable_ext_rb, TRUE, &loading) && !loading) break; *path = tmp; } @@ -1204,9 +1202,9 @@ static VALUE load_ext(VALUE path, VALUE fname) { VALUE loaded = path; - const rb_namespace_t *ns = rb_loading_namespace(); - if (NAMESPACE_USER_P(ns)) { - loaded = rb_namespace_local_extension(ns->ns_object, fname, path); + const rb_box_t *box = rb_loading_box(); + if (BOX_USER_P(box)) { + loaded = rb_box_local_extension(box->box_object, fname, path); } rb_scope_visibility_set(METHOD_VISI_PUBLIC); return (VALUE)dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); @@ -1228,7 +1226,7 @@ run_static_ext_init(VALUE vm_ptr, VALUE feature_value) } static int -no_feature_p(const rb_namespace_t *ns, const char *feature, const char *ext, int rb, int expanded, const char **fn) +no_feature_p(const rb_box_t *box, const char *feature, const char *ext, int rb, int expanded, const char **fn) { return 0; } @@ -1240,11 +1238,11 @@ rb_resolve_feature_path(VALUE klass, VALUE fname) VALUE path; int found; VALUE sym; - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); fname = rb_get_path(fname); path = rb_str_encode_ospath(fname); - found = search_required(ns, path, &path, no_feature_p); + found = search_required(box, path, &path, no_feature_p); switch (found) { case 'r': @@ -1291,22 +1289,22 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa { volatile int result = -1; rb_thread_t *th = rb_ec_thread_ptr(ec); - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); volatile const struct { VALUE wrapper, self, errinfo; rb_execution_context_t *ec; - const rb_namespace_t *ns; + const rb_box_t *box; } saved = { th->top_wrapper, th->top_self, ec->errinfo, - ec, ns, + ec, box, }; enum ruby_tag_type state; char *volatile ftptr = 0; VALUE path; volatile VALUE saved_path; volatile VALUE realpath = 0; - VALUE realpaths = ns->loaded_features_realpaths; - VALUE realpath_map = ns->loaded_features_realpath_map; + VALUE realpaths = box->loaded_features_realpaths; + VALUE realpath_map = box->loaded_features_realpath_map; volatile bool reset_ext_config = false; volatile struct rb_ext_config prev_ext_config; @@ -1322,18 +1320,18 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa int found; RUBY_DTRACE_HOOK(FIND_REQUIRE_ENTRY, RSTRING_PTR(fname)); - found = search_required(ns, path, &saved_path, rb_feature_p); + found = search_required(box, path, &saved_path, rb_feature_p); RUBY_DTRACE_HOOK(FIND_REQUIRE_RETURN, RSTRING_PTR(fname)); path = saved_path; if (found) { - if (!path || !(ftptr = load_lock(ns, RSTRING_PTR(path), warn))) { + if (!path || !(ftptr = load_lock(box, RSTRING_PTR(path), warn))) { result = 0; } else if (!*ftptr) { result = TAG_RETURN; } - else if (found == 's' && RTEST(rb_vm_call_cfunc_in_namespace(Qnil, run_static_ext_init, (VALUE)th->vm, path, path, ns))) { + else if (found == 's' && RTEST(rb_vm_call_cfunc_in_box(Qnil, run_static_ext_init, (VALUE)th->vm, path, path, box))) { result = TAG_RETURN; } else if (RTEST(rb_hash_aref(realpaths, @@ -1343,11 +1341,12 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa else { switch (found) { case 'r': - // iseq_eval_in_namespace will be called with the loading namespace eventually - if (NAMESPACE_OPTIONAL_P(ns)) { - // check with NAMESPACE_OPTIONAL_P (not NAMESPACE_USER_P) for NS1::xxx naming - // it is not expected for the main namespace - load_wrapping(saved.ec, path, ns->ns_object); + // iseq_eval_in_box will be called with the loading box eventually + if (BOX_OPTIONAL_P(box)) { + // check with BOX_OPTIONAL_P (not BOX_USER_P) for NS1::xxx naming + // it is not expected for the main box + // TODO: no need to use load_wrapping() here? + load_wrapping(saved.ec, path, box->box_object); } else { load_iseq_eval(saved.ec, path); @@ -1357,8 +1356,8 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa case 's': reset_ext_config = true; ext_config_push(th, &prev_ext_config); - handle = rb_vm_call_cfunc_in_namespace(ns->top_self, load_ext, path, fname, path, ns); - rb_hash_aset(ns->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle)); + handle = rb_vm_call_cfunc_in_box(box->top_self, load_ext, path, fname, path, box); + rb_hash_aset(box->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle)); break; } result = TAG_RETURN; @@ -1368,14 +1367,14 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa EC_POP_TAG(); ec = saved.ec; - ns = saved.ns; + box = saved.box; rb_thread_t *th2 = rb_ec_thread_ptr(ec); th2->top_self = saved.self; th2->top_wrapper = saved.wrapper; if (reset_ext_config) ext_config_pop(th2, &prev_ext_config); path = saved_path; - if (ftptr) load_unlock(ns, RSTRING_PTR(path), !state); + if (ftptr) load_unlock(box, RSTRING_PTR(path), !state); if (state) { if (state == TAG_FATAL || state == TAG_THROW) { @@ -1401,7 +1400,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa } if (result == TAG_RETURN) { - rb_provide_feature(ns, path); + rb_provide_feature(box, path); VALUE real = realpath; if (real) { real = rb_fstring(real); @@ -1505,9 +1504,9 @@ ruby_init_ext(const char *name, void (*init)(void)) { st_table *inits_table; rb_vm_t *vm = GET_VM(); - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); - if (feature_provided((rb_namespace_t *)ns, name, 0)) + if (feature_provided((rb_box_t *)box, name, 0)) return; inits_table = vm->static_ext_inits; @@ -1658,7 +1657,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) VALUE path; char *ext; VALUE fname_str = rb_str_new_cstr(fname); - const rb_namespace_t *ns = rb_loading_namespace(); + const rb_box_t *box = rb_loading_box(); resolved = rb_resolve_feature_path((VALUE)NULL, fname_str); if (NIL_P(resolved)) { @@ -1666,7 +1665,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) if (!ext || !IS_SOEXT(ext)) { rb_str_cat_cstr(fname_str, ".so"); } - if (rb_feature_p(ns, fname, 0, FALSE, FALSE, 0)) { + if (rb_feature_p(box, fname, 0, FALSE, FALSE, 0)) { return dln_symbol(NULL, symbol); } return NULL; @@ -1675,7 +1674,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) return NULL; } path = rb_ary_entry(resolved, 1); - handle = rb_hash_lookup(ns->ruby_dln_libmap, path); + handle = rb_hash_lookup(box->ruby_dln_libmap, path); if (NIL_P(handle)) { return NULL; } @@ -1689,14 +1688,14 @@ Init_load(void) ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1); rb_define_hooked_variable(var_load_path, 0, load_path_getter, rb_gvar_readonly_setter); - rb_gvar_namespace_ready(var_load_path); + rb_gvar_box_ready(var_load_path); rb_alias_variable(rb_intern_const("$-I"), id_load_path); rb_alias_variable(rb_intern_const("$LOAD_PATH"), id_load_path); rb_define_virtual_variable("$\"", get_LOADED_FEATURES, 0); - rb_gvar_namespace_ready("$\""); + rb_gvar_box_ready("$\""); rb_define_virtual_variable("$LOADED_FEATURES", get_LOADED_FEATURES, 0); // TODO: rb_alias_variable ? - rb_gvar_namespace_ready("$LOADED_FEATURES"); + rb_gvar_box_ready("$LOADED_FEATURES"); rb_define_global_function("load", rb_f_load, -1); rb_define_global_function("require", rb_f_require, 1); diff --git a/method.h b/method.h index 9e2fd249683c0c..87fb2498a054d7 100644 --- a/method.h +++ b/method.h @@ -204,7 +204,7 @@ struct rb_method_definition_struct { ID original_id; uintptr_t method_serial; - const rb_namespace_t *ns; + const rb_box_t *box; }; struct rb_id_table; diff --git a/mini_builtin.c b/mini_builtin.c index 3ff3dc5c1d2a08..48f401e78aa471 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -100,5 +100,5 @@ void rb_load_with_builtin_functions(const char *feature_name, const struct rb_builtin_function *table) { const rb_iseq_t *iseq = builtin_iseq_load(feature_name, table); - rb_iseq_eval(iseq, rb_root_namespace()); + rb_iseq_eval(iseq, rb_root_box()); } diff --git a/proc.c b/proc.c index 9c1a7a7fff2916..2b14c38938b8f0 100644 --- a/proc.c +++ b/proc.c @@ -735,7 +735,6 @@ sym_proc_new(VALUE klass, VALUE sym) GetProcPtr(procval, proc); vm_block_type_set(&proc->block, block_type_symbol); - // No namespace specified: similar to built-in methods proc->is_lambda = TRUE; RB_OBJ_WRITE(procval, &proc->block.as.symbol, sym); return procval; @@ -2029,23 +2028,22 @@ method_owner(VALUE obj) /* * call-see: - * meth.namespace -> namespace or nil + * meth.box -> box or nil * - * Returns the namespace where +meth+ is defined in. + * Returns the Ruby::Box where +meth+ is defined in. */ static VALUE -method_namespace(VALUE obj) +method_box(VALUE obj) { struct METHOD *data; - const rb_namespace_t *ns; + const rb_box_t *box; TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data); - ns = data->me->def->ns; - if (!ns) return Qfalse; - if (ns->ns_object) return ns->ns_object; - // This should not happen - rb_bug("Unexpected namespace on the method definition: %p", (void*) ns); - return Qtrue; + box = data->me->def->box; + if (!box) return Qnil; + if (box->box_object) return box->box_object; + rb_bug("Unexpected box on the method definition: %p", (void*) box); + UNREACHABLE_RETURN(Qnil); } void @@ -4520,7 +4518,7 @@ Init_Proc(void) rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 1); rb_define_method(rb_mKernel, "singleton_method", rb_obj_singleton_method, 1); - rb_define_method(rb_cMethod, "namespace", method_namespace, 0); + rb_define_method(rb_cMethod, "box", method_box, 0); /* UnboundMethod */ rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cObject); diff --git a/ruby.c b/ruby.c index 0f5e6d60f7f22b..c319fd1237a132 100644 --- a/ruby.c +++ b/ruby.c @@ -449,7 +449,7 @@ ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { const char sep = PATH_SEP_CHAR; const char *p, *s; - VALUE load_path = rb_root_namespace()->load_path; + VALUE load_path = rb_root_box()->load_path; #ifdef __CYGWIN__ char rubylib[FILENAME_MAX]; VALUE buf = 0; @@ -754,7 +754,7 @@ ruby_init_loadpath(void) rb_gc_register_address(&ruby_archlibdir_path); ruby_archlibdir_path = archlibdir; - load_path = rb_root_namespace()->load_path; + load_path = rb_root_box()->load_path; ruby_push_include(getenv("RUBYLIB"), identical_path); @@ -1831,11 +1831,11 @@ ruby_opt_init(ruby_cmdline_options_t *opt) ruby_init_prelude(); - /* Initialize the main namespace after loading libraries (including rubygems) + /* Initialize the main box after loading libraries (including rubygems) * to enable those in both root and main */ - if (rb_namespace_available()) - rb_initialize_main_namespace(); - rb_namespace_init_done(); + if (rb_box_available()) + rb_initialize_main_box(); + rb_box_init_done(); // Initialize JITs after ruby_init_prelude() because JITing prelude is typically not optimal. #if USE_YJIT @@ -2334,8 +2334,8 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) char fbuf[MAXPATHLEN]; int i = (int)proc_options(argc, argv, opt, 0); unsigned int dump = opt->dump & dump_exit_bits; - const rb_namespace_t *ns = rb_root_namespace(); - const long loaded_before_enc = RARRAY_LEN(ns->loaded_features); + const rb_box_t *box = rb_root_box(); + const long loaded_before_enc = RARRAY_LEN(box->loaded_features); if (opt->dump & (DUMP_BIT(usage)|DUMP_BIT(help))) { const char *const progname = @@ -2483,7 +2483,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) rb_obj_freeze(opt->script_name); if (IF_UTF8_PATH(uenc != lenc, 1)) { long i; - VALUE load_path = ns->load_path; + VALUE load_path = box->load_path; const ID id_initial_load_path_mark = INITIAL_LOAD_PATH_MARK; int modifiable = FALSE; @@ -2506,11 +2506,11 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) RARRAY_ASET(load_path, i, path); } if (modifiable) { - rb_ary_replace(ns->load_path_snapshot, load_path); + rb_ary_replace(box->load_path_snapshot, load_path); } } { - VALUE loaded_features = ns->loaded_features; + VALUE loaded_features = box->loaded_features; bool modified = false; for (long i = loaded_before_enc; i < RARRAY_LEN(loaded_features); ++i) { VALUE path = RARRAY_AREF(loaded_features, i); @@ -2522,7 +2522,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) RARRAY_ASET(loaded_features, i, path); } if (modified) { - rb_ary_replace(ns->loaded_features_snapshot, loaded_features); + rb_ary_replace(box->loaded_features_snapshot, loaded_features); } } diff --git a/shape.c b/shape.c index d6b5c3040881d2..7acfe72930a09f 100644 --- a/shape.c +++ b/shape.c @@ -744,7 +744,7 @@ rb_shape_transition_heap(VALUE obj, size_t heap_index) } void -rb_set_namespaced_class_shape_id(VALUE obj, shape_id_t shape_id) +rb_set_boxed_class_shape_id(VALUE obj, shape_id_t shape_id) { RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), shape_id); // FIXME: How to do multi-shape? diff --git a/shape.h b/shape.h index a20da1baa55668..6e4a1f079b44f3 100644 --- a/shape.h +++ b/shape.h @@ -177,7 +177,7 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id)); } -void rb_set_namespaced_class_shape_id(VALUE obj, shape_id_t shape_id); +void rb_set_boxed_class_shape_id(VALUE obj, shape_id_t shape_id); static inline void RB_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) @@ -185,7 +185,7 @@ RB_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - rb_set_namespaced_class_shape_id(obj, shape_id); + rb_set_boxed_class_shape_id(obj, shape_id); break; default: RBASIC_SET_SHAPE_ID(obj, shape_id); diff --git a/variable.c b/variable.c index 5a58f7ed6316d4..d7e04265c4276b 100644 --- a/variable.c +++ b/variable.c @@ -533,7 +533,7 @@ struct rb_global_variable { rb_gvar_marker_t *marker; rb_gvar_compact_t *compactor; struct trace_var *trace; - bool namespace_ready; + bool box_ready; }; struct rb_global_entry { @@ -612,10 +612,10 @@ rb_gvar_ractor_local(const char *name) } void -rb_gvar_namespace_ready(const char *name) +rb_gvar_box_ready(const char *name) { struct rb_global_entry *entry = rb_find_global_entry(rb_intern(name)); - entry->var->namespace_ready = true; + entry->var->box_ready = true; } static void @@ -645,7 +645,7 @@ rb_global_entry(ID id) var->block_trace = 0; var->trace = 0; - var->namespace_ready = false; + var->box_ready = false; rb_id_table_insert(rb_global_tbl, id, (VALUE)entry); } } @@ -1000,30 +1000,30 @@ rb_gvar_set_entry(struct rb_global_entry *entry, VALUE val) return val; } -#define USE_NAMESPACE_GVAR_TBL(ns,entry) \ - (NAMESPACE_USER_P(ns) && \ - (!entry || !entry->var->namespace_ready || entry->var->setter != rb_gvar_readonly_setter)) +#define USE_BOX_GVAR_TBL(ns,entry) \ + (BOX_USER_P(ns) && \ + (!entry || !entry->var->box_ready || entry->var->setter != rb_gvar_readonly_setter)) VALUE rb_gvar_set(ID id, VALUE val) { VALUE retval; struct rb_global_entry *entry; - const rb_namespace_t *ns = rb_current_namespace(); - bool use_namespace_tbl = false; + const rb_box_t *box = rb_current_box(); + bool use_box_tbl = false; RB_VM_LOCKING() { entry = rb_global_entry(id); - if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { - use_namespace_tbl = true; - rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); + if (USE_BOX_GVAR_TBL(box, entry)) { + use_box_tbl = true; + rb_hash_aset(box->gvar_tbl, rb_id2sym(entry->id), val); retval = val; // TODO: think about trace } } - if (!use_namespace_tbl) { + if (!use_box_tbl) { retval = rb_gvar_set_entry(entry, val); } return retval; @@ -1039,8 +1039,8 @@ VALUE rb_gvar_get(ID id) { VALUE retval, gvars, key; - const rb_namespace_t *ns = rb_current_namespace(); - bool use_namespace_tbl = false; + const rb_box_t *box = rb_current_box(); + bool use_box_tbl = false; struct rb_global_entry *entry; struct rb_global_variable *var; // TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist) @@ -1048,9 +1048,9 @@ rb_gvar_get(ID id) entry = rb_global_entry(id); var = entry->var; - if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { - use_namespace_tbl = true; - gvars = ns->gvar_tbl; + if (USE_BOX_GVAR_TBL(box, entry)) { + use_box_tbl = true; + gvars = box->gvar_tbl; key = rb_id2sym(entry->id); if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached retval = rb_hash_aref(gvars, key); @@ -1068,7 +1068,7 @@ rb_gvar_get(ID id) } } } - if (!use_namespace_tbl) { + if (!use_box_tbl) { retval = (*var->getter)(entry->id, var->data); } return retval; @@ -1125,7 +1125,7 @@ rb_f_global_variables(void) if (!rb_ractor_main_p()) { rb_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors"); } - /* gvar access (get/set) in namespaces creates gvar entries globally */ + /* gvar access (get/set) in boxes creates gvar entries globally */ rb_id_table_foreach(rb_global_tbl, gvar_i, (void *)ary); if (!NIL_P(backref)) { @@ -2619,9 +2619,9 @@ struct autoload_const { // The shared "autoload_data" if multiple constants are defined from the same feature. VALUE autoload_data_value; - // The namespace object when the autoload is called in a user namespace - // Otherwise, Qnil means the builtin namespace, Qfalse means unspecified. - VALUE namespace; + // The box object when the autoload is called in a user box + // Otherwise, Qnil means the root box + VALUE box_value; // The module we are loading a constant into. VALUE module; @@ -2698,7 +2698,7 @@ autoload_const_mark_and_move(void *ptr) rb_gc_mark_and_move(&ac->autoload_data_value); rb_gc_mark_and_move(&ac->value); rb_gc_mark_and_move(&ac->file); - rb_gc_mark_and_move(&ac->namespace); + rb_gc_mark_and_move(&ac->box_value); } static size_t @@ -2744,17 +2744,17 @@ get_autoload_data(VALUE autoload_const_value, struct autoload_const **autoload_c struct autoload_copy_table_data { VALUE dst_tbl_value; struct st_table *dst_tbl; - const rb_namespace_t *ns; + const rb_box_t *box; }; static int -autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t arg) +autoload_copy_table_for_box_i(st_data_t key, st_data_t value, st_data_t arg) { struct autoload_const *autoload_const; struct autoload_copy_table_data *data = (struct autoload_copy_table_data *)arg; struct st_table *tbl = data->dst_tbl; VALUE tbl_value = data->dst_tbl_value; - const rb_namespace_t *ns = data->ns; + const rb_box_t *box = data->box; VALUE src_value = (VALUE)value; struct autoload_const *src_const = rb_check_typeddata(src_value, &autoload_const_type); @@ -2763,7 +2763,7 @@ autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t ar struct autoload_data *autoload_data = rb_check_typeddata(autoload_data_value, &autoload_data_type); VALUE new_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->namespace = rb_get_namespace_object((rb_namespace_t *)ns); + autoload_const->box_value = rb_get_box_object((rb_box_t *)box); autoload_const->module = src_const->module; autoload_const->name = src_const->name; autoload_const->value = src_const->value; @@ -2778,7 +2778,7 @@ autoload_copy_table_for_namespace_i(st_data_t key, st_data_t value, st_data_t ar } void -rb_autoload_copy_table_for_namespace(st_table *iv_ptr, const rb_namespace_t *ns) +rb_autoload_copy_table_for_box(st_table *iv_ptr, const rb_box_t *box) { struct st_table *src_tbl, *dst_tbl; VALUE src_tbl_value, dst_tbl_value; @@ -2798,10 +2798,10 @@ rb_autoload_copy_table_for_namespace(st_table *iv_ptr, const rb_namespace_t *ns) struct autoload_copy_table_data data = { .dst_tbl_value = dst_tbl_value, .dst_tbl = dst_tbl, - .ns = ns, + .box = box, }; - st_foreach(src_tbl, autoload_copy_table_for_namespace_i, (st_data_t)&data); + st_foreach(src_tbl, autoload_copy_table_for_box_i, (st_data_t)&data); st_insert(iv_ptr, (st_data_t)autoload, (st_data_t)dst_tbl_value); } @@ -2822,7 +2822,7 @@ struct autoload_arguments { VALUE module; ID name; VALUE feature; - VALUE namespace; + VALUE box_value; }; static VALUE @@ -2892,7 +2892,7 @@ autoload_synchronized(VALUE _arguments) { struct autoload_const *autoload_const; VALUE autoload_const_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->namespace = arguments->namespace; + autoload_const->box_value = arguments->box_value; autoload_const->module = arguments->module; autoload_const->name = arguments->name; autoload_const->value = Qundef; @@ -2909,8 +2909,8 @@ autoload_synchronized(VALUE _arguments) void rb_autoload_str(VALUE module, ID name, VALUE feature) { - const rb_namespace_t *ns = rb_current_namespace(); - VALUE current_namespace = rb_get_namespace_object((rb_namespace_t *)ns); + const rb_box_t *box = rb_current_box(); + VALUE current_box_value = rb_get_box_object((rb_box_t *)box); if (!rb_is_const_id(name)) { rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(name)); @@ -2925,7 +2925,7 @@ rb_autoload_str(VALUE module, ID name, VALUE feature) .module = module, .name = name, .feature = feature, - .namespace = current_namespace, + .box_value = current_box_value, }; VALUE result = rb_mutex_synchronize(autoload_mutex, autoload_synchronized, (VALUE)&arguments); @@ -3192,17 +3192,17 @@ autoload_feature_require(VALUE _arguments) struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments; struct autoload_const *autoload_const = arguments->autoload_const; - VALUE autoload_namespace = autoload_const->namespace; + VALUE autoload_box_value = autoload_const->box_value; // We save this for later use in autoload_apply_constants: arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type); - if (rb_namespace_available() && NAMESPACE_OBJ_P(autoload_namespace)) - receiver = autoload_namespace; + if (rb_box_available() && BOX_OBJ_P(autoload_box_value)) + receiver = autoload_box_value; /* * Clear the global cc cache table because the require method can be different from the current - * namespace's one and it may cause inconsistent cc-cme states. + * box's one and it may cause inconsistent cc-cme states. * For example, the assertion below may fail in gccct_method_search(); * VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))) */ diff --git a/vm.c b/vm.c index 351951408d13e7..4bd6147fc504e9 100644 --- a/vm.c +++ b/vm.c @@ -98,19 +98,19 @@ rb_vm_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame } #if VM_CHECK_MODE > 0 -// ruby_namespace_crashed defined in internal/box.h -#define VM_NAMESPACE_CRASHED() {ruby_namespace_crashed = true;} -#define VM_NAMESPACE_ASSERT(expr, msg) \ - if (!(expr)) { ruby_namespace_crashed = true; rb_bug(msg); } +// ruby_box_crashed defined in internal/box.h +#define VM_BOX_CRASHED() {ruby_box_crashed = true;} +#define VM_BOX_ASSERT(expr, msg) \ + if (!(expr)) { ruby_box_crashed = true; rb_bug(msg); } #else -#define VM_NAMESPACE_CRASHED() {} -#define VM_NAMESPACE_ASSERT(expr, msg) ((void)0) +#define VM_BOX_CRASHED() {} +#define VM_BOX_ASSERT(expr, msg) ((void)0) #endif static const VALUE * VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp) { - // rb_vmdebug_namespace_env_dump_raw() simulates this function + // rb_vmdebug_box_env_dump_raw() simulates this function const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ const rb_control_frame_t *cfp = current_cfp; @@ -120,20 +120,20 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre /** * Returns CFUNC frame only in this case. * - * Usually CFUNC frame doesn't represent the current namespace and it should operate - * the caller namespace. See the example: + * Usually CFUNC frame doesn't represent the current box and it should operate + * the caller box. See the example: * - * # in the main namespace + * # in the main box * module Kernel * def foo = "foo" * module_function :foo * end * - * In the case above, `module_function` is defined in the root namespace. - * If `module_function` worked in the root namespace, `Kernel#foo` is invisible + * In the case above, `module_function` is defined in the root box. + * If `module_function` worked in the root box, `Kernel#foo` is invisible * from it and it causes NameError: undefined method `foo` for module `Kernel`. * - * But in cases of IFUNC (blocks written in C), IFUNC doesn't have its own namespace + * But in cases of IFUNC (blocks written in C), IFUNC doesn't have its own box * and its local env frame will be CFUNC frame. * For example, `Enumerator#chunk` calls IFUNC blocks, written as `chunk_i` function. * @@ -142,7 +142,7 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre * Before calling the Ruby block `{ it.even? }`, `#chunk` calls `chunk_i` as IFUNC * to iterate the array's members (it's just like `#each`). * We expect that `chunk_i` works as expected by the implementation of `#chunk` - * without any overwritten definitions from namespaces. + * without any overwritten definitions from boxes. * So the definitions on IFUNC frames should be equal to the caller CFUNC. */ VM_ASSERT(VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)); @@ -152,13 +152,13 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); - VM_NAMESPACE_ASSERT(cfp < eocfp, "CFUNC should have a valid caller frame"); + VM_BOX_ASSERT(cfp, "CFUNC should have a valid previous control frame"); + VM_BOX_ASSERT(cfp < eocfp, "CFUNC should have a valid caller frame"); if (!cfp || cfp >= eocfp) { return NULL; } - VM_NAMESPACE_ASSERT(cfp->ep, "CFUNC should have a valid caller frame with env"); + VM_BOX_ASSERT(cfp->ep, "CFUNC should have a valid caller frame with env"); ep = cfp->ep; if (!ep) { return NULL; @@ -196,10 +196,10 @@ static inline VALUE VM_CF_BLOCK_HANDLER(const rb_control_frame_t * const cfp) { const VALUE *ep; - if (VM_ENV_NAMESPACED_P(cfp->ep)) { + if (VM_ENV_BOXED_P(cfp->ep)) { VM_ASSERT(VM_ENV_LOCAL_P(cfp->ep)); /* Never set black_handler for VM_FRAME_MAGIC_TOP or VM_FRAME_MAGIC_CLASS - * and the specval is used for namespace (rb_namespace_t) in these case + * and the specval is used for boxes (rb_box_t) in these case */ return VM_BLOCK_HANDLER_NONE; } @@ -882,7 +882,7 @@ vm_stat(int argc, VALUE *argv, VALUE self) /* control stack frame */ static void -vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_namespace_t *ns) +vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_box_t *box) { if (ISEQ_BODY(iseq)->type != ISEQ_TYPE_TOP) { rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence"); @@ -891,7 +891,7 @@ vm_set_top_stack(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_nam /* for return */ vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, rb_ec_thread_ptr(ec)->top_self, - GC_GUARDED_PTR(ns), + GC_GUARDED_PTR(box), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp, ISEQ_BODY(iseq)->local_table_size, ISEQ_BODY(iseq)->stack_max); @@ -3032,11 +3032,11 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V /* misc */ VALUE -rb_iseq_eval(const rb_iseq_t *iseq, const rb_namespace_t *ns) +rb_iseq_eval(const rb_iseq_t *iseq, const rb_box_t *box) { rb_execution_context_t *ec = GET_EC(); VALUE val; - vm_set_top_stack(ec, iseq, ns); + vm_set_top_stack(ec, iseq, box); val = vm_exec(ec); return val; } @@ -3086,11 +3086,11 @@ rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, rb_execution_context_t *ec = GET_EC(); const rb_control_frame_t *reg_cfp = ec->cfp; const rb_iseq_t *iseq = rb_iseq_new(Qnil, filename, filename, Qnil, 0, ISEQ_TYPE_TOP); - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); VALUE val; vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, - recv, GC_GUARDED_PTR(ns), + recv, GC_GUARDED_PTR(box), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ 0, reg_cfp->sp, 0, 0); @@ -3100,11 +3100,11 @@ rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, return val; } -/* namespace */ +/* Ruby::Box */ VALUE -rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, - VALUE filename, const rb_namespace_t *ns) +rb_vm_call_cfunc_in_box(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg1, VALUE arg2, + VALUE filename, const rb_box_t *box) { rb_execution_context_t *ec = GET_EC(); const rb_control_frame_t *reg_cfp = ec->cfp; @@ -3112,7 +3112,7 @@ rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg VALUE val; vm_push_frame(ec, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, - recv, GC_GUARDED_PTR(ns), + recv, GC_GUARDED_PTR(box), (VALUE)vm_cref_new_toplevel(ec), /* cref or me */ 0, reg_cfp->sp, 0, 0); @@ -3123,50 +3123,50 @@ rb_vm_call_cfunc_in_namespace(VALUE recv, VALUE (*func)(VALUE, VALUE), VALUE arg } void -rb_vm_frame_flag_set_ns_require(const rb_execution_context_t *ec) +rb_vm_frame_flag_set_box_require(const rb_execution_context_t *ec) { - VM_ASSERT(rb_namespace_available()); - VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_NS_REQUIRE); + VM_ASSERT(rb_box_available()); + VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_BOX_REQUIRE); } -static const rb_namespace_t * -current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) +static const rb_box_t * +current_box_on_cfp(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) { rb_callable_method_entry_t *cme; - const rb_namespace_t *ns; + const rb_box_t *box; const VALUE *lep = VM_EP_RUBY_LEP(ec, cfp); - VM_NAMESPACE_ASSERT(lep, "lep should be valid"); - VM_NAMESPACE_ASSERT(rb_namespace_available(), "namespace should be available here"); + VM_BOX_ASSERT(lep, "lep should be valid"); + VM_BOX_ASSERT(rb_box_available(), "box should be available here"); if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CFUNC)) { cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); - VM_NAMESPACE_ASSERT(cme, "cme should be valid"); - VM_NAMESPACE_ASSERT(cme->def, "cme->def shold be valid"); - return cme->def->ns; + VM_BOX_ASSERT(cme, "cme should be valid"); + VM_BOX_ASSERT(cme->def, "cme->def shold be valid"); + return cme->def->box; } else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_TOP) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CLASS)) { - VM_NAMESPACE_ASSERT(VM_ENV_LOCAL_P(lep), "lep should be local on MAGIC_TOP or MAGIC_CLASS frames"); - return VM_ENV_NAMESPACE(lep); + VM_BOX_ASSERT(VM_ENV_LOCAL_P(lep), "lep should be local on MAGIC_TOP or MAGIC_CLASS frames"); + return VM_ENV_BOX(lep); } else if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_DUMMY)) { // No valid local ep found (just after process boot?) - // return the root namespace (the only valid namespace) until the main is initialized - ns = rb_main_namespace(); - if (ns) - return ns; - return rb_root_namespace(); + // return the root box (the only valid box) until the main is initialized + box = rb_main_box(); + if (box) + return box; + return rb_root_box(); } else { - VM_NAMESPACE_CRASHED(); - rb_bug("BUG: Local ep without cme/namespace, flags: %08lX", (unsigned long)lep[VM_ENV_DATA_INDEX_FLAGS]); + VM_BOX_CRASHED(); + rb_bug("BUG: Local ep without cme/box, flags: %08lX", (unsigned long)lep[VM_ENV_DATA_INDEX_FLAGS]); } UNREACHABLE_RETURN(0); } -const rb_namespace_t * -rb_vm_current_namespace(const rb_execution_context_t *ec) +const rb_box_t * +rb_vm_current_box(const rb_execution_context_t *ec) { - return current_namespace_on_cfp(ec, ec->cfp); + return current_box_on_cfp(ec, ec->cfp); } static const rb_control_frame_t * @@ -3175,7 +3175,7 @@ find_loader_control_frame(const rb_execution_context_t *ec, const rb_control_fra while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { if (!VM_ENV_FRAME_TYPE_P(cfp->ep, VM_FRAME_MAGIC_CFUNC)) break; - if (!NAMESPACE_ROOT_P(current_namespace_on_cfp(ec, cfp))) + if (!BOX_ROOT_P(current_box_on_cfp(ec, cfp))) break; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } @@ -3183,32 +3183,32 @@ find_loader_control_frame(const rb_execution_context_t *ec, const rb_control_fra return cfp; } -const rb_namespace_t * -rb_vm_loading_namespace(const rb_execution_context_t *ec) +const rb_box_t * +rb_vm_loading_box(const rb_execution_context_t *ec) { const rb_control_frame_t *cfp, *current_cfp, *end_cfp; - if (!rb_namespace_available() || !ec) - return rb_root_namespace(); + if (!rb_box_available() || !ec) + return rb_root_box(); cfp = ec->cfp; current_cfp = cfp; end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { - if (VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE)) { - if (RTEST(cfp->self) && NAMESPACE_OBJ_P(cfp->self)) { - // Namespace#require, #require_relative, #load - return rb_get_namespace_t(cfp->self); + if (VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_BOX_REQUIRE)) { + if (RTEST(cfp->self) && BOX_OBJ_P(cfp->self)) { + // Box#require, #require_relative, #load + return rb_get_box_t(cfp->self); } // Kernel#require, #require_relative, #load cfp = find_loader_control_frame(ec, cfp, end_cfp); - return current_namespace_on_cfp(ec, cfp); + return current_box_on_cfp(ec, cfp); } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } - // no require/load with explicit namespaces. - return current_namespace_on_cfp(ec, current_cfp); + // no require/load with explicit boxes. + return current_box_on_cfp(ec, current_cfp); } /* vm */ @@ -3223,10 +3223,10 @@ rb_vm_update_references(void *ptr) vm->mark_object_ary = rb_gc_location(vm->mark_object_ary); vm->orig_progname = rb_gc_location(vm->orig_progname); - if (vm->root_namespace) - rb_namespace_gc_update_references(vm->root_namespace); - if (vm->main_namespace) - rb_namespace_gc_update_references(vm->main_namespace); + if (vm->root_box) + rb_box_gc_update_references(vm->root_box); + if (vm->main_box) + rb_box_gc_update_references(vm->main_box); rb_gc_update_values(RUBY_NSIG, vm->trap_list.cmd); @@ -3299,11 +3299,11 @@ rb_vm_mark(void *ptr) rb_gc_mark_movable(vm->self); - if (vm->root_namespace) { - rb_namespace_entry_mark(vm->root_namespace); + if (vm->root_box) { + rb_box_entry_mark(vm->root_box); } - if (vm->main_namespace) { - rb_namespace_entry_mark(vm->main_namespace); + if (vm->main_box) { + rb_box_entry_mark(vm->main_box); } rb_gc_mark_movable(vm->mark_object_ary); @@ -3886,7 +3886,7 @@ rb_ec_clear_vm_stack(rb_execution_context_t *ec) static void th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) { - const rb_namespace_t *ns = rb_current_namespace(); + const rb_box_t *box = rb_current_box(); th->self = self; @@ -3911,8 +3911,8 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->status = THREAD_RUNNABLE; th->last_status = Qnil; th->top_wrapper = 0; - if (ns->top_self) { - th->top_self = ns->top_self; + if (box->top_self) { + th->top_self = box->top_self; } else { th->top_self = 0; @@ -4745,20 +4745,20 @@ main_to_s(VALUE obj) VALUE rb_vm_top_self(void) { - const rb_namespace_t *ns = rb_current_namespace(); - VM_ASSERT(ns); - VM_ASSERT(ns->top_self); - return ns->top_self; + const rb_box_t *box = rb_current_box(); + VM_ASSERT(box); + VM_ASSERT(box->top_self); + return box->top_self; } void Init_top_self(void) { rb_vm_t *vm = GET_VM(); - vm->root_namespace = (rb_namespace_t *)rb_root_namespace(); - vm->root_namespace->top_self = rb_obj_alloc(rb_cObject); - rb_define_singleton_method(vm->root_namespace->top_self, "to_s", main_to_s, 0); - rb_define_alias(rb_singleton_class(vm->root_namespace->top_self), "inspect", "to_s"); + vm->root_box = (rb_box_t *)rb_root_box(); + vm->root_box->top_self = rb_obj_alloc(rb_cObject); + rb_define_singleton_method(vm->root_box->top_self, "to_s", main_to_s, 0); + rb_define_alias(rb_singleton_class(vm->root_box->top_self), "inspect", "to_s"); } VALUE * diff --git a/vm_core.h b/vm_core.h index 599c7ada9fa9d1..79d12f9ee44ad6 100644 --- a/vm_core.h +++ b/vm_core.h @@ -754,9 +754,9 @@ typedef struct rb_vm_struct { struct global_object_list *global_object_list; const VALUE special_exceptions[ruby_special_error_count]; - /* namespace */ - rb_namespace_t *root_namespace; - rb_namespace_t *main_namespace; + /* Ruby Box */ + rb_box_t *root_box; + rb_box_t *main_box; /* load */ // For running the init function of statically linked @@ -1389,7 +1389,7 @@ enum vm_frame_env_flags { VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200, VM_FRAME_FLAG_CFRAME_KW = 0x0400, VM_FRAME_FLAG_PASSED = 0x0800, - VM_FRAME_FLAG_NS_REQUIRE = 0x1000, + VM_FRAME_FLAG_BOX_REQUIRE = 0x1000, /* env flag */ VM_ENV_FLAG_LOCAL = 0x0002, @@ -1528,7 +1528,7 @@ VM_FRAME_RUBYFRAME_P_UNCHECKED(const rb_control_frame_t *cfp) static inline int VM_FRAME_NS_REQUIRE_P(const rb_control_frame_t *cfp) { - return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_NS_REQUIRE) != 0; + return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_BOX_REQUIRE) != 0; } #define RUBYVM_CFUNC_FRAME_P(cfp) \ @@ -1563,7 +1563,7 @@ VM_ENV_PREV_EP(const VALUE *ep) } static inline bool -VM_ENV_NAMESPACED_P(const VALUE *ep) +VM_ENV_BOXED_P(const VALUE *ep) { return VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CLASS) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_TOP); } @@ -1571,7 +1571,7 @@ VM_ENV_NAMESPACED_P(const VALUE *ep) static inline VALUE VM_ENV_BLOCK_HANDLER(const VALUE *ep) { - if (VM_ENV_NAMESPACED_P(ep)) { + if (VM_ENV_BOXED_P(ep)) { VM_ASSERT(VM_ENV_LOCAL_P(ep)); return VM_BLOCK_HANDLER_NONE; } @@ -1580,18 +1580,18 @@ VM_ENV_BLOCK_HANDLER(const VALUE *ep) return ep[VM_ENV_DATA_INDEX_SPECVAL]; } -static inline const rb_namespace_t * -VM_ENV_NAMESPACE(const VALUE *ep) +static inline const rb_box_t * +VM_ENV_BOX(const VALUE *ep) { - VM_ASSERT(VM_ENV_NAMESPACED_P(ep)); + VM_ASSERT(VM_ENV_BOXED_P(ep)); VM_ASSERT(VM_ENV_LOCAL_P(ep)); - return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); + return (const rb_box_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); } -static inline const rb_namespace_t * -VM_ENV_NAMESPACE_UNCHECKED(const VALUE *ep) +static inline const rb_box_t * +VM_ENV_BOX_UNCHECKED(const VALUE *ep) { - return (const rb_namespace_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); + return (const rb_box_t *)GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]); } #if VM_CHECK_MODE > 0 @@ -1914,7 +1914,7 @@ NORETURN(void rb_bug_for_fatal_signal(ruby_sighandler_t default_sighandler, int /* functions about thread/vm execution */ RUBY_SYMBOL_EXPORT_BEGIN -VALUE rb_iseq_eval(const rb_iseq_t *iseq, const rb_namespace_t *ns); +VALUE rb_iseq_eval(const rb_iseq_t *iseq, const rb_box_t *box); VALUE rb_iseq_eval_main(const rb_iseq_t *iseq); VALUE rb_iseq_path(const rb_iseq_t *iseq); VALUE rb_iseq_realpath(const rb_iseq_t *iseq); diff --git a/vm_dump.c b/vm_dump.c index 2ed1f955b3445e..ece04f563e8f88 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -62,7 +62,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c VALUE tmp; const rb_iseq_t *iseq = NULL; const rb_callable_method_entry_t *me = rb_vm_frame_method_entry_unchecked(cfp); - const rb_namespace_t *ns = NULL; + const rb_box_t *box = NULL; if (ep < 0 || (size_t)ep > ec->vm_stack_size) { ep = (ptrdiff_t)cfp->ep; @@ -72,17 +72,17 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c switch (VM_FRAME_TYPE_UNCHECKED(cfp)) { case VM_FRAME_MAGIC_TOP: magic = "TOP"; - ns = VM_ENV_NAMESPACE_UNCHECKED(cfp->ep); + box = VM_ENV_BOX_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_METHOD: magic = "METHOD"; if (me) { - ns = me->def->ns; + box = me->def->box; } break; case VM_FRAME_MAGIC_CLASS: magic = "CLASS"; - ns = VM_ENV_NAMESPACE_UNCHECKED(cfp->ep); + box = VM_ENV_BOX_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_BLOCK: magic = "BLOCK"; @@ -163,11 +163,11 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c kprintf("s:%04"PRIdPTRDIFF" ", cfp->sp - ec->vm_stack); kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); kprintf("l:%s ", VM_ENV_LOCAL_P(cfp->ep) ? "y" : "n"); - if (ns) { - kprintf("n:%04ld ", ns->ns_id % 10000); + if (box) { + kprintf("b:%04ld ", box->box_id % 10000); } else { - kprintf("n:---- "); + kprintf("b:---- "); } kprintf("%-6s", magic); if (line) { @@ -274,7 +274,7 @@ vmdebug_frame_method_entry_unchecked(const VALUE *ep) } static bool -namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +box_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) { ptrdiff_t pc = -1; ptrdiff_t ep = env - ec->vm_stack; @@ -284,7 +284,7 @@ namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_ const char *magic, *iseq_name = "-"; VALUE tmp; const rb_iseq_t *iseq = NULL; - const rb_namespace_t *ns = NULL; + const rb_box_t *box = NULL; const rb_control_frame_t *cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, env); const rb_callable_method_entry_t *me = vmdebug_frame_method_entry_unchecked(env); @@ -298,17 +298,17 @@ namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_ switch (VM_ENV_FLAGS_UNCHECKED(env, VM_FRAME_MAGIC_MASK)) { case VM_FRAME_MAGIC_TOP: magic = "TOP"; - ns = VM_ENV_NAMESPACE_UNCHECKED(env); + box = VM_ENV_BOX_UNCHECKED(env); break; case VM_FRAME_MAGIC_METHOD: magic = "METHOD"; if (me) { - ns = me->def->ns; + box = me->def->box; } break; case VM_FRAME_MAGIC_CLASS: magic = "CLASS"; - ns = VM_ENV_NAMESPACE_UNCHECKED(env); + box = VM_ENV_BOX_UNCHECKED(env); break; case VM_FRAME_MAGIC_BLOCK: magic = "BLOCK"; @@ -316,7 +316,7 @@ namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_ case VM_FRAME_MAGIC_CFUNC: magic = "CFUNC"; if (me) { - ns = me->def->ns; + box = me->def->box; } break; case VM_FRAME_MAGIC_IFUNC: @@ -382,11 +382,11 @@ namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_ } kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); kprintf("l:%s ", VM_ENV_LOCAL_P(env) ? "y" : "n"); - if (ns) { - kprintf("n:%04ld ", ns->ns_id % 10000); + if (box) { + kprintf("b:%04ld ", box->box_id % 10000); } else { - kprintf("n:---- "); + kprintf("b:---- "); } kprintf("%-6s", magic); if (line) { @@ -402,36 +402,36 @@ namespace_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_ } static bool -namespace_env_dump_unchecked(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +box_env_dump_unchecked(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) { if (env == NULL) { - kprintf("c:---- e:000000 l:- n:---- (none)\n"); + kprintf("c:---- e:000000 l:- b:---- (none)\n"); return true; } else { - return namespace_env_dump(ec, env, checkpoint_cfp, errout); + return box_env_dump(ec, env, checkpoint_cfp, errout); } error: return false; } bool -rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp, FILE *errout) +rb_vmdebug_box_env_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp, FILE *errout) { // See VM_EP_RUBY_LEP for the original logic const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ const rb_control_frame_t *cfp = current_cfp, *checkpoint_cfp = current_cfp; - kprintf("-- Namespace detection information " + kprintf("-- Ruby Box detection information " "-----------------------------------------\n"); - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { while (!VM_ENV_LOCAL_P(ep)) { ep = VM_ENV_PREV_EP(ep); - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); } goto stop; } @@ -446,7 +446,7 @@ rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_con goto stop; } ep = cfp->ep; - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); if (!ep) { goto stop; } @@ -454,7 +454,7 @@ rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_con while (!VM_ENV_LOCAL_P(ep)) { ep = VM_ENV_PREV_EP(ep); - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); } stop: @@ -1408,21 +1408,21 @@ rb_vm_bugreport(const void *ctx, FILE *errout) enum {other_runtime_info = 0}; #endif const rb_vm_t *const vm = GET_VM(); - const rb_namespace_t *current_ns = rb_current_namespace_in_crash_report(); + const rb_box_t *current_box = rb_current_box_in_crash_report(); const rb_execution_context_t *ec = rb_current_execution_context(false); VALUE loaded_features; - if (current_ns) { - loaded_features = current_ns->loaded_features; + if (current_box) { + loaded_features = current_box->loaded_features; } else { - loaded_features = rb_root_namespace()->loaded_features; + loaded_features = rb_root_box()->loaded_features; } if (vm && ec) { rb_vmdebug_stack_dump_raw(ec, ec->cfp, errout); if (ns_env) { - rb_vmdebug_namespace_env_dump_raw(ec, ec->cfp, errout); + rb_vmdebug_box_env_dump_raw(ec, ec->cfp, errout); } rb_backtrace_print_as_bugreport(errout); kputs("\n"); @@ -1468,19 +1468,19 @@ rb_vm_bugreport(const void *ctx, FILE *errout) LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); kprintf("\n"); } - if (rb_namespace_available()) { - kprintf("* Namespace: enabled\n"); - if (current_ns) { - kprintf("* Current namespace id: %ld, type: %s\n", - current_ns->ns_id, - NAMESPACE_USER_P(current_ns) ? (NAMESPACE_MAIN_P(current_ns) ? "main" : "user") : "root"); + if (rb_box_available()) { + kprintf("* Ruby Box: enabled\n"); + if (current_box) { + kprintf("* Current box id: %ld, type: %s\n", + current_box->box_id, + BOX_USER_P(current_box) ? (BOX_MAIN_P(current_box) ? "main" : "user") : "root"); } else { - kprintf("* Current namespace: NULL (crashed)\n"); + kprintf("* Current box: NULL (crashed)\n"); } } else { - kprintf("* Namespace: disabled\n"); + kprintf("* Ruby Box: disabled\n"); } if (loaded_features) { kprintf("* Loaded features:\n\n"); diff --git a/vm_eval.c b/vm_eval.c index cd3f1bbafa4c57..281d63a1b8268a 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -400,9 +400,9 @@ static inline const rb_callable_method_entry_t *rb_search_method_entry(VALUE rec static inline enum method_missing_reason rb_method_call_status(rb_execution_context_t *ec, const rb_callable_method_entry_t *me, call_type scope, VALUE self); static VALUE -gccct_hash(VALUE klass, VALUE namespace, ID mid) +gccct_hash(VALUE klass, VALUE box_value, ID mid) { - return ((klass ^ namespace) >> 3) ^ (VALUE)mid; + return ((klass ^ box_value) >> 3) ^ (VALUE)mid; } NOINLINE(static const struct rb_callcache *gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, unsigned int index, const struct rb_callinfo * ci)); @@ -447,8 +447,8 @@ scope_to_ci(call_type scope, ID mid, int argc, struct rb_callinfo *ci) static inline const struct rb_callcache * gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { - VALUE klass, ns_value; - const rb_namespace_t *ns = rb_current_namespace(); + VALUE klass, box_value; + const rb_box_t *box = rb_current_box(); if (!SPECIAL_CONST_P(recv)) { klass = RBASIC_CLASS(recv); @@ -458,14 +458,14 @@ gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct klass = CLASS_OF(recv); } - if (NAMESPACE_USER_P(ns)) { - ns_value = ns->ns_object; + if (BOX_USER_P(box)) { + box_value = box->box_object; } else { - ns_value = 0; + box_value = 0; } // search global method cache - unsigned int index = (unsigned int)(gccct_hash(klass, ns_value, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); + unsigned int index = (unsigned int)(gccct_hash(klass, box_value, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; diff --git a/vm_method.c b/vm_method.c index f5ad38e07c10fe..506a2919b77cd0 100644 --- a/vm_method.c +++ b/vm_method.c @@ -384,7 +384,7 @@ struct invalidate_callable_method_entry_foreach_arg { }; static void -invalidate_callable_method_entry_in_every_m_table_i(rb_classext_t *ext, bool is_prime, VALUE namespace, void *data) +invalidate_callable_method_entry_in_every_m_table_i(rb_classext_t *ext, bool is_prime, VALUE box_value, void *data) { st_data_t me; struct invalidate_callable_method_entry_foreach_arg *arg = (struct invalidate_callable_method_entry_foreach_arg *)data; @@ -1089,7 +1089,7 @@ rb_method_definition_create(rb_method_type_t type, ID mid) def->type = type; def->original_id = mid; def->method_serial = (uintptr_t)RUBY_ATOMIC_FETCH_ADD(method_serial, 1); - def->ns = rb_current_namespace(); + def->box = rb_current_box(); return def; } From 95a110a9af0b5bfe393934fa177abea58c0fee46 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 15:33:58 +0900 Subject: [PATCH 0873/2435] Define ::Ruby module earlier to define classes under it --- inits.c | 1 + version.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/inits.c b/inits.c index 4569362b95b8b7..4988fa09d0b6f6 100644 --- a/inits.c +++ b/inits.c @@ -52,6 +52,7 @@ rb_call_inits(void) CALL(Time); CALL(Random); CALL(load); + CALL(Ruby_module); CALL(Box); CALL(Proc); CALL(Binding); diff --git a/version.c b/version.c index d634755efd16c0..0a3bc7bf9b91ae 100644 --- a/version.c +++ b/version.c @@ -12,6 +12,7 @@ #include "internal/cmdlineopt.h" #include "internal/parse.h" #include "internal/gc.h" +#include "ruby/internal/globals.h" #include "ruby/ruby.h" #include "version.h" #include "vm_core.h" @@ -112,6 +113,12 @@ define_ruby_const(VALUE mod, const char *name, VALUE value, bool toplevel) #define rb_define_const(mod, name, value) \ define_ruby_const(mod, (mod == mRuby ? "RUBY_" name : name), value, (mod == mRuby)) +void +Init_Ruby_module(void) +{ + rb_define_module("Ruby"); +} + /*! Defines platform-depended Ruby-level constants */ void Init_version(void) @@ -123,7 +130,7 @@ Init_version(void) * The constants defined here are aliased in the toplevel with * +RUBY_+ prefix. */ - VALUE mRuby = rb_define_module("Ruby"); + VALUE mRuby = rb_path2class("Ruby"); enum {ruby_patchlevel = RUBY_PATCHLEVEL}; VALUE version = MKSTR(version); From c4691ef061d8783de970a93c6c6e3a048677181f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 15:34:16 +0900 Subject: [PATCH 0874/2435] Rename Namespace to Ruby::Box --- box.c | 60 +++++++++++++++++---------------- include/ruby/internal/globals.h | 3 +- internal/box.h | 2 +- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/box.c b/box.c index f1290f60aa228e..fd9402c8e56db2 100644 --- a/box.c +++ b/box.c @@ -20,12 +20,12 @@ #include -VALUE rb_cNamespace = 0; -VALUE rb_cNamespaceEntry = 0; -VALUE rb_mNamespaceLoader = 0; +VALUE rb_cBox = 0; +VALUE rb_cBoxEntry = 0; +VALUE rb_mBoxLoader = 0; static rb_box_t root_box_data = { - /* Initialize values lazily in Init_namespace() */ + /* Initialize values lazily in Init_Box() */ (VALUE)NULL, 0, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (struct st_table *)NULL, (struct st_table *)NULL, (VALUE)NULL, (VALUE)NULL, @@ -332,7 +332,7 @@ box_initialize(VALUE box_value) rb_raise(rb_eRuntimeError, "Namespace is disabled. Set RUBY_NAMESPACE=1 environment variable to use Namespace."); } - entry = rb_class_new_instance_pass_kw(0, NULL, rb_cNamespaceEntry); + entry = rb_class_new_instance_pass_kw(0, NULL, rb_cBoxEntry); box = get_box_struct_internal(entry); box->box_object = box_value; @@ -750,14 +750,14 @@ initialize_root_box(void) if (rb_box_available()) { CONST_ID(id_box_entry, "__box_entry__"); - root_box = rb_obj_alloc(rb_cNamespace); + root_box = rb_obj_alloc(rb_cBox); RCLASS_SET_PRIME_CLASSEXT_WRITABLE(root_box, true); RCLASS_SET_CONST_TBL(root_box, RCLASSEXT_CONST_TBL(RCLASS_EXT_PRIME(rb_cObject)), true); root->box_id = box_generate_id(); root->box_object = root_box; - entry = TypedData_Wrap_Struct(rb_cNamespaceEntry, &rb_root_box_data_type, root); + entry = TypedData_Wrap_Struct(rb_cBoxEntry, &rb_root_box_data_type, root); rb_ivar_set(root_box, id_box_entry, entry); } else { @@ -800,14 +800,14 @@ rb_initialize_main_box(void) box_experimental_warned = 1; } - main_box_value = rb_class_new_instance(0, NULL, rb_cNamespace); + main_box_value = rb_class_new_instance(0, NULL, rb_cBox); VM_ASSERT(BOX_OBJ_P(main_box_value)); box = rb_get_box_t(main_box_value); box->box_object = main_box_value; box->is_user = true; box->is_optional = false; - rb_const_set(rb_cNamespace, rb_intern("MAIN"), main_box_value); + rb_const_set(rb_cBox, rb_intern("MAIN"), main_box_value); vm->main_box = main_box = box; @@ -853,8 +853,8 @@ rb_box_loading_func(int argc, VALUE *argv, VALUE _self) static void box_define_loader_method(const char *name) { - rb_define_private_method(rb_mNamespaceLoader, name, rb_box_loading_func, -1); - rb_define_singleton_method(rb_mNamespaceLoader, name, rb_box_loading_func, -1); + rb_define_private_method(rb_mBoxLoader, name, rb_box_loading_func, -1); + rb_define_singleton_method(rb_mBoxLoader, name, rb_box_loading_func, -1); } void @@ -1057,42 +1057,44 @@ Init_Box(void) tmp_dir = system_tmpdir(); tmp_dir_has_dirsep = (strcmp(tmp_dir + (strlen(tmp_dir) - strlen(DIRSEP)), DIRSEP) == 0); - rb_cNamespace = rb_define_class("Namespace", rb_cModule); - rb_define_method(rb_cNamespace, "initialize", box_initialize, 0); + VALUE mRuby = rb_path2class("Ruby"); + + rb_cBox = rb_define_class_under(mRuby, "Box", rb_cModule); + rb_define_method(rb_cBox, "initialize", box_initialize, 0); /* :nodoc: */ - rb_cNamespaceEntry = rb_define_class_under(rb_cNamespace, "Entry", rb_cObject); - rb_define_alloc_func(rb_cNamespaceEntry, rb_box_entry_alloc); + rb_cBoxEntry = rb_define_class_under(rb_cBox, "Entry", rb_cObject); + rb_define_alloc_func(rb_cBoxEntry, rb_box_entry_alloc); initialize_root_box(); /* :nodoc: */ - rb_mNamespaceLoader = rb_define_module_under(rb_cNamespace, "Loader"); + rb_mBoxLoader = rb_define_module_under(rb_cBox, "Loader"); box_define_loader_method("require"); box_define_loader_method("require_relative"); box_define_loader_method("load"); if (rb_box_available()) { - rb_include_module(rb_cObject, rb_mNamespaceLoader); + rb_include_module(rb_cObject, rb_mBoxLoader); - rb_define_singleton_method(rb_cNamespace, "root", rb_box_s_root, 0); - rb_define_singleton_method(rb_cNamespace, "main", rb_box_s_main, 0); - rb_define_method(rb_cNamespace, "root?", rb_box_root_p, 0); - rb_define_method(rb_cNamespace, "main?", rb_box_main_p, 0); + rb_define_singleton_method(rb_cBox, "root", rb_box_s_root, 0); + rb_define_singleton_method(rb_cBox, "main", rb_box_s_main, 0); + rb_define_method(rb_cBox, "root?", rb_box_root_p, 0); + rb_define_method(rb_cBox, "main?", rb_box_main_p, 0); #if RUBY_DEBUG rb_define_global_function("dump_classext", rb_f_dump_classext, 1); #endif } - rb_define_singleton_method(rb_cNamespace, "enabled?", rb_box_s_getenabled, 0); - rb_define_singleton_method(rb_cNamespace, "current", rb_box_s_current, 0); + rb_define_singleton_method(rb_cBox, "enabled?", rb_box_s_getenabled, 0); + rb_define_singleton_method(rb_cBox, "current", rb_box_s_current, 0); - rb_define_method(rb_cNamespace, "load_path", rb_box_load_path, 0); - rb_define_method(rb_cNamespace, "load", rb_box_load, -1); - rb_define_method(rb_cNamespace, "require", rb_box_require, 1); - rb_define_method(rb_cNamespace, "require_relative", rb_box_require_relative, 1); - rb_define_method(rb_cNamespace, "eval", rb_box_eval, 1); + rb_define_method(rb_cBox, "load_path", rb_box_load_path, 0); + rb_define_method(rb_cBox, "load", rb_box_load, -1); + rb_define_method(rb_cBox, "require", rb_box_require, 1); + rb_define_method(rb_cBox, "require_relative", rb_box_require_relative, 1); + rb_define_method(rb_cBox, "eval", rb_box_eval, 1); - rb_define_method(rb_cNamespace, "inspect", rb_box_inspect, 0); + rb_define_method(rb_cBox, "inspect", rb_box_inspect, 0); } diff --git a/include/ruby/internal/globals.h b/include/ruby/internal/globals.h index 528371abbb5923..9beb215c0ce03b 100644 --- a/include/ruby/internal/globals.h +++ b/include/ruby/internal/globals.h @@ -68,6 +68,7 @@ RUBY_EXTERN VALUE rb_cBasicObject; /**< `BasicObject` class. */ RUBY_EXTERN VALUE rb_cObject; /**< `Object` class. */ RUBY_EXTERN VALUE rb_cArray; /**< `Array` class. */ RUBY_EXTERN VALUE rb_cBinding; /**< `Binding` class. */ +RUBY_EXTERN VALUE rb_cBox; /**< `Ruby::Box` class. */ RUBY_EXTERN VALUE rb_cClass; /**< `Class` class. */ RUBY_EXTERN VALUE rb_cDir; /**< `Dir` class. */ RUBY_EXTERN VALUE rb_cEncoding; /**< `Encoding` class. */ @@ -84,8 +85,6 @@ RUBY_EXTERN VALUE rb_cMethod; /**< `Method` class. */ RUBY_EXTERN VALUE rb_cModule; /**< `Module` class. */ RUBY_EXTERN VALUE rb_cRefinement; /**< `Refinement` class. */ RUBY_EXTERN VALUE rb_cNameErrorMesg; /**< `NameError::Message` class. */ -RUBY_EXTERN VALUE rb_cNamespace; /**< `Namespace` class. */ -RUBY_EXTERN VALUE rb_mNamespaceRefiner; /**< `Namespace::Refiner` module. */ RUBY_EXTERN VALUE rb_cNilClass; /**< `NilClass` class. */ RUBY_EXTERN VALUE rb_cNumeric; /**< `Numeric` class. */ RUBY_EXTERN VALUE rb_cProc; /**< `Proc` class. */ diff --git a/internal/box.h b/internal/box.h index 36844ebf19b24a..41cad634823f71 100644 --- a/internal/box.h +++ b/internal/box.h @@ -40,7 +40,7 @@ struct rb_box_struct { }; typedef struct rb_box_struct rb_box_t; -#define BOX_OBJ_P(obj) (rb_obj_class(obj) == rb_cNamespace) +#define BOX_OBJ_P(obj) (rb_obj_class(obj) == rb_cBox) #define BOX_ROOT_P(box) (box && !box->is_user) #define BOX_USER_P(box) (box && box->is_user) From ccad753c64f2e8410e69a272c55b793a6d6c5e79 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 16:03:56 +0900 Subject: [PATCH 0875/2435] Re-rename files wrongly renamed --- doc/{box.h => box.md} | 0 test/ruby/{test_box.c => test_box.rb} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename doc/{box.h => box.md} (100%) rename test/ruby/{test_box.c => test_box.rb} (100%) diff --git a/doc/box.h b/doc/box.md similarity index 100% rename from doc/box.h rename to doc/box.md diff --git a/test/ruby/test_box.c b/test/ruby/test_box.rb similarity index 100% rename from test/ruby/test_box.c rename to test/ruby/test_box.rb From 19d4663d2f6b7da4a975dbfe83fb0d0a4ad8ae8b Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 16:04:40 +0900 Subject: [PATCH 0876/2435] Use RUBY_BOX environment variable instead of RUBY_NAMESPACE --- box.c | 6 +++--- class.c | 2 +- vm_dump.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/box.c b/box.c index fd9402c8e56db2..bdb80a788cb59a 100644 --- a/box.c +++ b/box.c @@ -78,7 +78,7 @@ const rb_box_t * rb_current_box(void) { /* - * If RUBY_NAMESPACE is not set, the root box is the only available one. + * If RUBY_BOX is not set, the root box is the only available one. * * Until the main_box is not initialized, the root box is * the only valid box. @@ -329,7 +329,7 @@ box_initialize(VALUE box_value) CONST_ID(id_box_entry, "__box_entry__"); if (!rb_box_available()) { - rb_raise(rb_eRuntimeError, "Namespace is disabled. Set RUBY_NAMESPACE=1 environment variable to use Namespace."); + rb_raise(rb_eRuntimeError, "Ruby Box is disabled. Set RUBY_BOX=1 environment variable to use Ruby::Box."); } entry = rb_class_new_instance_pass_kw(0, NULL, rb_cBoxEntry); @@ -866,7 +866,7 @@ Init_root_box(void) void Init_enable_box(void) { - const char *env = getenv("RUBY_NAMESPACE"); + const char *env = getenv("RUBY_BOX"); if (env && strlen(env) == 1 && env[0] == '1') { ruby_box_enabled = true; } diff --git a/class.c b/class.c index 16ebd3b88965ea..23e1188bb2f369 100644 --- a/class.c +++ b/class.c @@ -990,7 +990,7 @@ rb_class_new(VALUE super) RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super)); } - RUBY_ASSERT(getenv("RUBY_NAMESPACE") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); + RUBY_ASSERT(getenv("RUBY_BOX") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); return klass; } diff --git a/vm_dump.c b/vm_dump.c index ece04f563e8f88..c51c6cca81ea5a 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1377,7 +1377,7 @@ rb_dump_machine_register(FILE *errout, const ucontext_t *ctx) bool rb_vm_bugreport(const void *ctx, FILE *errout) { - const char *ns_env = getenv("RUBY_BUGREPORT_NAMESPACE_ENV"); + const char *box_env = getenv("RUBY_BUGREPORT_BOX_ENV"); const char *cmd = getenv("RUBY_ON_BUG"); if (cmd) { char buf[0x100]; @@ -1421,7 +1421,7 @@ rb_vm_bugreport(const void *ctx, FILE *errout) if (vm && ec) { rb_vmdebug_stack_dump_raw(ec, ec->cfp, errout); - if (ns_env) { + if (box_env) { rb_vmdebug_box_env_dump_raw(ec, ec->cfp, errout); } rb_backtrace_print_as_bugreport(errout); From 49c0502f5deccbf5ba788a9387770bfff2ef5794 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 17:28:57 +0900 Subject: [PATCH 0877/2435] Apply renaming to Ruby::Box in test codes --- test/ruby/test_autoload.rb | 6 +- test/ruby/test_box.rb | 195 +++++++++++++++++++------------------ 2 files changed, 104 insertions(+), 97 deletions(-) diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 7b6a0b5712556d..50949274a362f8 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -224,7 +224,7 @@ def ruby_impl_require Kernel.module_eval do alias old_require require end - Namespace.module_eval do + Ruby::Box.module_eval do alias old_require require end called_with = [] @@ -232,7 +232,7 @@ def ruby_impl_require called_with << path old_require path end - Namespace.send :define_method, :require do |path| + Ruby::Box.send :define_method, :require do |path| called_with << path old_require path end @@ -243,7 +243,7 @@ def ruby_impl_require alias require old_require undef old_require end - Namespace.module_eval do + Ruby::Box.module_eval do undef require alias require old_require undef old_require diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 4241ce62ff4a92..9b87f9b5bc458a 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -2,41 +2,48 @@ require 'test/unit' -class TestNamespace < Test::Unit::TestCase +class TestBox < Test::Unit::TestCase EXPERIMENTAL_WARNINGS = [ - "warning: Namespace is experimental, and the behavior may change in the future!", - "See doc/namespace.md for known issues, etc." + "warning: Ruby::Box is experimental, and the behavior may change in the future!", + "See doc/box.md for known issues, etc." ].join("\n") - ENV_ENABLE_NAMESPACE = {'RUBY_NAMESPACE' => '1'} + ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} def setup - @n = Namespace.new if Namespace.enabled? + @n = nil + @dir = __dir__ end def teardown @n = nil end - def test_namespace_availability - env_has_RUBY_NAMESPACE = (ENV['RUBY_NAMESPACE'].to_i == 1) - assert_equal env_has_RUBY_NAMESPACE, Namespace.enabled? + def test_box_availability_in_default + assert_separately([], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert_nil ENV['RUBY_BOX'] + assert !Ruby::Box.enabled? + end; end - def test_current_namespace - pend unless Namespace.enabled? - - main = Namespace.current - assert main.inspect.include?("main") - - @n.require_relative('namespace/current') + def test_box_availability_when_enabled + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert '1', ENV['RUBY_BOX'] + assert Ruby::Box.enabled? + end; + end - assert_equal @n, @n::CurrentNamespace.in_require - assert_equal @n, @n::CurrentNamespace.in_method_call - assert_equal main, Namespace.current + def test_current_box_in_main + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert_equal Ruby::Box.main, Ruby::Box.current + assert Ruby::Box.main.main? + end; end def test_require_rb_separately - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS_A } assert_raise(NameError) { NS_B } @@ -55,7 +62,7 @@ def test_require_rb_separately end def test_require_relative_rb_separately - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS_A } assert_raise(NameError) { NS_B } @@ -74,7 +81,7 @@ def test_require_relative_rb_separately end def test_load_separately - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS_A } assert_raise(NameError) { NS_B } @@ -92,8 +99,8 @@ def test_load_separately assert_raise(NameError) { NS_B } end - def test_namespace_in_namespace - pend unless Namespace.enabled? + def test_box_in_box + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS1 } assert_raise(NameError) { NS_A } @@ -115,7 +122,7 @@ def test_namespace_in_namespace end def test_require_rb_2versions - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS_A } @@ -136,14 +143,14 @@ def test_require_rb_2versions end def test_raising_errors_in_require - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_raise(RuntimeError, "Yay!") { @n.require(File.join(__dir__, 'namespace', 'raise')) } assert Namespace.current.inspect.include?("main") end - def test_autoload_in_namespace - pend unless Namespace.enabled? + def test_autoload_in_box + pend unless Ruby::Box.enabled? assert_raise(NameError) { NS_A } @@ -160,8 +167,8 @@ def test_autoload_in_namespace assert_raise(NameError) { NS_B } end - def test_continuous_top_level_method_in_a_namespace - pend unless Namespace.enabled? + def test_continuous_top_level_method_in_a_box + pend unless Ruby::Box.enabled? @n.require_relative('namespace/define_toplevel') @n.require_relative('namespace/call_toplevel') @@ -169,18 +176,18 @@ def test_continuous_top_level_method_in_a_namespace assert_raise(NameError) { foo } end - def test_top_level_methods_in_namespace - pend # TODO: fix loading/current namespace detection - pend unless Namespace.enabled? - @n.require_relative('namespace/top_level') + def test_top_level_methods_in_box + pend # TODO: fix loading/current box detection + pend unless Ruby::Box.enabled? + @n.require_relative('box/top_level') assert_equal "yay!", @n::Foo.foo assert_raise(NameError) { yaaay } assert_equal "foo", @n::Bar.bar assert_raise_with_message(RuntimeError, "boooo") { @n::Baz.baz } end - def test_proc_defined_in_namespace_refers_module_in_namespace - pend unless Namespace.enabled? + def test_proc_defined_in_box_refers_module_in_box + pend unless Ruby::Box.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) @@ -202,7 +209,7 @@ def test_proc_defined_in_namespace_refers_module_in_namespace end def test_proc_defined_globally_refers_global_module - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) @@ -227,7 +234,7 @@ def Target.foo end def test_instance_variable - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? @n.require_relative('namespace/instance_variables') @@ -246,8 +253,8 @@ def test_instance_variable assert_equal [], String.instance_variables end - def test_methods_added_in_namespace_are_invisible_globally - pend unless Namespace.enabled? + def test_methods_added_in_box_are_invisible_globally + pend unless Ruby::Box.enabled? @n.require_relative('namespace/string_ext') @@ -256,8 +263,8 @@ def test_methods_added_in_namespace_are_invisible_globally assert_raise(NoMethodError){ String.new.yay } end - def test_continuous_method_definitions_in_a_namespace - pend unless Namespace.enabled? + def test_continuous_method_definitions_in_a_box + pend unless Ruby::Box.enabled? @n.require_relative('namespace/string_ext') assert_equal "yay", @n::Bar.yay @@ -268,8 +275,8 @@ def test_continuous_method_definitions_in_a_namespace @n.require_relative('namespace/string_ext_calling') end - def test_methods_added_in_namespace_later_than_caller_code - pend unless Namespace.enabled? + def test_methods_added_in_box_later_than_caller_code + pend unless Ruby::Box.enabled? @n.require_relative('namespace/string_ext_caller') @n.require_relative('namespace/string_ext') @@ -278,8 +285,8 @@ def test_methods_added_in_namespace_later_than_caller_code assert_equal "yay", @n::Foo.yay end - def test_method_added_in_namespace_are_available_on_eval - pend unless Namespace.enabled? + def test_method_added_in_box_are_available_on_eval + pend unless Ruby::Box.enabled? @n.require_relative('namespace/string_ext') @n.require_relative('namespace/string_ext_eval_caller') @@ -287,8 +294,8 @@ def test_method_added_in_namespace_are_available_on_eval assert_equal "yay", @n::Baz.yay end - def test_method_added_in_namespace_are_available_on_eval_with_binding - pend unless Namespace.enabled? + def test_method_added_in_box_are_available_on_eval_with_binding + pend unless Ruby::Box.enabled? @n.require_relative('namespace/string_ext') @n.require_relative('namespace/string_ext_eval_caller') @@ -297,7 +304,7 @@ def test_method_added_in_namespace_are_available_on_eval_with_binding end def test_methods_and_constants_added_by_include - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? @n.require_relative('namespace/open_class_with_include') @@ -317,13 +324,13 @@ module B end end -class TestNamespace < Test::Unit::TestCase +class TestBox < Test::Unit::TestCase def make_proc_from_block(&b) b end def test_proc_from_main_works_with_global_definitions - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? @n.require_relative('namespace/procs') @@ -358,8 +365,8 @@ def test_proc_from_main_works_with_global_definitions end end - def test_proc_from_namespace_works_with_definitions_in_namespace - pend unless Namespace.enabled? + def test_proc_from_box_works_with_definitions_in_box + pend unless Ruby::Box.enabled? @n.require_relative('namespace/procs') @@ -379,7 +386,7 @@ def test_proc_from_namespace_works_with_definitions_in_namespace end def test_class_module_singleton_methods - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? @n.require_relative('namespace/singleton_methods') @@ -397,8 +404,8 @@ def test_class_module_singleton_methods assert_raise(NoMethodError) { Hash.http_200 } end - def test_add_constants_in_namespace - pend unless Namespace.enabled? + def test_add_constants_in_box + pend unless Ruby::Box.enabled? String.const_set(:STR_CONST0, 999) assert_equal 999, String::STR_CONST0 @@ -479,7 +486,7 @@ def test_global_variables default_l = $-0 default_f = $, - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert_equal "\n", $-0 # equal to $/, line splitter assert_equal nil, $, # field splitter @@ -517,7 +524,7 @@ def test_global_variables end def test_load_path_and_loaded_features - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? assert $LOAD_PATH.respond_to?(:resolve_feature_path) @@ -539,7 +546,7 @@ def test_load_path_and_loaded_features end def test_eval_basic - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Test basic evaluation result = @n.eval("1 + 1") @@ -551,7 +558,7 @@ def test_eval_basic end def test_eval_with_constants - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Define a constant in the namespace via eval @n.eval("TEST_CONST = 42") @@ -562,7 +569,7 @@ def test_eval_with_constants end def test_eval_with_classes - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Define a class in the namespace via eval @n.eval("class TestClass; def hello; 'from namespace'; end; end") @@ -576,7 +583,7 @@ def test_eval_with_classes end def test_eval_isolation - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Create another namespace n2 = Namespace.new @@ -594,7 +601,7 @@ def test_eval_isolation end def test_eval_with_variables - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Test local variable access (should work within the eval context) result = @n.eval("x = 10; y = 20; x + y") @@ -602,7 +609,7 @@ def test_eval_with_variables end def test_eval_error_handling - pend unless Namespace.enabled? + pend unless Ruby::Box.enabled? # Test syntax error assert_raise(SyntaxError) { @n.eval("1 +") } @@ -620,10 +627,10 @@ def test_eval_error_handling end end - # Tests which run always (w/o RUBY_NAMESPACE=1 globally) + # Tests which run always (w/o RUBY_BOX=1 globally) def test_prelude_gems_and_loaded_features - assert_in_out_err([ENV_ENABLE_NAMESPACE, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| begin; puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join @@ -646,7 +653,7 @@ def test_prelude_gems_and_loaded_features end def test_prelude_gems_and_loaded_features_with_disable_gems - assert_in_out_err([ENV_ENABLE_NAMESPACE, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + assert_in_out_err([ENV_ENABLE_BOX, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| begin; puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join @@ -668,23 +675,23 @@ def test_prelude_gems_and_loaded_features_with_disable_gems end def test_root_and_main_methods - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - pend unless Namespace.respond_to?(:root) and Namespace.respond_to?(:main) # for RUBY_DEBUG > 0 + pend unless Ruby::Box.respond_to?(:root) and Ruby::Box.respond_to?(:main) # for RUBY_DEBUG > 0 - assert Namespace.root.respond_to?(:root?) - assert Namespace.main.respond_to?(:main?) + assert Ruby::Box.root.respond_to?(:root?) + assert Ruby::Box.main.respond_to?(:main?) - assert Namespace.root.root? - assert Namespace.main.main? - assert_equal Namespace.main, Namespace.current + assert Ruby::Box.root.root? + assert Ruby::Box.main.main? + assert_equal Ruby::Box.main, Ruby::Box.current $a = 1 $LOADED_FEATURES.push("/tmp/foobar") - assert_equal 2, Namespace.root.eval('$a = 2; $a') - assert !Namespace.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') - assert "FooClass", Namespace.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + assert_equal 2, Ruby::Box.root.eval('$a = 2; $a') + assert !Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') + assert "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') assert_equal 1, $a assert !$LOADED_FEATURES.include?("/tmp/barbaz") @@ -696,30 +703,30 @@ def test_root_and_main_methods File.unlink(*Dir.glob(pat, base: tmp).map {|so| "#{tmp}/#{so}"}) end - def test_basic_namespace_detections - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + def test_basic_box_detections + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - ns = Namespace.new + ns = Ruby::Box.new $gvar1 = 'bar' code = <<~EOC - NS1 = Namespace.current + NS1 = Ruby::Box.current $gvar1 = 'foo' def toplevel = $gvar1 class Foo - NS2 = Namespace.current + NS2 = Ruby::Box.current NS2_proc = ->(){ NS2 } - NS3_proc = ->(){ Namespace.current } + NS3_proc = ->(){ Ruby::Box.current } - def ns4 = Namespace.current + def ns4 = Ruby::Box.current def self.ns5 = NS2 - def self.ns6 = Namespace.current - def self.ns6_proc = ->(){ Namespace.current } + def self.ns6 = Ruby::Box.current + def self.ns6_proc = ->(){ Ruby::Box.current } def self.ns7 res = [] [1,2].chunk{ it.even? }.each do |bool, members| - res << Namespace.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") + res << Ruby::Box.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") end res end @@ -733,15 +740,15 @@ def self.call_toplevel = toplevel FOO_NAME = Foo.name module Kernel - def foo_namespace = Namespace.current - module_function :foo_namespace + def foo_box = Ruby::Box.current + module_function :foo_box end NS_X = Foo.new.ns4 - NS_Y = foo_namespace + NS_Y = foo_box EOC ns.eval(code) - outer = Namespace.current + outer = Ruby::Box.current assert_equal ns, ns::NS1 # on TOP frame assert_equal ns, ns::Foo::NS2 # on CLASS frame assert_equal ns, ns::Foo::NS2_proc.call # proc -> a const on CLASS @@ -754,8 +761,8 @@ def foo_namespace = Namespace.current # a block after CFUNC/IFUNC in a method -> the current assert_equal ["#{ns.object_id}:false:1", "#{ns.object_id}:true:2"], ns::Foo.ns7 - assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields - assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block + assert_equal outer, ns::Foo.yield_block{ Ruby::Box.current } # method yields + assert_equal outer, ns::Foo.call_block{ Ruby::Box.current } # method calls a block assert_equal 'foo', ns::Foo.gvar1 # method refers gvar assert_equal 'bar', $gvar1 # gvar value out of the ns @@ -769,9 +776,9 @@ def foo_namespace = Namespace.current end; end - def test_loading_extension_libs_in_main_namespace + def test_loading_extension_libs_in_main_box pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; require "prism" require "optparse" From 8ebeb751eeb706a18c52033da27a0c45cbfc6630 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 18:01:40 +0900 Subject: [PATCH 0878/2435] Update Ruby Box document, delete problems/discussions already resolved --- doc/box.md | 260 ++++++++++++++++++++--------------------------------- 1 file changed, 97 insertions(+), 163 deletions(-) diff --git a/doc/box.md b/doc/box.md index eee6b940725bbb..96c0db26a9a853 100644 --- a/doc/box.md +++ b/doc/box.md @@ -1,45 +1,42 @@ -# Namespace - Ruby's in-process separation of Classes and Modules +# Ruby Box - Ruby's in-process separation of Classes and Modules -Namespace is designed to provide separated spaces in a Ruby process, to isolate applications and libraries. +Ruby Box is designed to provide separated spaces in a Ruby process, to isolate applications and libraries. ## Known issues -* Experimental warning is shown when ruby starts with `RUBY_NAMESPACE=1` (specify `-W:no-experimental` option to hide it) +* Experimental warning is shown when ruby starts with `RUBY_BOX=1` (specify `-W:no-experimental` option to hide it) * `bundle install` may fail * `require 'active_support'` may fail * A wrong current namespace detection happens sometimes in the root namespace ## TODOs -* Identify the CI failure cause and restore temporarily skipped tests (mmtk, test/ruby/test_allocation on i686) -* Reconstruct current/loading namespace management based on control frames * Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) -* Delete per-namespace extension files (.so) lazily or process exit -* Collect rb_classext_t entries for a namespace when the namespace is collected -* Allocate an rb_namespace_t entry as the root namespace at first, then construct the contents and wrap it as rb_cNamespace instance later (to eliminate root/builtin two namespaces situation) -* Assign its own TOPLEVEL_BINDING in namespaces -* Fix `warn` in namespaces to refer `$VERBOSE` in the namespace -* Make an internal data container `Namespace::Entry` invisible +* Delete per-box extension files (.so) lazily or process exit +* Collect `rb_classext_t` entries for a box when GC collects the box +* Assign its own TOPLEVEL_BINDING in boxes +* Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box +* Make an internal data container `Ruby::Box::Entry` invisible * More test cases about `$LOAD_PATH` and `$LOADED_FEATURES` * Return classpath and nesting without the namespace prefix in the namespace itself [#21316](https://bugs.ruby-lang.org/issues/21316), [#21318](https://bugs.ruby-lang.org/issues/21318) ## How to use -### Enabling namespace +### Enabling Ruby Box -First, an environment variable should be set at the ruby process bootup: `RUBY_NAMESPACE=1`. -The only valid value is `1` to enable namespace. Other values (or unset `RUBY_NAMESPACE`) means disabling namespace. And setting the value after Ruby program starts doesn't work. +First, an environment variable should be set at the ruby process bootup: `RUBY_BOX=1`. +The only valid value is `1` to enable namespace. Other values (or unset `RUBY_BOX`) means disabling namespace. And setting the value after Ruby program starts doesn't work. -### Using namespace +### Using Ruby Box -`Namespace` class is the entrypoint of namespaces. +`Ruby::Box` class is the entrypoint of Ruby Box. ```ruby -ns = Namespace.new -ns.require('something') # or require_relative, load +box = Ruby::Box.new +box.require('something') # or require_relative, load ``` -The required file (either .rb or .so/.dll/.bundle) is loaded in the namespace (`ns` here). The required/loaded files from `something` will be loaded in the namespace recursively. +The required file (either .rb or .so/.dll/.bundle) is loaded in the box (`box` here). The required/loaded files from `something` will be loaded in the box recursively. ```ruby # something.rb @@ -52,7 +49,7 @@ class Something end ``` -Classes/modules, those methods and constants defined in the namespace can be accessed via `ns` object. +Classes/modules, those methods and constants defined in the box can be accessed via `ns` object. ```ruby p ns::Something.x # 1 @@ -60,50 +57,48 @@ p ns::Something.x # 1 X = 2 p X # 2 p ::X # 2 -p ns::Something.x # 1 -p ns::X # 1 +p box::Something.x # 1 +p box::X # 1 ``` -Instance methods defined in the namespace also run with definitions in the namespace. +Instance methods defined in the box also run with definitions in the box. ```ruby -s = ns::Something.new +s = box::Something.new p s.x # 1 ``` ## Specifications -### Namespace types - -There are two namespace types: +### Ruby Box types -* Root namespace -* User namespace +There are two box types: -There is the root namespace, just a single namespace in a Ruby process. Ruby bootstrap runs in the root namespace, and all builtin classes/modules are defined in the root namespace. (See "Builtin classes and modules".) +* Root box +* User boxes -User namespaces are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" namespace, which is a user namespace automatically created at the end of Ruby's bootstrap, copied from the root namespace. +There is the root box, just a single box in a Ruby process. Ruby bootstrap runs in the root box, and all builtin classes/modules are defined in the root box. (See "Builtin classes and modules".) -When `Namespace.new` is called, an "optional" namespace (a user, non-main namespace) is created, copied from the root namespace. All user namespaces are flat, copied from the root namespace. +User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" box, which is a user namespace automatically created at the end of Ruby's bootstrap, copied from the root box. -### Namespace class and instances +When `Ruby::Box.new` is called, an "optional" box (a user, non-main box) is created, copied from the root box. All user boxes are flat, copied from the root box. -`Namespace` is a top level class, as a subclass of `Module`, and `Namespace` instances are a kind of `Module`. +### Ruby Box class and instances -### Classes and modules defined in namespace +`Ruby::Box` is a class, as a subclass of `Module`. `Ruby::Box` instances are a kind of `Module`. -The classes and modules, newly defined in a namespace `ns`, are defined under `ns`. For example, if a class `A` is defined in `ns`, it is actually defined as `ns::A`. +### Classes and modules defined in boxes -In the namespace `ns`, `ns::A` can be referred to as `A` (and `::A`). From outside of `ns`, it can be referred to as `ns::A`. +The classes and modules, newly defined in a box `box`, are accessible via `box`. For example, if a class `A` is defined in `box`, it is accessible as `box::A` from outside of the box. -The main namespace is exceptional. Top level classes and modules defined in the main namespace are just top level classes and modules. +In the box `box`, `A` can be referred to as `A` (and `::A`). -### Classes and modules reopened in namespace +### Built-in classes and modules reopened in boxes -In namespaces, builtin classes/modules are visible and can be reopened. Those classes/modules can be reopened using `class` or `module` clauses, and class/module definitions can be changed. +In boxes, builtin classes/modules are visible and can be reopened. Those classes/modules can be reopened using `class` or `module` clauses, and class/module definitions can be changed. -The changed definitions are visible only in the namespace. In other namespaces, builtin classes/modules and those instances work without changed definitions. +The changed definitions are visible only in the box. In other boxes, builtin classes/modules and those instances work without changed definitions. ```ruby # in foo.rb @@ -126,21 +121,20 @@ Foo.foo.blank? #=> false "foo".blank? #=> false # in main.rb -ns = Namespace.new -ns.require('foo') +box = Ruby::Box.new +box.require('foo') -Foo.foo_is_blank? #=> false (#blank? called in ns) +box::Foo.foo_is_blank? #=> false (#blank? called in box) -Foo.foo.blank? # NoMethodError -"foo".blank? # NoMethodError +"foo".blank? # NoMethodError String::BLANK_PATTERN # NameError ``` -The main namespace and `ns` are different namespaces, so monkey patches in main are also invisible in `ns`. +The main box and `box` are different boxes, so monkey patches in main are also invisible in `box`. ### Builtin classes and modules -In the namespace context, "builtin" classes and modules are classes and modules: +In the box context, "builtin" classes and modules are classes and modules: * Accessible without any `require` calls in user scripts * Defined before any user program start running @@ -148,11 +142,11 @@ In the namespace context, "builtin" classes and modules are classes and modules: Hereafter, "builtin classes and modules" will be referred to as just "builtin classes". -### Builtin classes referred via namespace objects +### Builtin classes referred via box objects -Builtin classes in a namespace `ns` can be referred from other namespace. For example, `ns::String` is a valid reference, and `String` and `ns::String` are identical (`String == ns::String`, `String.object_id == ns::String.object_id`). +Builtin classes in a box `box` can be referred from other boxes. For example, `box::String` is a valid reference, and `String` and `box::String` are identical (`String == box::String`, `String.object_id == box::String.object_id`). -`ns::String`-like reference returns just a `String` in the current namespace, so its definition is `String` in the namespace, not in `ns`. +`box::String`-like reference returns just a `String` in the current box, so its definition is `String` in the box, not in `box`. ```ruby # foo.rb @@ -161,15 +155,15 @@ class String end # main.rb -ns = Namespace.new -ns.require('foo') +box = Ruby::Box.new +box.require('foo') -ns::String.foo # NoMethodError +box::String.foo # NoMethodError ``` ### Class instance variables, class variables, constants -Builtin classes can have different sets of class instance variables, class variables and constants between namespaces. +Builtin classes can have different sets of class instance variables, class variables and constants between boxes. ```ruby # foo.rb @@ -184,8 +178,8 @@ Array.class_variable_get(:@@v) #=> "_foo_" Array.const_get(:V) #=> "FOO" # main.rb -ns = Namespace.new -ns.require('foo') +box = Ruby::Box.new +box.require('foo') Array.instance_variable_get(:@v) #=> nil Array.class_variable_get(:@@v) # NameError @@ -194,7 +188,7 @@ Array.const_get(:V) # NameError ### Global variables -In namespaces, changes on global variables are also isolated in the namespace. Changes on global variables in a namespace are visible/applied only in the namespace. +In boxes, changes on global variables are also isolated in the boxes. Changes on global variables in a box are visible/applied only in the box. ```ruby # foo.rb @@ -207,8 +201,8 @@ puts "This appears: '#{$foo}'" p $foo #=> nil p $VERBOSE #=> false -ns = Namespace.new -ns.require('foo') # "This appears: 'foo'" +box = Ruby::Box.new +box.require('foo') # "This appears: 'foo'" p $foo #=> nil p $VERBOSE #=> false @@ -216,7 +210,7 @@ p $VERBOSE #=> false ### Top level constants -Usually, top level constants are defined as constants of `Object`. In namespaces, top level constants are constants of `Object` in the namespace. And the namespace object `ns`'s constants are strictly equal to constants of `Object`. +Usually, top level constants are defined as constants of `Object`. In boxes, top level constants are constants of `Object` in the box. And the box object `box`'s constants are strictly equal to constants of `Object`. ```ruby # foo.rb @@ -226,10 +220,10 @@ FOO #=> 100 Object::FOO #=> 100 # main.rb -ns = Namespace.new -ns.require('foo') +box = Ruby::Box.new +box.require('foo') -ns::FOO #=> 100 +box::FOO #=> 100 FOO # NameError Object::FOO # NameError @@ -237,7 +231,7 @@ Object::FOO # NameError ### Top level methods -Top level methods are private instance methods of `Object`, in each namespace. +Top level methods are private instance methods of `Object`, in each box. ```ruby # foo.rb @@ -251,115 +245,74 @@ Foo.say #=> "foo" yay #=> "foo" # main.rb -ns = Namespace.new -ns.require('foo') +box = Ruby::Box.new +box.require('foo') -ns.Foo.say #=> "foo" +box.Foo.say #=> "foo" yay # NoMethodError ``` -There is no way to expose top level methods in namespaces to another namespace. -(See "Expose top level methods as a method of the namespace object" in "Discussions" section below) +There is no way to expose top level methods in boxes to others. +(See "Expose top level methods as a method of the box object" in "Discussions" section below) -### Namespace scopes +### Ruby Box scopes -Namespace works in file scope. One `.rb` file runs in a single namespace. +Ruby Box works in file scope. One `.rb` file runs in a single box. -Once a file is loaded in a namespace `ns`, all methods/procs defined/created in the file run in `ns`. +Once a file is loaded in a box `box`, all methods/procs defined/created in the file run in `box`. ## Implementation details -#### Object Shapes - -Once builtin classes are copied and modified in namespaces, its instance variable management fallbacks from Object Shapes to a traditional iv table (st_table) because RClass stores the shape in its `flags`, not in `rb_classext_t`. - -#### Size of RClass and rb_classext_t - -Namespace requires to move some fields from RClass to `rb_classext_t`, then the size of RClass and `rb_classext_t` is now larger than `4 * RVALUE_SIZE`. It's against the expectation of [Variable Width Allocation](https://rubykaigi.org/2021-takeout/presentations/peterzhu2118.html). - -Now the `STATIC_ASSERT` to check the size is commented-out. (See "Minimize the size of RClass and rb_classext_t" in "Discussion" section below) - #### ISeq inline method/constant cache -As described above in "Namespace scopes", an ".rb" file runs in a namespace. So method/constant resolution will be done in a namespace consistently. +As described above in "Ruby Box scopes", an ".rb" file runs in a box. So method/constant resolution will be done in a box consistently. -That means ISeq inline caches work well even with namespaces. Otherwise, it's a bug. +That means ISeq inline caches work well even with boxes. Otherwise, it's a bug. #### Method call global cache (gccct) -`rb_funcall()` C function refers to the global cc cache table (gccct), and the cache key is calculated with the current namespace. +`rb_funcall()` C function refers to the global cc cache table (gccct), and the cache key is calculated with the current box. -So, `rb_funcall()` calls have a performance penalty when namespace is enabled. +So, `rb_funcall()` calls have a performance penalty when Ruby Box is enabled. -#### Current namespace and loading namespace +#### Current box and loading box -The current namespace is the namespace that the executing code is in. `Namespace.current` returns the current namespace object. +The current box is the box that the executing code is in. `Ruby::Box.current` returns the current box object. -The loading namespace is an internally managed namespace to determine the namespace to load newly required/loaded files. For example, `ns` is the loading namespace when `ns.require("foo")` is called. +The loading box is an internally managed box to determine the box to load newly required/loaded files. For example, `box` is the loading box when `box.require("foo")` is called. ## Discussions -#### Namespace#inspect - -Currently, `Namespace#inspect` returns values like `"#"`. This results in the very redundant and poorly visible classpath outside the namespace. - -```ruby -# foo.rb -class C; end - -# main.rb -ns = Namespace.new -ns.require('foo') - -p ns::C # "#::C" -``` - -And currently, if a namespace is assigned to a constant `NS1`, the classpath output will be `NS1::C`. But the namespace object can be brought to another namespace and the constant `NS1` in the namespace is something different. So the constant-based classpath for namespace is not safe basically. - -So we should find a better format to show namespaces. Options are: - -* `NS1::C` (only when this namespace is created and assigned to NS1 in the current namespace) -* `#::C` (with namespace type and without preceding 0) -* or something else - -#### Namespace#eval - -Testing namespace features needs to create files to be loaded in namespaces. It's not easy nor casual. - -If `Namespace` class has an instance method `#eval` to evaluate code in the namespace, it can be helpful. - #### More builtin methods written in Ruby -If namespace is enabled by default, builtin methods can be written in Ruby because it can't be overridden by users' monkey patches. Builtin Ruby methods can be JIT-ed, and it could bring performance reward. +If Ruby Box is enabled by default, builtin methods can be written in Ruby because it can't be overridden by users' monkey patches. Builtin Ruby methods can be JIT-ed, and it could bring performance reward. #### Monkey patching methods called by builtin methods -Builtin methods sometimes call other builtin methods. For example, `Hash#map` calls `Hash#each` to retrieve entries to be mapped. Without namespace, Ruby users can overwrite `Hash#each` and expect the behavior change of `Hash#map` as a result. +Builtin methods sometimes call other builtin methods. For example, `Hash#map` calls `Hash#each` to retrieve entries to be mapped. Without Ruby Box, Ruby users can overwrite `Hash#each` and expect the behavior change of `Hash#map` as a result. -But with namespaces, `Hash#map` runs in the root namespace. Ruby users can define `Hash#each` only in user namespaces, so users cannot change `Hash#map`'s behavior in this case. To achieve it, users should override both`Hash#map` and `Hash#each` (or only `Hash#map`). +But with boxes, `Hash#map` runs in the root box. Ruby users can define `Hash#each` only in user boxes, so users cannot change `Hash#map`'s behavior in this case. To achieve it, users should override both`Hash#map` and `Hash#each` (or only `Hash#map`). It is a breaking change. -It's an option to change the behavior of methods in the root namespace to refer to definitions in user namespaces. But if we do so, that means we can't proceed with "More builtin methods written in Ruby". +Users can define methods using `Ruby::Box.root.eval(...)`, but it's clearly not ideal API. -#### Context of \$LOAD\_PATH and \$LOADED\_FEATURES +#### Context of `$LOAD_PATH` and `$LOADED_FEATURES` -Global variables `$LOAD_PATH` and `$LOADED_FEATURES` control `require` method behaviors. So those namespaces are determined by the loading namespace instead of the current namespace. +Global variables `$LOAD_PATH` and `$LOADED_FEATURES` control `require` method behaviors. So those variables are determined by the loading box instead of the current box. This could potentially conflict with the user's expectations. We should find the solution. -#### Expose top level methods as a method of the namespace object +#### Expose top level methods as a method of the box object -Currently, top level methods in namespaces are not accessible from outside of the namespace. But there might be a use case to call other namespace's top level methods. +Currently, top level methods in boxes are not accessible from outside of the box. But there might be a use case to call other box's top level methods. -#### Split root and builtin namespace +#### Split root and builtin box -NOTE: "builtin" namespace is a different one from the "builtin" namespace in the current implementation +Currently, the single "root" box is the source of classext CoW. And also, the "root" box can load additional files after starting main script evaluation by calling methods which contain lines like `require "openssl"`. -Currently, the single "root" namespace is the source of classext CoW. And also, the "root" namespace can load additional files after starting main script evaluation by calling methods which contain lines like `require "openssl"`. - -That means, user namespaces can have different sets of definitions according to when it is created. +That means, user boxes can have different sets of definitions according to when it is created. ``` [root] @@ -368,51 +321,32 @@ That means, user namespaces can have different sets of definitions according to | |(require "openssl" called in root) | - |----[ns1] having OpenSSL + |----[box1] having OpenSSL | |(remove_const called for OpenSSL in root) | - |----[ns2] without OpenSSL + |----[box2] without OpenSSL ``` -This could cause unexpected behavior differences between user namespaces. It should NOT be a problem because user scripts which refer to `OpenSSL` should call `require "openssl"` by themselves. -But in the worst case, a script (without `require "openssl"`) runs well in `ns1`, but doesn't run in `ns2`. This situation looks like a "random failure" to users. +This could cause unexpected behavior differences between user boxes. It should NOT be a problem because user scripts which refer to `OpenSSL` should call `require "openssl"` by themselves. +But in the worst case, a script (without `require "openssl"`) runs well in `box1`, but doesn't run in `box2`. This situation looks like a "random failure" to users. -An option possible to prevent this situation is to have "root" and "builtin" namespaces. +An option possible to prevent this situation is to have "root" and "builtin" boxes. * root - * The namespace for the Ruby process bootstrap, then the source of CoW - * After starting the main namespace, no code runs in this namespace + * The box for the Ruby process bootstrap, then the source of CoW + * After starting the main box, no code runs in this box * builtin - * The namespace copied from the root namespace at the same time with "main" - * Methods and procs defined in the "root" namespace run in this namespace - * Classes and modules required will be loaded in this namespace + * The box copied from the root box at the same time with "main" + * Methods and procs defined in the "root" box run in this box + * Classes and modules required will be loaded in this box -This design realizes a consistent source of namespace CoW. +This design realizes a consistent source of box CoW. -#### Separate cc_tbl and callable_m_tbl, cvc_tbl for less classext CoW +#### Separate `cc_tbl` and `callable_m_tbl`, `cvc_tbl` for less classext CoW The fields of `rb_classext_t` contains several cache(-like) data, `cc_tbl`(callcache table), `callable_m_tbl`(table of resolved complemented methods) and `cvc_tbl`(class variable cache table). The classext CoW is triggered when the contents of `rb_classext_t` are changed, including `cc_tbl`, `callable_m_tbl`, and `cvc_tbl`. But those three tables are changed by just calling methods or referring class variables. So, currently, classext CoW is triggered much more times than the original expectation. If we can move those three tables outside of `rb_classext_t`, the number of copied `rb_classext_t` will be much less than the current implementation. - -#### Object Shapes per namespace - -Now the classext CoW requires RClass and `rb_classext_t` to fallback its instance variable management from Object Shapes to the traditional `st_table`. It may have a performance penalty. - -If we can apply Object Shapes on `rb_classext_t` instead of `RClass`, per-namespace classext can have its own shapes, and it may be able to avoid the performance penalty. - -#### Minimize the size of RClass and rb_classext_t - -As described in "Size of RClass and rb_classext_t" section above, the size of RClass and `rb_classext_t` is currently larger than `4 * RVALUE_SIZE` (`20 * VALUE_SIZE`). Now the size is `23 * VALUE_SIZE + 7 bits`. - -The fields possibly removed from `rb_classext_t` are: - -* `cc_tbl`, `callable_m_tbl`, `cvc_tbl` (See the section "Separate cc_tbl and callable_m_tbl, cvc_tbl for less classext CoW" above) -* `ns_super_subclasses`, `module_super_subclasses` - * `RCLASSEXT_SUBCLASSES(RCLASS_EXT_PRIME(RCLASSEXT_SUPER(klass)))->ns_subclasses` can replace it - * These fields are used only in GC, how's the actual performance benefit? - -If we can move or remove those fields, the size satisfies the assertion (`<= 4 * RVALUE_SIZE`). From aaf1f53df0317e3be1cb59b45b8b17179f918042 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Thu, 6 Nov 2025 18:12:01 +0900 Subject: [PATCH 0879/2435] Follow renaming from Namespace to Ruby::Box --- test/-ext-/test_abi.rb | 4 ++-- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/src/cruby_bindings.inc.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/-ext-/test_abi.rb b/test/-ext-/test_abi.rb index 307ad9f382542a..7f30feb9449187 100644 --- a/test/-ext-/test_abi.rb +++ b/test/-ext-/test_abi.rb @@ -9,7 +9,7 @@ def test_require_lib_with_incorrect_abi_on_dev_ruby assert_separately [], <<~RUBY err = assert_raise(LoadError) { require "-test-/abi" } assert_match(/incompatible ABI version/, err.message) - if Namespace.enabled? + if Ruby::Box.enabled? assert_include err.message, "_-test-+abi." else assert_include err.message, "/-test-/abi." @@ -31,7 +31,7 @@ def test_enable_abi_check_using_environment_variable assert_separately [{ "RUBY_ABI_CHECK" => "1" }], <<~RUBY err = assert_raise(LoadError) { require "-test-/abi" } assert_match(/incompatible ABI version/, err.message) - if Namespace.enabled? + if Ruby::Box.enabled? assert_include err.message, "_-test-+abi." else assert_include err.message, "/-test-/abi." diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index f6e4e0e22f4d22..253598bce1465d 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -626,7 +626,7 @@ pub const VM_FRAME_FLAG_LAMBDA: vm_frame_env_flags = 256; pub const VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM: vm_frame_env_flags = 512; pub const VM_FRAME_FLAG_CFRAME_KW: vm_frame_env_flags = 1024; pub const VM_FRAME_FLAG_PASSED: vm_frame_env_flags = 2048; -pub const VM_FRAME_FLAG_NS_REQUIRE: vm_frame_env_flags = 4096; +pub const VM_FRAME_FLAG_BOX_REQUIRE: vm_frame_env_flags = 4096; pub const VM_ENV_FLAG_LOCAL: vm_frame_env_flags = 2; pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 86239588292adb..457cd584cf0e01 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -753,7 +753,7 @@ pub const VM_FRAME_FLAG_LAMBDA: vm_frame_env_flags = 256; pub const VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM: vm_frame_env_flags = 512; pub const VM_FRAME_FLAG_CFRAME_KW: vm_frame_env_flags = 1024; pub const VM_FRAME_FLAG_PASSED: vm_frame_env_flags = 2048; -pub const VM_FRAME_FLAG_NS_REQUIRE: vm_frame_env_flags = 4096; +pub const VM_FRAME_FLAG_BOX_REQUIRE: vm_frame_env_flags = 4096; pub const VM_ENV_FLAG_LOCAL: vm_frame_env_flags = 2; pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; From 6d81969b475262aba251e99b518181bdf7c5a523 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Fri, 7 Nov 2025 16:41:47 +0900 Subject: [PATCH 0880/2435] Development of 4.0.0 started. --- NEWS.md | 2 +- include/ruby/internal/abi.h | 2 +- include/ruby/version.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index b22cf5c290e9ae..3407140eecddb9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# NEWS for Ruby 3.5.0 +# NEWS for Ruby 4.0.0 This document is a list of user-visible feature changes since the **3.4.0** release, except for bug fixes. diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index 322761fc683341..e735a67564d885 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,7 +24,7 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 4 +#define RUBY_ABI_VERSION 0 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ diff --git a/include/ruby/version.h b/include/ruby/version.h index a521d925edd7ce..24c846a1ca6ce2 100644 --- a/include/ruby/version.h +++ b/include/ruby/version.h @@ -61,13 +61,13 @@ * doesn't mean a total rewrite. Practically when it comes to API versioning, * major and minor version changes are equally catastrophic. */ -#define RUBY_API_VERSION_MAJOR 3 +#define RUBY_API_VERSION_MAJOR 4 /** * Minor version. As of writing this version changes annually. Greater * version doesn't mean "better"; they just mean years passed. */ -#define RUBY_API_VERSION_MINOR 5 +#define RUBY_API_VERSION_MINOR 0 /** * Teeny version. This digit is kind of reserved these days. Kept 0 for the From da6ba845545036f9bb89dae390dda78e23a3a337 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 7 Nov 2025 09:45:06 +0100 Subject: [PATCH 0881/2435] [ruby/json] Get rid of JSON.deep_const_get (private API) https://github.com/ruby/json/commit/826cb2a4f4 --- ext/json/lib/json/common.rb | 19 +++++++------------ test/json/json_common_interface_test.rb | 5 ----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 60178c2a59c464..f22d911f552ec5 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -71,8 +71,13 @@ def create_additions_proc(opts) end when object_class if opts[:create_additions] != false - if class_name = object[JSON.create_id] - klass = JSON.deep_const_get(class_name) + if class_path = object[JSON.create_id] + klass = begin + Object.const_get(class_path) + rescue NameError => e + raise ArgumentError, "can't get const #{class_path}: #{e}" + end + if klass.respond_to?(:json_creatable?) ? klass.json_creatable? : klass.respond_to?(:json_create) create_additions_warning if create_additions.nil? object = klass.json_create(object) @@ -147,16 +152,6 @@ def parser=(parser) # :nodoc: const_set :Parser, parser end - # Return the constant located at _path_. The format of _path_ has to be - # either ::A::B::C or A::B::C. In any case, A has to be located at the top - # level (absolute namespace path?). If there doesn't exist a constant at - # the given path, an ArgumentError is raised. - def deep_const_get(path) # :nodoc: - Object.const_get(path) - rescue NameError => e - raise ArgumentError, "can't get const #{path}: #{e}" - end - # Set the module _generator_ to be used by JSON. def generator=(generator) # :nodoc: old, $VERBOSE = $VERBOSE, nil diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 077d7e1539666e..37fa439575cd87 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -68,11 +68,6 @@ def test_create_id JSON.create_id = 'json_class' end - def test_deep_const_get - assert_raise(ArgumentError) { JSON.deep_const_get('Nix::Da') } - assert_equal File::SEPARATOR, JSON.deep_const_get('File::SEPARATOR') - end - def test_parse assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]') end From cd8902cce871144931a492408945d233f0926584 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 7 Nov 2025 09:51:34 +0100 Subject: [PATCH 0882/2435] [ruby/json] Deprecate `JSON::State#[]` and `JSON::State#[]=` This prevent from freezing and sharing state instances. If you needs some sort of arguments or extra state to the generator methods, consider using `JSON::Coder` instead. https://github.com/ruby/json/commit/e9fbc8937f --- ext/json/lib/json/ext/generator/state.rb | 4 ++ test/json/json_generator_test.rb | 50 ++++++++++++++---------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/ext/json/lib/json/ext/generator/state.rb b/ext/json/lib/json/ext/generator/state.rb index 1f56e6c682cc6d..ce5c185cabeed2 100644 --- a/ext/json/lib/json/ext/generator/state.rb +++ b/ext/json/lib/json/ext/generator/state.rb @@ -75,6 +75,8 @@ def to_h # # Returns the value returned by method +name+. def [](name) + ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0") + if respond_to?(name) __send__(name) else @@ -87,6 +89,8 @@ def [](name) # # Sets the attribute name to value. def []=(name, value) + ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0") + if respond_to?(name_writer = "#{name}=") __send__ name_writer, value else diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index a6950f88878db8..1b3c702c982f94 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -183,7 +183,9 @@ def test_states assert_equal('{"1":2}', json) s = JSON.state.new assert s.check_circular? - assert s[:check_circular?] + assert_deprecated_warning(/JSON::State/) do + assert s[:check_circular?] + end h = { 1=>2 } h[3] = h assert_raise(JSON::NestingError) { generate(h) } @@ -193,7 +195,9 @@ def test_states a << a assert_raise(JSON::NestingError) { generate(a, s) } assert s.check_circular? - assert s[:check_circular?] + assert_deprecated_warning(/JSON::State/) do + assert s[:check_circular?] + end end def test_falsy_state @@ -375,28 +379,32 @@ def to_s end def test_hash_likeness_set_symbol - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil.class, state[:foo].class - assert_equal nil, state['foo'] - state[:foo] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] + assert_deprecated_warning(/JSON::State/) do + state = JSON.state.new + assert_equal nil, state[:foo] + assert_equal nil.class, state[:foo].class + assert_equal nil, state['foo'] + state[:foo] = :bar + assert_equal :bar, state[:foo] + assert_equal :bar, state['foo'] + state_hash = state.to_hash + assert_kind_of Hash, state_hash + assert_equal :bar, state_hash[:foo] + end end def test_hash_likeness_set_string - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil, state['foo'] - state['foo'] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] + assert_deprecated_warning(/JSON::State/) do + state = JSON.state.new + assert_equal nil, state[:foo] + assert_equal nil, state['foo'] + state['foo'] = :bar + assert_equal :bar, state[:foo] + assert_equal :bar, state['foo'] + state_hash = state.to_hash + assert_kind_of Hash, state_hash + assert_equal :bar, state_hash[:foo] + end end def test_json_state_to_h_roundtrip From a881f2a0f441bf6d06a68bf711ca81dd6b3a1026 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 7 Nov 2025 10:04:43 +0100 Subject: [PATCH 0883/2435] [ruby/json] Release 2.16.0 https://github.com/ruby/json/commit/5a12067f88 --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index dc01ba290b8cc2..cc25a0453e20c9 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.15.2' + VERSION = '2.16.0' end From a00f425e824b599240415eee7c1256b224d073c9 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 7 Nov 2025 09:07:35 +0000 Subject: [PATCH 0884/2435] Update default gems list at a881f2a0f441bf6d06a68bf711ca81 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 3407140eecddb9..d8dc21f795d291 100644 --- a/NEWS.md +++ b/NEWS.md @@ -192,7 +192,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.3 -* json 2.15.2 +* json 2.16.0 * net-http 0.7.0 * openssl 4.0.0.pre * optparse 0.8.0 From f3dd4bef786f8d56a2702b340f66e3a374c3ad3f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 18:41:04 +0900 Subject: [PATCH 0885/2435] Skip removed test for JSON.deep_const_get --- tool/rbs_skip_tests | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index bdea3e59571b91..28133153739292 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -73,6 +73,8 @@ test_iconv(JSONSingletonTest) test_recurse_proc(JSONInstanceTest) test_recurse_proc(JSONSingletonTest) +test_deep_const_get(JSONSingletonTest) + CGITest CGI is retired CGISingletonTest CGI is retired From a4c051b870ac7f7b3c5482baf05600e1f6751b47 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 29 Oct 2025 22:14:22 +0900 Subject: [PATCH 0886/2435] Remove PATH check --- file.c | 90 ---------------------------------------------------------- 1 file changed, 90 deletions(-) diff --git a/file.c b/file.c index 4fc2fec75f59a6..2c8e87b768e4db 100644 --- a/file.c +++ b/file.c @@ -6494,96 +6494,6 @@ rb_is_absolute_path(const char *path) return 0; } -#ifndef ENABLE_PATH_CHECK -# if defined DOSISH || defined __CYGWIN__ -# define ENABLE_PATH_CHECK 0 -# else -# define ENABLE_PATH_CHECK 1 -# endif -#endif - -#if ENABLE_PATH_CHECK -static int -path_check_0(VALUE path) -{ - struct stat st; - const char *p0 = StringValueCStr(path); - const char *e0; - rb_encoding *enc; - char *p = 0, *s; - - if (!rb_is_absolute_path(p0)) { - char *buf = ruby_getcwd(); - VALUE newpath; - - newpath = rb_str_new2(buf); - xfree(buf); - - rb_str_cat2(newpath, "/"); - rb_str_cat2(newpath, p0); - path = newpath; - p0 = RSTRING_PTR(path); - } - e0 = p0 + RSTRING_LEN(path); - enc = rb_enc_get(path); - for (;;) { -#ifndef S_IWOTH -# define S_IWOTH 002 -#endif - if (STAT(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) -#ifdef S_ISVTX - && !(p && (st.st_mode & S_ISVTX)) -#endif - && !access(p0, W_OK)) { - rb_enc_warn(enc, "Insecure world writable dir %s in PATH, mode 0%" -#if SIZEOF_DEV_T > SIZEOF_INT - PRI_MODET_PREFIX"o", -#else - "o", -#endif - p0, st.st_mode); - if (p) *p = '/'; - RB_GC_GUARD(path); - return 0; - } - s = strrdirsep(p0, e0, enc); - if (p) *p = '/'; - if (!s || s == p0) return 1; - p = s; - e0 = p; - *p = '\0'; - } -} -#endif - -int -rb_path_check(const char *path) -{ - rb_warn_deprecated_to_remove_at(3.6, "rb_path_check", NULL); -#if ENABLE_PATH_CHECK - const char *p0, *p, *pend; - const char sep = PATH_SEP_CHAR; - - if (!path) return 1; - - pend = path + strlen(path); - p0 = path; - p = strchr(path, sep); - if (!p) p = pend; - - for (;;) { - if (!path_check_0(rb_str_new(p0, p - p0))) { - return 0; /* not safe */ - } - p0 = p + 1; - if (p0 > pend) break; - p = strchr(p0, sep); - if (!p) p = pend; - } -#endif - return 1; -} - int ruby_is_fd_loadable(int fd) { From f4e01783d3412b10f9978b5297142979cb067ce8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 14:14:58 +0900 Subject: [PATCH 0887/2435] Prism update for 4.0 --- lib/prism/ffi.rb | 2 ++ prism/options.c | 10 ++++++++++ prism/options.h | 5 ++++- test/prism/test_helper.rb | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index a3bf5786bd67b7..f6ad6f98b1cb92 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -434,6 +434,8 @@ def dump_options_version(version) 2 when /\A3\.5(\.\d+)?\z/ 3 + when /\A4\.0(\.\d+)?\z/ + 4 else if current raise CurrentVersionError, RUBY_VERSION diff --git a/prism/options.c b/prism/options.c index 1b5c022cf54f9f..373d76a21fee45 100644 --- a/prism/options.c +++ b/prism/options.c @@ -93,6 +93,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return true; } + if (strncmp(version, "4.0", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_0; + return true; + } + return false; } @@ -111,6 +116,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length options->version = PM_OPTIONS_VERSION_CRUBY_3_5; return true; } + + if (strncmp(version, "4.0.", 4) == 0 && is_number(version + 4, length - 4)) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_0; + return true; + } } if (length >= 6) { diff --git a/prism/options.h b/prism/options.h index 1a92c470f1ea7d..44cd745e15520d 100644 --- a/prism/options.h +++ b/prism/options.h @@ -94,8 +94,11 @@ typedef enum { /** The vendored version of prism in CRuby 3.5.x. */ PM_OPTIONS_VERSION_CRUBY_3_5 = 3, + /** The vendored version of prism in CRuby 4.0.x. */ + PM_OPTIONS_VERSION_CRUBY_4_0 = 4, + /** The current version of prism. */ - PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_3_5 + PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_0 } pm_options_version_t; /** diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index 84871722c9b6ad..faf6117668a404 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -230,7 +230,7 @@ def self.windows? end # All versions that prism can parse - SYNTAX_VERSIONS = %w[3.3 3.4 3.5] + SYNTAX_VERSIONS = %w[3.3 3.4 3.5 4.0] # Returns an array of ruby versions that a given filepath should test against: # test.txt # => all available versions From 996cae65f3cc8fed60c6bb758b00882cac49389d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 30 Oct 2025 12:43:26 +0900 Subject: [PATCH 0888/2435] Depricate IO operation with `|` --- io.c | 35 +++------------------------ test/ruby/test_io.rb | 50 +++++++-------------------------------- test/ruby/test_io_m17n.rb | 24 ------------------- 3 files changed, 11 insertions(+), 98 deletions(-) diff --git a/io.c b/io.c index 78ac0bb2c65d9e..5366c74c498332 100644 --- a/io.c +++ b/io.c @@ -8223,21 +8223,6 @@ rb_io_s_sysopen(int argc, VALUE *argv, VALUE _) return INT2NUM(fd); } -static VALUE -check_pipe_command(VALUE filename_or_command) -{ - char *s = RSTRING_PTR(filename_or_command); - long l = RSTRING_LEN(filename_or_command); - char *e = s + l; - int chlen; - - if (rb_enc_ascget(s, e, &chlen, rb_enc_get(filename_or_command)) == '|') { - VALUE cmd = rb_str_new(s+chlen, l-chlen); - return cmd; - } - return Qnil; -} - /* * call-seq: * open(path, mode = 'r', perm = 0666, **opts) -> io or nil @@ -8283,13 +8268,7 @@ rb_f_open(int argc, VALUE *argv, VALUE _) redirect = TRUE; } else { - VALUE cmd = check_pipe_command(tmp); - if (!NIL_P(cmd)) { - // TODO: when removed in 4.0, update command_injection.rdoc - rb_warn_deprecated_to_remove_at(4.0, "Calling Kernel#open with a leading '|'", "IO.popen"); - argv[0] = cmd; - return rb_io_s_popen(argc, argv, rb_cIO); - } + argv[0] = tmp; } } } @@ -8308,16 +8287,8 @@ static VALUE rb_io_open_generic(VALUE klass, VALUE filename, int oflags, enum rb_io_mode fmode, const struct rb_io_encoding *convconfig, mode_t perm) { - VALUE cmd; - if (klass == rb_cIO && !NIL_P(cmd = check_pipe_command(filename))) { - // TODO: when removed in 4.0, update command_injection.rdoc - rb_warn_deprecated_to_remove_at(4.0, "IO process creation with a leading '|'", "IO.popen"); - return pipe_open_s(cmd, rb_io_oflags_modestr(oflags), fmode, convconfig); - } - else { - return rb_file_open_generic(io_alloc(klass), filename, - oflags, fmode, convconfig, perm); - } + return rb_file_open_generic(io_alloc(klass), filename, + oflags, fmode, convconfig, perm); } static VALUE diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 0025aa5a7dd502..dd8ccbc96a63a5 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -2619,36 +2619,15 @@ def o.to_open(kw); kw; end assert_equal({:a=>1}, open(o, {a: 1})) end - def test_open_pipe - assert_deprecated_warning(/Kernel#open with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - open("|" + EnvUtil.rubybin, "r+") do |f| - f.puts "puts 'foo'" - f.close_write - assert_equal("foo\n", f.read) - end - end - end + def test_path_with_pipe + mkcdtmpdir do + cmd = "|echo foo" + assert_file.not_exist?(cmd) - def test_read_command - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - assert_equal("foo\n", IO.read("|echo foo")) - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - File.read("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - File.binread("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - Class.new(IO).read("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ESPIPE) do - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|#{EnvUtil.rubybin} -e 'puts :foo'", 1, 1) - end + pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM] + assert_raise(*pipe_errors) { open(cmd, "r+") } + assert_raise(*pipe_errors) { IO.read(cmd) } + assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } } end end @@ -2853,19 +2832,6 @@ def test_reopen_ivar end def test_foreach - a = [] - - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x } - end - assert_equal(["foo\n", "bar\n", "baz\n"], a) - - a = [] - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x } - end - assert_equal(["zot\n"], a) - make_tempfile {|t| a = [] IO.foreach(t.path) {|x| a << x } diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index b01d627d920d7e..1986026bfb3d9c 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -1395,30 +1395,6 @@ def test_popenv_r_enc_enc_in_opt2 } end - def test_open_pipe_r_enc - EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - open("|#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f| - assert_equal(Encoding::ASCII_8BIT, f.external_encoding) - assert_equal(nil, f.internal_encoding) - s = f.read - assert_equal(Encoding::ASCII_8BIT, s.encoding) - assert_equal("\xff".force_encoding("ascii-8bit"), s) - } - end - end - - def test_open_pipe_r_enc2 - EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f| - assert_equal(Encoding::UTF_8, f.external_encoding) - assert_equal(nil, f.internal_encoding) - s = f.read - assert_equal(Encoding::UTF_8, s.encoding) - assert_equal("\u3042", s) - } - end - end - def test_s_foreach_enc with_tmpdir { generate_file("t", "\xff") From 1f32464a2dcaf641c9fd77a323a13e44dd1d2670 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 17:57:55 +0900 Subject: [PATCH 0889/2435] Update Bundler::CurrentRuby::ALL_RUBY_VERSIONS --- lib/bundler/current_ruby.rb | 2 +- spec/bundler/bundler/current_ruby_spec.rb | 14 +++++++------- spec/bundler/bundler/dsl_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index b350baa24fd528..abb7ca2195daa6 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -11,7 +11,7 @@ def self.current_ruby end class CurrentRuby - ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..35).to_a).freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, *40].freeze KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze PLATFORM_MAP = { diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb index 83c42e73e1efe4..aa19a414076508 100644 --- a/spec/bundler/bundler/current_ruby_spec.rb +++ b/spec/bundler/bundler/current_ruby_spec.rb @@ -22,7 +22,7 @@ ruby_32: Gem::Platform::RUBY, ruby_33: Gem::Platform::RUBY, ruby_34: Gem::Platform::RUBY, - ruby_35: Gem::Platform::RUBY, + ruby_40: Gem::Platform::RUBY, mri: Gem::Platform::RUBY, mri_18: Gem::Platform::RUBY, mri_19: Gem::Platform::RUBY, @@ -39,7 +39,7 @@ mri_32: Gem::Platform::RUBY, mri_33: Gem::Platform::RUBY, mri_34: Gem::Platform::RUBY, - mri_35: Gem::Platform::RUBY, + mri_40: Gem::Platform::RUBY, rbx: Gem::Platform::RUBY, truffleruby: Gem::Platform::RUBY, jruby: Gem::Platform::JAVA, @@ -61,7 +61,7 @@ windows_32: Gem::Platform::WINDOWS, windows_33: Gem::Platform::WINDOWS, windows_34: Gem::Platform::WINDOWS, - windows_35: Gem::Platform::WINDOWS } + windows_40: Gem::Platform::WINDOWS } end let(:deprecated) do @@ -81,7 +81,7 @@ mswin_32: Gem::Platform::MSWIN, mswin_33: Gem::Platform::MSWIN, mswin_34: Gem::Platform::MSWIN, - mswin_35: Gem::Platform::MSWIN, + mswin_40: Gem::Platform::MSWIN, mswin64: Gem::Platform::MSWIN64, mswin64_19: Gem::Platform::MSWIN64, mswin64_20: Gem::Platform::MSWIN64, @@ -97,7 +97,7 @@ mswin64_32: Gem::Platform::MSWIN64, mswin64_33: Gem::Platform::MSWIN64, mswin64_34: Gem::Platform::MSWIN64, - mswin64_35: Gem::Platform::MSWIN64, + mswin64_40: Gem::Platform::MSWIN64, mingw: Gem::Platform::UNIVERSAL_MINGW, mingw_18: Gem::Platform::UNIVERSAL_MINGW, mingw_19: Gem::Platform::UNIVERSAL_MINGW, @@ -114,7 +114,7 @@ mingw_32: Gem::Platform::UNIVERSAL_MINGW, mingw_33: Gem::Platform::UNIVERSAL_MINGW, mingw_34: Gem::Platform::UNIVERSAL_MINGW, - mingw_35: Gem::Platform::UNIVERSAL_MINGW, + mingw_40: Gem::Platform::UNIVERSAL_MINGW, x64_mingw: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_20: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_21: Gem::Platform::UNIVERSAL_MINGW, @@ -129,7 +129,7 @@ x64_mingw_32: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_33: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_34: Gem::Platform::UNIVERSAL_MINGW, - x64_mingw_35: Gem::Platform::UNIVERSAL_MINGW } + x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW } end # rubocop:enable Naming/VariableNumber diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index e88033e9554240..286dfa71151877 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -201,8 +201,8 @@ describe "#gem" do # rubocop:disable Naming/VariableNumber [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :ruby_26, :ruby_27, - :ruby_30, :ruby_31, :ruby_32, :ruby_33, :ruby_34, :ruby_35, :mri, :mri_18, :mri_19, :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, - :mri_25, :mri_26, :mri_27, :mri_30, :mri_31, :mri_32, :mri_33, :mri_34, :mri_35, :jruby, :rbx, :truffleruby].each do |platform| + :ruby_30, :ruby_31, :ruby_32, :ruby_33, :ruby_34, :ruby_40, :mri, :mri_18, :mri_19, :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, + :mri_25, :mri_26, :mri_27, :mri_30, :mri_31, :mri_32, :mri_33, :mri_34, :mri_40, :jruby, :rbx, :truffleruby].each do |platform| it "allows #{platform} as a valid platform" do subject.gem("foo", platform: platform) end From 41865bb6712a14ad1fc2e729316b4e2d8452babc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 21:57:24 +0900 Subject: [PATCH 0890/2435] Use `IO.popen` instead of `IO.foreach` with pipe --- win32/mkexports.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/win32/mkexports.rb b/win32/mkexports.rb index 1a9f474be28826..44bda94990d52f 100755 --- a/win32/mkexports.rb +++ b/win32/mkexports.rb @@ -138,7 +138,11 @@ def each_export(objs) class Exports::Cygwin < Exports def self.nm - @@nm ||= RbConfig::CONFIG["NM"] + @@nm ||= + begin + require 'shellwords' + RbConfig::CONFIG["NM"].shellsplit + end end def exports(*) @@ -146,7 +150,9 @@ def exports(*) end def each_line(objs, &block) - IO.foreach("|#{self.class.nm} --extern-only --defined-only #{objs.join(' ')}", &block) + IO.popen([*self.class.nm, *%w[--extern-only --defined-only], *objs]) do |f| + f.each(&block) + end end def each_export(objs) From a2201570bd12e096b0cecf9d82f4d45eb19c8676 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:55:17 +0100 Subject: [PATCH 0891/2435] Remove `rb_path_check` declaration Implementation was removed in https://github.com/ruby/ruby/commit/a4c051b870ac7f7b3c5482baf05600e1f6751b47 --- include/ruby/internal/intern/file.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/include/ruby/internal/intern/file.h b/include/ruby/internal/intern/file.h index b669758d21ac09..8508b7ab9e0b51 100644 --- a/include/ruby/internal/intern/file.h +++ b/include/ruby/internal/intern/file.h @@ -211,22 +211,6 @@ int rb_is_absolute_path(const char *path); */ rb_off_t rb_file_size(VALUE file); -#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY -RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() -#endif -/** - * If the PATH_SEPARATOR-separated list of directory names contains the name of - * a world-writable directory, issue a warning for it. This may do nothing on - * some platforms. - * - * @param[in] path A local path. - * @retval 0 The "check" succeeded. - * @retval otherwise The "check" failed. - * @note This feature may be disabled by setting `ENABLE_PATH_CHECK` - * macro to zero at compilation time. - */ -int rb_path_check(const char *path); - RBIMPL_SYMBOL_EXPORT_END() #endif /* RBIMPL_INTERN_FILE_H */ From 201ed7541b840310105c272625e5c0d01fe1d48b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 23:09:24 +0900 Subject: [PATCH 0892/2435] Suppress sign-compare warnings --- vm_dump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm_dump.c b/vm_dump.c index c51c6cca81ea5a..1b0d69b30e9b5a 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -134,7 +134,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c iseq = cfp->iseq; pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); - if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) { + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { line = rb_vm_get_sourceline(cfp); } if (line) { @@ -355,7 +355,7 @@ box_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_contro iseq = cfp->iseq; pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); - if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) { + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { line = rb_vm_get_sourceline(cfp); } if (line) { From a1366f21e3beb936fedcd2dcce51b2d10a5434a0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 23:47:16 +0900 Subject: [PATCH 0893/2435] [Feature #19630] [DOC] News about removal of IO operation with `|` --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index d8dc21f795d291..3306e3e5f6e713 100644 --- a/NEWS.md +++ b/NEWS.md @@ -55,6 +55,9 @@ Note: We're only listing outstanding class updates. [[Feature #21219]] + * A deprecated behavior, process creation by `Kernel#open` with a + leading `|`, was removed. [[Feature #19630]] + * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -66,6 +69,9 @@ Note: We're only listing outstanding class updates. * `IO.select` accepts `Float::INFINITY` as a timeout argument. [[Feature #20610]] + * A deprecated behavior, process creation by `IO` class methods + with a leading `|`, was removed. [[Feature #19630]] + * Math * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] @@ -317,6 +323,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 +[Feature #19630]: https://bugs.ruby-lang.org/issues/19630 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 From ae60b0bfd84a3d860f43fe8b6ea91048a25e41e6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Nov 2025 00:29:34 +0900 Subject: [PATCH 0894/2435] Use DOT_WAIT for old GNU make [ci skip] --- defs/gmake.mk | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index 3dcfe9f639a244..da98f70d9a7cb7 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -548,13 +548,17 @@ matz: OLD := $(MAJOR).$(MINOR).0 ifdef NEW matz: MAJOR := $(word 1,$(subst ., ,$(NEW))) matz: MINOR := $(word 2,$(subst ., ,$(NEW))) -matz: .WAIT bump_news +matz: $(DOT_WAIT) bump_news +bump_news$(DOT_WAIT): up +bump_headers$(DOT_WAIT): bump_news else matz: MINOR := $(shell expr $(MINOR) + 1) -matz: .WAIT reset_news +matz: $(DOT_WAIT) reset_news +flush_news$(DOT_WAIT): up +bump_headers$(DOT_WAIT): reset_news endif -matz: .WAIT bump_headers +matz: $(DOT_WAIT) bump_headers matz: override NEW := $(MAJOR).$(MINOR).0 matz: files := include/ruby/version.h include/ruby/internal/abi.h matz: message := Development of $(NEW) started. From 9bbe4b600b2bc2866561d095c3409589a214a358 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 7 Nov 2025 08:54:21 -0800 Subject: [PATCH 0895/2435] ZJIT: Carve out IseqPayload into a separate module (#15098) --- zjit/src/codegen.rs | 3 +- zjit/src/gc.rs | 87 +----------------------------------------- zjit/src/hir.rs | 2 +- zjit/src/invariants.rs | 5 ++- zjit/src/lib.rs | 1 + zjit/src/payload.rs | 86 +++++++++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 2 +- 7 files changed, 96 insertions(+), 90 deletions(-) create mode 100644 zjit/src/payload.rs diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f90c4605a26050..fd34b6e0dfba17 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -13,7 +13,8 @@ use crate::invariants::{ track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption }; -use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; +use crate::gc::append_gc_offsets; +use crate::payload::{get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index baa0926c515189..93a9b10e562c4a 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -1,94 +1,11 @@ //! This module is responsible for marking/moving objects on GC. use std::{ffi::c_void, ops::Range}; -use crate::codegen::IseqCallRef; -use crate::stats::CompileError; -use crate::{cruby::*, profile::IseqProfile, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; +use crate::{cruby::*, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; +use crate::payload::{IseqPayload, get_or_create_iseq_payload, payload_ptr_as_mut}; use crate::stats::Counter::gc_time_ns; use crate::state::gc_mark_raw_samples; -/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. -#[derive(Debug)] -pub struct IseqPayload { - /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. - pub status: IseqStatus, - - /// Type information of YARV instruction operands - pub profile: IseqProfile, - - /// GC offsets of the JIT code. These are the addresses of objects that need to be marked. - pub gc_offsets: Vec, - - /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. - pub iseq_calls: Vec, -} - -impl IseqPayload { - fn new(iseq_size: u32) -> Self { - Self { - status: IseqStatus::NotCompiled, - profile: IseqProfile::new(iseq_size), - gc_offsets: vec![], - iseq_calls: vec![], - } - } -} - -/// Set of CodePtrs for an ISEQ -#[derive(Clone, Debug, PartialEq)] -pub struct IseqCodePtrs { - /// Entry for the interpreter - pub start_ptr: CodePtr, - /// Entries for JIT-to-JIT calls - pub jit_entry_ptrs: Vec, -} - -#[derive(Debug, PartialEq)] -pub enum IseqStatus { - Compiled(IseqCodePtrs), - CantCompile(CompileError), - NotCompiled, -} - -/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists. -pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload { - type VoidPtr = *mut c_void; - - unsafe { - let payload = rb_iseq_get_zjit_payload(iseq); - if payload.is_null() { - // Allocate a new payload with Box and transfer ownership to the GC. - // We drop the payload with Box::from_raw when the GC frees the ISEQ and calls us. - // NOTE(alan): Sometimes we read from an ISEQ without ever writing to it. - // We allocate in those cases anyways. - let iseq_size = get_iseq_encoded_size(iseq); - let new_payload = IseqPayload::new(iseq_size); - let new_payload = Box::into_raw(Box::new(new_payload)); - rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr); - - new_payload - } else { - payload as *mut IseqPayload - } - } -} - -/// Get the payload object associated with an ISEQ. Create one if none exists. -pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { - let payload_non_null = get_or_create_iseq_payload_ptr(iseq); - payload_ptr_as_mut(payload_non_null) -} - -/// Convert an IseqPayload pointer to a mutable reference. Only one reference -/// should be kept at a time. -fn payload_ptr_as_mut(payload_ptr: *mut IseqPayload) -> &'static mut IseqPayload { - // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have - // exclusive mutable access. - // Hmm, nothing seems to stop calling this on the same - // iseq twice, though, which violates aliasing rules. - unsafe { payload_ptr.as_mut() }.unwrap() -} - /// GC callback for marking GC objects in the per-ISEQ payload. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_iseq_mark(payload: *mut c_void) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 07ffe4b00a7f5e..5a15aac06c43cb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6,7 +6,7 @@ #![allow(clippy::if_same_then_else)] #![allow(clippy::match_like_matches_macro)] use crate::{ - cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState + cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState }; use std::{ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 119aa5ca52fb66..f1adc7b32cfdb9 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -2,7 +2,8 @@ use std::{collections::{HashMap, HashSet}, mem}; -use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID, VALUE}, gc::IseqPayload, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; +use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID, VALUE}, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; +use crate::payload::IseqPayload; use crate::stats::with_time_stat; use crate::stats::Counter::invalidation_time_ns; use crate::gc::remove_gc_offsets; @@ -367,7 +368,7 @@ pub fn track_no_trace_point_assumption(patch_point_ptr: CodePtr, side_exit_ptr: #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_tracing_invalidate_all() { - use crate::gc::{get_or_create_iseq_payload, IseqStatus}; + use crate::payload::{get_or_create_iseq_payload, IseqStatus}; use crate::cruby::{for_each_iseq, rb_iseq_reset_jit_func}; if !zjit_enabled_p() { diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs index e58cf2bec4787b..f8ff380148004e 100644 --- a/zjit/src/lib.rs +++ b/zjit/src/lib.rs @@ -28,3 +28,4 @@ mod profile; mod invariants; mod bitset; mod gc; +mod payload; diff --git a/zjit/src/payload.rs b/zjit/src/payload.rs new file mode 100644 index 00000000000000..1fb3f919946dbb --- /dev/null +++ b/zjit/src/payload.rs @@ -0,0 +1,86 @@ +use std::ffi::c_void; +use crate::codegen::IseqCallRef; +use crate::stats::CompileError; +use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr}; + +/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. +#[derive(Debug)] +pub struct IseqPayload { + /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. + pub status: IseqStatus, + + /// Type information of YARV instruction operands + pub profile: IseqProfile, + + /// GC offsets of the JIT code. These are the addresses of objects that need to be marked. + pub gc_offsets: Vec, + + /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. + pub iseq_calls: Vec, +} + +impl IseqPayload { + fn new(iseq_size: u32) -> Self { + Self { + status: IseqStatus::NotCompiled, + profile: IseqProfile::new(iseq_size), + gc_offsets: vec![], + iseq_calls: vec![], + } + } +} + +/// Set of CodePtrs for an ISEQ +#[derive(Clone, Debug, PartialEq)] +pub struct IseqCodePtrs { + /// Entry for the interpreter + pub start_ptr: CodePtr, + /// Entries for JIT-to-JIT calls + pub jit_entry_ptrs: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum IseqStatus { + Compiled(IseqCodePtrs), + CantCompile(CompileError), + NotCompiled, +} + +/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists. +pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload { + type VoidPtr = *mut c_void; + + unsafe { + let payload = rb_iseq_get_zjit_payload(iseq); + if payload.is_null() { + // Allocate a new payload with Box and transfer ownership to the GC. + // We drop the payload with Box::from_raw when the GC frees the ISEQ and calls us. + // NOTE(alan): Sometimes we read from an ISEQ without ever writing to it. + // We allocate in those cases anyways. + let iseq_size = get_iseq_encoded_size(iseq); + let new_payload = IseqPayload::new(iseq_size); + let new_payload = Box::into_raw(Box::new(new_payload)); + rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr); + + new_payload + } else { + payload as *mut IseqPayload + } + } +} + +/// Get the payload object associated with an ISEQ. Create one if none exists. +pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { + let payload_non_null = get_or_create_iseq_payload_ptr(iseq); + payload_ptr_as_mut(payload_non_null) +} + +/// Convert an IseqPayload pointer to a mutable reference. Only one reference +/// should be kept at a time. +pub fn payload_ptr_as_mut(payload_ptr: *mut IseqPayload) -> &'static mut IseqPayload { + // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have + // exclusive mutable access. + // Hmm, nothing seems to stop calling this on the same + // iseq twice, though, which violates aliasing rules. + unsafe { payload_ptr.as_mut() }.unwrap() +} diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 3366fe8e58db17..47bae3ac633a86 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -3,7 +3,7 @@ // We use the YARV bytecode constants which have a CRuby-style name #![allow(non_upper_case_globals)] -use crate::{cruby::*, gc::get_or_create_iseq_payload, options::{get_option, NumProfiles}}; +use crate::{cruby::*, payload::get_or_create_iseq_payload, options::{get_option, NumProfiles}}; use crate::distribution::{Distribution, DistributionSummary}; use crate::stats::Counter::profile_time_ns; use crate::stats::with_time_stat; From f55421e81c20dea72eb81b38cd1093e5d9936597 Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 7 Nov 2025 13:50:17 -0500 Subject: [PATCH 0896/2435] ZJIT: Add compilation for checkkeyword (#14764)
Before
``` **ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (64.0% of total 3,683,424): Kernel#is_a?: 427,127 (11.6%) Hash#[]=: 426,276 (11.6%) String#start_with?: 336,245 ( 9.1%) ObjectSpace::WeakKeyMap#[]: 139,406 ( 3.8%) Hash#fetch: 127,291 ( 3.5%) String#hash: 79,259 ( 2.2%) Process.clock_gettime: 74,658 ( 2.0%) Array#any?: 74,441 ( 2.0%) Integer#==: 71,067 ( 1.9%) Kernel#dup: 68,058 ( 1.8%) Hash#key?: 62,306 ( 1.7%) Regexp#match?: 62,247 ( 1.7%) SQLite3::Statement#step: 61,172 ( 1.7%) SQLite3::Statement#done?: 61,172 ( 1.7%) Kernel#Array: 55,015 ( 1.5%) Integer#<=>: 49,127 ( 1.3%) String.new: 48,363 ( 1.3%) IO#read: 47,753 ( 1.3%) Array#include?: 43,307 ( 1.2%) Struct#initialize: 42,650 ( 1.2%) Top-3 not optimized method types for send (100.0% of total 1,022,743): iseq: 736,483 (72.0%) cfunc: 286,174 (28.0%) null: 86 ( 0.0%) Top-6 not optimized method types for send_without_block (100.0% of total 189,556): optimized_call: 115,966 (61.2%) optimized_send: 36,767 (19.4%) optimized_struct_aset: 33,788 (17.8%) null: 2,521 ( 1.3%) optimized_block_call: 510 ( 0.3%) cfunc: 4 ( 0.0%) Top-13 not optimized instructions (100.0% of total 1,648,882): invokesuper: 697,471 (42.3%) invokeblock: 496,687 (30.1%) sendforward: 221,094 (13.4%) opt_eq: 147,620 ( 9.0%) opt_minus: 40,865 ( 2.5%) opt_plus: 22,912 ( 1.4%) opt_send_without_block: 18,932 ( 1.1%) opt_gt: 867 ( 0.1%) opt_mult: 768 ( 0.0%) opt_neq: 654 ( 0.0%) opt_or: 508 ( 0.0%) opt_lt: 359 ( 0.0%) opt_ge: 145 ( 0.0%) Top-13 send fallback reasons (100.0% of total 8,308,826): send_without_block_polymorphic: 3,174,975 (38.2%) not_optimized_instruction: 1,648,882 (19.8%) fancy_call_feature: 1,072,807 (12.9%) send_not_optimized_method_type: 1,022,743 (12.3%) send_no_profiles: 599,715 ( 7.2%) send_without_block_no_profiles: 486,108 ( 5.9%) send_without_block_not_optimized_optimized_method_type: 187,031 ( 2.3%) send_polymorphic: 101,834 ( 1.2%) obj_to_string_not_string: 7,610 ( 0.1%) send_without_block_not_optimized_method_type: 2,525 ( 0.0%) send_without_block_direct_too_many_args: 2,369 ( 0.0%) send_without_block_cfunc_array_variadic: 2,190 ( 0.0%) ccall_with_frame_too_many_args: 37 ( 0.0%) Top-8 popular unsupported argument-parameter features (100.0% of total 1,209,121): param_opt: 583,595 (48.3%) param_forwardable: 178,162 (14.7%) param_block: 162,689 (13.5%) param_kw: 150,575 (12.5%) param_rest: 90,091 ( 7.5%) param_kwrest: 33,791 ( 2.8%) caller_splat: 10,214 ( 0.8%) caller_kw_splat: 4 ( 0.0%) Top-7 unhandled YARV insns (100.0% of total 128,032): checkkeyword: 88,698 (69.3%) invokesuperforward: 22,296 (17.4%) getblockparam: 16,292 (12.7%) getconstant: 336 ( 0.3%) checkmatch: 290 ( 0.2%) setblockparam: 101 ( 0.1%) once: 19 ( 0.0%) Top-1 compile error reasons (100.0% of total 21,283): exception_handler: 21,283 (100.0%) Top-18 side exit reasons (100.0% of total 2,335,562): guard_type_failure: 677,930 (29.0%) guard_shape_failure: 410,183 (17.6%) unhandled_kwarg: 235,100 (10.1%) patchpoint_stable_constant_names: 206,172 ( 8.8%) block_param_proxy_not_iseq_or_ifunc: 199,931 ( 8.6%) patchpoint_no_singleton_class: 188,359 ( 8.1%) unhandled_yarv_insn: 128,032 ( 5.5%) unknown_newarray_send: 124,805 ( 5.3%) patchpoint_method_redefined: 73,062 ( 3.1%) unhandled_hir_insn: 56,688 ( 2.4%) compile_error: 21,283 ( 0.9%) block_param_proxy_modified: 11,647 ( 0.5%) fixnum_mult_overflow: 954 ( 0.0%) patchpoint_no_ep_escape: 813 ( 0.0%) guard_bit_equals_failure: 316 ( 0.0%) obj_to_string_fallback: 230 ( 0.0%) interrupt: 35 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 26,775,579 dynamic_send_count: 8,308,826 (31.0%) optimized_send_count: 18,466,753 (69.0%) iseq_optimized_send_count: 7,611,729 (28.4%) inline_cfunc_optimized_send_count: 5,935,290 (22.2%) inline_iseq_optimized_send_count: 657,555 ( 2.5%) non_variadic_cfunc_optimized_send_count: 3,169,054 (11.8%) variadic_cfunc_optimized_send_count: 1,093,125 ( 4.1%) dynamic_getivar_count: 2,793,635 dynamic_setivar_count: 3,040,844 compiled_iseq_count: 4,496 failed_iseq_count: 0 compile_time: 915ms profile_time: 6ms gc_time: 6ms invalidation_time: 20ms vm_write_pc_count: 26,857,114 vm_write_sp_count: 25,770,558 vm_write_locals_count: 25,770,558 vm_write_stack_count: 25,770,558 vm_write_to_parent_iseq_local_count: 106,036 vm_read_from_parent_iseq_local_count: 3,213,992 guard_type_count: 27,683,170 guard_type_exit_ratio: 2.4% code_region_bytes: 32,178,176 side_exit_count: 2,335,562 total_insn_count: 170,714,077 vm_insn_count: 28,999,194 zjit_insn_count: 141,714,883 ratio_in_zjit: 83.0% ```
After
``` **ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (63.9% of total 3,686,703): Kernel#is_a?: 427,123 (11.6%) Hash#[]=: 426,276 (11.6%) String#start_with?: 336,245 ( 9.1%) ObjectSpace::WeakKeyMap#[]: 139,406 ( 3.8%) Hash#fetch: 127,291 ( 3.5%) String#hash: 79,259 ( 2.1%) Process.clock_gettime: 74,658 ( 2.0%) Array#any?: 74,441 ( 2.0%) Integer#==: 71,067 ( 1.9%) Kernel#dup: 68,058 ( 1.8%) Regexp#match?: 62,336 ( 1.7%) Hash#key?: 62,306 ( 1.7%) SQLite3::Statement#step: 61,172 ( 1.7%) SQLite3::Statement#done?: 61,172 ( 1.7%) Kernel#Array: 55,048 ( 1.5%) Integer#<=>: 49,127 ( 1.3%) String.new: 48,363 ( 1.3%) IO#read: 47,753 ( 1.3%) Array#include?: 43,309 ( 1.2%) Struct#initialize: 42,650 ( 1.2%) Top-3 not optimized method types for send (100.0% of total 1,026,413): iseq: 737,496 (71.9%) cfunc: 288,831 (28.1%) null: 86 ( 0.0%) Top-6 not optimized method types for send_without_block (100.0% of total 189,556): optimized_call: 115,966 (61.2%) optimized_send: 36,767 (19.4%) optimized_struct_aset: 33,788 (17.8%) null: 2,521 ( 1.3%) optimized_block_call: 510 ( 0.3%) cfunc: 4 ( 0.0%) Top-13 not optimized instructions (100.0% of total 1,648,949): invokesuper: 697,452 (42.3%) invokeblock: 496,687 (30.1%) sendforward: 221,094 (13.4%) opt_eq: 147,620 ( 9.0%) opt_minus: 40,863 ( 2.5%) opt_plus: 22,912 ( 1.4%) opt_send_without_block: 19,020 ( 1.2%) opt_gt: 867 ( 0.1%) opt_mult: 768 ( 0.0%) opt_neq: 654 ( 0.0%) opt_or: 508 ( 0.0%) opt_lt: 359 ( 0.0%) opt_ge: 145 ( 0.0%) Top-13 send fallback reasons (100.0% of total 8,318,975): send_without_block_polymorphic: 3,177,471 (38.2%) not_optimized_instruction: 1,648,949 (19.8%) fancy_call_feature: 1,075,143 (12.9%) send_not_optimized_method_type: 1,026,413 (12.3%) send_no_profiles: 599,748 ( 7.2%) send_without_block_no_profiles: 486,190 ( 5.8%) send_without_block_not_optimized_optimized_method_type: 187,031 ( 2.2%) send_polymorphic: 102,497 ( 1.2%) obj_to_string_not_string: 8,412 ( 0.1%) send_without_block_not_optimized_method_type: 2,525 ( 0.0%) send_without_block_direct_too_many_args: 2,369 ( 0.0%) send_without_block_cfunc_array_variadic: 2,190 ( 0.0%) ccall_with_frame_too_many_args: 37 ( 0.0%) Top-8 popular unsupported argument-parameter features (100.0% of total 1,211,457): param_opt: 584,073 (48.2%) param_forwardable: 178,907 (14.8%) param_block: 162,689 (13.4%) param_kw: 151,688 (12.5%) param_rest: 90,091 ( 7.4%) param_kwrest: 33,791 ( 2.8%) caller_splat: 10,214 ( 0.8%) caller_kw_splat: 4 ( 0.0%) Top-6 unhandled YARV insns (100.0% of total 39,334): invokesuperforward: 22,296 (56.7%) getblockparam: 16,292 (41.4%) getconstant: 336 ( 0.9%) checkmatch: 290 ( 0.7%) setblockparam: 101 ( 0.3%) once: 19 ( 0.0%) Top-1 compile error reasons (100.0% of total 21,283): exception_handler: 21,283 (100.0%) Top-18 side exit reasons (100.0% of total 2,253,541): guard_type_failure: 682,695 (30.3%) guard_shape_failure: 410,183 (18.2%) unhandled_kwarg: 236,780 (10.5%) patchpoint_stable_constant_names: 206,310 ( 9.2%) block_param_proxy_not_iseq_or_ifunc: 199,931 ( 8.9%) patchpoint_no_singleton_class: 188,438 ( 8.4%) unknown_newarray_send: 124,805 ( 5.5%) patchpoint_method_redefined: 73,056 ( 3.2%) unhandled_hir_insn: 56,686 ( 2.5%) unhandled_yarv_insn: 39,334 ( 1.7%) compile_error: 21,283 ( 0.9%) block_param_proxy_modified: 11,647 ( 0.5%) fixnum_mult_overflow: 954 ( 0.0%) patchpoint_no_ep_escape: 813 ( 0.0%) guard_bit_equals_failure: 316 ( 0.0%) obj_to_string_fallback: 230 ( 0.0%) interrupt: 58 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 27,032,751 dynamic_send_count: 8,318,975 (30.8%) optimized_send_count: 18,713,776 (69.2%) iseq_optimized_send_count: 7,809,698 (28.9%) inline_cfunc_optimized_send_count: 5,980,083 (22.1%) inline_iseq_optimized_send_count: 657,677 ( 2.4%) non_variadic_cfunc_optimized_send_count: 3,170,381 (11.7%) variadic_cfunc_optimized_send_count: 1,095,937 ( 4.1%) dynamic_getivar_count: 2,793,987 dynamic_setivar_count: 3,350,905 compiled_iseq_count: 4,498 failed_iseq_count: 0 compile_time: 884ms profile_time: 6ms gc_time: 6ms invalidation_time: 19ms vm_write_pc_count: 27,417,915 vm_write_sp_count: 26,327,928 vm_write_locals_count: 26,327,928 vm_write_stack_count: 26,327,928 vm_write_to_parent_iseq_local_count: 106,036 vm_read_from_parent_iseq_local_count: 3,213,992 guard_type_count: 27,937,831 guard_type_exit_ratio: 2.4% code_region_bytes: 32,571,392 side_exit_count: 2,253,541 total_insn_count: 170,630,429 vm_insn_count: 26,617,244 zjit_insn_count: 144,013,185 ratio_in_zjit: 84.4% ```
--- zjit/src/codegen.rs | 7 ++++ zjit/src/hir.rs | 26 ++++++++++++ zjit/src/hir/tests.rs | 97 ++++++++++++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 2 + 4 files changed, 131 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index fd34b6e0dfba17..3ef93ee3d971ec 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -433,6 +433,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), Insn::SetInstanceVariable { self_val, id, ic, val, state } => no_output!(gen_set_instance_variable(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))), + Insn::FixnumBitCheck { val, index } => gen_fixnum_bit_check(asm, opnd!(val), *index), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)), @@ -652,6 +653,12 @@ fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_in asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) } +fn gen_fixnum_bit_check(asm: &mut Assembler, val: Opnd, index: u8) -> Opnd { + let bit_test: u64 = 0x01 << (index + 1); + asm.test(val, bit_test.into()); + asm.csel_z(Qtrue.into(), Qfalse.into()) +} + fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, leaf: bool, args: Vec) -> lir::Opnd { assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5a15aac06c43cb..7c180929abef9f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -462,6 +462,7 @@ pub enum SideExitReason { UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), UnhandledCallType(CallType), + TooManyKeywordParameters, FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, @@ -664,6 +665,9 @@ pub enum Insn { /// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with /// `DEFINED_YIELD` IsBlockGiven, + /// Test the bit at index of val, a Fixnum. + /// Return Qtrue if the bit is set, else Qfalse. + FixnumBitCheck { val: InsnId, index: u8 }, /// Get a global variable named `id` GetGlobal { id: ID, state: InsnId }, @@ -1147,6 +1151,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, + Insn::FixnumBitCheck {val, index} => { write!(f, "FixnumBitCheck {val}, {index}") }, Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => { write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { @@ -1691,6 +1696,7 @@ impl Function { } }, &Return { val } => Return { val: find!(val) }, + &FixnumBitCheck { val, index } => FixnumBitCheck { val: find!(val), index }, &Throw { throw_state, val, state } => Throw { throw_state, val: find!(val), state }, &StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state }, &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, @@ -1952,6 +1958,7 @@ impl Function { Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, Insn::IsBlockGiven => types::BoolExact, + Insn::FixnumBitCheck { .. } => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::ArrayInclude { .. } => types::BoolExact, Insn::DupArrayInclude { .. } => types::BoolExact, @@ -3245,6 +3252,9 @@ impl Function { | &Insn::GetConstantPath { ic: _, state } => { worklist.push_back(state); } + &Insn::FixnumBitCheck { val, index: _ } => { + worklist.push_back(val) + } &Insn::ArrayMax { ref elements, state } | &Insn::NewHash { ref elements, state } | &Insn::NewArray { ref elements, state } => { @@ -4648,6 +4658,22 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); state.stack_push(fun.push_insn(block, Insn::DefinedIvar { self_val: self_param, id, pushval, state: exit_id })); } + YARVINSN_checkkeyword => { + // When a keyword is unspecified past index 32, a hash will be used instead. + // This can only happen in iseqs taking more than 32 keywords. + // In this case, we side exit to the interpreter. + // TODO(Jacob): Replace the magic number 32 with a named constant. (Can be completed after PR 15039) + if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= 32} { + let exit_id = fun.push_insn(block, Insn::Snapshot {state: exit_state}); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyKeywordParameters }); + break; + } + let ep_offset = get_arg(pc, 0).as_u32(); + let index = get_arg(pc, 1).as_u64(); + let index: u8 = index.try_into().map_err(|_| ParseError::MalformedIseq(insn_idx))?; + let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); + state.stack_push(fun.push_insn(block, Insn::FixnumBitCheck { val, index })); + } YARVINSN_opt_getconstant_path => { let ic = get_arg(pc, 0).as_ptr(); let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state }); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index af3fd3de9153d0..accc10471911ad 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3273,4 +3273,99 @@ pub mod hir_build_tests { SideExit UnhandledYARVInsn(expandarray) "); } -} + + #[test] + fn test_checkkeyword_tests_fixnum_bit() { + eval(r#" + def test(kw: 1 + 1) = kw + "#); + assert_contains_opcode("test", YARVINSN_checkkeyword); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v14:BasicObject = GetLocal l0, EP@3 + v15:BoolExact = FixnumBitCheck v14, 0 + CheckInterrupts + v18:CBool = Test v15 + IfTrue v18, bb3(v10, v11, v12) + v20:Fixnum[1] = Const Value(1) + v21:Fixnum[1] = Const Value(1) + v25:BasicObject = SendWithoutBlock v20, :+, v21 + PatchPoint NoEPEscape(test) + Jump bb3(v10, v25, v12) + bb3(v29:BasicObject, v30:BasicObject, v31:BasicObject): + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_checkkeyword_too_many_keywords_causes_side_exit() { + eval(r#" + def test(k1: k1, k2: k2, k3: k3, k4: k4, k5: k5, + k6: k6, k7: k7, k8: k8, k9: k9, k10: k10, k11: k11, + k12: k12, k13: k13, k14: k14, k15: k15, k16: k16, + k17: k17, k18: k18, k19: k19, k20: k20, k21: k21, + k22: k22, k23: k23, k24: k24, k25: k25, k26: k26, + k27: k27, k28: k28, k29: k29, k30: k30, k31: k31, + k32: k32, k33: k33) = k1 + "#); + assert_contains_opcode("test", YARVINSN_checkkeyword); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@37 + v3:BasicObject = GetLocal l0, SP@36 + v4:BasicObject = GetLocal l0, SP@35 + v5:BasicObject = GetLocal l0, SP@34 + v6:BasicObject = GetLocal l0, SP@33 + v7:BasicObject = GetLocal l0, SP@32 + v8:BasicObject = GetLocal l0, SP@31 + v9:BasicObject = GetLocal l0, SP@30 + v10:BasicObject = GetLocal l0, SP@29 + v11:BasicObject = GetLocal l0, SP@28 + v12:BasicObject = GetLocal l0, SP@27 + v13:BasicObject = GetLocal l0, SP@26 + v14:BasicObject = GetLocal l0, SP@25 + v15:BasicObject = GetLocal l0, SP@24 + v16:BasicObject = GetLocal l0, SP@23 + v17:BasicObject = GetLocal l0, SP@22 + v18:BasicObject = GetLocal l0, SP@21 + v19:BasicObject = GetLocal l0, SP@20 + v20:BasicObject = GetLocal l0, SP@19 + v21:BasicObject = GetLocal l0, SP@18 + v22:BasicObject = GetLocal l0, SP@17 + v23:BasicObject = GetLocal l0, SP@16 + v24:BasicObject = GetLocal l0, SP@15 + v25:BasicObject = GetLocal l0, SP@14 + v26:BasicObject = GetLocal l0, SP@13 + v27:BasicObject = GetLocal l0, SP@12 + v28:BasicObject = GetLocal l0, SP@11 + v29:BasicObject = GetLocal l0, SP@10 + v30:BasicObject = GetLocal l0, SP@9 + v31:BasicObject = GetLocal l0, SP@8 + v32:BasicObject = GetLocal l0, SP@7 + v33:BasicObject = GetLocal l0, SP@6 + v34:BasicObject = GetLocal l0, SP@5 + v35:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) + bb1(v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject, v52:BasicObject, v53:BasicObject, v54:BasicObject, v55:BasicObject, v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject): + EntryPoint JIT(0) + Jump bb2(v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66, v67, v68, v69, v70, v71, v72) + bb2(v74:BasicObject, v75:BasicObject, v76:BasicObject, v77:BasicObject, v78:BasicObject, v79:BasicObject, v80:BasicObject, v81:BasicObject, v82:BasicObject, v83:BasicObject, v84:BasicObject, v85:BasicObject, v86:BasicObject, v87:BasicObject, v88:BasicObject, v89:BasicObject, v90:BasicObject, v91:BasicObject, v92:BasicObject, v93:BasicObject, v94:BasicObject, v95:BasicObject, v96:BasicObject, v97:BasicObject, v98:BasicObject, v99:BasicObject, v100:BasicObject, v101:BasicObject, v102:BasicObject, v103:BasicObject, v104:BasicObject, v105:BasicObject, v106:BasicObject, v107:BasicObject, v108:BasicObject): + SideExit TooManyKeywordParameters + "); + } + } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index e1d7c692ed52e0..6b3f9b5ce8179b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -159,6 +159,7 @@ make_counters! { exit_stackoverflow, exit_block_param_proxy_modified, exit_block_param_proxy_not_iseq_or_ifunc, + exit_too_many_keyword_parameters, } // Send fallback counters that are summed as dynamic_send_count @@ -395,6 +396,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { StackOverflow => exit_stackoverflow, BlockParamProxyModified => exit_block_param_proxy_modified, BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc, + TooManyKeywordParameters => exit_too_many_keyword_parameters, PatchPoint(Invariant::BOPRedefined { .. }) => exit_patchpoint_bop_redefined, PatchPoint(Invariant::MethodRedefined { .. }) From 4816969c28def82d2fe57045758f5b39b3ac8081 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:12:25 +0900 Subject: [PATCH 0897/2435] [ruby/rubygems] Replaced pathname auto-loading in bootstrap of bundler https://github.com/ruby/rubygems/commit/79ba4a537d --- lib/bundler.rb | 2 +- lib/bundler/shared_helpers.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index e76dfde122d7e8..51ea3beeb0f70a 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -2,7 +2,7 @@ require_relative "bundler/rubygems_ext" require_relative "bundler/vendored_fileutils" -require "pathname" +autoload :Pathname, "pathname" unless defined?(Pathname) require "rbconfig" require_relative "bundler/errors" diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 987a68afd75326..a03f1f83f0128a 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -4,8 +4,6 @@ require_relative "rubygems_integration" require_relative "current_ruby" -autoload :Pathname, "pathname" - module Bundler autoload :WINDOWS, File.expand_path("constants", __dir__) autoload :FREEBSD, File.expand_path("constants", __dir__) From 9767b31090f923200abff68657c6489e8f32cfde Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:14:31 +0900 Subject: [PATCH 0898/2435] [ruby/rubygems] Removed unnecessary loading of pathname https://github.com/ruby/rubygems/commit/6e965b7872 --- lib/bundler/cli/gem.rb | 2 -- lib/bundler/compact_index_client.rb | 1 - lib/bundler/vendor/thor/lib/thor/runner.rb | 2 +- spec/bundler/support/path.rb | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 23b29bf36bee36..20d678d66d7e98 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "pathname" - module Bundler class CLI Bundler.require_thor_actions diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index 37e2ccced8e1d1..6865e30dbcaab1 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "set" module Bundler diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index 95f8b16e31893a..f0ce6df96ce99b 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -2,7 +2,7 @@ require_relative "group" require "digest/sha2" -require "pathname" +require "pathname" unless defined?(Pathname) class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index cc750f55d81f53..bbbf5cc9086ac0 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "rbconfig" require_relative "env" From 2518aa9117907d7ed08f469df596c0be961fc8fe Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 07:23:07 +0900 Subject: [PATCH 0899/2435] [ruby/rubygems] Replace Pathname#rmtree to FileUtils.rm_rf directly https://github.com/ruby/rubygems/commit/33c7a9a565 --- spec/bundler/cache/gems_spec.rb | 10 +++++----- spec/bundler/install/gemfile/path_spec.rb | 2 +- spec/bundler/install/gems/compact_index_spec.rb | 2 +- spec/bundler/install/gems/dependency_api_spec.rb | 2 +- spec/bundler/install/path_spec.rb | 4 ++-- spec/bundler/runtime/load_spec.rb | 2 +- spec/bundler/support/builders.rb | 2 +- spec/bundler/update/git_spec.rb | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index b23d475a5f63cd..c9b85556e1ef1d 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -226,7 +226,7 @@ it "re-caches during install" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(out).to include("Updating files in vendor/cache") expect(cached_gem("myrack-1.0.0")).to exist @@ -307,7 +307,7 @@ it "doesn't remove gems cached gems that don't match their remote counterparts, but also refuses to install and prints an error" do setup_main_repo cached_myrack = cached_gem("myrack-1.0.0") - cached_myrack.rmtree + FileUtils.rm_rf cached_myrack build_gem "myrack", "1.0.0", path: cached_myrack.parent, rubygems_version: "1.3.2" @@ -338,7 +338,7 @@ it "raises an error when a cached gem is altered and produces a different checksum than the remote gem" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") checksums = checksums_section do |c| @@ -362,14 +362,14 @@ expect(err).to include("1. remove the gem at #{cached_gem("myrack-1.0.0")}") expect(cached_gem("myrack-1.0.0")).to exist - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(cached_gem("myrack-1.0.0")).to exist end it "installs a modified gem with a non-matching checksum when the API implementation does not provide checksums" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") pristine_system_gems diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 31d79ed41ca5da..b55fe3dbbf1e85 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -454,7 +454,7 @@ it "handles directories in bin/" do build_lib "foo" - lib_path("foo-1.0").join("foo.gemspec").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join("foo.gemspec") lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 64c59d4826f357..32a42aa93a4809 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -698,7 +698,7 @@ def start bundle :install, artifice: "compact_index_forbidden" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 1650df3dfb6ae6..ac986a0c67a2f9 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -649,7 +649,7 @@ def start bundle "install", artifice: "endpoint_marshal_fail" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index beea1e36dbc73d..8c0793642baf15 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -35,7 +35,7 @@ bundle :install, dir: dir expect(out).to include("installed into `./vendor/bundle`") - dir.rmtree + FileUtils.rm_rf dir end it "prints a message to let the user know where gems where installed" do @@ -181,7 +181,7 @@ def set_bundle_path(type, location) expect(vendored_gems("extensions")).to be_directory expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" - vendored_gems("extensions").rmtree + FileUtils.rm_rf vendored_gems("extensions") run "require 'very_simple_binary_c'", raise_on_error: false expect(err).to include("Bundler::GemNotFound") diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index 15f3d0eb5bcd6c..472cde87c5421f 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -68,7 +68,7 @@ begin expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound) ensure - bundler_gemfile.rmtree if @remove_bundler_gemfile + FileUtils.rm_rf bundler_gemfile if @remove_bundler_gemfile end end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 50fedd38f16a7b..3ebb7e386490bf 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -463,7 +463,7 @@ def _build(options = {}) FileUtils.mv bundler_path, options[:path] end ensure - build_path.rmtree + FileUtils.rm_rf build_path end end diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 2cb0abe02fb57b..aaa039211e2b2d 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -199,7 +199,7 @@ gem "foo", :git => "#{lib_path("foo-1.0")}" G - lib_path("foo-1.0").join(".git").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join(".git") bundle :update, all: true, raise_on_error: false expect(err).to include(lib_path("foo-1.0").to_s). From 110f24c47e162db7730757cb4a9b13d1148ec85e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 09:08:50 +0900 Subject: [PATCH 0900/2435] [ruby/rubygems] Restore pathname for rake dev:deps https://github.com/ruby/rubygems/commit/89e95d0f15 --- spec/bundler/support/path.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index bbbf5cc9086ac0..0a534dd40e44eb 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "pathname" unless defined?(Pathname) require "rbconfig" require_relative "env" From a7c23b9a9f726bec7cbd6f89d157d7dd140420da Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 7 Nov 2025 13:16:34 +0900 Subject: [PATCH 0901/2435] [ruby/rubygems] Use Dir.pwd instead of Pathname.pwd https://github.com/ruby/rubygems/commit/6c161b253d --- lib/bundler/cli/gem.rb | 4 ++-- lib/bundler/shared_helpers.rb | 2 +- lib/bundler/source/path.rb | 2 +- spec/bundler/bundler/shared_helpers_spec.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 20d678d66d7e98..abfb095d469f81 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -24,7 +24,7 @@ def initialize(options, gem_name, thor) thor.destination_root = nil @name = @gem_name - @target = SharedHelpers.pwd.join(gem_name) + @target = Pathname.new(SharedHelpers.pwd).join(gem_name) @extension = options[:ext] @@ -276,7 +276,7 @@ def run private def resolve_name(name) - SharedHelpers.pwd.join(name).basename.to_s + Pathname.new(SharedHelpers.pwd).join(name).basename.to_s end def ask_and_set(key, prompt, explanation) diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index a03f1f83f0128a..4c914eb1a447a4 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -55,7 +55,7 @@ def chdir(dir, &blk) def pwd Bundler.rubygems.ext_lock.synchronize do - Pathname.pwd + Dir.pwd end end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index d258270fe09dd9..82e782ba257a6a 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -24,7 +24,7 @@ def initialize(options) @path = Pathname.new(options["path"]) expanded_path = expand(@path) @path = if @path.relative? - expanded_path.relative_path_from(root_path.expand_path) + expanded_path.relative_path_from(File.expand_path(root_path)) else expanded_path end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 0aacb93c16e2a9..c93c3e98149a0e 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -159,7 +159,7 @@ let(:pwd_stub) { nil } it "returns the current absolute path" do - expect(subject.pwd).to eq(source_root) + expect(subject.pwd).to eq(source_root.to_s) end end From 7037d8f89e71a13547d031d76747e45cfe930c9f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:14:07 +0100 Subject: [PATCH 0902/2435] [ruby/prism] Rename Ruby 3.5 to Ruby 4.0 See https://github.com/ruby/ruby/commit/6d81969b475262aba251e99b518181bdf7c5a523 It leaves the old variant around. RuboCop for examples accesses `Prism::Translation::Parser35` to test against ruby-head. For now I left these simply as an alias https://github.com/ruby/prism/commit/d0a823f045 --- lib/prism/ffi.rb | 4 +--- lib/prism/prism.gemspec | 2 ++ lib/prism/translation.rb | 1 + lib/prism/translation/parser.rb | 6 +++--- lib/prism/translation/parser35.rb | 7 +------ lib/prism/translation/parser40.rb | 13 ++++++++++++ lib/prism/translation/parser_current.rb | 4 ++-- prism/options.c | 20 +++++-------------- prism/options.h | 4 ++-- prism/prism.c | 8 ++++---- test/prism/api/parse_test.rb | 3 +++ .../endless_methods_command_call.txt | 0 .../fixtures/{3.5 => 4.0}/leading_logical.txt | 0 test/prism/fixtures_test.rb | 4 ++-- test/prism/lex_test.rb | 4 ++-- test/prism/locals_test.rb | 4 ++-- test/prism/ractor_test.rb | 2 +- test/prism/ruby/parser_test.rb | 7 ++++--- test/prism/ruby/ripper_test.rb | 4 ++-- test/prism/ruby/ruby_parser_test.rb | 4 ++-- test/prism/test_helper.rb | 3 ++- 21 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 lib/prism/translation/parser40.rb rename test/prism/fixtures/{3.5 => 4.0}/endless_methods_command_call.txt (100%) rename test/prism/fixtures/{3.5 => 4.0}/leading_logical.txt (100%) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index f6ad6f98b1cb92..7e6103fde775bf 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -432,10 +432,8 @@ def dump_options_version(version) 1 when /\A3\.4(\.\d+)?\z/ 2 - when /\A3\.5(\.\d+)?\z/ + when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ 3 - when /\A4\.0(\.\d+)?\z/ - 4 else if current raise CurrentVersionError, RUBY_VERSION diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 168f8211ff384a..10c2eaad209ed4 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -101,6 +101,7 @@ Gem::Specification.new do |spec| "lib/prism/translation/parser33.rb", "lib/prism/translation/parser34.rb", "lib/prism/translation/parser35.rb", + "lib/prism/translation/parser40.rb", "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", @@ -123,6 +124,7 @@ Gem::Specification.new do |spec| "rbi/prism/translation/parser33.rbi", "rbi/prism/translation/parser34.rbi", "rbi/prism/translation/parser35.rbi", + "rbi/prism/translation/parser40.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index d127f2006c59f2..7933b4a7222e93 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -10,6 +10,7 @@ module Translation # steep:ignore autoload :Parser33, "prism/translation/parser33" autoload :Parser34, "prism/translation/parser34" autoload :Parser35, "prism/translation/parser35" + autoload :Parser40, "prism/translation/parser40" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 1ad7a193c4f18b..23245dc383e9e9 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -84,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 35 + 40 end # The default encoding for Ruby files is UTF-8. @@ -356,8 +356,8 @@ def convert_for_prism(version) "3.3.1" when 34 "3.4.0" - when 35 - "3.5.0" + when 35, 40 + "4.0.0" else "latest" end diff --git a/lib/prism/translation/parser35.rb b/lib/prism/translation/parser35.rb index 79cd59cbd96a8e..52eeeb6c8c4d75 100644 --- a/lib/prism/translation/parser35.rb +++ b/lib/prism/translation/parser35.rb @@ -3,11 +3,6 @@ module Prism module Translation - # This class is the entry-point for Ruby 3.5 of `Prism::Translation::Parser`. - class Parser35 < Parser - def version # :nodoc: - 35 - end - end + Parser35 = Parser40 # :nodoc: end end diff --git a/lib/prism/translation/parser40.rb b/lib/prism/translation/parser40.rb new file mode 100644 index 00000000000000..2ec7445882ce36 --- /dev/null +++ b/lib/prism/translation/parser40.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 4.0 of `Prism::Translation::Parser`. + class Parser40 < Parser + def version # :nodoc: + 40 + end + end + end +end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index 1b1794abbe047a..76d71e940926cc 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -10,8 +10,8 @@ module Translation ParserCurrent = Parser33 when /^3\.4\./ ParserCurrent = Parser34 - when /^3\.5\./ - ParserCurrent = Parser35 + when /^3\.5\./, /^4\.0\./ + ParserCurrent = Parser40 else # Keep this in sync with released Ruby. parser = Parser34 diff --git a/prism/options.c b/prism/options.c index 373d76a21fee45..4a8953da7d2aac 100644 --- a/prism/options.c +++ b/prism/options.c @@ -88,12 +88,7 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return true; } - if (strncmp(version, "3.5", 3) == 0) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_5; - return true; - } - - if (strncmp(version, "4.0", 3) == 0) { + if (strncmp(version, "3.5", 3) == 0 || strncmp(version, "4.0", 3) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } @@ -101,23 +96,18 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return false; } - if (length >= 4) { - if (strncmp(version, "3.3.", 4) == 0 && is_number(version + 4, length - 4)) { + if (length >= 4 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.3.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_3_3; return true; } - if (strncmp(version, "3.4.", 4) == 0 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.4.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_3_4; return true; } - if (strncmp(version, "3.5.", 4) == 0 && is_number(version + 4, length - 4)) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_5; - return true; - } - - if (strncmp(version, "4.0.", 4) == 0 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.5.", 4) == 0 || strncmp(version, "4.0.", 4) == 0) { options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } diff --git a/prism/options.h b/prism/options.h index 44cd745e15520d..a663c9767e994e 100644 --- a/prism/options.h +++ b/prism/options.h @@ -91,11 +91,11 @@ typedef enum { /** The vendored version of prism in CRuby 3.4.x. */ PM_OPTIONS_VERSION_CRUBY_3_4 = 2, - /** The vendored version of prism in CRuby 3.5.x. */ + /** The vendored version of prism in CRuby 4.0.x. */ PM_OPTIONS_VERSION_CRUBY_3_5 = 3, /** The vendored version of prism in CRuby 4.0.x. */ - PM_OPTIONS_VERSION_CRUBY_4_0 = 4, + PM_OPTIONS_VERSION_CRUBY_4_0 = 3, /** The current version of prism. */ PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_0 diff --git a/prism/prism.c b/prism/prism.c index 03b12e9db82d34..02dd2f117564ee 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10864,11 +10864,11 @@ parser_lex(pm_parser_t *parser) { } - // If we are parsing as CRuby 3.5 or later and we + // If we are parsing as CRuby 4.0 or later and we // hit a '&&' or a '||' then we will lex the ignored // newline. if ( - (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) && + (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) && following && ( (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || @@ -10915,7 +10915,7 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_AMPERSAND_DOT); } - if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { // If we hit an && then we are in a logical chain // and we need to return the logical operator. if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') { @@ -19625,7 +19625,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statements = (pm_node_t *) pm_statements_node_create(parser); bool allow_command_call; - if (parser->version >= PM_OPTIONS_VERSION_CRUBY_3_5) { + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { allow_command_call = accepts_command_call; } else { // Allow `def foo = puts "Hello"` but not `private def foo = puts "Hello"` diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 1f885fa4935302..bb1761109fadbb 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -119,6 +119,9 @@ def test_version assert Prism.parse_success?("1 + 1", version: "3.5") assert Prism.parse_success?("1 + 1", version: "3.5.0") + assert Prism.parse_success?("1 + 1", version: "4.0") + assert Prism.parse_success?("1 + 1", version: "4.0.0") + assert Prism.parse_success?("1 + 1", version: "latest") # Test edge case diff --git a/test/prism/fixtures/3.5/endless_methods_command_call.txt b/test/prism/fixtures/4.0/endless_methods_command_call.txt similarity index 100% rename from test/prism/fixtures/3.5/endless_methods_command_call.txt rename to test/prism/fixtures/4.0/endless_methods_command_call.txt diff --git a/test/prism/fixtures/3.5/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt similarity index 100% rename from test/prism/fixtures/3.5/leading_logical.txt rename to test/prism/fixtures/4.0/leading_logical.txt diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 0f0577c10d5735..2aebb1847782dc 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -35,8 +35,8 @@ class FixturesTest < TestCase except << "3.3-3.3/return_in_sclass.txt" # Leaving these out until they are supported by parse.y. - except << "3.5/leading_logical.txt" - except << "3.5/endless_methods_command_call.txt" + except << "4.0/leading_logical.txt" + except << "4.0/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 9682bf8a322c21..19dd845d755621 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -43,10 +43,10 @@ class LexTest < TestCase end # https://bugs.ruby-lang.org/issues/20925 - except << "3.5/leading_logical.txt" + except << "4.0/leading_logical.txt" # https://bugs.ruby-lang.org/issues/17398#note-12 - except << "3.5/endless_methods_command_call.txt" + except << "4.0/endless_methods_command_call.txt" # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 439625b750a6f1..814c9a9978d84a 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -38,8 +38,8 @@ class LocalsTest < TestCase "3.3-3.3/return_in_sclass.txt", # Leaving these out until they are supported by parse.y. - "3.5/leading_logical.txt", - "3.5/endless_methods_command_call.txt", + "4.0/leading_logical.txt", + "4.0/endless_methods_command_call.txt", "command_method_call_2.txt" ] diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb index 6169940bebc558..0e008ffb088c4f 100644 --- a/test/prism/ractor_test.rb +++ b/test/prism/ractor_test.rb @@ -64,7 +64,7 @@ def with_ractor(*arguments, &block) else ractor = ignore_warnings { Ractor.new(*arguments, &block) } - # Somewhere in the Ruby 3.5.* series, Ractor#take was removed and + # Somewhere in the Ruby 4.0.* series, Ractor#take was removed and # Ractor#value was added. puts(ractor.respond_to?(:value) ? ractor.value : ractor.take) end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 3104369d3eadd3..1629c36b382b53 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -69,10 +69,10 @@ class ParserTest < TestCase "3.4/circular_parameters.txt", # Cannot yet handling leading logical operators. - "3.5/leading_logical.txt", + "4.0/leading_logical.txt", - # Ruby >= 3.5 specific syntax - "3.5/endless_methods_command_call.txt", + # Ruby >= 4.0 specific syntax + "4.0/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", @@ -172,6 +172,7 @@ def test_non_prism_builder_class_deprecated if RUBY_VERSION >= "3.3" def test_current_parser_for_current_ruby major, minor = current_major_minor.split(".") + return if major == "3" && minor == "5" # TODO: Remove once ruby-dev becomes 4.0 # Let's just hope there never is a Ruby 3.10 or similar expected = major.to_i * 10 + minor.to_i assert_equal(expected, Translation::ParserCurrent.new.version) diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 12c854aea660be..400139acc03d01 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -9,7 +9,7 @@ class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ # Not yet supported. - "3.5/leading_logical.txt", + "4.0/leading_logical.txt", # Ripper incorrectly attributes the block to the keyword. "seattlerb/block_break.txt", @@ -40,7 +40,7 @@ class RipperTest < TestCase "3.4/circular_parameters.txt", # https://bugs.ruby-lang.org/issues/17398#note-12 - "3.5/endless_methods_command_call.txt", + "4.0/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 42a888be820924..fae5077e20f334 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -84,8 +84,8 @@ class RubyParserTest < TestCase "3.4/circular_parameters.txt", - "3.5/endless_methods_command_call.txt", - "3.5/leading_logical.txt", + "4.0/endless_methods_command_call.txt", + "4.0/leading_logical.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index faf6117668a404..c03f70b2cdab18 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -230,7 +230,7 @@ def self.windows? end # All versions that prism can parse - SYNTAX_VERSIONS = %w[3.3 3.4 3.5 4.0] + SYNTAX_VERSIONS = %w[3.3 3.4 4.0] # Returns an array of ruby versions that a given filepath should test against: # test.txt # => all available versions @@ -256,6 +256,7 @@ def current_major_minor if RUBY_VERSION >= "3.3.0" def test_all_syntax_versions_present + return if RUBY_VERSION.start_with?("3.5") # TODO: Remove once ruby-dev becomes 4.0 assert_include(SYNTAX_VERSIONS, current_major_minor) end end From 3b588dab91f13b32b54cb1b9dedb9691e732c981 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 7 Nov 2025 12:12:25 -0800 Subject: [PATCH 0903/2435] Don't modify fstrings in rb_str_tmp_frozen_no_embed_acquire [Bug #21671] --- string.c | 6 +++++- test/ruby/test_string.rb | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/string.c b/string.c index 472159ffe470e1..96a693eddcd138 100644 --- a/string.c +++ b/string.c @@ -612,6 +612,10 @@ rb_gc_free_fstring(VALUE obj) { ASSERT_vm_locking_with_barrier(); + RUBY_ASSERT(FL_TEST(obj, RSTRING_FSTR)); + RUBY_ASSERT(OBJ_FROZEN(obj)); + RUBY_ASSERT(!FL_TEST(obj, STR_SHARED)); + rb_concurrent_set_delete_by_identity(fstring_table_obj, obj); RB_DEBUG_COUNTER_INC(obj_str_fstr); @@ -1554,7 +1558,7 @@ rb_str_tmp_frozen_no_embed_acquire(VALUE orig) * allocated. If the string is shared then the shared root must be * embedded, so we want to create a copy. If the string is a shared root * then it must be embedded, so we want to create a copy. */ - if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT)) { + if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT | RSTRING_FSTR)) { RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), capa, sizeof(char), TERM_LEN(orig)); memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), capa); } diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 99ab85ddbe9faa..e2384e15007d2b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3441,6 +3441,15 @@ def test_uminus_no_freeze_not_bare assert_equal(false, str.frozen?) end + def test_uminus_no_embed_gc + pad = "a"*2048 + ("aa".."zz").each do |c| + fstr = -(c + pad).freeze + File.open(IO::NULL, "w").write(fstr) + end + GC.start + end + def test_ord assert_equal(97, S("a").ord) assert_equal(97, S("abc").ord) From c65f8b6370f5ef692148786b5e8de54c4d14c132 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Nov 2025 10:17:07 +0900 Subject: [PATCH 0904/2435] Prevent the path for copied extension from GC --- load.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/load.c b/load.c index 69f1d365f269a0..02b75ceb2bd4e3 100644 --- a/load.c +++ b/load.c @@ -1207,7 +1207,10 @@ load_ext(VALUE path, VALUE fname) loaded = rb_box_local_extension(box->box_object, fname, path); } rb_scope_visibility_set(METHOD_VISI_PUBLIC); - return (VALUE)dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); + void *handle = dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); + RB_GC_GUARD(loaded); + RB_GC_GUARD(fname); + return (VALUE)handle; } static VALUE From 4aea392e69488209d1f3c7b671aadafc0d2d76b3 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 7 Nov 2025 22:47:32 -0500 Subject: [PATCH 0905/2435] ZJIT: Specialize String#setbyte for fixnum case (#14927) --- zjit/src/codegen.rs | 30 +++++++++++ zjit/src/cruby_methods.rs | 26 +++++++++ zjit/src/hir.rs | 48 +++++++++++++++++ zjit/src/hir/opt_tests.rs | 110 ++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 4 ++ 5 files changed, 218 insertions(+) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 3ef93ee3d971ec..68e8ad8966d9f5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -348,6 +348,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio let out_opnd = match insn { &Insn::Const { val: Const::Value(val) } => gen_const_value(val), &Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val), + &Insn::Const { val: Const::CInt64(val) } => gen_const_long(val), Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), @@ -365,6 +366,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state), Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)), &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), + Insn::StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum(asm, opnd!(string), opnd!(index), opnd!(value)), Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), @@ -407,12 +409,15 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)), &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), &Insn::BoxFixnum { val, state } => gen_box_fixnum(jit, asm, opnd!(val), &function.frame_state(state)), + &Insn::UnboxFixnum { val } => gen_unbox_fixnum(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::GuardNotFrozen { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &function.frame_state(*state)), + &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), + &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. @@ -571,6 +576,10 @@ fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd { } } +fn gen_unbox_fixnum(asm: &mut Assembler, val: Opnd) -> Opnd { + asm.rshift(val, Opnd::UImm(1)) +} + /// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs. /// We generate this instruction with level=0 only when the local variable is on the heap, so we /// can't optimize the level=0 case using the SP register. @@ -642,6 +651,18 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: & val } +fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { + asm.cmp(left, right); + asm.jge(side_exit(jit, state, SideExitReason::GuardLess)); + left +} + +fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { + asm.cmp(left, right); + asm.jl(side_exit(jit, state, SideExitReason::GuardGreaterEq)); + left +} + fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { unsafe extern "C" { fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; @@ -1047,6 +1068,10 @@ fn gen_const_cptr(val: *const u8) -> lir::Opnd { Opnd::const_ptr(val) } +fn gen_const_long(val: i64) -> lir::Opnd { + Opnd::Imm(val) +} + /// Compile a basic block argument fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd { // Allocate a register or a stack slot @@ -2302,6 +2327,11 @@ fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) -> asm_ccall!(asm, rb_str_getbyte, string, index) } +fn gen_string_setbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd, value: Opnd) -> Opnd { + // rb_str_setbyte is not leaf, but we guard types and index ranges in HIR + asm_ccall!(asm, rb_str_setbyte, string, index, value) +} + fn gen_string_append(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: Opnd, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); asm_ccall!(asm, rb_str_buf_append, string, val) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 37d75f45974e2a..7fba755a6facda 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -199,6 +199,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "getbyte", inline_string_getbyte); + annotate!(rb_cString, "setbyte", inline_string_setbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cString, "==", inline_string_eq); @@ -338,6 +339,31 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir None } +fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[index, value] = args else { return None; }; + if fun.likely_a(index, types::Fixnum, state) && fun.likely_a(value, types::Fixnum, state) { + let index = fun.coerce_to(block, index, types::Fixnum, state); + let value = fun.coerce_to(block, value, types::Fixnum, state); + + let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); + let len = fun.push_insn(block, hir::Insn::LoadField { + recv, + id: ID!(len), + offset: RUBY_OFFSET_RSTRING_LEN as i32, + return_type: types::CInt64, + }); + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); + let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); + let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state }); + let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); + // String#setbyte returns the fixnum provided as its `value` argument back to the caller. + Some(value) + } else { + None + } +} + fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[other] = args else { return None; }; // Inline only StringExact << String, which matches original type check from diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7c180929abef9f..04f1291e2d33de 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -471,6 +471,8 @@ pub enum SideExitReason { GuardShape(ShapeId), GuardBitEquals(Const), GuardNotFrozen, + GuardLess, + GuardGreaterEq, PatchPoint(Invariant), CalleeSideExit, ObjToStringFallback, @@ -600,6 +602,7 @@ pub enum Insn { StringConcat { strings: Vec, state: InsnId }, /// Call rb_str_getbyte with known-Fixnum index StringGetbyteFixnum { string: InsnId, index: InsnId }, + StringSetbyteFixnum { string: InsnId, index: InsnId, value: InsnId }, StringAppend { recv: InsnId, other: InsnId, state: InsnId }, /// Combine count stack values into a regexp @@ -659,6 +662,7 @@ pub enum Insn { BoxBool { val: InsnId }, /// Convert a C `long` to a Ruby `Fixnum`. Side exit on overflow. BoxFixnum { val: InsnId, state: InsnId }, + UnboxFixnum { val: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, @@ -844,6 +848,10 @@ pub enum Insn { GuardBlockParamProxy { level: u32, state: InsnId }, /// Side-exit if val is frozen. GuardNotFrozen { val: InsnId, state: InsnId }, + /// Side-exit if left is not greater than or equal to right (both operands are C long). + GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, + /// Side-exit if left is not less than right (both operands are C long). + GuardLess { left: InsnId, right: InsnId, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -1036,6 +1044,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::StringGetbyteFixnum { string, index, .. } => { write!(f, "StringGetbyteFixnum {string}, {index}") } + Insn::StringSetbyteFixnum { string, index, value, .. } => { + write!(f, "StringSetbyteFixnum {string}, {index}, {value}") + } Insn::StringAppend { recv, other, .. } => { write!(f, "StringAppend {recv}, {other}") } @@ -1068,6 +1079,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsBitNotEqual { left, right } => write!(f, "IsBitNotEqual {left}, {right}"), Insn::BoxBool { val } => write!(f, "BoxBool {val}"), Insn::BoxFixnum { val, .. } => write!(f, "BoxFixnum {val}"), + Insn::UnboxFixnum { val } => write!(f, "UnboxFixnum {val}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") } @@ -1148,6 +1160,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::GuardNotFrozen { val, .. } => write!(f, "GuardNotFrozen {val}"), + Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), + Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, @@ -1702,6 +1716,7 @@ impl Function { &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, &StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) }, &StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) }, + &StringSetbyteFixnum { string, index, value } => StringSetbyteFixnum { string: find!(string), index: find!(index), value: find!(value) }, &StringAppend { recv, other, state } => StringAppend { recv: find!(recv), other: find!(other), state: find!(state) }, &ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state }, &Test { val } => Test { val: find!(val) }, @@ -1711,6 +1726,7 @@ impl Function { &IsBitNotEqual { left, right } => IsBitNotEqual { left: find!(left), right: find!(right) }, &BoxBool { val } => BoxBool { val: find!(val) }, &BoxFixnum { val, state } => BoxFixnum { val: find!(val), state: find!(state) }, + &UnboxFixnum { val } => UnboxFixnum { val: find!(val) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, &IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) }, @@ -1720,6 +1736,8 @@ impl Function { &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, &GuardNotFrozen { val, state } => GuardNotFrozen { val: find!(val), state }, + &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, + &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, &FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state }, &FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state }, @@ -1906,10 +1924,12 @@ impl Function { Insn::IsBitNotEqual { .. } => types::CBool, Insn::BoxBool { .. } => types::BoolExact, Insn::BoxFixnum { .. } => types::Fixnum, + Insn::UnboxFixnum { .. } => types::CInt64, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass), + Insn::StringSetbyteFixnum { .. } => types::Fixnum, Insn::StringAppend { .. } => types::StringExact, Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, @@ -1932,6 +1952,8 @@ impl Function { Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), Insn::GuardNotFrozen { val, .. } => self.type_of(*val), + Insn::GuardLess { left, .. } => self.type_of(*left), + Insn::GuardGreaterEq { left, .. } => self.type_of(*left), Insn::FixnumAdd { .. } => types::Fixnum, Insn::FixnumSub { .. } => types::Fixnum, Insn::FixnumMult { .. } => types::Fixnum, @@ -3284,6 +3306,11 @@ impl Function { worklist.push_back(string); worklist.push_back(index); } + &Insn::StringSetbyteFixnum { string, index, value } => { + worklist.push_back(string); + worklist.push_back(index); + worklist.push_back(value); + } &Insn::StringAppend { recv, other, state } => { worklist.push_back(recv); worklist.push_back(other); @@ -3316,6 +3343,16 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + &Insn::GuardGreaterEq { left, right, state } => { + worklist.push_back(left); + worklist.push_back(right); + worklist.push_back(state); + } + &Insn::GuardLess { left, right, state } => { + worklist.push_back(left); + worklist.push_back(right); + worklist.push_back(state); + } Insn::Snapshot { state } => { worklist.extend(&state.stack); worklist.extend(&state.locals); @@ -3430,6 +3467,7 @@ impl Function { &Insn::GetSpecialNumber { state, .. } | &Insn::ObjectAllocClass { state, .. } | &Insn::SideExit { state, .. } => worklist.push_back(state), + &Insn::UnboxFixnum { val } => worklist.push_back(val), } } @@ -3792,6 +3830,7 @@ impl Function { } Insn::BoxBool { val } => self.assert_subtype(insn_id, val, types::CBool), Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64), + Insn::UnboxFixnum { val } => self.assert_subtype(insn_id, val, types::Fixnum), Insn::SetGlobal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::GetIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), Insn::SetIvar { self_val, val, .. } => { @@ -3867,9 +3906,18 @@ impl Function { } Insn::GuardShape { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::GuardNotFrozen { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), + Insn::GuardLess { left, right, .. } | Insn::GuardGreaterEq { left, right, .. } => { + self.assert_subtype(insn_id, left, types::CInt64)?; + self.assert_subtype(insn_id, right, types::CInt64) + }, Insn::StringGetbyteFixnum { string, index } => { self.assert_subtype(insn_id, string, types::String)?; self.assert_subtype(insn_id, index, types::Fixnum) + }, + Insn::StringSetbyteFixnum { string, index, value } => { + self.assert_subtype(insn_id, string, types::String)?; + self.assert_subtype(insn_id, index, types::Fixnum)?; + self.assert_subtype(insn_id, value, types::Fixnum) } _ => Ok(()), } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 9b757433e1b032..543e1b5287297e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5801,6 +5801,116 @@ mod hir_opt_tests { "); } + #[test] + fn test_optimize_string_setbyte_fixnum() { + eval(r#" + def test(s, idx, val) + s.setbyte(idx, val) + end + test("foo", 0, 127) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v29:StringExact = GuardType v13, StringExact + v30:Fixnum = GuardType v14, Fixnum + v31:Fixnum = GuardType v15, Fixnum + v32:CInt64 = UnboxFixnum v30 + v33:CInt64 = LoadField v29, :len@0x1038 + v34:CInt64 = GuardLess v32, v33 + v35:CInt64[0] = Const CInt64(0) + v36:CInt64 = GuardGreaterEq v34, v35 + v37:StringExact = GuardNotFrozen v29 + v38:Fixnum = StringSetbyteFixnum v37, v30, v31 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_optimize_string_subclass_setbyte_fixnum() { + eval(r#" + class MyString < String + end + def test(s, idx, val) + s.setbyte(idx, val) + end + test(MyString.new('foo'), 0, 127) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(MyString@0x1000, setbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyString@0x1000) + v29:StringSubclass[class_exact:MyString] = GuardType v13, StringSubclass[class_exact:MyString] + v30:Fixnum = GuardType v14, Fixnum + v31:Fixnum = GuardType v15, Fixnum + v32:CInt64 = UnboxFixnum v30 + v33:CInt64 = LoadField v29, :len@0x1038 + v34:CInt64 = GuardLess v32, v33 + v35:CInt64[0] = Const CInt64(0) + v36:CInt64 = GuardGreaterEq v34, v35 + v37:StringSubclass[class_exact:MyString] = GuardNotFrozen v29 + v38:Fixnum = StringSetbyteFixnum v37, v30, v31 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_do_not_optimize_string_setbyte_non_fixnum() { + eval(r#" + def test(s, idx, val) + s.setbyte(idx, val) + end + test("foo", 0, 3.14) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v29:StringExact = GuardType v13, StringExact + v30:BasicObject = CCallWithFrame setbyte@0x1038, v29, v14, v15 + CheckInterrupts + Return v30 + "); + } + #[test] fn test_specialize_string_empty() { eval(r#" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 6b3f9b5ce8179b..099609b90a8624 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -146,6 +146,8 @@ make_counters! { exit_guard_int_equals_failure, exit_guard_shape_failure, exit_guard_not_frozen_failure, + exit_guard_less_failure, + exit_guard_greater_eq_failure, exit_patchpoint_bop_redefined, exit_patchpoint_method_redefined, exit_patchpoint_stable_constant_names, @@ -390,6 +392,8 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, GuardNotFrozen => exit_guard_not_frozen_failure, + GuardLess => exit_guard_less_failure, + GuardGreaterEq => exit_guard_greater_eq_failure, CalleeSideExit => exit_callee_side_exit, ObjToStringFallback => exit_obj_to_string_fallback, Interrupt => exit_interrupt, From aacd9f206a57375df38e1b2552da37a68d77e1e1 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 7 Nov 2025 21:55:41 -0800 Subject: [PATCH 0906/2435] Notify CI failures of Cygwin --- .github/workflows/cygwin.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 39a98cd30ec9d1..1941309b1a52a8 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -63,3 +63,9 @@ jobs: timeout-minutes: 30 run: make -j4 V=1 shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} + + - uses: ./.github/actions/slack + with: + label: Cygwin + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} From 3a5e7e9580fd8cea5f9b53dda377e61faeebe621 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Nov 2025 13:20:01 +0900 Subject: [PATCH 0907/2435] Constify --- box.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/box.c b/box.c index bdb80a788cb59a..0ea74182849269 100644 --- a/box.c +++ b/box.c @@ -509,7 +509,7 @@ copy_ext_file_error(char *message, size_t size) } #else static const char * -copy_ext_file_error(char *message, size_t size, int copy_retvalue, char *src_path, char *dst_path) +copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path) { switch (copy_retvalue) { case 1: @@ -532,7 +532,7 @@ copy_ext_file_error(char *message, size_t size, int copy_retvalue, char *src_pat #endif static int -copy_ext_file(char *src_path, char *dst_path) +copy_ext_file(const char *src_path, const char *dst_path) { #if defined(_WIN32) int rvalue; @@ -664,7 +664,7 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) { char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN]; int copy_error, wrote; - char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); + const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); rb_box_t *box = rb_get_box_t(box_value); fname_without_suffix(fname_ptr, fname2, sizeof(fname2)); From 4365c4fb6bd816fc8e9a9dcabacde47c5264d713 Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Sat, 8 Nov 2025 21:52:39 +1000 Subject: [PATCH 0908/2435] [DOC] Replace 3.5 reference in NEWS.md --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3306e3e5f6e713..9bd78dd6b3a168 100644 --- a/NEWS.md +++ b/NEWS.md @@ -314,8 +314,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * ZJIT * Add an experimental method-based JIT compiler. Use `--enable-zjit` on `configure` to enable the `--zjit` support. - * As of Ruby 3.5.0-preview2, ZJIT is not yet ready for speeding up most benchmarks. - Please refrain from evaluating ZJIT just yet. Stay tuned for the Ruby 3.5 release. + * As of Ruby 4.0.0-preview1, ZJIT is not yet ready for speeding up most benchmarks. + Please refrain from evaluating ZJIT just yet. Stay tuned for the Ruby 4.0 release. * RJIT * `--rjit` is removed. We will move the implementation of the third-party JIT API to the [ruby/rjit](https://github.com/ruby/rjit) repository. From 75d25a42e6052b5f5d90d2d4022ae996fee5913a Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 8 Nov 2025 09:08:09 -0600 Subject: [PATCH 0909/2435] [DOC] Tweaks for String#to_c --- complex.c | 162 +++++++++++++++++++++++++++++++++++---- doc/string.rb | 1 + doc/syntax/literals.rdoc | 4 +- 3 files changed, 148 insertions(+), 19 deletions(-) diff --git a/complex.c b/complex.c index d6daee307cc170..54c8cd0d6e8ad5 100644 --- a/complex.c +++ b/complex.c @@ -2229,28 +2229,156 @@ string_to_c_strict(VALUE self, int raise) * call-seq: * to_c -> complex * - * Returns +self+ interpreted as a Complex object; - * leading whitespace and trailing garbage are ignored: - * - * '9'.to_c # => (9+0i) - * '2.5'.to_c # => (2.5+0i) - * '2.5/1'.to_c # => ((5/2)+0i) - * '-3/2'.to_c # => ((-3/2)+0i) - * '-i'.to_c # => (0-1i) - * '45i'.to_c # => (0+45i) - * '3-4i'.to_c # => (3-4i) - * '-4e2-4e-2i'.to_c # => (-400.0-0.04i) - * '-0.0-0.0i'.to_c # => (-0.0-0.0i) - * '1/2+3/4i'.to_c # => ((1/2)+(3/4)*i) - * '1.0@0'.to_c # => (1+0.0i) + * Returns a Complex object: + * parses the leading substring of +self+ + * to extract two numeric values that become the coordinates of the complex object. + * + * The substring is interpreted as containing + * either rectangular coordinates (real and imaginary parts) + * or polar coordinates (magnitude and angle parts), + * depending on an included or implied "separator" character: + * + * - '+', '-', or no separator: rectangular coordinates. + * - '@': polar coordinates. + * + * In Brief + * + * In these examples, we use method Complex#rect to display rectangular coordinates, + * and method Complex#polar to display polar coordinates. + * + * # Rectangular coordinates. + * + * # Real-only: no separator; imaginary part is zero. + * '9'.to_c.rect # => [9, 0] # Integer. + * '-9'.to_c.rect # => [-9, 0] # Integer (negative). + * '2.5'.to_c.rect # => [2.5, 0] # Float. + * '1.23e-14'.to_c.rect # => [1.23e-14, 0] # Float with exponent. + * '2.5/1'.to_c.rect # => [(5/2), 0] # Rational. + * + * # Some things are ignored. + * 'foo1'.to_c.rect # => [0, 0] # Unparsed entire substring. + * '1foo'.to_c.rect # => [1, 0] # Unparsed trailing substring. + * ' 1 '.to_c.rect # => [1, 0] # Leading and trailing whitespace. + * * + * # Imaginary only: trailing 'i' required; real part is zero. + * '9i'.to_c.rect # => [0, 9] + * '-9i'.to_c.rect # => [0, -9] + * '2.5i'.to_c.rect # => [0, 2.5] + * '1.23e-14i'.to_c.rect # => [0, 1.23e-14] + * '2.5/1i'.to_c.rect # => [0, (5/2)] + * + * # Real and imaginary; '+' or '-' separator; trailing 'i' required. + * '2+3i'.to_c.rect # => [2, 3] + * '-2-3i'.to_c.rect # => [-2, -3] + * '2.5+3i'.to_c.rect # => [2.5, 3] + * '2.5+3/2i'.to_c.rect # => [2.5, (3/2)] + * + * # Polar coordinates; '@' separator; magnitude required. + * '1.0@0'.to_c.polar # => [1.0, 0.0] + * '1.0@'.to_c.polar # => [1.0, 0.0] + * "1.0@#{Math::PI}".to_c.polar # => [1.0, 3.141592653589793] + * "1.0@#{Math::PI/2}".to_c.polar # => [1.0, 1.5707963267948966] + * + * Parsed Values + * + * The parsing may be thought of as searching for numeric literals + * embedded in the substring. + * + * This section shows how the method parses numeric values from leading substrings. + * The examples show real-only or imaginary-only parsing; + * the parsing is the same for each part. + * + * '1foo'.to_c # => (1+0i) # Ignores trailing unparsed characters. + * ' 1 '.to_c # => (1+0i) # Ignores leading and trailing whitespace. + * 'x1'.to_c # => (0+0i) # Finds no leading numeric. + * + * # Integer literal embedded in the substring. + * '1'.to_c # => (1+0i) + * '-1'.to_c # => (-1+0i) + * '1i'.to_c # => (0+1i) + * + * # Integer literals that don't work. + * '0b100'.to_c # => (0+0i) # Not parsed as binary. + * '0o100'.to_c # => (0+0i) # Not parsed as octal. + * '0d100'.to_c # => (0+0i) # Not parsed as decimal. + * '0x100'.to_c # => (0+0i) # Not parsed as hexadecimal. + * '010'.to_c # => (10+0i) # Not parsed as octal. + * + * # Float literals: + * '3.14'.to_c # => (3.14+0i) + * '3.14i'.to_c # => (0+3.14i) + * '1.23e4'.to_c # => (12300.0+0i) + * '1.23e+4'.to_c # => (12300.0+0i) + * '1.23e-4'.to_c # => (0.000123+0i) + * + * # Rational literals: + * '1/2'.to_c # => ((1/2)+0i) + * '-1/2'.to_c # => ((-1/2)+0i) + * '1/2r'.to_c # => ((1/2)+0i) + * '-1/2r'.to_c # => ((-1/2)+0i) + * + * Rectangular Coordinates + * + * With separator '+' or '-', + * or with no separator, + * interprets the values as rectangular coordinates: real and imaginary. + * + * With no separator, assigns a single value to either the real or the imaginary part: + * + * ''.to_c # => (0+0i) # Defaults to zero. + * '1'.to_c # => (1+0i) # Real (no trailing 'i'). + * '1i'.to_c # => (0+1i) # Imaginary (trailing 'i'). + * 'i'.to_c # => (0+1i) # Special case (imaginary 1). + * + * With separator '+', both parts positive (or zero): + * + * # Without trailing 'i'. + * '+'.to_c # => (0+0i) # No values: defaults to zero. + * '+1'.to_c # => (1+0i) # Value after '+': real only. + * '1+'.to_c # => (1+0i) # Value before '+': real only. + * '2+1'.to_c # => (2+0i) # Values before and after '+': real and imaginary. + * # With trailing 'i'. + * '+1i'.to_c # => (0+1i) # Value after '+': imaginary only. + * '2+i'.to_c # => (2+1i) # Value before '+': real and imaginary 1. + * '2+1i'.to_c # => (2+1i) # Values before and after '+': real and imaginary. + * + * With separator '-', negative imaginary part: + * + * # Without trailing 'i'. + * '-'.to_c # => (0+0i) # No values: defaults to zero. + * '-1'.to_c # => (-1+0i) # Value after '-': negative real, zero imaginary. + * '1-'.to_c # => (1+0i) # Value before '-': positive real, zero imaginary. + * '2-1'.to_c # => (2+0i) # Values before and after '-': positive real, zero imaginary. + * # With trailing 'i'. + * '-1i'.to_c # => (0-1i) # Value after '-': negative real, zero imaginary. + * '2-i'.to_c # => (2-1i) # Value before '-': positive real, negative imaginary. + * '2-1i'.to_c # => (2-1i) # Values before and after '-': positive real, negative imaginary. + * + * Note that the suffixed character 'i' + * may instead be one of 'I', 'j', or 'J', + * with the same effect. + * + * Polar Coordinates + * + * With separator '@') + * interprets the values as polar coordinates: magnitude and angle. + * + * '2@'.to_c.polar # => [2, 0.0] # Value before '@': magnitude only. + * # Values before and after '@': magnitude and angle. + * '2@1'.to_c.polar # => [2.0, 1.0] * "1.0@#{Math::PI/2}".to_c # => (0.0+1i) * "1.0@#{Math::PI}".to_c # => (-1+0.0i) + * # Magnitude not given: defaults to zero. + * '@'.to_c.polar # => [0, 0.0] + * '@1'.to_c.polar # => [0, 0.0] * - * Returns \Complex zero if the string cannot be converted: + * '1.0@0'.to_c # => (1+0.0i) * - * 'ruby'.to_c # => (0+0i) + * Note that in all cases, the suffixed character 'i' + * may instead be one of 'I', 'j', 'J', + * with the same effect. * - * See Kernel#Complex. + * See {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE string_to_c(VALUE self) diff --git a/doc/string.rb b/doc/string.rb index 3f3461573f5ca3..b3d5886b8dd718 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -399,6 +399,7 @@ # - #hex: Returns the integer value of the leading characters, interpreted as hexadecimal digits. # - #oct: Returns the integer value of the leading characters, interpreted as octal digits. # - #ord: Returns the integer ordinal of the first character in +self+. +# - #to_c: Returns the complex value of leading characters, interpreted as a complex number. # - #to_i: Returns the integer value of leading characters, interpreted as an integer. # - #to_f: Returns the floating-point value of leading characters, interpreted as a floating-point number. # diff --git a/doc/syntax/literals.rdoc b/doc/syntax/literals.rdoc index 46bb7673f3e040..87a891bf2d3c05 100644 --- a/doc/syntax/literals.rdoc +++ b/doc/syntax/literals.rdoc @@ -3,7 +3,7 @@ Literals create objects you can use in your program. Literals include: * {Boolean and Nil Literals}[#label-Boolean+and+Nil+Literals] -* {Number Literals}[#label-Number+Literals] +* {Numeric Literals}[#label-Numeric+Literals] * {Integer Literals}[#label-Integer+Literals] * {Float Literals}[#label-Float+Literals] @@ -36,7 +36,7 @@ Literals create objects you can use in your program. Literals include: +true+ is a true value. All objects except +nil+ and +false+ evaluate to a true value in conditional expressions. -== Number Literals +== \Numeric Literals === \Integer Literals From 79eed1158de4164f72def32093aa31bcac339639 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 8 Nov 2025 16:27:26 -0600 Subject: [PATCH 0910/2435] [DOC] Tweaks for String#to_i (#15036) --- string.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index 96a693eddcd138..531b1c5594381b 100644 --- a/string.c +++ b/string.c @@ -7027,12 +7027,13 @@ rb_str_include(VALUE str, VALUE arg) * to_i(base = 10) -> integer * * Returns the result of interpreting leading characters in +self+ - * as an integer in the given +base+ (which must be in (0, 2..36)): + * as an integer in the given +base+; + * +base+ must be either +0+ or in range (2..36): * * '123456'.to_i # => 123456 * '123def'.to_i(16) # => 1195503 * - * With +base+ zero, string +object+ may contain leading characters + * With +base+ zero given, string +object+ may contain leading characters * to specify the actual base: * * '123def'.to_i(0) # => 123 @@ -7052,6 +7053,7 @@ rb_str_include(VALUE str, VALUE arg) * 'abcdef'.to_i # => 0 * '2'.to_i(2) # => 0 * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE From 827f11fce3c92dce80ebc5dc80be9acdafae1173 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 8 Nov 2025 17:13:11 -0500 Subject: [PATCH 0911/2435] Move rb_gc_verify_shareable to gc.c rb_gc_verify_shareable is not GC implementation specific so it should live in gc.c. --- gc.c | 78 ++++++++++++++++++++++++++++++++++++-------- gc/default/default.c | 61 +--------------------------------- gc/gc.h | 1 + gc/gc_impl.h | 1 - gc/mmtk/mmtk.c | 6 ---- 5 files changed, 67 insertions(+), 80 deletions(-) diff --git a/gc.c b/gc.c index 92ba02809abb90..24d897d4bc5ee4 100644 --- a/gc.c +++ b/gc.c @@ -621,7 +621,6 @@ typedef struct gc_function_map { void (*config_set)(void *objspace_ptr, VALUE hash); void (*stress_set)(void *objspace_ptr, VALUE flag); VALUE (*stress_get)(void *objspace_ptr); - bool (*checking_shareable)(void *objspace_ptr); // Object allocation VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); size_t (*obj_slot_size)(VALUE obj); @@ -796,7 +795,6 @@ ruby_modular_gc_init(void) load_modular_gc_func(config_get); load_modular_gc_func(stress_set); load_modular_gc_func(stress_get); - load_modular_gc_func(checking_shareable); // Object allocation load_modular_gc_func(new_obj); load_modular_gc_func(obj_slot_size); @@ -877,7 +875,6 @@ ruby_modular_gc_init(void) # define rb_gc_impl_config_set rb_gc_functions.config_set # define rb_gc_impl_stress_set rb_gc_functions.stress_set # define rb_gc_impl_stress_get rb_gc_functions.stress_get -# define rb_gc_impl_checking_shareable rb_gc_functions.checking_shareable // Object allocation # define rb_gc_impl_new_obj rb_gc_functions.new_obj # define rb_gc_impl_obj_slot_size rb_gc_functions.obj_slot_size @@ -2808,19 +2805,12 @@ mark_m_tbl(void *objspace, struct rb_id_table *tbl) } } -bool -rb_gc_checking_shareable(void) -{ - return rb_gc_impl_checking_shareable(rb_gc_get_objspace()); -} - - static enum rb_id_table_iterator_result mark_const_entry_i(VALUE value, void *objspace) { const rb_const_entry_t *ce = (const rb_const_entry_t *)value; - if (!rb_gc_impl_checking_shareable(objspace)) { + if (!rb_gc_checking_shareable()) { gc_mark_internal(ce->value); gc_mark_internal(ce->file); // TODO: ce->file should be shareable? } @@ -3085,7 +3075,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE box_value, void *a } mark_m_tbl(objspace, RCLASSEXT_M_TBL(ext)); - if (!rb_gc_impl_checking_shareable(objspace)) { + if (!rb_gc_checking_shareable()) { // unshareable gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext)); } @@ -3156,7 +3146,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_CLASS: if (FL_TEST_RAW(obj, FL_SINGLETON) && - !rb_gc_impl_checking_shareable(objspace)) { + !rb_gc_checking_shareable()) { gc_mark_internal(RCLASS_ATTACHED_OBJECT(obj)); } // Continue to the shared T_CLASS/T_MODULE @@ -5441,6 +5431,68 @@ rb_gc_rp(VALUE obj) rp(obj); } +struct check_shareable_data { + VALUE parent; + long err_count; +}; + +static void +check_shareable_i(const VALUE child, void *ptr) +{ + struct check_shareable_data *data = (struct check_shareable_data *)ptr; + + if (!rb_gc_obj_shareable_p(child)) { + fprintf(stderr, "(a) "); + rb_gc_rp(data->parent); + fprintf(stderr, "(b) "); + rb_gc_rp(child); + fprintf(stderr, "check_shareable_i: shareable (a) -> unshareable (b)\n"); + + data->err_count++; + rb_bug("!! violate shareable constraint !!"); + } +} + +static bool gc_checking_shareable = false; + +static void +gc_verify_shareable(void *objspace, VALUE obj, void *data) +{ + // while gc_checking_shareable is true, + // other Ractors should not run the GC, until the flag is not local. + // TODO: remove VM locking if the flag is Ractor local + + unsigned int lev = RB_GC_VM_LOCK(); + { + gc_checking_shareable = true; + rb_objspace_reachable_objects_from(obj, check_shareable_i, (void *)data); + gc_checking_shareable = false; + } + RB_GC_VM_UNLOCK(lev); +} + +// TODO: only one level (non-recursive) +void +rb_gc_verify_shareable(VALUE obj) +{ + rb_objspace_t *objspace = rb_gc_get_objspace(); + struct check_shareable_data data = { + .parent = obj, + .err_count = 0, + }; + gc_verify_shareable(objspace, obj, &data); + + if (data.err_count > 0) { + rb_bug("rb_gc_verify_shareable"); + } +} + +bool +rb_gc_checking_shareable(void) +{ + return gc_checking_shareable; +} + /* * Document-module: ObjectSpace * diff --git a/gc/default/default.c b/gc/default/default.c index 6045cec59887a0..f84303e02f500e 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -491,7 +491,6 @@ typedef struct rb_objspace { unsigned int during_minor_gc : 1; unsigned int during_incremental_marking : 1; unsigned int measure_gc : 1; - unsigned int check_shareable : 1; } flags; rb_event_flag_t hook_events; @@ -1459,13 +1458,6 @@ RVALUE_WHITE_P(rb_objspace_t *objspace, VALUE obj) return !RVALUE_MARKED(objspace, obj); } -bool -rb_gc_impl_checking_shareable(void *objspace_ptr) -{ - rb_objspace_t *objspace = objspace_ptr; - return objspace->flags.check_shareable; -} - bool rb_gc_impl_gc_enabled_p(void *objspace_ptr) { @@ -4980,55 +4972,6 @@ check_children_i(const VALUE child, void *ptr) } } -static void -check_shareable_i(const VALUE child, void *ptr) -{ - struct verify_internal_consistency_struct *data = (struct verify_internal_consistency_struct *)ptr; - - if (!rb_gc_obj_shareable_p(child)) { - fprintf(stderr, "(a) "); - rb_gc_rp(data->parent); - fprintf(stderr, "(b) "); - rb_gc_rp(child); - fprintf(stderr, "check_shareable_i: shareable (a) -> unshareable (b)\n"); - - data->err_count++; - rb_bug("!! violate shareable constraint !!"); - } -} - -static void -gc_verify_shareable(rb_objspace_t *objspace, VALUE obj, void *data) -{ - // while objspace->flags.check_shareable is true, - // other Ractors should not run the GC, until the flag is not local. - // TODO: remove VM locking if the flag is Ractor local - - unsigned int lev = RB_GC_VM_LOCK(); - { - objspace->flags.check_shareable = true; - rb_objspace_reachable_objects_from(obj, check_shareable_i, (void *)data); - objspace->flags.check_shareable = false; - } - RB_GC_VM_UNLOCK(lev); -} - -// TODO: only one level (non-recursive) -void -rb_gc_verify_shareable(VALUE obj) -{ - rb_objspace_t *objspace = rb_gc_get_objspace(); - struct verify_internal_consistency_struct data = { - .parent = obj, - .err_count = 0, - }; - gc_verify_shareable(objspace, obj, &data); - - if (data.err_count > 0) { - rb_bug("rb_gc_verify_shareable"); - } -} - static int verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, struct verify_internal_consistency_struct *data) @@ -5061,7 +5004,7 @@ verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, } if (!is_marking(objspace) && rb_gc_obj_shareable_p(obj)) { - gc_verify_shareable(objspace, obj, data); + rb_gc_verify_shareable(obj); } if (is_incremental_marking(objspace)) { @@ -6716,7 +6659,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ gc_enter_count(event); if (RB_UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0"); if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace); - GC_ASSERT(!objspace->flags.check_shareable); during_gc = TRUE; RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace)); @@ -6730,7 +6672,6 @@ static inline void gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev) { GC_ASSERT(during_gc != 0); - GC_ASSERT(!objspace->flags.check_shareable); rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_EXIT); diff --git a/gc/gc.h b/gc/gc.h index 89219eb7934692..f1cf8038e0fe51 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -63,6 +63,7 @@ const char *rb_obj_info(VALUE obj); size_t rb_obj_memsize_of(VALUE obj); bool ruby_free_at_exit_p(void); void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data); +void rb_gc_verify_shareable(VALUE); MODULAR_GC_FN unsigned int rb_gc_vm_lock(const char *file, int line); MODULAR_GC_FN void rb_gc_vm_unlock(unsigned int lev, const char *file, int line); diff --git a/gc/gc_impl.h b/gc/gc_impl.h index 2c05fe6cff0671..3250483639e775 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -54,7 +54,6 @@ GC_IMPL_FN void rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag); GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); -GC_IMPL_FN bool rb_gc_impl_checking_shareable(void *objspace_ptr); // Object allocation GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 5861f5e70fdb66..9dd3129e01664e 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1260,12 +1260,6 @@ rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj) rb_gc_impl_copy_finalizer(objspace_ptr, dest, obj); } -bool -rb_gc_impl_checking_shareable(void *ptr) -{ - return false; -} - // GC Identification const char * From 529dd8d76efbe655fabce8933852504851266b2b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 8 Nov 2025 19:49:27 -0800 Subject: [PATCH 0912/2435] cygwin.yml: Disable a broken step https://github.com/ruby/ruby/actions/runs/19201736990/job/54890646022 --- .github/workflows/cygwin.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 1941309b1a52a8..95242138274f30 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -53,11 +53,12 @@ jobs: ./configure --disable-install-doc shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} - - name: Extract bundled gems - run: | - make ruby -j5 - make extract-gems - shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} + # This fails with: tool/outdate-bundled-gems.rb:3:in 'Kernel#require': cannot load such file -- rubygems (LoadError) + #- name: Extract bundled gems + # run: | + # make ruby -j5 + # make extract-gems + # shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0} - name: make all timeout-minutes: 30 From a88d7718f427acdd040f7b978e0514f0fea0b1d8 Mon Sep 17 00:00:00 2001 From: git Date: Sun, 9 Nov 2025 06:50:26 +0000 Subject: [PATCH 0913/2435] Update bundled gems list as of 2025-11-09 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9bd78dd6b3a168..37bf4bcd2372c2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -218,7 +218,7 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.26.0 +* minitest 5.26.1 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 114e2f4e6c2de5..67385af0a6222b 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.26.0 https://github.com/minitest/minitest +minitest 5.26.1 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.1 https://github.com/test-unit/test-unit From a4dff09be79b52288a47658964d25e5aa84fc960 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 15:17:56 +0900 Subject: [PATCH 0914/2435] [Bug #21673] Fix resolving refined module-defined method A method defined in a module has no `defined_class`, use the ICLASS for it as the `defined_class`. --- test/ruby/test_refinement.rb | 12 ++++++++++++ vm_method.c | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 6ce434790be13b..bd61ccfe1d579a 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -2712,6 +2712,18 @@ def test INPUT end + def test_refined_module_method + m = Module.new { + x = Module.new {def qux;end} + refine(x) {def qux;end} + break x + } + extend m + meth = method(:qux) + assert_equal m, meth.owner + assert_equal :qux, meth.name + end + private def eval_using(mod, s) diff --git a/vm_method.c b/vm_method.c index 506a2919b77cd0..bf04140cb7fd19 100644 --- a/vm_method.c +++ b/vm_method.c @@ -2005,7 +2005,12 @@ resolve_refined_method(VALUE refinements, const rb_method_entry_t *me, VALUE *de tmp_me = me->def->body.refined.orig_me; if (tmp_me) { - if (defined_class_ptr) *defined_class_ptr = tmp_me->defined_class; + if (!tmp_me->defined_class) { + VM_ASSERT_TYPE(tmp_me->owner, T_MODULE); + } + else if (defined_class_ptr) { + *defined_class_ptr = tmp_me->defined_class; + } return tmp_me; } From f08030e9dccf38d9ea5c9505203fe26484dc28d8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 20:16:22 +0900 Subject: [PATCH 0915/2435] [ruby/optparse] [DOC] A constant for compatibility https://github.com/ruby/optparse/commit/0125cb4918 --- lib/optparse.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 0a3d04b09a6813..c66c84270a1517 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -427,7 +427,8 @@ class OptionParser # The version string VERSION = "0.8.0" - Version = VERSION # for compatibility + # An alias for compatibility + Version = VERSION # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From 44d19928b6b6af945b79415fcff4ffc731e99173 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 17:59:42 +0900 Subject: [PATCH 0916/2435] [ruby/net-protocol] Exclude unneeded files https://github.com/ruby/net-protocol/commit/8286341e8c --- lib/net/net-protocol.gemspec | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/net/net-protocol.gemspec b/lib/net/net-protocol.gemspec index f9fd83f12b05f2..2d911a966cbf05 100644 --- a/lib/net/net-protocol.gemspec +++ b/lib/net/net-protocol.gemspec @@ -25,9 +25,8 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.require_paths = ["lib"] spec.add_dependency "timeout" From 2bf82c627494e785737037bbaf9a6b98f3c6354c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 18:15:58 +0900 Subject: [PATCH 0917/2435] [ruby/net-protocol] [DOC] Suppress documentation for internals https://github.com/ruby/net-protocol/commit/6c5734dc1e --- lib/net/protocol.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 197ea090890f76..1443f3e8b71966 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -57,6 +57,7 @@ def ssl_socket_connect(s, timeout) end + # :stopdoc: class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end @@ -66,6 +67,7 @@ class ProtoAuthError < ProtocolError; end class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError + # :startdoc: ## # OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot @@ -78,6 +80,7 @@ class OpenTimeout < Timeout::Error; end # response cannot be read within the read_timeout. class ReadTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -97,6 +100,7 @@ def message # response cannot be written within the write_timeout. Not raised on Windows. class WriteTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -484,6 +488,7 @@ def buffer_filling(buf, src) # The writer adapter class # class WriteAdapter + # :stopdoc: def initialize(writer) @writer = writer end From 001890b851cd058b2b9980709139070af27e3612 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 18:38:47 +0900 Subject: [PATCH 0918/2435] [ruby/net-http] Exclude unneeded files https://github.com/ruby/net-http/commit/89e1ecb556 --- lib/net/http/net-http.gemspec | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec index 70528d58cb34af..e4d26c9b3a634c 100644 --- a/lib/net/http/net-http.gemspec +++ b/lib/net/http/net-http.gemspec @@ -30,9 +30,8 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.require_paths = ["lib"] From 155cdce539a95b510a80a19e3840cde6b293cd4d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 18:50:23 +0900 Subject: [PATCH 0919/2435] [ruby/net-http] [DOC] Suppress documentation for internals https://github.com/ruby/net-http/commit/e4d80bd609 --- lib/net/http.rb | 35 ++++++++++------- lib/net/http/exceptions.rb | 3 +- lib/net/http/generic_request.rb | 2 + lib/net/http/header.rb | 12 ++++-- lib/net/http/requests.rb | 16 +++++++- lib/net/http/response.rb | 3 +- lib/net/http/responses.rb | 67 +++++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 21 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 6cacc556037993..43e3349ac0e1b1 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1321,6 +1321,9 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets wheter the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1632,6 +1635,21 @@ def start # :yield: http self end + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1758,20 +1776,6 @@ def on_connect end private :on_connect - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish - end - def do_finish @started = false @socket.close if @socket @@ -1915,6 +1919,7 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) require 'cgi/escape' @@ -2397,6 +2402,8 @@ def send_entity(path, data, initheader, dest, type, &block) res end + # :stopdoc: + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index ceec8f7b0a3822..4342cfc0ef4be3 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index c92004e5578b38..d9a4c4fef05ad5 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -264,6 +264,8 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index f6c36f1b5e78e7..5dcdcc7d74051c 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ def add_field(key, val) end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ def add_field(key, val) ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ def connection_close? false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index e58057adf1664c..939d413f91961c 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Net::HTTP#get: sends +GET+ request, returns response object. # class Net::HTTP::Get < Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ class Net::HTTP::Get < Net::HTTPRequest # - Net::HTTP#head: sends +HEAD+ request, returns response object. # class Net::HTTP::Head < Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ class Net::HTTP::Head < Net::HTTPRequest # - Net::HTTP#post: sends +POST+ request, returns response object. # class Net::HTTP::Post < Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ class Net::HTTP::Post < Net::HTTPRequest # - Net::HTTP#put: sends +PUT+ request, returns response object. # class Net::HTTP::Put < Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ class Net::HTTP::Put < Net::HTTPRequest # - Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Net::HTTP::Delete < Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ class Net::HTTP::Delete < Net::HTTPRequest # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Net::HTTP::Options < Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ class Net::HTTP::Options < Net::HTTPRequest # - Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Net::HTTP::Trace < Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ class Net::HTTP::Trace < Net::HTTPRequest # - Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Net::HTTP::Patch < Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ class Net::HTTP::Patch < Net::HTTPRequest # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Net::HTTP::Propfind < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ class Net::HTTP::Propfind < Net::HTTPRequest # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Net::HTTP::Proppatch < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ class Net::HTTP::Proppatch < Net::HTTPRequest # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Net::HTTP::Mkcol < Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ class Net::HTTP::Mkcol < Net::HTTPRequest # - Net::HTTP#copy: sends +COPY+ request, returns response object. # class Net::HTTP::Copy < Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ class Net::HTTP::Copy < Net::HTTPRequest # - Net::HTTP#move: sends +MOVE+ request, returns response object. # class Net::HTTP::Move < Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ class Net::HTTP::Move < Net::HTTPRequest # - Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Net::HTTP::Lock < Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ class Net::HTTP::Lock < Net::HTTPRequest # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Net::HTTP::Unlock < Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 40de96386804f3..8804a99c9e5269 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -153,6 +153,7 @@ def read_new(sock) #:nodoc: internal use only end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 5e2f8ce1aa176a..2fa01e2c0c3f33 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -5,6 +5,7 @@ module Net class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +20,7 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +36,7 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +52,7 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +67,7 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +82,7 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +100,7 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +118,7 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +135,7 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +154,7 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +172,7 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +190,7 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +208,7 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +228,7 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +246,7 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +265,7 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +284,7 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +302,7 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +322,7 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +340,7 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +358,7 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +377,7 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +395,7 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +413,7 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +431,7 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +448,7 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +466,7 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +483,7 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +500,7 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +517,7 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +534,7 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +552,7 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +569,7 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +586,7 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +604,7 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +621,7 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +638,7 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +656,7 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +674,7 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +692,7 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +710,7 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +727,7 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +745,7 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +764,7 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +781,7 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +799,7 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +820,7 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +837,7 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +853,7 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +870,7 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +890,7 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +907,7 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +924,7 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +942,7 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +960,7 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +981,7 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +999,7 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1017,7 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1035,7 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1053,7 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1071,7 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1088,7 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1105,7 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1122,7 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1140,7 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,12 +1157,14 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Net::HTTPInformation, '2' => Net::HTTPSuccess, From 6cac64348725b4961a70597a4aa202f9e0c3c120 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 11:24:42 +0900 Subject: [PATCH 0920/2435] [ruby/win32-registry] [DOC] Convert documents from RD2 https://github.com/ruby/win32-registry/commit/8680eedd43 --- ext/win32/lib/win32/registry.rb | 251 +++++++++++++++++--------------- 1 file changed, 134 insertions(+), 117 deletions(-) diff --git a/ext/win32/lib/win32/registry.rb b/ext/win32/lib/win32/registry.rb index 8e2c8b2e1ab135..734d6987a2518a 100644 --- a/ext/win32/lib/win32/registry.rb +++ b/ext/win32/lib/win32/registry.rb @@ -2,83 +2,74 @@ require 'fiddle/import' module Win32 - -=begin rdoc -= Win32 Registry - -win32/registry is registry accessor library for Win32 platform. -It uses importer to call Win32 Registry APIs. - -== example - Win32::Registry::HKEY_CURRENT_USER.open('SOFTWARE\foo') do |reg| - value = reg['foo'] # read a value - value = reg['foo', Win32::Registry::REG_SZ] # read a value with type - type, value = reg.read('foo') # read a value - reg['foo'] = 'bar' # write a value - reg['foo', Win32::Registry::REG_SZ] = 'bar' # write a value with type - reg.write('foo', Win32::Registry::REG_SZ, 'bar') # write a value - - reg.each_value { |name, type, data| ... } # Enumerate values - reg.each_key { |key, wtime| ... } # Enumerate subkeys - - reg.delete_value(name) # Delete a value - reg.delete_key(name) # Delete a subkey - reg.delete_key(name, true) # Delete a subkey recursively - end - -= Reference - -== Win32::Registry class - ---- info - ---- num_keys - ---- max_key_length - ---- num_values - ---- max_value_name_length - ---- max_value_length - ---- descriptor_length - ---- wtime - Returns an item of key information. - -=== constants ---- HKEY_CLASSES_ROOT - ---- HKEY_CURRENT_USER - ---- HKEY_LOCAL_MACHINE - ---- HKEY_PERFORMANCE_DATA - ---- HKEY_CURRENT_CONFIG - ---- HKEY_DYN_DATA - - Win32::Registry object whose key is predefined key. -For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/predefined_keys.asp] article. - -=end rdoc - + # :stopdoc: WCHAR = Encoding::UTF_16LE WCHAR_NUL = "\0".encode(WCHAR).freeze WCHAR_CR = "\r".encode(WCHAR).freeze WCHAR_SIZE = WCHAR_NUL.bytesize LOCALE = Encoding::UTF_8 + # :startdoc: + + # win32/registry is registry accessor library for Win32 platform. + # It uses importer to call Win32 Registry APIs. + # + # == example + # Win32::Registry::HKEY_CURRENT_USER.open('SOFTWARE\foo') do |reg| + # value = reg['foo'] # read a value + # value = reg['foo', Win32::Registry::REG_SZ] # read a value with type + # type, value = reg.read('foo') # read a value + # reg['foo'] = 'bar' # write a value + # reg['foo', Win32::Registry::REG_SZ] = 'bar' # write a value with type + # reg.write('foo', Win32::Registry::REG_SZ, 'bar') # write a value + # + # reg.each_value { |name, type, data| ... } # Enumerate values + # reg.each_key { |key, wtime| ... } # Enumerate subkeys + # + # reg.delete_value(name) # Delete a value + # reg.delete_key(name) # Delete a subkey + # reg.delete_key(name, true) # Delete a subkey recursively + # end + # + # == Predefined keys + # + # * +HKEY_CLASSES_ROOT+ + # * +HKEY_CURRENT_USER+ + # * +HKEY_LOCAL_MACHINE+ + # * +HKEY_PERFORMANCE_DATA+ + # * +HKEY_CURRENT_CONFIG+ + # * +HKEY_DYN_DATA+ + # + # Win32::Registry object whose key is predefined key. + # For detail, see the article[https://learn.microsoft.com/en-us/windows/win32/sysinfo/predefined-keys]. + # + # == Value types + # + # * +REG_NONE+ + # * +REG_SZ+ + # * +REG_EXPAND_SZ+ + # * +REG_BINARY+ + # * +REG_DWORD+ + # * +REG_DWORD_BIG_ENDIAN+ + # * +REG_LINK+ + # * +REG_MULTI_SZ+ + # * +REG_RESOURCE_LIST+ + # * +REG_FULL_RESOURCE_DESCRIPTOR+ + # * +REG_RESOURCE_REQUIREMENTS_LIST+ + # * +REG_QWORD+ + # + # For detail, see the article[https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types]. + # class Registry + # :stopdoc: + # # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/registry.asp]. # # --- HKEY_* # - # Predefined key ((*handle*)). + # Predefined key *handle*. # These are Integer, not Win32::Registry. # # --- REG_* @@ -100,6 +91,7 @@ class Registry # If the key is created newly or opened existing key. # See also Registry#disposition method. module Constants + # :stopdoc: HKEY_CLASSES_ROOT = 0x80000000 HKEY_CURRENT_USER = 0x80000001 HKEY_LOCAL_MACHINE = 0x80000002 @@ -115,7 +107,6 @@ module Constants REG_EXPAND_SZ = 2 REG_BINARY = 3 REG_DWORD = 4 - REG_DWORD_LITTLE_ENDIAN = 4 REG_DWORD_BIG_ENDIAN = 5 REG_LINK = 6 REG_MULTI_SZ = 7 @@ -163,16 +154,23 @@ module Constants end include Constants include Enumerable + # :startdoc: # # Error # class Error < ::StandardError + # :stopdoc: module Kernel32 extend Fiddle::Importer dlload "kernel32.dll" end FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall + # :startdoc: + + # new(code) -> error object + # + # Initializes the message for Win32 API error +code+. def initialize(code) @code = code buff = WCHAR_NUL * 1024 @@ -190,6 +188,8 @@ def initialize(code) end super msg end + + # Win32 API error code. attr_reader :code end @@ -197,6 +197,7 @@ def initialize(code) # Predefined Keys # class PredefinedKey < Registry + # :stopdoc: def initialize(hkey, keyname) @hkey = Fiddle::Pointer.new(hkey) @parent = nil @@ -224,6 +225,7 @@ def class # Win32 APIs # module API + # :stopdoc: include Constants extend Fiddle::Importer dlload "advapi32.dll" @@ -367,12 +369,14 @@ def QueryInfoKey(hkey) unpackdw(secdescs), unpackqw(wtime) ] end end + # :startdoc: # - # Replace %\w+% into the environment value of what is contained between the %'s + # Replace %-enclosed substrings in +str+ into the + # environment value of what is contained between the %s. # This method is used for REG_EXPAND_SZ. # - # For detail, see expandEnvironmentStrings[https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsa] \Win32 \API. + # For detail, see ExpandEnvironmentStrings[https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsw] \Win32 \API. # def self.expand_environ(str) str.gsub(Regexp.compile("%([^%]+)%".encode(str.encoding))) { @@ -392,21 +396,21 @@ def self.expand_environ(str) end.freeze # - # Convert registry type value to readable string. + # Convert registry type value +type+ to readable string. # def self.type2name(type) @@type2name[type] || type.to_s end # - # Convert 64-bit FILETIME integer into Time object. + # Convert 64-bit FILETIME integer +wtime+ into Time object. # def self.wtime2time(wtime) Time.at((wtime - 116444736000000000) / 10000000) end # - # Convert Time object or Integer object into 64-bit FILETIME. + # Convert Time object or Integer object +time+ into 64-bit FILETIME. # def self.time2wtime(time) time.to_i * 10000000 + 116444736000000000 @@ -418,16 +422,19 @@ def self.time2wtime(time) private_class_method :new # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) + # call-seq: + # open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) + # open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) { |reg| ... } # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) { |reg| ... } + # Open the registry key +subkey+ under +key+. + # +key+ is Win32::Registry object of parent key. + # You can use {predefined key}[rdoc-ref:Win32::Registry@Predefined+keys] +HKEY_+*. + # +desired+ and +opt+ is access mask and key option. # - # Open the registry key subkey under key. - # key is Win32::Registry object of parent key. - # You can use predefined key HKEY_* (see Constants) - # desired and opt is access mask and key option. # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/regopenkeyex.asp]. - # If block is given, the key is closed automatically. + # + # If block is given, the key +reg+ is yielded and closed + # automatically after the block exists. def self.open(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) subkey = subkey.chomp('\\') newkey = API.OpenKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) @@ -444,17 +451,19 @@ def self.open(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) end # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) - # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) { |reg| ... } + # call-seq: + # create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) + # create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) { |reg| ... } # - # Create or open the registry key subkey under key. - # You can use predefined key HKEY_* (see Constants) + # Create or open the registry key +subkey+ under +key+. + # You can use {predefined key}[rdoc-ref:Win32::Registry@Predefined+keys] +HKEY_+*. + # +desired+ and +opt+ is access mask and key option. # - # If subkey is already exists, key is opened and Registry#created? + # If +subkey+ is already exists, key is opened and Registry#created? # method will return false. # - # If block is given, the key is closed automatically. + # If block is given, the key +reg+ is yielded and closed + # automatically after the block exists. # def self.create(hkey, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) newkey, disp = API.CreateKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) @@ -476,7 +485,9 @@ def self.create(hkey, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVE @@final = proc { |hkey| proc { API.CloseKey(hkey[0]) if hkey[0] } } # - # initialize + # :nodoc: + # + # Use self.open, self.create, #open and #create. # def initialize(hkey, parent, keyname, disposition) @hkey = Fiddle::Pointer.new(hkey) @@ -501,7 +512,7 @@ def hkey end # - # Returns if key is created ((*newly*)). + # Returns +true+ if key is created *newly*. # (see Registry.create) -- basically you call create # then when you call created? on the instance returned # it will tell if it was successful or not @@ -518,7 +529,7 @@ def open? end # - # Full path of key such as 'HKEY_CURRENT_USER\SOFTWARE\foo\bar'. + # Full path of key such as 'HKEY_CURRENT_USER\SOFTWARE\foo\bar'. # def name parent = self @@ -529,6 +540,9 @@ def name name end + # + # Retruns inspected string + # def inspect "\#" end @@ -541,14 +555,14 @@ def _dump(depth) end # - # Same as Win32::Registry.open (self, subkey, desired, opt) + # Same as Win32::Registry.open(self, subkey, desired, opt) # def open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED, &blk) self.class.open(self, subkey, desired, opt, &blk) end # - # Same as Win32::Registry.create (self, subkey, desired, opt) + # Same as Win32::Registry.create(self, subkey, desired, opt) # def create(subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED, &blk) self.class.create(self, subkey, desired, opt, &blk) @@ -571,7 +585,7 @@ def close # For each value it yields key, type and data. # # key is a String which contains name of key. - # type is a type constant kind of Win32::Registry::REG_* + # type is a type constant kind of +Win32::Registry::REG_+* # data is the value of this key. # def each_value @@ -640,21 +654,23 @@ def keys end # Read a registry value named name and return array of - # [ type, data ]. - # When name is nil, the `default' value is read. - # type is value type. (see Win32::Registry::Constants module) - # data is value data, its class is: - # :REG_SZ, REG_EXPAND_SZ + # [ type, data ]. + # When name is +nil+, the `default' value is read. + # + # +type+ is {value type}[rdoc-ref:Win32::Registry@Value+types]. + # + # +data+ is value data, its class is: + # REG_SZ, REG_EXPAND_SZ:: # String - # :REG_MULTI_SZ + # REG_MULTI_SZ:: # Array of String - # :REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD + # REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD:: # Integer - # :REG_BINARY, REG_NONE + # REG_BINARY, REG_NONE:: # String (contains binary data) # - # When rtype is specified, the value type must be included by - # rtype array, or TypeError is raised. + # When _rtype_ is specified, the value type must be included by + # _rtype_ array, or +TypeError+ is raised. def read(name, *rtype) type, data = API.QueryValue(@hkey, name) unless rtype.empty? or rtype.include?(type) @@ -687,9 +703,9 @@ def read(name, *rtype) # If the value type is REG_EXPAND_SZ, returns value data whose environment # variables are replaced. # If the value type is neither REG_SZ, REG_MULTI_SZ, REG_DWORD, - # REG_DWORD_BIG_ENDIAN, nor REG_QWORD, TypeError is raised. + # REG_DWORD_BIG_ENDIAN, nor REG_QWORD, +TypeError+ is raised. # - # The meaning of rtype is the same as for the #read method. + # The meaning of _rtype_ is the same as for the #read method. # def [](name, *rtype) type, data = read(name, *rtype) @@ -706,7 +722,7 @@ def [](name, *rtype) # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) # registry value named name. # - # If the values type does not match, TypeError is raised. + # If the values type does not match, +TypeError+ is raised. def read_s(name) read(name, REG_SZ)[1] end @@ -715,7 +731,7 @@ def read_s(name) # Read a REG_SZ or REG_EXPAND_SZ registry value named name. # # If the value type is REG_EXPAND_SZ, environment variables are replaced. - # Unless the value type is REG_SZ or REG_EXPAND_SZ, TypeError is raised. + # Unless the value type is REG_SZ or REG_EXPAND_SZ, +TypeError+ is raised. # def read_s_expand(name) type, data = read(name, REG_SZ, REG_EXPAND_SZ) @@ -730,7 +746,7 @@ def read_s_expand(name) # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) # registry value named name. # - # If the values type does not match, TypeError is raised. + # If the values type does not match, +TypeError+ is raised. # def read_i(name) read(name, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD)[1] @@ -740,7 +756,7 @@ def read_i(name) # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) # registry value named name. # - # If the values type does not match, TypeError is raised. + # If the values type does not match, +TypeError+ is raised. # def read_bin(name) read(name, REG_BINARY)[1] @@ -750,7 +766,7 @@ def read_bin(name) # Write data to a registry value named name. # When name is nil, write to the `default' value. # - # type is type value. (see Registry::Constants module) + # +type+ is {value type}[rdoc-ref:Win32::Registry@Value+types]. # Class of data must be same as which #read # method returns. # @@ -779,11 +795,12 @@ def write(name, type, data) # # If wtype is specified, the value type is it. # Otherwise, the value type is depend on class of value: - # :Integer + # + # Integer:: # REG_DWORD - # :String + # String:: # REG_SZ - # :Array + # Array:: # REG_MULTI_SZ # def []=(name, rtype, value = nil) @@ -880,19 +897,19 @@ def flush # # Returns key information as Array of: - # :num_keys + # num_keys:: # The number of subkeys. - # :max_key_length + # max_key_length:: # Maximum length of name of subkeys. - # :num_values + # num_values:: # The number of values. - # :max_value_name_length + # max_value_name_length:: # Maximum length of name of values. - # :max_value_length + # max_value_length:: # Maximum length of value of values. - # :descriptor_length + # descriptor_length:: # Length of security descriptor. - # :wtime + # wtime:: # Last write time as FILETIME(64-bit integer) # # For detail, see RegQueryInfoKey[http://msdn.microsoft.com/library/en-us/sysinfo/base/regqueryinfokey.asp] Win32 API. From f23fab66c294ab39f822587432f1e8d1b6da2e3a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:21:06 +0900 Subject: [PATCH 0921/2435] [ruby/ipaddr] gemspec files does not need to be included in spec.files https://github.com/ruby/ipaddr/commit/ada04589fe --- lib/ipaddr.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ipaddr.gemspec b/lib/ipaddr.gemspec index 5719f83fc44c46..cabc9161ba71c0 100644 --- a/lib/ipaddr.gemspec +++ b/lib/ipaddr.gemspec @@ -29,7 +29,7 @@ Both IPv4 and IPv6 are supported. spec.homepage = "https://github.com/ruby/ipaddr" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.files = ["LICENSE.txt", "README.md", "ipaddr.gemspec", "lib/ipaddr.rb"] + spec.files = ["LICENSE.txt", "README.md", "lib/ipaddr.rb"] spec.require_paths = ["lib"] spec.required_ruby_version = ">= 2.4" From 79342334e0d1be3101568a0eb695414e5b0d3801 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:23:05 +0900 Subject: [PATCH 0922/2435] [ruby/ipaddr] [DOC] Stop documentation for internals https://github.com/ruby/ipaddr/commit/cb9f561883 --- lib/ipaddr.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index fca75deff95ebf..513b7778a92f0b 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,6 +40,7 @@ # p ipaddr3 #=> # class IPAddr + # The version string VERSION = "1.2.7" # 32 bit mask for IPv4 @@ -353,7 +354,7 @@ def ipv4_compat? _ipv4_compat? end - def _ipv4_compat? + def _ipv4_compat? # :nodoc: if !ipv6? || (@addr >> 32) != 0 return false end @@ -545,6 +546,7 @@ def zone_id=(zid) end protected + # :stopdoc: def begin_addr @addr & @mask_addr @@ -560,6 +562,7 @@ def end_addr raise AddressFamilyError, "unsupported address family" end end + #:startdoc: # Set +@addr+, the internal stored ip address, to given +addr+. The # parameter +addr+ is validated using the first +family+ member, @@ -701,6 +704,7 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC) end end + # :stopdoc: def coerce_other(other) case other when IPAddr @@ -812,7 +816,7 @@ class Socket < BasicSocket class << IPSocket private - def valid_v6?(addr) + def valid_v6?(addr) # :nodoc: case addr when IPAddr::RE_IPV6ADDRLIKE_FULL if $2 From 063aea8ce47162e8cf0abc48e00bdbbc5dfcbea2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:39:38 +0900 Subject: [PATCH 0923/2435] [ruby/resolv] Exclude unneeded files https://github.com/ruby/resolv/commit/60bf151a1d --- lib/resolv.gemspec | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec index bfa2f9ff31f957..66aed34e01bef7 100644 --- a/lib/resolv.gemspec +++ b/lib/resolv.gemspec @@ -21,9 +21,8 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] From d79ff407825352953293ea7c399cd3a94aef021f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:30:03 +0900 Subject: [PATCH 0924/2435] [ruby/resolv] Require win32/resolv just once And Use Win32::Resolv instead of a constant `WINDOWS`. https://github.com/ruby/resolv/commit/b2c775cd80 --- lib/resolv.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index fce5092d0e9cb7..b7ed1dcb7aff18 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -174,21 +174,18 @@ class ResolvError < StandardError; end class ResolvTimeout < Timeout::Error; end - WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ - private_constant :WINDOWS - ## # Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if WINDOWS + if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ begin require 'win32/resolv' unless defined?(Win32::Resolv) - DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL + hosts = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end end - DefaultFileName ||= '/etc/hosts' + DefaultFileName = hosts || '/etc/hosts' ## # Creates a new Resolv::Hosts, using +filename+ for its data source. @@ -1022,8 +1019,7 @@ def Config.parse_resolv_conf(filename) def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename Config.parse_resolv_conf(filename) - elsif WINDOWS - require 'win32/resolv' unless defined?(Win32::Resolv) + elsif defined?(Win32::Resolv) search, nameserver = Win32::Resolv.get_resolv_info config_hash = {} config_hash[:nameserver] = nameserver if nameserver From bf29ba452aa946bc6791b9d04b69003ae41c42cb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:32:23 +0900 Subject: [PATCH 0925/2435] [ruby/resolv] [DOC] Fix documentations https://github.com/ruby/resolv/commit/d8b8d36f63 --- lib/resolv.rb | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index b7ed1dcb7aff18..34749afb674af7 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -34,6 +34,7 @@ class Resolv + # The version string VERSION = "0.6.3" ## @@ -185,6 +186,7 @@ class Hosts rescue LoadError end end + # The default file name for host names DefaultFileName = hosts || '/etc/hosts' ## @@ -523,6 +525,8 @@ def each_resource(name, typeclass, &proc) } end + # :stopdoc: + def fetch_resource(name, typeclass) lazy_initialize truncated = {} @@ -2923,15 +2927,21 @@ class HTTPS < ServiceBinding class IPv4 - ## - # Regular expression IPv4 addresses must match. - Regex256 = /0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? - |[3-9][0-9]?/x + |[3-9][0-9]?/x # :nodoc: + + ## + # Regular expression IPv4 addresses must match. Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ + ## + # Creates a new IPv4 address from +arg+ which may be: + # + # IPv4:: returns +arg+. + # String:: +arg+ must match the IPv4::Regex constant + def self.create(arg) case arg when IPv4 @@ -3247,6 +3257,8 @@ module LOC class Size + # Regular expression LOC size must match. + Regex = /^(\d+\.*\d*)[m]$/ ## @@ -3272,6 +3284,7 @@ def self.create(arg) end end + # Internal use; use self.create. def initialize(scalar) @scalar = scalar end @@ -3309,6 +3322,8 @@ def hash # :nodoc: class Coord + # Regular expression LOC Coord must match. + Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ ## @@ -3338,6 +3353,7 @@ def self.create(arg) end end + # Internal use; use self.create. def initialize(coordinates,orientation) unless coordinates.kind_of?(String) raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") @@ -3400,6 +3416,8 @@ def hash # :nodoc: class Alt + # Regular expression LOC Alt must match. + Regex = /^([+-]*\d+\.*\d*)[m]$/ ## @@ -3425,6 +3443,7 @@ def self.create(arg) end end + # Internal use; use self.create. def initialize(altitude) @altitude = altitude end From 4170b70d32c3165da92eb258556f3ddf5b9767f3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 22:06:30 +0900 Subject: [PATCH 0926/2435] [ruby/net-http] [DOC] Fix too stopped documentations https://github.com/ruby/net-http/commit/58685b78ab --- lib/net/http.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/net/http.rb b/lib/net/http.rb index 43e3349ac0e1b1..7efb468e782e06 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1825,6 +1825,8 @@ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ss } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1948,6 +1950,7 @@ def edit_path(path) path end end + # :startdoc: # # HTTP operations From 953fee11b39197f4ab6170c4c0a48a396be2b4cc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 19:09:28 +0900 Subject: [PATCH 0927/2435] [DOC] Document of Coverage.line_stub from NEWS-2.6.0 --- ext/coverage/lib/coverage.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/coverage/lib/coverage.rb b/ext/coverage/lib/coverage.rb index f1923ef366cbf3..4bd20e22cbadbe 100644 --- a/ext/coverage/lib/coverage.rb +++ b/ext/coverage/lib/coverage.rb @@ -1,6 +1,11 @@ require "coverage.so" module Coverage + # call-seq: + # line_stub(file) -> array + # + # A simple helper function that creates the "stub" of line coverage + # from a given source code. def self.line_stub(file) lines = File.foreach(file).map { nil } iseqs = [RubyVM::InstructionSequence.compile_file(file)] From 87c39667bf881527d4ddfe1c558fc98adb9543a0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:13:01 +0900 Subject: [PATCH 0928/2435] [DOC] ObjectSpace.trace_object_allocations_debug_start --- ext/objspace/object_tracing.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index 0156642ef25897..63eec6739320f9 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -411,6 +411,13 @@ object_allocations_reporter(FILE *out, void *ptr) fprintf(out, "== object_allocations_reporter: END\n"); } +/* + * call-seq: trace_object_allocations_debug_start + * + * Starts tracing object allocations for GC debugging. + * If you encounter the BUG "... is T_NONE" (and so on) on your + * application, please try this method at the beginning of your app. + */ static VALUE trace_object_allocations_debug_start(VALUE self) { From 26fc938b9f81a023494ca356583c7c1a86972619 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:48:30 +0900 Subject: [PATCH 0929/2435] [DOC] Sort undocumented items by locations --- common.mk | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common.mk b/common.mk index fe8ef3b19efe43..093fea6b3fb56c 100644 --- a/common.mk +++ b/common.mk @@ -624,15 +624,20 @@ html: PHONY main srcs-doc @echo Generating RDoc HTML files $(Q) $(RDOC) --op "$(HTMLOUT)" $(RDOC_GEN_OPTS) $(RDOCFLAGS) . +RDOC_COVERAGE_EXCLUDES = -x ^ext/json -x ^ext/openssl -x ^ext/psych \ + -x ^lib/bundler -x ^lib/rubygems \ + -x ^lib/did_you_mean -x ^lib/error_highlight -x ^lib/syntax_suggest + rdoc-coverage: PHONY main srcs-doc @echo Generating RDoc coverage report - $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) . + $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) $(RDOC_COVERAGE_EXCLUDES) . undocumented: PHONY main srcs-doc - $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) . | \ + $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) $(RDOC_COVERAGE_EXCLUDES) . | \ sed -n \ -e '/^ *# in file /{' -e 's///;N;s/\n/: /p' -e '}' \ - -e 's/^ *\(.*[^ ]\) *# in file \(.*\)/\2: \1/p' | sort + -e 's/^ *\(.*[^ ]\) *# in file \(.*\)/\2: \1/p' | \ + sort -t: -k1,1 -k2n,2 RDOCBENCHOUT=/tmp/rdocbench From af610e107c3a7515228843eb6b1c5978f2ee2685 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 21:58:53 +0900 Subject: [PATCH 0930/2435] Revert "[ruby/net-http] [DOC] Suppress documentation for internals" This reverts commit 155cdce539a95b510a80a19e3840cde6b293cd4d. --- lib/net/http.rb | 35 +++++++---------- lib/net/http/exceptions.rb | 3 +- lib/net/http/generic_request.rb | 2 - lib/net/http/header.rb | 12 ++---- lib/net/http/requests.rb | 16 +------- lib/net/http/response.rb | 3 +- lib/net/http/responses.rb | 67 --------------------------------- 7 files changed, 21 insertions(+), 117 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 7efb468e782e06..9511f19f268730 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1321,9 +1321,6 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass - - # Sets wheter the proxy uses SSL; - # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1635,21 +1632,6 @@ def start # :yield: http self end - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish - end - - # :stopdoc: def do_start connect @started = true @@ -1776,6 +1758,20 @@ def on_connect end private :on_connect + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + def do_finish @started = false @socket.close if @socket @@ -1921,7 +1917,6 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private - # :stopdoc: def unescape(value) require 'cgi/escape' @@ -2405,8 +2400,6 @@ def send_entity(path, data, initheader, dest, type, &block) res end - # :stopdoc: - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index 4342cfc0ef4be3..ceec8f7b0a3822 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions # :nodoc: + module HTTPExceptions def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,7 +12,6 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end - # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index d9a4c4fef05ad5..c92004e5578b38 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -264,8 +264,6 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private - # :stopdoc: - class Chunker #:nodoc: def initialize(sock) @sock = sock diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index 5dcdcc7d74051c..f6c36f1b5e78e7 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -179,9 +179,7 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader - # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 - # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -269,7 +267,6 @@ def add_field(key, val) end end - # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -297,7 +294,6 @@ def add_field(key, val) ary.push val end end - # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -494,7 +490,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) # :nodoc: + def capitalize(name) name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -961,12 +957,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) # :nodoc: + def basic_encode(account, password) 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode - # Returns whether the HTTP session is to be closed. +# Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -974,7 +970,7 @@ def connection_close? false end - # Returns whether the HTTP session is to be kept alive. +# Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index 939d413f91961c..e58057adf1664c 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -29,7 +29,6 @@ # - Net::HTTP#get: sends +GET+ request, returns response object. # class Net::HTTP::Get < Net::HTTPRequest - # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -61,7 +60,6 @@ class Net::HTTP::Get < Net::HTTPRequest # - Net::HTTP#head: sends +HEAD+ request, returns response object. # class Net::HTTP::Head < Net::HTTPRequest - # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -97,7 +95,6 @@ class Net::HTTP::Head < Net::HTTPRequest # - Net::HTTP#post: sends +POST+ request, returns response object. # class Net::HTTP::Post < Net::HTTPRequest - # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -133,7 +130,6 @@ class Net::HTTP::Post < Net::HTTPRequest # - Net::HTTP#put: sends +PUT+ request, returns response object. # class Net::HTTP::Put < Net::HTTPRequest - # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -166,7 +162,6 @@ class Net::HTTP::Put < Net::HTTPRequest # - Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Net::HTTP::Delete < Net::HTTPRequest - # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -198,7 +193,6 @@ class Net::HTTP::Delete < Net::HTTPRequest # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Net::HTTP::Options < Net::HTTPRequest - # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -230,7 +224,6 @@ class Net::HTTP::Options < Net::HTTPRequest # - Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Net::HTTP::Trace < Net::HTTPRequest - # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -265,7 +258,6 @@ class Net::HTTP::Trace < Net::HTTPRequest # - Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Net::HTTP::Patch < Net::HTTPRequest - # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -293,7 +285,6 @@ class Net::HTTP::Patch < Net::HTTPRequest # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Net::HTTP::Propfind < Net::HTTPRequest - # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -317,7 +308,6 @@ class Net::HTTP::Propfind < Net::HTTPRequest # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Net::HTTP::Proppatch < Net::HTTPRequest - # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -341,7 +331,6 @@ class Net::HTTP::Proppatch < Net::HTTPRequest # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Net::HTTP::Mkcol < Net::HTTPRequest - # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -365,7 +354,6 @@ class Net::HTTP::Mkcol < Net::HTTPRequest # - Net::HTTP#copy: sends +COPY+ request, returns response object. # class Net::HTTP::Copy < Net::HTTPRequest - # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -389,7 +377,6 @@ class Net::HTTP::Copy < Net::HTTPRequest # - Net::HTTP#move: sends +MOVE+ request, returns response object. # class Net::HTTP::Move < Net::HTTPRequest - # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -413,7 +400,6 @@ class Net::HTTP::Move < Net::HTTPRequest # - Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Net::HTTP::Lock < Net::HTTPRequest - # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -437,8 +423,8 @@ class Net::HTTP::Lock < Net::HTTPRequest # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Net::HTTP::Unlock < Net::HTTPRequest - # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end + diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 8804a99c9e5269..40de96386804f3 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -153,7 +153,6 @@ def read_new(sock) #:nodoc: internal use only end private - # :stopdoc: def read_status_line(sock) str = sock.readline @@ -260,7 +259,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect # :nodoc: + def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 2fa01e2c0c3f33..5e2f8ce1aa176a 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -5,7 +5,6 @@ module Net class HTTPUnknownResponse < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -20,7 +19,6 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse - # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -36,7 +34,6 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -52,7 +49,6 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -67,7 +63,6 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -82,7 +77,6 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -100,7 +94,6 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -118,7 +111,6 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -135,7 +127,6 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -154,7 +145,6 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -172,7 +162,6 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -190,7 +179,6 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -208,7 +196,6 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -228,7 +215,6 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -246,7 +232,6 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess - # :stopdoc: HAS_BODY = false end @@ -265,7 +250,6 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess - # :stopdoc: HAS_BODY = false end @@ -284,7 +268,6 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -302,7 +285,6 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -322,7 +304,6 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -340,7 +321,6 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -358,7 +338,6 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection - # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -377,7 +356,6 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -395,7 +373,6 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection - # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -413,7 +390,6 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -431,7 +407,6 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection - # :stopdoc: HAS_BODY = false end @@ -448,7 +423,6 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection - # :stopdoc: HAS_BODY = false end @@ -466,7 +440,6 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -483,7 +456,6 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -500,7 +472,6 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -517,7 +488,6 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -534,7 +504,6 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -552,7 +521,6 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -569,7 +537,6 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -586,7 +553,6 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -604,7 +570,6 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -621,7 +586,6 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -638,7 +602,6 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -656,7 +619,6 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -674,7 +636,6 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -692,7 +653,6 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -710,7 +670,6 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -727,7 +686,6 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -745,7 +703,6 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -764,7 +721,6 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -781,7 +737,6 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -799,7 +754,6 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -820,7 +774,6 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -837,7 +790,6 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -853,7 +805,6 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -870,7 +821,6 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -890,7 +840,6 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -907,7 +856,6 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -924,7 +872,6 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -942,7 +889,6 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -960,7 +906,6 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError - # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -981,7 +926,6 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -999,7 +943,6 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1017,7 +960,6 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1035,7 +977,6 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1053,7 +994,6 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError - # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1071,7 +1011,6 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1088,7 +1027,6 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1105,7 +1043,6 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1122,7 +1059,6 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError - # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1140,7 +1076,6 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1157,14 +1092,12 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError - # :stopdoc: HAS_BODY = true end end class Net::HTTPResponse - # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Net::HTTPInformation, '2' => Net::HTTPSuccess, From 4fe0342a860bc3aceb5f51707732545f83a0ac35 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Nov 2025 22:18:37 +0900 Subject: [PATCH 0931/2435] Reapply "[ruby/net-http] [DOC] Suppress documentation for internals" This reverts commit af610e107c3a7515228843eb6b1c5978f2ee2685. Reverted by a mistake. --- lib/net/http.rb | 35 ++++++++++------- lib/net/http/exceptions.rb | 3 +- lib/net/http/generic_request.rb | 2 + lib/net/http/header.rb | 12 ++++-- lib/net/http/requests.rb | 16 +++++++- lib/net/http/response.rb | 3 +- lib/net/http/responses.rb | 67 +++++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 21 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 9511f19f268730..7efb468e782e06 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1321,6 +1321,9 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets wheter the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1632,6 +1635,21 @@ def start # :yield: http self end + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1758,20 +1776,6 @@ def on_connect end private :on_connect - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish - end - def do_finish @started = false @socket.close if @socket @@ -1917,6 +1921,7 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) require 'cgi/escape' @@ -2400,6 +2405,8 @@ def send_entity(path, data, initheader, dest, type, &block) res end + # :stopdoc: + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index ceec8f7b0a3822..4342cfc0ef4be3 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index c92004e5578b38..d9a4c4fef05ad5 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -264,6 +264,8 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index f6c36f1b5e78e7..5dcdcc7d74051c 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ def add_field(key, val) end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ def add_field(key, val) ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ def connection_close? false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index e58057adf1664c..939d413f91961c 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Net::HTTP#get: sends +GET+ request, returns response object. # class Net::HTTP::Get < Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ class Net::HTTP::Get < Net::HTTPRequest # - Net::HTTP#head: sends +HEAD+ request, returns response object. # class Net::HTTP::Head < Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ class Net::HTTP::Head < Net::HTTPRequest # - Net::HTTP#post: sends +POST+ request, returns response object. # class Net::HTTP::Post < Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ class Net::HTTP::Post < Net::HTTPRequest # - Net::HTTP#put: sends +PUT+ request, returns response object. # class Net::HTTP::Put < Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ class Net::HTTP::Put < Net::HTTPRequest # - Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Net::HTTP::Delete < Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ class Net::HTTP::Delete < Net::HTTPRequest # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Net::HTTP::Options < Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ class Net::HTTP::Options < Net::HTTPRequest # - Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Net::HTTP::Trace < Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ class Net::HTTP::Trace < Net::HTTPRequest # - Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Net::HTTP::Patch < Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ class Net::HTTP::Patch < Net::HTTPRequest # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Net::HTTP::Propfind < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ class Net::HTTP::Propfind < Net::HTTPRequest # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Net::HTTP::Proppatch < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ class Net::HTTP::Proppatch < Net::HTTPRequest # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Net::HTTP::Mkcol < Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ class Net::HTTP::Mkcol < Net::HTTPRequest # - Net::HTTP#copy: sends +COPY+ request, returns response object. # class Net::HTTP::Copy < Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ class Net::HTTP::Copy < Net::HTTPRequest # - Net::HTTP#move: sends +MOVE+ request, returns response object. # class Net::HTTP::Move < Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ class Net::HTTP::Move < Net::HTTPRequest # - Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Net::HTTP::Lock < Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ class Net::HTTP::Lock < Net::HTTPRequest # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Net::HTTP::Unlock < Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 40de96386804f3..8804a99c9e5269 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -153,6 +153,7 @@ def read_new(sock) #:nodoc: internal use only end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 5e2f8ce1aa176a..2fa01e2c0c3f33 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -5,6 +5,7 @@ module Net class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +20,7 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +36,7 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +52,7 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +67,7 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +82,7 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +100,7 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +118,7 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +135,7 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +154,7 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +172,7 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +190,7 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +208,7 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +228,7 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +246,7 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +265,7 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +284,7 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +302,7 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +322,7 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +340,7 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +358,7 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +377,7 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +395,7 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +413,7 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +431,7 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +448,7 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +466,7 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +483,7 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +500,7 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +517,7 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +534,7 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +552,7 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +569,7 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +586,7 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +604,7 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +621,7 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +638,7 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +656,7 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +674,7 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +692,7 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +710,7 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +727,7 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +745,7 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +764,7 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +781,7 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +799,7 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +820,7 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +837,7 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +853,7 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +870,7 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +890,7 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +907,7 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +924,7 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +942,7 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +960,7 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +981,7 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +999,7 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1017,7 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1035,7 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1053,7 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1071,7 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1088,7 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1105,7 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1122,7 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1140,7 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,12 +1157,14 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Net::HTTPInformation, '2' => Net::HTTPSuccess, From a7a4bb93fc37b3d4ad5a7b2147c9adf952928342 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 9 Nov 2025 07:16:42 -0800 Subject: [PATCH 0932/2435] Automatically review default-gem pull requests (#15116) --- .github/workflows/auto_review_pr.yml | 33 +++++++++ tool/auto_review_pr.rb | 106 +++++++++++++++++++++++++++ tool/sync_default_gems.rb | 1 + 3 files changed, 140 insertions(+) create mode 100644 .github/workflows/auto_review_pr.yml create mode 100755 tool/auto_review_pr.rb diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml new file mode 100644 index 00000000000000..c8095dfd8e34a0 --- /dev/null +++ b/.github/workflows/auto_review_pr.yml @@ -0,0 +1,33 @@ +name: Auto Review PR +on: + pull_request_target: + types: [opened, ready_for_review, reopened] + branches: [master] + +permissions: + contents: read + +jobs: + auto-review-pr: + name: Auto Review PR + runs-on: ubuntu-latest + if: ${{ github.repository == 'ruby/ruby' && github.base_ref == 'master' }} + + permissions: + pull-requests: write + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + with: + ruby-version: '3.4' + bundler: none + + - name: Auto Review PR + run: ruby tool/auto_review_pr.rb "$GITHUB_PR_NUMBER" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/tool/auto_review_pr.rb b/tool/auto_review_pr.rb new file mode 100755 index 00000000000000..a3494e4c1a4144 --- /dev/null +++ b/tool/auto_review_pr.rb @@ -0,0 +1,106 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'json' +require 'net/http' +require 'uri' +require_relative './sync_default_gems' + +class GitHubAPIClient + def initialize(token) + @token = token + end + + def get(path) + response = Net::HTTP.get_response(URI("https://api.github.com#{path}"), { + 'Authorization' => "token #{@token}", + 'Accept' => 'application/vnd.github.v3+json', + }).tap(&:value) + JSON.parse(response.body, symbolize_names: true) + end + + def post(path, body = {}) + body = JSON.dump(body) + response = Net::HTTP.post(URI("https://api.github.com#{path}"), body, { + 'Authorization' => "token #{@token}", + 'Accept' => 'application/vnd.github.v3+json', + 'Content-Type' => 'application/json', + }).tap(&:value) + JSON.parse(response.body, symbolize_names: true) + end +end + +class AutoReviewPR + REPO = 'ruby/ruby' + + COMMENT_USER = 'github-actions[bot]' + COMMENT_PREFIX = 'The following files are maintained in the following upstream repositories:' + COMMENT_SUFFIX = 'Please file a pull request to the above instead. Thank you.' + + def initialize(client) + @client = client + end + + def review(pr_number) + comment_body = "Please file a pull request to ruby/foo instead." + + # Fetch the list of files changed by the PR + changed_files = @client.get("/repos/#{REPO}/pulls/#{pr_number}/files").map { it.fetch(:filename) } + + # Build a Hash: { upstream_repo => files, ... } + upstream_repos = changed_files.group_by { |file| find_upstream_repo(file) } + upstream_repos.delete(nil) # exclude no-upstream files + upstream_repos.delete('prism') if changed_files.include?('prism_compile.c') # allow prism changes in this case + if upstream_repos.empty? + puts "Skipped: The PR ##{pr_number} doesn't have upstream repositories." + return + end + + # Check if the PR is already reviewed + existing_comments = @client.get("/repos/#{REPO}/issues/#{pr_number}/comments") + existing_comments.map! { [it.fetch(:user).fetch(:login), it.fetch(:body)] } + if existing_comments.any? { |user, comment| user == COMMENT_USER && comment.start_with?(COMMENT_PREFIX) } + puts "Skipped: The PR ##{pr_number} already has an automated review comment." + return + end + + # Post a comment + comment = format_comment(upstream_repos) + result = @client.post("/repos/#{REPO}/issues/#{pr_number}/comments", { body: comment }) + puts "Success: #{JSON.pretty_generate(result)}" + end + + private + + def find_upstream_repo(file) + SyncDefaultGems::REPOSITORIES.each do |repo_name, repository| + repository.mappings.each do |_src, dst| + if file.start_with?(dst) + return repo_name + end + end + end + nil + end + + # upstream_repos: { upstream_repo => files, ... } + def format_comment(upstream_repos) + comment = +'' + comment << "#{COMMENT_PREFIX}\n\n" + + upstream_repos.each do |upstream_repo, files| + comment << "* https://github.com/ruby/#{upstream_repo}\n" + files.each do |file| + comment << " * #{file}\n" + end + end + + comment << "\n#{COMMENT_SUFFIX}" + comment + end +end + +pr_number = ARGV[0] || abort("Usage: #{$0} ") +client = GitHubAPIClient.new(ENV.fetch('GITHUB_TOKEN')) + +AutoReviewPR.new(client).review(pr_number) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index b2509f0062b0b5..997cd5eddc8f16 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -61,6 +61,7 @@ def lib((upstream, branch), gemspec_in_subdir: false) ]) end + # Note: tool/auto_review_pr.rb also depends on this constant. REPOSITORIES = { "io-console": repo("ruby/io-console", [ ["ext/io/console", "ext/io/console"], From 2b6580d44aef3f867321d1f8b346c500e51a4344 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 9 Nov 2025 07:34:18 -0800 Subject: [PATCH 0933/2435] auto_review_pr.rb: Remove an unused variable Follow-up: https://github.com/ruby/ruby/pull/15116 --- tool/auto_review_pr.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/tool/auto_review_pr.rb b/tool/auto_review_pr.rb index a3494e4c1a4144..f434a4f92a294c 100755 --- a/tool/auto_review_pr.rb +++ b/tool/auto_review_pr.rb @@ -42,8 +42,6 @@ def initialize(client) end def review(pr_number) - comment_body = "Please file a pull request to ruby/foo instead." - # Fetch the list of files changed by the PR changed_files = @client.get("/repos/#{REPO}/pulls/#{pr_number}/files").map { it.fetch(:filename) } From 4639bbc8e91238caa4774a8916ec6555b71d6e1b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 9 Nov 2025 07:35:21 -0800 Subject: [PATCH 0934/2435] auto_review_pr.rb: Tweak the review message --- tool/auto_review_pr.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/auto_review_pr.rb b/tool/auto_review_pr.rb index f434a4f92a294c..c63640354d3593 100755 --- a/tool/auto_review_pr.rb +++ b/tool/auto_review_pr.rb @@ -35,7 +35,7 @@ class AutoReviewPR COMMENT_USER = 'github-actions[bot]' COMMENT_PREFIX = 'The following files are maintained in the following upstream repositories:' - COMMENT_SUFFIX = 'Please file a pull request to the above instead. Thank you.' + COMMENT_SUFFIX = 'Please file a pull request to the above instead. Thank you!' def initialize(client) @client = client From f1b1899a749b28ce5c951034efeb096d15ae244c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 9 Nov 2025 11:33:03 -0500 Subject: [PATCH 0935/2435] [ruby/mmtk] Lock the VM when freeing objects in rb_gc_impl_shutdown_call_finalizer https://github.com/ruby/mmtk/commit/1828f6596f --- gc/mmtk/mmtk.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 9dd3129e01664e..cc0b1afd4d2051 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1022,16 +1022,20 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr) gc_run_finalizers(objspace); } - struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates(); - for (size_t i = 0; i < registered_candidates.len; i++) { - VALUE obj = (VALUE)registered_candidates.ptr[i]; - - if (rb_gc_shutdown_call_finalizer_p(obj)) { - rb_gc_obj_free(objspace_ptr, obj); - RBASIC(obj)->flags = 0; + unsigned int lev = RB_GC_VM_LOCK(); + { + struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates(); + for (size_t i = 0; i < registered_candidates.len; i++) { + VALUE obj = (VALUE)registered_candidates.ptr[i]; + + if (rb_gc_shutdown_call_finalizer_p(obj)) { + rb_gc_obj_free(objspace_ptr, obj); + RBASIC(obj)->flags = 0; + } } + mmtk_free_raw_vec_of_obj_ref(registered_candidates); } - mmtk_free_raw_vec_of_obj_ref(registered_candidates); + RB_GC_VM_UNLOCK(lev); gc_run_finalizers(objspace); } From 17efb770c89029f6b7df639b673a9a8cc08c8d0d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 9 Nov 2025 09:54:41 -0800 Subject: [PATCH 0936/2435] sync_default_gems.yml: Pull --rebase before push It was supposed to update a local branch in case the remote branch was updated between actions/checkout and the end of tool/sync_default_gems.rb. Note that this `git pull` didn't exist in the original bin/update-default-gem.sh https://github.com/ruby/git.ruby-lang.org/commit/d9e9bcc60dcf0dd1cbdc2dd42bc0286439e471aa, so it was newly introduced at https://github.com/ruby/ruby/commit/3ba5cfd1cb77b61b2b1ad1d03271bc1fe7b71969. But `git pull --no-ff` failed when master had updates in an unrelated repository. I think we need to use `git pull --rebase` instead. This fixes: * https://github.com/ruby/ruby/actions/runs/19207693793/job/54905403070 * checkout: f08030e9dccf38d9ea5c9505203fe26484dc28d8 * ruby tool/sync_default_gems.rb net-http ec9c70a6fba75a63c128864ef3cb32c883665a33..e4d80bd609f22cad04a2e2c1d54c981bb853c938 * pull: 2bf82c627494e785737037bbaf9a6b98f3c6354c --- .github/workflows/sync_default_gems.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 2f0b4d8865eb4f..1a9b43ec5241a9 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -56,7 +56,7 @@ jobs: - name: Push run: | - git pull --ff-only origin ${GITHUB_REF#refs/heads/} + git pull --rebase origin ${GITHUB_REF#refs/heads/} git push origin ${GITHUB_REF#refs/heads/} if: ${{ steps.sync.outputs.update }} From a731080f46f69bcb23aa98e565930931cf1faa0c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 5 Nov 2025 19:52:46 -0800 Subject: [PATCH 0937/2435] Make rb_gc_obj_optimal_size always return allocatable size It may return sizes that aren't allocatable for arrays and strings. --- gc.c | 20 ++++++++++++++++++-- gc/default/default.c | 7 +++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/gc.c b/gc.c index 24d897d4bc5ee4..74502dc21b74a5 100644 --- a/gc.c +++ b/gc.c @@ -3317,7 +3317,15 @@ rb_gc_obj_optimal_size(VALUE obj) { switch (BUILTIN_TYPE(obj)) { case T_ARRAY: - return rb_ary_size_as_embedded(obj); + { + size_t size = rb_ary_size_as_embedded(obj); + if (rb_gc_size_allocatable_p(size)) { + return size; + } + else { + return sizeof(struct RArray); + } + } case T_OBJECT: if (rb_shape_obj_too_complex_p(obj)) { @@ -3328,7 +3336,15 @@ rb_gc_obj_optimal_size(VALUE obj) } case T_STRING: - return rb_str_size_as_embedded(obj); + { + size_t size = rb_str_size_as_embedded(obj); + if (rb_gc_size_allocatable_p(size)) { + return size; + } + else { + return sizeof(struct RString); + } + } case T_HASH: return sizeof(struct RHash) + (RHASH_ST_TABLE_P(obj) ? sizeof(st_table) : sizeof(ar_table)); diff --git a/gc/default/default.c b/gc/default/default.c index f84303e02f500e..82741458bb8ff3 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -5484,10 +5484,9 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_heap_t *src_pool, VALUE return src_pool; } - size_t idx = 0; - if (rb_gc_impl_size_allocatable_p(obj_size)) { - idx = heap_idx_for_size(obj_size); - } + GC_ASSERT(rb_gc_impl_size_allocatable_p(obj_size)); + + size_t idx = heap_idx_for_size(obj_size); return &heaps[idx]; } From 6902bc71b9afeaa305342576dc1e75ceb0a175f4 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 1 Nov 2025 15:49:10 +0000 Subject: [PATCH 0938/2435] ZJIT: Refactor receiver type resolution --- zjit/src/hir.rs | 155 ++++++++++++++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 57 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 04f1291e2d33de..9d6f396ac87fbc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -563,6 +563,22 @@ impl std::fmt::Display for SideExitReason { } } +/// Result of resolving the receiver type for method dispatch optimization. +/// Represents whether we know the receiver's class statically at compile-time, +/// have profiled type information, or know nothing about it. +pub enum ReceiverTypeResolution { + /// The receiver's class is statically known at JIT compile-time (no guard needed) + StaticallyKnown { class: VALUE }, + /// The receiver has a monomorphic profile (single type observed, guard needed) + Monomorphic { class: VALUE, profiled_type: ProfiledType }, + /// The receiver has a skewed polymorphic profile (dominant type with some other types, guard needed) + SkewedPolymorphic { class: VALUE, profiled_type: ProfiledType }, + /// The receiver is polymorphic (multiple types, none dominant) + Polymorphic, + /// No profile information available for the receiver + NoProfile, +} + /// Reason why a send-ish instruction cannot be optimized from a fallback instruction #[derive(Debug, Clone, Copy)] pub enum SendFallbackReason { @@ -2133,26 +2149,49 @@ impl Function { None } - /// Return whether a given HIR instruction as profiled by the interpreter is polymorphic or - /// whether it lacks a profile entirely. + /// Resolve the receiver type for method dispatch optimization. /// - /// * `Some(true)` if polymorphic - /// * `Some(false)` if monomorphic - /// * `None` if no profiled information so far - fn is_polymorphic_at(&self, insn: InsnId, iseq_insn_idx: usize) -> Option { - let profiles = self.profiles.as_ref()?; - let entries = profiles.types.get(&iseq_insn_idx)?; - let insn = self.chase_insn(insn); + /// Takes the receiver's Type, receiver HIR instruction, and ISEQ instruction index. + /// Performs a single iteration through profile data to determine the receiver type. + /// + /// Returns: + /// - `StaticallyKnown` if the receiver's exact class is known at compile-time + /// - `Monomorphic`/`SkewedPolymorphic` if we have usable profile data + /// - `Polymorphic` if the receiver has multiple types + /// - `NoProfile` if we have no type information + fn resolve_receiver_type(&self, recv: InsnId, recv_type: Type, insn_idx: usize) -> ReceiverTypeResolution { + if let Some(class) = recv_type.runtime_exact_ruby_class() { + return ReceiverTypeResolution::StaticallyKnown { class }; + } + let Some(profiles) = self.profiles.as_ref() else { + return ReceiverTypeResolution::NoProfile; + }; + let Some(entries) = profiles.types.get(&insn_idx) else { + return ReceiverTypeResolution::NoProfile; + }; + let recv = self.chase_insn(recv); + for (entry_insn, entry_type_summary) in entries { - if self.union_find.borrow().find_const(*entry_insn) == insn { - if !entry_type_summary.is_monomorphic() && !entry_type_summary.is_skewed_polymorphic() { - return Some(true); - } else { - return Some(false); + if self.union_find.borrow().find_const(*entry_insn) == recv { + if entry_type_summary.is_monomorphic() { + let profiled_type = entry_type_summary.bucket(0); + return ReceiverTypeResolution::Monomorphic { + class: profiled_type.class(), + profiled_type, + }; + } else if entry_type_summary.is_skewed_polymorphic() { + let profiled_type = entry_type_summary.bucket(0); + return ReceiverTypeResolution::SkewedPolymorphic { + class: profiled_type.class(), + profiled_type, + }; + } else if entry_type_summary.is_polymorphic() { + return ReceiverTypeResolution::Polymorphic; } } } - None + + ReceiverTypeResolution::NoProfile } pub fn assume_expected_cfunc(&mut self, block: BlockId, class: VALUE, method_id: ID, cfunc: *mut c_void, state: InsnId) -> bool { @@ -2299,24 +2338,24 @@ impl Function { self.try_rewrite_uminus(block, insn_id, recv, state), Insn::SendWithoutBlock { mut recv, cd, args, state, .. } => { let frame_state = self.frame_state(state); - let (klass, profiled_type) = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { - // If we know the class statically, use it to fold the lookup at compile-time. - (klass, None) - } else { - // If we know that self is reasonably monomorphic from profile information, guard and use it to fold the lookup at compile-time. - // TODO(max): Figure out how to handle top self? - let Some(recv_type) = self.profiled_type_of_at(recv, frame_state.insn_idx) else { + let (klass, profiled_type) = match self.resolve_receiver_type(recv, self.type_of(recv), frame_state.insn_idx) { + ReceiverTypeResolution::StaticallyKnown { class } => (class, None), + ReceiverTypeResolution::Monomorphic { class, profiled_type } + | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), + ReceiverTypeResolution::Polymorphic => { if get_option!(stats) { - match self.is_polymorphic_at(recv, frame_state.insn_idx) { - Some(true) => self.set_dynamic_send_reason(insn_id, SendWithoutBlockPolymorphic), - // If the class isn't known statically, then it should not also be monomorphic - Some(false) => panic!("Should not have monomorphic profile at this point in this branch"), - None => self.set_dynamic_send_reason(insn_id, SendWithoutBlockNoProfiles), - } + self.set_dynamic_send_reason(insn_id, SendWithoutBlockPolymorphic); } - self.push_insn_id(block, insn_id); continue; - }; - (recv_type.class(), Some(recv_type)) + self.push_insn_id(block, insn_id); + continue; + } + ReceiverTypeResolution::NoProfile => { + if get_option!(stats) { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNoProfiles); + } + self.push_insn_id(block, insn_id); + continue; + } }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site @@ -2492,22 +2531,24 @@ impl Function { // The actual optimization is done in reduce_send_to_ccall. Insn::Send { recv, cd, state, .. } => { let frame_state = self.frame_state(state); - let klass = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { - // If we know the class statically, use it to fold the lookup at compile-time. - klass - } else { - let Some(recv_type) = self.profiled_type_of_at(recv, frame_state.insn_idx) else { + let klass = match self.resolve_receiver_type(recv, self.type_of(recv), frame_state.insn_idx) { + ReceiverTypeResolution::StaticallyKnown { class } => class, + ReceiverTypeResolution::Monomorphic { class, .. } + | ReceiverTypeResolution::SkewedPolymorphic { class, .. } => class, + ReceiverTypeResolution::Polymorphic => { if get_option!(stats) { - match self.is_polymorphic_at(recv, frame_state.insn_idx) { - Some(true) => self.set_dynamic_send_reason(insn_id, SendPolymorphic), - // If the class isn't known statically, then it should not also be monomorphic - Some(false) => panic!("Should not have monomorphic profile at this point in this branch"), - None => self.set_dynamic_send_reason(insn_id, SendNoProfiles), - } + self.set_dynamic_send_reason(insn_id, SendPolymorphic); } - self.push_insn_id(block, insn_id); continue; - }; - recv_type.class() + self.push_insn_id(block, insn_id); + continue; + } + ReceiverTypeResolution::NoProfile => { + if get_option!(stats) { + self.set_dynamic_send_reason(insn_id, SendNoProfiles); + } + self.push_insn_id(block, insn_id); + continue; + } }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site let mid = unsafe { vm_ci_mid(ci) }; @@ -2769,12 +2810,12 @@ impl Function { let method_id = unsafe { rb_vm_ci_mid(call_info) }; // If we have info about the class of the receiver - let (recv_class, profiled_type) = if let Some(class) = self_type.runtime_exact_ruby_class() { - (class, None) - } else { - let iseq_insn_idx = fun.frame_state(state).insn_idx; - let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) }; - (recv_type.class(), Some(recv_type)) + let iseq_insn_idx = fun.frame_state(state).insn_idx; + let (recv_class, profiled_type) = match fun.resolve_receiver_type(recv, self_type, iseq_insn_idx) { + ReceiverTypeResolution::StaticallyKnown { class } => (class, None), + ReceiverTypeResolution::Monomorphic { class, profiled_type } + | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), + ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::NoProfile => return Err(()), }; // Do method lookup @@ -2874,12 +2915,12 @@ impl Function { let method_id = unsafe { rb_vm_ci_mid(call_info) }; // If we have info about the class of the receiver - let (recv_class, profiled_type) = if let Some(class) = self_type.runtime_exact_ruby_class() { - (class, None) - } else { - let iseq_insn_idx = fun.frame_state(state).insn_idx; - let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) }; - (recv_type.class(), Some(recv_type)) + let iseq_insn_idx = fun.frame_state(state).insn_idx; + let (recv_class, profiled_type) = match fun.resolve_receiver_type(recv, self_type, iseq_insn_idx) { + ReceiverTypeResolution::StaticallyKnown { class } => (class, None), + ReceiverTypeResolution::Monomorphic { class, profiled_type } + | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), + ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::NoProfile => return Err(()), }; // Do method lookup From d05f23b51f8b9c3f80eff6f5a304b07b9f9d7fcf Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 6 Nov 2025 10:59:59 +0900 Subject: [PATCH 0939/2435] ZJIT: handle megamorphic and skewed megamorphic profiling results --- zjit/src/hir.rs | 48 +++++++++++++++++++++++++++++++++++++++-------- zjit/src/stats.rs | 4 ++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9d6f396ac87fbc..46b2bc905e3487 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -567,22 +567,27 @@ impl std::fmt::Display for SideExitReason { /// Represents whether we know the receiver's class statically at compile-time, /// have profiled type information, or know nothing about it. pub enum ReceiverTypeResolution { - /// The receiver's class is statically known at JIT compile-time (no guard needed) - StaticallyKnown { class: VALUE }, + /// No profile information available for the receiver + NoProfile, /// The receiver has a monomorphic profile (single type observed, guard needed) Monomorphic { class: VALUE, profiled_type: ProfiledType }, - /// The receiver has a skewed polymorphic profile (dominant type with some other types, guard needed) - SkewedPolymorphic { class: VALUE, profiled_type: ProfiledType }, /// The receiver is polymorphic (multiple types, none dominant) Polymorphic, - /// No profile information available for the receiver - NoProfile, + /// The receiver has a skewed polymorphic profile (dominant type with some other types, guard needed) + SkewedPolymorphic { class: VALUE, profiled_type: ProfiledType }, + /// More than N types seen with no clear winner + Megamorphic, + /// Megamorphic, but with a significant skew towards one type + SkewedMegamorphic { class: VALUE, profiled_type: ProfiledType }, + /// The receiver's class is statically known at JIT compile-time (no guard needed) + StaticallyKnown { class: VALUE }, } /// Reason why a send-ish instruction cannot be optimized from a fallback instruction #[derive(Debug, Clone, Copy)] pub enum SendFallbackReason { SendWithoutBlockPolymorphic, + SendWithoutBlockMegamorphic, SendWithoutBlockNoProfiles, SendWithoutBlockCfuncNotVariadic, SendWithoutBlockCfuncArrayVariadic, @@ -590,6 +595,7 @@ pub enum SendFallbackReason { SendWithoutBlockNotOptimizedOptimizedMethodType(OptimizedMethodType), SendWithoutBlockDirectTooManyArgs, SendPolymorphic, + SendMegamorphic, SendNoProfiles, SendNotOptimizedMethodType(MethodType), CCallWithFrameTooManyArgs, @@ -2158,6 +2164,8 @@ impl Function { /// - `StaticallyKnown` if the receiver's exact class is known at compile-time /// - `Monomorphic`/`SkewedPolymorphic` if we have usable profile data /// - `Polymorphic` if the receiver has multiple types + /// - `Megamorphic`/`SkewedMegamorphic` if the receiver has too many types to optimize + /// (SkewedMegamorphic may be optimized in the future, but for now we don't) /// - `NoProfile` if we have no type information fn resolve_receiver_type(&self, recv: InsnId, recv_type: Type, insn_idx: usize) -> ReceiverTypeResolution { if let Some(class) = recv_type.runtime_exact_ruby_class() { @@ -2185,8 +2193,16 @@ impl Function { class: profiled_type.class(), profiled_type, }; + } else if entry_type_summary.is_skewed_megamorphic() { + let profiled_type = entry_type_summary.bucket(0); + return ReceiverTypeResolution::SkewedMegamorphic { + class: profiled_type.class(), + profiled_type, + }; } else if entry_type_summary.is_polymorphic() { return ReceiverTypeResolution::Polymorphic; + } else if entry_type_summary.is_megamorphic() { + return ReceiverTypeResolution::Megamorphic; } } } @@ -2342,6 +2358,14 @@ impl Function { ReceiverTypeResolution::StaticallyKnown { class } => (class, None), ReceiverTypeResolution::Monomorphic { class, profiled_type } | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), + ReceiverTypeResolution::SkewedMegamorphic { .. } + | ReceiverTypeResolution::Megamorphic => { + if get_option!(stats) { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockMegamorphic); + } + self.push_insn_id(block, insn_id); + continue; + } ReceiverTypeResolution::Polymorphic => { if get_option!(stats) { self.set_dynamic_send_reason(insn_id, SendWithoutBlockPolymorphic); @@ -2535,6 +2559,14 @@ impl Function { ReceiverTypeResolution::StaticallyKnown { class } => class, ReceiverTypeResolution::Monomorphic { class, .. } | ReceiverTypeResolution::SkewedPolymorphic { class, .. } => class, + ReceiverTypeResolution::SkewedMegamorphic { .. } + | ReceiverTypeResolution::Megamorphic => { + if get_option!(stats) { + self.set_dynamic_send_reason(insn_id, SendMegamorphic); + } + self.push_insn_id(block, insn_id); + continue; + } ReceiverTypeResolution::Polymorphic => { if get_option!(stats) { self.set_dynamic_send_reason(insn_id, SendPolymorphic); @@ -2815,7 +2847,7 @@ impl Function { ReceiverTypeResolution::StaticallyKnown { class } => (class, None), ReceiverTypeResolution::Monomorphic { class, profiled_type } | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), - ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::NoProfile => return Err(()), + ReceiverTypeResolution::SkewedMegamorphic { .. } | ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::Megamorphic | ReceiverTypeResolution::NoProfile => return Err(()), }; // Do method lookup @@ -2920,7 +2952,7 @@ impl Function { ReceiverTypeResolution::StaticallyKnown { class } => (class, None), ReceiverTypeResolution::Monomorphic { class, profiled_type } | ReceiverTypeResolution::SkewedPolymorphic { class, profiled_type } => (class, Some(profiled_type)), - ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::NoProfile => return Err(()), + ReceiverTypeResolution::SkewedMegamorphic { .. } | ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::Megamorphic | ReceiverTypeResolution::NoProfile => return Err(()), }; // Do method lookup diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 099609b90a8624..a2105ae27e16dc 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -168,6 +168,7 @@ make_counters! { dynamic_send { // send_fallback_: Fallback reasons for send-ish instructions send_fallback_send_without_block_polymorphic, + send_fallback_send_without_block_megamorphic, send_fallback_send_without_block_no_profiles, send_fallback_send_without_block_cfunc_not_variadic, send_fallback_send_without_block_cfunc_array_variadic, @@ -175,6 +176,7 @@ make_counters! { send_fallback_send_without_block_not_optimized_optimized_method_type, send_fallback_send_without_block_direct_too_many_args, send_fallback_send_polymorphic, + send_fallback_send_megamorphic, send_fallback_send_no_profiles, send_fallback_send_not_optimized_method_type, send_fallback_ccall_with_frame_too_many_args, @@ -428,6 +430,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter use crate::stats::Counter::*; match reason { SendWithoutBlockPolymorphic => send_fallback_send_without_block_polymorphic, + SendWithoutBlockMegamorphic => send_fallback_send_without_block_megamorphic, SendWithoutBlockNoProfiles => send_fallback_send_without_block_no_profiles, SendWithoutBlockCfuncNotVariadic => send_fallback_send_without_block_cfunc_not_variadic, SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, @@ -436,6 +439,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter => send_fallback_send_without_block_not_optimized_optimized_method_type, SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args, SendPolymorphic => send_fallback_send_polymorphic, + SendMegamorphic => send_fallback_send_megamorphic, SendNoProfiles => send_fallback_send_no_profiles, ComplexArgPass => send_fallback_one_or_more_complex_arg_pass, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, From a43acf9a3f411fac78d92b9f2f8060c83f9ab15b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 10:52:01 +0900 Subject: [PATCH 0940/2435] [ruby/resolv] [DOC] `Resolv::LOC` itself is undocumented https://github.com/ruby/resolv/commit/7c5bfe7acd --- lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 34749afb674af7..b98c7ecdd2aa32 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3250,7 +3250,7 @@ def make_udp_requester # :nodoc: end - module LOC + module LOC # :nodoc: ## # A Resolv::LOC::Size From 8fa29a75abf265c20cdeb9779bf25c1b3eafcf26 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 10 Nov 2025 11:10:15 +0900 Subject: [PATCH 0941/2435] Fix condition for Timeout Error with >= macOS 26.1 --- test/resolv/test_dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 2bb8061edbd1ac..86a1ecf569c181 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -525,7 +525,7 @@ def test_no_server if RUBY_PLATFORM.match?(/mingw/) # cannot repo locally omit 'Timeout Error on MinGW CI' - elsif macos?(26,1) + elsif macos?([26,1]..) omit 'Timeout Error on macOS 26.1+' else raise Timeout::Error From f710e6bb54a1e2cfe808222bc8d70d8f68ab5dc9 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Thu, 17 Jul 2025 00:09:47 +0900 Subject: [PATCH 0942/2435] [ruby/net-http] Replace Timeout.timeout with TCPSocket.open(open_timeout:) when available This patch replaces the implementation of #open_timeout from Timeout.timeout from the builtin timeout in TCPSocket.open, which was introduced in Ruby 3.5 (https://bugs.ruby-lang.org/issues/21347). The builtin timeout in TCPSocket.open is better in several ways than Timeout.timeout. It does not rely on a separate Ruby Thread for monitoring Timeout (which is what the timeout library internally does). Furthermore, it is compatible with Ractors, as opposed to Timeout.timeout (it internally uses Thread::Mutex which can not be used in non-main Ractors). This change allows the following code to work. require 'net/http' Ractor.new { uri = URI('http://example.com/') http = Net::HTTP.new(uri.host, uri.port) http.open_timeout = 1 http.get(uri.path) }.value In Ruby <3.5 environments where `TCPSocket.open` does not have the `open_timeout` option, I have kept the behavior unchanged. net/http will use `Timeout.timeout { TCPSocket.open }`. https://github.com/ruby/net-http/commit/728eb8fc42 --- lib/net/http.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 7efb468e782e06..d6db9ba132abfe 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1672,14 +1672,22 @@ def connect end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = begin + # Use built-in timeout in TCPSocket.open if available + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + rescue ArgumentError => e + raise if !e.message.include?('unknown keyword: :open_timeout') + # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } end - } + rescue => e + e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? From 97efbc47d080e3ade3b5db889d3740fdf0711161 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Tue, 22 Jul 2025 23:34:25 +0900 Subject: [PATCH 0943/2435] [ruby/net-http] Ruby 2 compat https://github.com/ruby/net-http/commit/09bf573dd5 --- lib/net/http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index d6db9ba132abfe..810ee008facaaf 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1677,7 +1677,7 @@ def connect # Use built-in timeout in TCPSocket.open if available TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) rescue ArgumentError => e - raise if !e.message.include?('unknown keyword: :open_timeout') + raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout Timeout.timeout(@open_timeout, Net::OpenTimeout) { TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) From f29d772a73ed25c5bcc6eab1d55684894292d160 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Thu, 6 Nov 2025 11:20:11 +0900 Subject: [PATCH 0944/2435] [ruby/net-http] Remember if TCPSocket impl supports open_timeout For open_timeout support detection, the previous implementation relied on an ArgumentError being raised and then rescued. In Ruby, rescue is a rather expensive operation and should be avoided when possible. This patch reduces the number of begin-rescues by remembering if the TCPSocket implementation supports open_timeout. https://github.com/ruby/net-http/commit/06d982f3a1 --- lib/net/http.rb | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 810ee008facaaf..c60649b8123c09 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1179,6 +1179,7 @@ def initialize(address, port = nil) # :nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1673,16 +1674,30 @@ def connect debug "opening connection to #{conn_addr}:#{conn_port}..." begin - s = begin - # Use built-in timeout in TCPSocket.open if available - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) - rescue ArgumentError => e - raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) - # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout - Timeout.timeout(@open_timeout, Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } - end + s = + case @tcpsocket_supports_open_timeout + when nil, true + begin + # Use built-in timeout in TCPSocket.open if available + sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + @tcpsocket_supports_open_timeout = true + sock + rescue ArgumentError => e + raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) + @tcpsocket_supports_open_timeout = false + + # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + when false + # The current Ruby is known to not support TCPSocket(open_timeout:). + # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue. + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end rescue => e e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions raise e, "Failed to open TCP connection to " + From cb50ed2cc2218f077789e75fca3afeaf51ebba0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 02:04:22 +0000 Subject: [PATCH 0945/2435] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/auto_review_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index c8095dfd8e34a0..c8c9a76777cb76 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: From 3147df87baf8898e6fb3c1482e7a5ed58945c97a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 12:36:55 +0900 Subject: [PATCH 0946/2435] [ruby/net-http] [DOC] Suppress documentation for internals https://github.com/ruby/net-http/commit/b7c586985a --- lib/net/http/responses.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 2fa01e2c0c3f33..941a6fed80414b 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -4,6 +4,7 @@ module Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse # :stopdoc: HAS_BODY = true From b1dfcd6507452d577162d12ecb0828abe64d8162 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 12:40:32 +0900 Subject: [PATCH 0947/2435] [ruby/stringio] [DOC] Suppress documentation for internals https://github.com/ruby/stringio/commit/27b2fb2fce --- ext/stringio/stringio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 39e4be58538f9f..5da0b4c9457a2e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -2244,7 +2244,9 @@ Init_stringio(void) rb_define_method(StringIO, "set_encoding_by_bom", strio_set_encoding_by_bom, 0); { + /* :stopdoc: */ VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); + /* :startdoc: */ rb_define_method(mReadable, "readchar", strio_readchar, 0); rb_define_method(mReadable, "readbyte", strio_readbyte, 0); rb_define_method(mReadable, "readline", strio_readline, -1); @@ -2254,7 +2256,9 @@ Init_stringio(void) rb_include_module(StringIO, mReadable); } { + /* :stopdoc: */ VALUE mWritable = rb_define_module_under(rb_cIO, "generic_writable"); + /* :startdoc: */ rb_define_method(mWritable, "<<", strio_addstr, 1); rb_define_method(mWritable, "print", strio_print, -1); rb_define_method(mWritable, "printf", strio_printf, -1); From 40d6626bbf0b0bd4c672d48dbf928ab8429912f9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 8 Nov 2025 07:25:33 +0900 Subject: [PATCH 0948/2435] [ruby/rubygems] Fixed with Lint/RedundantSplatExpansion https://github.com/ruby/rubygems/commit/2078f3d351 --- lib/bundler/current_ruby.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index abb7ca2195daa6..ab6520a106a40f 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -11,7 +11,7 @@ def self.current_ruby end class CurrentRuby - ALL_RUBY_VERSIONS = [*18..27, *30..34, *40].freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, 40].freeze KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze PLATFORM_MAP = { From 19295f5db3316dd47d0aded90a21b07d7e761902 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 12:55:41 +0900 Subject: [PATCH 0949/2435] [ruby/cgi] [DOC] Missing documents https://github.com/ruby/cgi/commit/ebd04d1eb1 --- lib/cgi/escape.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb index 59a310b32d6187..6d84773fdd62f6 100644 --- a/lib/cgi/escape.rb +++ b/lib/cgi/escape.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true +# :stopdoc class CGI module Escape; end include Escape extend Escape + module EscapeExt; end # :nodoc: end +# :startdoc: +# Escape/unescape for CGI, HTML, URI. module CGI::Escape @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) From 28f760bf70085d97bab62c29e13c69c5a732e758 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 13:03:50 +0900 Subject: [PATCH 0950/2435] [ruby/stringio] Suppress warnings against pattern matching on ruby 2.7 https://github.com/ruby/stringio/commit/cf58a203eb --- test/stringio/test_stringio.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index fe890406074654..024906261efaac 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -996,7 +996,7 @@ def test_overflow intptr_max = RbConfig::LIMITS["INTPTR_MAX"] return if intptr_max > StringIO::MAX_LENGTH limit = intptr_max - 0x10 - assert_separately(%w[-rstringio], "#{<<-"begin;"}\n#{<<-"end;"}") + assert_separately(%w[-W0 -rstringio], "#{<<-"begin;"}\n#{<<-"end;"}") begin; limit = #{limit} ary = [] From 73339ff2a1e5e8f35ad7224886ed56ae1da3e3ca Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 13:21:17 +0900 Subject: [PATCH 0951/2435] [ruby/rubygems] [DOC] Fix the location of Gem::Deprecate document It was bound to `module Gem`, instead of `module Deprecate`. https://github.com/ruby/rubygems/commit/da29f74ba1 --- lib/rubygems/deprecate.rb | 138 +++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index 303b344d42c838..fae99052ea4f6a 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -1,75 +1,75 @@ # frozen_string_literal: true -## -# Provides 3 methods for declaring when something is going away. -# -# +deprecate(name, repl, year, month)+: -# Indicate something may be removed on/after a certain date. -# -# +rubygems_deprecate(name, replacement=:none)+: -# Indicate something will be removed in the next major RubyGems version, -# and (optionally) a replacement for it. -# -# +rubygems_deprecate_command+: -# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be -# removed in the next RubyGems version. -# -# Also provides +skip_during+ for temporarily turning off deprecation warnings. -# This is intended to be used in the test suite, so deprecation warnings -# don't cause test failures if you need to make sure stderr is otherwise empty. -# -# -# Example usage of +deprecate+ and +rubygems_deprecate+: -# -# class Legacy -# def self.some_class_method -# # ... -# end -# -# def some_instance_method -# # ... -# end -# -# def some_old_method -# # ... -# end -# -# extend Gem::Deprecate -# deprecate :some_instance_method, "X.z", 2011, 4 -# rubygems_deprecate :some_old_method, "Modern#some_new_method" -# -# class << self -# extend Gem::Deprecate -# deprecate :some_class_method, :none, 2011, 4 -# end -# end -# -# -# Example usage of +rubygems_deprecate_command+: -# -# class Gem::Commands::QueryCommand < Gem::Command -# extend Gem::Deprecate -# rubygems_deprecate_command -# -# # ... -# end -# -# -# Example usage of +skip_during+: -# -# class TestSomething < Gem::Testcase -# def test_some_thing_with_deprecations -# Gem::Deprecate.skip_during do -# actual_stdout, actual_stderr = capture_output do -# Gem.something_deprecated -# end -# assert_empty actual_stdout -# assert_equal(expected, actual_stderr) -# end -# end -# end - module Gem + ## + # Provides 3 methods for declaring when something is going away. + # + # +deprecate(name, repl, year, month)+: + # Indicate something may be removed on/after a certain date. + # + # +rubygems_deprecate(name, replacement=:none)+: + # Indicate something will be removed in the next major RubyGems version, + # and (optionally) a replacement for it. + # + # +rubygems_deprecate_command+: + # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be + # removed in the next RubyGems version. + # + # Also provides +skip_during+ for temporarily turning off deprecation warnings. + # This is intended to be used in the test suite, so deprecation warnings + # don't cause test failures if you need to make sure stderr is otherwise empty. + # + # + # Example usage of +deprecate+ and +rubygems_deprecate+: + # + # class Legacy + # def self.some_class_method + # # ... + # end + # + # def some_instance_method + # # ... + # end + # + # def some_old_method + # # ... + # end + # + # extend Gem::Deprecate + # deprecate :some_instance_method, "X.z", 2011, 4 + # rubygems_deprecate :some_old_method, "Modern#some_new_method" + # + # class << self + # extend Gem::Deprecate + # deprecate :some_class_method, :none, 2011, 4 + # end + # end + # + # + # Example usage of +rubygems_deprecate_command+: + # + # class Gem::Commands::QueryCommand < Gem::Command + # extend Gem::Deprecate + # rubygems_deprecate_command + # + # # ... + # end + # + # + # Example usage of +skip_during+: + # + # class TestSomething < Gem::Testcase + # def test_some_thing_with_deprecations + # Gem::Deprecate.skip_during do + # actual_stdout, actual_stderr = capture_output do + # Gem.something_deprecated + # end + # assert_empty actual_stdout + # assert_equal(expected, actual_stderr) + # end + # end + # end + module Deprecate def self.skip # :nodoc: @skip ||= false From 57f2ac720d70b2245706f323cd7f3178aa5cfe6c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 13:46:15 +0900 Subject: [PATCH 0952/2435] [ruby/rubygems] [DOC] Fix markups Use `` instead of `+` that cannot enclose punctuations. https://github.com/ruby/rubygems/commit/f84035c0b6 --- lib/rubygems/deprecate.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index fae99052ea4f6a..eb503bb2699976 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -4,10 +4,10 @@ module Gem ## # Provides 3 methods for declaring when something is going away. # - # +deprecate(name, repl, year, month)+: + # deprecate(name, repl, year, month): # Indicate something may be removed on/after a certain date. # - # +rubygems_deprecate(name, replacement=:none)+: + # rubygems_deprecate(name, replacement=:none): # Indicate something will be removed in the next major RubyGems version, # and (optionally) a replacement for it. # From 57daafc1da2eea71b50746893d77ad21658dd3b1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 3 Nov 2025 12:24:22 -0800 Subject: [PATCH 0953/2435] [ruby/rubygems] Make verification methods private I would like to start making some of the methods in Gem::Package private so that we can refactor them better. Right now we have many methods that are public, and since they are public we can't refactor them. Historically, I think "private" methods have just been tagged with :nodoc:, but I would like to be more strict about our APIs https://github.com/ruby/rubygems/commit/fb352e9176 --- lib/rubygems/package.rb | 2 + test/rubygems/test_gem_package.rb | 74 ++++++------------------------- 2 files changed, 16 insertions(+), 60 deletions(-) diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index b56d68cc45bb15..6b21ff1b953303 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -642,6 +642,8 @@ def verify raise Gem::Package::FormatError.new e.message, @gem end + private + ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 8a9cc8558022b7..34fa65b1e03028 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -1247,71 +1247,25 @@ def test_verify_truncate # end #verify tests - def test_verify_entry - entry = Object.new - def entry.full_name - raise ArgumentError, "whatever" - end - - package = Gem::Package.new @gem - - _, err = use_ui @ui do - e = nil - - out_err = capture_output do - e = assert_raise ArgumentError do - package.verify_entry entry + def test_missing_metadata + invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"] + invalid_metadata.each do |fname| + tar = StringIO.new + + Gem::Package::TarWriter.new(tar) do |gem_tar| + gem_tar.add_file fname, 0o444 do |io| + gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION + gz_io.write "bad metadata" + gz_io.close end end - assert_equal "whatever", e.message - assert_equal "full_name", e.backtrace_locations.first.label - - out_err - end - - assert_equal "Exception while verifying #{@gem}\n", err - - valid_metadata = ["metadata", "metadata.gz"] - valid_metadata.each do |vm| - $spec_loaded = false - $good_name = vm - - entry = Object.new - def entry.full_name - $good_name - end + tar.rewind - package = Gem::Package.new(@gem) - package.instance_variable_set(:@files, []) - def package.load_spec(entry) - $spec_loaded = true - end - - package.verify_entry(entry) - - assert $spec_loaded - end - - invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"] - invalid_metadata.each do |vm| - $spec_loaded = false - $bad_name = vm - - entry = Object.new - def entry.full_name - $bad_name - end - - package = Gem::Package.new(@gem) - package.instance_variable_set(:@files, []) - def package.load_spec(entry) - $spec_loaded = true + package = Gem::Package.new(Gem::Package::IOSource.new(tar)) + assert_raise Gem::Package::FormatError do + package.verify end - - package.verify_entry(entry) - - refute $spec_loaded end end From bc177ff1cebee9e76234aad51ff7b3a158d037d6 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Mon, 10 Nov 2025 09:27:07 +0100 Subject: [PATCH 0954/2435] [ruby/timeout] Suppress warnings in two tests Failed build in #70. Pre-3.0 versions of Ruby didn't support pattern matching, and power_assert warned. https://github.com/ruby/timeout/commit/983cbf636a --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 01156867b05609..71d8e1f5c273c5 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -250,7 +250,7 @@ def test_fork end def test_threadgroup - assert_separately(%w[-rtimeout], <<-'end;') + assert_separately(%w[-W0 -rtimeout], <<-'end;') tg = ThreadGroup.new thr = Thread.new do tg.add(Thread.current) @@ -263,7 +263,7 @@ def test_threadgroup # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup - assert_separately(%w[-rtimeout], <<-'end;') + assert_separately(%w[-W0 -rtimeout], <<-'end;') Thread.new { t = Thread.current group = ThreadGroup.new From 2af63204de6047f5c3317467404a38853f414cbe Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 14:29:37 +0900 Subject: [PATCH 0955/2435] [ruby/digest] [DOC] Missing documents https://github.com/ruby/digest/commit/16b598d6f2 --- ext/digest/lib/digest/version.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/digest/lib/digest/version.rb b/ext/digest/lib/digest/version.rb index 691323f1099fe0..a56e80c54ee84c 100644 --- a/ext/digest/lib/digest/version.rb +++ b/ext/digest/lib/digest/version.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true module Digest + # The version string VERSION = "3.2.1" end From 309b6ca1c2c979f8370ef6466910edb52dba69aa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:00:41 +0900 Subject: [PATCH 0956/2435] [ruby/delegate] [DOC] Update missing docs and mark ups https://github.com/ruby/delegate/commit/020a6cfe4b --- lib/delegate.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 824d02f28b0946..838de675f1f5e3 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -39,6 +39,7 @@ # Be advised, RDoc will not detect delegated methods. # class Delegator < BasicObject + # The version string VERSION = "0.4.0" kernel = ::Kernel.dup @@ -77,7 +78,7 @@ def initialize(obj) end # - # Handles the magic of delegation through \_\_getobj\_\_. + # Handles the magic of delegation through +__getobj__+. # ruby2_keywords def method_missing(m, *args, &block) r = true @@ -94,7 +95,7 @@ def initialize(obj) # # Checks for a method provided by this the delegate object by forwarding the - # call through \_\_getobj\_\_. + # call through +__getobj__+. # def respond_to_missing?(m, include_private) r = true @@ -107,7 +108,7 @@ def respond_to_missing?(m, include_private) r end - KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) + KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) # :nodoc: private_constant :KERNEL_RESPOND_TO # Handle BasicObject instances @@ -126,7 +127,7 @@ def respond_to_missing?(m, include_private) # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ methods. + # of this object's and +__getobj__+ methods. # def methods(all=true) __getobj__.methods(all) | super @@ -134,7 +135,7 @@ def methods(all=true) # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ public methods. + # of this object's and +__getobj__+ public methods. # def public_methods(all=true) __getobj__.public_methods(all) | super @@ -142,7 +143,7 @@ def public_methods(all=true) # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ protected methods. + # of this object's and +__getobj__+ protected methods. # def protected_methods(all=true) __getobj__.protected_methods(all) | super @@ -175,7 +176,7 @@ def eql?(obj) end # - # Delegates ! to the \_\_getobj\_\_ + # Delegates ! to the +__getobj__+ # def ! !__getobj__ @@ -198,7 +199,7 @@ def __setobj__(obj) end # - # Serialization support for the object returned by \_\_getobj\_\_. + # Serialization support for the object returned by +__getobj__+. # def marshal_dump ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var} @@ -232,7 +233,7 @@ def initialize_dup(obj) # :nodoc: ## # :method: freeze - # Freeze both the object returned by \_\_getobj\_\_ and self. + # Freeze both the object returned by +__getobj__+ and self. # def freeze __getobj__.freeze From ea647f52c923330932998f34501cf647ac02d3d2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:13:50 +0900 Subject: [PATCH 0957/2435] [ruby/erb] [DOC] Suppress documentation for internals https://github.com/ruby/erb/commit/332e200060 --- lib/erb/compiler.rb | 1 - lib/erb/util.rb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb index 08b5eb4ee1d220..8a4d85c6ea5d54 100644 --- a/lib/erb/compiler.rb +++ b/lib/erb/compiler.rb @@ -480,7 +480,6 @@ def initialize from end }.new(caller(0)).c private_constant :WARNING_UPLEVEL - # :startdoc: def warn_invalid_trim_mode(mode, uplevel:) warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL diff --git a/lib/erb/util.rb b/lib/erb/util.rb index efa8ca1d137cbd..9c3d186edefb62 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -19,6 +19,7 @@ # A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope # Rails will not monkey-patch ERB::Escape#html_escape. module ERB::Escape + # :stopdoc: def html_escape(s) CGI.escapeHTML(s.to_s) end From daf8c2fa13eb51734e73769428182c18c409b92b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:19:51 +0900 Subject: [PATCH 0958/2435] [ruby/find] [DOC] Missing documents https://github.com/ruby/find/commit/01232ad51a --- lib/find.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/find.rb b/lib/find.rb index 98a79cc76dad7c..8223eea4560319 100644 --- a/lib/find.rb +++ b/lib/find.rb @@ -27,6 +27,7 @@ # module Find + # The version string VERSION = "0.2.0" # From 0dfca2e3c3d9fbae19ecd2bfcf5da11cf2177614 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:21:02 +0900 Subject: [PATCH 0959/2435] [ruby/forwardable] [DOC] Missing documents https://github.com/ruby/forwardable/commit/909986fee9 --- lib/forwardable.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 71b4e6adad4d9a..5652e728642906 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -114,6 +114,8 @@ module Forwardable # Version of +forwardable.rb+ VERSION = "1.3.3" VERSION.freeze + + # Version for backward compatibility FORWARDABLE_VERSION = VERSION FORWARDABLE_VERSION.freeze From 98f9211dc3cf9e1930463dbffa117f2c3b3385b2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:24:25 +0900 Subject: [PATCH 0960/2435] [ruby/open-uri] [DOC] Missing documents https://github.com/ruby/open-uri/commit/1ccc576e9a --- lib/open-uri.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/open-uri.rb b/lib/open-uri.rb index de710af261eaa9..5983c7368b1d35 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -91,8 +91,10 @@ def self.open(name, *rest, &block) module OpenURI + # The version string VERSION = "0.5.0" + # The default options Options = { :proxy => true, :proxy_http_basic_authentication => true, @@ -394,24 +396,28 @@ def OpenURI.open_http(buf, target, proxy, options) # :nodoc: end end + # Raised on HTTP session failure class HTTPError < StandardError - def initialize(message, io) + def initialize(message, io) # :nodoc: super(message) @io = io end + # StringIO having the received data attr_reader :io end # Raised on redirection, # only occurs when +redirect+ option for HTTP is +false+. class HTTPRedirect < HTTPError - def initialize(message, io, uri) + def initialize(message, io, uri) # :nodoc: super(message, io) @uri = uri end + # URI to redirect attr_reader :uri end + # Raised on too many redirection, class TooManyRedirects < HTTPError end From 54b5f83aca2fc27a3e2d00b68109f6b6473122c7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:40:00 +0900 Subject: [PATCH 0961/2435] [ruby/open3] [DOC] Missing documents https://github.com/ruby/open3/commit/e6d09a6aa8 --- lib/open3/version.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/open3/version.rb b/lib/open3/version.rb index bfcec44ccc920b..322dd71e2a3a46 100644 --- a/lib/open3/version.rb +++ b/lib/open3/version.rb @@ -1,3 +1,4 @@ module Open3 + # The version string VERSION = "0.2.1" end From caf40a39963c5badba08bd310e950ae9832a0a3f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:45:10 +0900 Subject: [PATCH 0962/2435] [ruby/pp] [DOC] Suppress documentation for internals https://github.com/ruby/pp/commit/e1f39cb39c --- lib/pp.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index 700a39cdc906a2..fcd33ba80e9e2e 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -174,7 +174,7 @@ def pop_inspect_key(id) Thread.current[:__recursive_key__][:inspect].delete id end - private def guard_inspect(object) + private def guard_inspect(object) # :nodoc: recursive_state = Thread.current[:__recursive_key__] if recursive_state&.key?(:inspect) @@ -277,7 +277,7 @@ def seplist(list, sep=nil, iter_method=:each) # :yield: element kwsplat ? yield(*v, **kwsplat) : yield(*v) } end - EMPTY_KWHASH = if RUBY_VERSION >= "3.0" + EMPTY_KWHASH = if RUBY_VERSION >= "3.0" # :nodoc: {}.freeze end private_constant :EMPTY_KWHASH From 87f863276546e73dfed552fb171f0025912a61c7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:46:28 +0900 Subject: [PATCH 0963/2435] [ruby/prettyprint] [DOC] Missing documents https://github.com/ruby/prettyprint/commit/3a43a4bbf6 --- lib/prettyprint.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb index 6f50192f5daf29..a65407c1304df9 100644 --- a/lib/prettyprint.rb +++ b/lib/prettyprint.rb @@ -33,6 +33,7 @@ # class PrettyPrint + # The version string VERSION = "0.2.0" # This is a convenience method which is same as follows: From f4b18c5d00f70a5dae36ef6466384037f3afd96f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 19 Jun 2024 14:28:19 +0900 Subject: [PATCH 0964/2435] [ruby/rubygems] Hide patchlevel from lockfile https://github.com/ruby/rubygems/commit/9b169c700f --- lib/bundler/ruby_version.rb | 4 +--- spec/bundler/bundler/ruby_version_spec.rb | 10 ++-------- spec/bundler/install/gemfile/ruby_spec.rb | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 0ed5cbc6cacfa2..7f60dde4768ddd 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -43,7 +43,6 @@ def initialize(versions, patchlevel, engine, engine_version) def to_s(versions = self.versions) output = String.new("ruby #{versions_string(versions)}") - output << "p#{patchlevel}" if patchlevel && patchlevel != "-1" output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby" output @@ -72,8 +71,7 @@ def single_version_string def ==(other) versions == other.versions && engine == other.engine && - engine_versions == other.engine_versions && - patchlevel == other.patchlevel + engine_versions == other.engine_versions end def host diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb index 39d0571361ef93..b96893cefe8cc5 100644 --- a/spec/bundler/bundler/ruby_version_spec.rb +++ b/spec/bundler/bundler/ruby_version_spec.rb @@ -100,7 +100,7 @@ describe "#to_s" do it "should return info string with the ruby version, patchlevel, engine, and engine version" do - expect(subject.to_s).to eq("ruby 2.0.0p645 (jruby 2.0.1)") + expect(subject.to_s).to eq("ruby 2.0.0 (jruby 2.0.1)") end context "no patchlevel" do @@ -115,7 +115,7 @@ let(:engine) { "ruby" } it "should return info string with the ruby version and patchlevel" do - expect(subject.to_s).to eq("ruby 2.0.0p645") + expect(subject.to_s).to eq("ruby 2.0.0") end end @@ -149,12 +149,6 @@ it_behaves_like "two ruby versions are not equal" end - context "the patchlevels do not match" do - let(:other_patchlevel) { "21" } - - it_behaves_like "two ruby versions are not equal" - end - context "the engines do not match" do let(:other_engine) { "ruby" } diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb index 3e15d82bbe25cc..313babc81d7595 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -83,7 +83,7 @@ def locked_ruby_version myrack RUBY VERSION - ruby 2.1.4p422 + ruby 2.1.4 BUNDLED WITH #{Bundler::VERSION} From 95c4ca62a891c4bad936c24f97959ae428661585 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:54:32 +0900 Subject: [PATCH 0965/2435] [ruby/singleton] [DOC] Missing documentation Suppress documentation for internals https://github.com/ruby/singleton/commit/4ac0cc497d --- lib/singleton.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/singleton.rb b/lib/singleton.rb index b8e43a7794891d..74aec8903cdc98 100644 --- a/lib/singleton.rb +++ b/lib/singleton.rb @@ -92,9 +92,10 @@ # p a.strip # => nil # module Singleton + # The version string VERSION = "0.3.0" - module SingletonInstanceMethods + module SingletonInstanceMethods # :nodoc: # Raises a TypeError to prevent cloning. def clone raise TypeError, "can't clone instance of singleton #{self.class}" @@ -143,11 +144,11 @@ def set_mutex(val) end end - def self.module_with_class_methods + def self.module_with_class_methods # :nodoc: SingletonClassMethods end - module SingletonClassProperties + module SingletonClassProperties # :nodoc: def self.included(c) # extending an object with Singleton is a bad idea @@ -196,10 +197,10 @@ def included(klass) end if defined?(Ractor) - module RactorLocalSingleton + module RactorLocalSingleton # :nodoc: include Singleton::SingletonInstanceMethods - module RactorLocalSingletonClassMethods + module RactorLocalSingletonClassMethods # :nodoc: include Singleton::SingletonClassMethods def instance set_mutex(Thread::Mutex.new) if Ractor.current[mutex_key].nil? From ae0dd72b96ebd6e968ac639d08126e989cebf366 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:55:27 +0900 Subject: [PATCH 0966/2435] [ruby/tempfile] [DOC] Suppress documentation for internals https://github.com/ruby/tempfile/commit/475d719e4d --- lib/tempfile.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tempfile.rb b/lib/tempfile.rb index 7292e72c252599..27ebb96439c7f3 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -564,6 +564,8 @@ def Tempfile.create(basename="", tmpdir=nil, mode: 0, anonymous: false, **option end class << Tempfile +# :stopdoc: + private def create_with_filename(basename="", tmpdir=nil, mode: 0, **options) tmpfile = nil Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| From d2044ce01744a0f9b70d32d340009326133a1638 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:57:37 +0900 Subject: [PATCH 0967/2435] [ruby/weakref] [DOC] Missing documents https://github.com/ruby/weakref/commit/cccde64080 --- lib/weakref.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/weakref.rb b/lib/weakref.rb index 0a09f7f9936d0f..c7274f96641d81 100644 --- a/lib/weakref.rb +++ b/lib/weakref.rb @@ -17,6 +17,7 @@ # class WeakRef < Delegator + # The version string VERSION = "0.1.4" ## From df9b8fdb16269ca2dde62eab81767c1f81a5cc63 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 18:58:45 +0900 Subject: [PATCH 0968/2435] [ruby/yaml] [DOC] Missing documents https://github.com/ruby/yaml/commit/388cd27291 --- lib/yaml.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/yaml.rb b/lib/yaml.rb index 2cf11fc3dfb04e..c6f0f89fd2a1e0 100644 --- a/lib/yaml.rb +++ b/lib/yaml.rb @@ -66,5 +66,6 @@ # # Syck can also be found on github: https://github.com/ruby/syck module YAML + # The version of YAML wrapper LOADER_VERSION = "0.4.0" end From 9720d499ffc6b1990b6f80cff072c3380e02d10d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 19:06:49 +0900 Subject: [PATCH 0969/2435] [ruby/zlib] [DOC] Missing documents https://github.com/ruby/zlib/commit/25355bc1dc --- ext/zlib/zlib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index d019891afeacae..7e319cae0de3cf 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -1456,6 +1456,7 @@ rb_zstream_finish(VALUE obj) * call-seq: * flush_next_in -> input * + * Flushes input buffer and returns all data in that buffer. */ static VALUE rb_zstream_flush_next_in(VALUE obj) From 286a8eee82767614641414a05d8b9400fb373c94 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 12:21:15 +0900 Subject: [PATCH 0970/2435] Move grouping by upstreams to SyncDefaultGems::Repository It is also useful to distribution changes to each upstream repository. --- tool/auto_review_pr.rb | 13 +------------ tool/sync_default_gems.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tool/auto_review_pr.rb b/tool/auto_review_pr.rb index c63640354d3593..07c98c7e0aed9e 100755 --- a/tool/auto_review_pr.rb +++ b/tool/auto_review_pr.rb @@ -46,7 +46,7 @@ def review(pr_number) changed_files = @client.get("/repos/#{REPO}/pulls/#{pr_number}/files").map { it.fetch(:filename) } # Build a Hash: { upstream_repo => files, ... } - upstream_repos = changed_files.group_by { |file| find_upstream_repo(file) } + upstream_repos = SyncDefaultGems::Repository.group(changed_files) upstream_repos.delete(nil) # exclude no-upstream files upstream_repos.delete('prism') if changed_files.include?('prism_compile.c') # allow prism changes in this case if upstream_repos.empty? @@ -70,17 +70,6 @@ def review(pr_number) private - def find_upstream_repo(file) - SyncDefaultGems::REPOSITORIES.each do |repo_name, repository| - repository.mappings.each do |_src, dst| - if file.start_with?(dst) - return repo_name - end - end - end - nil - end - # upstream_repos: { upstream_repo => files, ... } def format_comment(upstream_repos) comment = +'' diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 997cd5eddc8f16..ff0518b1a599d4 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -293,6 +293,20 @@ def lib((upstream, branch), gemspec_in_subdir: false) ]), }.transform_keys(&:to_s) + class << Repository + def find_upstream(file) + REPOSITORIES.find do |repo_name, repository| + if repository.mappings.any? {|_src, dst| file.start_with?(dst) } + break repo_name + end + end + end + + def group(files) + files.group_by {|file| find_upstream(file)} + end + end + # Allow synchronizing commits up to this FETCH_DEPTH. We've historically merged PRs # with about 250 commits to ruby/ruby, so we use this depth for ruby/ruby in general. FETCH_DEPTH = 500 From 557eec792e520a25c3ca59aa0a824af5d009c7d8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Nov 2025 13:33:38 +0900 Subject: [PATCH 0971/2435] [DOC] Update missing docs --- gem_prelude.rb | 1 - lib/bundled_gems.rb | 5 +++++ tool/mkconfig.rb | 1 + tool/rdoc-srcdir | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gem_prelude.rb b/gem_prelude.rb index bcd2560fab52ca..1b78d80c726625 100644 --- a/gem_prelude.rb +++ b/gem_prelude.rb @@ -25,4 +25,3 @@ rescue LoadError warn "`syntax_suggest' was not loaded." end if defined?(SyntaxSuggest) - diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index 852d7c48e3a217..914c8465032fdc 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -1,5 +1,10 @@ # -*- frozen-string-literal: true -*- +# :stopdoc: +module Gem +end +# :startdoc: + module Gem::BUNDLED_GEMS # :nodoc: SINCE = { "racc" => "3.3.0", diff --git a/tool/mkconfig.rb b/tool/mkconfig.rb index ffa4c1c0b263e0..db741157303256 100755 --- a/tool/mkconfig.rb +++ b/tool/mkconfig.rb @@ -394,6 +394,7 @@ def RbConfig.ruby ) end end +# Non-nil if configured for cross compiling. CROSS_COMPILING = nil unless defined? CROSS_COMPILING EOS diff --git a/tool/rdoc-srcdir b/tool/rdoc-srcdir index f830fdc3027ffd..275f12fe421cd3 100755 --- a/tool/rdoc-srcdir +++ b/tool/rdoc-srcdir @@ -17,7 +17,7 @@ options.title = options.title.sub(/Ruby \K.*version/) { .sort # "MAJOR" < "MINOR", fortunately .to_h.values.join(".") } -options.parse ARGV +options.parse ARGV + ["#{invoked}/rbconfig.rb"] options.singleton_class.define_method(:finish) do super() From b539cd2a33dae904e55fd8f054349b9ff076793a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 10 Nov 2025 07:30:17 -0800 Subject: [PATCH 0972/2435] ZJIT: Deduplicate side exits (#15105) --- zjit/src/asm/mod.rs | 2 +- zjit/src/backend/arm64/mod.rs | 16 ++- zjit/src/backend/lir.rs | 188 +++++++++++++++++++-------------- zjit/src/backend/x86_64/mod.rs | 8 +- zjit/src/codegen.rs | 37 ++++--- 5 files changed, 154 insertions(+), 97 deletions(-) diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index 86176c0ec9bae5..9b792f5f376325 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -16,7 +16,7 @@ pub mod x86_64; pub mod arm64; /// Index to a label created by cb.new_label() -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct Label(pub usize); /// The object that knows how to encode the branch instruction. diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 428d4bff779084..79c07d42ada67c 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -2,6 +2,7 @@ use std::mem::take; use crate::asm::{CodeBlock, Label}; use crate::asm::arm64::*; +use crate::codegen::split_patch_point; use crate::cruby::*; use crate::backend::lir::*; use crate::options::asm_dump; @@ -826,6 +827,14 @@ impl Assembler { *opnds = vec![]; asm.push_insn(insn); } + // For compile_exits, support splitting simple return values here + Insn::CRet(opnd) => { + match opnd { + Opnd::Reg(C_RET_REG) => {}, + _ => asm.load_into(C_RET_OPND, *opnd), + } + asm.cret(C_RET_OPND); + } Insn::Lea { opnd, out } => { *opnd = split_only_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state); let mem_out = split_memory_write(out, SCRATCH0_OPND); @@ -894,6 +903,9 @@ impl Assembler { } } } + &mut Insn::PatchPoint { ref target, invariant, payload } => { + split_patch_point(asm, target, invariant, payload); + } _ => { asm.push_insn(insn); } @@ -1514,7 +1526,7 @@ impl Assembler { Insn::Jonz(opnd, target) => { emit_cmp_zero_jump(cb, opnd.into(), false, target.clone()); }, - Insn::PatchPoint(_) | + Insn::PatchPoint { .. } => unreachable!("PatchPoint should have been lowered to PadPatchPoint in arm64_scratch_split"), Insn::PadPatchPoint => { // If patch points are too close to each other or the end of the block, fill nop instructions if let Some(last_patch_pos) = last_patch_pos { @@ -1694,7 +1706,7 @@ mod tests { let val64 = asm.add(CFP, Opnd::UImm(64)); asm.store(Opnd::mem(64, SP, 0x10), val64); - let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None }; + let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, exit: SideExit { pc: Opnd::const_ptr(0 as *const u8), stack: vec![], locals: vec![] } }; asm.push_insn(Insn::Joz(val64, side_exit)); asm.parallel_mov(vec![(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32)), (C_ARG_OPNDS[1], Opnd::mem(64, SP, -8))]); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 69b030608be776..cb8382a43c940c 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -6,9 +6,10 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; -use crate::hir::SideExitReason; +use crate::hir::{Invariant, SideExitReason}; use crate::options::{TraceExits, debug, get_option}; use crate::cruby::VALUE; +use crate::payload::IseqPayload; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; @@ -25,7 +26,7 @@ pub use crate::backend::current::{ pub static JIT_PRESERVED_REGS: &[Opnd] = &[CFP, SP, EC]; // Memory operand base -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum MemBase { /// Register: Every Opnd::Mem should have MemBase::Reg as of emit. @@ -37,7 +38,7 @@ pub enum MemBase } // Memory location -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct Mem { // Base register number or instruction index @@ -87,7 +88,7 @@ impl fmt::Debug for Mem { } /// Operand to an IR instruction -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum Opnd { None, // For insns with no output @@ -298,6 +299,14 @@ impl From for Opnd { } } +/// Context for a side exit. If `SideExit` matches, it reuses the same code. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct SideExit { + pub pc: Opnd, + pub stack: Vec, + pub locals: Vec, +} + /// Branch target (something that we can jump to) /// for branch instructions #[derive(Clone, Debug)] @@ -309,13 +318,10 @@ pub enum Target Label(Label), /// Side exit to the interpreter SideExit { - pc: *const VALUE, - stack: Vec, - locals: Vec, - /// We use this to enrich asm comments. + /// Context used for compiling the side exit + exit: SideExit, + /// We use this to increment exit counters reason: SideExitReason, - /// Some if the side exit should write this label. We use it for patch points. - label: Option
': missing argument: -r (Gem::OptionParser::MissingArgument) +# optparse-test.rb:9:in '
': missing argument: -r (Gem::OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # @@ -235,7 +236,7 @@ # $ ruby optparse-test.rb --user 2 # # # $ ruby optparse-test.rb --user 3 -# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # @@ -425,7 +426,8 @@ # class Gem::OptionParser # The version string - Gem::OptionParser::Version = "0.6.0" + VERSION = "0.8.0" + Version = VERSION # for compatibility # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -461,6 +463,10 @@ def self.candidate(key, icase = false, pat = nil, &block) candidates end + def self.completable?(key) + String.try_convert(key) or defined?(key.id2name) + end + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end @@ -496,7 +502,6 @@ def convert(opt = nil, val = nil, *) end end - # # Map from option/keyword string to object with completion. # @@ -504,7 +509,6 @@ class OptionMap < Hash include Completion end - # # Individual switch class. Not important to the user. # @@ -546,11 +550,11 @@ def self.pattern def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, - desc = ([] if short or long), block = nil, &_block) + desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern block ||= _block - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # @@ -583,11 +587,15 @@ def parse_arg(arg) # :nodoc: # exception. # def conv_arg(arg, val = []) # :nodoc: + v, = *val if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end + if @values + @values.include?(val) or raise InvalidArgument, v + end return arg, block, val end private :conv_arg @@ -668,7 +676,7 @@ def compsys(sdone, ldone) # :nodoc: (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" - if /^--\[no-\](.+)$/ =~ opt + if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) @@ -1032,7 +1040,6 @@ def match(key) DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} - COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args @@ -1051,16 +1058,16 @@ def compsys(to, name = File.basename($0)) # :nodoc: end def help_exit - if STDOUT.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) less = ENV["LESS"] - args = [{"LESS" => "#{!less || less.empty? ? '-' : less}Fe"}, pager, "w"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] print = proc do |f| f.puts help rescue Errno::EPIPE # pager terminated end if Process.respond_to?(:fork) and false - IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call(STDOUT)} + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} # unreachable end IO.popen(*args, &print) @@ -1102,7 +1109,7 @@ def help_exit # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| - parser.compsys(STDOUT, arg) + parser.compsys($stdout, arg) exit end end @@ -1288,7 +1295,15 @@ def banner # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1467,6 +1482,7 @@ def make_switch(opts, block = nil) klass = nil q, a = nil has_arg = false + values = nil opts.each do |o| # argument class @@ -1480,7 +1496,7 @@ def make_switch(opts, block = nil) end # directly specified pattern(any object possible to match) - if (!(String === o || Symbol === o)) and o.respond_to?(:match) + if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc @@ -1494,7 +1510,12 @@ def make_switch(opts, block = nil) case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set + if Array === o + o, v = o.partition {|v,| Completion.completable?(v)} + values = notwice(v, values, 'values') unless v.empty? + next if o.empty? + end case pattern when CompletingHash when nil @@ -1504,11 +1525,13 @@ def make_switch(opts, block = nil) raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Range + values = notwice(o, values, 'values') when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') - when /^--no-([^\[\]=\s]*)(.+)?/ + when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style @@ -1519,7 +1542,7 @@ def make_switch(opts, block = nil) (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q - when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a @@ -1532,7 +1555,7 @@ def make_switch(opts, block = nil) not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" - when /^--([^\[\]=\s]*)(.+)?/ + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1542,7 +1565,7 @@ def make_switch(opts, block = nil) ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a @@ -1553,7 +1576,7 @@ def make_switch(opts, block = nil) end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1562,7 +1585,7 @@ def make_switch(opts, block = nil) end sdesc << "-#{q}" short << q - when /^=/ + when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else @@ -1571,12 +1594,18 @@ def make_switch(opts, block = nil) end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern + if Range === values and klass + unless (!values.begin or klass === values.begin) and + (!values.end or klass === values.end) + raise ArgumentError, "range does not match class" + end + end if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, - conv, sdesc, ldesc, arg, desc, block) + conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) @@ -1585,7 +1614,7 @@ def make_switch(opts, block = nil) else short << pattern s = (style || default_style).new(pattern, - conv, nil, nil, arg, desc, block) + conv, nil, nil, arg, desc, block, values) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), @@ -1827,7 +1856,7 @@ def permute(*argv, **keywords) # def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, **keywords, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1880,13 +1909,16 @@ def getopts(*args, symbolize_names: false, **keywords) single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1895,16 +1927,16 @@ def getopts(*args, symbolize_names: false, **keywords) arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=), **keywords) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1954,7 +1986,7 @@ def complete(typ, opt, icase = false, *pat) # :nodoc: visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end private :complete @@ -2019,19 +2051,27 @@ def candidate(word) def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end return [ - # XDG - ENV['XDG_CONFIG_HOME'], - '~/.config', + xdg, + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku - '~/config/settings', - ].any? {|dir| + ['~/config/settings', true], + ].any? {|dir, expand| next if !dir or dir.empty? - load(File.expand_path(basename, dir), **keywords) rescue nil + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil } end begin @@ -2237,9 +2277,10 @@ def recover(argv) argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end From 0af941db7a9534587e72a3a9cc96cedda022f13d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 17 Nov 2025 14:25:17 +0900 Subject: [PATCH 1179/2435] [ruby/rubygems] Update resolv-0.6.3 https://github.com/ruby/rubygems/commit/778426fb73 --- lib/rubygems/vendor/resolv/lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index 2825b1ea974ba0..168df21f3ece42 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'socket' -require_relative '../../timeout/lib/timeout' +require_relative '../../../vendored_timeout' require 'io/wait' require_relative '../../../vendored_securerandom' From 59461d123773902be41ac472aed3d52931d08e6d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 17 Nov 2025 12:47:22 +0900 Subject: [PATCH 1180/2435] Use released version of net-http-0.8.0 --- lib/rubygems/vendor/net-http/lib/net/http.rb | 107 ++++++++++++------ .../net-http/lib/net/http/exceptions.rb | 3 +- .../net-http/lib/net/http/generic_request.rb | 45 +++++--- .../vendor/net-http/lib/net/http/header.rb | 12 +- .../vendor/net-http/lib/net/http/requests.rb | 16 ++- .../vendor/net-http/lib/net/http/response.rb | 3 +- .../vendor/net-http/lib/net/http/responses.rb | 72 +++++++++++- tool/bundler/vendor_gems.rb | 5 + tool/bundler/vendor_gems.rb.lock | 25 ++-- 9 files changed, 219 insertions(+), 69 deletions(-) diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index 0e860566148ea6..438f2977c9b948 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -102,14 +102,14 @@ class HTTPHeaderSyntaxError < StandardError; end # # == URIs # - # On the internet, a URI + # On the internet, a Gem::URI # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) # is a string that identifies a particular resource. # It consists of some or all of: scheme, hostname, path, query, and fragment; - # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. + # see {Gem::URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # - # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem/URI/Generic.html] object - # represents an internet URI. + # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem::URI/Generic.html] object + # represents an internet Gem::URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. # @@ -144,7 +144,7 @@ class HTTPHeaderSyntaxError < StandardError; end # # === Queries # - # A host-specific query adds name/value pairs to the URI: + # A host-specific query adds name/value pairs to the Gem::URI: # # _uri = uri.dup # params = {userId: 1, completed: false} @@ -154,7 +154,7 @@ class HTTPHeaderSyntaxError < StandardError; end # # === Fragments # - # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect + # A {Gem::URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect # in \Gem::Net::HTTP; # the same data is returned, regardless of whether a fragment is included. # @@ -327,9 +327,9 @@ class HTTPHeaderSyntaxError < StandardError; end # res = http.request(req) # end # - # Or if you simply want to make a GET request, you may pass in a URI + # Or if you simply want to make a GET request, you may pass in a Gem::URI # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS - # verification if the URI object has a 'https' :URI scheme: + # verification if the Gem::URI object has a 'https' Gem::URI scheme: # # uri # => # # Gem::Net::HTTP.get(uri) @@ -374,7 +374,7 @@ class HTTPHeaderSyntaxError < StandardError; end # # When environment variable 'http_proxy' # is set to a \Gem::URI string, - # the returned +http+ will have the server at that URI as its proxy; + # the returned +http+ will have the server at that Gem::URI as its proxy; # note that the \Gem::URI string must have a protocol # such as 'http' or 'https': # @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.6.0" + VERSION = "0.8.0" HTTPVersion = '1.1' begin require 'zlib' @@ -790,7 +790,7 @@ def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil) # "completed": false # } # - # With URI object +uri+ and optional hash argument +headers+: + # With Gem::URI object +uri+ and optional hash argument +headers+: # # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1') # headers = {'Content-type' => 'application/json; charset=UTF-8'} @@ -863,7 +863,7 @@ def HTTP.post(url, data, header = nil) # Posts data to a host; returns a Gem::Net::HTTPResponse object. # - # Argument +url+ must be a URI; + # Argument +url+ must be a Gem::URI; # argument +data+ must be a hash: # # _uri = uri.dup @@ -1179,6 +1179,7 @@ def initialize(address, port = nil) # :nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1321,6 +1322,9 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets wheter the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1529,7 +1533,7 @@ def use_ssl=(flag) :verify_hostname, ] # :nodoc: - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc: + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1632,6 +1636,21 @@ def start # :yield: http self end + # Finishes the \HTTP session: + # + # http = Gem::Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1654,14 +1673,36 @@ def connect end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" - end - } + begin + s = + case @tcpsocket_supports_open_timeout + when nil, true + begin + # Use built-in timeout in TCPSocket.open if available + sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + @tcpsocket_supports_open_timeout = true + sock + rescue ArgumentError => e + raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) + @tcpsocket_supports_open_timeout = false + + # Fallback to Gem::Timeout.timeout if TCPSocket.open does not support open_timeout + Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + when false + # The current Ruby is known to not support TCPSocket(open_timeout:). + # Directly fall back to Gem::Timeout.timeout to avoid performance penalty incured by rescue. + Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + rescue => e + e = Gem::Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1758,20 +1799,6 @@ def on_connect end private :on_connect - # Finishes the \HTTP session: - # - # http = Gem::Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish - end - def do_finish @started = false @socket.close if @socket @@ -1821,6 +1848,8 @@ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ss } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1860,7 +1889,7 @@ def proxy_from_env? @proxy_from_env end - # The proxy URI determined from the environment for this connection. + # The proxy Gem::URI determined from the environment for this connection. def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= Gem::URI::HTTP.new( @@ -1915,6 +1944,7 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) require 'cgi/escape' @@ -1943,6 +1973,7 @@ def edit_path(path) path end end + # :startdoc: # # HTTP operations @@ -2397,6 +2428,8 @@ def send_entity(path, data, initheader, dest, type, &block) res end + # :stopdoc: + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) @@ -2554,7 +2587,7 @@ def debug(msg) alias_method :D, :debug end - # for backward compatibility until Ruby 3.5 + # for backward compatibility until Ruby 4.0 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb index c629c0113b1a91..218df9a8bd1524 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb index 5cfe75a7cde734..d6496d4ac191e3 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -102,6 +99,31 @@ def inspect "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Gem::Net::HTTP::Post.new(uri) + # post.inspect # => "#" + # post.pretty_inspect + # # => # ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -220,7 +242,7 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only end if host = self['host'] - host.sub!(/:.*/m, '') + host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -239,6 +261,8 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -260,7 +284,6 @@ def finish def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +294,6 @@ def send_request_with_body_stream(sock, ver, path, f) raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +395,6 @@ def flush_buffer(out, buf, chunked_p) buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +427,3 @@ def write_header(sock, ver, path) end end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb index 5cb1da01ec07f6..bc68cd2eefb130 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ def add_field(key, val) end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ def add_field(key, val) ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ def connection_close? false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb index 45727e7f61fc0f..f990761042dad8 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest # - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb index cbbd191d879ba7..dc164f1504488a 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb @@ -153,6 +153,7 @@ def read_new(sock) #:nodoc: internal use only end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb index 0f26ae6c26c1ab..62ce1cba1b2917 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Gem::Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,19 +1158,21 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Gem::Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, '3' => Gem::Net::HTTPRedirection, '4' => Gem::Net::HTTPClientError, '5' => Gem::Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Gem::Net::HTTPContinue, '101' => Gem::Net::HTTPSwitchProtocol, @@ -1170,5 +1238,5 @@ class Gem::Net::HTTPResponse '508' => Gem::Net::HTTPLoopDetected, '510' => Gem::Net::HTTPNotExtended, '511' => Gem::Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index 72546faf31632e..dd1325e84e4221 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -4,8 +4,13 @@ gem "fileutils", "1.7.3" gem "molinillo", github: "cocoapods/molinillo" +<<<<<<< HEAD gem "net-http", github: "ruby/net-http", ref: "d8fd39c589279b1aaec85a7c8de9b3e199c72efe" gem "net-http-persistent", github: "hsbt/net-http-persistent", ref: "9b6fbd733cf35596dfe7f80c0c154f9f3d17dbdb" +======= +gem "net-http", "0.8.0" +gem "net-http-persistent", "4.0.6" +>>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) gem "net-protocol", "0.2.2" gem "optparse", "0.6.0" gem "pub_grub", github: "jhawthorn/pub_grub", ref: "df6add45d1b4d122daff2f959c9bd1ca93d14261" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index b1a2351e949e4d..3221b53e58e8c2 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -19,19 +19,18 @@ GIT specs: pub_grub (0.5.0) -GIT - remote: https://github.com/ruby/net-http.git - revision: d8fd39c589279b1aaec85a7c8de9b3e199c72efe - ref: d8fd39c589279b1aaec85a7c8de9b3e199c72efe - specs: - net-http (0.6.0) - uri - GEM remote: https://rubygems.org/ specs: connection_pool (2.4.1) fileutils (1.7.3) +<<<<<<< HEAD +======= + net-http (0.8.0) + uri (>= 0.11.1) + net-http-persistent (4.0.6) + connection_pool (~> 2.2, >= 2.2.4) +>>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (0.2.2) timeout optparse (0.6.0) @@ -54,8 +53,13 @@ PLATFORMS DEPENDENCIES fileutils (= 1.7.3) molinillo! +<<<<<<< HEAD net-http! net-http-persistent! +======= + net-http (= 0.8.0) + net-http-persistent (= 4.0.6) +>>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (= 0.2.2) optparse (= 0.6.0) pub_grub! @@ -70,8 +74,13 @@ CHECKSUMS connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 fileutils (1.7.3) sha256=57271e854b694a87755d76f836f5c57b2c9538ebbaf4b2154bb66addf15eb5da molinillo (0.8.0) +<<<<<<< HEAD net-http (0.6.0) net-http-persistent (4.0.6) +======= + net-http (0.8.0) sha256=df42c47ce9f9e95ad32a317c97c12f945bc1af365288837ea4ff259876ecb46d + net-http-persistent (4.0.6) sha256=2abb3a04438edf6cb9e0e7e505969605f709eda3e3c5211beadd621a2c84dd5d +>>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 optparse (0.6.0) sha256=25e90469c1cd44048a89dc01c1dde9d5f0bdf717851055fb18237780779b068c pub_grub (0.5.0) From 27770210615a56655af3837fd2d4c250354ea226 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 11:58:00 +0900 Subject: [PATCH 1181/2435] Downgrade net-http 0.7.0 because JRuby is not working --- lib/rubygems/vendor/net-http/lib/net/http.rb | 81 ++++++------------- .../net-http/lib/net/http/exceptions.rb | 3 +- .../net-http/lib/net/http/generic_request.rb | 11 +-- .../vendor/net-http/lib/net/http/header.rb | 12 +-- .../vendor/net-http/lib/net/http/requests.rb | 16 +--- .../vendor/net-http/lib/net/http/response.rb | 3 +- .../vendor/net-http/lib/net/http/responses.rb | 68 ---------------- tool/bundler/vendor_gems.rb | 6 ++ tool/bundler/vendor_gems.rb.lock | 15 ++++ 9 files changed, 58 insertions(+), 157 deletions(-) diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index 438f2977c9b948..b5b93befffd4f3 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.8.0" + VERSION = "0.7.0" HTTPVersion = '1.1' begin require 'zlib' @@ -1179,7 +1179,6 @@ def initialize(address, port = nil) # :nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] - @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1322,9 +1321,6 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass - - # Sets wheter the proxy uses SSL; - # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1636,21 +1632,6 @@ def start # :yield: http self end - # Finishes the \HTTP session: - # - # http = Gem::Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish - end - - # :stopdoc: def do_start connect @started = true @@ -1673,36 +1654,14 @@ def connect end debug "opening connection to #{conn_addr}:#{conn_port}..." - begin - s = - case @tcpsocket_supports_open_timeout - when nil, true - begin - # Use built-in timeout in TCPSocket.open if available - sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) - @tcpsocket_supports_open_timeout = true - sock - rescue ArgumentError => e - raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) - @tcpsocket_supports_open_timeout = false - - # Fallback to Gem::Timeout.timeout if TCPSocket.open does not support open_timeout - Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } - end - when false - # The current Ruby is known to not support TCPSocket(open_timeout:). - # Directly fall back to Gem::Timeout.timeout to avoid performance penalty incured by rescue. - Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } - end - rescue => e - e = Gem::Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" - end + s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + begin + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + rescue => e + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end + } s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1799,6 +1758,20 @@ def on_connect end private :on_connect + # Finishes the \HTTP session: + # + # http = Gem::Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + def do_finish @started = false @socket.close if @socket @@ -1848,8 +1821,6 @@ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ss } end - # :startdoc: - class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1944,7 +1915,6 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private - # :stopdoc: def unescape(value) require 'cgi/escape' @@ -1973,7 +1943,6 @@ def edit_path(path) path end end - # :startdoc: # # HTTP operations @@ -2428,8 +2397,6 @@ def send_entity(path, data, initheader, dest, type, &block) res end - # :stopdoc: - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) @@ -2587,7 +2554,7 @@ def debug(msg) alias_method :D, :debug end - # for backward compatibility until Ruby 4.0 + # for backward compatibility until Ruby 3.5 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb index 218df9a8bd1524..c629c0113b1a91 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions # :nodoc: + module HTTPExceptions def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,7 +12,6 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end - # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb index d6496d4ac191e3..0737b3f02afa95 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb @@ -19,13 +19,16 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path - hostname = uri_or_path.host + hostname = uri_or_path.hostname raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup + host = @uri.hostname.dup + host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil + host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -48,7 +51,7 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= @uri.authority if @uri + self['Host'] ||= host if host @body = nil @body_stream = nil @body_data = nil @@ -242,7 +245,7 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only end if host = self['host'] - host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header + host.sub!(/:.*/m, '') elsif host = @uri.host else host = addr @@ -261,8 +264,6 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private - # :stopdoc: - class Chunker #:nodoc: def initialize(sock) @sock = sock diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb index bc68cd2eefb130..5cb1da01ec07f6 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb @@ -179,9 +179,7 @@ # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader - # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 - # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -269,7 +267,6 @@ def add_field(key, val) end end - # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -297,7 +294,6 @@ def add_field(key, val) ary.push val end end - # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -494,7 +490,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) # :nodoc: + def capitalize(name) name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -961,12 +957,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) # :nodoc: + def basic_encode(account, password) 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode - # Returns whether the HTTP session is to be closed. +# Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -974,7 +970,7 @@ def connection_close? false end - # Returns whether the HTTP session is to be kept alive. +# Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb index f990761042dad8..45727e7f61fc0f 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb @@ -29,7 +29,6 @@ # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -61,7 +60,6 @@ class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -97,7 +95,6 @@ class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -133,7 +130,6 @@ class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest # - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -166,7 +162,6 @@ class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -198,7 +193,6 @@ class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -230,7 +224,6 @@ class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -265,7 +258,6 @@ class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -293,7 +285,6 @@ class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -317,7 +308,6 @@ class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -341,7 +331,6 @@ class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -365,7 +354,6 @@ class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -389,7 +377,6 @@ class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -413,7 +400,6 @@ class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -437,8 +423,8 @@ class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest - # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end + diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb index dc164f1504488a..cbbd191d879ba7 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb @@ -153,7 +153,6 @@ def read_new(sock) #:nodoc: internal use only end private - # :stopdoc: def read_status_line(sock) str = sock.readline @@ -260,7 +259,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect # :nodoc: + def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb index 62ce1cba1b2917..32738adc14429f 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb @@ -4,9 +4,7 @@ module Gem::Net - # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -21,7 +19,6 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse - # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -37,7 +34,6 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -53,7 +49,6 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -68,7 +63,6 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -83,7 +77,6 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse - # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -101,7 +94,6 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -119,7 +111,6 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -136,7 +127,6 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -155,7 +145,6 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation - # :stopdoc: HAS_BODY = false end @@ -173,7 +162,6 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -191,7 +179,6 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -209,7 +196,6 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -229,7 +215,6 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -247,7 +232,6 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess - # :stopdoc: HAS_BODY = false end @@ -266,7 +250,6 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess - # :stopdoc: HAS_BODY = false end @@ -285,7 +268,6 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -303,7 +285,6 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -323,7 +304,6 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -341,7 +321,6 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess - # :stopdoc: HAS_BODY = true end @@ -359,7 +338,6 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection - # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -378,7 +356,6 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -396,7 +373,6 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection - # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -414,7 +390,6 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -432,7 +407,6 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection - # :stopdoc: HAS_BODY = false end @@ -449,7 +423,6 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection - # :stopdoc: HAS_BODY = false end @@ -467,7 +440,6 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -484,7 +456,6 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection - # :stopdoc: HAS_BODY = true end @@ -501,7 +472,6 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -518,7 +488,6 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -535,7 +504,6 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -553,7 +521,6 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -570,7 +537,6 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -587,7 +553,6 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -605,7 +570,6 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -622,7 +586,6 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -639,7 +602,6 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -657,7 +619,6 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -675,7 +636,6 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -693,7 +653,6 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -711,7 +670,6 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -728,7 +686,6 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -746,7 +703,6 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -765,7 +721,6 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -782,7 +737,6 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError - # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -800,7 +754,6 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -821,7 +774,6 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -838,7 +790,6 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -854,7 +805,6 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -871,7 +821,6 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -891,7 +840,6 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -908,7 +856,6 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -925,7 +872,6 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -943,7 +889,6 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError - # :stopdoc: HAS_BODY = true end @@ -961,7 +906,6 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError - # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -982,7 +926,6 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1000,7 +943,6 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1018,7 +960,6 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1036,7 +977,6 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1054,7 +994,6 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError - # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1072,7 +1011,6 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1089,7 +1027,6 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1106,7 +1043,6 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1123,7 +1059,6 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError - # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1141,7 +1076,6 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError - # :stopdoc: HAS_BODY = true end @@ -1158,14 +1092,12 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError - # :stopdoc: HAS_BODY = true end end class Gem::Net::HTTPResponse - # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index dd1325e84e4221..d779d4a3c911cc 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -2,6 +2,7 @@ source "https://rubygems.org" +<<<<<<< HEAD gem "fileutils", "1.7.3" gem "molinillo", github: "cocoapods/molinillo" <<<<<<< HEAD @@ -9,6 +10,11 @@ gem "net-http-persistent", github: "hsbt/net-http-persistent", ref: "9b6fbd733cf35596dfe7f80c0c154f9f3d17dbdb" ======= gem "net-http", "0.8.0" +======= +gem "fileutils", "1.8.0" +gem "molinillo", github: "cocoapods/molinillo", ref: "1d62d7d5f448e79418716dc779a4909509ccda2a" +gem "net-http", "0.7.0" # net-http-0.8.0 is broken with JRuby +>>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) gem "net-http-persistent", "4.0.6" >>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) gem "net-protocol", "0.2.2" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 3221b53e58e8c2..ec3f569add47fe 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -22,12 +22,19 @@ GIT GEM remote: https://rubygems.org/ specs: +<<<<<<< HEAD connection_pool (2.4.1) fileutils (1.7.3) <<<<<<< HEAD ======= net-http (0.8.0) uri (>= 0.11.1) +======= + connection_pool (2.5.4) + fileutils (1.8.0) + net-http (0.7.0) + uri +>>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (4.0.6) connection_pool (~> 2.2, >= 2.2.4) >>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) @@ -53,11 +60,15 @@ PLATFORMS DEPENDENCIES fileutils (= 1.7.3) molinillo! +<<<<<<< HEAD <<<<<<< HEAD net-http! net-http-persistent! ======= net-http (= 0.8.0) +======= + net-http (= 0.7.0) +>>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (= 4.0.6) >>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (= 0.2.2) @@ -74,11 +85,15 @@ CHECKSUMS connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 fileutils (1.7.3) sha256=57271e854b694a87755d76f836f5c57b2c9538ebbaf4b2154bb66addf15eb5da molinillo (0.8.0) +<<<<<<< HEAD <<<<<<< HEAD net-http (0.6.0) net-http-persistent (4.0.6) ======= net-http (0.8.0) sha256=df42c47ce9f9e95ad32a317c97c12f945bc1af365288837ea4ff259876ecb46d +======= + net-http (0.7.0) sha256=4db7d9f558f8ffd4dcf832d0aefd02320c569c7d4f857def49e585069673a425 +>>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (4.0.6) sha256=2abb3a04438edf6cb9e0e7e505969605f709eda3e3c5211beadd621a2c84dd5d >>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 From ccdf83f17a115b53749ec22816467b2aa4f0ddc2 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 18 Nov 2025 06:52:35 +0000 Subject: [PATCH 1182/2435] Update bundled gems list as of 2025-11-18 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 03debc5aae1a59..bd84c081ac4d07 100644 --- a/NEWS.md +++ b/NEWS.md @@ -218,7 +218,7 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.26.1 +* minitest 5.26.2 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 3245e9efbc6ae7..2d5c6bdaa1cea1 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.26.1 https://github.com/minitest/minitest +minitest 5.26.2 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.1 https://github.com/test-unit/test-unit From 54c07383095957c49d91300af6012ff43d41160f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 18 Nov 2025 16:10:33 +0900 Subject: [PATCH 1183/2435] [ruby/resolv] Fix syntax error on older versions https://github.com/ruby/resolv/commit/599f78c451 --- test/resolv/test_dns.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 86a1ecf569c181..d5d2648e1bc649 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -525,7 +525,7 @@ def test_no_server if RUBY_PLATFORM.match?(/mingw/) # cannot repo locally omit 'Timeout Error on MinGW CI' - elsif macos?([26,1]..) + elsif macos?([26,1]..[]) omit 'Timeout Error on macOS 26.1+' else raise Timeout::Error From f272aabb5c007d4a8f15e141edbd1bf2d079c5fc Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Mon, 17 Nov 2025 21:08:29 -0600 Subject: [PATCH 1184/2435] [ruby/json] Use #if instead of #ifdef when checking for JSON_DEBUG so debugging code is not generated when JSON_DEBUG=0. https://github.com/ruby/json/commit/4f1adb10d3 --- ext/json/fbuffer/fbuffer.h | 12 ++++++------ ext/json/generator/extconf.rb | 2 +- ext/json/parser/extconf.rb | 2 +- ext/json/vendor/fpconv.c | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index ecba61465e46c0..752d153b31d5d5 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -14,7 +14,7 @@ typedef struct FBufferStruct { unsigned long initial_length; unsigned long len; unsigned long capa; -#ifdef JSON_DEBUG +#if JSON_DEBUG unsigned long requested; #endif char *ptr; @@ -45,14 +45,14 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char * fb->ptr = stack_buffer; fb->capa = stack_buffer_size; } -#ifdef JSON_DEBUG +#if JSON_DEBUG fb->requested = 0; #endif } static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed) { -#ifdef JSON_DEBUG +#if JSON_DEBUG if (consumed > fb->requested) { rb_bug("fbuffer: Out of bound write"); } @@ -122,7 +122,7 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) { -#ifdef JSON_DEBUG +#if JSON_DEBUG fb->requested = requested; #endif @@ -148,7 +148,7 @@ static inline void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long /* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) { -#ifdef JSON_DEBUG +#if JSON_DEBUG if (fb->requested < 1) { rb_bug("fbuffer: unreserved write"); } @@ -174,7 +174,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) fbuffer_inc_capa(fb, repeat * len); while (repeat) { -#ifdef JSON_DEBUG +#if JSON_DEBUG fb->requested = len; #endif fbuffer_append_reserved(fb, newstr, len); diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index fb9afd07f7bfee..ee1bbeaba76346 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -6,7 +6,7 @@ else append_cflags("-std=c99") $defs << "-DJSON_GENERATOR" - $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] + $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) load __dir__ + "/../simd/conf.rb" diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index 079b37382d7bc3..2440e66d8bdb39 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'mkmf' -$defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] +$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c index e91c7889c26b5d..4888ab4b8bcefe 100644 --- a/ext/json/vendor/fpconv.c +++ b/ext/json/vendor/fpconv.c @@ -29,7 +29,7 @@ #include #include -#ifdef JSON_DEBUG +#if JSON_DEBUG #include #endif @@ -472,7 +472,7 @@ static int fpconv_dtoa(double d, char dest[28]) int ndigits = grisu2(d, digits, &K); str_len += emit_digits(digits, ndigits, dest + str_len, K, neg); -#ifdef JSON_DEBUG +#if JSON_DEBUG assert(str_len <= 32); #endif From 85abc59c32fc79c88db98bb0b9d9665254b0ff98 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 27 Oct 2025 11:35:09 +0000 Subject: [PATCH 1185/2435] [DOC] Add documentation about Ruby's VM stack --- doc/contributing/vm_stack_and_frames.md | 163 ++++++++++++++++++++++++ doc/yarv_frame_layout.md | 77 ----------- doc/zjit.md | 2 + 3 files changed, 165 insertions(+), 77 deletions(-) create mode 100644 doc/contributing/vm_stack_and_frames.md delete mode 100644 doc/yarv_frame_layout.md diff --git a/doc/contributing/vm_stack_and_frames.md b/doc/contributing/vm_stack_and_frames.md new file mode 100644 index 00000000000000..c7dc59db16793c --- /dev/null +++ b/doc/contributing/vm_stack_and_frames.md @@ -0,0 +1,163 @@ +# Ruby VM Stack and Frame Layout + +This document explains the Ruby VM stack architecture, including how the value +stack (SP) and control frames (CFP) share a single contiguous memory region, +and how individual frames are structured. + +## VM Stack Architecture + +The Ruby VM uses a single contiguous stack (`ec->vm_stack`) with two different +regions growing toward each other. Understanding this requires distinguishing +the overall architecture (how CFPs and values share one stack) from individual +frame internals (how values are organized for one single frame). + +```text +High addresses (ec->vm_stack + ec->vm_stack_size) + ↓ + [CFP region starts here] ← RUBY_VM_END_CONTROL_FRAME(ec) + [CFP - 1] New frame pushed here (grows downward) + [CFP - 2] Another frame + ... + + (Unused space - stack overflow when they meet) + + ... Value stack grows UP toward higher addresses + [SP + n] Values pushed here + [ec->cfp->sp] Current executing frame's stack pointer + ↑ +Low addresses (ec->vm_stack) +``` + +The "unused space" represents free space available for new frames and values. When this gap closes (CFP meets SP), stack overflow occurs. + +### Stack Growth Directions + +**Control Frames (CFP):** + +- Start at `ec->vm_stack + ec->vm_stack_size` (high addresses) +- Grow **downward** toward lower addresses as frames are pushed +- Each new frame is allocated at `cfp - 1` (lower address) +- The `rb_control_frame_t` structure itself moves downward + +**Value Stack (SP):** + +- Starts at `ec->vm_stack` (low addresses) +- Grows **upward** toward higher addresses as values are pushed +- Each frame's `cfp->sp` points to the top of its value stack + +### Stack Overflow + +When recursive calls push too many frames, CFP grows downward until it collides +with SP growing upward. The VM detects this with `CHECK_VM_STACK_OVERFLOW0`, +which computes `const rb_control_frame_struct *bound = (void *)&sp[margin];` +and raises if `cfp <= &bound[1]`. + +## Understanding Individual Frame Value Stacks + +Each frame has its own portion of the overall VM stack, called its "VM value stack" +or simply "value stack". This space is pre-allocated when the frame is created, +with size determined by: + +- `local_size` - space for local variables +- `stack_max` - maximum depth for temporary values during execution + +The frame's value stack grows upward from its base (where self/arguments/locals +live) toward `cfp->sp` (the current top of temporary values). + +## Visualizing How Frames Fit in the VM Stack + +The left side shows the overall VM stack with CFP metadata separated from frame +values. The right side zooms into one frame's value region, revealing its internal +structure. + +```text +Overall VM Stack (ec->vm_stack): Zooming into Frame 2's value stack: + +High addr (vm_stack + vm_stack_size) High addr (cfp->sp) + ↓ ┌ + [CFP 1 metadata] │ [Temporaries] + [CFP 2 metadata] ─────────┐ │ [Env: Flags/Block/CME] ← cfp->ep + [CFP 3 metadata] │ │ [Locals] + ──────────────── │ ┌─┤ [Arguments] + (unused space) │ │ │ [self] + ──────────────── │ │ └ + [Frame 3 values] │ │ Low addr (frame base) + [Frame 2 values] <────────┴───────┘ + [Frame 1 values] + ↑ +Low addr (vm_stack) +``` + +## Examining a Single Frame's Value Stack + +Now let's walk through a concrete Ruby program to see how a single frame's +value stack is structured internally: + +```ruby +def foo(x, y) + z = x.casecmp(y) +end + +foo(:one, :two) +``` + +First, after arguments are evaluated and right before the `send` to `foo`: + +```text + ┌────────────┐ + putself │ :two │ + putobject :one 0x2 ├────────────┤ + putobject :two │ :one │ +► send <:foo, argc:2> 0x1 ├────────────┤ + leave │ self │ + 0x0 └────────────┘ +``` + +The `put*` instructions have pushed 3 items onto the stack. It's now time to +add a new control frame for `foo`. The following is the shape of the stack +after one instruction in `foo`: + +```text + cfp->sp=0x8 at this point. + 0x8 ┌────────────┐◄──Stack space for temporaries + │ :one │ live above the environment. + 0x7 ├────────────┤ + getlocal x@0 │ < flags > │ foo's rb_control_frame_t +► getlocal y@1 0x6 ├────────────┤◄──has cfp->ep=0x6 + send <:casecmp, argc:1> │ │ + dup 0x5 ├────────────┤ The flags, block, and CME triple + setlocal z@2 │ │ (VM_ENV_DATA_SIZE) form an + leave 0x4 ├────────────┤ environment. They can be used to + │ z (nil) │ figure out what local variables + 0x3 ├────────────┤ are below them. + │ :two │ + 0x2 ├────────────┤ Notice how the arguments, now + │ :one │ locals, never moved. This layout + 0x1 ├────────────┤ allows for argument transfer + │ self │ without copying. + 0x0 └────────────┘ +``` + +Given that locals have lower address than `cfp->ep`, it makes sense then that +`getlocal` in `insns.def` has `val = *(vm_get_ep(GET_EP(), level) - idx);`. +When accessing variables in the immediate scope, where `level=0`, it's +essentially `val = cfp->ep[-idx];`. + +Note that this EP-relative index has a different basis than the index that comes +after "@" in disassembly listings. The "@" index is relative to the 0th local +(`x` in this case). + +### Q&A + +Q: It seems that the receiver is always at an offset relative to EP, + like locals. Couldn't we use EP to access it instead of using `cfp->self`? + +A: Not all calls put the `self` in the callee on the stack. Two + examples are `Proc#call`, where the receiver is the Proc object, but `self` + inside the callee is `Proc#receiver`, and `yield`, where the receiver isn't + pushed onto the stack before the arguments. + +Q: Why have `cfp->ep` when it seems that everything is below `cfp->sp`? + +A: In the example, `cfp->ep` points to the stack, but it can also point to the + GC heap. Blocks can capture and evacuate their environment to the heap. diff --git a/doc/yarv_frame_layout.md b/doc/yarv_frame_layout.md deleted file mode 100644 index ea8ad013cf85a6..00000000000000 --- a/doc/yarv_frame_layout.md +++ /dev/null @@ -1,77 +0,0 @@ -# YARV Frame Layout - -This document is an introduction to what happens on the VM stack as the VM -services calls. The code holds the ultimate truth for this subject, so beware -that this document can become stale. - -We'll walk through the following program, with explanation at selected points -in execution and abridged disassembly listings: - -```ruby -def foo(x, y) - z = x.casecmp(y) -end - -foo(:one, :two) -``` - -First, after arguments are evaluated and right before the `send` to `foo`: - -``` - ┌────────────┐ - putself │ :two │ - putobject :one 0x2 ├────────────┤ - putobject :two │ :one │ -► send <:foo, argc:2> 0x1 ├────────────┤ - leave │ self │ - 0x0 └────────────┘ -``` - -The `put*` instructions have pushed 3 items onto the stack. It's now time to -add a new control frame for `foo`. The following is the shape of the stack -after one instruction in `foo`: - -``` - cfp->sp=0x8 at this point. - 0x8 ┌────────────┐◄──Stack space for temporaries - │ :one │ live above the environment. - 0x7 ├────────────┤ - getlocal x@0 │ < flags > │ foo's rb_control_frame_t -► getlocal y@1 0x6 ├────────────┤◄──has cfp->ep=0x6 - send <:casecmp, argc:1> │ │ - dup 0x5 ├────────────┤ The flags, block, and CME triple - setlocal z@2 │ │ (VM_ENV_DATA_SIZE) form an - leave 0x4 ├────────────┤ environment. They can be used to - │ z (nil) │ figure out what local variables - 0x3 ├────────────┤ are below them. - │ :two │ - 0x2 ├────────────┤ Notice how the arguments, now - │ :one │ locals, never moved. This layout - 0x1 ├────────────┤ allows for argument transfer - │ self │ without copying. - 0x0 └────────────┘ -``` - -Given that locals have lower address than `cfp->ep`, it makes sense then that -`getlocal` in `insns.def` has `val = *(vm_get_ep(GET_EP(), level) - idx);`. -When accessing variables in the immediate scope, where `level=0`, it's -essentially `val = cfp->ep[-idx];`. - -Note that this EP-relative index has a different basis the index that comes -after "@" in disassembly listings. The "@" index is relative to the 0th local -(`x` in this case). - -## Q&A - -Q: It seems that the receiver is always at an offset relative to EP, - like locals. Couldn't we use EP to access it instead of using `cfp->self`? - -A: Not all calls put the `self` in the callee on the stack. Two - examples are `Proc#call`, where the receiver is the Proc object, but `self` - inside the callee is `Proc#receiver`, and `yield`, where the receiver isn't - pushed onto the stack before the arguments. - -Q: Why have `cfp->ep` when it seems that everything is below `cfp->sp`? - -A: In the example, `cfp->ep` points to the stack, but it can also point to the - GC heap. Blocks can capture and evacuate their environment to the heap. diff --git a/doc/zjit.md b/doc/zjit.md index 0c50bd44120639..3d7ee33abfa438 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -204,6 +204,8 @@ Ruby execution involves three distinct stacks and understanding them will help y The Ruby VM uses a single contiguous memory region (`ec->vm_stack`) containing two sub-stacks that grow toward each other. When they meet, stack overflow occurs. +See [doc/contributing/vm_stack_and_frames.md](contributing/vm_stack_and_frames.md) for detailed architecture and frame layout. + **Control Frame Stack:** - **Stores**: Frame metadata (`rb_control_frame_t` structures) From a1c76c7e3aeb403132a486ce96e3ae8a5ee0ea0a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 18:47:32 +0900 Subject: [PATCH 1186/2435] Fixed conflict of vendor_gems.rb --- tool/bundler/vendor_gems.rb | 17 ++------- tool/bundler/vendor_gems.rb.lock | 63 +++++++------------------------- 2 files changed, 17 insertions(+), 63 deletions(-) diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index d779d4a3c911cc..8d12c5addeb279 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -2,27 +2,16 @@ source "https://rubygems.org" -<<<<<<< HEAD -gem "fileutils", "1.7.3" -gem "molinillo", github: "cocoapods/molinillo" -<<<<<<< HEAD -gem "net-http", github: "ruby/net-http", ref: "d8fd39c589279b1aaec85a7c8de9b3e199c72efe" -gem "net-http-persistent", github: "hsbt/net-http-persistent", ref: "9b6fbd733cf35596dfe7f80c0c154f9f3d17dbdb" -======= -gem "net-http", "0.8.0" -======= gem "fileutils", "1.8.0" gem "molinillo", github: "cocoapods/molinillo", ref: "1d62d7d5f448e79418716dc779a4909509ccda2a" gem "net-http", "0.7.0" # net-http-0.8.0 is broken with JRuby ->>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) gem "net-http-persistent", "4.0.6" ->>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) gem "net-protocol", "0.2.2" -gem "optparse", "0.6.0" +gem "optparse", "0.8.0" gem "pub_grub", github: "jhawthorn/pub_grub", ref: "df6add45d1b4d122daff2f959c9bd1ca93d14261" gem "resolv", "0.6.2" gem "securerandom", "0.4.1" -gem "timeout", "0.4.3" +gem "timeout", "0.4.4" gem "thor", "1.4.0" gem "tsort", "0.2.0" -gem "uri", "1.0.4" +gem "uri", "1.1.1" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index ec3f569add47fe..cc7886e60b6054 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -1,17 +1,10 @@ GIT remote: https://github.com/cocoapods/molinillo.git revision: 1d62d7d5f448e79418716dc779a4909509ccda2a + ref: 1d62d7d5f448e79418716dc779a4909509ccda2a specs: molinillo (0.8.0) -GIT - remote: https://github.com/hsbt/net-http-persistent.git - revision: 9b6fbd733cf35596dfe7f80c0c154f9f3d17dbdb - ref: 9b6fbd733cf35596dfe7f80c0c154f9f3d17dbdb - specs: - net-http-persistent (4.0.6) - connection_pool (~> 2.2, >= 2.2.4) - GIT remote: https://github.com/jhawthorn/pub_grub.git revision: df6add45d1b4d122daff2f959c9bd1ca93d14261 @@ -22,31 +15,21 @@ GIT GEM remote: https://rubygems.org/ specs: -<<<<<<< HEAD - connection_pool (2.4.1) - fileutils (1.7.3) -<<<<<<< HEAD -======= - net-http (0.8.0) - uri (>= 0.11.1) -======= connection_pool (2.5.4) fileutils (1.8.0) net-http (0.7.0) uri ->>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (4.0.6) connection_pool (~> 2.2, >= 2.2.4) ->>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (0.2.2) timeout - optparse (0.6.0) + optparse (0.8.0) resolv (0.6.2) securerandom (0.4.1) thor (1.4.0) - timeout (0.4.3) + timeout (0.4.4) tsort (0.2.0) - uri (1.0.4) + uri (1.1.1) PLATFORMS java @@ -58,53 +41,35 @@ PLATFORMS x86_64-linux DEPENDENCIES - fileutils (= 1.7.3) + fileutils (= 1.8.0) molinillo! -<<<<<<< HEAD -<<<<<<< HEAD - net-http! - net-http-persistent! -======= - net-http (= 0.8.0) -======= net-http (= 0.7.0) ->>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (= 4.0.6) ->>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (= 0.2.2) - optparse (= 0.6.0) + optparse (= 0.8.0) pub_grub! resolv (= 0.6.2) securerandom (= 0.4.1) thor (= 1.4.0) - timeout (= 0.4.3) + timeout (= 0.4.4) tsort (= 0.2.0) - uri (= 1.0.4) + uri (= 1.1.1) CHECKSUMS - connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 - fileutils (1.7.3) sha256=57271e854b694a87755d76f836f5c57b2c9538ebbaf4b2154bb66addf15eb5da + connection_pool (2.5.4) sha256=e9e1922327416091f3f6542f5f4446c2a20745276b9aa796dd0bb2fd0ea1e70a + fileutils (1.8.0) sha256=8c6b1df54e2540bdb2f39258f08af78853aa70bad52b4d394bbc6424593c6e02 molinillo (0.8.0) -<<<<<<< HEAD -<<<<<<< HEAD - net-http (0.6.0) - net-http-persistent (4.0.6) -======= - net-http (0.8.0) sha256=df42c47ce9f9e95ad32a317c97c12f945bc1af365288837ea4ff259876ecb46d -======= net-http (0.7.0) sha256=4db7d9f558f8ffd4dcf832d0aefd02320c569c7d4f857def49e585069673a425 ->>>>>>> 198b10c12d6 (Downgrade net-http 0.7.0 because JRuby is not working) net-http-persistent (4.0.6) sha256=2abb3a04438edf6cb9e0e7e505969605f709eda3e3c5211beadd621a2c84dd5d ->>>>>>> 7e6ce7be57a (Use released version of net-http-0.8.0) net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 - optparse (0.6.0) sha256=25e90469c1cd44048a89dc01c1dde9d5f0bdf717851055fb18237780779b068c + optparse (0.8.0) sha256=ef6b7fbaf7ec331474f325bc08dd5622e6e1e651007a5341330ee4b08ce734f0 pub_grub (0.5.0) resolv (0.6.2) sha256=61efe545cedddeb1b14f77e51f85c85ca66af5098fdbf567fadf32c34590fb14 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d - timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e + timeout (0.4.4) sha256=f0f6f970104b82427cd990680f539b6bbb8b1e55efa913a55c6492935e4e0edb tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f - uri (1.0.4) sha256=34485d137c079f8753a0ca1d883841a7ba2e5fae556e3c30c2aab0dde616344b + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.0.dev + 4.0.0.dev From f3e8bc877076e4d0759cd788c4a8f87d88ffc836 Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Sun, 26 Jul 2020 16:45:41 -0500 Subject: [PATCH 1187/2435] [ruby/rubygems] use DidYouMean::SpellChecker for gem suggestions replaces Bundler::SimilarityDetector with DidYouMean::SpellChecker https://github.com/ruby/rubygems/commit/959bea1506 --- lib/bundler/cli/common.rb | 24 ++++++++++++++++++++---- lib/bundler/similarity_detector.rb | 3 +++ spec/bundler/bundler/cli_common_spec.rb | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 spec/bundler/bundler/cli_common_spec.rb diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 7608adf2ef9e9c..a756e8eed3cc23 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -94,11 +94,11 @@ def self.ask_for_spec_from(specs) end def self.gem_not_found_message(missing_gem_name, alternatives) - require_relative "../similarity_detector" + require "did_you_mean/spell_checker" message = "Could not find gem '#{missing_gem_name}'." - alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } - suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name) - message += "\nDid you mean #{suggestions}?" if suggestions + alternate_names = alternatives.map { |a| a.respond_to?(:name) ? a.name : a } + suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name) + message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? message end @@ -134,5 +134,21 @@ def self.clean_after_install? clean &&= !Bundler.use_system_gems? clean end + + protected + + def self.word_list(words) + if words.empty? + return '' + end + + words = words.map { |word| "'#{word}'" } + + if words.length == 1 + return words[0] + end + + [words[0..-2].join(", "), words[-1]].join(" or ") + end end end diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb index 50e66b9cab20e8..1f4e61e940b3cb 100644 --- a/lib/bundler/similarity_detector.rb +++ b/lib/bundler/similarity_detector.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# This module is not used anywhere in Bundler +# It is included for backwards compatibility in-case someone is relying on it + module Bundler class SimilarityDetector SimilarityScore = Struct.new(:string, :distance) diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb new file mode 100644 index 00000000000000..de29d732658c6b --- /dev/null +++ b/spec/bundler/bundler/cli_common_spec.rb @@ -0,0 +1,18 @@ +require 'bundler/cli' + +RSpec.describe Bundler::CLI::Common do + describe 'gem_not_found_message' do + it 'should suggest alternate gem names' do + message = subject.gem_not_found_message('ralis', ['BOGUS']) + expect(message).to match("Could not find gem 'ralis'.$") + message = subject.gem_not_found_message("ralis", ["rails"]) + expect(message).to match("Did you mean 'rails'?") + message = subject.gem_not_found_message("meail", %w[email fail eval]) + expect(message).to match("Did you mean 'email'?") + message = subject.gem_not_found_message("nokogri", %w[nokogiri rails sidekiq dog]) + expect(message).to match("Did you mean 'nokogiri'?") + message = subject.gem_not_found_message("methosd", %w[method methods bogus]) + expect(message).to match("Did you mean 'methods' or 'method'?") + end + end +end \ No newline at end of file From 6aa16246f78de49df5ebd6dc5ea3a8c26830025a Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Sun, 26 Jul 2020 17:35:02 -0500 Subject: [PATCH 1188/2435] [ruby/rubygems] Rubocop https://github.com/ruby/rubygems/commit/a6bc30a827 --- lib/bundler/cli/common.rb | 10 +++++----- spec/bundler/bundler/cli_common_spec.rb | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index a756e8eed3cc23..c0eefadf510bc3 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -96,8 +96,8 @@ def self.ask_for_spec_from(specs) def self.gem_not_found_message(missing_gem_name, alternatives) require "did_you_mean/spell_checker" message = "Could not find gem '#{missing_gem_name}'." - alternate_names = alternatives.map { |a| a.respond_to?(:name) ? a.name : a } - suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name) + alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } + suggestions = DidYouMean::SpellChecker.new(:dictionary => alternate_names).correct(missing_gem_name) message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? message end @@ -135,14 +135,14 @@ def self.clean_after_install? clean end - protected + protected def self.word_list(words) if words.empty? - return '' + return "" end - words = words.map { |word| "'#{word}'" } + words = words.map {|word| "'#{word}'" } if words.length == 1 return words[0] diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb index de29d732658c6b..ab7d69b969bca1 100644 --- a/spec/bundler/bundler/cli_common_spec.rb +++ b/spec/bundler/bundler/cli_common_spec.rb @@ -1,9 +1,11 @@ -require 'bundler/cli' +# frozen_string_literal: true + +require "bundler/cli" RSpec.describe Bundler::CLI::Common do - describe 'gem_not_found_message' do - it 'should suggest alternate gem names' do - message = subject.gem_not_found_message('ralis', ['BOGUS']) + describe "gem_not_found_message" do + it "should suggest alternate gem names" do + message = subject.gem_not_found_message("ralis", ["BOGUS"]) expect(message).to match("Could not find gem 'ralis'.$") message = subject.gem_not_found_message("ralis", ["rails"]) expect(message).to match("Did you mean 'rails'?") @@ -15,4 +17,4 @@ expect(message).to match("Did you mean 'methods' or 'method'?") end end -end \ No newline at end of file +end From dd6ccb44f0a5207ea5a0308f0917a23f7157dcfe Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Sat, 1 Aug 2020 20:30:32 -0500 Subject: [PATCH 1189/2435] [ruby/rubygems] Progressively enhance if DidYouMean is available https://github.com/ruby/rubygems/commit/a02353fb96 --- lib/bundler/cli/common.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index c0eefadf510bc3..b410ba9d26a5e7 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -94,11 +94,12 @@ def self.ask_for_spec_from(specs) end def self.gem_not_found_message(missing_gem_name, alternatives) - require "did_you_mean/spell_checker" message = "Could not find gem '#{missing_gem_name}'." - alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } - suggestions = DidYouMean::SpellChecker.new(:dictionary => alternate_names).correct(missing_gem_name) - message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? + if defined?(DidYouMean::SpellChecker) + alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } + suggestions = DidYouMean::SpellChecker.new(:dictionary => alternate_names).correct(missing_gem_name) + message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? + end message end From 55afec32ff255b74ee7490177ed7ae0f4b5bde29 Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Sat, 1 Aug 2020 21:53:48 -0500 Subject: [PATCH 1190/2435] [ruby/rubygems] fix tests https://github.com/ruby/rubygems/commit/1dc669a0ab --- lib/bundler/cli/common.rb | 6 ++++-- spec/bundler/bundler/cli_common_spec.rb | 2 ++ spec/bundler/commands/update_spec.rb | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index b410ba9d26a5e7..7de6fb7fdb232a 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -95,8 +95,10 @@ def self.ask_for_spec_from(specs) def self.gem_not_found_message(missing_gem_name, alternatives) message = "Could not find gem '#{missing_gem_name}'." - if defined?(DidYouMean::SpellChecker) - alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } + alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } + if alternate_names.include?(missing_gem_name.downcase) + message += "\nDid you mean '#{missing_gem_name.downcase}'?" + elsif defined?(DidYouMean::SpellChecker) suggestions = DidYouMean::SpellChecker.new(:dictionary => alternate_names).correct(missing_gem_name) message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? end diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb index ab7d69b969bca1..37a92c20e64629 100644 --- a/spec/bundler/bundler/cli_common_spec.rb +++ b/spec/bundler/bundler/cli_common_spec.rb @@ -9,6 +9,8 @@ expect(message).to match("Could not find gem 'ralis'.$") message = subject.gem_not_found_message("ralis", ["rails"]) expect(message).to match("Did you mean 'rails'?") + message = subject.gem_not_found_message("Rails", ["rails"]) + expect(message).to match("Did you mean 'rails'?") message = subject.gem_not_found_message("meail", %w[email fail eval]) expect(message).to match("Did you mean 'email'?") message = subject.gem_not_found_message("nokogri", %w[nokogiri rails sidekiq dog]) diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 92b9e4df0a6b47..61d8ece2798a75 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -193,7 +193,7 @@ end it "should suggest alternatives" do bundle "update platformspecific", raise_on_error: false - expect(err).to include "Did you mean platform_specific?" + expect(err).to include "Did you mean 'platform_specific'?" end end From 2c169e15884d163e0a3b939f9d6a30b55c3e3b4f Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Sat, 1 Aug 2020 22:14:43 -0500 Subject: [PATCH 1191/2435] [ruby/rubygems] More tests https://github.com/ruby/rubygems/commit/210fa87f65 --- spec/bundler/commands/open_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index e0c79aa407a3b3..72038152f4f0cc 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -49,7 +49,7 @@ it "suggests alternatives for similar-sounding gems" do bundle "open Rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false - expect(err).to match(/did you mean rails\?/i) + expect(err).to match(/did you mean 'rails'\?/i) end it "opens the gem with short words" do From e3c483b51d036664cd1d0da0896114ce38550fc4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 14:43:44 +0900 Subject: [PATCH 1192/2435] [ruby/rubygems] Removed unused SimilarityDetector https://github.com/ruby/rubygems/commit/40ace48651 --- lib/bundler/similarity_detector.rb | 66 ------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 lib/bundler/similarity_detector.rb diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb deleted file mode 100644 index 1f4e61e940b3cb..00000000000000 --- a/lib/bundler/similarity_detector.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -# This module is not used anywhere in Bundler -# It is included for backwards compatibility in-case someone is relying on it - -module Bundler - class SimilarityDetector - SimilarityScore = Struct.new(:string, :distance) - - # initialize with an array of words to be matched against - def initialize(corpus) - @corpus = corpus - end - - # return an array of words similar to 'word' from the corpus - def similar_words(word, limit = 3) - words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) } - words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string) - end - - # return the result of 'similar_words', concatenated into a list - # (eg "a, b, or c") - def similar_word_list(word, limit = 3) - words = similar_words(word, limit) - if words.length == 1 - words[0] - elsif words.length > 1 - [words[0..-2].join(", "), words[-1]].join(" or ") - end - end - - protected - - # https://www.informit.com/articles/article.aspx?p=683059&seqNum=36 - def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1) - # ins, del, sub are weighted costs - return nil if this.nil? - return nil if that.nil? - dm = [] # distance matrix - - # Initialize first row values - dm[0] = (0..this.length).collect {|i| i * ins } - fill = [0] * (this.length - 1) - - # Initialize first column values - (1..that.length).each do |i| - dm[i] = [i * del, fill.flatten] - end - - # populate matrix - (1..that.length).each do |i| - (1..this.length).each do |j| - # critical comparison - dm[i][j] = [ - dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub), - dm[i][j - 1] + ins, - dm[i - 1][j] + del, - ].min - end - end - - # The last value in matrix is the Levenshtein distance between the strings - dm[that.length][this.length] - end - end -end From c87b36aba1706c907caed5d7189103c27aaf81c0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 14:44:29 +0900 Subject: [PATCH 1193/2435] [ruby/rubygems] bin/rubocop -a https://github.com/ruby/rubygems/commit/fee8dd2f08 --- lib/bundler/cli/common.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 7de6fb7fdb232a..2f332ff364404d 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -99,7 +99,7 @@ def self.gem_not_found_message(missing_gem_name, alternatives) if alternate_names.include?(missing_gem_name.downcase) message += "\nDid you mean '#{missing_gem_name.downcase}'?" elsif defined?(DidYouMean::SpellChecker) - suggestions = DidYouMean::SpellChecker.new(:dictionary => alternate_names).correct(missing_gem_name) + suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name) message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? end message @@ -138,8 +138,6 @@ def self.clean_after_install? clean end - protected - def self.word_list(words) if words.empty? return "" From e78a96b729d2817967857157cef476e1da295c09 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 15:15:16 +0900 Subject: [PATCH 1194/2435] [ruby/rubygems] Spelling with the latest version of did_you_mean https://github.com/ruby/rubygems/commit/d604c1d1cb --- spec/bundler/commands/open_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index 72038152f4f0cc..77e7815017ee55 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -80,12 +80,12 @@ it "suggests alternatives for similar-sounding gems when using subpath" do bundle "open Rails --path README.md", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false - expect(err).to match(/did you mean rails\?/i) + expect(err).to match(/did you mean 'rails'\?/i) end it "suggests alternatives for similar-sounding gems when using deep subpath" do bundle "open Rails --path some/path/here", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false - expect(err).to match(/did you mean rails\?/i) + expect(err).to match(/did you mean 'rails'\?/i) end it "opens subpath of the short worded gem" do From f168a6d0c2485b3bad5883865cc68ed16fd2ae5f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 18 Nov 2025 18:32:44 +0900 Subject: [PATCH 1195/2435] [ruby/rubygems] Handle to reverse order result in Ruby 3.2 https://github.com/ruby/rubygems/actions/runs/19458155903/job/55676075439?pr=3857 ``` -Did you mean 'methods' or 'method'? +Could not find gem 'methosd'. +Did you mean 'method' or 'methods'? ``` https://github.com/ruby/rubygems/commit/ab7d0fc7b0 --- spec/bundler/bundler/cli_common_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb index 37a92c20e64629..015894b3a13e2e 100644 --- a/spec/bundler/bundler/cli_common_spec.rb +++ b/spec/bundler/bundler/cli_common_spec.rb @@ -16,7 +16,7 @@ message = subject.gem_not_found_message("nokogri", %w[nokogiri rails sidekiq dog]) expect(message).to match("Did you mean 'nokogiri'?") message = subject.gem_not_found_message("methosd", %w[method methods bogus]) - expect(message).to match("Did you mean 'methods' or 'method'?") + expect(message).to match(/Did you mean 'method(|s)' or 'method(|s)'?/) end end end From 522b7d823fb00821eea8d0cf13f33a73e91c0ab7 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 18 Nov 2025 21:18:26 +0900 Subject: [PATCH 1196/2435] [ruby/openssl] ssl: fix test_pqc_sigalg on RHEL 9.7 RHEL 9.7 ships OpenSSL 3.5.1 with ML-DSA support, but it is disabled for TLS by default, according to the system configuration file: /etc/crypto-policies/back-ends/opensslcnf.config Specify SSLContext#sigalgs to override the default list. https://github.com/ruby/openssl/commit/fac3a26748 --- test/openssl/test_ssl.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 5082dadd1b7971..5d20ccd1f4b2e4 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -2084,6 +2084,7 @@ def test_pqc_sigalg ctx_proc = -> ctx { # Unset values set by start_server ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65" ctx.add_certificate(mldsa_cert, mldsa) ctx.add_certificate(rsa_cert, rsa) } From c38486ffef14f4991288afe9c0d8d23f57b617fc Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 18 Nov 2025 13:44:40 +0100 Subject: [PATCH 1197/2435] ZJIT: Validate types for all instructions * This can catch subtle errors early, so avoid a fallback case and handle every instruction explicitly. --- zjit/src/hir.rs | 180 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 121 insertions(+), 59 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4df3ddbb26f162..d68ddd2479ebd0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3828,33 +3828,132 @@ impl Function { let insn_id = self.union_find.borrow().find_const(insn_id); let insn = self.find(insn_id); match insn { - Insn::StringCopy { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), - Insn::StringIntern { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), - Insn::ArrayDup { val, .. } => self.assert_subtype(insn_id, val, types::ArrayExact), - Insn::StringAppend { recv, other, .. } => { - self.assert_subtype(insn_id, recv, types::StringExact)?; - self.assert_subtype(insn_id, other, types::String) + // Instructions with no InsnId operands (except state) or nothing to assert + Insn::Const { .. } + | Insn::Param + | Insn::PutSpecialObject { .. } + | Insn::LoadField { .. } + | Insn::GetConstantPath { .. } + | Insn::IsBlockGiven + | Insn::GetGlobal { .. } + | Insn::LoadPC + | Insn::LoadSelf + | Insn::Snapshot { .. } + | Insn::Jump { .. } + | Insn::EntryPoint { .. } + | Insn::GuardBlockParamProxy { .. } + | Insn::PatchPoint { .. } + | Insn::SideExit { .. } + | Insn::IncrCounter { .. } + | Insn::IncrCounterPtr { .. } + | Insn::CheckInterrupts { .. } + | Insn::CCall { .. } + | Insn::GetClassVar { .. } + | Insn::GetSpecialNumber { .. } + | Insn::GetSpecialSymbol { .. } + | Insn::GetLocal { .. } => { + Ok(()) + } + // Instructions with 1 Ruby object operand + Insn::Test { val } + | Insn::IsNil { val } + | Insn::IsMethodCfunc { val, .. } + | Insn::GuardShape { val, .. } + | Insn::GuardNotFrozen { val, .. } + | Insn::SetGlobal { val, .. } + | Insn::SetLocal { val, .. } + | Insn::SetClassVar { val, .. } + | Insn::Return { val } + | Insn::Throw { val, .. } + | Insn::ObjToString { val, .. } + | Insn::GuardType { val, .. } + | Insn::GuardTypeNot { val, .. } + | Insn::ToArray { val, .. } + | Insn::ToNewArray { val, .. } + | Insn::Defined { v: val, .. } + | Insn::ObjectAlloc { val, .. } + | Insn::DupArrayInclude { target: val, .. } + | Insn::GetIvar { self_val: val, .. } + | Insn::FixnumBitCheck { val, .. } // TODO (https://github.com/Shopify/ruby/issues/859) this should check Fixnum, but then test_checkkeyword_tests_fixnum_bit fails + | Insn::DefinedIvar { self_val: val, .. } => { + self.assert_subtype(insn_id, val, types::BasicObject) + } + // Instructions with 2 Ruby object operands + Insn::SetIvar { self_val: left, val: right, .. } + | Insn::SetInstanceVariable { self_val: left, val: right, .. } + | Insn::NewRange { low: left, high: right, .. } + | Insn::AnyToString { val: left, str: right, .. } => { + self.assert_subtype(insn_id, left, types::BasicObject)?; + self.assert_subtype(insn_id, right, types::BasicObject) + } + // Instructions with recv and a Vec of Ruby objects + Insn::SendWithoutBlock { recv, ref args, .. } + | Insn::SendWithoutBlockDirect { recv, ref args, .. } + | Insn::Send { recv, ref args, .. } + | Insn::SendForward { recv, ref args, .. } + | Insn::InvokeSuper { recv, ref args, .. } + | Insn::CCallVariadic { recv, ref args, .. } + | Insn::ArrayInclude { target: recv, elements: ref args, .. } => { + self.assert_subtype(insn_id, recv, types::BasicObject)?; + for &arg in args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) + } + // Instructions with a Vec of Ruby objects + Insn::CCallWithFrame { ref args, .. } + | Insn::InvokeBuiltin { ref args, .. } + | Insn::InvokeBlock { ref args, .. } + | Insn::NewArray { elements: ref args, .. } + | Insn::ArrayMax { elements: ref args, .. } => { + for &arg in args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) } Insn::NewHash { ref elements, .. } => { if elements.len() % 2 != 0 { return Err(ValidationError::MiscValidationError(insn_id, "NewHash elements length is not even".to_string())); } + for &element in elements { + self.assert_subtype(insn_id, element, types::BasicObject)?; + } + Ok(()) + } + Insn::StringConcat { ref strings, .. } + | Insn::ToRegexp { values: ref strings, .. } => { + for &string in strings { + self.assert_subtype(insn_id, string, types::String)?; + } Ok(()) } - Insn::NewRangeFixnum { low, high, .. } => { - self.assert_subtype(insn_id, low, types::Fixnum)?; - self.assert_subtype(insn_id, high, types::Fixnum) + // Instructions with String operands + Insn::StringCopy { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), + Insn::StringIntern { val, .. } => self.assert_subtype(insn_id, val, types::StringExact), + Insn::StringAppend { recv, other, .. } => { + self.assert_subtype(insn_id, recv, types::StringExact)?; + self.assert_subtype(insn_id, other, types::String) } + // Instructions with Array operands + Insn::ArrayDup { val, .. } => self.assert_subtype(insn_id, val, types::ArrayExact), Insn::ArrayExtend { left, right, .. } => { // TODO(max): Do left and right need to be ArrayExact? self.assert_subtype(insn_id, left, types::Array)?; self.assert_subtype(insn_id, right, types::Array) } - Insn::ArrayPush { array, .. } => self.assert_subtype(insn_id, array, types::Array), - Insn::ArrayPop { array, .. } => self.assert_subtype(insn_id, array, types::Array), - Insn::ArrayLength { array, .. } => self.assert_subtype(insn_id, array, types::Array), + Insn::ArrayPush { array, .. } + | Insn::ArrayPop { array, .. } + | Insn::ArrayLength { array, .. } => { + self.assert_subtype(insn_id, array, types::Array) + } + Insn::ArrayArefFixnum { array, index } => { + self.assert_subtype(insn_id, array, types::Array)?; + self.assert_subtype(insn_id, index, types::Fixnum) + } + // Instructions with Hash operands Insn::HashAref { hash, .. } => self.assert_subtype(insn_id, hash, types::Hash), Insn::HashDup { val, .. } => self.assert_subtype(insn_id, val, types::HashExact), + // Other Insn::ObjectAllocClass { class, .. } => { let has_leaf_allocator = unsafe { rb_zjit_class_has_default_allocator(class) } || class_has_leaf_allocator(class); if !has_leaf_allocator { @@ -3862,9 +3961,6 @@ impl Function { } Ok(()) } - Insn::Test { val } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::IsNil { val } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::IsMethodCfunc { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::IsBitEqual { left, right } | Insn::IsBitNotEqual { left, right } => { if self.is_a(left, types::CInt) && self.is_a(right, types::CInt) { @@ -3878,41 +3974,15 @@ impl Function { return Err(ValidationError::MiscValidationError(insn_id, "IsBitEqual can only compare CInt/CInt or RubyValue/RubyValue".to_string())); } } - Insn::BoxBool { val } => self.assert_subtype(insn_id, val, types::CBool), - Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64), - Insn::UnboxFixnum { val } => self.assert_subtype(insn_id, val, types::Fixnum), - Insn::SetGlobal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::GetIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), - Insn::SetIvar { self_val, val, .. } => { - self.assert_subtype(insn_id, self_val, types::BasicObject)?; - self.assert_subtype(insn_id, val, types::BasicObject) - } - Insn::DefinedIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject), - Insn::SetLocal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::SetClassVar { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::IfTrue { val, .. } | Insn::IfFalse { val, .. } => self.assert_subtype(insn_id, val, types::CBool), - Insn::SendWithoutBlock { recv, ref args, .. } - | Insn::SendWithoutBlockDirect { recv, ref args, .. } - | Insn::Send { recv, ref args, .. } - | Insn::SendForward { recv, ref args, .. } - | Insn::InvokeSuper { recv, ref args, .. } - | Insn::CCallVariadic { recv, ref args, .. } => { - self.assert_subtype(insn_id, recv, types::BasicObject)?; - for &arg in args { - self.assert_subtype(insn_id, arg, types::BasicObject)?; - } - Ok(()) + Insn::BoxBool { val } + | Insn::IfTrue { val, .. } + | Insn::IfFalse { val, .. } => { + self.assert_subtype(insn_id, val, types::CBool) } - Insn::CCallWithFrame { ref args, .. } - | Insn::InvokeBuiltin { ref args, .. } - | Insn::InvokeBlock { ref args, .. } => { - for &arg in args { - self.assert_subtype(insn_id, arg, types::BasicObject)?; - } - Ok(()) + Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64), + Insn::UnboxFixnum { val } => { + self.assert_subtype(insn_id, val, types::Fixnum) } - Insn::Return { val } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::Throw { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::FixnumAdd { left, right, .. } | Insn::FixnumSub { left, right, .. } | Insn::FixnumMult { left, right, .. } @@ -3927,17 +3997,11 @@ impl Function { | Insn::FixnumAnd { left, right } | Insn::FixnumOr { left, right } | Insn::FixnumXor { left, right } + | Insn::NewRangeFixnum { low: left, high: right, .. } => { self.assert_subtype(insn_id, left, types::Fixnum)?; self.assert_subtype(insn_id, right, types::Fixnum) } - Insn::ObjToString { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::AnyToString { val, str, .. } => { - self.assert_subtype(insn_id, val, types::BasicObject)?; - self.assert_subtype(insn_id, str, types::BasicObject) - } - Insn::GuardType { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::GuardTypeNot { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), Insn::GuardBitEquals { val, expected, .. } => { match expected { Const::Value(_) => self.assert_subtype(insn_id, val, types::RubyValue), @@ -3954,9 +4018,8 @@ impl Function { Const::CPtr(_) => self.assert_subtype(insn_id, val, types::CPtr), } } - Insn::GuardShape { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::GuardNotFrozen { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject), - Insn::GuardLess { left, right, .. } | Insn::GuardGreaterEq { left, right, .. } => { + Insn::GuardLess { left, right, .. } + | Insn::GuardGreaterEq { left, right, .. } => { self.assert_subtype(insn_id, left, types::CInt64)?; self.assert_subtype(insn_id, right, types::CInt64) }, @@ -3969,7 +4032,6 @@ impl Function { self.assert_subtype(insn_id, index, types::Fixnum)?; self.assert_subtype(insn_id, value, types::Fixnum) } - _ => Ok(()), } } From f84bbb423836d9d0d018b8ab71ecceb5868fd5be Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 17 Nov 2025 08:15:16 -0800 Subject: [PATCH 1198/2435] ZJIT: add support for lazy `RubyVM::ZJIT.enable` This implements Shopify#854: - Splits boot-time and enable-time initialization, tracks progress with `InitializationState` enum - Introduces `RubyVM::ZJIT.enable` Ruby method for enabling the JIT lazily, if not already enabled - Introduces `--zjit-disable` flag, which can be used alongside the other `--zjit-*` flags but prevents enabling the JIT at boot time - Adds ZJIT infra to support JIT hooks, but this is not currently exercised (Shopify/ruby#667) Left for future enhancements: - Support kwargs for overriding the CLI flags in `RubyVM::ZJIT.enable` Closes Shopify#854 --- jit_hook.rb | 5 +- ruby.c | 19 ++++---- test/ruby/test_zjit.rb | 38 +++++++++++++++ version.c | 9 ++++ zjit.c | 1 + zjit.rb | 25 ++++++++++ zjit/src/options.rs | 25 ++++++---- zjit/src/state.rs | 103 ++++++++++++++++++++++++++++++++++++----- 8 files changed, 194 insertions(+), 31 deletions(-) diff --git a/jit_hook.rb b/jit_hook.rb index 487361c049ed32..346b7169480031 100644 --- a/jit_hook.rb +++ b/jit_hook.rb @@ -3,9 +3,8 @@ class Module # This method is removed in jit_undef.rb. private def with_jit(&block) # :nodoc: # ZJIT currently doesn't compile Array#each properly, so it's disabled for now. - if defined?(RubyVM::ZJIT) && Primitive.rb_zjit_option_enabled_p && false # TODO: remove `&& false` (Shopify/ruby#667) - # We don't support lazily enabling ZJIT yet, so we can call the block right away. - block.call + if defined?(RubyVM::ZJIT) && false # TODO: remove `&& false` (Shopify/ruby#667) + RubyVM::ZJIT.send(:add_jit_hook, block) elsif defined?(RubyVM::YJIT) RubyVM::YJIT.send(:add_jit_hook, block) end diff --git a/ruby.c b/ruby.c index 872a317e3bbf7e..f1089ca41e9173 100644 --- a/ruby.c +++ b/ruby.c @@ -1842,10 +1842,8 @@ ruby_opt_init(ruby_cmdline_options_t *opt) rb_yjit_init(opt->yjit); #endif #if USE_ZJIT - if (opt->zjit) { - extern void rb_zjit_init(void); - rb_zjit_init(); - } + extern void rb_zjit_init(bool); + rb_zjit_init(opt->zjit); #endif ruby_set_script_name(opt->script_name); @@ -2368,6 +2366,12 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) #if USE_ZJIT if (!FEATURE_USED_P(opt->features, zjit) && env_var_truthy("RUBY_ZJIT_ENABLE")) { FEATURE_SET(opt->features, FEATURE_BIT(zjit)); + + // When the --zjit flag is specified, we would have call setup_zjit_options(""), + // which would have called rb_zjit_prepare_options() internally. This ensures we + // go through the same set up but with less overhead than setup_zjit_options(""). + extern void rb_zjit_prepare_options(); + rb_zjit_prepare_options(); } #endif } @@ -2383,10 +2387,9 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } #endif #if USE_ZJIT - if (FEATURE_SET_P(opt->features, zjit) && !opt->zjit) { - extern void rb_zjit_prepare_options(void); - rb_zjit_prepare_options(); - opt->zjit = true; + if (FEATURE_SET_P(opt->features, zjit)) { + bool rb_zjit_option_enable(void); + opt->zjit = rb_zjit_option_enable(); // set opt->zjit for Init_ruby_description() and calling rb_zjit_init() } #endif diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 13ee4a45c83188..2e04dbddd55b21 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -59,6 +59,44 @@ def test_enable_through_env end end + def test_zjit_enable + assert_separately([], <<~'RUBY') + refute_predicate RubyVM::ZJIT, :enabled? + refute_predicate RubyVM::ZJIT, :stats_enabled? + refute_includes RUBY_DESCRIPTION, "+ZJIT" + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + refute_predicate RubyVM::ZJIT, :stats_enabled? + assert_includes RUBY_DESCRIPTION, "+ZJIT" + RUBY + end + + def test_zjit_disable + assert_separately(["--zjit", "--zjit-disable"], <<~'RUBY') + refute_predicate RubyVM::ZJIT, :enabled? + refute_includes RUBY_DESCRIPTION, "+ZJIT" + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + assert_includes RUBY_DESCRIPTION, "+ZJIT" + RUBY + end + + def test_zjit_enable_respects_existing_options + assert_separately(['--zjit-disable', '--zjit-stats=quiet'], <<~RUBY) + refute_predicate RubyVM::ZJIT, :enabled? + assert_predicate RubyVM::ZJIT, :stats_enabled? + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + assert_predicate RubyVM::ZJIT, :stats_enabled? + RUBY + end + def test_call_itself assert_compiles '42', <<~RUBY, call_threshold: 2 def test = 42.itself diff --git a/version.c b/version.c index b9c57a71b82b17..366ba1b2b5c6b7 100644 --- a/version.c +++ b/version.c @@ -276,6 +276,15 @@ ruby_set_yjit_description(void) define_ruby_description(YJIT_DESCRIPTION); } +void +ruby_set_zjit_description(void) +{ + VALUE mRuby = rb_path2class("Ruby"); + rb_const_remove(rb_cObject, rb_intern("RUBY_DESCRIPTION")); + rb_const_remove(mRuby, rb_intern("DESCRIPTION")); + define_ruby_description(ZJIT_DESCRIPTION); +} + void ruby_show_version(void) { diff --git a/zjit.c b/zjit.c index d1f192801a2ec8..b8a89aad1a8498 100644 --- a/zjit.c +++ b/zjit.c @@ -305,6 +305,7 @@ rb_zjit_class_has_default_allocator(VALUE klass) VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp); // Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them. +VALUE rb_zjit_enable(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key); VALUE rb_zjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self); diff --git a/zjit.rb b/zjit.rb index 7cdf84cfbe4328..f5ed347f2cb6ab 100644 --- a/zjit.rb +++ b/zjit.rb @@ -7,6 +7,8 @@ # This module may not exist if ZJIT does not support the particular platform # for which CRuby is built. module RubyVM::ZJIT + # Blocks that are called when YJIT is enabled + @jit_hooks = [] # Avoid calling a Ruby method here to avoid interfering with compilation tests if Primitive.rb_zjit_print_stats_p at_exit { print_stats } @@ -22,6 +24,18 @@ def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end + # Enable ZJIT compilation. + def enable + return false if enabled? + + if Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p)' + warn("Only one JIT can be enabled at the same time.") + return false + end + + Primitive.rb_zjit_enable + end + # Check if `--zjit-trace-exits` is used def trace_exit_locations_enabled? Primitive.rb_zjit_trace_exit_locations_enabled_p @@ -234,6 +248,17 @@ def assert_compiles # :nodoc: # :stopdoc: private + # Register a block to be called when ZJIT is enabled + def add_jit_hook(hook) + @jit_hooks << hook + end + + # Run ZJIT hooks registered by `#with_jit` + def call_jit_hooks + @jit_hooks.each(&:call) + @jit_hooks.clear + end + def print_counters(keys, buf:, stats:, right_align: false, base: nil) key_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 key_align = '-' unless right_align diff --git a/zjit/src/options.rs b/zjit/src/options.rs index cd3a6439719b3f..c165035eaa1af0 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -45,7 +45,7 @@ pub struct Options { /// Number of times YARV instructions should be profiled. pub num_profiles: NumProfiles, - /// Enable YJIT statsitics + /// Enable ZJIT statistics pub stats: bool, /// Print stats on exit (when stats is also true) @@ -54,6 +54,10 @@ pub struct Options { /// Enable debug logging pub debug: bool, + // Whether to enable JIT at boot. This option prevents other + // ZJIT tuning options from enabling ZJIT at boot. + pub disable: bool, + /// Turn off the HIR optimizer pub disable_hir_opt: bool, @@ -97,6 +101,7 @@ impl Default for Options { stats: false, print_stats: false, debug: false, + disable: false, disable_hir_opt: false, dump_hir_init: None, dump_hir_opt: None, @@ -123,6 +128,8 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-num-profiles=num", "Number of profiled calls before JIT (default: 5)."), ("--zjit-stats[=quiet]", "Enable collecting ZJIT statistics (=quiet to suppress output)."), + ("--zjit-disable", + "Disable ZJIT for lazily enabling it with RubyVM::ZJIT.enable."), ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", "Log compiled ISEQs to the file. The file will be truncated."), @@ -175,7 +182,7 @@ const DUMP_LIR_ALL: &[DumpLIR] = &[ DumpLIR::scratch_split, ]; -/// Mamximum value for --zjit-mem-size/--zjit-exec-mem-size in MiB. +/// Maximum value for --zjit-mem-size/--zjit-exec-mem-size in MiB. /// We set 1TiB just to avoid overflow. We could make it smaller. const MAX_MEM_MIB: usize = 1024 * 1024; @@ -319,6 +326,8 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { ("debug", "") => options.debug = true, + ("disable", "") => options.disable = true, + ("disable-hir-opt", "") => options.disable_hir_opt = true, // --zjit-dump-hir dumps the actual input to the codegen, which is currently the same as --zjit-dump-hir-opt. @@ -442,15 +451,13 @@ macro_rules! debug { } pub(crate) use debug; -/// Return Qtrue if --zjit* has been specified. For the `#with_jit` hook, -/// this becomes Qtrue before ZJIT is actually initialized and enabled. +/// Return true if ZJIT should be enabled at boot. #[unsafe(no_mangle)] -pub extern "C" fn rb_zjit_option_enabled_p(_ec: EcPtr, _self: VALUE) -> VALUE { - // If any --zjit* option is specified, OPTIONS becomes Some. - if unsafe { OPTIONS.is_some() } { - Qtrue +pub extern "C" fn rb_zjit_option_enable() -> bool { + if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| !opts.disable) { + true } else { - Qfalse + false } } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 3cb60cffcb6c52..06296eb8f20d08 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -1,11 +1,11 @@ //! Runtime state of ZJIT. use crate::codegen::{gen_entry_trampoline, gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline}; -use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, EcPtr, Qnil, rb_vm_insn_addr2opcode, rb_profile_frames, VALUE, VM_INSTRUCTION_SIZE, size_t, rb_gc_mark}; +use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, src_loc, EcPtr, Qnil, Qtrue, rb_vm_insn_addr2opcode, rb_profile_frames, VALUE, VM_INSTRUCTION_SIZE, size_t, rb_gc_mark, with_vm_lock}; use crate::cruby_methods; use crate::invariants::Invariants; use crate::asm::CodeBlock; -use crate::options::get_option; +use crate::options::{get_option, rb_zjit_prepare_options}; use crate::stats::{Counters, InsnCounters, SideExitLocations}; use crate::virtualmem::CodePtr; use std::collections::HashMap; @@ -63,12 +63,42 @@ pub struct ZJITState { exit_locations: Option, } +/// Tracks the initialization progress +enum InitializationState { + Uninitialized, + + /// At boot time, rb_zjit_init will be called regardless of whether + /// ZJIT is enabled, in this phase we initialize any states that must + /// be captured at during boot. + Initialized(cruby_methods::Annotations), + + /// When ZJIT is enabled, either during boot with `--zjit`, or lazily + /// at a later time with `RubyVM::ZJIT.enable`, we perform the rest + /// of the initialization steps and produce the `ZJITState` instance. + Enabled(ZJITState), + + /// Indicates that ZJITState::init has panicked. Should never be + /// encountered in practice since we abort immediately when that + /// happens. + Panicked, +} + /// Private singleton instance of the codegen globals -static mut ZJIT_STATE: Option = None; +static mut ZJIT_STATE: InitializationState = InitializationState::Uninitialized; impl ZJITState { /// Initialize the ZJIT globals. Return the address of the JIT entry trampoline. pub fn init() -> *const u8 { + use InitializationState::*; + + let initialization_state = unsafe { + std::mem::replace(&mut ZJIT_STATE, Panicked) + }; + + let Initialized(method_annotations) = initialization_state else { + panic!("rb_zjit_init was never called"); + }; + let mut cb = { use crate::options::*; use crate::virtualmem::*; @@ -99,7 +129,7 @@ impl ZJITState { send_fallback_counters: [0; VM_INSTRUCTION_SIZE as usize], invariants: Invariants::default(), assert_compiles: false, - method_annotations: cruby_methods::init(), + method_annotations, exit_trampoline, function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, @@ -107,7 +137,7 @@ impl ZJITState { not_annotated_frame_cfunc_counter_pointers: HashMap::new(), exit_locations, }; - unsafe { ZJIT_STATE = Some(zjit_state); } + unsafe { ZJIT_STATE = Enabled(zjit_state); } // With --zjit-stats, use a different trampoline on function stub exits // to count exit_compilation_failure. Note that the trampoline code depends @@ -123,12 +153,16 @@ impl ZJITState { /// Return true if zjit_state has been initialized pub fn has_instance() -> bool { - unsafe { ZJIT_STATE.as_mut().is_some() } + matches!(unsafe { &ZJIT_STATE }, InitializationState::Enabled(_)) } /// Get a mutable reference to the codegen globals instance fn get_instance() -> &'static mut ZJITState { - unsafe { ZJIT_STATE.as_mut().unwrap() } + if let InitializationState::Enabled(instance) = unsafe { &mut ZJIT_STATE } { + instance + } else { + panic!("ZJITState::get_instance called when ZJIT is not enabled") + } } /// Get a mutable reference to the inline code block @@ -249,14 +283,39 @@ impl ZJITState { } } -/// Initialize ZJIT +/// Initialize ZJIT at boot. This is called even if ZJIT is disabled. #[unsafe(no_mangle)] -pub extern "C" fn rb_zjit_init() { +pub extern "C" fn rb_zjit_init(zjit_enabled: bool) { + use InitializationState::*; + + debug_assert!( + matches!(unsafe { &ZJIT_STATE }, Uninitialized), + "rb_zjit_init should only be called once during boot", + ); + + // Initialize IDs and method annotations. + // cruby_methods::init() must be called at boot, + // as cmes could have been re-defined after boot. + cruby::ids::init(); + + let method_annotations = cruby_methods::init(); + + unsafe { ZJIT_STATE = Initialized(method_annotations); } + + // If --zjit, enable ZJIT immediately + if zjit_enabled { + zjit_enable(); + } +} + +/// Enable ZJIT compilation. +fn zjit_enable() { + // TODO: call RubyVM::ZJIT::call_jit_hooks here + // Catch panics to avoid UB for unwinding into C frames. // See https://doc.rust-lang.org/nomicon/exception-safety.html let result = std::panic::catch_unwind(|| { // Initialize ZJIT states - cruby::ids::init(); let zjit_entry = ZJITState::init(); // Install a panic hook for ZJIT @@ -271,11 +330,33 @@ pub extern "C" fn rb_zjit_init() { }); if result.is_err() { - println!("ZJIT: zjit_init() panicked. Aborting."); + println!("ZJIT: zjit_enable() panicked. Aborting."); std::process::abort(); } } +/// Enable ZJIT compilation, returning Qtrue if ZJIT was previously disabled +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_enable(_ec: EcPtr, _self: VALUE) -> VALUE { + with_vm_lock(src_loc!(), || { + // Options would not have been initialized during boot if no flags were specified + rb_zjit_prepare_options(); + + // Initialize and enable ZJIT + zjit_enable(); + + // Add "+ZJIT" to RUBY_DESCRIPTION + unsafe { + unsafe extern "C" { + fn ruby_set_zjit_description(); + } + ruby_set_zjit_description(); + } + + Qtrue + }) +} + /// Assert that any future ZJIT compilation will return a function pointer (not fail to compile) #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE { From 0e10dfded0498cf71efb9fc61a804db6db540009 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 4 Nov 2025 20:37:57 +0100 Subject: [PATCH 1199/2435] ZJIT: Inline setting Struct fields * Add Insn::StoreField and Insn::WriteBarrier --- test/ruby/test_zjit.rb | 19 ++++++ zjit/src/codegen.rs | 17 ++++++ zjit/src/hir.rs | 122 +++++++++++++++++++++++--------------- zjit/src/hir/opt_tests.rs | 65 ++++++++++++++++++++ 4 files changed, 174 insertions(+), 49 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 2e04dbddd55b21..64372c231cf26f 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2957,6 +2957,25 @@ def test } end + def test_struct_set + assert_compiles '[42, 42, :frozen_error]', %q{ + C = Struct.new(:foo).new(1) + + def test + C.foo = Object.new + 42 + end + + r = [test, test] + C.freeze + r << begin + test + rescue FrozenError + :frozen_error + end + }, call_threshold: 2 + end + def test_global_tracepoint assert_compiles 'true', %q{ def foo = 1 diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7d72acfe14056f..fd72ea8a47f5f8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -460,6 +460,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadPC => gen_load_pc(asm), Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), + &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val))), + &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), @@ -1042,6 +1044,21 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd asm.load(Opnd::mem(64, recv, offset)) } +fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd) { + asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset); + let recv = asm.load(recv); + asm.store(Opnd::mem(64, recv, offset), val); +} + +fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { + // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written()->rb_gc_writebarrier() + if !val_type.is_immediate() { + asm_comment!(asm, "Write barrier"); + let recv = asm.load(recv); + asm_ccall!(asm, rb_gc_writebarrier, recv, val); + } +} + /// Compile an interpreter entry block to be inserted into an ISEQ fn gen_entry_prologue(asm: &mut Assembler) { asm_comment!(asm, "ZJIT entry trampoline"); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d68ddd2479ebd0..e1a61b23995eff 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -721,6 +721,10 @@ pub enum Insn { /// Load cfp->self LoadSelf, LoadField { recv: InsnId, id: ID, offset: i32, return_type: Type }, + /// Write `val` at an offset of `recv`. + /// When writing a Ruby object to a Ruby object, one must use GuardNotFrozen (or equivalent) before and WriteBarrier after. + StoreField { recv: InsnId, id: ID, offset: i32, val: InsnId }, + WriteBarrier { recv: InsnId, val: InsnId }, /// Get a local variable from a higher scope or the heap. /// If `use_sp` is true, it uses the SP register to optimize the read. @@ -908,7 +912,7 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::SetInstanceVariable { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::SetInstanceVariable { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => false, _ => true, } } @@ -1241,6 +1245,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::LoadPC => write!(f, "LoadPC"), Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), + &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:p}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), + &Insn::WriteBarrier { recv, val } => write!(f, "WriteBarrier {recv}, {val}"), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::SetInstanceVariable { self_val, id, val, .. } => write!(f, "SetInstanceVariable {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), @@ -1888,6 +1894,8 @@ impl Function { &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, + &StoreField { recv, id, offset, val } => StoreField { recv: find!(recv), id, offset, val: find!(val) }, + &WriteBarrier { recv, val } => WriteBarrier { recv: find!(recv), val: find!(val) }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, &SetInstanceVariable { self_val, id, ic, val, state } => SetInstanceVariable { self_val: find!(self_val), id, ic, val: find!(val), state }, &GetClassVar { id, ic, state } => GetClassVar { id, ic, state }, @@ -1944,8 +1952,8 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } - | Insn::SetInstanceVariable { .. } => - panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), + | Insn::SetInstanceVariable { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => + panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), Insn::Const { val: Const::CInt8(val) } => Type::from_cint(types::CInt8, *val as i64), @@ -2463,53 +2471,62 @@ impl Function { self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); self.make_equal_to(insn_id, val); } else if def_type == VM_METHOD_TYPE_OPTIMIZED { - let opt_type = unsafe { get_cme_def_body_optimized_type(cme) }; - if opt_type == OPTIMIZED_METHOD_TYPE_STRUCT_AREF { - if unsafe { vm_ci_argc(ci) } != 0 { - self.push_insn_id(block, insn_id); continue; - } - let index: i32 = unsafe { get_cme_def_body_optimized_index(cme) } - .try_into() - .unwrap(); - // We are going to use an encoding that takes a 4-byte immediate which - // limits the offset to INT32_MAX. - { - let native_index = (index as i64) * (SIZEOF_VALUE as i64); - if native_index > (i32::MAX as i64) { + let opt_type: OptimizedMethodType = unsafe { get_cme_def_body_optimized_type(cme) }.into(); + match (opt_type, args.as_slice()) { + (OptimizedMethodType::StructAref, &[]) | (OptimizedMethodType::StructAset, &[_]) => { + let index: i32 = unsafe { get_cme_def_body_optimized_index(cme) } + .try_into() + .unwrap(); + // We are going to use an encoding that takes a 4-byte immediate which + // limits the offset to INT32_MAX. + { + let native_index = (index as i64) * (SIZEOF_VALUE as i64); + if native_index > (i32::MAX as i64) { + self.push_insn_id(block, insn_id); continue; + } + } + // Get the profiled type to check if the fields is embedded or heap allocated. + let Some(is_embedded) = self.profiled_type_of_at(recv, frame_state.insn_idx).map(|t| t.flags().is_struct_embedded()) else { + // No (monomorphic/skewed polymorphic) profile info self.push_insn_id(block, insn_id); continue; + }; + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if klass.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); } - } - // Get the profiled type to check if the fields is embedded or heap allocated. - let Some(is_embedded) = self.profiled_type_of_at(recv, frame_state.insn_idx).map(|t| t.flags().is_struct_embedded()) else { - // No (monomorphic/skewed polymorphic) profile info + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + // All structs from the same Struct class should have the same + // length. So if our recv is embedded all runtime + // structs of the same class should be as well, and the same is + // true of the converse. + // + // No need for a GuardShape. + let (target, offset) = if is_embedded { + let offset = RUBY_OFFSET_RSTRUCT_AS_ARY + (SIZEOF_VALUE_I32 * index); + (recv, offset) + } else { + let as_heap = self.push_insn(block, Insn::LoadField { recv, id: ID!(_as_heap), offset: RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR, return_type: types::CPtr }); + let offset = SIZEOF_VALUE_I32 * index; + (as_heap, offset) + }; + + let replacement = if let (OptimizedMethodType::StructAset, &[val]) = (opt_type, args.as_slice()) { + self.push_insn(block, Insn::GuardNotFrozen { val: recv, state }); + self.push_insn(block, Insn::StoreField { recv: target, id: mid, offset, val }); + self.push_insn(block, Insn::WriteBarrier { recv, val }); + val + } else { // StructAref + self.push_insn(block, Insn::LoadField { recv: target, id: mid, offset, return_type: types::BasicObject }) + }; + self.make_equal_to(insn_id, replacement); + }, + _ => { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType::from(opt_type))); self.push_insn_id(block, insn_id); continue; - }; - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); - if klass.instance_can_have_singleton_class() { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); - } - if let Some(profiled_type) = profiled_type { - recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); - } - // All structs from the same Struct class should have the same - // length. So if our recv is embedded all runtime - // structs of the same class should be as well, and the same is - // true of the converse. - // - // No need for a GuardShape. - let replacement = if is_embedded { - let offset = RUBY_OFFSET_RSTRUCT_AS_ARY + (SIZEOF_VALUE_I32 * index); - self.push_insn(block, Insn::LoadField { recv, id: mid, offset, return_type: types::BasicObject }) - } else { - let as_heap = self.push_insn(block, Insn::LoadField { recv, id: ID!(_as_heap), offset: RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR, return_type: types::CPtr }); - let offset = SIZEOF_VALUE_I32 * index; - self.push_insn(block, Insn::LoadField { recv: as_heap, id: mid, offset, return_type: types::BasicObject }) - }; - self.make_equal_to(insn_id, replacement); - } else { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType::from(opt_type))); - self.push_insn_id(block, insn_id); continue; - } + }, + }; } else { self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; @@ -3511,6 +3528,11 @@ impl Function { &Insn::LoadField { recv, .. } => { worklist.push_back(recv); } + &Insn::StoreField { recv, val, .. } + | &Insn::WriteBarrier { recv, val } => { + worklist.push_back(recv); + worklist.push_back(val); + } &Insn::GuardBlockParamProxy { state, .. } | &Insn::GetGlobal { state, .. } | &Insn::GetSpecialSymbol { state, .. } | @@ -3851,7 +3873,8 @@ impl Function { | Insn::GetClassVar { .. } | Insn::GetSpecialNumber { .. } | Insn::GetSpecialSymbol { .. } - | Insn::GetLocal { .. } => { + | Insn::GetLocal { .. } + | Insn::StoreField { .. } => { Ok(()) } // Instructions with 1 Ruby object operand @@ -3882,7 +3905,8 @@ impl Function { Insn::SetIvar { self_val: left, val: right, .. } | Insn::SetInstanceVariable { self_val: left, val: right, .. } | Insn::NewRange { low: left, high: right, .. } - | Insn::AnyToString { val: left, str: right, .. } => { + | Insn::AnyToString { val: left, str: right, .. } + | Insn::WriteBarrier { recv: left, val: right } => { self.assert_subtype(insn_id, left, types::BasicObject)?; self.assert_subtype(insn_id, right, types::BasicObject) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2c56120bf621be..be770553767f0c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5335,6 +5335,71 @@ mod hir_opt_tests { "); } + #[test] + fn test_inline_struct_aset_embedded() { + eval(r#" + C = Struct.new(:foo) + def test(o, v) = o.foo = v + value = Object.new + test C.new, value + test C.new, value + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v30:HeapObject[class_exact:C] = GuardNotFrozen v29 + StoreField v29, :foo=@0x1038, v12 + WriteBarrier v29, v12 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_struct_aset_heap() { + eval(r#" + C = Struct.new(*(0..1000).map {|i| :"a#{i}"}, :foo) + def test(o, v) = o.foo = v + value = Object.new + test C.new, value + test C.new, value + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v30:CPtr = LoadField v29, :_as_heap@0x1038 + v31:HeapObject[class_exact:C] = GuardNotFrozen v29 + StoreField v30, :foo=@0x1039, v12 + WriteBarrier v29, v12 + CheckInterrupts + Return v12 + "); + } + #[test] fn test_array_reverse_returns_array() { eval(r#" From 79633437e1a971abd5dda54dc584eec3adb4e7a7 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 18 Nov 2025 16:41:15 +0100 Subject: [PATCH 1200/2435] ZJIT: Rename the operand of Insn::GuardNotFrozen from val to recv * When writing to an object, the receiver should be checked if it's frozen, not the value, so this avoids an error-prone autocomplete. --- zjit/src/codegen.rs | 8 ++++---- zjit/src/cruby_methods.rs | 4 ++-- zjit/src/hir.rs | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index fd72ea8a47f5f8..8fc66791a665ad 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -415,7 +415,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), - Insn::GuardNotFrozen { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &function.frame_state(*state)), + Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), @@ -645,12 +645,12 @@ fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc)); } -fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: &FrameState) -> Opnd { - let ret = asm_ccall!(asm, rb_obj_frozen_p, val); +fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { + let ret = asm_ccall!(asm, rb_obj_frozen_p, recv); asm_comment!(asm, "side-exit if rb_obj_frozen_p returns Qtrue"); asm.cmp(ret, Qtrue.into()); asm.je(side_exit(jit, state, GuardNotFrozen)); - val + recv } fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 8dc53302835d23..d2d6be2dec8009 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -306,7 +306,7 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { // Only inline the case of no arguments. let &[] = args else { return None; }; - let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state }); + let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state })) } @@ -367,7 +367,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); - let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state }); + let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); // String#setbyte returns the fixnum provided as its `value` argument back to the caller. Some(value) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e1a61b23995eff..b3c72393f20e32 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -879,7 +879,7 @@ pub enum Insn { /// is neither ISEQ nor ifunc, which makes it incompatible with rb_block_param_proxy. GuardBlockParamProxy { level: u32, state: InsnId }, /// Side-exit if val is frozen. - GuardNotFrozen { val: InsnId, state: InsnId }, + GuardNotFrozen { recv: InsnId, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). @@ -1191,7 +1191,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), - Insn::GuardNotFrozen { val, .. } => write!(f, "GuardNotFrozen {val}"), + Insn::GuardNotFrozen { recv, .. } => write!(f, "GuardNotFrozen {recv}"), Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, @@ -1786,7 +1786,7 @@ impl Function { &GuardBitEquals { val, expected, state } => GuardBitEquals { val: find!(val), expected, state }, &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, - &GuardNotFrozen { val, state } => GuardNotFrozen { val: find!(val), state }, + &GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, @@ -2004,7 +2004,7 @@ impl Function { Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), - Insn::GuardNotFrozen { val, .. } => self.type_of(*val), + Insn::GuardNotFrozen { recv, .. } => self.type_of(*recv), Insn::GuardLess { left, .. } => self.type_of(*left), Insn::GuardGreaterEq { left, .. } => self.type_of(*left), Insn::FixnumAdd { .. } => types::Fixnum, @@ -2513,7 +2513,7 @@ impl Function { }; let replacement = if let (OptimizedMethodType::StructAset, &[val]) = (opt_type, args.as_slice()) { - self.push_insn(block, Insn::GuardNotFrozen { val: recv, state }); + self.push_insn(block, Insn::GuardNotFrozen { recv, state }); self.push_insn(block, Insn::StoreField { recv: target, id: mid, offset, val }); self.push_insn(block, Insn::WriteBarrier { recv, val }); val @@ -3402,7 +3402,7 @@ impl Function { | &Insn::GuardTypeNot { val, state, .. } | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardShape { val, state, .. } - | &Insn::GuardNotFrozen { val, state } + | &Insn::GuardNotFrozen { recv: val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } @@ -3882,7 +3882,7 @@ impl Function { | Insn::IsNil { val } | Insn::IsMethodCfunc { val, .. } | Insn::GuardShape { val, .. } - | Insn::GuardNotFrozen { val, .. } + | Insn::GuardNotFrozen { recv: val, .. } | Insn::SetGlobal { val, .. } | Insn::SetLocal { val, .. } | Insn::SetClassVar { val, .. } From ce73b6c0b6a81c70e5ac4f4e43dea05cd23bab20 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 18 Nov 2025 17:26:08 +0100 Subject: [PATCH 1201/2435] ZJIT: Pass the result of GuardNotFrozen to StoreField and WriteBarrier --- zjit/src/hir.rs | 5 ++++- zjit/src/hir/opt_tests.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b3c72393f20e32..c96e0357a037ac 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2503,6 +2503,10 @@ impl Function { // true of the converse. // // No need for a GuardShape. + if let OptimizedMethodType::StructAset = opt_type { + recv = self.push_insn(block, Insn::GuardNotFrozen { recv, state }); + } + let (target, offset) = if is_embedded { let offset = RUBY_OFFSET_RSTRUCT_AS_ARY + (SIZEOF_VALUE_I32 * index); (recv, offset) @@ -2513,7 +2517,6 @@ impl Function { }; let replacement = if let (OptimizedMethodType::StructAset, &[val]) = (opt_type, args.as_slice()) { - self.push_insn(block, Insn::GuardNotFrozen { recv, state }); self.push_insn(block, Insn::StoreField { recv: target, id: mid, offset, val }); self.push_insn(block, Insn::WriteBarrier { recv, val }); val diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index be770553767f0c..e7aaba7fffb73b 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5360,8 +5360,8 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v30:HeapObject[class_exact:C] = GuardNotFrozen v29 - StoreField v29, :foo=@0x1038, v12 - WriteBarrier v29, v12 + StoreField v30, :foo=@0x1038, v12 + WriteBarrier v30, v12 CheckInterrupts Return v12 "); @@ -5391,10 +5391,10 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v30:CPtr = LoadField v29, :_as_heap@0x1038 - v31:HeapObject[class_exact:C] = GuardNotFrozen v29 - StoreField v30, :foo=@0x1039, v12 - WriteBarrier v29, v12 + v30:HeapObject[class_exact:C] = GuardNotFrozen v29 + v31:CPtr = LoadField v30, :_as_heap@0x1038 + StoreField v31, :foo=@0x1039, v12 + WriteBarrier v30, v12 CheckInterrupts Return v12 "); From ff2d2fc1bd9aa6a768e85276d7ba69bbe5af9572 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 18 Nov 2025 14:24:49 -0500 Subject: [PATCH 1202/2435] YJIT: omit single ractor mode assumption for `proc#call` (#15092) The comptime receiver, which is a proc, is either shareable or from this ractor so we don't need to assume single-ractor mode. We should never get the "defined with an un-shareable Proc in a different ractor" error. --- bootstraptest/test_yjit.rb | 20 ++++++++++++++++++++ yjit/src/codegen.rs | 7 ------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 94bda9951eec2e..f9cdca6f28c76a 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -4081,6 +4081,26 @@ def bar(&block) bar { } } +# unshareable bmethod call through Method#to_proc#call +assert_equal '1000', %q{ + define_method(:bmethod) do + self + end + + Ractor.new do + errors = 0 + 1000.times do + p = method(:bmethod).to_proc + begin + p.call + rescue RuntimeError + errors += 1 + end + end + errors + end.value +} + # test for return stub lifetime issue assert_equal '1', %q{ def foo(n) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 6c42dd1713dd0a..a04c95ca09c16e 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -9335,13 +9335,6 @@ fn gen_send_general( return None; } - // Optimize for single ractor mode and avoid runtime check for - // "defined with an un-shareable Proc in a different Ractor" - if !assume_single_ractor_mode(jit, asm) { - gen_counter_incr(jit, asm, Counter::send_call_multi_ractor); - return None; - } - // If this is a .send call we need to adjust the stack if flags & VM_CALL_OPT_SEND != 0 { handle_opt_send_shift_stack(asm, argc); From 656600371239a4a62e7a26e148af70e98d0fa979 Mon Sep 17 00:00:00 2001 From: Shannon Skipper Date: Sun, 16 Nov 2025 08:21:20 -0800 Subject: [PATCH 1203/2435] ZJIT: Avoid `NaN%` ratio appearing in stats --- zjit.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/zjit.rb b/zjit.rb index f5ed347f2cb6ab..243e6bcf6de9bf 100644 --- a/zjit.rb +++ b/zjit.rb @@ -165,8 +165,12 @@ def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats - stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 - stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 + if stats[:guard_type_count].nonzero? + stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 + end + if stats[:guard_shape_count].nonzero? + stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 + end # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) @@ -269,7 +273,10 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) next unless stats.key?(key) value = stats[key] if base && key != base - ratio = " (%4.1f%%)" % (100.0 * value / stats[base]) + total = stats[base] + if total.nonzero? + ratio = " (%4.1f%%)" % (100.0 * value / total) + end end case key From cbe65ebbc3f2b77316d50b94e84df1c00822d0f2 Mon Sep 17 00:00:00 2001 From: Shannon Skipper Date: Sun, 16 Nov 2025 09:01:50 -0800 Subject: [PATCH 1204/2435] ZJIT: Skip empty counter sections in stats --- zjit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index 243e6bcf6de9bf..bb6d4d3cdca16c 100644 --- a/zjit.rb +++ b/zjit.rb @@ -297,7 +297,7 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 } - return if stats.empty? + return if counters.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } key_pad = counters.keys.map(&:size).max From f3f3e76882d01d5e0a006ff731b70053997396e8 Mon Sep 17 00:00:00 2001 From: Jacob Date: Tue, 18 Nov 2025 17:32:11 -0500 Subject: [PATCH 1205/2435] Extract `KW_SPECIFIED_BITS_MAX` for JITs (GH-15039) Rename to `VM_KW_SPECIFIED_BITS_MAX` now that it's in `vm_core.h`. --- vm_args.c | 10 ++++------ vm_core.h | 2 ++ vm_insnhelper.c | 2 +- yjit/bindgen/src/main.rs | 1 + yjit/src/codegen.rs | 2 +- yjit/src/cruby_bindings.inc.rs | 1 + zjit/bindgen/src/main.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 3 +-- 9 files changed, 13 insertions(+), 10 deletions(-) diff --git a/vm_args.c b/vm_args.c index 5952b32f1fdb36..64ed88d0e1dcce 100644 --- a/vm_args.c +++ b/vm_args.c @@ -318,8 +318,6 @@ args_setup_kw_parameters_lookup(const ID key, VALUE *ptr, const VALUE *const pas return FALSE; } -#define KW_SPECIFIED_BITS_MAX (32-1) /* TODO: 32 -> Fixnum's max bits */ - static void args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme, VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, @@ -355,7 +353,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons if (UNDEF_P(default_values[di])) { locals[i] = Qnil; - if (LIKELY(i < KW_SPECIFIED_BITS_MAX)) { + if (LIKELY(i < VM_KW_SPECIFIED_BITS_MAX)) { unspecified_bits |= 0x01 << di; } else { @@ -364,7 +362,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons int j; unspecified_bits_value = rb_hash_new(); - for (j=0; j Fixnum's max bits */ + # define CALLING_ARGC(calling) ((calling)->heap_argv ? RARRAY_LENINT((calling)->heap_argv) : (calling)->argc) struct rb_execution_context_struct; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 63dcaba8a33bf5..1b1eeb69d9ffb4 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5808,7 +5808,7 @@ vm_check_keyword(lindex_t bits, lindex_t idx, const VALUE *ep) if (FIXNUM_P(kw_bits)) { unsigned int b = (unsigned int)FIX2ULONG(kw_bits); - if ((idx < KW_SPECIFIED_BITS_MAX) && (b & (0x01 << idx))) + if ((idx < VM_KW_SPECIFIED_BITS_MAX) && (b & (0x01 << idx))) return Qfalse; } else { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 100abbb33fc8cb..67a461cd16d95c 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -155,6 +155,7 @@ fn main() { .opaque_type("rb_callcache.*") .allowlist_type("rb_callinfo") .allowlist_var("VM_ENV_DATA_INDEX_ME_CREF") + .allowlist_var("VM_KW_SPECIFIED_BITS_MAX") .allowlist_var("rb_block_param_proxy") .allowlist_function("rb_range_new") .allowlist_function("rb_intern") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index a04c95ca09c16e..9eeccddf6ce490 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2747,7 +2747,7 @@ fn gen_checkkeyword( ) -> Option { // When a keyword is unspecified past index 32, a hash will be used // instead. This can only happen in iseqs taking more than 32 keywords. - if unsafe { (*get_iseq_body_param_keyword(jit.iseq)).num >= 32 } { + if unsafe { (*get_iseq_body_param_keyword(jit.iseq)).num >= VM_KW_SPECIFIED_BITS_MAX.try_into().unwrap() } { return None; } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 253598bce1465d..a6aef48313ad71 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -165,6 +165,7 @@ pub const NIL_REDEFINED_OP_FLAG: u32 = 512; pub const TRUE_REDEFINED_OP_FLAG: u32 = 1024; pub const FALSE_REDEFINED_OP_FLAG: u32 = 2048; pub const PROC_REDEFINED_OP_FLAG: u32 = 4096; +pub const VM_KW_SPECIFIED_BITS_MAX: u32 = 31; pub const VM_ENV_DATA_SIZE: u32 = 3; pub const VM_ENV_DATA_INDEX_ME_CREF: i32 = -2; pub const VM_ENV_DATA_INDEX_SPECVAL: i32 = -1; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 95209375dcfd71..7873a209777605 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -101,6 +101,7 @@ fn main() { .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") .allowlist_var("rb_invalid_shape_id") + .allowlist_var("VM_KW_SPECIFIED_BITS_MAX") .allowlist_var("SHAPE_ID_NUM_BITS") .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 457cd584cf0e01..0fde4e3ab70a93 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -227,6 +227,7 @@ pub const NIL_REDEFINED_OP_FLAG: u32 = 512; pub const TRUE_REDEFINED_OP_FLAG: u32 = 1024; pub const FALSE_REDEFINED_OP_FLAG: u32 = 2048; pub const PROC_REDEFINED_OP_FLAG: u32 = 4096; +pub const VM_KW_SPECIFIED_BITS_MAX: u32 = 31; pub const VM_ENV_DATA_SIZE: u32 = 3; pub const VM_ENV_DATA_INDEX_ME_CREF: i32 = -2; pub const VM_ENV_DATA_INDEX_SPECVAL: i32 = -1; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c96e0357a037ac..58638f30f0264d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4848,8 +4848,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // When a keyword is unspecified past index 32, a hash will be used instead. // This can only happen in iseqs taking more than 32 keywords. // In this case, we side exit to the interpreter. - // TODO(Jacob): Replace the magic number 32 with a named constant. (Can be completed after PR 15039) - if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= 32} { + if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= VM_KW_SPECIFIED_BITS_MAX.try_into().unwrap()} { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyKeywordParameters }); break; } From d5d12efde75515997d046448aa36eb9ed893517b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 18 Nov 2025 22:45:08 +0000 Subject: [PATCH 1206/2435] [ruby/json] parser.c: Remove unued JSON_ParserStruct.parsing_name https://github.com/ruby/json/commit/ab5efca015 --- ext/json/parser/parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 2bf3ae0eb38dd6..62ee1f24e71c40 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -336,7 +336,6 @@ typedef struct JSON_ParserStruct { int max_nesting; bool allow_nan; bool allow_trailing_comma; - bool parsing_name; bool symbolize_names; bool freeze; } JSON_ParserConfig; From 6f6a9ead961feb5c2d794bf9d1594c9e8e1de6ab Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 18 Nov 2025 11:56:09 -0500 Subject: [PATCH 1207/2435] [ruby/rubygems] Replace instance method look up in plugin installer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Gem::Installer.instance_methods(false).include?(:generate_plugins)` is 63x slower than `Gem::Installer.method_defined?(:generate_plugins)` in a microbenchmark. The latter is a direct lookup, whereas the former will create an array, which will be slower. ```ruby require "benchmark/ips" Benchmark.ips do |x| x.report "instance_methods" do Gem::Installer.instance_methods(false).include?(:generate_plugins) end x.report "method_defined" do Gem::Installer.method_defined?(:generate_plugins) end x.compare! end ``` ``` $ ruby -I lib/ benchmark_methods.rb ruby 3.4.4 (2025-05-14 revision https://github.com/ruby/rubygems/commit/a38531fd3f) +PRISM [arm64-darwin23] Warming up -------------------------------------- instance_methods 58.449k i/100ms method_defined 3.375M i/100ms Calculating ------------------------------------- instance_methods 541.874k (± 5.7%) i/s (1.85 μs/i) - 2.747M in 5.087825s method_defined 34.263M (± 1.1%) i/s (29.19 ns/i) - 172.135M in 5.024524s Comparison: method_defined: 34263189.1 i/s instance_methods: 541874.3 i/s - 63.23x slower ``` This does not make much difference in an overall benchmark or profile, but this is more idiomatic Ruby than the prior code. https://github.com/ruby/rubygems/commit/49dec52cb2 --- lib/bundler/rubygems_gem_installer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 1af1b85ff015a4..25ddbeaceb6da3 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -69,7 +69,7 @@ def ensure_writable_dir(dir) end def generate_plugins - return unless Gem::Installer.instance_methods(false).include?(:generate_plugins) + return unless Gem::Installer.method_defined?(:generate_plugins) latest = Gem::Specification.stubs_for(spec.name).first return if latest && latest.version > spec.version From 32b8f97b3438f74234b84f920085abc37e821164 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 18 Nov 2025 18:50:36 -0500 Subject: [PATCH 1208/2435] ZJIT: Delete outdated optional param test [ci skip] Name contradictory now, and we have other tests testing the same thing. --- zjit/src/hir/opt_tests.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e7aaba7fffb73b..f4afd656c7d533 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2651,34 +2651,6 @@ mod hir_opt_tests { "); } - #[test] - fn dont_specialize_call_to_iseq_with_opt() { - eval(" - def foo(arg=1) = 1 - def test = foo 1 - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v11 - CheckInterrupts - Return v21 - "); - } - #[test] fn dont_specialize_call_to_iseq_with_block() { eval(" From 0f89fa97e3629af427282b5b6a800d2b97dd7d65 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 18 Nov 2025 16:36:29 -0800 Subject: [PATCH 1209/2435] ZJIT: Inline BasicObject#! (#15201) --- zjit/src/cruby_methods.rs | 15 +++++- zjit/src/hir/opt_tests.rs | 100 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index d2d6be2dec8009..7a4a11a8e18bfd 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -221,7 +221,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "nil?", inline_kernel_nil_p); annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "!", inline_basic_object_not, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); @@ -517,6 +517,19 @@ fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hi Some(result) } +fn inline_basic_object_not(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[] = args else { return None; }; + if fun.type_of(recv).is_known_truthy() { + let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) }); + return Some(result); + } + if fun.type_of(recv).is_known_falsy() { + let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qtrue) }); + return Some(result); + } + None +} + fn inline_basic_object_neq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[other] = args else { return None; }; let result = try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumNeq { left, right }, BOP_NEQ, recv, other, state); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index f4afd656c7d533..fadb6ced5f1c0a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4602,7 +4602,7 @@ mod hir_opt_tests { } #[test] - fn test_specialize_basicobject_not_to_ccall() { + fn test_specialize_basicobject_not_truthy() { eval(" def test(a) = !a @@ -4622,10 +4622,104 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact + v24:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall !@0x1038, v23 CheckInterrupts - Return v25 + Return v24 + "); + } + + #[test] + fn test_specialize_basicobject_not_false() { + eval(" + def test(a) = !a + + test(false) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(FalseClass@0x1000, !@0x1008, cme:0x1010) + v22:FalseClass = GuardType v9, FalseClass + v23:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_specialize_basicobject_not_nil() { + eval(" + def test(a) = !a + + test(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, !@0x1008, cme:0x1010) + v22:NilClass = GuardType v9, NilClass + v23:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_specialize_basicobject_not_falsy() { + eval(" + def test(a) = !(if a then false else nil end) + + # TODO(max): Make this not GuardType NilClass and instead just reason + # statically + test(false) + test(true) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + v15:CBool = Test v9 + IfFalse v15, bb3(v8, v9) + v18:FalseClass = Const Value(false) + CheckInterrupts + Jump bb4(v8, v9, v18) + bb3(v22:BasicObject, v23:BasicObject): + v26:NilClass = Const Value(nil) + Jump bb4(v22, v23, v26) + bb4(v28:BasicObject, v29:BasicObject, v30:NilClass|FalseClass): + PatchPoint MethodRedefined(NilClass@0x1000, !@0x1008, cme:0x1010) + v41:NilClass = GuardType v30, NilClass + v42:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v42 "); } From 4423facbffd5bd523541ebf42dc274272b1de732 Mon Sep 17 00:00:00 2001 From: Go Sueyoshi Date: Wed, 19 Nov 2025 09:46:35 +0900 Subject: [PATCH 1210/2435] [ruby/rubygems] Add `--ext=go` to `bundle gem` (https://github.com/ruby/rubygems/pull/8183) * Add new gem templates * Add `--ext=go` in `bundle gem` * Add setup-go to .github/workflows/main.yml * Embed go version in go.mod * Use go in bundler CI * Add example method to template * Install Go in .circleci/config.yml * Install Go in .gitlab-ci.yml * Allow hard tabs in go template * Run `rake update_manifest` * Fix test * Move go_gem to gemspec Respect to 9b0ec80 * nits: :golf: * includes valid module name in go.mod * generate header file * Run `go mod tidy` to create `go.sum` * Check if `go.sum` is generated only when Go is installed To avoid test failure in environments where Go is not installed * Run CI * Workaround for hung up c.f. https://github.com/rubygems/rubygems/actions/runs/11639408044/job/32415545422 * Write man for --ext=go * Re-generate man with `./bin/rake man:build` * pinning :pushpin: * Update with `./bin/rake man:build` * nits: Extract to method * nits: Use `sys_exec` instead of `system` * Clean go module cache after test Workaround following error ``` 1) bundle gem gem naming with underscore --ext parameter set with go includes go_gem extension in extconf.rb Failure/Error: FileUtils.rm_r(dir) Errno::EACCES: Permission denied @ apply2files - /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go # ./spec/support/helpers.rb:37:in `block in reset!' # ./spec/support/helpers.rb:21:in `each' # ./spec/support/helpers.rb:21:in `reset!' # ./spec/spec_helper.rb:130:in `block (2 levels) in ' # /home/runner/work/rubygems/rubygems/lib/rubygems.rb:303:in `load' # /home/runner/work/rubygems/rubygems/lib/rubygems.rb:303:in `activate_and_load_bin_path' ``` Files installed with `go get` have permissions set to 444 ref. https://github.com/golang/go/issues/35615 ``` $ ls -l /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go -r--r--r-- 1 runner runner 42320 Nov 15 06:38 /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go ``` So they cannot be deleted by `FileUtils.rm_r`. Therefore, this is necessary to execute `go clean -modcache` separately from `FileUtils.rm_r` to circumvent it. * Remove needless changes ref. https://github.com/ruby/rubygems/pull/8183#discussion_r2532902051 * ci: setup-go is needless * Don't run go command in `bundle gem` ref. https://github.com/ruby/rubygems/pull/8183#discussion_r2532765470 * Revert unrelated date changes --------- https://github.com/ruby/rubygems/commit/260d7d60b3 Co-authored-by: Hiroshi SHIBATA --- lib/bundler/cli.rb | 2 +- lib/bundler/cli/gem.rb | 16 +- lib/bundler/man/bundle-gem.1 | 4 +- lib/bundler/man/bundle-gem.1.ronn | 4 +- .../templates/newgem/circleci/config.yml.tt | 12 ++ .../newgem/ext/newgem/extconf-go.rb.tt | 11 ++ .../templates/newgem/ext/newgem/go.mod.tt | 5 + .../newgem/ext/newgem/newgem-go.c.tt | 2 + .../templates/newgem/ext/newgem/newgem.go.tt | 31 ++++ .../newgem/github/workflows/main.yml.tt | 6 + lib/bundler/templates/newgem/gitlab-ci.yml.tt | 9 ++ .../templates/newgem/newgem.gemspec.tt | 5 +- spec/bundler/commands/newgem_spec.rb | 151 ++++++++++++++++++ spec/bundler/quality_spec.rb | 3 + 14 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt create mode 100644 lib/bundler/templates/newgem/ext/newgem/go.mod.tt create mode 100644 lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt create mode 100644 lib/bundler/templates/newgem/ext/newgem/newgem.go.tt diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index af634291dd4737..86b86b359fcf82 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -11,7 +11,7 @@ class CLI < Thor AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze - EXTENSIONS = ["c", "rust"].freeze + EXTENSIONS = ["c", "rust", "go"].freeze COMMAND_ALIASES = { "check" => "c", diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index abfb095d469f81..236ce530eccb75 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -13,6 +13,8 @@ class CLI::Gem "test-unit" => "3.0", }.freeze + DEFAULT_GITHUB_USERNAME = "[USERNAME]" + attr_reader :options, :gem_name, :thor, :name, :target, :extension def initialize(options, gem_name, thor) @@ -72,7 +74,7 @@ def run bundle: options[:bundle], bundler_version: bundler_dependency_version, git: use_git, - github_username: github_username.empty? ? "[USERNAME]" : github_username, + github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username, required_ruby_version: required_ruby_version, rust_builder_required_rubygems_version: rust_builder_required_rubygems_version, minitest_constant_name: minitest_constant_name, @@ -231,6 +233,18 @@ def run ) end + if extension == "go" + templates.merge!( + "ext/newgem/go.mod.tt" => "ext/#{name}/go.mod", + "ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go", + "ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c", + ) + + config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username] + end + if target.exist? && !target.directory? Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 670a69d67e317b..85c0f57674a41c 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -38,8 +38,8 @@ Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this op \fB\-\-no\-changelog\fR Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. .TP -\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR -Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. +\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR +Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. .TP \fB\-\-no\-ext\fR Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index b71bde9f6506f1..488c8113e4f263 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -51,8 +51,8 @@ configuration file using the following names: Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the global config). -* `--ext=c`, `--ext=rust`: - Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior +* `--ext=c`, `--ext=go`, `--ext=rust`: + Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior is disabled by default. * `--no-ext`: diff --git a/lib/bundler/templates/newgem/circleci/config.yml.tt b/lib/bundler/templates/newgem/circleci/config.yml.tt index f40f029bf130b5..c4dd9d06471eb8 100644 --- a/lib/bundler/templates/newgem/circleci/config.yml.tt +++ b/lib/bundler/templates/newgem/circleci/config.yml.tt @@ -6,6 +6,10 @@ jobs: <%- if config[:ext] == 'rust' -%> environment: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' +<%- end -%> +<%- if config[:ext] == 'go' -%> + environment: + GO_VERSION: '1.23.0' <%- end -%> steps: - checkout @@ -16,6 +20,14 @@ jobs: - run: name: Install a RubyGems version that can compile rust extensions command: gem update --system '<%= ::Gem.rubygems_version %>' +<%- end -%> +<%- if config[:ext] == 'go' -%> + - run: + name: Install Go + command: | + wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + tar -C /usr/local -xzf /tmp/go.tar.gz + echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV" <%- end -%> - run: name: Run the default task diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt new file mode 100644 index 00000000000000..a689e21ebe9c3f --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "mkmf" +require "go_gem/mkmf" + +# Makes all symbols private by default to avoid unintended conflict +# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED +# selectively, or entirely remove this flag. +append_cflags("-fvisibility=hidden") + +create_go_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/go.mod.tt b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt new file mode 100644 index 00000000000000..3f4819d0046509 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt @@ -0,0 +1,5 @@ +module github.com/<%= config[:go_module_username] %>/<%= config[:underscored_name] %> + +go 1.23 + +require github.com/ruby-go-gem/go-gem-wrapper latest diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt new file mode 100644 index 00000000000000..119c0c96ea5a4c --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt @@ -0,0 +1,2 @@ +#include "<%= config[:underscored_name] %>.h" +#include "_cgo_export.h" diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt new file mode 100644 index 00000000000000..f19b750e58c46b --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt @@ -0,0 +1,31 @@ +package main + +/* +#include "<%= config[:underscored_name] %>.h" + +VALUE rb_<%= config[:underscored_name] %>_sum(VALUE self, VALUE a, VALUE b); +*/ +import "C" + +import ( + "github.com/ruby-go-gem/go-gem-wrapper/ruby" +) + +//export rb_<%= config[:underscored_name] %>_sum +func rb_<%= config[:underscored_name] %>_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE { + longA := ruby.NUM2LONG(ruby.VALUE(a)) + longB := ruby.NUM2LONG(ruby.VALUE(b)) + + sum := longA + longB + + return C.VALUE(ruby.LONG2NUM(sum)) +} + +//export Init_<%= config[:underscored_name] %> +func Init_<%= config[:underscored_name] %>() { + rb_m<%= config[:constant_array].join %> := ruby.RbDefineModule(<%= config[:constant_name].inspect %>) + ruby.RbDefineSingletonMethod(rb_m<%= config[:constant_array].join %>, "sum", C.rb_<%= config[:underscored_name] %>_sum, 2) +} + +func main() { +} diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index 9224ee0ca27266..7f3e3a5b66f94f 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -34,6 +34,12 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true +<%- end -%> +<%- if config[:ext] == 'go' -%> + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: ext/<%= config[:underscored_name] %>/go.mod <%- end -%> - name: Run the default task run: bundle exec rake diff --git a/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/lib/bundler/templates/newgem/gitlab-ci.yml.tt index d2e1f337362fb7..adbd70cbc05007 100644 --- a/lib/bundler/templates/newgem/gitlab-ci.yml.tt +++ b/lib/bundler/templates/newgem/gitlab-ci.yml.tt @@ -5,6 +5,11 @@ default: <%- if config[:ext] == 'rust' -%> - apt-get update && apt-get install -y clang - gem update --system '<%= ::Gem.rubygems_version %>' +<%- end -%> +<%- if config[:ext] == 'go' -%> + - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + - tar -C /usr/local -xzf /tmp/go.tar.gz + - export PATH=/usr/local/go/bin:$PATH <%- end -%> - gem install bundler -v <%= Bundler::VERSION %> - bundle install @@ -13,6 +18,10 @@ example_job: <%- if config[:ext] == 'rust' -%> variables: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' +<%- end -%> +<%- if config[:ext] == 'go' -%> + variables: + GO_VERSION: '1.23.0' <%- end -%> script: - bundle exec rake diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index c87abda8a01555..513875fd63ef07 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -38,7 +38,7 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] -<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%> +<%- if %w(c rust go).include?(config[:ext]) -%> spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> @@ -47,6 +47,9 @@ Gem::Specification.new do |spec| <%- if config[:ext] == 'rust' -%> spec.add_dependency "rb_sys", "~> 0.9.91" <%- end -%> +<%- if config[:ext] == 'go' -%> + spec.add_dependency "go_gem", "~> 0.2" +<%- end -%> # For more information and examples about making a new gem, check out our # guide at: https://bundler.io/guides/creating_gem.html diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 1ce4a0da09cac1..7a837bd08f0112 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -31,6 +31,13 @@ def ignore_paths matched[:ignored]&.split(" ") end + def installed_go? + sys_exec("go version", raise_on_error: true) + true + rescue StandardError + false + end + let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) } let(:gem_name) { "mygem" } @@ -1748,6 +1755,150 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end end + + context "--ext parameter set with go" do + let(:flags) { "--ext=go" } + + before do + bundle ["gem", gem_name, flags].compact.join(" ") + end + + after do + sys_exec("go clean -modcache", raise_on_error: true) if installed_go? + end + + it "is not deprecated" do + expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated." + end + + it "builds ext skeleton" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod")).to exist + end + + it "includes extconf.rb in gem_name.gemspec" do + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(%(spec.extensions = ["ext/#{gem_name}/extconf.rb"])) + end + + it "includes go_gem in gem_name.gemspec" do + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "go_gem", "~> 0.2"') + end + + it "includes go_gem extension in extconf.rb" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(<<~RUBY) + require "mkmf" + require "go_gem/mkmf" + RUBY + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(%(create_go_makefile("#{gem_name}/#{gem_name}"))) + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).not_to include("create_makefile") + end + + it "includes go_gem extension in gem_name.c" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c").read).to eq(<<~C) + #include "#{gem_name}.h" + #include "_cgo_export.h" + C + end + + it "includes skeleton code in gem_name.go" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + /* + #include "#{gem_name}.h" + + VALUE rb_#{gem_name}_sum(VALUE self, VALUE a, VALUE b); + */ + import "C" + GO + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + //export rb_#{gem_name}_sum + func rb_#{gem_name}_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE { + GO + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + //export Init_#{gem_name} + func Init_#{gem_name}() { + GO + end + + it "includes valid module name in go.mod" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}") + end + + context "with --no-ci" do + let(:flags) { "--ext=go --no-ci" } + + it_behaves_like "CI config is absent" + end + + context "--ci set to github" do + let(:flags) { "--ext=go --ci=github" } + + it "generates .github/workflows/main.yml" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + expect(bundled_app("#{gem_name}/.github/workflows/main.yml").read).to include("go-version-file: ext/#{gem_name}/go.mod") + end + end + + context "--ci set to circle" do + let(:flags) { "--ext=go --ci=circle" } + + it "generates a .circleci/config.yml" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + + expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML.strip) + environment: + GO_VERSION: + YAML + + expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML) + - run: + name: Install Go + command: | + wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + tar -C /usr/local -xzf /tmp/go.tar.gz + echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV" + YAML + end + end + + context "--ci set to gitlab" do + let(:flags) { "--ext=go --ci=gitlab" } + + it "generates a .gitlab-ci.yml" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + + expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML) + - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + - tar -C /usr/local -xzf /tmp/go.tar.gz + - export PATH=/usr/local/go/bin:$PATH + YAML + + expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML.strip) + variables: + GO_VERSION: + YAML + end + end + + context "without github.user" do + before do + # FIXME: GitHub Actions Windows Runner hang up here for some reason... + skip "Workaround for hung up" if Gem.win_platform? + + git("config --global --unset github.user") + bundle ["gem", gem_name, flags].compact.join(" ") + end + + it "includes valid module name in go.mod" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/username/#{gem_name}") + end + end + end end context "gem naming with dashed" do diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb index bbd6517f21dbea..b60be9980fc145 100644 --- a/spec/bundler/quality_spec.rb +++ b/spec/bundler/quality_spec.rb @@ -20,6 +20,9 @@ def check_for_git_merge_conflicts(filename) end def check_for_tab_characters(filename) + # Because Go uses hard tabs + return if filename.end_with?(".go.tt") + failing_lines = [] each_line(filename) do |line, number| failing_lines << number + 1 if line.include?("\t") From 1f299dd309a963f533f107c576966a723568820f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 18 Nov 2025 15:42:53 -0800 Subject: [PATCH 1211/2435] Fix crash in optimal size for large T_OBJECT Previously any T_OBJECT with >= 94 IVARs would crash during compaction attempting to make an object too large to embed. --- gc.c | 8 +++++++- test/ruby/test_gc_compact.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 74502dc21b74a5..c9fa18f42b8e8e 100644 --- a/gc.c +++ b/gc.c @@ -3332,7 +3332,13 @@ rb_gc_obj_optimal_size(VALUE obj) return sizeof(struct RObject); } else { - return rb_obj_embedded_size(ROBJECT_FIELDS_CAPACITY(obj)); + size_t size = rb_obj_embedded_size(ROBJECT_FIELDS_CAPACITY(obj)); + if (rb_gc_size_allocatable_p(size)) { + return size; + } + else { + return sizeof(struct RObject); + } } case T_STRING: diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index 32b0a5def0b8f5..a03535171c42e8 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -361,6 +361,22 @@ def add_ivars end; end + def test_compact_objects_of_varying_sizes + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + $objects = [] + 160.times do |n| + obj = Class.new.new + n.times { |i| obj.instance_variable_set("@foo" + i.to_s, 0) } + $objects << obj + end + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + end; + end + def test_moving_strings_up_heaps omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 From 83b0cfe1b5d5cab1328aeeac0c1c6eb2484b66de Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Wed, 30 Aug 2023 16:52:45 -0600 Subject: [PATCH 1212/2435] [ruby/rubygems] Handle BUNDLER_VERSION being set to an empty string This is useful, in case you're using Docker, and an upstream Dockerfile sets BUNDLER_VERSION to something you don't want. It's impossible to unset it... only override to be the empty string. https://github.com/ruby/rubygems/commit/ffa3eb9ac6 --- lib/bundler/self_manager.rb | 2 +- lib/rubygems/bundler_version_finder.rb | 1 + test/rubygems/test_gem_bundler_version_finder.rb | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 7db6c9f6f1cd9e..1db77fd46b3fef 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -97,7 +97,7 @@ def needs_switching?(restart_version) end def autoswitching_applies? - ENV["BUNDLER_VERSION"].nil? && + (ENV["BUNDLER_VERSION"].nil? || ENV["BUNDLER_VERSION"].empty?) && ruby_can_restart_with_same_arguments? && lockfile_version end diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index 4ebbad1c0cfa33..ac8988dea577bb 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -3,6 +3,7 @@ module Gem::BundlerVersionFinder def self.bundler_version v = ENV["BUNDLER_VERSION"] + v = nil if v&.empty? v ||= bundle_update_bundler_version return if v == true diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 39255bc4e1d7cb..908f9278323c09 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -32,6 +32,11 @@ def test_bundler_version_with_env_var assert_equal v("1.1.1.1"), bvf.bundler_version end + def test_bundler_version_with_empty_env_var + ENV["BUNDLER_VERSION"] = "" + assert_nil bvf.bundler_version + end + def test_bundler_version_with_bundle_update_bundler ARGV.replace %w[update --bundler] assert_nil bvf.bundler_version From 3b9539176bc37b3e95864b04b0eef1263f434eef Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 17 Nov 2025 21:33:58 +0100 Subject: [PATCH 1213/2435] [ruby/rubygems] Warn users that `bundle` now display the help: - In https://github.com/ruby/rubygems/commit/31d67ecc056fb5a9193bc66a6e69e21576a87702 we enforced the new behaviour where running `bundle` no longer installs gems but displays the help. Users now have a way to configure their preferred default command using the `BUNDLE_DEFAULT_CLI_COMMAND` flag. With the preview of Ruby 4.0 now being released, some people will start to see this new change. The problem is that the previous behaviour had existed for like an eternity and we didn't warn users about this change in advance. I'd like to provide a deprecation/warning cycle because this is confusing users already and this breaks various CI setup that now needs to be changed immediately. https://github.com/ruby/rubygems/commit/e415480ac5 --- lib/bundler/cli.rb | 17 ++++++++++++++++- lib/bundler/settings.rb | 1 - spec/bundler/bundler/cli_spec.rb | 12 ++++-------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 86b86b359fcf82..79f93a7784734c 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -107,7 +107,22 @@ def cli_help shell.say self.class.send(:class_options_help, shell) end - default_task(Bundler.settings[:default_cli_command]) + + def self.default_command(meth = nil) + return super if meth + + default_cli_command = Bundler.settings[:default_cli_command] + return default_cli_command if default_cli_command + + Bundler.ui.warn(<<~MSG) + In the next version of Bundler, running `bundle` without argument will no longer run `bundle install`. + Instead, the `help` command will be displayed. + + If you'd like to keep the previous behaviour please run `bundle config set default_cli_command install --global`. + MSG + + "install" + end class_option "no-color", type: :boolean, desc: "Disable colorization in output" class_option "retry", type: :numeric, aliases: "-r", banner: "NUM", diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 81f1857eec7648..fb1b875b264500 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -83,7 +83,6 @@ class Settings "BUNDLE_VERSION" => "lockfile", "BUNDLE_LOCKFILE_CHECKSUMS" => true, "BUNDLE_CACHE_ALL" => true, - "BUNDLE_DEFAULT_CLI_COMMAND" => "cli_help", "BUNDLE_PLUGINS" => true, "BUNDLE_GLOBAL_GEM_CACHE" => false, "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false, diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index b3a97e72ceb2d8..ee3c0d0fd50ff5 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,14 +87,10 @@ def out_with_macos_man_workaround end context "with no arguments" do - it "prints a concise help message by default" do - bundle "" - expect(err).to be_empty - expect(out).to include("Bundler version #{Bundler::VERSION}"). - and include("\n\nBundler commands:\n\n"). - and include("\n\n Primary commands:\n"). - and include("\n\n Utilities:\n"). - and include("\n\nOptions:\n") + it "installs and log a warning by default" do + bundle "", raise_on_error: false + expect(err).to include("running `bundle` without argument will no longer run `bundle install`.") + expect(err).to include("Could not locate Gemfile") end it "prints a concise help message when default_cli_command set to cli_help" do From 1979f8c07d6c2794dda7b482372c9dc0e9f305b0 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Fri, 14 Nov 2025 10:14:44 -0500 Subject: [PATCH 1214/2435] [ruby/prism] Add docs for super nodes https://github.com/ruby/prism/commit/69abcdbb18 --- prism/config.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/prism/config.yml b/prism/config.yml index e57aa850445c5d..69a46de628e63c 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -2628,11 +2628,18 @@ nodes: - name: block type: node? kind: BlockNode + comment: | + All other arguments are forwarded as normal, except the original block is replaced with the new block. comment: | - Represents the use of the `super` keyword without parentheses or arguments. + Represents the use of the `super` keyword without parentheses or arguments, but which might have a block. super ^^^^^ + + super { 123 } + ^^^^^^^^^^^^^ + + If it has any other arguments, it would be a `SuperNode` instead. - name: GlobalVariableAndWriteNode fields: - name: name @@ -4508,6 +4515,7 @@ nodes: - name: arguments type: node? kind: ArgumentsNode + comment: "Can be only `nil` when there are empty parentheses, like `super()`." - name: rparen_loc type: location? - name: block @@ -4523,6 +4531,8 @@ nodes: super foo, bar ^^^^^^^^^^^^^^ + + If no arguments are provided (except for a block), it would be a `ForwardingSuperNode` instead. - name: SymbolNode flags: SymbolFlags fields: From cdb9893c552f67a6065dcb165b2040d35c57aee3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Dec 2024 14:56:58 +0900 Subject: [PATCH 1215/2435] Win32: Drop support for older than MSVC 8.0/_MSC_VER 1400 Visual C++ 2005 (8.0): - _MSC_VER: 1400 - MSVCRT_VERSION: 80 --- configure.ac | 1 + debug.c | 4 - error.c | 2 +- ext/socket/getaddrinfo.c | 3 - ext/socket/getnameinfo.c | 3 - include/ruby/internal/compiler_is/msvc.h | 13 +-- include/ruby/win32.h | 13 +-- internal/bits.h | 33 +++--- numeric.c | 35 +----- parser_bits.h | 30 ++--- random.c | 2 +- regint.h | 13 --- vm_insnhelper.c | 23 ---- win32/Makefile.sub | 43 ++----- win32/file.c | 4 - win32/win32.c | 143 +---------------------- 16 files changed, 50 insertions(+), 315 deletions(-) diff --git a/configure.ac b/configure.ac index 339ee3b2f2e66e..43e517f370982d 100644 --- a/configure.ac +++ b/configure.ac @@ -526,6 +526,7 @@ AS_CASE(["$target_os"], RT_VER=`echo "$rb_cv_msvcrt" | tr -cd [0-9]` test "$RT_VER" = "" && RT_VER=60 test "$rb_cv_msvcrt" = "ucrt" && RT_VER=140 + AS_IF([test $RT_VER -lt 80], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) AC_DEFINE_UNQUOTED(RUBY_MSVCRT_VERSION, $RT_VER) sysconfdir= ]) diff --git a/debug.c b/debug.c index 4717a0bc9c5e90..b92faa8f369398 100644 --- a/debug.c +++ b/debug.c @@ -168,9 +168,7 @@ ruby_debug_breakpoint(void) } #if defined _WIN32 -# if RUBY_MSVCRT_VERSION >= 80 extern int ruby_w32_rtc_error; -# endif #endif #if defined _WIN32 || defined __CYGWIN__ #include @@ -233,9 +231,7 @@ ruby_env_debug_option(const char *str, int len, void *arg) SET_WHEN("ci", ruby_on_ci, 1); SET_WHEN_UINT("rgengc", &ruby_rgengc_debug, 1, ruby_rgengc_debug = 1); #if defined _WIN32 -# if RUBY_MSVCRT_VERSION >= 80 SET_WHEN("rtc_error", ruby_w32_rtc_error, 1); -# endif #endif #if defined _WIN32 || defined __CYGWIN__ SET_WHEN_UINT("codepage", ruby_w32_codepage, numberof(ruby_w32_codepage), diff --git a/error.c b/error.c index abf50b696aefa6..f452f7b01cf23b 100644 --- a/error.c +++ b/error.c @@ -1076,7 +1076,7 @@ NORETURN(static void die(void)); static void die(void) { -#if defined(_WIN32) && defined(RUBY_MSVCRT_VERSION) && RUBY_MSVCRT_VERSION >= 80 +#if defined(_WIN32) _set_abort_behavior( 0, _CALL_REPORTFAULT); #endif diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index 5b824996552e5f..9a65490b1d662d 100644 --- a/ext/socket/getaddrinfo.c +++ b/ext/socket/getaddrinfo.c @@ -62,9 +62,6 @@ #endif #include #else -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include -#endif #include #include #include diff --git a/ext/socket/getnameinfo.c b/ext/socket/getnameinfo.c index ffda7f98c73f6d..98da8c1647afb9 100644 --- a/ext/socket/getnameinfo.c +++ b/ext/socket/getnameinfo.c @@ -55,9 +55,6 @@ #endif #endif #ifdef _WIN32 -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include -#endif #include #include #define snprintf _snprintf diff --git a/include/ruby/internal/compiler_is/msvc.h b/include/ruby/internal/compiler_is/msvc.h index 8a864ea558a53f..824f0ecc21e83a 100644 --- a/include/ruby/internal/compiler_is/msvc.h +++ b/include/ruby/internal/compiler_is/msvc.h @@ -38,19 +38,8 @@ # define RBIMPL_COMPILER_VERSION_MINOR (_MSC_FULL_VER % 10000000 / 100000) # define RBIMPL_COMPILER_VERSION_PATCH (_MSC_FULL_VER % 100000) -#elif defined(_MSC_FULL_VER) -# define RBIMPL_COMPILER_IS_MSVC 1 -# /* _MSC_FULL_VER = XXYYZZZZ */ -# define RBIMPL_COMPILER_VERSION_MAJOR (_MSC_FULL_VER / 1000000) -# define RBIMPL_COMPILER_VERSION_MINOR (_MSC_FULL_VER % 1000000 / 10000) -# define RBIMPL_COMPILER_VERSION_PATCH (_MSC_FULL_VER % 10000) - #else -# define RBIMPL_COMPILER_IS_MSVC 1 -# /* _MSC_VER = XXYY */ -# define RBIMPL_COMPILER_VERSION_MAJOR (_MSC_VER / 100) -# define RBIMPL_COMPILER_VERSION_MINOR (_MSC_VER % 100) -# define RBIMPL_COMPILER_VERSION_PATCH 0 +# error Unsupported MSVC version #endif #endif /* RBIMPL_COMPILER_IS_MSVC_H */ diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 69e92ed9ffa1ba..57e8ab471b8fc4 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -30,15 +30,10 @@ extern "C++" { /* template without extern "C++" */ #if !defined(_WIN64) && !defined(WIN32) #define WIN32 #endif -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include -#endif #include #include #include -#if !defined(_MSC_VER) || _MSC_VER >= 1400 #include -#endif #if defined(__cplusplus) && defined(_MSC_VER) } #endif @@ -59,13 +54,7 @@ extern "C++" { /* template without extern "C++" */ #include #include #include -#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER == 1200 -extern "C++" { /* template without extern "C++" */ -#endif #include -#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER == 1200 -} -#endif #include #include #include @@ -436,7 +425,7 @@ extern int rb_w32_utruncate(const char *path, rb_off_t length); #define HAVE_TRUNCATE 1 #define truncate rb_w32_utruncate -#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1800 +#if defined(_MSC_VER) && _MSC_VER < 1800 #define strtoll _strtoi64 #define strtoull _strtoui64 #endif diff --git a/internal/bits.h b/internal/bits.h index 2b5aecf1128a72..bbd59ffaca5cbc 100644 --- a/internal/bits.h +++ b/internal/bits.h @@ -30,13 +30,13 @@ #include /* for uintptr_t */ #include "internal/compilers.h" /* for MSC_VERSION_SINCE */ -#if MSC_VERSION_SINCE(1310) +#ifdef _MSC_VER # include /* for _byteswap_uint64 */ #endif #if defined(HAVE_X86INTRIN_H) # include /* for _lzcnt_u64 */ -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) # include /* for the following intrinsics */ #endif @@ -50,16 +50,13 @@ # pragma intrinsic(__lzcnt64) #endif -#if MSC_VERSION_SINCE(1310) +#if defined(_MSC_VER) # pragma intrinsic(_rotl) # pragma intrinsic(_rotr) # ifdef _WIN64 # pragma intrinsic(_rotl64) # pragma intrinsic(_rotr64) # endif -#endif - -#if MSC_VERSION_SINCE(1400) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # ifdef _WIN64 @@ -266,7 +263,7 @@ ruby_swap16(uint16_t x) #if __has_builtin(__builtin_bswap16) return __builtin_bswap16(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_ushort(x); #else @@ -281,7 +278,7 @@ ruby_swap32(uint32_t x) #if __has_builtin(__builtin_bswap32) return __builtin_bswap32(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_ulong(x); #else @@ -298,7 +295,7 @@ ruby_swap64(uint64_t x) #if __has_builtin(__builtin_bswap64) return __builtin_bswap64(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_uint64(x); #else @@ -323,7 +320,7 @@ nlz_int32(uint32_t x) #elif defined(__x86_64__) && defined(__LZCNT__) return (unsigned int)_lzcnt_u32(x); -#elif MSC_VERSION_SINCE(1400) /* &&! defined(__AVX2__) */ +#elif defined(_MSC_VER) /* &&! defined(__AVX2__) */ unsigned long r; return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; @@ -352,7 +349,7 @@ nlz_int64(uint64_t x) #elif defined(__x86_64__) && defined(__LZCNT__) return (unsigned int)_lzcnt_u64(x); -#elif defined(_WIN64) && MSC_VERSION_SINCE(1400) /* &&! defined(__AVX2__) */ +#elif defined(_WIN64) && defined(_MSC_VER) /* &&! defined(__AVX2__) */ unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; @@ -538,7 +535,7 @@ ntz_int32(uint32_t x) #if defined(__x86_64__) && defined(__BMI__) return (unsigned)_tzcnt_u32(x); -#elif MSC_VERSION_SINCE(1400) +#elif defined(_MSC_VER) /* :FIXME: Is there any way to issue TZCNT instead of BSF, apart from using * assembly? Because issuing LZCNT seems possible (see nlz.h). */ unsigned long r; @@ -559,8 +556,8 @@ ntz_int64(uint64_t x) { #if defined(__x86_64__) && defined(__BMI__) return (unsigned)_tzcnt_u64(x); - -#elif defined(_WIN64) && MSC_VERSION_SINCE(1400) +` +#elif defined(_WIN64) && defined(_MSC_VER) unsigned long r; return _BitScanForward64(&r, x) ? (int)r : 64; @@ -608,10 +605,10 @@ RUBY_BIT_ROTL(VALUE v, int n) #elif __has_builtin(__builtin_rotateleft64) && (SIZEOF_VALUE * CHAR_BIT == 64) return __builtin_rotateleft64(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 32) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 32) return _rotl(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 64) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 64) return _rotl64(v, n); #elif defined(_lrotl) && (SIZEOF_VALUE == SIZEOF_LONG) @@ -632,10 +629,10 @@ RUBY_BIT_ROTR(VALUE v, int n) #elif __has_builtin(__builtin_rotateright64) && (SIZEOF_VALUE * CHAR_BIT == 64) return __builtin_rotateright64(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 32) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 32) return _rotr(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 64) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 64) return _rotr64(v, n); #elif defined(_lrotr) && (SIZEOF_VALUE == SIZEOF_LONG) diff --git a/numeric.c b/numeric.c index 731a8095239107..3d6648f7d6fbd7 100644 --- a/numeric.c +++ b/numeric.c @@ -1591,17 +1591,11 @@ rb_float_equal(VALUE x, VALUE y) } else if (RB_FLOAT_TYPE_P(y)) { b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(b)) return Qfalse; -#endif } else { return num_equal(x, y); } a = RFLOAT_VALUE(x); -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) return Qfalse; -#endif return RBOOL(a == b); } @@ -1734,16 +1728,10 @@ rb_float_gt(VALUE x, VALUE y) } else if (RB_FLOAT_TYPE_P(y)) { b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(b)) return Qfalse; -#endif } else { return rb_num_coerce_relop(x, y, '>'); } -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) return Qfalse; -#endif return RBOOL(a > b); } @@ -1777,16 +1765,10 @@ flo_ge(VALUE x, VALUE y) } else if (RB_FLOAT_TYPE_P(y)) { b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(b)) return Qfalse; -#endif } else { return rb_num_coerce_relop(x, y, idGE); } -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) return Qfalse; -#endif return RBOOL(a >= b); } @@ -1819,16 +1801,10 @@ flo_lt(VALUE x, VALUE y) } else if (RB_FLOAT_TYPE_P(y)) { b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(b)) return Qfalse; -#endif } else { return rb_num_coerce_relop(x, y, '<'); } -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) return Qfalse; -#endif return RBOOL(a < b); } @@ -1862,16 +1838,10 @@ flo_le(VALUE x, VALUE y) } else if (RB_FLOAT_TYPE_P(y)) { b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(b)) return Qfalse; -#endif } else { return rb_num_coerce_relop(x, y, idLE); } -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) return Qfalse; -#endif return RBOOL(a <= b); } @@ -1899,10 +1869,7 @@ rb_float_eql(VALUE x, VALUE y) if (RB_FLOAT_TYPE_P(y)) { double a = RFLOAT_VALUE(x); double b = RFLOAT_VALUE(y); -#if MSC_VERSION_BEFORE(1300) - if (isnan(a) || isnan(b)) return Qfalse; -#endif - return RBOOL(a == b); + return RBOOL(a == b); } return Qfalse; } diff --git a/parser_bits.h b/parser_bits.h index ca7535280efb61..cbe42db39631be 100644 --- a/parser_bits.h +++ b/parser_bits.h @@ -30,13 +30,13 @@ #include /* for uintptr_t */ #include "internal/compilers.h" /* for MSC_VERSION_SINCE */ -#if MSC_VERSION_SINCE(1310) +#if defined(_MSC_VER) # include /* for _byteswap_uint64 */ #endif #if defined(HAVE_X86INTRIN_H) # include /* for _lzcnt_u64 */ -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) # include /* for the following intrinsics */ #endif @@ -50,7 +50,7 @@ # pragma intrinsic(__lzcnt64) #endif -#if MSC_VERSION_SINCE(1310) +#if defined(_MSC_VER) # pragma intrinsic(_rotl) # pragma intrinsic(_rotr) # ifdef _WIN64 @@ -59,7 +59,7 @@ # endif #endif -#if MSC_VERSION_SINCE(1400) +#if defined(_MSC_VER) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # ifdef _WIN64 @@ -180,7 +180,7 @@ ruby_swap16(uint16_t x) #if __has_builtin(__builtin_bswap16) return __builtin_bswap16(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_ushort(x); #else @@ -195,7 +195,7 @@ ruby_swap32(uint32_t x) #if __has_builtin(__builtin_bswap32) return __builtin_bswap32(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_ulong(x); #else @@ -212,7 +212,7 @@ ruby_swap64(uint64_t x) #if __has_builtin(__builtin_bswap64) return __builtin_bswap64(x); -#elif MSC_VERSION_SINCE(1310) +#elif defined(_MSC_VER) return _byteswap_uint64(x); #else @@ -237,7 +237,7 @@ nlz_int32(uint32_t x) #elif defined(__x86_64__) && defined(__LZCNT__) return (unsigned int)_lzcnt_u32(x); -#elif MSC_VERSION_SINCE(1400) /* &&! defined(__AVX2__) */ +#elif defined(_MSC_VER) /* &&! defined(__AVX2__) */ unsigned long r; return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; @@ -266,7 +266,7 @@ nlz_int64(uint64_t x) #elif defined(__x86_64__) && defined(__LZCNT__) return (unsigned int)_lzcnt_u64(x); -#elif defined(_WIN64) && MSC_VERSION_SINCE(1400) /* &&! defined(__AVX2__) */ +#elif defined(_WIN64) && defined(_MSC_VER) /* &&! defined(__AVX2__) */ unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; @@ -452,7 +452,7 @@ ntz_int32(uint32_t x) #if defined(__x86_64__) && defined(__BMI__) return (unsigned)_tzcnt_u32(x); -#elif MSC_VERSION_SINCE(1400) +#elif defined(_MSC_VER) /* :FIXME: Is there any way to issue TZCNT instead of BSF, apart from using * assembly? Because issuing LZCNT seems possible (see nlz.h). */ unsigned long r; @@ -474,7 +474,7 @@ ntz_int64(uint64_t x) #if defined(__x86_64__) && defined(__BMI__) return (unsigned)_tzcnt_u64(x); -#elif defined(_WIN64) && MSC_VERSION_SINCE(1400) +#elif defined(_WIN64) && defined(_MSC_VER) unsigned long r; return _BitScanForward64(&r, x) ? (int)r : 64; @@ -522,10 +522,10 @@ RUBY_BIT_ROTL(VALUE v, int n) #elif __has_builtin(__builtin_rotateleft64) && (SIZEOF_VALUE * CHAR_BIT == 64) return __builtin_rotateleft64(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 32) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 32) return _rotl(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 64) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 64) return _rotl64(v, n); #elif defined(_lrotl) && (SIZEOF_VALUE == SIZEOF_LONG) @@ -546,10 +546,10 @@ RUBY_BIT_ROTR(VALUE v, int n) #elif __has_builtin(__builtin_rotateright64) && (SIZEOF_VALUE * CHAR_BIT == 64) return __builtin_rotateright64(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 32) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 32) return _rotr(v, n); -#elif MSC_VERSION_SINCE(1310) && (SIZEOF_VALUE * CHAR_BIT == 64) +#elif defined(_MSC_VER) && (SIZEOF_VALUE * CHAR_BIT == 64) return _rotr64(v, n); #elif defined(_lrotr) && (SIZEOF_VALUE == SIZEOF_LONG) diff --git a/random.c b/random.c index bf5588186c2771..0fd953b81e9eed 100644 --- a/random.c +++ b/random.c @@ -230,7 +230,7 @@ int_pair_to_real_inclusive(uint32_t a, uint32_t b) const uint128_t m = ((uint128_t)1 << dig) | 1; uint128_t x = ((uint128_t)a << 32) | b; r = (double)(uint64_t)((x * m) >> 64); -#elif defined HAVE_UINT64_T && !MSC_VERSION_BEFORE(1300) +#elif defined HAVE_UINT64_T uint64_t x = ((uint64_t)a << dig_u) + (((uint64_t)b + (a >> dig_u)) >> dig_r64); r = (double)x; diff --git a/regint.h b/regint.h index 9924e5f62ab5f3..9f59ca6006deb7 100644 --- a/regint.h +++ b/regint.h @@ -266,19 +266,6 @@ # include #endif -#ifdef _WIN32 -# if defined(_MSC_VER) && (_MSC_VER < 1300) -# ifndef _INTPTR_T_DEFINED -# define _INTPTR_T_DEFINED -typedef int intptr_t; -# endif -# ifndef _UINTPTR_T_DEFINED -# define _UINTPTR_T_DEFINED -typedef unsigned int uintptr_t; -# endif -# endif -#endif /* _WIN32 */ - #ifndef PRIdPTR # ifdef _WIN64 # define PRIdPTR "I64d" diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 1b1eeb69d9ffb4..8495ee59ef438e 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2518,15 +2518,6 @@ opt_equality_specialized(VALUE recv, VALUE obj) double a = RFLOAT_VALUE(recv); double b = RFLOAT_VALUE(obj); -#if MSC_VERSION_BEFORE(1300) - if (isnan(a)) { - return Qfalse; - } - else if (isnan(b)) { - return Qfalse; - } - else -#endif return RBOOL(a == b); } else if (RBASIC_CLASS(recv) == rb_cString && EQ_UNREDEFINED_P(STRING)) { @@ -2624,37 +2615,27 @@ check_match(rb_execution_context_t *ec, VALUE pattern, VALUE target, enum vm_che } -#if MSC_VERSION_BEFORE(1300) -#define CHECK_CMP_NAN(a, b) if (isnan(a) || isnan(b)) return Qfalse; -#else -#define CHECK_CMP_NAN(a, b) /* do nothing */ -#endif - static inline VALUE double_cmp_lt(double a, double b) { - CHECK_CMP_NAN(a, b); return RBOOL(a < b); } static inline VALUE double_cmp_le(double a, double b) { - CHECK_CMP_NAN(a, b); return RBOOL(a <= b); } static inline VALUE double_cmp_gt(double a, double b) { - CHECK_CMP_NAN(a, b); return RBOOL(a > b); } static inline VALUE double_cmp_ge(double a, double b) { - CHECK_CMP_NAN(a, b); return RBOOL(a >= b); } @@ -6878,7 +6859,6 @@ vm_opt_lt(VALUE recv, VALUE obj) else if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat && BASIC_OP_UNREDEFINED_P(BOP_LT, FLOAT_REDEFINED_OP_FLAG)) { - CHECK_CMP_NAN(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)); return RBOOL(RFLOAT_VALUE(recv) < RFLOAT_VALUE(obj)); } else { @@ -6903,7 +6883,6 @@ vm_opt_le(VALUE recv, VALUE obj) else if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat && BASIC_OP_UNREDEFINED_P(BOP_LE, FLOAT_REDEFINED_OP_FLAG)) { - CHECK_CMP_NAN(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)); return RBOOL(RFLOAT_VALUE(recv) <= RFLOAT_VALUE(obj)); } else { @@ -6928,7 +6907,6 @@ vm_opt_gt(VALUE recv, VALUE obj) else if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat && BASIC_OP_UNREDEFINED_P(BOP_GT, FLOAT_REDEFINED_OP_FLAG)) { - CHECK_CMP_NAN(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)); return RBOOL(RFLOAT_VALUE(recv) > RFLOAT_VALUE(obj)); } else { @@ -6953,7 +6931,6 @@ vm_opt_ge(VALUE recv, VALUE obj) else if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat && BASIC_OP_UNREDEFINED_P(BOP_GE, FLOAT_REDEFINED_OP_FLAG)) { - CHECK_CMP_NAN(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)); return RBOOL(RFLOAT_VALUE(recv) >= RFLOAT_VALUE(obj)); } else { diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 81a33277afb161..5bad1d3d752861 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -130,6 +130,11 @@ TEST_RUNNABLE = yes CAT_DEPEND = type +!if !defined(MSC_VER) +! error MSC_VER not defined. Retry from configure pass. +!else if $(MSC_VER) < 1400 +! error MSVC $(MSC_VER) is not supported +!endif !if !defined(MACHINE) MACHINE = x86 !endif @@ -140,9 +145,6 @@ PROCESSOR_LEVEL = 5 !if 6 < $(PROCESSOR_LEVEL) PROCESSOR_LEVEL = 6 !endif -!if $(MSC_VER) < 1400 -PROCESSOR_FLAG = -G$(PROCESSOR_LEVEL) -!endif CPU = i$(PROCESSOR_LEVEL)86 ARCH = i386 !else @@ -159,12 +161,8 @@ XCFLAGS = $(XCFLAGS) -DRUBY_DEVEL=1 XCFLAGS = $(XCFLAGS) -Dmodular_gc_dir="$(modular_gc_dir)" !endif !if !defined(OPTFLAGS) -!if $(MSC_VER) < 1400 -OPTFLAGS = -O2b2xg- -!else OPTFLAGS = -O2sy- !endif -!endif !if $(MSC_VER) >= 1900 OPTFLAGS = $(OPTFLAGS) -Zc:inline !endif @@ -176,6 +174,8 @@ PLATFORM = mswin32 !endif !if !defined(RT) !error RT not defined. Retry from configure pass. +!else if $(RT_VER) < 80 +! error Runtime library $(RT_VER) is not supported !endif !ifndef NTVER NTVER = _WIN32_WINNT_WIN8 @@ -278,13 +278,9 @@ RUNTIMEFLAG = -MD COMPILERFLAG = -Zm600 !endif !if !defined(WARNFLAGS) -!if $(MSC_VER) >= 1400 WARNFLAGS = -W2 -wd4100 -wd4127 -wd4210 -wd4214 -wd4255 -wd4574 \ -wd4668 -wd4710 -wd4711 -wd4820 -wd4996 \ -we4028 -we4142 -we4047 -we4013 -!else -WARNFLAGS = -W2 -!endif !if $(MSC_VER) >= 1944 # https://developercommunity.visualstudio.com/t/warning-C5287:-operands-are-different-e/10877942 WARNFLAGS = $(WARNFLAGS) -wd5287 @@ -318,9 +314,7 @@ EXTSOLIBS = !endif !if !defined(LIBS) LIBS = user32.lib advapi32.lib shell32.lib ws2_32.lib -!if $(MSC_VER) >= 1400 LIBS = $(LIBS) iphlpapi.lib -!endif !if defined(USE_GMP) LIBS = $(LIBS) gmp.lib !endif @@ -697,11 +691,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub !if $(MSC_VER) >= 1920 #define HAVE_AFUNIX_H 1 !endif -!if $(MSC_VER) >= 1400 #define HAVE_LONG_LONG 1 -!else -#define ULL_TO_DOUBLE(n) ((double)(unsigned long)((n)>>32) * (1I64 << 32) + (unsigned long)(n)) -!endif #define HAVE_OFF_T 1 #define rb_off_t __int64 #define SIGNEDNESS_OF_OFF_T -1 @@ -710,11 +700,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define SIZEOF_INT 4 #define SIZEOF_SHORT 2 #define SIZEOF_LONG 4 -!if $(MSC_VER) >= 1400 #define SIZEOF_LONG_LONG 8 -!else -#define SIZEOF_LONG_LONG 0 -!endif #define SIZEOF___INT64 8 #ifndef _INTEGRAL_MAX_BITS #define _INTEGRAL_MAX_BITS 64 @@ -729,15 +715,9 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define SIZEOF_DOUBLE 8 #define SIGNEDNESS_OF_TIME_T -1 #define NEGATIVE_TIME_T 1 -!if $(RT_VER) >= 80 #define SIZEOF_TIME_T 8 #define TIMET2NUM(v) LL2NUM(v) #define NUM2TIMET(v) NUM2LL(v) -!else -#define SIZEOF_TIME_T 4 -#define TIMET2NUM(v) LONG2NUM(v) -#define NUM2TIMET(v) NUM2LONG(v) -!endif #define CLOCKID2NUM(v) INT2NUM(v) #define NUM2CLOCKID(v) NUM2INT(v) #define SIZEOF_CLOCK_T 4 @@ -753,22 +733,15 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define SIZEOF_INTPTR_T 4 #define SIZEOF_UINTPTR_T 4 !endif -!if $(MSC_VER) < 1400 -#define SIZE_MAX UINT_MAX -!endif !if $(MSC_VER) >= 1800 #define HAVE_VA_COPY 1 !else #define HAVE_VA_COPY_VIA_STRUCT_ASSIGNMENT 1 !endif -!if $(MSC_VER) > 1100 #define NORETURN(x) __declspec(noreturn) x -!endif -!if $(MSC_VER) >= 1300 #define DEPRECATED(x) __declspec(deprecated) x #define RUBY_CXX_DEPRECATED(mesg) __declspec(deprecated(mesg)) #define NOINLINE(x) __declspec(noinline) x -!endif #define ALWAYS_INLINE(x) __forceinline x #define WARN_UNUSED_RESULT(x) x #define MAYBE_UNUSED(x) x @@ -1119,7 +1092,7 @@ s,@LIBPATHFLAG@,-libpath:%s,;t t s,@RPATHFLAG@,,;t t s,@LIBARG@,%s.lib,;t t s,@LINK_SO@,$$(LDSHARED) -Fe$$(@) $$(OBJS) $$(LIBS) $$(LOCAL_LIBS) -link $$(DLDFLAGS) -implib:$$(*F:.so=)-$$(arch).lib -pdb:$$(*F:.so=)-$$(arch).pdb -def:$$(DEFFILE),;t t -!if $(MSC_VER) >= 1400 && $(MSC_VER) < 1800 +!if $(MSC_VER) < 1800 s,@LINK_SO@,@if exist $$(@).manifest $$(RUBY) -run -e wait_writable -- -n 10 $$(@),;t t s,@LINK_SO@,@if exist $$(@).manifest $(MANIFESTTOOL) -manifest $$(@).manifest -outputresource:$$(@);2,;t t s,@LINK_SO@,@if exist $$(@).manifest $$(RM) $$(@:/=\).manifest,;t t diff --git a/win32/file.c b/win32/file.c index f137f04c43db94..26b99715cd6ca8 100644 --- a/win32/file.c +++ b/win32/file.c @@ -629,14 +629,10 @@ rb_freopen(VALUE fname, const char *mode, FILE *file) len = MultiByteToWideChar(CP_UTF8, 0, name, n, wname, len); wname[len] = L'\0'; RB_GC_GUARD(fname); -#if RUBY_MSVCRT_VERSION < 80 && !defined(HAVE__WFREOPEN_S) - e = _wfreopen(wname, wmode, file) ? 0 : errno; -#else { FILE *newfp = 0; e = _wfreopen_s(&newfp, wname, wmode, file); } -#endif ALLOCV_END(wtmp); return e; } diff --git a/win32/win32.c b/win32/win32.c index e7dfe2b065975a..9cffd67597aae0 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -21,6 +21,10 @@ #undef __STRICT_ANSI__ +/* Visual C++ 2005 (8.0): + * - _MSC_VER: 1400 + * - MSVCRT_VERSION: 80 + */ #include "ruby/ruby.h" #include "ruby/encoding.h" #include "ruby/io.h" @@ -42,7 +46,7 @@ #include #include #include -#if defined _MSC_VER && _MSC_VER >= 1400 +#if defined _MSC_VER #include #include #endif @@ -65,10 +69,6 @@ #include "encindex.h" #define isdirsep(x) ((x) == '/' || (x) == '\\') -#if defined _MSC_VER && _MSC_VER <= 1200 -# define CharNextExA(cp, p, flags) CharNextExA((WORD)(cp), (p), (flags)) -#endif - static int w32_wopen(const WCHAR *file, int oflag, int perm); static int w32_stati128(const char *path, struct stati128 *st, UINT cp, BOOL lstat); static char *w32_getenv(const char *name, UINT cp); @@ -503,11 +503,6 @@ rb_w32_special_folder(int type) return rb_w32_conv_from_wchar(path, rb_filesystem_encoding()); } -#if defined _MSC_VER && _MSC_VER <= 1200 -/* License: Ruby's */ -#define GetSystemWindowsDirectoryW GetWindowsDirectoryW -#endif - /* License: Ruby's */ UINT rb_w32_system_tmpdir(WCHAR *path, UINT len) @@ -629,7 +624,6 @@ init_env(void) static void init_stdhandle(void); -#if RUBY_MSVCRT_VERSION >= 80 /* License: Ruby's */ static void invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, unsigned int line, uintptr_t dummy) @@ -639,7 +633,7 @@ invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, int ruby_w32_rtc_error; -# ifndef __MINGW32__ +#ifndef __MINGW32__ /* License: Ruby's */ RBIMPL_ATTR_NONNULL((5)) RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 5, 6) @@ -658,7 +652,6 @@ rtc_error_handler(int e, const char *src, int line, const char *exe, const char rb_write_error2(RSTRING_PTR(str), RSTRING_LEN(str)); return 0; } -# endif #endif static CRITICAL_SECTION select_mutex; @@ -852,13 +845,11 @@ socklist_delete(SOCKET *sockp, int *flagp) return ret; } -#if RUBY_MSVCRT_VERSION >= 80 # ifdef __MINGW32__ # define _CrtSetReportMode(type,mode) ((void)0) # define _RTC_SetErrorFunc(func) ((void)0) # endif static void set_pioinfo_extra(void); -#endif static int w32_cmdvector(const WCHAR *, char ***, UINT, rb_encoding *); // // Initialization stuff @@ -867,13 +858,10 @@ static int w32_cmdvector(const WCHAR *, char ***, UINT, rb_encoding *); void rb_w32_sysinit(int *argc, char ***argv) { -#if RUBY_MSVCRT_VERSION >= 80 - _CrtSetReportMode(_CRT_ASSERT, 0); _set_invalid_parameter_handler(invalid_parameter); _RTC_SetErrorFunc(rtc_error_handler); set_pioinfo_extra(); -#endif SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX); get_version(); @@ -2464,10 +2452,8 @@ typedef struct { char pipech; /* one char buffer for handles opened on pipes */ int lockinitflag; CRITICAL_SECTION lock; -#if RUBY_MSVCRT_VERSION >= 80 char textmode; char pipech2[2]; -#endif } ioinfo; #endif @@ -2492,7 +2478,6 @@ static inline ioinfo* _pioinfo(int); #define rb_acrt_lowio_lock_fh(i) EnterCriticalSection(&_pioinfo(i)->lock) #define rb_acrt_lowio_unlock_fh(i) LeaveCriticalSection(&_pioinfo(i)->lock) -#if RUBY_MSVCRT_VERSION >= 80 static size_t pioinfo_extra = 0; /* workaround for VC++8 SP1 */ /* License: Ruby's */ @@ -2656,9 +2641,6 @@ set_pioinfo_extra(void) pioinfo_extra = 0; } } -#else -#define pioinfo_extra 0 -#endif static inline ioinfo* _pioinfo(int fd) @@ -4235,7 +4217,6 @@ socketpair(int af, int type, int protocol, int *sv) return 0; } -#if !defined(_MSC_VER) || _MSC_VER >= 1400 /* License: Ruby's */ static void str2guid(const char *str, GUID *guid) @@ -4361,7 +4342,6 @@ freeifaddrs(struct ifaddrs *ifp) ifp = next; } } -#endif #if 0 // Have never been used // @@ -7592,20 +7572,6 @@ rb_w32_write_console(uintptr_t strarg, int fd) return (long)reslen; } -#if RUBY_MSVCRT_VERSION < 80 && !defined(HAVE__GMTIME64_S) -/* License: Ruby's */ -static int -unixtime_to_filetime(time_t time, FILETIME *ft) -{ - ULARGE_INTEGER tmp; - - tmp.QuadPart = unix_to_filetime((ULONGLONG)time); - ft->dwLowDateTime = tmp.LowPart; - ft->dwHighDateTime = tmp.HighPart; - return 0; -} -#endif - /* License: Ruby's */ static int timespec_to_filetime(const struct timespec *ts, FILETIME *ft) @@ -7982,23 +7948,6 @@ rb_w32_isatty(int fd) return 1; } -#if defined(_MSC_VER) && RUBY_MSVCRT_VERSION <= 60 -extern long _ftol(double); -/* License: Ruby's */ -long -_ftol2(double d) -{ - return _ftol(d); -} - -/* License: Ruby's */ -long -_ftol2_sse(double d) -{ - return _ftol(d); -} -#endif - #ifndef signbit /* License: Ruby's */ int @@ -8030,68 +7979,6 @@ rb_w32_fd_is_text(int fd) return _osfile(fd) & FTEXT; } -#if RUBY_MSVCRT_VERSION < 80 && !defined(HAVE__GMTIME64_S) -/* License: Ruby's */ -static int -unixtime_to_systemtime(const time_t t, SYSTEMTIME *st) -{ - FILETIME ft; - if (unixtime_to_filetime(t, &ft)) return -1; - if (!FileTimeToSystemTime(&ft, st)) return -1; - return 0; -} - -/* License: Ruby's */ -static void -systemtime_to_tm(const SYSTEMTIME *st, struct tm *t) -{ - int y = st->wYear, m = st->wMonth, d = st->wDay; - t->tm_sec = st->wSecond; - t->tm_min = st->wMinute; - t->tm_hour = st->wHour; - t->tm_mday = st->wDay; - t->tm_mon = st->wMonth - 1; - t->tm_year = y - 1900; - t->tm_wday = st->wDayOfWeek; - switch (m) { - case 1: - break; - case 2: - d += 31; - break; - default: - d += 31 + 28 + (!(y % 4) && ((y % 100) || !(y % 400))); - d += ((m - 3) * 153 + 2) / 5; - break; - } - t->tm_yday = d - 1; -} - -/* License: Ruby's */ -static int -systemtime_to_localtime(TIME_ZONE_INFORMATION *tz, SYSTEMTIME *gst, SYSTEMTIME *lst) -{ - TIME_ZONE_INFORMATION stdtz; - SYSTEMTIME sst; - - if (!SystemTimeToTzSpecificLocalTime(tz, gst, lst)) return -1; - if (!tz) { - GetTimeZoneInformation(&stdtz); - tz = &stdtz; - } - if (tz->StandardBias == tz->DaylightBias) return 0; - if (!tz->StandardDate.wMonth) return 0; - if (!tz->DaylightDate.wMonth) return 0; - if (tz != &stdtz) stdtz = *tz; - - stdtz.StandardDate.wMonth = stdtz.DaylightDate.wMonth = 0; - if (!SystemTimeToTzSpecificLocalTime(&stdtz, gst, &sst)) return 0; - if (lst->wMinute == sst.wMinute && lst->wHour == sst.wHour) - return 0; - return 1; -} -#endif - #ifdef HAVE__GMTIME64_S # ifndef HAVE__LOCALTIME64_S /* assume same as _gmtime64_s() */ @@ -8115,17 +8002,8 @@ gmtime_r(const time_t *tp, struct tm *rp) errno = e; return NULL; } -#if RUBY_MSVCRT_VERSION >= 80 || defined(HAVE__GMTIME64_S) e = gmtime_s(rp, tp); if (e != 0) goto error; -#else - { - SYSTEMTIME st; - if (unixtime_to_systemtime(*tp, &st)) goto error; - rp->tm_isdst = 0; - systemtime_to_tm(&st, rp); - } -#endif return rp; } @@ -8139,17 +8017,8 @@ localtime_r(const time_t *tp, struct tm *rp) errno = e; return NULL; } -#if RUBY_MSVCRT_VERSION >= 80 || defined(HAVE__LOCALTIME64_S) e = localtime_s(rp, tp); if (e) goto error; -#else - { - SYSTEMTIME gst, lst; - if (unixtime_to_systemtime(*tp, &gst)) goto error; - rp->tm_isdst = systemtime_to_localtime(NULL, &gst, &lst); - systemtime_to_tm(&lst, rp); - } -#endif return rp; } From 3dd39134cde1a5ecd3c5d3128afcabd3c95e5bea Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Dec 2024 14:58:15 +0900 Subject: [PATCH 1216/2435] Win32: Drop support for older than MSVC 9.0/_MSC_VER 1500 Visual C++ 2008 (9.0): - _MSC_VER: 1500 - MSVCRT_VERSION: 90 --- configure.ac | 2 +- ext/socket/rubysocket.h | 2 -- regint.h | 4 +--- win32/Makefile.sub | 6 ++---- win32/win32.c | 6 +++--- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index 43e517f370982d..fc24e847366687 100644 --- a/configure.ac +++ b/configure.ac @@ -526,7 +526,7 @@ AS_CASE(["$target_os"], RT_VER=`echo "$rb_cv_msvcrt" | tr -cd [0-9]` test "$RT_VER" = "" && RT_VER=60 test "$rb_cv_msvcrt" = "ucrt" && RT_VER=140 - AS_IF([test $RT_VER -lt 80], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) + AS_IF([test $RT_VER -lt 90], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) AC_DEFINE_UNQUOTED(RUBY_MSVCRT_VERSION, $RT_VER) sysconfdir= ]) diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 29b8afc1634b80..1adccee4276da0 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -510,8 +510,6 @@ extern ID tcp_fast_fallback; const char *inet_ntop(int, const void *, char *, size_t); #elif defined __MINGW32__ # define inet_ntop(f,a,n,l) rb_w32_inet_ntop(f,a,n,l) -#elif defined _MSC_VER && RUBY_MSVCRT_VERSION < 90 -const char *WSAAPI inet_ntop(int, const void *, char *, size_t); #endif #endif diff --git a/regint.h b/regint.h index 9f59ca6006deb7..9d69e2d25e51a8 100644 --- a/regint.h +++ b/regint.h @@ -215,9 +215,7 @@ #define xmemcpy memcpy #define xmemmove memmove -#if ((defined(RUBY_MSVCRT_VERSION) && RUBY_MSVCRT_VERSION >= 90) \ - || (!defined(RUBY_MSVCRT_VERSION) && defined(_WIN32))) \ - && !defined(__GNUC__) +#if defined(_WIN32) && !defined(__GNUC__) # define xalloca _alloca # define xvsnprintf(buf,size,fmt,args) _vsnprintf_s(buf,size,_TRUNCATE,fmt,args) # define xsnprintf sprintf_s diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 5bad1d3d752861..d2b080c9392b9d 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -132,7 +132,7 @@ CAT_DEPEND = type !if !defined(MSC_VER) ! error MSC_VER not defined. Retry from configure pass. -!else if $(MSC_VER) < 1400 +!else if $(MSC_VER) < 1500 ! error MSVC $(MSC_VER) is not supported !endif !if !defined(MACHINE) @@ -174,7 +174,7 @@ PLATFORM = mswin32 !endif !if !defined(RT) !error RT not defined. Retry from configure pass. -!else if $(RT_VER) < 80 +!else if $(RT_VER) < 90 ! error Runtime library $(RT_VER) is not supported !endif !ifndef NTVER @@ -749,11 +749,9 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define FUNC_STDCALL(x) __stdcall x #define FUNC_CDECL(x) __cdecl x #define FUNC_FASTCALL(x) __fastcall x -!if $(MSC_VER) >= 1500 #define RUBY_FUNCTION_NAME_STRING __FUNCTION__ #define RBIMPL_ATTR_PACKED_STRUCT_BEGIN() __pragma(pack(push, 1)) #define RBIMPL_ATTR_PACKED_STRUCT_END() __pragma(pack(pop)) -!endif #define RUBY_EXTERN extern __declspec(dllimport) #define RUBY_FUNC_EXPORTED extern __declspec(dllexport) #define RUBY_ALIGNAS(n) __declspec(align(n)) diff --git a/win32/win32.c b/win32/win32.c index 9cffd67597aae0..23233635819631 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -21,9 +21,9 @@ #undef __STRICT_ANSI__ -/* Visual C++ 2005 (8.0): - * - _MSC_VER: 1400 - * - MSVCRT_VERSION: 80 +/* Visual C++ 2008 (9.0): + * - _MSC_VER: 1500 + * - MSVCRT_VERSION: 90 */ #include "ruby/ruby.h" #include "ruby/encoding.h" From 25f9e678bfb118309300de90803fe1ba4751f7da Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Dec 2024 15:00:05 +0900 Subject: [PATCH 1217/2435] Win32: Drop support for older than MSVC 10.0/_MSC_VER 1600 Visual C++ 2010 (10.0): - _MSC_VER: 1600 - MSVCRT_VERSION: 100 --- win32/Makefile.sub | 35 ++--------------------------------- win32/win32.c | 6 +++--- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index d2b080c9392b9d..5152e11d45d010 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -132,7 +132,7 @@ CAT_DEPEND = type !if !defined(MSC_VER) ! error MSC_VER not defined. Retry from configure pass. -!else if $(MSC_VER) < 1500 +!else if $(MSC_VER) < 1600 ! error MSVC $(MSC_VER) is not supported !endif !if !defined(MACHINE) @@ -174,7 +174,7 @@ PLATFORM = mswin32 !endif !if !defined(RT) !error RT not defined. Retry from configure pass. -!else if $(RT_VER) < 90 +!else if $(RT_VER) < 100 ! error Runtime library $(RT_VER) is not supported !endif !ifndef NTVER @@ -262,11 +262,7 @@ OUTFLAG = -Fe COUTFLAG = -Fo !endif !if !defined(CPPOUTFLAG) -! if $(MSC_VER) < 1600 -CPPOUTFLAG = > -! else CPPOUTFLAG = -Fi -! endif !endif !if !defined(CSRCFLAG) CSRCFLAG = -Tc @@ -367,10 +363,8 @@ MAINLIBS = $(LIBS) SOLIBS = RCFILES = $(RUBY_INSTALL_NAME).rc $(RUBYW_INSTALL_NAME).rc $(RUBY_SO_NAME).rc !ifndef RCFLAGS -!if $(MSC_VER) >= 1600 RCFLAGS=-nologo !endif -!endif ENABLE_SHARED = yes LIBRUBY_LDSHARED = $(LDSHARED) @@ -770,31 +764,8 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub !if $(MSC_VER) >= 1900 #define HAVE_STRUCT_TIMESPEC !endif -!if $(MSC_VER) >= 1600 #define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 -!else -#define int8_t signed char -#define uint8_t unsigned char -#define int16_t short -#define uint16_t unsigned short -#define int32_t int -#define uint32_t unsigned int -#define int64_t __int64 -#define uint64_t unsigned __int64 -#define INT8_MIN _I8_MIN -#define INT8_MAX _I8_MAX -#define UINT8_MAX _UI8_MAX -#define INT16_MIN _I16_MIN -#define INT16_MAX _I16_MAX -#define UINT16_MAX _UI16_MAX -#define INT32_MIN _I32_MIN -#define INT32_MAX _I32_MAX -#define UINT32_MAX _UI32_MAX -#define INT64_MIN _I64_MIN -#define INT64_MAX _I64_MAX -#define UINT64_MAX _UI64_MAX -!endif #define HAVE_INT8_T 1 #define HAVE_UINT8_T 1 #define SIZEOF_INT8_T 1 @@ -911,9 +882,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_QSORT_S !endif #define HAVE_TYPE_NET_LUID 1 -!if $(MSC_VER) >= 1600 #define HAVE_NULLPTR 1 -!endif #define SETPGRP_VOID 1 #define RSHIFT(x,y) ((x)>>(int)y) #define HAVE_RB_FD_INIT 1 diff --git a/win32/win32.c b/win32/win32.c index 23233635819631..e10d4743ec420a 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -21,9 +21,9 @@ #undef __STRICT_ANSI__ -/* Visual C++ 2008 (9.0): - * - _MSC_VER: 1500 - * - MSVCRT_VERSION: 90 +/* Visual C++ 2010 (10.0): + * - _MSC_VER: 1600 + * - MSVCRT_VERSION: 100 */ #include "ruby/ruby.h" #include "ruby/encoding.h" From 7743123551eded908f0606319a66df1f123c7cd9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Dec 2024 15:04:01 +0900 Subject: [PATCH 1218/2435] Win32: Drop support for older than MSVC 12.0/_MSC_VER 1800 Visual C++ 2013 (12.0): - _MSC_VER: 1800 - MSVCRT_VERSION: 120 --- configure.ac | 2 +- include/ruby/win32.h | 7 +---- win32/Makefile.sub | 62 ++------------------------------------------ win32/win32.c | 10 +++---- 4 files changed, 7 insertions(+), 74 deletions(-) diff --git a/configure.ac b/configure.ac index fc24e847366687..55d65534db9ee6 100644 --- a/configure.ac +++ b/configure.ac @@ -526,7 +526,7 @@ AS_CASE(["$target_os"], RT_VER=`echo "$rb_cv_msvcrt" | tr -cd [0-9]` test "$RT_VER" = "" && RT_VER=60 test "$rb_cv_msvcrt" = "ucrt" && RT_VER=140 - AS_IF([test $RT_VER -lt 90], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) + AS_IF([test $RT_VER -lt 120], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) AC_DEFINE_UNQUOTED(RUBY_MSVCRT_VERSION, $RT_VER) sysconfdir= ]) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 57e8ab471b8fc4..8d9f9ddd80be6b 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -342,7 +342,7 @@ extern int rb_w32_dup2(int, int); #include -#if defined _MSC_VER && _MSC_VER >= 1800 && defined INFINITY +#if defined _MSC_VER && defined INFINITY #pragma warning(push) #pragma warning(disable:4756) static inline float @@ -425,11 +425,6 @@ extern int rb_w32_utruncate(const char *path, rb_off_t length); #define HAVE_TRUNCATE 1 #define truncate rb_w32_utruncate -#if defined(_MSC_VER) && _MSC_VER < 1800 -#define strtoll _strtoi64 -#define strtoull _strtoui64 -#endif - /* * stubs */ diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 5152e11d45d010..ab787d8ddd71c2 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -132,7 +132,7 @@ CAT_DEPEND = type !if !defined(MSC_VER) ! error MSC_VER not defined. Retry from configure pass. -!else if $(MSC_VER) < 1600 +!else if $(MSC_VER) < 1800 ! error MSVC $(MSC_VER) is not supported !endif !if !defined(MACHINE) @@ -174,7 +174,7 @@ PLATFORM = mswin32 !endif !if !defined(RT) !error RT not defined. Retry from configure pass. -!else if $(RT_VER) < 100 +!else if $(RT_VER) < 120 ! error Runtime library $(RT_VER) is not supported !endif !ifndef NTVER @@ -318,9 +318,6 @@ LIBS = $(LIBS) imagehlp.lib shlwapi.lib bcrypt.lib $(EXTLIBS) !endif !if !defined(MISSING) MISSING = crypt.obj ffs.obj langinfo.obj lgamma_r.obj strlcat.obj strlcpy.obj win32/win32.obj win32/file.obj setproctitle.obj -!if $(RT_VER) < 120 -MISSING = $(MISSING) acosh.obj cbrt.obj erf.obj nan.obj tgamma.obj -!endif MISSING = $(MISSING) explicit_bzero.obj !endif DLNOBJ = dln.obj @@ -343,15 +340,7 @@ ARFLAGS = -machine:$(MACHINE) -out: LD = $(CC) LDSHARED = $(LD) -LD XCFLAGS = -DRUBY_EXPORT $(INCFLAGS) $(XCFLAGS) $(XINCFLAGS) -!if $(MSC_VER) >= 1800 LDFLAGS = $(LDFLAGS) -manifest:embed,ID=2 -!elseif $(MSC_VER) >= 1400 -# Prevents VC++ 2005 (cl ver 14) warnings -MANIFESTTOOL = mt -nologo -LDSHARED_0 = @if exist $(@).manifest $(MINIRUBY) -run -e wait_writable -- -n 10 $@ -LDSHARED_1 = @if exist $(@).manifest $(MANIFESTTOOL) -manifest $(@).manifest -outputresource:$(@);2 -LDSHARED_2 = @if exist $(@).manifest @$(RM) $(@:/=\).manifest -!endif CPPFLAGS = $(DEFS) $(ARCHDEFS) $(CPPFLAGS) !if "$(USE_RUBYGEMS)" == "no" CPPFLAGS = -DDISABLE_RUBYGEMS $(CPPFLAGS) @@ -675,9 +664,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define STDC_HEADERS 1 #define HAVE_SYS_TYPES_H 1 #define HAVE_SYS_STAT_H 1 -!if $(MSC_VER) >= 1800 #define HAVE_STDBOOL_H 1 -!endif #define HAVE_STDLIB_H 1 #define HAVE_STDDEF_H 1 #define HAVE_STRING_H 1 @@ -727,11 +714,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define SIZEOF_INTPTR_T 4 #define SIZEOF_UINTPTR_T 4 !endif -!if $(MSC_VER) >= 1800 #define HAVE_VA_COPY 1 -!else -#define HAVE_VA_COPY_VIA_STRUCT_ASSIGNMENT 1 -!endif #define NORETURN(x) __declspec(noreturn) x #define DEPRECATED(x) __declspec(deprecated) x #define RUBY_CXX_DEPRECATED(mesg) __declspec(deprecated(mesg)) @@ -796,7 +779,6 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define PRI_PIDT_PREFIX PRI_INT_PREFIX #define GETGROUPS_T int #define TYPEOF_TIMEVAL_TV_SEC long -!if $(RT_VER) >= 120 #define HAVE_ACOSH 1 #define HAVE_ASINH 1 #define HAVE_ATANH 1 @@ -808,7 +790,6 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_ROUND 1 #define HAVE_TGAMMA 1 #define HAVE_NEXTAFTER 1 -!endif #define HAVE_ALLOCA 1 #define HAVE_DUP2 1 #define HAVE_MEMCMP 1 @@ -825,14 +806,10 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_STRCHR 1 #define HAVE_STRSTR 1 #define HAVE_FLOCK 1 -!if $(MSC_VER) >= 1800 #define HAVE_ISINF 1 -!endif #define HAVE_ISNAN 1 #define HAVE_FINITE 1 -!if $(RT_VER) >= 120 #define HAVE_NAN 1 -!endif #define HAVE_HYPOT 1 #define HAVE_FMOD 1 #define HAVE_FREXP 1 @@ -891,11 +868,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define RUBY_JMP_BUF jmp_buf #ifndef __cplusplus #define inline __inline -!if $(MSC_VER) >= 1800 #define restrict __restrict -!else -#define restrict /* not supported */ -!endif #endif #define NEED_IO_SEEK_BETWEEN_RW 1 !if "$(MACHINE)" == "x86" || "$(ARCH)" == "x64" @@ -1059,11 +1032,6 @@ s,@LIBPATHFLAG@,-libpath:%s,;t t s,@RPATHFLAG@,,;t t s,@LIBARG@,%s.lib,;t t s,@LINK_SO@,$$(LDSHARED) -Fe$$(@) $$(OBJS) $$(LIBS) $$(LOCAL_LIBS) -link $$(DLDFLAGS) -implib:$$(*F:.so=)-$$(arch).lib -pdb:$$(*F:.so=)-$$(arch).pdb -def:$$(DEFFILE),;t t -!if $(MSC_VER) < 1800 -s,@LINK_SO@,@if exist $$(@).manifest $$(RUBY) -run -e wait_writable -- -n 10 $$(@),;t t -s,@LINK_SO@,@if exist $$(@).manifest $(MANIFESTTOOL) -manifest $$(@).manifest -outputresource:$$(@);2,;t t -s,@LINK_SO@,@if exist $$(@).manifest $$(RM) $$(@:/=\).manifest,;t t -!endif s,@COMPILE_C@,$$(CC) $$(INCFLAGS) $$(CFLAGS) $$(CPPFLAGS) $$(COUTFLAG)$$(@) -c $$(CSRCFLAG)$$(<:\=/),;t t s,@COMPILE_CXX@,$$(CXX) $$(INCFLAGS) $$(CXXFLAGS) $$(CPPFLAGS) $$(COUTFLAG)$$(@) -c -Tp$$(<:\=/),;t t s,@ASSEMBLE_C@,$$(CC) $$(CFLAGS) $$(CPPFLAGS) -Fa$$(@) -c $$(CSRCFLAG)$$(<:\=/),;t t @@ -1145,11 +1113,6 @@ $(PROGRAM): $(MAINOBJ) $(LIBRUBY_SO) $(RUBY_INSTALL_NAME).res $(ECHO) linking $(@:\=/) $(Q) $(PURIFY) $(CC) $(MAINOBJ) $(EXTOBJS) $(RUBY_INSTALL_NAME).res \ $(OUTFLAG)$@ $(LIBRUBYARG) -link $(LDFLAGS) $(XLDFLAGS) -! if defined(LDSHARED_0) - $(Q) $(LDSHARED_0) - $(Q) $(LDSHARED_1) - $(Q) $(LDSHARED_2) -! endif !endif !if "$(WPROGRAM)" != "" @@ -1158,11 +1121,6 @@ $(WPROGRAM): $(MAINOBJ) $(WINMAINOBJ) $(LIBRUBY_SO) $(RUBYW_INSTALL_NAME).res $(Q) $(PURIFY) $(CC) $(MAINOBJ) $(WINMAINOBJ) \ $(RUBYW_INSTALL_NAME).res $(OUTFLAG)$@ $(LIBRUBYARG) \ -link $(LDFLAGS) $(XLDFLAGS) -subsystem:Windows -! if defined(LDSHARED_0) - $(Q) $(LDSHARED_0) - $(Q) $(LDSHARED_1) - $(Q) $(LDSHARED_2) -! endif !endif !if "$(STUBPROGRAM)" != "" @@ -1170,11 +1128,6 @@ $(STUBPROGRAM): rubystub.$(OBJEXT) $(LIBRUBY) $(LIBRUBY_SO) $(RUBY_INSTALL_NAME) $(ECHO) linking $(@:\=/) $(Q) $(PURIFY) $(CC) rubystub.$(OBJEXT) $(RUBY_INSTALL_NAME).res \ $(OUTFLAG)$@ $(LIBRUBYARG) -link $(LDFLAGS) $(XLDFLAGS) -! if defined(LDSHARED_0) - $(Q) $(LDSHARED_0) - $(Q) $(LDSHARED_1) - $(Q) $(LDSHARED_2) -! endif !endif !if "$(LIBRUBY_SO_UPDATE)" == "" @@ -1205,12 +1158,6 @@ $(LIBRUBY_SO): $(LIBRUBY_A) $(DLDOBJS) $(RUBYDEF) $(RUBY_SO_NAME).res $(OUTFLAG)$@ -link $(LDFLAGS) $(XLDFLAGS) \ $(LIBRUBY_DLDFLAGS) @$(RM) dummy.lib dummy.exp -!if defined(LDSHARED_0) - $(Q) $(LDSHARED_0) - $(Q) $(LDSHARED_1) - $(Q) $(LDSHARED_2) -# | findstr -v -c:LNK4049 -c:LNK4217 -!endif $(RUBYDEF): $(LIBRUBY_A) $(RBCONFIG) $(ECHO) generating $(@:\=/) @@ -1445,11 +1392,6 @@ rubyspec-capiext: $(RUBYSPEC_CAPIEXT_EXTS) $(Q)$(MAKEDIRS) $(@D) $(Q)(echo EXPORTS&&echo Init_$(*F))> $*.def $(Q)$(LDSHARED) -Fe$(@) -Fo$(*).obj $(INCFLAGS) $(CFLAGS) $(CPPFLAGS) $< $(LIBRUBYARG) -link $(DLDFLAGS) $(XLDFLAGS) $(LIBS) $(LOCAL_LIBS) -implib:$*.lib -pdb:$*.pdb -def:$*.def -!if defined(LDSHARED_0) - $(Q)$(LDSHARED_0) - $(Q)$(LDSHARED_1) - $(Q)$(LDSHARED_2) -!endif $(Q)$(RM) $*.def $*.exp $*.lib $*.obj $*.pdb exts: rubyspec-capiext diff --git a/win32/win32.c b/win32/win32.c index e10d4743ec420a..a1e63283b9a9bd 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -21,9 +21,9 @@ #undef __STRICT_ANSI__ -/* Visual C++ 2010 (10.0): - * - _MSC_VER: 1600 - * - MSVCRT_VERSION: 100 +/* Visual C++ 2013 (12.0): + * - _MSC_VER: 1800 + * - MSVCRT_VERSION: 120 */ #include "ruby/ruby.h" #include "ruby/encoding.h" @@ -8235,10 +8235,6 @@ rb_w32_set_thread_description_str(HANDLE th, VALUE name) VALUE (*const rb_f_notimplement_)(int, const VALUE *, VALUE, VALUE) = rb_f_notimplement; -#if RUBY_MSVCRT_VERSION < 120 -#include "missing/nextafter.c" -#endif - void * rb_w32_mmap(void *addr, size_t len, int prot, int flags, int fd, rb_off_t offset) { From 1f2913e7417f5193ba52028e2a18c1e01b10d358 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Dec 2024 15:06:13 +0900 Subject: [PATCH 1219/2435] Win32: Drop support for older than MSVC 14.0/_MSC_VER 1900 Visual C++ 2015 (14.0): - _MSC_VER: 1900 - MSVCRT_VERSION: 140 --- win32/Makefile.sub | 10 ++-------- win32/win32.c | 49 +++++++++++----------------------------------- 2 files changed, 13 insertions(+), 46 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index ab787d8ddd71c2..f95b583fb82a5e 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -132,7 +132,7 @@ CAT_DEPEND = type !if !defined(MSC_VER) ! error MSC_VER not defined. Retry from configure pass. -!else if $(MSC_VER) < 1800 +!else if $(MSC_VER) < 1900 ! error MSVC $(MSC_VER) is not supported !endif !if !defined(MACHINE) @@ -163,9 +163,7 @@ XCFLAGS = $(XCFLAGS) -Dmodular_gc_dir="$(modular_gc_dir)" !if !defined(OPTFLAGS) OPTFLAGS = -O2sy- !endif -!if $(MSC_VER) >= 1900 OPTFLAGS = $(OPTFLAGS) -Zc:inline -!endif !if !defined(incflags) incflags = !endif @@ -174,7 +172,7 @@ PLATFORM = mswin32 !endif !if !defined(RT) !error RT not defined. Retry from configure pass. -!else if $(RT_VER) < 120 +!else if $(RT_VER) < 140 ! error Runtime library $(RT_VER) is not supported !endif !ifndef NTVER @@ -744,9 +742,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define rb_uid_t int #define HAVE_STRUCT_STAT_ST_RDEV 1 #define HAVE_STRUCT_TIMEVAL 1 -!if $(MSC_VER) >= 1900 #define HAVE_STRUCT_TIMESPEC -!endif #define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 #define HAVE_INT8_T 1 @@ -855,9 +851,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_SYSTEM 1 #define HAVE_TZSET 1 #define HAVE_UMASK 1 -!if $(RT_VER) > 120 #define HAVE_QSORT_S -!endif #define HAVE_TYPE_NET_LUID 1 #define HAVE_NULLPTR 1 #define SETPGRP_VOID 1 diff --git a/win32/win32.c b/win32/win32.c index a1e63283b9a9bd..66ce195092f5e5 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -21,9 +21,9 @@ #undef __STRICT_ANSI__ -/* Visual C++ 2013 (12.0): - * - _MSC_VER: 1800 - * - MSVCRT_VERSION: 120 +/* Visual C++ 2015 (14.0): + * - _MSC_VER: 1900 + * - MSVCRT_VERSION: 140 */ #include "ruby/ruby.h" #include "ruby/encoding.h" @@ -113,10 +113,8 @@ static char *w32_getenv(const char *name, UINT cp); #undef dup2 #undef strdup -#if RUBY_MSVCRT_VERSION >= 140 -# define _filbuf _fgetc_nolock -# define _flsbuf _fputc_nolock -#endif +#define _filbuf _fgetc_nolock +#define _flsbuf _fputc_nolock #define enough_to_get(n) (--(n) >= 0) #define enough_to_put(n) (--(n) >= 0) @@ -2401,7 +2399,6 @@ rb_w32_closedir(DIR *dirp) return 0; } -#if RUBY_MSVCRT_VERSION >= 140 typedef struct { union { @@ -2421,14 +2418,8 @@ typedef struct { #define FILE_COUNT(stream) ((vcruntime_file*)stream)->_cnt #define FILE_READPTR(stream) ((vcruntime_file*)stream)->_ptr #define FILE_FILENO(stream) ((vcruntime_file*)stream)->_file -#else -#define FILE_COUNT(stream) stream->_cnt -#define FILE_READPTR(stream) stream->_ptr -#define FILE_FILENO(stream) stream->_file -#endif /* License: Ruby's */ -#if RUBY_MSVCRT_VERSION >= 140 typedef char lowio_text_mode; typedef char lowio_pipe_lookahead[3]; @@ -2445,30 +2436,14 @@ typedef struct { uint8_t dbcsBufferUsed : 1; // Is the dbcsBuffer in use? char dbcsBuffer; // Buffer for the lead byte of DBCS when converting from DBCS to Unicode } ioinfo; -#else -typedef struct { - intptr_t osfhnd; /* underlying OS file HANDLE */ - char osfile; /* attributes of file (e.g., open in text mode?) */ - char pipech; /* one char buffer for handles opened on pipes */ - int lockinitflag; - CRITICAL_SECTION lock; - char textmode; - char pipech2[2]; -} ioinfo; -#endif #if !defined _CRTIMP || defined __MINGW32__ #undef _CRTIMP #define _CRTIMP __declspec(dllimport) #endif -#if RUBY_MSVCRT_VERSION >= 140 static ioinfo ** __pioinfo = NULL; #define IOINFO_L2E 6 -#else -EXTERN_C _CRTIMP ioinfo * __pioinfo[]; -#define IOINFO_L2E 5 -#endif static inline ioinfo* _pioinfo(int); @@ -2484,13 +2459,12 @@ static size_t pioinfo_extra = 0; /* workaround for VC++8 SP1 */ static void set_pioinfo_extra(void) { -#if RUBY_MSVCRT_VERSION >= 140 -# define FUNCTION_RET 0xc3 /* ret */ -# ifdef _DEBUG -# define UCRTBASE "ucrtbased.dll" -# else -# define UCRTBASE "ucrtbase.dll" -# endif +#define FUNCTION_RET 0xc3 /* ret */ +#ifdef _DEBUG +# define UCRTBASE "ucrtbased.dll" +#else +# define UCRTBASE "ucrtbase.dll" +#endif /* get __pioinfo addr with _isatty */ /* * Why Ruby depends to _pioinfo is @@ -2625,7 +2599,6 @@ set_pioinfo_extra(void) __pioinfo = *(ioinfo***)(p); #endif #endif /* _M_ARM64 */ -#endif /* RUBY_MSVCRT_VERSION */ int fd; fd = _open("NUL", O_RDONLY); From 69b1c567d71b269edb59a026a9a9f04a6a9a0a49 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 17 Nov 2025 21:28:51 -0500 Subject: [PATCH 1220/2435] [ruby/mmtk] Add VM barrier in rb_gc_impl_before_fork We need the VM barrier in rb_gc_impl_before_fork to stop the other Ractors because otherwise they could be allocating objects in the fast path which could be calling mmtk_add_obj_free_candidate. Since mmtk_add_obj_free_candidate acquires a lock on obj_free_candidates in weak_proc.rs, this lock may not be released in the child process after the Ractor dies. For example, the following script demonstrates the issue: puts "Hello #{Process.pid}" 100.times do |i| puts "i = #{i}" Ractor.new(i) do |j| puts "Ractor #{j} hello" 1000.times do |i| s = "#{j}-#{i}" end Ractor.receive puts "Ractor #{j} goodbye" end pid = fork { } puts "Child pid is #{pid}" _, status = Process.waitpid2 pid puts status.success? end puts "Goodbye" In the child process, we can see that it is stuck trying to acquire the lock on obj_free_candidates: #5 0x00007192bfb53f10 in mmtk_ruby::weak_proc::WeakProcessor::get_all_obj_free_candidates (self=0x7192c0657498 ) at src/weak_proc.rs:52 #6 0x00007192bfa634c3 in mmtk_ruby::api::mmtk_get_all_obj_free_candidates () at src/api.rs:295 #7 0x00007192bfa61d50 in rb_gc_impl_shutdown_call_finalizer (objspace_ptr=0x578c17abfc50) at gc/mmtk/mmtk.c:1032 #8 0x0000578c1601e48e in rb_ec_finalize (ec=0x578c17ac06d0) at eval.c:166 #9 rb_ec_cleanup (ec=, ex=) at eval.c:257 #10 0x0000578c1601ebf6 in ruby_cleanup (ex=) at eval.c:180 #11 ruby_stop (ex=) at eval.c:292 #12 0x0000578c16127124 in rb_f_fork (obj=) at process.c:4291 #13 rb_f_fork (obj=) at process.c:4281 https://github.com/ruby/mmtk/commit/eb4b229858 --- gc/mmtk/mmtk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 0d78d61b7fb4b6..428833488c73ed 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1050,6 +1050,7 @@ rb_gc_impl_before_fork(void *objspace_ptr) struct objspace *objspace = objspace_ptr; objspace->fork_hook_vm_lock_lev = RB_GC_VM_LOCK(); + rb_gc_vm_barrier(); mmtk_before_fork(); } From f040b94cf559855ab3755f6333fb2d4a8f81e0d5 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 17 Nov 2025 21:39:02 -0500 Subject: [PATCH 1221/2435] [ruby/mmtk] Ensure not blocking for GC in rb_gc_impl_before_fork In rb_gc_impl_before_fork, it locks the VM and barriers all the Ractors before calling mmtk_before_fork. However, since rb_mmtk_block_for_gc is a barrier point, one or more Ractors could be paused there. However, mmtk_before_fork is not compatible with that because it assumes that the MMTk workers are idle, but the workers are not idle because they are busy working on a GC. This commit essentially implements a trylock. It will optimistically lock but will release the lock if it detects that any other Ractors are waiting in rb_mmtk_block_for_gc. For example, the following script demonstrates the issue: puts "Hello #{Process.pid}" 100.times do |i| puts "i = #{i}" Ractor.new(i) do |j| puts "Ractor #{j} hello" 1000.times do |i| s = "#{j}-#{i}" end Ractor.receive puts "Ractor #{j} goodbye" end pid = fork { } puts "Child pid is #{pid}" _, status = Process.waitpid2 pid puts status.success? end puts "Goodbye" We can see the MMTk worker thread is waiting to start the GC: #4 0x00007ffff66538b1 in rb_mmtk_stop_the_world () at gc/mmtk/mmtk.c:101 #5 0x00007ffff6d04caf in mmtk_ruby::collection::{impl#0}::stop_all_mutators>> (_tls=..., mutator_visitor=...) at src/collection.rs:23 However, the mutator thread is stuck in mmtk_before_fork trying to stop that worker thread: #4 0x00007ffff6c0b621 in std::sys::thread::unix::Thread::join () at library/std/src/sys/thread/unix.rs:134 #5 0x00007ffff6658b6e in std::thread::JoinInner<()>::join<()> (self=...) #6 0x00007ffff6658d4c in std::thread::JoinHandle<()>::join<()> (self=...) #7 0x00007ffff665795e in mmtk_ruby::binding::RubyBinding::join_all_gc_threads (self=0x7ffff72462d0 ) at src/binding.rs:115 #8 0x00007ffff66561a8 in mmtk_ruby::api::mmtk_before_fork () at src/api.rs:309 #9 0x00007ffff66556ff in rb_gc_impl_before_fork (objspace_ptr=0x555555d17980) at gc/mmtk/mmtk.c:1054 #10 0x00005555556bbc3e in rb_gc_before_fork () at gc.c:5429 https://github.com/ruby/mmtk/commit/1a629504a7 --- gc/mmtk/mmtk.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 428833488c73ed..e1678dcf6ab0b4 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -32,6 +32,7 @@ struct objspace { unsigned long live_ractor_cache_count; pthread_mutex_t mutex; + rb_atomic_t mutator_blocking_count; bool world_stopped; pthread_cond_t cond_world_stopped; pthread_cond_t cond_world_started; @@ -131,7 +132,9 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) struct objspace *objspace = rb_gc_get_objspace(); size_t starting_gc_count = objspace->gc_count; + RUBY_ATOMIC_INC(objspace->mutator_blocking_count); int lock_lev = RB_GC_VM_LOCK(); + RUBY_ATOMIC_DEC(objspace->mutator_blocking_count); int err; if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) { rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err)); @@ -1049,9 +1052,26 @@ rb_gc_impl_before_fork(void *objspace_ptr) { struct objspace *objspace = objspace_ptr; + retry: objspace->fork_hook_vm_lock_lev = RB_GC_VM_LOCK(); rb_gc_vm_barrier(); + /* At this point, we know that all the Ractors are paused because of the + * rb_gc_vm_barrier above. Since rb_mmtk_block_for_gc is a barrier point, + * one or more Ractors could be paused there. However, mmtk_before_fork is + * not compatible with that because it assumes that the MMTk workers are idle, + * but the workers are not idle because they are busy working on a GC. + * + * This essentially implements a trylock. It will optimistically lock but will + * release the lock if it detects that any other Ractors are waiting in + * rb_mmtk_block_for_gc. + */ + rb_atomic_t mutator_blocking_count = RUBY_ATOMIC_LOAD(objspace->mutator_blocking_count); + if (mutator_blocking_count != 0) { + RB_GC_VM_UNLOCK(objspace->fork_hook_vm_lock_lev); + goto retry; + } + mmtk_before_fork(); } From 685903e56efd9e4db178d8209c4eb079127b625a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 11:00:40 +0900 Subject: [PATCH 1222/2435] [ruby/etc] Bump up the required ruby version to 2.7 https://github.com/ruby/etc/commit/d047bb6856 --- ext/etc/etc.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/etc/etc.gemspec b/ext/etc/etc.gemspec index 3facc7486669a9..0e9803dc624cb1 100644 --- a/ext/etc/etc.gemspec +++ b/ext/etc/etc.gemspec @@ -40,5 +40,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.extensions = %w{ext/etc/extconf.rb} - spec.required_ruby_version = ">= 2.6.0" + spec.required_ruby_version = ">= 2.7.0" end From a6cecda1dcf57d4db09c92706f69b018915f6530 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 11:02:22 +0900 Subject: [PATCH 1223/2435] [ruby/etc] Win32: Drop support for older MSVC Ruby 2.7 supports MSVC 12.0/_MSC_VER 1800 or later. https://github.com/ruby/etc/commit/6f4404ec88 --- ext/etc/etc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ext/etc/etc.c b/ext/etc/etc.c index 66ef8fc9a4f3d0..8d50a96a621035 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -832,11 +832,7 @@ etc_uname(VALUE obj) rb_w32_conv_from_wchar(v.szCSDVersion, rb_utf8_encoding())); rb_hash_aset(result, SYMBOL_LIT("version"), version); -# if defined _MSC_VER && _MSC_VER < 1300 -# define GET_COMPUTER_NAME(ptr, plen) GetComputerNameW(ptr, plen) -# else # define GET_COMPUTER_NAME(ptr, plen) GetComputerNameExW(ComputerNameDnsFullyQualified, ptr, plen) -# endif GET_COMPUTER_NAME(NULL, &len); buf = ALLOCV_N(WCHAR, vbuf, len); if (GET_COMPUTER_NAME(buf, &len)) { From 3ee08c8df8581ca5b1a95dae0f5825abf54700f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 02:08:11 +0000 Subject: [PATCH 1224/2435] Bump actions/checkout in /.github/actions/setup/directories Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...93cb6efe18208431cddfb8368fd83d5badbf9bfd) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index c0fc75ad7d9b2f..f93733a919066c 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -88,7 +88,7 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} From 319001192d59bc57923ba3838eb83685cb3af014 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 18 Nov 2025 20:56:14 -0600 Subject: [PATCH 1225/2435] [DOC] Tweaks for String#dump and String#undump --- doc/string.rb | 3 +- doc/string/dump.rdoc | 137 ++++++++++++++++++++++++++++--------------- string.c | 12 +--- 3 files changed, 95 insertions(+), 57 deletions(-) diff --git a/doc/string.rb b/doc/string.rb index 4304b96aee7f9a..b37cb5d324ec95 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -322,8 +322,7 @@ # _Substitution_ # # - #dump: Returns a printable version of +self+, enclosed in double-quotes. -# - #undump: Returns a copy of +self+ with all \xNN notations replaced by \uNNNN notations -# and all escaped characters unescaped. +# - #undump: Inverse of #dump; returns a copy of +self+ with changes of the kinds made by #dump "undone." # - #sub: Returns a copy of +self+ with the first substring matching a given pattern # replaced with a given replacement string. # - #gsub: Returns a copy of +self+ with each substring that matches a given pattern diff --git a/doc/string/dump.rdoc b/doc/string/dump.rdoc index a5ab0bb42f2464..2ab9521540dcda 100644 --- a/doc/string/dump.rdoc +++ b/doc/string/dump.rdoc @@ -1,52 +1,97 @@ -Returns a printable version of +self+, enclosed in double-quotes: +For an ordinary string, this method, +String#dump+, +returns a printable ASCII-only version of +self+, enclosed in double-quotes. - 'hello'.dump # => "\"hello\"" +For a dumped string, method String#undump is the inverse of +String#dump+; +it returns a "restored" version of +self+, +where all the dumping changes have been undone. -Certain special characters are rendered with escapes: +In the simplest case, the dumped string contains the original string, +enclosed in double-quotes; +this example is done in +irb+ (interactive Ruby), which uses method `inspect` to render the results: - '"'.dump # => "\"\\\"\"" - '\\'.dump # => "\"\\\\\"" + s = 'hello' # => "hello" + s.dump # => "\"hello\"" + s.dump.undump # => "hello" -Non-printing characters are rendered with escapes: +Keep in mind that in the second line above: + +- The outer double-quotes are put on by +inspect+, + and _are_ _not_ part of the output of #dump. +- The inner double-quotes _are_ part of the output of +dump+, + and are escaped by +inspect+ because they are within the outer double-quotes. + +To avoid confusion, we'll use this helper method to omit the outer double-quotes: + + def dump(s) + print "String: ", s, "\n" + print "Dumped: ", s.dump, "\n" + print "Undumped: ", s.dump.undump, "\n" + end + +So that for string 'hello', we'll see: + + String: hello + Dumped: "hello" + Undumped: hello + +In a dump, certain special characters are escaped: + + String: " + Dumped: "\"" + Undumped: " + + String: \ + Dumped: "\\" + Undumped: \ + +In a dump, unprintable characters are replaced by printable ones; +the unprintable characters are the whitespace characters (other than space itself); +here we see the ordinals for those characers, together with explanatory text: + + h = { + 7 => 'Alert (BEL)', + 8 => 'Backspace (BS)', + 9 => 'Horizontal tab (HT)', + 10 => 'Linefeed (LF)', + 11 => 'Vertical tab (VT)', + 12 => 'Formfeed (FF)', + 13 => 'Carriage return (CR)' + } + +In this example, the dumped output is printed by method #inspect, +and so contains both outer double-quotes and escaped inner double-quotes: s = '' - s << 7 # Alarm (bell). - s << 8 # Back space. - s << 9 # Horizontal tab. - s << 10 # Line feed. - s << 11 # Vertical tab. - s << 12 # Form feed. - s << 13 # Carriage return. - s # => "\a\b\t\n\v\f\r" - s.dump # => "\"\\a\\b\\t\\n\\v\\f\\r\"" - -If +self+ is encoded in UTF-8 and contains Unicode characters, renders Unicode -characters in Unicode escape sequence: - - 'тест'.dump # => "\"\\u0442\\u0435\\u0441\\u0442\"" - 'こんにちは'.dump # => "\"\\u3053\\u3093\\u306B\\u3061\\u306F\"" - -If the encoding of +self+ is not ASCII-compatible (i.e., +self.encoding.ascii_compatible?+ -returns +false+), renders all ASCII-compatible bytes as ASCII characters and all -other bytes as hexadecimal. Appends .dup.force_encoding(\"encoding\"), where - is +self.encoding.name+: - - s = 'hello' - s.encoding # => # - s.dump # => "\"hello\"" - s.encode('utf-16').dump # => "\"\\xFE\\xFF\\x00h\\x00e\\x00l\\x00l\\x00o\".dup.force_encoding(\"UTF-16\")" - s.encode('utf-16le').dump # => "\"h\\x00e\\x00l\\x00l\\x00o\\x00\".dup.force_encoding(\"UTF-16LE\")" - - s = 'тест' - s.encoding # => # - s.dump # => "\"\\u0442\\u0435\\u0441\\u0442\"" - s.encode('utf-16').dump # => "\"\\xFE\\xFF\\x04B\\x045\\x04A\\x04B\".dup.force_encoding(\"UTF-16\")" - s.encode('utf-16le').dump # => "\"B\\x045\\x04A\\x04B\\x04\".dup.force_encoding(\"UTF-16LE\")" - - s = 'こんにちは' - s.encoding # => # - s.dump # => "\"\\u3053\\u3093\\u306B\\u3061\\u306F\"" - s.encode('utf-16').dump # => "\"\\xFE\\xFF0S0\\x930k0a0o\".dup.force_encoding(\"UTF-16\")" - s.encode('utf-16le').dump # => "\"S0\\x930k0a0o0\".dup.force_encoding(\"UTF-16LE\")" - -Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. + h.keys.each {|i| s << i } # => [7, 8, 9, 10, 11, 12, 13] + s # => "\a\b\t\n\v\f\r" + s.dump # => "\"\\a\\b\\t\\n\\v\\f\\r\"" + +If +self+ is encoded in UTF-8 and contains Unicode characters, +each Unicode character is dumped as a Unicode escape sequence: + + String: тест + Dumped: "\u0442\u0435\u0441\u0442" + Undumped: тест + + String: こんにちは + Dumped: "\u3053\u3093\u306B\u3061\u306F" + Undumped: こんにちは + +If the encoding of +self+ is not ASCII-compatible +(i.e., if self.encoding.ascii_compatible? returns +false+), +each ASCII-compatible byte is dumped as an ASCII character, +and all other bytes are dumped as hexadecimal; +also appends .dup.force_encoding(\"encoding\"), +where is self.encoding.name: + + String: hello + Dumped: "\xFE\xFF\x00h\x00e\x00l\x00l\x00o".dup.force_encoding("UTF-16") + Undumped: hello + + String: тест + Dumped: "\xFE\xFF\x04B\x045\x04A\x04B".dup.force_encoding("UTF-16") + Undumped: тест + + String: こんにちは + Dumped: "\xFE\xFF0S0\x930k0a0o".dup.force_encoding("UTF-16") + Undumped: こんにちは diff --git a/string.c b/string.c index 827555d9e0faab..f371e185d618b0 100644 --- a/string.c +++ b/string.c @@ -7628,17 +7628,11 @@ static VALUE rb_str_is_ascii_only_p(VALUE str); /* * call-seq: - * undump -> string + * undump -> new_string * - * Returns an unescaped version of +self+: - * - * s_orig = "\f\x00\xff\\\"" # => "\f\u0000\xFF\\\"" - * s_dumped = s_orig.dump # => "\"\\f\\x00\\xFF\\\\\\\"\"" - * s_undumped = s_dumped.undump # => "\f\u0000\xFF\\\"" - * s_undumped == s_orig # => true - * - * Related: String#dump (inverse of String#undump). + * Inverse of String#dump; returns a copy of +self+ with changes of the kinds made by String#dump "undone." * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From 1443f89d6942e19516a0fb10d25876021202ec5e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 16 Nov 2025 19:46:03 +0000 Subject: [PATCH 1226/2435] [DOC] Tweaks for String#unicode_normalize --- doc/string/unicode_normalize.rdoc | 28 ++++++++++++++++++++++++++++ string.c | 28 +--------------------------- 2 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 doc/string/unicode_normalize.rdoc diff --git a/doc/string/unicode_normalize.rdoc b/doc/string/unicode_normalize.rdoc new file mode 100644 index 00000000000000..5f733c0fb84f58 --- /dev/null +++ b/doc/string/unicode_normalize.rdoc @@ -0,0 +1,28 @@ +Returns a copy of +self+ with +{Unicode normalization}[https://unicode.org/reports/tr15] applied. + +Argument +form+ must be one of the following symbols +(see {Unicode normalization forms}[https://unicode.org/reports/tr15/#Norm_Forms]): + +- +:nfc+: Canonical decomposition, followed by canonical composition. +- +:nfd+: Canonical decomposition. +- +:nfkc+: Compatibility decomposition, followed by canonical composition. +- +:nfkd+: Compatibility decomposition. + +The encoding of +self+ must be one of: + +- Encoding::UTF_8. +- Encoding::UTF_16BE. +- Encoding::UTF_16LE. +- Encoding::UTF_32BE. +- Encoding::UTF_32LE. +- Encoding::GB18030. +- Encoding::UCS_2BE. +- Encoding::UCS_4BE. + +Examples: + + "a\u0300".unicode_normalize # => "à" # Lowercase 'a' with grave accens. + "a\u0300".unicode_normalize(:nfd) # => "à" # Same. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index f371e185d618b0..d78d7320be2b2b 100644 --- a/string.c +++ b/string.c @@ -11926,34 +11926,8 @@ unicode_normalize_common(int argc, VALUE *argv, VALUE str, ID id) * call-seq: * unicode_normalize(form = :nfc) -> string * - * Returns a copy of +self+ with - * {Unicode normalization}[https://unicode.org/reports/tr15] applied. + * :include: doc/string/unicode_normalize.rdoc * - * Argument +form+ must be one of the following symbols - * (see {Unicode normalization forms}[https://unicode.org/reports/tr15/#Norm_Forms]): - * - * - +:nfc+: Canonical decomposition, followed by canonical composition. - * - +:nfd+: Canonical decomposition. - * - +:nfkc+: Compatibility decomposition, followed by canonical composition. - * - +:nfkd+: Compatibility decomposition. - * - * The encoding of +self+ must be one of: - * - * - Encoding::UTF_8 - * - Encoding::UTF_16BE - * - Encoding::UTF_16LE - * - Encoding::UTF_32BE - * - Encoding::UTF_32LE - * - Encoding::GB18030 - * - Encoding::UCS_2BE - * - Encoding::UCS_4BE - * - * Examples: - * - * "a\u0300".unicode_normalize # => "a" - * "\u00E0".unicode_normalize(:nfd) # => "a " - * - * Related: String#unicode_normalize!, String#unicode_normalized?. */ static VALUE rb_str_unicode_normalize(int argc, VALUE *argv, VALUE str) From e31dc5f193e7bc87b0bf9a672f2ca184810cf098 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 16:05:12 +0900 Subject: [PATCH 1227/2435] Fix a typo --- internal/bits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/bits.h b/internal/bits.h index bbd59ffaca5cbc..698ab3e2194159 100644 --- a/internal/bits.h +++ b/internal/bits.h @@ -556,7 +556,7 @@ ntz_int64(uint64_t x) { #if defined(__x86_64__) && defined(__BMI__) return (unsigned)_tzcnt_u64(x); -` + #elif defined(_WIN64) && defined(_MSC_VER) unsigned long r; return _BitScanForward64(&r, x) ? (int)r : 64; From 8986115e0a2a989f2b2ea5945f02c7a13989d640 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 15:12:28 +0900 Subject: [PATCH 1228/2435] [Bug #21697] Keep revision.h outside VCS --- common.mk | 3 ++- tool/lib/vcs.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index 648dd00b0252b6..82afc80c275869 100644 --- a/common.mk +++ b/common.mk @@ -1306,7 +1306,8 @@ $(BUILTIN_RB_INCS): $(top_srcdir)/tool/mk_builtin_loader.rb $(srcdir)/revision.h$(no_baseruby:no=~disabled~): $(REVISION_H) $(REVISION_H)$(no_baseruby:no=~disabled~): - $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" --output=revision.h --timestamp=$@ + $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" | \ + $(IFCHANGE) --timestamp=$@ --empty revision.h - $(REVISION_H)$(yes_baseruby:yes=~disabled~): $(Q) exit > $@ diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 602f6dd519c5b3..e006d2f5a0529c 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -647,7 +647,7 @@ def get_revisions(path, srcdir = nil) end def revision_header(last, release_date, release_datetime = nil, branch = nil, title = nil, limit: 20) - self.release_date(release_date) + [] end end end From 339c1731b260e2fcc52e0dfeab0e16f7591af36d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 16:41:04 +0900 Subject: [PATCH 1229/2435] Revert "[Bug #21697] Keep revision.h outside VCS" This reverts commit 8986115e0a2a989f2b2ea5945f02c7a13989d640. `RELEASE_DATE` including `YEAR`, `MONTH`, `DAY` are mandatory, while `REVISION` is not. --- common.mk | 3 +-- tool/lib/vcs.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common.mk b/common.mk index 82afc80c275869..648dd00b0252b6 100644 --- a/common.mk +++ b/common.mk @@ -1306,8 +1306,7 @@ $(BUILTIN_RB_INCS): $(top_srcdir)/tool/mk_builtin_loader.rb $(srcdir)/revision.h$(no_baseruby:no=~disabled~): $(REVISION_H) $(REVISION_H)$(no_baseruby:no=~disabled~): - $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" | \ - $(IFCHANGE) --timestamp=$@ --empty revision.h - + $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" --output=revision.h --timestamp=$@ $(REVISION_H)$(yes_baseruby:yes=~disabled~): $(Q) exit > $@ diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index e006d2f5a0529c..602f6dd519c5b3 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -647,7 +647,7 @@ def get_revisions(path, srcdir = nil) end def revision_header(last, release_date, release_datetime = nil, branch = nil, title = nil, limit: 20) - [] + self.release_date(release_date) end end end From 169d6c7cadce7a27e62d9943559b9f712ea231f8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 19 Nov 2025 15:29:56 +0900 Subject: [PATCH 1230/2435] [ruby/rubygems] Use method_defined?(:method, false) https://github.com/ruby/rubygems/commit/6cc7d71dac --- lib/bundler/rubygems_gem_installer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 25ddbeaceb6da3..0da5ed236b2bdf 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -69,7 +69,7 @@ def ensure_writable_dir(dir) end def generate_plugins - return unless Gem::Installer.method_defined?(:generate_plugins) + return unless Gem::Installer.method_defined?(:generate_plugins, false) latest = Gem::Specification.stubs_for(spec.name).first return if latest && latest.version > spec.version From bbb4c7b88bf4fca08487d6b115b1031f56ac9cf6 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 19:38:18 -0600 Subject: [PATCH 1231/2435] Update to ruby/mspec@bd8efcf --- spec/mspec/lib/mspec/utils/name_map.rb | 13 ++++++++++--- spec/mspec/spec/utils/fixtures/this_file_raises.rb | 1 + spec/mspec/spec/utils/fixtures/this_file_raises2.rb | 1 + spec/mspec/spec/utils/name_map_spec.rb | 12 ++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 spec/mspec/spec/utils/fixtures/this_file_raises.rb create mode 100644 spec/mspec/spec/utils/fixtures/this_file_raises2.rb diff --git a/spec/mspec/lib/mspec/utils/name_map.rb b/spec/mspec/lib/mspec/utils/name_map.rb index bf70e651a23b97..9b04112e2e356d 100644 --- a/spec/mspec/lib/mspec/utils/name_map.rb +++ b/spec/mspec/lib/mspec/utils/name_map.rb @@ -66,10 +66,17 @@ def exception?(name) end def class_or_module(c) - const = Object.const_get(c, false) + begin + const = Object.const_get(c, false) + rescue NameError, RuntimeError + # Either the constant doesn't exist or it is + # explicitly raising an error, like `SortedSet`. + return nil + end + return nil unless Module === const + filtered = @filter && EXCLUDED.include?(const.name) - return const if Module === const and !filtered - rescue NameError + return const unless filtered end def namespace(mod, const) diff --git a/spec/mspec/spec/utils/fixtures/this_file_raises.rb b/spec/mspec/spec/utils/fixtures/this_file_raises.rb new file mode 100644 index 00000000000000..8e37a587bf1bbf --- /dev/null +++ b/spec/mspec/spec/utils/fixtures/this_file_raises.rb @@ -0,0 +1 @@ +raise "This is a BAD file" diff --git a/spec/mspec/spec/utils/fixtures/this_file_raises2.rb b/spec/mspec/spec/utils/fixtures/this_file_raises2.rb new file mode 100644 index 00000000000000..8efc10199a3994 --- /dev/null +++ b/spec/mspec/spec/utils/fixtures/this_file_raises2.rb @@ -0,0 +1 @@ +raise "This is a BAD file 2" diff --git a/spec/mspec/spec/utils/name_map_spec.rb b/spec/mspec/spec/utils/name_map_spec.rb index a18a481500d692..a42dc9ffecb17e 100644 --- a/spec/mspec/spec/utils/name_map_spec.rb +++ b/spec/mspec/spec/utils/name_map_spec.rb @@ -21,6 +21,9 @@ class Fixnum def f; end end + autoload :BadFile, "#{__dir__}/fixtures/this_file_raises.rb" + autoload :BadFile2, "#{__dir__}/fixtures/this_file_raises2.rb" + def self.n; end def n; end end @@ -84,6 +87,15 @@ def n; end expect(@map.class_or_module("Hell")).to eq(nil) expect(@map.class_or_module("Bush::Brain")).to eq(nil) end + + it "returns nil if accessing the constant raises RuntimeError" do + expect { NameMapSpecs::BadFile }.to raise_error(RuntimeError) + expect(@map.class_or_module("NameMapSpecs::BadFile")).to eq(nil) + end + + it "returns nil if accessing the constant raises RuntimeError when not triggering the autoload before" do + expect(@map.class_or_module("NameMapSpecs::BadFile2")).to eq(nil) + end end RSpec.describe NameMap, "#dir_name" do From 85cd08e4f9f422357207ace0a53b9ec8020df976 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 19:38:20 -0600 Subject: [PATCH 1232/2435] Update to ruby/spec@2e11d2a --- spec/ruby/core/comparable/clamp_spec.rb | 70 ++++++- spec/ruby/core/enumerable/shared/inject.rb | 2 +- spec/ruby/core/hash/compact_spec.rb | 2 +- spec/ruby/core/hash/constructor_spec.rb | 11 +- spec/ruby/core/hash/except_spec.rb | 28 ++- spec/ruby/core/hash/invert_spec.rb | 21 +++ spec/ruby/core/hash/merge_spec.rb | 23 +++ spec/ruby/core/hash/reject_spec.rb | 21 +++ spec/ruby/core/hash/replace_spec.rb | 53 +++--- spec/ruby/core/hash/shared/select.rb | 21 +++ spec/ruby/core/hash/slice_spec.rb | 21 +++ spec/ruby/core/hash/to_h_spec.rb | 28 ++- spec/ruby/core/hash/transform_keys_spec.rb | 21 +++ spec/ruby/core/hash/transform_values_spec.rb | 21 +++ spec/ruby/core/io/buffer/empty_spec.rb | 29 +++ spec/ruby/core/io/buffer/external_spec.rb | 108 +++++++++++ spec/ruby/core/io/buffer/free_spec.rb | 104 +++++++++++ spec/ruby/core/io/buffer/initialize_spec.rb | 103 ++++++++++ spec/ruby/core/io/buffer/internal_spec.rb | 108 +++++++++++ spec/ruby/core/io/buffer/locked_spec.rb | 75 ++++++++ spec/ruby/core/io/buffer/mapped_spec.rb | 108 +++++++++++ spec/ruby/core/io/buffer/null_spec.rb | 29 +++ spec/ruby/core/io/buffer/private_spec.rb | 111 +++++++++++ spec/ruby/core/io/buffer/readonly_spec.rb | 143 ++++++++++++++ spec/ruby/core/io/buffer/resize_spec.rb | 155 +++++++++++++++ .../core/io/buffer/shared/null_and_empty.rb | 59 ++++++ spec/ruby/core/io/buffer/shared_spec.rb | 117 ++++++++++++ spec/ruby/core/io/buffer/transfer_spec.rb | 118 ++++++++++++ spec/ruby/core/io/buffer/valid_spec.rb | 119 ++++++++++++ spec/ruby/core/kernel/Float_spec.rb | 4 + spec/ruby/core/kernel/autoload_spec.rb | 5 +- spec/ruby/core/matchdata/bytebegin_spec.rb | 132 +++++++++++++ spec/ruby/core/matchdata/byteend_spec.rb | 104 +++++++++++ spec/ruby/core/matchdata/offset_spec.rb | 106 +++++++++-- spec/ruby/core/module/const_added_spec.rb | 2 + .../core/module/const_source_location_spec.rb | 10 + spec/ruby/core/module/name_spec.rb | 1 + .../core/module/set_temporary_name_spec.rb | 1 + spec/ruby/core/random/new_spec.rb | 2 +- spec/ruby/language/regexp/encoding_spec.rb | 6 +- spec/ruby/language/rescue_spec.rb | 10 +- .../net-http/HTTPServerException_spec.rb | 4 +- spec/ruby/library/tempfile/callback_spec.rb | 6 - spec/ruby/library/tempfile/create_spec.rb | 176 ++++++++++++++++++ spec/ruby/optional/capi/encoding_spec.rb | 8 +- spec/ruby/optional/capi/ext/struct_spec.c | 5 + spec/ruby/optional/capi/globals_spec.rb | 13 +- spec/ruby/optional/capi/module_spec.rb | 2 +- spec/ruby/optional/capi/struct_spec.rb | 74 +++++++- 49 files changed, 2410 insertions(+), 90 deletions(-) create mode 100644 spec/ruby/core/io/buffer/empty_spec.rb create mode 100644 spec/ruby/core/io/buffer/external_spec.rb create mode 100644 spec/ruby/core/io/buffer/free_spec.rb create mode 100644 spec/ruby/core/io/buffer/initialize_spec.rb create mode 100644 spec/ruby/core/io/buffer/internal_spec.rb create mode 100644 spec/ruby/core/io/buffer/locked_spec.rb create mode 100644 spec/ruby/core/io/buffer/mapped_spec.rb create mode 100644 spec/ruby/core/io/buffer/null_spec.rb create mode 100644 spec/ruby/core/io/buffer/private_spec.rb create mode 100644 spec/ruby/core/io/buffer/readonly_spec.rb create mode 100644 spec/ruby/core/io/buffer/resize_spec.rb create mode 100644 spec/ruby/core/io/buffer/shared/null_and_empty.rb create mode 100644 spec/ruby/core/io/buffer/shared_spec.rb create mode 100644 spec/ruby/core/io/buffer/transfer_spec.rb create mode 100644 spec/ruby/core/io/buffer/valid_spec.rb create mode 100644 spec/ruby/core/matchdata/bytebegin_spec.rb create mode 100644 spec/ruby/core/matchdata/byteend_spec.rb delete mode 100644 spec/ruby/library/tempfile/callback_spec.rb create mode 100644 spec/ruby/library/tempfile/create_spec.rb diff --git a/spec/ruby/core/comparable/clamp_spec.rb b/spec/ruby/core/comparable/clamp_spec.rb index 796d4a18c1dee7..cc1df977e2ae09 100644 --- a/spec/ruby/core/comparable/clamp_spec.rb +++ b/spec/ruby/core/comparable/clamp_spec.rb @@ -24,7 +24,7 @@ c.clamp(two, three).should equal(c) end - it 'returns the min parameter if smaller than it' do + it 'returns the min parameter if less than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) c = ComparableSpecs::Weird.new(0) @@ -52,7 +52,7 @@ c.clamp(two..three).should equal(c) end - it 'returns the minimum value of the range parameters if smaller than it' do + it 'returns the minimum value of the range parameters if less than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) c = ComparableSpecs::Weird.new(0) @@ -75,4 +75,70 @@ -> { c.clamp(one...two) }.should raise_error(ArgumentError) end + + context 'with endless range' do + it 'returns minimum value of the range parameters if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(one..).should equal(one) + c.clamp(zero..).should equal(c) + end + + it 'always returns self if greater than minimum value of the range parameters' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + two = ComparableSpecs::WithOnlyCompareDefined.new(2) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one..).should equal(c) + c.clamp(two..).should equal(c) + end + + it 'works with exclusive range' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one...).should equal(c) + end + end + + context 'with beginless range' do + it 'returns maximum value of the range parameters if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(..one).should equal(one) + end + + it 'always returns self if less than maximum value of the range parameters' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(..one).should equal(c) + c.clamp(..zero).should equal(c) + end + + it 'raises an Argument error if the range parameter is exclusive' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + + -> { c.clamp(...one) }.should raise_error(ArgumentError) + end + end + + context 'with beginless-and-endless range' do + it 'always returns self' do + c = ComparableSpecs::Weird.new(1) + + c.clamp(nil..nil).should equal(c) + end + + it 'works with exclusive range' do + c = ComparableSpecs::Weird.new(2) + + c.clamp(nil...nil).should equal(c) + end + end end diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb index aae9e06c974fa0..8fb7e98c2b294c 100644 --- a/spec/ruby/core/enumerable/shared/inject.rb +++ b/spec/ruby/core/enumerable/shared/inject.rb @@ -103,7 +103,7 @@ def name.to_str; "-"; end it "without inject arguments(legacy rubycon)" do # no inject argument - EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 } .should == 2 + EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 }.should == 2 EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| acc }.should == 2 EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2 diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index 76aa43949d9e61..13371bce434fc9 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -35,7 +35,7 @@ hash.compact.default_proc.should == pr end - it "retains compare_by_identity_flag" do + it "retains compare_by_identity flag" do hash = {}.compare_by_identity hash.compact.compare_by_identity?.should == true hash[:a] = 1 diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index 8d2977390916b2..0f97f7b40e9c2c 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -103,14 +103,14 @@ def obj.to_hash() { 1 => 2, 3 => 4 } end HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash) end - it "removes the default value" do + it "does not retain the default value" do hash = Hash.new(1) Hash[hash].default.should be_nil hash[:a] = 1 Hash[hash].default.should be_nil end - it "removes the default_proc" do + it "does not retain the default_proc" do hash = Hash.new { |h, k| h[k] = [] } Hash[hash].default_proc.should be_nil hash[:a] = 1 @@ -118,10 +118,11 @@ def obj.to_hash() { 1 => 2, 3 => 4 } end end ruby_version_is '3.3' do - it "does not retain compare_by_identity_flag" do - hash = {}.compare_by_identity + it "does not retain compare_by_identity flag" do + hash = { a: 1 }.compare_by_identity Hash[hash].compare_by_identity?.should == false - hash[:a] = 1 + + hash = {}.compare_by_identity Hash[hash].compare_by_identity?.should == false end end diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb index ac84f9975cf0db..026e454b13cabf 100644 --- a/spec/ruby/core/hash/except_spec.rb +++ b/spec/ruby/core/hash/except_spec.rb @@ -19,14 +19,24 @@ @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 } end - it "always returns a Hash without a default" do - klass = Class.new(Hash) - h = klass.new(:default) - h[:bar] = 12 - h[:foo] = 42 - r = h.except(:foo) - r.should == {bar: 12} - r.class.should == Hash - r.default.should == nil + it "does not retain the default value" do + h = Hash.new(1) + h.except(:a).default.should be_nil + h[:a] = 1 + h.except(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.except(:a).default_proc.should be_nil + h[:a] = 1 + h.except(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.except(:a) + h2.compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb index 73377a9e974a58..c06e15ff7cfadf 100644 --- a/spec/ruby/core/hash/invert_spec.rb +++ b/spec/ruby/core/hash/invert_spec.rb @@ -24,4 +24,25 @@ HashSpecs::MyHash[1 => 2, 3 => 4].invert.class.should == Hash HashSpecs::MyHash[].invert.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.invert.default.should be_nil + h[:a] = 1 + h.invert.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.invert.default_proc.should be_nil + h[:a] = 1 + h.invert.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.invert + h2.compare_by_identity?.should == false + end end diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb index 55218642979af8..6710d121efb5d7 100644 --- a/spec/ruby/core/hash/merge_spec.rb +++ b/spec/ruby/core/hash/merge_spec.rb @@ -93,6 +93,29 @@ merged.should eql(hash) merged.should_not equal(hash) end + + it "retains the default value" do + h = Hash.new(1) + h.merge(b: 1, d: 2).default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.merge(b: 1, d: 2).default_proc.should == pr + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.merge(b: 1, d: 2) + h2.compare_by_identity?.should == true + end + + it "ignores compare_by_identity flag of an argument" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = { b: 1, d: 2 }.merge(h) + h2.compare_by_identity?.should == false + end end describe "Hash#merge!" do diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb index dd8e81723744e2..8381fc7fc1b743 100644 --- a/spec/ruby/core/hash/reject_spec.rb +++ b/spec/ruby/core/hash/reject_spec.rb @@ -44,6 +44,27 @@ def h.to_a() end reject_pairs.should == reject_bang_pairs end + it "does not retain the default value" do + h = Hash.new(1) + h.reject { false }.default.should be_nil + h[:a] = 1 + h.reject { false }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.reject { false }.default_proc.should be_nil + h[:a] = 1 + h.reject { false }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.reject { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_behaves_like :hash_iteration_no_block, :reject it_behaves_like :enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/replace_spec.rb b/spec/ruby/core/hash/replace_spec.rb index a26a31f5f9a9b6..db30145e1a7c4a 100644 --- a/spec/ruby/core/hash/replace_spec.rb +++ b/spec/ruby/core/hash/replace_spec.rb @@ -23,39 +23,48 @@ h.should == { 1 => 2 } end - it "transfers the compare_by_identity flag" do - hash_a = { a: 1 } - hash_b = { b: 2 } - hash_b.compare_by_identity - hash_a.should_not.compare_by_identity? - hash_a.replace(hash_b) - hash_a.should.compare_by_identity? + it "does not retain the default value" do + hash = Hash.new(1) + hash.replace(b: 2).default.should be_nil + end - hash_a = { a: 1 } - hash_b = { b: 2 } - hash_a.compare_by_identity - hash_a.should.compare_by_identity? - hash_a.replace(hash_b) - hash_a.should_not.compare_by_identity? + it "transfers the default value of an argument" do + hash = Hash.new(1) + { a: 1 }.replace(hash).default.should == 1 end - it "does not transfer default values" do - hash_a = {} - hash_b = Hash.new(5) - hash_a.replace(hash_b) - hash_a.default.should == 5 + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.replace(b: 2).default_proc.should be_nil + end - hash_a = {} - hash_b = Hash.new { |h, k| k * 2 } - hash_a.replace(hash_b) - hash_a.default(5).should == 10 + it "transfers the default_proc of an argument" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + { a: 1 }.replace(hash).default_proc.should == pr + end + it "does not call the default_proc of an argument" do hash_a = Hash.new { |h, k| k * 5 } hash_b = Hash.new(-> { raise "Should not invoke lambda" }) hash_a.replace(hash_b) hash_a.default.should == hash_b.default end + it "transfers compare_by_identity flag of an argument" do + h = { a: 1, c: 3 } + h2 = { b: 2, d: 4 }.compare_by_identity + h.replace(h2) + h.compare_by_identity?.should == true + end + + it "does not retain compare_by_identity flag" do + h = { a: 1, c: 3 }.compare_by_identity + h.replace(b: 2, d: 4) + h.compare_by_identity?.should == false + end + it "raises a FrozenError if called on a frozen instance that would not be modified" do -> do HashSpecs.frozen_hash.replace(HashSpecs.frozen_hash) diff --git a/spec/ruby/core/hash/shared/select.rb b/spec/ruby/core/hash/shared/select.rb index 5170af50d69738..fbeff073304225 100644 --- a/spec/ruby/core/hash/shared/select.rb +++ b/spec/ruby/core/hash/shared/select.rb @@ -40,6 +40,27 @@ @empty.send(@method).should be_an_instance_of(Enumerator) end + it "does not retain the default value" do + h = Hash.new(1) + h.send(@method) { true }.default.should be_nil + h[:a] = 1 + h.send(@method) { true }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.send(@method) { true }.default_proc.should be_nil + h[:a] = 1 + h.send(@method) { true }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.send(@method) { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_should_behave_like :hash_iteration_no_block before :each do diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb index e3046d83d7c252..4fcc01f9a6f6b9 100644 --- a/spec/ruby/core/hash/slice_spec.rb +++ b/spec/ruby/core/hash/slice_spec.rb @@ -50,4 +50,25 @@ def [](value) ScratchPad.recorded.should == [] end + + it "does not retain the default value" do + h = Hash.new(1) + h.slice(:a).default.should be_nil + h[:a] = 1 + h.slice(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.slice(:a).default_proc.should be_nil + h[:a] = 1 + h.slice(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.slice(:a) + h2.compare_by_identity?.should == true + end end diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb index e17ca7e67112f6..f84fd7b503d165 100644 --- a/spec/ruby/core/hash/to_h_spec.rb +++ b/spec/ruby/core/hash/to_h_spec.rb @@ -19,17 +19,22 @@ @h[:foo].should == :bar end - it "copies the default" do + it "retains the default" do @h.default = 42 @h.to_h.default.should == 42 @h[:hello].should == 42 end - it "copies the default_proc" do + it "retains the default_proc" do @h.default_proc = prc = Proc.new{ |h, k| h[k] = 2 * k } @h.to_h.default_proc.should == prc @h[42].should == 84 end + + it "retains compare_by_identity flag" do + @h.compare_by_identity + @h.to_h.compare_by_identity?.should == true + end end context "with block" do @@ -78,5 +83,24 @@ { a: 1 }.to_h { |k| x } end.should raise_error(TypeError, /wrong element type MockObject/) end + + it "does not retain the default value" do + h = Hash.new(1) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.compare_by_identity?.should == false + end end end diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index f63d39ecc88f5c..e2eeab1813fade 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -54,6 +54,27 @@ it "allows a combination of hash and block argument" do @hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 } end + + it "does not retain the default value" do + h = Hash.new(1) + h.transform_keys(&:succ).default.should be_nil + h[:a] = 1 + h.transform_keys(&:succ).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_keys(&:succ) + h2.compare_by_identity?.should == false + end end describe "Hash#transform_keys!" do diff --git a/spec/ruby/core/hash/transform_values_spec.rb b/spec/ruby/core/hash/transform_values_spec.rb index acb469416a2d7c..4a0ae8a5a5711c 100644 --- a/spec/ruby/core/hash/transform_values_spec.rb +++ b/spec/ruby/core/hash/transform_values_spec.rb @@ -39,6 +39,27 @@ r[:foo].should == 84 r.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.transform_values(&:succ).default.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_values(&:succ) + h2.compare_by_identity?.should == true + end end describe "Hash#transform_values!" do diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb new file mode 100644 index 00000000000000..e1fd4ab6a23268 --- /dev/null +++ b/spec/ruby/core/io/buffer/empty_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#empty?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :empty? + + it "is true for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.empty?.should be_true + end + + ruby_version_is "3.3" do + it "is true for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.empty?.should be_true + end + end + end + + it "is true for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).empty?.should be_true + end +end diff --git a/spec/ruby/core/io/buffer/external_spec.rb b/spec/ruby/core/io/buffer/external_spec.rb new file mode 100644 index 00000000000000..4377a383578167 --- /dev/null +++ b/spec/ruby/core/io/buffer/external_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#external?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.external?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.external?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.external?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.external?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.external?.should be_true + end + + it "is true for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.external?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is true" do + IO::Buffer.string(4) do |buffer| + buffer.external?.should be_true + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.external?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.external?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.external?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.external?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.external?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.external?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/free_spec.rb b/spec/ruby/core/io/buffer/free_spec.rb new file mode 100644 index 00000000000000..f3a491897849ae --- /dev/null +++ b/spec/ruby/core/io/buffer/free_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#free" do + context "with a buffer created with .new" do + it "frees internal memory and nullifies the buffer" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + end + + it "frees mapped memory and nullifies the buffer" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + buffer.free + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "frees mapped memory and nullifies the buffer" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + buffer.free + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + buffer = IO::Buffer.for(string) + # Read-only buffer, can't modify the string. + buffer.free + buffer.null?.should be_true + end + end + + context "with a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + IO::Buffer.for(string) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = + IO::Buffer.string(4) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + it "can be called repeatedly without an error" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + buffer.free + buffer.null?.should be_true + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + buffer = IO::Buffer.new(4) + buffer.locked do + -> { buffer.free }.should raise_error(IO::Buffer::LockedError, "Buffer is locked!") + end + buffer.free + buffer.null?.should be_true + end + + context "with a slice of a buffer" do + it "nullifies the slice, not touching the buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + slice.free + slice.null?.should be_true + buffer.null?.should be_false + + buffer.free + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + buffer.free + slice.null?.should be_false + slice.valid?.should be_false + end + end +end diff --git a/spec/ruby/core/io/buffer/initialize_spec.rb b/spec/ruby/core/io/buffer/initialize_spec.rb new file mode 100644 index 00000000000000..c86d1e7f1d634a --- /dev/null +++ b/spec/ruby/core/io/buffer/initialize_spec.rb @@ -0,0 +1,103 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#initialize" do + after :each do + @buffer&.free + @buffer = nil + end + + it "creates a new zero-filled buffer with default size" do + @buffer = IO::Buffer.new + @buffer.size.should == IO::Buffer::DEFAULT_SIZE + @buffer.each(:U8).should.all? { |_offset, value| value.eql?(0) } + end + + it "creates a buffer with default state" do + @buffer = IO::Buffer.new + @buffer.should_not.shared? + @buffer.should_not.readonly? + + @buffer.should_not.empty? + @buffer.should_not.null? + + # This is run-time state, set by #locked. + @buffer.should_not.locked? + end + + context "with size argument" do + it "creates a new internal buffer if size is less than IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE - 1 + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "creates a new mapped buffer if size is greater than or equal to IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should_not.empty? + end + + it "creates a null buffer if size is 0" do + @buffer = IO::Buffer.new(0) + @buffer.size.should.zero? + @buffer.should_not.internal? + @buffer.should_not.mapped? + @buffer.should.null? + @buffer.should.empty? + end + + it "raises TypeError if size is not an Integer" do + -> { IO::Buffer.new(nil) }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.new(10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + -> { IO::Buffer.new(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + end + + context "with size and flags arguments" do + it "forces mapped buffer with IO::Buffer::MAPPED flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE - 1, IO::Buffer::MAPPED) + @buffer.should.mapped? + @buffer.should_not.internal? + @buffer.should_not.empty? + end + + it "forces internal buffer with IO::Buffer::INTERNAL flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::INTERNAL) + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "raises IO::Buffer::AllocationError if neither IO::Buffer::MAPPED nor IO::Buffer::INTERNAL is given" do + -> { IO::Buffer.new(10, IO::Buffer::READONLY) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + -> { IO::Buffer.new(10, 0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + + ruby_version_is "3.3" do + it "raises ArgumentError if flags is negative" do + -> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!") + end + end + + ruby_version_is ""..."3.3" do + it "raises IO::Buffer::AllocationError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + end + + ruby_version_is "3.3" do + it "raises TypeError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer") + end + end + end +end diff --git a/spec/ruby/core/io/buffer/internal_spec.rb b/spec/ruby/core/io/buffer/internal_spec.rb new file mode 100644 index 00000000000000..409699cc3c9230 --- /dev/null +++ b/spec/ruby/core/io/buffer/internal_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#internal?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is true for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.internal?.should be_true + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.internal?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.internal?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.internal?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.internal?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.internal?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.internal?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.internal?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.internal?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/locked_spec.rb b/spec/ruby/core/io/buffer/locked_spec.rb new file mode 100644 index 00000000000000..4ffa569fd20c5a --- /dev/null +++ b/spec/ruby/core/io/buffer/locked_spec.rb @@ -0,0 +1,75 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#locked" do + after :each do + @buffer&.free + @buffer = nil + end + + context "when buffer is locked" do + it "allows reading and writing operations on the buffer" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + @buffer.locked do + @buffer.get_string.should == "test" + @buffer.set_string("meat") + end + @buffer.get_string.should == "meat" + end + + it "disallows operations changing buffer itself, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + # Just an example, each method is responsible for checking the lock state. + -> { @buffer.resize(8) }.should raise_error(IO::Buffer::LockedError) + end + end + end + + it "disallows reentrant locking, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.locked {} }.should raise_error(IO::Buffer::LockedError, "Buffer already locked!") + end + end + + it "does not propagate to buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.locked do + @buffer.locked?.should be_true + slice.locked?.should be_false + slice.locked { slice.locked?.should be_true } + end + end + + it "does not propagate backwards from buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.locked do + slice.locked?.should be_true + @buffer.locked?.should be_false + @buffer.locked { @buffer.locked?.should be_true } + end + end +end + +describe "IO::Buffer#locked?" do + after :each do + @buffer&.free + @buffer = nil + end + + it "is false by default" do + @buffer = IO::Buffer.new(4) + @buffer.locked?.should be_false + end + + it "is true only inside of #locked block" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + @buffer.locked?.should be_true + end + @buffer.locked?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/mapped_spec.rb b/spec/ruby/core/io/buffer/mapped_spec.rb new file mode 100644 index 00000000000000..b3610207ffb100 --- /dev/null +++ b/spec/ruby/core/io/buffer/mapped_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#mapped?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.mapped?.should be_false + end + + it "is true for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.mapped?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.mapped?.should be_true + end + end + + ruby_version_is "3.3" do + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.mapped?.should be_true + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.mapped?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.mapped?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.mapped?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.mapped?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/null_spec.rb b/spec/ruby/core/io/buffer/null_spec.rb new file mode 100644 index 00000000000000..3fb1144d0ed66f --- /dev/null +++ b/spec/ruby/core/io/buffer/null_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#null?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :null? + + it "is false for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.null?.should be_false + end + + ruby_version_is "3.3" do + it "is false for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.null?.should be_false + end + end + end + + it "is false for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).null?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/private_spec.rb b/spec/ruby/core/io/buffer/private_spec.rb new file mode 100644 index 00000000000000..7aa308997b1939 --- /dev/null +++ b/spec/ruby/core/io/buffer/private_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../../spec_helper' + +ruby_version_is "3.3" do + describe "IO::Buffer#private?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.private?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.private?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.private?.should be_false + end + end + + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.private?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.private?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.private?.should be_false + end + end + end + + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.private?.should be_false + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.private?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.private?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.private?.should be_false + end + end + + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.private?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.private?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.private?.should be_false + end + end + end + + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.private?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/readonly_spec.rb b/spec/ruby/core/io/buffer/readonly_spec.rb new file mode 100644 index 00000000000000..0014a876ed743e --- /dev/null +++ b/spec/ruby/core/io/buffer/readonly_spec.rb @@ -0,0 +1,143 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#readonly?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.readonly?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.readonly?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a writable mapping" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.readonly?.should be_false + end + end + + it "is true for a readonly mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.readonly?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.readonly?.should be_true + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.readonly?.should be_false + end + end + + it "is true for a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.readonly?.should be_false + end + end + end + end + + # This seems to be the only flag propagated from the source buffer to the slice. + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.readonly?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.readonly?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a read-write file-backed buffer" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a readonly file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.slice.readonly?.should be_false + end + end + end + end + + context "created with .for" do + it "is true when slicing a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.slice.readonly?.should be_true + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.slice.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.readonly?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/resize_spec.rb b/spec/ruby/core/io/buffer/resize_spec.rb new file mode 100644 index 00000000000000..0da3a23356c0dc --- /dev/null +++ b/spec/ruby/core/io/buffer/resize_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#resize" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "resizes internal buffer, preserving type" do + @buffer = IO::Buffer.new(4) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + + platform_is :linux do + it "resizes mapped buffer, preserving type" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + end + + platform_is_not :linux do + it "resizes mapped buffer, changing type to internal" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + end + + context "with a file-backed buffer created with .map" do + it "disallows resizing shared buffer, raising IO::Buffer::AccessError" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + ruby_version_is "3.3" do + it "resizes private buffer, discarding excess contents" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.get_string.should == "require_re" + @buffer.resize(12) + @buffer.size.should == 12 + @buffer.get_string.should == "require_re\0\0" + end + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + @buffer = IO::Buffer.for(+"test") + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + context "with a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.for(+'test') do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.string(4) do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + context "with a null buffer" do + it "allows resizing a 0-sized buffer, creating a regular buffer according to new size" do + @buffer = IO::Buffer.new(0) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + + it "allows resizing after a free, creating a regular buffer according to new size" do + @buffer = IO::Buffer.for("test") + @buffer.free + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + + it "allows resizing to 0, freeing memory" do + @buffer = IO::Buffer.new(4) + @buffer.resize(0) + @buffer.null?.should be_true + end + + it "can be called repeatedly" do + @buffer = IO::Buffer.new(4) + @buffer.resize(10) + @buffer.resize(27) + @buffer.resize(1) + @buffer.size.should == 1 + end + + it "always clears extra memory" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + # This should not cause a re-allocation, just a technical resizing, + # even with very aggressive memory allocation. + @buffer.resize(2) + @buffer.resize(4) + @buffer.get_string.should == "te\0\0" + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::LockedError, "Cannot resize locked buffer!") + end + end + + it "raises ArgumentError if size is negative" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + + it "raises TypeError if size is not an Integer" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(nil) }.should raise_error(TypeError, "not an Integer") + -> { @buffer.resize(10.0) }.should raise_error(TypeError, "not an Integer") + end + + context "with a slice of a buffer" do + # Current behavior of slice resizing seems unintended (it's undocumented, too). + # It either creates a completely new buffer, or breaks the slice on size 0. + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/io/buffer/shared/null_and_empty.rb b/spec/ruby/core/io/buffer/shared/null_and_empty.rb new file mode 100644 index 00000000000000..c8fe9e5e46ca9b --- /dev/null +++ b/spec/ruby/core/io/buffer/shared/null_and_empty.rb @@ -0,0 +1,59 @@ +describe :io_buffer_null_and_empty, shared: true do + it "is false for a buffer with size > 0" do + @buffer = IO::Buffer.new(1) + @buffer.send(@method).should be_false + end + + it "is false for a slice with length > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(1, 2).send(@method).should be_false + end + + it "is false for a file-mapped buffer" do + File.open(__FILE__, "rb") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.send(@method).should be_false + end + end + + it "is false for a non-empty String-backed buffer created with .for" do + @buffer = IO::Buffer.for("test") + @buffer.send(@method).should be_false + end + + ruby_version_is "3.3" do + it "is false for a non-empty String-backed buffer created with .string" do + IO::Buffer.string(4) do |buffer| + buffer.send(@method).should be_false + end + end + end + + it "is true for a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.send(@method).should be_true + end + + it "is true for a slice of a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.slice(0, 0).send(@method).should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(1) + @buffer.free + @buffer.send(@method).should be_true + end + + it "is true for a buffer resized to 0" do + @buffer = IO::Buffer.new(1) + @buffer.resize(0) + @buffer.send(@method).should be_true + end + + it "is true for a buffer whose memory was transferred" do + buffer = IO::Buffer.new(1) + @buffer = buffer.transfer + buffer.send(@method).should be_true + end +end diff --git a/spec/ruby/core/io/buffer/shared_spec.rb b/spec/ruby/core/io/buffer/shared_spec.rb new file mode 100644 index 00000000000000..f2a638cf39f9b1 --- /dev/null +++ b/spec/ruby/core/io/buffer/shared_spec.rb @@ -0,0 +1,117 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#shared?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.shared?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.shared?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.shared?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.shared?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.shared?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.shared?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.shared?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.shared?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.shared?.should be_false + end + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.shared?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/transfer_spec.rb b/spec/ruby/core/io/buffer/transfer_spec.rb new file mode 100644 index 00000000000000..cb8c843ff24750 --- /dev/null +++ b/spec/ruby/core/io/buffer/transfer_spec.rb @@ -0,0 +1,118 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#transfer" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "transfers internal memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + + it "transfers mapped memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "transfers mapped memory to a new buffer, nullifying the original" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "transfers memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.for("test") + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a block" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.for(+"test") do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.string(4) do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + it "allows multiple transfers" do + buffer_1 = IO::Buffer.new(4) + buffer_2 = buffer_1.transfer + @buffer = buffer_2.transfer + buffer_1.null?.should be_true + buffer_2.null?.should be_true + @buffer.null?.should be_false + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.transfer }.should raise_error(IO::Buffer::LockedError, "Cannot transfer ownership of locked buffer!") + end + end + + context "with a slice of a buffer" do + it "transfers source to a new slice, not touching the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.set_string("test") + + new_slice = slice.transfer + slice.null?.should be_true + new_slice.null?.should be_false + @buffer.null?.should be_false + + new_slice.set_string("ea") + @buffer.get_string.should == "east" + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + + slice.null?.should be_false + slice.valid?.should be_false + -> { slice.get_string }.should raise_error(IO::Buffer::InvalidatedError, "Buffer has been invalidated!") + end + end +end diff --git a/spec/ruby/core/io/buffer/valid_spec.rb b/spec/ruby/core/io/buffer/valid_spec.rb new file mode 100644 index 00000000000000..695415dff6332f --- /dev/null +++ b/spec/ruby/core/io/buffer/valid_spec.rb @@ -0,0 +1,119 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#valid?" do + after :each do + @buffer&.free + @buffer = nil + end + + # Non-slices are always valid + context "with a non-slice buffer" do + it "is true for a regular buffer" do + @buffer = IO::Buffer.new(4) + @buffer.valid?.should be_true + end + + it "is true for a 0-size buffer" do + @buffer = IO::Buffer.new(0) + @buffer.valid?.should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(4) + @buffer.free + @buffer.valid?.should be_true + end + + it "is true for a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + it "is true for a freed string-backed buffer" do + @buffer = IO::Buffer.for("hello") + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + # "A buffer becomes invalid if it is a slice of another buffer (or string) + # which has been freed or re-allocated at a different address." + context "with a slice" do + it "is true for a slice of a live buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + end + + context "when buffer is resized" do + platform_is_not :windows do + it "is true when slice is still inside the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(1, 2) + @buffer.resize(3) + slice.valid?.should be_true + end + end + + it "is false when slice becomes outside the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(2, 2) + @buffer.resize(3) + slice.valid?.should be_false + end + + platform_is_not :linux do + # This test does not cause a copy-resize on Linux. + # `#resize` MAY cause the buffer to move, but there is no guarantee. + it "is false when buffer is copied on resize" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + slice = @buffer.slice(0, 2) + @buffer.resize(8) + slice.valid?.should be_false + end + end + end + + it "is false for a slice of a transferred buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + slice.valid?.should be_false + end + + it "is false for a slice of a freed buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + slice.valid?.should be_false + end + + it "is false for a slice of a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_false + end + end + + it "is true for a slice of a freed string-backed buffer while string is alive" do + @buffer = IO::Buffer.for("alive") + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_true + end + + # There probably should be a test with a garbage-collected string, + # but it's not clear how to force that. + + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index e00fe815726318..74f6f1b0bdbcce 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -231,6 +231,10 @@ def to_f() 1.2 end @object.send(:Float, "0x0f").should == 15.0 end + it "interprets negative hex value" do + @object.send(:Float, "-0x10").should == -16.0 + end + it "accepts embedded _ if the number does not contain a-f" do @object.send(:Float, "0x1_0").should == 16.0 end diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index 0404caec6d8d79..5edb70541d1da1 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -7,7 +7,9 @@ autoload :KSAutoloadA, "autoload_a.rb" autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb") -autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +define_autoload_KSAutoloadCallsRequire = -> { + autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +} def check_autoload(const) autoload? const @@ -43,6 +45,7 @@ def check_autoload(const) end it "calls main.require(path) to load the file" do + define_autoload_KSAutoloadCallsRequire.call main = TOPLEVEL_BINDING.eval("self") main.should_receive(:require).with("main_autoload_not_exist.rb") # The constant won't be defined since require is mocked to do nothing diff --git a/spec/ruby/core/matchdata/bytebegin_spec.rb b/spec/ruby/core/matchdata/bytebegin_spec.rb new file mode 100644 index 00000000000000..08c1fd6d1ea499 --- /dev/null +++ b/spec/ruby/core/matchdata/bytebegin_spec.rb @@ -0,0 +1,132 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "MatchData#bytebegin" do + context "when passed an integer argument" do + it "returns the byte-based offset of the start of the nth element" do + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 2 + end + + it "returns nil when the nth match isn't found" do + match_data = /something is( not)? (right)/.match("something is right") + match_data.bytebegin(1).should be_nil + end + + it "returns the byte-based offset for multi-byte strings" do + match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 3 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi-byte strings with unicode regexp" do + match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 3 + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.bytebegin(obj).should == 2 + end + + it "raises IndexError if index is out of bounds" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.bytebegin(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + match_data.bytebegin(3) + }.should raise_error(IndexError, "index 3 out of matches") + end + end + + context "when passed a String argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 3 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?.)(.)(?\d+)(\d)/.match("TñX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?.)(.)(?\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 4 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin("a").should == 3 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin("æ").should == 1 + end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.bytebegin("y") + }.should raise_error(IndexError, "undefined group name reference: y") + end + end + + context "when passed a Symbol argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 3 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?.)(.)(?\d+)(\d)/.match("TñX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?.)(.)(?\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 4 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:a).should == 3 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:æ).should == 1 + end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.bytebegin(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end + end + end +end diff --git a/spec/ruby/core/matchdata/byteend_spec.rb b/spec/ruby/core/matchdata/byteend_spec.rb new file mode 100644 index 00000000000000..98015e287d5b7f --- /dev/null +++ b/spec/ruby/core/matchdata/byteend_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "MatchData#byteend" do + context "when passed an integer argument" do + it "returns the byte-based offset of the end of the nth element" do + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.byteend(0).should == 7 + match_data.byteend(2).should == 3 + end + + it "returns nil when the nth match isn't found" do + match_data = /something is( not)? (right)/.match("something is right") + match_data.byteend(1).should be_nil + end + + it "returns the byte-based offset for multi-byte strings" do + match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") + match_data.byteend(0).should == 8 + match_data.byteend(2).should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi-byte strings with unicode regexp" do + match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") + match_data.byteend(0).should == 8 + match_data.byteend(2).should == 4 + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.byteend(obj).should == 3 + end + end + + context "when passed a String argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend("a").should == 2 + match_data.byteend("b").should == 6 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?.)(.)(?\d+)(\d)/.match("TñX1138.") + match_data.byteend("a").should == 3 + match_data.byteend("b").should == 7 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?.)(.)(?\d+)(\d)/u.match("TñX1138.") + match_data.byteend("a").should == 3 + match_data.byteend("b").should == 7 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend("a").should == 6 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend("æ").should == 2 + end + end + + context "when passed a Symbol argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend(:a).should == 2 + match_data.byteend(:b).should == 6 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?.)(.)(?\d+)(\d)/.match("TñX1138.") + match_data.byteend(:a).should == 3 + match_data.byteend(:b).should == 7 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?.)(.)(?\d+)(\d)/u.match("TñX1138.") + match_data.byteend(:a).should == 3 + match_data.byteend(:b).should == 7 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend(:a).should == 6 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") + match_data.byteend(:æ).should == 2 + end + end + end +end diff --git a/spec/ruby/core/matchdata/offset_spec.rb b/spec/ruby/core/matchdata/offset_spec.rb index 1ccb54b7a7f2e0..a03d58aad15ead 100644 --- a/spec/ruby/core/matchdata/offset_spec.rb +++ b/spec/ruby/core/matchdata/offset_spec.rb @@ -1,30 +1,102 @@ -# -*- encoding: utf-8 -*- - require_relative '../../spec_helper' describe "MatchData#offset" do - it "returns a two element array with the begin and end of the nth match" do - match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns beginning and ending character offset of whole matched substring for 0 element" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + m.offset(0).should == [1, 7] + end + + it "returns beginning and ending character offset of n-th match, all the subsequent elements are capturing groups" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + + m.offset(2).should == [2, 3] + m.offset(3).should == [3, 6] + m.offset(4).should == [6, 7] + end + + it "accepts String as a reference to a named capture" do + m = /(?foo)(?bar)/.match("foobar") + + m.offset("f").should == [0, 3] + m.offset("b").should == [3, 6] + end + + it "accepts Symbol as a reference to a named capture" do + m = /(?foo)(?bar)/.match("foobar") + + m.offset(:f).should == [0, 3] + m.offset(:b).should == [3, 6] end - it "returns [nil, nil] when the nth match isn't found" do - match_data = /something is( not)? (right)/.match("something is right") - match_data.offset(1).should == [nil, nil] + it "returns [nil, nil] if a capturing group is optional and doesn't match" do + m = /(?q..)?/.match("foobarbaz") + + m.offset("x").should == [nil, nil] + m.offset(1).should == [nil, nil] end - it "returns the offset for multi byte strings" do - match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns correct beginning and ending character offset for multi-byte strings" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + + m.offset(1).should == [1, 2] + m.offset(3).should == [2, 3] end not_supported_on :opal do - it "returns the offset for multi byte strings with unicode regexp" do - match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns correct character offset for multi-byte strings with unicode regexp" do + m = /\A\u3042(.)(.)?(.)\z/u.match("\u3042\u3043\u3044") + + m.offset(1).should == [1, 2] + m.offset(3).should == [2, 3] end end + + it "returns [nil, nil] if a capturing group is optional and doesn't match for multi-byte string" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + + m.offset(2).should == [nil, nil] + end + + it "converts argument into integer if is not String nor Symbol" do + m = /(?foo)(?bar)/.match("foobar") + + obj = Object.new + def obj.to_int; 2; end + + m.offset(1r).should == [0, 3] + m.offset(1.1).should == [0, 3] + m.offset(obj).should == [3, 6] + end + + it "raises IndexError if there is no group with the provided name" do + m = /(?foo)(?bar)/.match("foobar") + + -> { + m.offset("y") + }.should raise_error(IndexError, "undefined group name reference: y") + + -> { + m.offset(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end + + it "raises IndexError if index is out of bounds" do + m = /(?foo)(?bar)/.match("foobar") + + -> { + m.offset(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + m.offset(3) + }.should raise_error(IndexError, "index 3 out of matches") + end + + it "raises TypeError if can't convert argument into Integer" do + m = /(?foo)(?bar)/.match("foobar") + + -> { + m.offset([]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") + end end diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb index 739be3ead84488..90cd36551aa7b7 100644 --- a/spec/ruby/core/module/const_added_spec.rb +++ b/spec/ruby/core/module/const_added_spec.rb @@ -117,6 +117,7 @@ module self::B end ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule end it "is called when a new class is defined under self" do @@ -158,6 +159,7 @@ class self::B end ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB end it "is called when an autoload is defined" do diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb index 06b3b215c29921..96649ea10bdfb7 100644 --- a/spec/ruby/core/module/const_source_location_spec.rb +++ b/spec/ruby/core/module/const_source_location_spec.rb @@ -245,6 +245,14 @@ @line = __LINE__ - 1 end + before :each do + @loaded_features = $".dup + end + + after :each do + $".replace @loaded_features + end + it 'returns the autoload location while not resolved' do ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line] end @@ -265,6 +273,8 @@ ConstantSpecs.const_source_location(:ConstSource).should == autoload_location ConstantSpecs::ConstSource::LOCATION.should == ConstantSpecs.const_source_location(:ConstSource) ConstantSpecs::BEFORE_DEFINE_LOCATION.should == autoload_location + ConstantSpecs.send :remove_const, :ConstSource + ConstantSpecs.send :remove_const, :BEFORE_DEFINE_LOCATION end end end diff --git a/spec/ruby/core/module/name_spec.rb b/spec/ruby/core/module/name_spec.rb index fd28ee0a33a718..d3318e16451485 100644 --- a/spec/ruby/core/module/name_spec.rb +++ b/spec/ruby/core/module/name_spec.rb @@ -190,6 +190,7 @@ module self::B ScratchPad.recorded.should.one?(/#::A$/) ScratchPad.recorded.should.one?(/#::A::B$/) + ModuleSpecs::NameSpecs.send :remove_const, :NamedModule end it "returns a frozen String" do diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index 12c1c214ddf852..624b02c90e7713 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -86,6 +86,7 @@ module m::N; end ModuleSpecs::SetTemporaryNameSpec::M = m m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M end it "can update the name when assigned to a constant" do diff --git a/spec/ruby/core/random/new_spec.rb b/spec/ruby/core/random/new_spec.rb index 90e2a9d6f2f808..69210cef03bd43 100644 --- a/spec/ruby/core/random/new_spec.rb +++ b/spec/ruby/core/random/new_spec.rb @@ -11,7 +11,7 @@ it "returns Random instances initialized with different seeds" do first = Random.new second = Random.new - (0..20).map { first.rand } .should_not == (0..20).map { second.rand } + (0..20).map { first.rand }.should_not == (0..20).map { second.rand } end it "accepts an Integer seed value as an argument" do diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb index 898b6d4ff7a846..ceb9cf823ac53c 100644 --- a/spec/ruby/language/regexp/encoding_spec.rb +++ b/spec/ruby/language/regexp/encoding_spec.rb @@ -39,7 +39,11 @@ end it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do - -> { /./n.match("\303\251".dup.force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) + -> { + eval <<~RUBY + /./n.match("\303\251".dup.force_encoding('utf-8')) + RUBY + }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) end it 'uses US-ASCII as /n encoding if all chars are 7-bit' do diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index 79571d689f35e1..6be3bfd023a36e 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -136,10 +136,14 @@ class << Object.new it 'captures successfully at the top-level' do ScratchPad.record [] + loaded_features = $".dup + begin + require_relative 'fixtures/rescue/top_level' - require_relative 'fixtures/rescue/top_level' - - ScratchPad.recorded.should == ["message"] + ScratchPad.recorded.should == ["message"] + ensure + $".replace loaded_features + end end end diff --git a/spec/ruby/library/net-http/HTTPServerException_spec.rb b/spec/ruby/library/net-http/HTTPServerException_spec.rb index 5e0a833feebe7b..020d3cce85d562 100644 --- a/spec/ruby/library/net-http/HTTPServerException_spec.rb +++ b/spec/ruby/library/net-http/HTTPServerException_spec.rb @@ -3,10 +3,10 @@ describe "Net::HTTPServerException" do it "is a subclass of Net::ProtoServerError and is warned as deprecated" do - -> { Net::HTTPServerException.should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/) + -> { eval("Net::HTTPServerException").should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/) end it "includes the Net::HTTPExceptions module and is warned as deprecated" do - -> { Net::HTTPServerException.should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/) + -> { eval("Net::HTTPServerException").should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/) end end diff --git a/spec/ruby/library/tempfile/callback_spec.rb b/spec/ruby/library/tempfile/callback_spec.rb deleted file mode 100644 index c0b15183263fca..00000000000000 --- a/spec/ruby/library/tempfile/callback_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require_relative '../../spec_helper' -require 'tempfile' - -describe "Tempfile.callback" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/library/tempfile/create_spec.rb b/spec/ruby/library/tempfile/create_spec.rb new file mode 100644 index 00000000000000..74c48bf32a9a07 --- /dev/null +++ b/spec/ruby/library/tempfile/create_spec.rb @@ -0,0 +1,176 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile.create" do + after :each do + if @tempfile + @tempfile.close + File.unlink(@tempfile.path) if File.file?(@tempfile.path) + end + end + + it "returns a new, open regular File instance placed in tmpdir" do + @tempfile = Tempfile.create + # Unlike Tempfile.open this returns a true File, + # but `.should be_an_instance_of(File)` would be true either way. + @tempfile.instance_of?(File).should be_true + + @tempfile.should_not.closed? + File.file?(@tempfile.path).should be_true + + @tempfile.path.should.start_with?(Dir.tmpdir) + @tempfile.path.should_not == "#{Dir.tmpdir}/" + end + + it "returns file in w+ mode" do + @tempfile = Tempfile.create + @tempfile << "Test!\nMore test!" + @tempfile.rewind + @tempfile.read.should == "Test!\nMore test!" + + # Not "a+" mode, which would write at the end of the file. + @tempfile.rewind + @tempfile.print "Trust" + @tempfile.rewind + @tempfile.read.should == "Trust\nMore test!" + end + + platform_is_not :windows do + it "returns a private, readable and writable file" do + @tempfile = Tempfile.create + stat = @tempfile.stat + stat.should.readable? + stat.should.writable? + stat.should_not.executable? + stat.should_not.world_readable? + stat.should_not.world_writable? + end + end + + platform_is :windows do + it "returns a public, readable and writable file" do + @tempfile = Tempfile.create + stat = @tempfile.stat + stat.should.readable? + stat.should.writable? + stat.should_not.executable? + stat.should.world_readable? + stat.should.world_writable? + end + end + + context "when called with a block" do + it "returns the value of the block" do + value = Tempfile.create do |tempfile| + tempfile << "Test!" + "return" + end + value.should == "return" + end + + it "closes and unlinks file after block execution" do + Tempfile.create do |tempfile| + @tempfile = tempfile + @tempfile.should_not.closed? + File.exist?(@tempfile.path).should be_true + end + + @tempfile.should.closed? + File.exist?(@tempfile.path).should be_false + end + end + + context "when called with a single positional argument" do + it "uses a String as a prefix for the filename" do + @tempfile = Tempfile.create("create_spec") + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should_not == "#{Dir.tmpdir}/create_spec" + end + + it "uses an array of one String as a prefix for the filename" do + @tempfile = Tempfile.create(["create_spec"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should_not == "#{Dir.tmpdir}/create_spec" + end + + it "uses an array of two Strings as a prefix and suffix for the filename" do + @tempfile = Tempfile.create(["create_spec", ".temp"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should.end_with?(".temp") + end + + it "ignores excessive array elements after the first two" do + @tempfile = Tempfile.create(["create_spec", ".temp", :".txt"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should.end_with?(".temp") + end + + it "raises ArgumentError if passed something else than a String or an array of Strings" do + -> { Tempfile.create(:create_spec) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec") + -> { Tempfile.create([:create_spec]) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec") + -> { Tempfile.create(["create_spec", :temp]) }.should raise_error(ArgumentError, "unexpected suffix: :temp") + end + end + + context "when called with a second positional argument" do + it "uses it as a directory for the tempfile" do + @tempfile = Tempfile.create("create_spec", "./") + @tempfile.path.should.start_with?("./create_spec") + end + + it "raises TypeError if argument can not be converted to a String" do + -> { Tempfile.create("create_spec", :temp) }.should raise_error(TypeError, "no implicit conversion of Symbol into String") + end + end + + context "when called with a mode option" do + it "ORs it with the default mode, forcing it to be readable and writable" do + @tempfile = Tempfile.create(mode: File::RDONLY) + @tempfile.puts "test" + @tempfile.rewind + @tempfile.read.should == "test\n" + end + + it "raises NoMethodError if passed a String mode" do + -> { Tempfile.create(mode: "wb") }.should raise_error(NoMethodError, /undefined method ['`]|' for .+String/) + end + end + + ruby_version_is "3.4" do + context "when called with anonymous: true" do + it "returns an already unlinked File without a proper path" do + @tempfile = Tempfile.create(anonymous: true) + @tempfile.should_not.closed? + @tempfile.path.should == "#{Dir.tmpdir}/" + File.file?(@tempfile.path).should be_false + end + + it "unlinks file before calling the block" do + Tempfile.create(anonymous: true) do |tempfile| + @tempfile = tempfile + @tempfile.should_not.closed? + @tempfile.path.should == "#{Dir.tmpdir}/" + File.file?(@tempfile.path).should be_false + end + @tempfile.should.closed? + end + end + + context "when called with anonymous: false" do + it "returns a usual File with a path" do + @tempfile = Tempfile.create(anonymous: false) + @tempfile.should_not.closed? + @tempfile.path.should.start_with?(Dir.tmpdir) + File.file?(@tempfile.path).should be_true + end + end + end + + context "when called with other options" do + it "passes them along to File.open" do + @tempfile = Tempfile.create(encoding: "IBM037:IBM037", binmode: true) + @tempfile.external_encoding.should == Encoding.find("IBM037") + @tempfile.binmode?.should be_true + end + end +end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 0c3c98a5c0a832..c14983c7ead703 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -724,14 +724,16 @@ end describe "rb_define_dummy_encoding" do + run = 0 + it "defines the dummy encoding" do - @s.rb_define_dummy_encoding("FOO") - enc = Encoding.find("FOO") + @s.rb_define_dummy_encoding("FOO#{run += 1}") + enc = Encoding.find("FOO#{run}") enc.should.dummy? end it "returns the index of the dummy encoding" do - index = @s.rb_define_dummy_encoding("BAR") + index = @s.rb_define_dummy_encoding("BAR#{run += 1}") index.should == Encoding.list.size - 1 end diff --git a/spec/ruby/optional/capi/ext/struct_spec.c b/spec/ruby/optional/capi/ext/struct_spec.c index 413249e828f6c6..756cfca8dd37bf 100644 --- a/spec/ruby/optional/capi/ext/struct_spec.c +++ b/spec/ruby/optional/capi/ext/struct_spec.c @@ -62,6 +62,10 @@ static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st) { return rb_struct_size(st); } +static VALUE struct_spec_rb_struct_initialize(VALUE self, VALUE st, VALUE values) { + return rb_struct_initialize(st, values); +} + #if defined(RUBY_VERSION_IS_3_3) /* Only allow setting three attributes, should be sufficient for testing. */ static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass, @@ -90,6 +94,7 @@ void Init_struct_spec(void) { rb_define_method(cls, "rb_struct_define_under", struct_spec_rb_struct_define_under, 5); rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4); rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1); + rb_define_method(cls, "rb_struct_initialize", struct_spec_rb_struct_initialize, 2); #if defined(RUBY_VERSION_IS_3_3) rb_define_method(cls, "rb_data_define", struct_spec_rb_data_define, 4); #endif diff --git a/spec/ruby/optional/capi/globals_spec.rb b/spec/ruby/optional/capi/globals_spec.rb index 48677620bcf8a2..4657293e15824d 100644 --- a/spec/ruby/optional/capi/globals_spec.rb +++ b/spec/ruby/optional/capi/globals_spec.rb @@ -41,14 +41,19 @@ @f.sb_get_global_value.should == "XYZ" end + run = 0 + it "rb_define_readonly_variable should define a new readonly global variable" do + name = "ro_gvar#{run += 1}" + eval <<~RUBY # Check the gvar doesn't exist and ensure rb_gv_get doesn't implicitly declare the gvar, # otherwise the rb_define_readonly_variable call will conflict. - suppress_warning { @f.sb_gv_get("ro_gvar") } .should == nil + suppress_warning { @f.sb_gv_get("#{name}") }.should == nil - @f.rb_define_readonly_variable("ro_gvar", 15) - $ro_gvar.should == 15 - -> { $ro_gvar = 10 }.should raise_error(NameError) + @f.rb_define_readonly_variable("#{name}", 15) + $#{name}.should == 15 + -> { $#{name} = 10 }.should raise_error(NameError) + RUBY end it "rb_define_hooked_variable should define a C hooked global variable" do diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb index b7684e566becdd..af39ec01927b83 100644 --- a/spec/ruby/optional/capi/module_spec.rb +++ b/spec/ruby/optional/capi/module_spec.rb @@ -38,7 +38,7 @@ CApiModuleSpecs::C.const_set(:_INVALID, 1) }.should raise_error(NameError, /wrong constant name/) - @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) + suppress_warning { @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) } @m.rb_const_get(CApiModuleSpecs::C, :_INVALID).should == 2 # Ruby-level should still not allow access diff --git a/spec/ruby/optional/capi/struct_spec.rb b/spec/ruby/optional/capi/struct_spec.rb index 474c39795690ca..cc8d7f932e53b1 100644 --- a/spec/ruby/optional/capi/struct_spec.rb +++ b/spec/ruby/optional/capi/struct_spec.rb @@ -208,20 +208,48 @@ @s.rb_struct_size(@struct).should == 3 end end + + describe "rb_struct_initialize" do + it "sets all members" do + @s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil + @struct.a.should == 1 + @struct.b.should == 2 + @struct.c.should == 3 + end + + it "does not freeze the Struct instance" do + @s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil + @struct.should_not.frozen? + @s.rb_struct_initialize(@struct, [4, 5, 6]).should == nil + @struct.a.should == 4 + @struct.b.should == 5 + @struct.c.should == 6 + end + + it "raises ArgumentError if too many values" do + -> { @s.rb_struct_initialize(@struct, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") + end + + it "treats missing values as nil" do + @s.rb_struct_initialize(@struct, [1, 2]).should == nil + @struct.a.should == 1 + @struct.b.should == 2 + @struct.c.should == nil + end + end end ruby_version_is "3.3" do describe "C-API Data function" do - before :each do + before :all do @s = CApiStructSpecs.new + @klass = @s.rb_data_define(nil, "a", "b", "c") end describe "rb_data_define" do it "returns a subclass of Data class when passed nil as the first argument" do - klass = @s.rb_data_define(nil, "a", "b", "c") - - klass.should.is_a? Class - klass.superclass.should == Data + @klass.should.is_a? Class + @klass.superclass.should == Data end it "returns a subclass of a class when passed as the first argument" do @@ -233,8 +261,7 @@ end it "creates readers for the members" do - klass = @s.rb_data_define(nil, "a", "b", "c") - obj = klass.new(1, 2, 3) + obj = @klass.new(1, 2, 3) obj.a.should == 1 obj.b.should == 2 @@ -242,8 +269,7 @@ end it "returns the member names as Symbols" do - klass = @s.rb_data_define(nil, "a", "b", "c") - obj = klass.new(0, 0, 0) + obj = @klass.new(0, 0, 0) obj.members.should == [:a, :b, :c] end @@ -256,5 +282,35 @@ -> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)") end end + + describe "rb_struct_initialize" do + it "sets all members for a Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == 3 + end + + it "freezes the Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.should.frozen? + -> { @s.rb_struct_initialize(data, [1, 2, 3]) }.should raise_error(FrozenError) + end + + it "raises ArgumentError if too many values" do + data = @klass.allocate + -> { @s.rb_struct_initialize(data, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") + end + + it "treats missing values as nil" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == nil + end + end end end From f3f3a40cdc7ade6c35b5a38016a0c72f0bb09315 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 19 Nov 2025 11:37:02 +0100 Subject: [PATCH 1233/2435] The C-API specs cache should be invalidated when C-API specs .c & .h files are changed --- .github/actions/capiext/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index 147c5387b3183e..49562725f46782 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -27,11 +27,11 @@ runs: eval $(grep -e '^arch *=' -e '^ruby_version *=' -e '^DLEXT *=' Makefile | sed 's/ *= */=/') case "${ruby_version}" in - *+*) key=capiexts-${arch}-${ruby_version};; + *+*) key=capiexts-${arch}-${ruby_version}-${{ hashFiles('src/spec/ruby/optional/capi/ext/*.[ch]') }};; *) key=;; esac echo version=$ruby_version >> $GITHUB_OUTPUT - echo key=$key >> $GITHUB_OUTPUT + echo key="$key" >> $GITHUB_OUTPUT echo DLEXT=$DLEXT >> $GITHUB_OUTPUT working-directory: ${{ inputs.builddir }} From 7840ef2f4370d22c5d793e2d32537d6bf9923ce6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Nov 2025 22:51:09 +0900 Subject: [PATCH 1234/2435] Win32: Allow some mingw implemeations to use old msvcrt --- configure.ac | 1 - error.c | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 55d65534db9ee6..339ee3b2f2e66e 100644 --- a/configure.ac +++ b/configure.ac @@ -526,7 +526,6 @@ AS_CASE(["$target_os"], RT_VER=`echo "$rb_cv_msvcrt" | tr -cd [0-9]` test "$RT_VER" = "" && RT_VER=60 test "$rb_cv_msvcrt" = "ucrt" && RT_VER=140 - AS_IF([test $RT_VER -lt 120], AC_MSG_ERROR(Runtime library $RT_VER is not supported)) AC_DEFINE_UNQUOTED(RUBY_MSVCRT_VERSION, $RT_VER) sysconfdir= ]) diff --git a/error.c b/error.c index f452f7b01cf23b..ed66e6c8488d0f 100644 --- a/error.c +++ b/error.c @@ -1076,7 +1076,8 @@ NORETURN(static void die(void)); static void die(void) { -#if defined(_WIN32) +#if defined(_WIN32) && defined(RUBY_MSVCRT_VERSION) && RUBY_MSVCRT_VERSION >= 80 + /* mingw32 declares in stdlib.h but does not provide. */ _set_abort_behavior( 0, _CALL_REPORTFAULT); #endif From 28908a95c4d303e3cf12f03b0b475a48778435a7 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 19 Nov 2025 21:46:12 +0100 Subject: [PATCH 1235/2435] Fix provided features spec with --repeat 2 --- spec/ruby/core/kernel/require_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 81777c5313a528..d680859dd6195d 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -32,13 +32,14 @@ features.sort.should == provided.sort + requires = provided ruby_version_is "3.5" do - provided.map! { |f| f == "pathname" ? "pathname.so" : f } + requires = requires.map { |f| f == "pathname" ? "pathname.so" : f } end - code = provided.map { |f| "puts require #{f.inspect}\n" }.join + code = requires.map { |f| "puts require #{f.inspect}\n" }.join required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size + required.should == "false\n" * requires.size end it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new From 4a1af72a13d41dcc38af7d69ea1f44856265d43f Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 19 Nov 2025 12:58:24 -0800 Subject: [PATCH 1236/2435] ZJIT: Count all calls to C functions from generated code (#15240) lobsters: ``` Top-20 calls to C functions from JIT code (79.9% of total 97,004,883): rb_vm_opt_send_without_block: 19,874,212 (20.5%) rb_vm_setinstancevariable: 9,774,841 (10.1%) rb_ivar_get: 9,358,866 ( 9.6%) rb_hash_aref: 6,828,948 ( 7.0%) rb_vm_send: 6,441,551 ( 6.6%) rb_vm_env_write: 5,375,989 ( 5.5%) rb_vm_invokesuper: 3,037,836 ( 3.1%) Module#===: 2,562,446 ( 2.6%) rb_ary_entry: 2,354,546 ( 2.4%) Kernel#is_a?: 1,424,092 ( 1.5%) rb_vm_opt_getconstant_path: 1,344,923 ( 1.4%) Thread.current: 1,300,822 ( 1.3%) rb_zjit_defined_ivar: 1,222,613 ( 1.3%) rb_vm_invokeblock: 1,184,555 ( 1.2%) Hash#[]=: 1,061,969 ( 1.1%) rb_ary_push: 1,024,987 ( 1.1%) rb_ary_new_capa: 904,003 ( 0.9%) rb_str_buf_append: 833,782 ( 0.9%) rb_class_allocate_instance: 822,626 ( 0.8%) Hash#fetch: 755,913 ( 0.8%) ``` railsbench: ``` Top-20 calls to C functions from JIT code (74.8% of total 189,170,268): rb_vm_opt_send_without_block: 29,870,307 (15.8%) rb_vm_setinstancevariable: 17,631,199 ( 9.3%) rb_hash_aref: 16,928,890 ( 8.9%) rb_ivar_get: 14,441,240 ( 7.6%) rb_vm_env_write: 11,571,001 ( 6.1%) rb_vm_send: 11,153,457 ( 5.9%) rb_vm_invokesuper: 7,568,267 ( 4.0%) Module#===: 6,065,923 ( 3.2%) Hash#[]=: 2,842,990 ( 1.5%) rb_ary_entry: 2,766,125 ( 1.5%) rb_ary_push: 2,722,079 ( 1.4%) rb_vm_invokeblock: 2,594,398 ( 1.4%) Thread.current: 2,560,129 ( 1.4%) rb_str_getbyte: 1,965,627 ( 1.0%) Kernel#is_a?: 1,961,815 ( 1.0%) rb_vm_opt_getconstant_path: 1,863,678 ( 1.0%) rb_hash_new_with_size: 1,796,456 ( 0.9%) rb_class_allocate_instance: 1,785,043 ( 0.9%) String#empty?: 1,713,414 ( 0.9%) rb_ary_new_capa: 1,678,834 ( 0.9%) ``` shipit: ``` Top-20 calls to C functions from JIT code (83.4% of total 182,402,821): rb_vm_opt_send_without_block: 45,753,484 (25.1%) rb_ivar_get: 21,020,650 (11.5%) rb_vm_setinstancevariable: 17,528,603 ( 9.6%) rb_hash_aref: 11,892,856 ( 6.5%) rb_vm_send: 11,723,471 ( 6.4%) rb_vm_env_write: 10,434,452 ( 5.7%) Module#===: 4,225,048 ( 2.3%) rb_vm_invokesuper: 3,705,906 ( 2.0%) Thread.current: 3,337,603 ( 1.8%) rb_ary_entry: 3,114,378 ( 1.7%) Hash#[]=: 2,509,912 ( 1.4%) Array#empty?: 2,282,994 ( 1.3%) rb_vm_invokeblock: 2,210,511 ( 1.2%) Hash#fetch: 2,017,960 ( 1.1%) _bi20: 1,975,147 ( 1.1%) rb_zjit_defined_ivar: 1,897,127 ( 1.0%) rb_vm_opt_getconstant_path: 1,813,294 ( 1.0%) rb_ary_new_capa: 1,615,406 ( 0.9%) Kernel#is_a?: 1,567,854 ( 0.9%) rb_class_allocate_instance: 1,560,035 ( 0.9%) ``` Thanks to @eregon for the idea. Co-authored-by: Jacob Denbeaux Co-authored-by: Alan Wu --- zjit.rb | 1 + zjit/src/backend/lir.rs | 12 ++++++ zjit/src/codegen.rs | 68 +++++++++++++++++++------------- zjit/src/cruby.rs | 12 ++++-- zjit/src/hir.rs | 11 ++++-- zjit/src/hir/opt_tests.rs | 82 +++++++++++++++++++-------------------- zjit/src/state.rs | 9 +++++ zjit/src/stats.rs | 7 ++++ 8 files changed, 127 insertions(+), 75 deletions(-) diff --git a/zjit.rb b/zjit.rb index bb6d4d3cdca16c..fc306c19a47fba 100644 --- a/zjit.rb +++ b/zjit.rb @@ -174,6 +174,7 @@ def stats_string # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index cb8382a43c940c..3c9bf72023d90b 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2065,6 +2065,17 @@ impl Assembler { out } + pub fn count_call_to(&mut self, fn_name: &str) { + // We emit ccalls while initializing the JIT. Unfortunately, we skip those because + // otherwise we have no counter pointers to read. + if crate::state::ZJITState::has_instance() && get_option!(stats) { + let ccall_counter_pointers = crate::state::ZJITState::get_ccall_counter_pointers(); + let counter_ptr = ccall_counter_pointers.entry(fn_name.to_string()).or_insert_with(|| Box::new(0)); + let counter_ptr: &mut u64 = counter_ptr.as_mut(); + self.incr_counter(Opnd::const_ptr(counter_ptr), 1.into()); + } + } + pub fn cmp(&mut self, left: Opnd, right: Opnd) { self.push_insn(Insn::Cmp { left, right }); } @@ -2389,6 +2400,7 @@ pub(crate) use asm_comment; macro_rules! asm_ccall { [$asm: ident, $fn_name:ident, $($args:expr),* ] => {{ $crate::backend::lir::asm_comment!($asm, concat!("call ", stringify!($fn_name))); + $asm.count_call_to(stringify!($fn_name)); $asm.ccall($fn_name as *const u8, vec![$($args),*]) }}; } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8fc66791a665ad..18266b46933e6c 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -419,14 +419,14 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), - Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), + Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), - Insn::CCallWithFrame { cfunc, args, cme, state, blockiseq, .. } => - gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), - Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state, return_type: _, elidable: _ } => { - gen_ccall_variadic(jit, asm, *cfunc, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) + Insn::CCallWithFrame { cfunc, name, args, cme, state, blockiseq, .. } => + gen_ccall_with_frame(jit, asm, *cfunc, *name, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), + Insn::CCallVariadic { cfunc, recv, args, name, cme, state, return_type: _, elidable: _ } => { + gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), @@ -697,6 +697,7 @@ fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf let mut cargs = vec![EC]; cargs.extend(args); + asm.count_call_to(unsafe { std::ffi::CStr::from_ptr(bf.name).to_str().unwrap() }); asm.ccall(bf.func_ptr as *const u8, cargs) } @@ -754,6 +755,7 @@ fn gen_ccall_with_frame( jit: &mut JITState, asm: &mut Assembler, cfunc: *const u8, + name: ID, args: Vec, cme: *const rb_callable_method_entry_t, blockiseq: Option, @@ -801,6 +803,7 @@ fn gen_ccall_with_frame( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.count_call_to(&name.contents_lossy()); let result = asm.ccall(cfunc, args); asm_comment!(asm, "pop C frame"); @@ -817,7 +820,8 @@ fn gen_ccall_with_frame( /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. -fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, args: Vec) -> lir::Opnd { +fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, args: Vec) -> lir::Opnd { + asm.count_call_to(&name.contents_lossy()); asm.ccall(cfunc, args) } @@ -827,6 +831,7 @@ fn gen_ccall_variadic( jit: &mut JITState, asm: &mut Assembler, cfunc: *const u8, + name: ID, recv: Opnd, args: Vec, cme: *const rb_callable_method_entry_t, @@ -859,6 +864,7 @@ fn gen_ccall_variadic( asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); let argv_ptr = gen_push_opnds(asm, &args); + asm.count_call_to(&name.contents_lossy()); let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); gen_pop_opnds(asm, &args); @@ -1169,9 +1175,10 @@ fn gen_send( unsafe extern "C" { fn rb_vm_send(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE; } - asm.ccall( - rb_vm_send as *const u8, - vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], + asm_ccall!( + asm, + rb_vm_send, + EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into() ) } @@ -1192,9 +1199,10 @@ fn gen_send_forward( unsafe extern "C" { fn rb_vm_sendforward(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE; } - asm.ccall( - rb_vm_sendforward as *const u8, - vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], + asm_ccall!( + asm, + rb_vm_sendforward, + EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into() ) } @@ -1213,9 +1221,10 @@ fn gen_send_without_block( unsafe extern "C" { fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE; } - asm.ccall( - rb_vm_opt_send_without_block as *const u8, - vec![EC, CFP, Opnd::const_ptr(cd)], + asm_ccall!( + asm, + rb_vm_opt_send_without_block, + EC, CFP, Opnd::const_ptr(cd) ) } @@ -1331,9 +1340,10 @@ fn gen_invokeblock( unsafe extern "C" { fn rb_vm_invokeblock(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE; } - asm.ccall( - rb_vm_invokeblock as *const u8, - vec![EC, CFP, Opnd::const_ptr(cd)], + asm_ccall!( + asm, + rb_vm_invokeblock, + EC, CFP, Opnd::const_ptr(cd) ) } @@ -1353,9 +1363,10 @@ fn gen_invokesuper( unsafe extern "C" { fn rb_vm_invokesuper(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE; } - asm.ccall( - rb_vm_invokesuper as *const u8, - vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], + asm_ccall!( + asm, + rb_vm_invokesuper, + EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into() ) } @@ -1436,9 +1447,10 @@ fn gen_array_include( unsafe extern "C" { fn rb_vm_opt_newarray_include_p(ec: EcPtr, num: c_long, elts: *const VALUE, target: VALUE) -> VALUE; } - asm.ccall( - rb_vm_opt_newarray_include_p as *const u8, - vec![EC, num.into(), elements_ptr, target], + asm_ccall!( + asm, + rb_vm_opt_newarray_include_p, + EC, num.into(), elements_ptr, target ) } @@ -1454,9 +1466,10 @@ fn gen_dup_array_include( unsafe extern "C" { fn rb_vm_opt_duparray_include_p(ec: EcPtr, ary: VALUE, target: VALUE) -> VALUE; } - asm.ccall( - rb_vm_opt_duparray_include_p as *const u8, - vec![EC, ary.into(), target], + asm_ccall!( + asm, + rb_vm_opt_duparray_include_p, + EC, ary.into(), target ) } @@ -1527,6 +1540,7 @@ fn gen_object_alloc_class(asm: &mut Assembler, class: VALUE, state: &FrameState) let alloc_func = unsafe { rb_zjit_class_get_alloc_func(class) }; assert!(alloc_func.is_some(), "class {} passed to ObjectAllocClass must have an allocator", get_class_name(class)); asm_comment!(asm, "call allocator for class {}", get_class_name(class)); + asm.count_call_to(&format!("{}::allocator", get_class_name(class))); asm.ccall(alloc_func.unwrap() as *const u8, vec![class.into()]) } } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index db47385bc88321..61c25a4092bdc4 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -775,11 +775,17 @@ pub fn rust_str_to_ruby(str: &str) -> VALUE { unsafe { rb_utf8_str_new(str.as_ptr() as *const _, str.len() as i64) } } -/// Produce a Ruby symbol from a Rust string slice -pub fn rust_str_to_sym(str: &str) -> VALUE { +/// Produce a Ruby ID from a Rust string slice +pub fn rust_str_to_id(str: &str) -> ID { let c_str = CString::new(str).unwrap(); let c_ptr: *const c_char = c_str.as_ptr(); - unsafe { rb_id2sym(rb_intern(c_ptr)) } + unsafe { rb_intern(c_ptr) } +} + +/// Produce a Ruby symbol from a Rust string slice +pub fn rust_str_to_sym(str: &str) -> VALUE { + let id = rust_str_to_id(str); + unsafe { rb_id2sym(id) } } /// Produce an owned Rust String from a C char pointer diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 58638f30f0264d..982400db5030cd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2889,12 +2889,13 @@ impl Function { let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); + let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme, - name: method_id, + name, state, return_type: types::BasicObject, elidable: false, @@ -3018,6 +3019,7 @@ impl Function { // No inlining; emit a call let cfunc = unsafe { get_mct_func(cfunc) }.cast(); + let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); let return_type = props.return_type; @@ -3025,7 +3027,7 @@ impl Function { // Filter for a leaf and GC free function if props.leaf && props.no_gc { fun.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); - let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); + let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { if get_option!(stats) { @@ -3036,7 +3038,7 @@ impl Function { cfunc, args: cfunc_args, cme, - name: method_id, + name, state, return_type, elidable, @@ -3099,12 +3101,13 @@ impl Function { } let return_type = props.return_type; let elidable = props.elidable; + let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); let ccall = fun.push_insn(block, Insn::CCallVariadic { cfunc, recv, args, cme, - name: method_id, + name, state, return_type, elidable, diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index fadb6ced5f1c0a..19f0e91b47e82b 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -560,7 +560,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(CustomEq@0x1000, !=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(CustomEq@0x1000) v28:HeapObject[class_exact:CustomEq] = GuardType v9, HeapObject[class_exact:CustomEq] - v29:BoolExact = CCallWithFrame !=@0x1038, v28, v9 + v29:BoolExact = CCallWithFrame BasicObject#!=@0x1038, v28, v9 v20:NilClass = Const Value(nil) CheckInterrupts Return v20 @@ -784,7 +784,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v24:ArraySubclass[class_exact:C] = GuardType v13, ArraySubclass[class_exact:C] - v25:BasicObject = CCallWithFrame fun_new_map@0x1038, v24, block=0x1040 + v25:BasicObject = CCallWithFrame C#fun_new_map@0x1038, v24, block=0x1040 v16:BasicObject = GetLocal l0, EP@3 CheckInterrupts Return v25 @@ -1043,7 +1043,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Object@0x1008) v22:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] - v23:BasicObject = CCallVariadic puts@0x1040, v22, v12 + v23:BasicObject = CCallVariadic Kernel#puts@0x1040, v22, v12 CheckInterrupts Return v23 "); @@ -2241,7 +2241,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_cfunc_optimized_send_count - v34:StringExact|NilClass = CCall name@0x1048, v29 + v34:StringExact|NilClass = CCall Module#name@0x1048, v29 PatchPoint NoEPEscape(test) v22:Fixnum[1] = Const Value(1) CheckInterrupts @@ -2273,7 +2273,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v29:Fixnum = CCall length@0x1038, v13 + v29:Fixnum = CCall Array#length@0x1038, v13 v20:Fixnum[5] = Const Value(5) CheckInterrupts Return v20 @@ -2417,7 +2417,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v29:Fixnum = CCall size@0x1038, v13 + v29:Fixnum = CCall Array#size@0x1038, v13 v20:Fixnum[5] = Const Value(5) CheckInterrupts Return v20 @@ -3150,7 +3150,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Class@0x1040) - v57:BasicObject = CCallVariadic new@0x1048, v46, v16 + v57:BasicObject = CCallVariadic Array.new@0x1048, v46, v16 CheckInterrupts Return v57 "); @@ -3181,7 +3181,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Set@0x1008) v49:SetExact = GuardType v18, SetExact - v50:BasicObject = CCallVariadic initialize@0x1070, v49 + v50:BasicObject = CCallVariadic Set#initialize@0x1070, v49 CheckInterrupts CheckInterrupts Return v18 @@ -3211,7 +3211,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Class@0x1040) - v54:BasicObject = CCallVariadic new@0x1048, v43 + v54:BasicObject = CCallVariadic String.new@0x1048, v43 CheckInterrupts Return v54 "); @@ -3243,7 +3243,7 @@ mod hir_opt_tests { v50:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) PatchPoint NoSingletonClass(Regexp@0x1008) - v54:BasicObject = CCallVariadic initialize@0x1078, v50, v17 + v54:BasicObject = CCallVariadic Regexp#initialize@0x1078, v50, v17 CheckInterrupts CheckInterrupts Return v50 @@ -3271,7 +3271,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v30:Fixnum = CCall length@0x1038, v18 + v30:Fixnum = CCall Array#length@0x1038, v18 CheckInterrupts Return v30 "); @@ -3298,7 +3298,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v30:Fixnum = CCall size@0x1038, v18 + v30:Fixnum = CCall Array#size@0x1038, v18 CheckInterrupts Return v30 "); @@ -3458,7 +3458,7 @@ mod hir_opt_tests { v10:HashExact = NewHash PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) - v22:BasicObject = CCallWithFrame dup@0x1038, v10 + v22:BasicObject = CCallWithFrame Kernel#dup@0x1038, v10 v14:BasicObject = SendWithoutBlock v22, :freeze CheckInterrupts Return v14 @@ -3551,7 +3551,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v22:BasicObject = CCallWithFrame dup@0x1038, v10 + v22:BasicObject = CCallWithFrame Kernel#dup@0x1038, v10 v14:BasicObject = SendWithoutBlock v22, :freeze CheckInterrupts Return v14 @@ -3645,7 +3645,7 @@ mod hir_opt_tests { v11:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:BasicObject = CCallWithFrame dup@0x1040, v11 + v23:BasicObject = CCallWithFrame String#dup@0x1040, v11 v15:BasicObject = SendWithoutBlock v23, :freeze CheckInterrupts Return v15 @@ -3740,7 +3740,7 @@ mod hir_opt_tests { v11:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:BasicObject = CCallWithFrame dup@0x1040, v11 + v23:BasicObject = CCallWithFrame String#dup@0x1040, v11 v15:BasicObject = SendWithoutBlock v23, :-@ CheckInterrupts Return v15 @@ -3882,7 +3882,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallWithFrame to_s@0x1040, v31 + v32:BasicObject = CCallWithFrame Array#to_s@0x1040, v31 v19:String = AnyToString v9, str: v32 v21:StringExact = StringConcat v13, v19 CheckInterrupts @@ -4745,7 +4745,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall empty?@0x1038, v23 + v25:BoolExact = CCall Array#empty?@0x1038, v23 CheckInterrupts Return v25 "); @@ -4773,7 +4773,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v23:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall empty?@0x1038, v23 + v25:BoolExact = CCall Hash#empty?@0x1038, v23 CheckInterrupts Return v25 "); @@ -5036,7 +5036,7 @@ mod hir_opt_tests { v11:ArrayExact = ArrayDup v10 PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v21:BasicObject = CCallWithFrame map@0x1040, v11, block=0x1048 + v21:BasicObject = CCallWithFrame Array#map@0x1040, v11, block=0x1048 CheckInterrupts Return v21 "); @@ -5484,7 +5484,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v20:ArrayExact = CCallWithFrame reverse@0x1038, v10 + v20:ArrayExact = CCallWithFrame Array#reverse@0x1038, v10 CheckInterrupts Return v20 "); @@ -5537,7 +5537,7 @@ mod hir_opt_tests { v13:StringExact = StringCopy v12 PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v23:StringExact = CCallVariadic join@0x1040, v10, v13 + v23:StringExact = CCallVariadic Array#join@0x1040, v10, v13 CheckInterrupts Return v23 "); @@ -5859,7 +5859,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) IncrCounter inline_cfunc_optimized_send_count - v25:BasicObject = CCall current@0x1048, v20 + v25:BasicObject = CCall Thread.current@0x1048, v20 CheckInterrupts Return v25 "); @@ -5889,7 +5889,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallVariadic []=@0x1038, v31, v16, v18 + v32:BasicObject = CCallVariadic Array#[]=@0x1038, v31, v16, v18 CheckInterrupts Return v18 "); @@ -5980,7 +5980,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v28:ArrayExact = GuardType v9, ArrayExact - v29:BasicObject = CCallVariadic push@0x1038, v28, v14, v16, v18 + v29:BasicObject = CCallVariadic Array#push@0x1038, v28, v14, v16, v18 CheckInterrupts Return v29 "); @@ -6008,7 +6008,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall length@0x1038, v23 + v25:Fixnum = CCall Array#length@0x1038, v23 CheckInterrupts Return v25 "); @@ -6036,7 +6036,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall size@0x1038, v23 + v25:Fixnum = CCall Array#size@0x1038, v23 CheckInterrupts Return v25 "); @@ -6064,7 +6064,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) v25:StringExact = GuardType v9, StringExact - v26:BasicObject = CCallWithFrame =~@0x1040, v25, v14 + v26:BasicObject = CCallWithFrame String#=~@0x1040, v25, v14 CheckInterrupts Return v26 "); @@ -6235,7 +6235,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v30:StringExact = GuardType v13, StringExact - v31:BasicObject = CCallWithFrame setbyte@0x1038, v30, v14, v15 + v31:BasicObject = CCallWithFrame String#setbyte@0x1038, v30, v14, v15 CheckInterrupts Return v31 "); @@ -6264,7 +6264,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall empty?@0x1038, v23 + v25:BoolExact = CCall String#empty?@0x1038, v23 CheckInterrupts Return v25 "); @@ -6348,7 +6348,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) v22:Integer = GuardType v9, Integer - v23:BasicObject = CCallWithFrame succ@0x1038, v22 + v23:BasicObject = CCallWithFrame Integer#succ@0x1038, v22 CheckInterrupts Return v23 "); @@ -6405,7 +6405,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v27:StringExact = GuardType v11, StringExact - v28:BasicObject = CCallWithFrame <<@0x1038, v27, v12 + v28:BasicObject = CCallWithFrame String#<<@0x1038, v27, v12 CheckInterrupts Return v28 "); @@ -6465,7 +6465,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(MyString@0x1000) v27:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] - v28:BasicObject = CCallWithFrame <<@0x1038, v27, v12 + v28:BasicObject = CCallWithFrame String#<<@0x1038, v27, v12 CheckInterrupts Return v28 "); @@ -6622,7 +6622,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) v25:Integer = GuardType v11, Integer - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame Integer#^@0x1038, v25, v12 CheckInterrupts Return v26 "); @@ -6645,7 +6645,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) v25:Fixnum = GuardType v11, Fixnum - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame Integer#^@0x1038, v25, v12 CheckInterrupts Return v26 "); @@ -6668,7 +6668,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) v25:TrueClass = GuardType v11, TrueClass - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame TrueClass#^@0x1038, v25, v12 CheckInterrupts Return v26 "); @@ -6718,7 +6718,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v23:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall size@0x1038, v23 + v25:Fixnum = CCall Hash#size@0x1038, v23 CheckInterrupts Return v25 "); @@ -7086,7 +7086,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) PatchPoint NoSingletonClass(C@0x1008) v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v14 + v25:BasicObject = CCallVariadic Kernel#respond_to?@0x1040, v24, v14 CheckInterrupts Return v25 "); @@ -7637,7 +7637,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall size@0x1038, v23 + v25:Fixnum = CCall String#size@0x1038, v23 CheckInterrupts Return v25 "); @@ -7756,7 +7756,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall length@0x1038, v23 + v25:Fixnum = CCall String#length@0x1038, v23 CheckInterrupts Return v25 "); @@ -7922,7 +7922,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Class@0x1038) v30:ModuleSubclass[class_exact*:Class@VALUE(0x1038)] = GuardType v26, ModuleSubclass[class_exact*:Class@VALUE(0x1038)] IncrCounter inline_cfunc_optimized_send_count - v32:StringExact|NilClass = CCall name@0x1070, v30 + v32:StringExact|NilClass = CCall Module#name@0x1070, v30 CheckInterrupts Return v32 "); diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 06296eb8f20d08..fd59161812a7ee 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -59,6 +59,9 @@ pub struct ZJITState { /// Counter pointers for un-annotated C functions not_annotated_frame_cfunc_counter_pointers: HashMap>, + /// Counter pointers for all calls to any kind of C function from JIT code + ccall_counter_pointers: HashMap>, + /// Locations of side exists within generated code exit_locations: Option, } @@ -135,6 +138,7 @@ impl ZJITState { exit_trampoline_with_counter: exit_trampoline, full_frame_cfunc_counter_pointers: HashMap::new(), not_annotated_frame_cfunc_counter_pointers: HashMap::new(), + ccall_counter_pointers: HashMap::new(), exit_locations, }; unsafe { ZJIT_STATE = Enabled(zjit_state); } @@ -215,6 +219,11 @@ impl ZJITState { &mut ZJITState::get_instance().not_annotated_frame_cfunc_counter_pointers } + /// Get a mutable reference to ccall counter pointers + pub fn get_ccall_counter_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().ccall_counter_pointers + } + /// Was --zjit-save-compiled-iseqs specified? pub fn should_log_compiled_iseqs() -> bool { get_option!(log_compiled_iseqs).is_some() diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index b0ca28d258506a..df172997ce9793 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -689,6 +689,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, &key_string, **counter); } + // Set ccall counters + let ccall = ZJITState::get_ccall_counter_pointers(); + for (signature, counter) in ccall.iter() { + let key_string = format!("ccall_{}", signature); + set_stat_usize!(hash, &key_string, **counter); + } + hash } From 4e1f20fee6d97b6dc65e0d4eac1f9cc37312bd5f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 19 Nov 2025 13:08:26 -0800 Subject: [PATCH 1237/2435] [ruby/error_highlight] Fix prism_spot_def_for_name for singletons Previously calling a singleton method with invalid arguments would give: RuntimeError: Incompatible locations This is because `join` wants the operator to come before the location https://github.com/ruby/error_highlight/commit/44920551dd --- lib/error_highlight/base.rb | 2 +- test/error_highlight/test_error_highlight.rb | 56 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index 22e0bf0dc39738..bc4a62c9d63508 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -913,7 +913,7 @@ def prism_spot_constant_path_operator_write # ^^^ def prism_spot_def_for_name location = @node.name_loc - location = location.join(@node.operator_loc) if @node.operator_loc + location = @node.operator_loc.join(location) if @node.operator_loc prism_location(location) end diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index 1276a0a0d93a7b..d3ca99021b9ad2 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1733,6 +1733,62 @@ def test_spot_with_node assert_equal expected_spot, actual_spot end + module SingletonMethodWithSpacing + LINENO = __LINE__ + 1 + def self . baz(x:) + x + end + end + + def test_singleton_method_with_spacing_missing_keyword + lineno = __LINE__ + assert_error_message(ArgumentError, <<~END) do +missing keyword: :x (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 16 } + | SingletonMethodWithSpacing.baz + ^^^^ + callee: #{ __FILE__ }:#{ SingletonMethodWithSpacing::LINENO } + #{ + MethodDefLocationSupported ? + "| def self . baz(x:) + ^^^^^" : + "(cannot highlight method definition; try Ruby 4.0 or later)" + } + END + + SingletonMethodWithSpacing.baz + end + end + + module SingletonMethodMultipleKwargs + LINENO = __LINE__ + 1 + def self.run(shop_id:, param1:) + shop_id + param1 + end + end + + def test_singleton_method_multiple_missing_keywords + lineno = __LINE__ + assert_error_message(ArgumentError, <<~END) do +missing keywords: :shop_id, :param1 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 16 } + | SingletonMethodMultipleKwargs.run + ^^^^ + callee: #{ __FILE__ }:#{ SingletonMethodMultipleKwargs::LINENO } + #{ + MethodDefLocationSupported ? + "| def self.run(shop_id:, param1:) + ^^^^" : + "(cannot highlight method definition; try Ruby 4.0 or later)" + } + END + + SingletonMethodMultipleKwargs.run + end + end + private def find_node_by_id(node, node_id) From ba2b97a9440d92e78d519fbcbdecc25b72a42705 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 19 Nov 2025 23:37:37 +0100 Subject: [PATCH 1238/2435] Update to ruby/spec@6e62695 --- spec/ruby/command_line/dash_0_spec.rb | 2 +- spec/ruby/core/enumerable/to_set_spec.rb | 4 ++-- spec/ruby/core/file/stat/birthtime_spec.rb | 2 +- spec/ruby/core/kernel/caller_locations_spec.rb | 4 ++-- spec/ruby/core/kernel/caller_spec.rb | 4 ++-- spec/ruby/core/kernel/inspect_spec.rb | 2 +- spec/ruby/core/kernel/require_spec.rb | 4 ++-- spec/ruby/core/marshal/dump_spec.rb | 6 +++--- spec/ruby/core/method/source_location_spec.rb | 4 ++-- spec/ruby/core/module/ruby2_keywords_spec.rb | 2 +- spec/ruby/core/module/set_temporary_name_spec.rb | 2 +- spec/ruby/core/objectspace/_id2ref_spec.rb | 4 ++-- spec/ruby/core/proc/ruby2_keywords_spec.rb | 2 +- spec/ruby/core/proc/source_location_spec.rb | 8 ++++---- spec/ruby/core/process/status/bit_and_spec.rb | 4 ++-- spec/ruby/core/process/status/right_shift_spec.rb | 4 ++-- spec/ruby/core/range/max_spec.rb | 4 ++-- spec/ruby/core/range/reverse_each_spec.rb | 2 +- spec/ruby/core/set/compare_by_identity_spec.rb | 4 ++-- spec/ruby/core/set/divide_spec.rb | 4 ++-- spec/ruby/core/set/equal_value_spec.rb | 2 +- spec/ruby/core/set/flatten_merge_spec.rb | 2 +- spec/ruby/core/set/flatten_spec.rb | 4 ++-- spec/ruby/core/set/hash_spec.rb | 2 +- spec/ruby/core/set/join_spec.rb | 2 +- spec/ruby/core/set/pretty_print_cycle_spec.rb | 4 ++-- spec/ruby/core/set/proper_subset_spec.rb | 2 +- spec/ruby/core/set/proper_superset_spec.rb | 2 +- spec/ruby/core/set/shared/inspect.rb | 8 ++++---- spec/ruby/core/set/sortedset/sortedset_spec.rb | 2 +- spec/ruby/core/set/subset_spec.rb | 2 +- spec/ruby/core/set/superset_spec.rb | 2 +- spec/ruby/core/unboundmethod/source_location_spec.rb | 4 ++-- spec/ruby/language/numbered_parameters_spec.rb | 4 ++-- spec/ruby/language/predefined_spec.rb | 8 ++++---- spec/ruby/language/regexp_spec.rb | 2 +- spec/ruby/language/send_spec.rb | 2 +- spec/ruby/language/variables_spec.rb | 4 ++-- spec/ruby/library/cgi/cookie/domain_spec.rb | 2 +- spec/ruby/library/cgi/cookie/expires_spec.rb | 2 +- spec/ruby/library/cgi/cookie/initialize_spec.rb | 2 +- spec/ruby/library/cgi/cookie/name_spec.rb | 2 +- spec/ruby/library/cgi/cookie/parse_spec.rb | 2 +- spec/ruby/library/cgi/cookie/path_spec.rb | 2 +- spec/ruby/library/cgi/cookie/secure_spec.rb | 2 +- spec/ruby/library/cgi/cookie/to_s_spec.rb | 2 +- spec/ruby/library/cgi/cookie/value_spec.rb | 2 +- spec/ruby/library/cgi/escapeElement_spec.rb | 4 ++-- spec/ruby/library/cgi/htmlextension/a_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/base_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/blockquote_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/br_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/caption_spec.rb | 2 +- .../ruby/library/cgi/htmlextension/checkbox_group_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/checkbox_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/doctype_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/file_field_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/form_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/frame_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/frameset_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/hidden_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/html_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/image_button_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/img_spec.rb | 2 +- .../ruby/library/cgi/htmlextension/multipart_form_spec.rb | 2 +- .../ruby/library/cgi/htmlextension/password_field_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/radio_button_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/radio_group_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/reset_spec.rb | 2 +- .../ruby/library/cgi/htmlextension/scrolling_list_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/submit_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/text_field_spec.rb | 2 +- spec/ruby/library/cgi/htmlextension/textarea_spec.rb | 2 +- spec/ruby/library/cgi/http_header_spec.rb | 2 +- spec/ruby/library/cgi/initialize_spec.rb | 2 +- spec/ruby/library/cgi/out_spec.rb | 2 +- spec/ruby/library/cgi/parse_spec.rb | 2 +- spec/ruby/library/cgi/pretty_spec.rb | 2 +- spec/ruby/library/cgi/print_spec.rb | 2 +- .../library/cgi/queryextension/accept_charset_spec.rb | 2 +- .../library/cgi/queryextension/accept_encoding_spec.rb | 2 +- .../library/cgi/queryextension/accept_language_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/accept_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/auth_type_spec.rb | 2 +- .../ruby/library/cgi/queryextension/cache_control_spec.rb | 2 +- .../library/cgi/queryextension/content_length_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/content_type_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/cookies_spec.rb | 2 +- .../library/cgi/queryextension/element_reference_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/from_spec.rb | 2 +- .../library/cgi/queryextension/gateway_interface_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/has_key_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/host_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/include_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/key_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/keys_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/multipart_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/negotiate_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/params_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/path_info_spec.rb | 2 +- .../library/cgi/queryextension/path_translated_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/pragma_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/query_string_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/referer_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/remote_addr_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/remote_host_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/remote_ident_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/remote_user_spec.rb | 2 +- .../library/cgi/queryextension/request_method_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/script_name_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/server_name_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/server_port_spec.rb | 2 +- .../library/cgi/queryextension/server_protocol_spec.rb | 2 +- .../library/cgi/queryextension/server_software_spec.rb | 2 +- spec/ruby/library/cgi/queryextension/user_agent_spec.rb | 2 +- spec/ruby/library/cgi/rfc1123_date_spec.rb | 2 +- spec/ruby/library/cgi/unescapeElement_spec.rb | 4 ++-- spec/ruby/library/cgi/unescape_spec.rb | 4 ++-- spec/ruby/library/net-http/http/post_spec.rb | 2 +- .../ruby/library/net-http/httpgenericrequest/exec_spec.rb | 4 ++-- spec/ruby/library/stringscanner/check_spec.rb | 4 ---- spec/ruby/library/stringscanner/check_until_spec.rb | 6 ------ spec/ruby/library/stringscanner/exist_spec.rb | 4 ---- spec/ruby/library/stringscanner/get_byte_spec.rb | 4 ---- spec/ruby/library/stringscanner/getch_spec.rb | 4 ---- spec/ruby/library/stringscanner/scan_byte_spec.rb | 4 ---- spec/ruby/library/stringscanner/scan_integer_spec.rb | 8 +------- spec/ruby/library/stringscanner/scan_until_spec.rb | 6 ------ spec/ruby/library/stringscanner/search_full_spec.rb | 4 ---- spec/ruby/library/stringscanner/skip_until_spec.rb | 6 ------ spec/ruby/optional/capi/ext/rubyspec.h | 4 ++-- spec/ruby/optional/capi/ext/set_spec.c | 2 +- spec/ruby/optional/capi/ext/thread_spec.c | 4 ++-- spec/ruby/optional/capi/set_spec.rb | 2 +- spec/ruby/optional/capi/string_spec.rb | 6 +++--- spec/ruby/optional/capi/thread_spec.rb | 2 +- spec/ruby/security/cve_2020_10663_spec.rb | 2 +- spec/ruby/shared/kernel/raise.rb | 4 ++-- 141 files changed, 168 insertions(+), 216 deletions(-) diff --git a/spec/ruby/command_line/dash_0_spec.rb b/spec/ruby/command_line/dash_0_spec.rb index 73c5e29004eb42..2ce4f49b5e4d83 100755 --- a/spec/ruby/command_line/dash_0_spec.rb +++ b/spec/ruby/command_line/dash_0_spec.rb @@ -5,7 +5,7 @@ ruby_exe("puts $/, $-0", options: "-072").should == ":\n:\n" end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "sets $/ and $-0 as a frozen string" do ruby_exe("puts $/.frozen?, $-0.frozen?", options: "-072").should == "true\ntrue\n" end diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb index e0437fea613bd9..e1fcd3a20d0273 100644 --- a/spec/ruby/core/enumerable/to_set_spec.rb +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -11,7 +11,7 @@ [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9] end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "instantiates an object of provided as the first argument set class" do set = nil proc{set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)}.should complain(/Enumerable#to_set/) @@ -20,7 +20,7 @@ end end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "instantiates an object of provided as the first argument set class" do set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass) set.should be_kind_of(EnumerableSpecs::SetSubclass) diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index adecee15b0ec86..9aa39297b24d3c 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -1,7 +1,7 @@ require_relative '../../../spec_helper' platform_is(:windows, :darwin, :freebsd, :netbsd, - *ruby_version_is("3.5") { :linux }, + *ruby_version_is("4.0") { :linux }, ) do not_implemented_messages = [ "birthtime() function is unimplemented", # unsupported OS/version diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 6074879d594f8f..a917dba504a931 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -83,7 +83,7 @@ end end - ruby_version_is "3.4"..."3.5" do + ruby_version_is "3.4"..."4.0" do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location file.should.start_with?(' { Kernel.instance_method(:tap).source_location } do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location file.should.start_with?(' *a, b { } diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 484466f5774771..69b4e2fd8273b6 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -53,12 +53,12 @@ end it "works even if the proc was created on the same line" do - ruby_version_is(""..."3.5") do + ruby_version_is(""..."4.0") do proc { true }.source_location.should == [__FILE__, __LINE__] Proc.new { true }.source_location.should == [__FILE__, __LINE__] -> { true }.source_location.should == [__FILE__, __LINE__] end - ruby_version_is("3.5") do + ruby_version_is("4.0") do proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] @@ -94,10 +94,10 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) location = proc.source_location - ruby_version_is(""..."3.5") do + ruby_version_is(""..."4.0") do location.should == ["foo", 100] end - ruby_version_is("3.5") do + ruby_version_is("4.0") do location.should == ["foo", 100, 2, 100, 5] end end diff --git a/spec/ruby/core/process/status/bit_and_spec.rb b/spec/ruby/core/process/status/bit_and_spec.rb index 0e0edb0afa3958..a80536462947f2 100644 --- a/spec/ruby/core/process/status/bit_and_spec.rb +++ b/spec/ruby/core/process/status/bit_and_spec.rb @@ -1,6 +1,6 @@ require_relative '../../../spec_helper' -ruby_version_is ""..."3.5" do +ruby_version_is ""..."4.0" do describe "Process::Status#&" do it "returns a bitwise and of the integer status of an exited child" do @@ -17,7 +17,7 @@ end end - ruby_version_is "3.3"..."3.5" do + ruby_version_is "3.3"..."4.0" do it "raises an ArgumentError if mask is negative" do suppress_warning do ruby_exe("exit(0)") diff --git a/spec/ruby/core/process/status/right_shift_spec.rb b/spec/ruby/core/process/status/right_shift_spec.rb index a1ab75141a3822..355aaf4c9532cb 100644 --- a/spec/ruby/core/process/status/right_shift_spec.rb +++ b/spec/ruby/core/process/status/right_shift_spec.rb @@ -1,6 +1,6 @@ require_relative '../../../spec_helper' -ruby_version_is ""..."3.5" do +ruby_version_is ""..."4.0" do describe "Process::Status#>>" do it "returns a right shift of the integer status of an exited child" do @@ -16,7 +16,7 @@ end end - ruby_version_is "3.3"..."3.5" do + ruby_version_is "3.3"..."4.0" do it "raises an ArgumentError if shift value is negative" do suppress_warning do ruby_exe("exit(0)") diff --git a/spec/ruby/core/range/max_spec.rb b/spec/ruby/core/range/max_spec.rb index 8b83f69a5a2121..09371f52987862 100644 --- a/spec/ruby/core/range/max_spec.rb +++ b/spec/ruby/core/range/max_spec.rb @@ -55,7 +55,7 @@ (..1.0).max.should == 1.0 end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "raises for an exclusive beginless Integer range" do -> { (...1).max @@ -63,7 +63,7 @@ end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "returns the end point for exclusive beginless Integer ranges" do (...1).max.should == 0 end diff --git a/spec/ruby/core/range/reverse_each_spec.rb b/spec/ruby/core/range/reverse_each_spec.rb index b51e04c3fff24e..56390cc0da4822 100644 --- a/spec/ruby/core/range/reverse_each_spec.rb +++ b/spec/ruby/core/range/reverse_each_spec.rb @@ -88,7 +88,7 @@ (1..3).reverse_each.size.should == 3 end - ruby_bug "#20936", "3.4"..."3.5" do + ruby_bug "#20936", "3.4"..."4.0" do it "returns Infinity when Range size is infinite" do (..3).reverse_each.size.should == Float::INFINITY end diff --git a/spec/ruby/core/set/compare_by_identity_spec.rb b/spec/ruby/core/set/compare_by_identity_spec.rb index 0dda6d79f09177..238dc117a6ccfa 100644 --- a/spec/ruby/core/set/compare_by_identity_spec.rb +++ b/spec/ruby/core/set/compare_by_identity_spec.rb @@ -90,7 +90,7 @@ def o.hash; 123; end set.to_a.sort.should == [a1, a2].sort end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "raises a FrozenError on frozen sets" do set = Set.new.freeze -> { @@ -99,7 +99,7 @@ def o.hash; 123; end end end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "raises a FrozenError on frozen sets" do set = Set.new.freeze -> { diff --git a/spec/ruby/core/set/divide_spec.rb b/spec/ruby/core/set/divide_spec.rb index cbe0042f16e6ae..c6c6003e99d8b6 100644 --- a/spec/ruby/core/set/divide_spec.rb +++ b/spec/ruby/core/set/divide_spec.rb @@ -25,7 +25,7 @@ set.map{ |x| x.to_a.sort }.sort.should == [[1], [3, 4], [6], [9, 10, 11]] end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "yields each two Object to the block" do ret = [] Set[1, 2].divide { |x, y| ret << [x, y] } @@ -33,7 +33,7 @@ end end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "yields each two Object to the block" do ret = [] Set[1, 2].divide { |x, y| ret << [x, y] } diff --git a/spec/ruby/core/set/equal_value_spec.rb b/spec/ruby/core/set/equal_value_spec.rb index e3514928c816d3..721a79a3f1370b 100644 --- a/spec/ruby/core/set/equal_value_spec.rb +++ b/spec/ruby/core/set/equal_value_spec.rb @@ -24,7 +24,7 @@ set1.should == set2 end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when comparing to a Set-like object" do it "returns true when a Set and a Set-like object contain the same elements" do Set[1, 2, 3].should == SetSpecs::SetLike.new([1, 2, 3]) diff --git a/spec/ruby/core/set/flatten_merge_spec.rb b/spec/ruby/core/set/flatten_merge_spec.rb index d7c2b306579443..13cedeead953de 100644 --- a/spec/ruby/core/set/flatten_merge_spec.rb +++ b/spec/ruby/core/set/flatten_merge_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Set#flatten_merge" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "is protected" do Set.should have_protected_instance_method("flatten_merge") end diff --git a/spec/ruby/core/set/flatten_spec.rb b/spec/ruby/core/set/flatten_spec.rb index 870eccc2f10c99..f2cb3dfa524a35 100644 --- a/spec/ruby/core/set/flatten_spec.rb +++ b/spec/ruby/core/set/flatten_spec.rb @@ -16,7 +16,7 @@ -> { set.flatten }.should raise_error(ArgumentError) end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when Set contains a Set-like object" do it "returns a copy of self with each included Set-like object flattened" do Set[SetSpecs::SetLike.new([1])].flatten.should == Set[1] @@ -48,7 +48,7 @@ end version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when Set contains a Set-like object" do it "flattens self, including Set-like objects" do Set[SetSpecs::SetLike.new([1])].flatten!.should == Set[1] diff --git a/spec/ruby/core/set/hash_spec.rb b/spec/ruby/core/set/hash_spec.rb index 4b4696e34ccbf3..63a0aa66a55ef9 100644 --- a/spec/ruby/core/set/hash_spec.rb +++ b/spec/ruby/core/set/hash_spec.rb @@ -10,7 +10,7 @@ Set[1, 2, 3].hash.should_not == Set[:a, "b", ?c].hash end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do # see https://github.com/jruby/jruby/issues/8393 it "is equal to nil.hash for an uninitialized Set" do Set.allocate.hash.should == nil.hash diff --git a/spec/ruby/core/set/join_spec.rb b/spec/ruby/core/set/join_spec.rb index cdb593597d8641..1c1e8a8af8457d 100644 --- a/spec/ruby/core/set/join_spec.rb +++ b/spec/ruby/core/set/join_spec.rb @@ -20,7 +20,7 @@ set.join(' | ').should == "a | b | c" end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "calls #to_a to convert the Set in to an Array" do set = Set[:a, :b, :c] set.should_receive(:to_a).and_return([:a, :b, :c]) diff --git a/spec/ruby/core/set/pretty_print_cycle_spec.rb b/spec/ruby/core/set/pretty_print_cycle_spec.rb index d4cca515e2a2d3..7e6017c112b77e 100644 --- a/spec/ruby/core/set/pretty_print_cycle_spec.rb +++ b/spec/ruby/core/set/pretty_print_cycle_spec.rb @@ -3,10 +3,10 @@ describe "Set#pretty_print_cycle" do it "passes the 'pretty print' representation of a self-referencing Set to the pretty print writer" do pp = mock("PrettyPrint") - ruby_version_is(""..."3.5") do + ruby_version_is(""..."4.0") do pp.should_receive(:text).with("#") end - ruby_version_is("3.5") do + ruby_version_is("4.0") do pp.should_receive(:text).with("Set[...]") end Set[1, 2, 3].pretty_print_cycle(pp) diff --git a/spec/ruby/core/set/proper_subset_spec.rb b/spec/ruby/core/set/proper_subset_spec.rb index a84c4197c23dd6..fb7848c0015200 100644 --- a/spec/ruby/core/set/proper_subset_spec.rb +++ b/spec/ruby/core/set/proper_subset_spec.rb @@ -34,7 +34,7 @@ end version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when comparing to a Set-like object" do it "returns true if passed a Set-like object that self is a proper subset of" do Set[1, 2, 3].proper_subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true diff --git a/spec/ruby/core/set/proper_superset_spec.rb b/spec/ruby/core/set/proper_superset_spec.rb index 653411f6b23452..dc1e87e2308e67 100644 --- a/spec/ruby/core/set/proper_superset_spec.rb +++ b/spec/ruby/core/set/proper_superset_spec.rb @@ -32,7 +32,7 @@ -> { Set[].proper_superset?(Object.new) }.should raise_error(ArgumentError) end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when comparing to a Set-like object" do it "returns true if passed a Set-like object that self is a proper superset of" do Set[1, 2, 3, 4].proper_superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true diff --git a/spec/ruby/core/set/shared/inspect.rb b/spec/ruby/core/set/shared/inspect.rb index fbc7486acd61d4..a90af66c980dbf 100644 --- a/spec/ruby/core/set/shared/inspect.rb +++ b/spec/ruby/core/set/shared/inspect.rb @@ -7,13 +7,13 @@ Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String) end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "does include the elements of the set" do Set["1"].send(@method).should == 'Set["1"]' end end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "does include the elements of the set" do Set["1"].send(@method).should == '#' end @@ -23,7 +23,7 @@ Set["1", "2"].send(@method).should include('", "') end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "correctly handles cyclic-references" do set1 = Set[] set2 = Set[set1] @@ -33,7 +33,7 @@ end end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "correctly handles cyclic-references" do set1 = Set[] set2 = Set[set1] diff --git a/spec/ruby/core/set/sortedset/sortedset_spec.rb b/spec/ruby/core/set/sortedset/sortedset_spec.rb index 41f010e011c8de..f3c1ec058d80ac 100644 --- a/spec/ruby/core/set/sortedset/sortedset_spec.rb +++ b/spec/ruby/core/set/sortedset/sortedset_spec.rb @@ -1,7 +1,7 @@ require_relative '../../../spec_helper' describe "SortedSet" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "raises error including message that it has been extracted from the set stdlib" do -> { SortedSet diff --git a/spec/ruby/core/set/subset_spec.rb b/spec/ruby/core/set/subset_spec.rb index cde61d7cd7745f..112bd9b38adc12 100644 --- a/spec/ruby/core/set/subset_spec.rb +++ b/spec/ruby/core/set/subset_spec.rb @@ -34,7 +34,7 @@ end version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when comparing to a Set-like object" do it "returns true if passed a Set-like object that self is a subset of" do Set[1, 2, 3].subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true diff --git a/spec/ruby/core/set/superset_spec.rb b/spec/ruby/core/set/superset_spec.rb index 9d7bab964a235c..9b3df2d047d4c0 100644 --- a/spec/ruby/core/set/superset_spec.rb +++ b/spec/ruby/core/set/superset_spec.rb @@ -32,7 +32,7 @@ -> { Set[].superset?(Object.new) }.should raise_error(ArgumentError) end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do context "when comparing to a Set-like object" do it "returns true if passed a Set-like object that self is a superset of" do Set[1, 2, 3, 4].superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 2391d07d9958ef..85078ff34e8cd5 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -55,10 +55,10 @@ eval('def m; end', nil, "foo", 100) end location = c.instance_method(:m).source_location - ruby_version_is(""..."3.5") do + ruby_version_is(""..."4.0") do location.should == ["foo", 100] end - ruby_version_is("3.5") do + ruby_version_is("4.0") do location.should == ["foo", 100, 0, 100, 10] end end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 39ddd6fee83e19..de532c326d4cd1 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -90,14 +90,14 @@ proc { _2 }.parameters.should == [[:opt, :_1], [:opt, :_2]] end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "affects binding local variables" do -> { _1; binding.local_variables }.call("a").should == [:_1] -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "does not affect binding local variables" do -> { _1; binding.local_variables }.call("a").should == [] -> { _2; binding.local_variables }.call("a", "b").should == [] diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index d90e19858ae5e9..f2488615aaec37 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -687,7 +687,7 @@ def foo $VERBOSE = @verbose end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "can be assigned a String" do str = +"abc" $/ = str @@ -695,7 +695,7 @@ def foo end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "makes a new frozen String from the assigned String" do string_subclass = Class.new(String) str = string_subclass.new("abc") @@ -763,7 +763,7 @@ def foo $VERBOSE = @verbose end - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "can be assigned a String" do str = +"abc" $-0 = str @@ -771,7 +771,7 @@ def foo end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "makes a new frozen String from the assigned String" do string_subclass = Class.new(String) str = string_subclass.new("abc") diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index dbf341b19ea526..ce344b5b05f067 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -112,7 +112,7 @@ /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] end - ruby_bug "#13671", ""..."3.5" do # https://bugs.ruby-lang.org/issues/13671 + ruby_bug "#13671", ""..."4.0" do # https://bugs.ruby-lang.org/issues/13671 it "handles a lookbehind with ss characters" do r = Regexp.new("(? "application/x-www-form-urlencoded" }.inspect.delete("{}")) diff --git a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb index 0912e5a71f0e71..a09f9d5becec42 100644 --- a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb +++ b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb @@ -31,7 +31,7 @@ end describe "when a request body is set" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") request.body = "Some Content" @@ -64,7 +64,7 @@ end describe "when a body stream is set" do - ruby_version_is ""..."3.5" do + ruby_version_is ""..."4.0" do it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", "Content-Length" => "10") diff --git a/spec/ruby/library/stringscanner/check_spec.rb b/spec/ruby/library/stringscanner/check_spec.rb index 235f2f22e954fd..5e855e154ad4de 100644 --- a/spec/ruby/library/stringscanner/check_spec.rb +++ b/spec/ruby/library/stringscanner/check_spec.rb @@ -39,7 +39,6 @@ context "when #check was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.check("This") @@ -47,7 +46,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "raises IndexError when matching succeeded" do @s.check("This") @@ -68,7 +66,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) @@ -80,7 +77,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) diff --git a/spec/ruby/library/stringscanner/check_until_spec.rb b/spec/ruby/library/stringscanner/check_until_spec.rb index 701a703ebe8352..582da66b375a9f 100644 --- a/spec/ruby/library/stringscanner/check_until_spec.rb +++ b/spec/ruby/library/stringscanner/check_until_spec.rb @@ -35,7 +35,6 @@ end # https://github.com/ruby/strscan/issues/131 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" it "sets the last match result if given a String" do @s.check_until("a") @@ -45,7 +44,6 @@ @s.post_match.should == " test" end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "sets the last match result if given a String" do @@ -76,7 +74,6 @@ version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" context "when #check_until was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.check_until("This") @@ -84,7 +81,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do @s.check_until("This") @@ -105,7 +101,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) @@ -117,7 +112,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) diff --git a/spec/ruby/library/stringscanner/exist_spec.rb b/spec/ruby/library/stringscanner/exist_spec.rb index 3f40c7a5a5a763..a408fd0b8dc1c7 100644 --- a/spec/ruby/library/stringscanner/exist_spec.rb +++ b/spec/ruby/library/stringscanner/exist_spec.rb @@ -64,7 +64,6 @@ version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" context "when #exist? was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.exist?("This") @@ -72,7 +71,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do @s.exist?("This") @@ -93,7 +91,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) @@ -105,7 +102,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) diff --git a/spec/ruby/library/stringscanner/get_byte_spec.rb b/spec/ruby/library/stringscanner/get_byte_spec.rb index b3c2b7f678edd6..144859abc92a8c 100644 --- a/spec/ruby/library/stringscanner/get_byte_spec.rb +++ b/spec/ruby/library/stringscanner/get_byte_spec.rb @@ -32,7 +32,6 @@ describe "#[] successive call with a capture group name" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil" do s = StringScanner.new("This is a test") @@ -41,7 +40,6 @@ s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError" do s = StringScanner.new("This is a test") @@ -58,7 +56,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("This is a test") @@ -71,7 +68,6 @@ s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("This is a test") diff --git a/spec/ruby/library/stringscanner/getch_spec.rb b/spec/ruby/library/stringscanner/getch_spec.rb index c9c3eb6fd3ecce..d369391b140ce8 100644 --- a/spec/ruby/library/stringscanner/getch_spec.rb +++ b/spec/ruby/library/stringscanner/getch_spec.rb @@ -33,7 +33,6 @@ describe "#[] successive call with a capture group name" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil" do s = StringScanner.new("This is a test") @@ -42,7 +41,6 @@ s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError" do s = StringScanner.new("This is a test") @@ -59,7 +57,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("This is a test") @@ -73,7 +70,6 @@ s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("This is a test") diff --git a/spec/ruby/library/stringscanner/scan_byte_spec.rb b/spec/ruby/library/stringscanner/scan_byte_spec.rb index c60e22be4f508c..aa2decc8f747ba 100644 --- a/spec/ruby/library/stringscanner/scan_byte_spec.rb +++ b/spec/ruby/library/stringscanner/scan_byte_spec.rb @@ -43,7 +43,6 @@ describe "#[] successive call with a capture group name" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil" do s = StringScanner.new("abc") @@ -52,7 +51,6 @@ s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError" do s = StringScanner.new("abc") @@ -69,7 +67,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("abc") @@ -83,7 +80,6 @@ s[:a].should == nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do s = StringScanner.new("abc") diff --git a/spec/ruby/library/stringscanner/scan_integer_spec.rb b/spec/ruby/library/stringscanner/scan_integer_spec.rb index a0b3685bae1086..fe0d26f4049076 100644 --- a/spec/ruby/library/stringscanner/scan_integer_spec.rb +++ b/spec/ruby/library/stringscanner/scan_integer_spec.rb @@ -25,7 +25,7 @@ end # https://github.com/ruby/strscan/issues/130 - ruby_bug "", "3.4"..."3.5" do # introduced in strscan v3.1.1 + ruby_bug "", "3.4"..."4.0" do # introduced in strscan v3.1.1 it "sets the last match result" do s = StringScanner.new("42abc") s.scan_integer @@ -68,7 +68,6 @@ }.should raise_error(ArgumentError, "Unsupported integer base: 5, expected 10 or 16") end - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "does not match '0x' prefix on its own" do StringScanner.new("0x").scan_integer(base: 16).should == nil @@ -76,7 +75,6 @@ StringScanner.new("+0x").scan_integer(base: 16).should == nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "matches '0' in a '0x' that is followed by non-hex characters" do @@ -96,7 +94,6 @@ describe "#[] successive call with a capture group name" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil substring when matching succeeded" do s = StringScanner.new("42") @@ -105,7 +102,6 @@ s[:a].should == nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do s = StringScanner.new("42") @@ -131,7 +127,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "does not ignore the previous matching with Regexp" do s = StringScanner.new("42") @@ -145,7 +140,6 @@ s[:a].should == "42" end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "ignores the previous matching with Regexp" do s = StringScanner.new("42") diff --git a/spec/ruby/library/stringscanner/scan_until_spec.rb b/spec/ruby/library/stringscanner/scan_until_spec.rb index 737d83a14ca32d..610060d6f1ee25 100644 --- a/spec/ruby/library/stringscanner/scan_until_spec.rb +++ b/spec/ruby/library/stringscanner/scan_until_spec.rb @@ -41,7 +41,6 @@ end # https://github.com/ruby/strscan/issues/131 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" it "sets the last match result if given a String" do @s.scan_until("a") @@ -51,7 +50,6 @@ @s.post_match.should == " test" end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "sets the last match result if given a String" do @@ -82,7 +80,6 @@ version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" context "when #scan_until was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.scan_until("This") @@ -90,7 +87,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do @s.scan_until("This") @@ -111,7 +107,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) @@ -123,7 +118,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) diff --git a/spec/ruby/library/stringscanner/search_full_spec.rb b/spec/ruby/library/stringscanner/search_full_spec.rb index a089da2043b1ea..197adfda4d4519 100644 --- a/spec/ruby/library/stringscanner/search_full_spec.rb +++ b/spec/ruby/library/stringscanner/search_full_spec.rb @@ -50,7 +50,6 @@ end # https://github.com/ruby/strscan/issues/131 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" it "sets the last match result if given a String" do @s.search_full("is a", false, false) @@ -60,7 +59,6 @@ @s.post_match.should == " test" end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "sets the last match result if given a String" do @@ -91,7 +89,6 @@ version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" context "when #search_full was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.search_full("This", false, false) @@ -99,7 +96,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do @s.search_full("This", false, false) diff --git a/spec/ruby/library/stringscanner/skip_until_spec.rb b/spec/ruby/library/stringscanner/skip_until_spec.rb index f5be4b5ceb0a15..5d73d8f0b91104 100644 --- a/spec/ruby/library/stringscanner/skip_until_spec.rb +++ b/spec/ruby/library/stringscanner/skip_until_spec.rb @@ -38,7 +38,6 @@ end # https://github.com/ruby/strscan/issues/131 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" it "sets the last match result if given a String" do @s.skip_until("a") @@ -48,7 +47,6 @@ @s.post_match.should == " test" end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "sets the last match result if given a String" do @@ -79,7 +77,6 @@ version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" context "when #skip_until was called with a String pattern" do # https://github.com/ruby/strscan/issues/139 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "returns nil when matching succeeded" do @s.skip_until("This") @@ -87,7 +84,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" it "raises IndexError when matching succeeded" do @s.skip_until("This") @@ -108,7 +104,6 @@ end # https://github.com/ruby/strscan/issues/135 - ruby_version_is ""..."3.5" do # Don't run on 3.5.0dev that already contains not released fixes version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) @@ -120,7 +115,6 @@ @s[:a].should be_nil end end - end version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" it "ignores the previous matching with Regexp" do @s.exist?(/(?This)/) diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h index 8aaec36f465818..6c4bea5da0e124 100644 --- a/spec/ruby/optional/capi/ext/rubyspec.h +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -35,8 +35,8 @@ (RUBY_API_VERSION_MAJOR == (major) && RUBY_API_VERSION_MINOR < (minor))) #define RUBY_VERSION_SINCE(major,minor) (!RUBY_VERSION_BEFORE(major, minor)) -#if RUBY_VERSION_SINCE(3, 5) -#define RUBY_VERSION_IS_3_5 +#if RUBY_VERSION_SINCE(4, 0) +#define RUBY_VERSION_IS_4_0 #endif #if RUBY_VERSION_SINCE(3, 4) diff --git a/spec/ruby/optional/capi/ext/set_spec.c b/spec/ruby/optional/capi/ext/set_spec.c index 7af922fd49ea96..11a271b361ba6b 100644 --- a/spec/ruby/optional/capi/ext/set_spec.c +++ b/spec/ruby/optional/capi/ext/set_spec.c @@ -1,7 +1,7 @@ #include "ruby.h" #include "rubyspec.h" -#ifdef RUBY_VERSION_IS_3_5 +#ifdef RUBY_VERSION_IS_4_0 #ifdef __cplusplus extern "C" { #endif diff --git a/spec/ruby/optional/capi/ext/thread_spec.c b/spec/ruby/optional/capi/ext/thread_spec.c index 6ee111b7b7ea72..ac77e4e813b517 100644 --- a/spec/ruby/optional/capi/ext/thread_spec.c +++ b/spec/ruby/optional/capi/ext/thread_spec.c @@ -166,7 +166,7 @@ static VALUE thread_spec_ruby_native_thread_p_new_thread(VALUE self) { #endif } -#ifdef RUBY_VERSION_IS_3_5 +#ifdef RUBY_VERSION_IS_4_0 static VALUE thread_spec_ruby_thread_has_gvl_p(VALUE self) { return ruby_thread_has_gvl_p() ? Qtrue : Qfalse; } @@ -185,7 +185,7 @@ void Init_thread_spec(void) { rb_define_method(cls, "rb_thread_create", thread_spec_rb_thread_create, 2); rb_define_method(cls, "ruby_native_thread_p", thread_spec_ruby_native_thread_p, 0); rb_define_method(cls, "ruby_native_thread_p_new_thread", thread_spec_ruby_native_thread_p_new_thread, 0); -#ifdef RUBY_VERSION_IS_3_5 +#ifdef RUBY_VERSION_IS_4_0 rb_define_method(cls, "ruby_thread_has_gvl_p", thread_spec_ruby_thread_has_gvl_p, 0); #endif } diff --git a/spec/ruby/optional/capi/set_spec.rb b/spec/ruby/optional/capi/set_spec.rb index 3b7ee812c56ade..3e35be0505fffa 100644 --- a/spec/ruby/optional/capi/set_spec.rb +++ b/spec/ruby/optional/capi/set_spec.rb @@ -1,6 +1,6 @@ require_relative 'spec_helper' -ruby_version_is "3.5" do +ruby_version_is "4.0" do load_extension("set") describe "C-API Set function" do diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 605c43769ddb0b..72f20ee6a52455 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -193,7 +193,7 @@ def inspect it "returns a new String object filled with \\0 bytes" do lens = [4] - ruby_version_is "3.5" do + ruby_version_is "4.0" do lens << 100 end @@ -1230,7 +1230,7 @@ def inspect -> { str.upcase! }.should raise_error(RuntimeError, 'can\'t modify string; temporarily locked') end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "raises FrozenError if string is frozen" do str = -"rb_str_locktmp" -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) @@ -1254,7 +1254,7 @@ def inspect -> { @s.rb_str_unlocktmp(+"test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "raises FrozenError if string is frozen" do str = -"rb_str_locktmp" -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) diff --git a/spec/ruby/optional/capi/thread_spec.rb b/spec/ruby/optional/capi/thread_spec.rb index cd9ae8ff1923bb..117726f0e2a392 100644 --- a/spec/ruby/optional/capi/thread_spec.rb +++ b/spec/ruby/optional/capi/thread_spec.rb @@ -185,7 +185,7 @@ def call_capi_rb_thread_wakeup end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do describe "ruby_thread_has_gvl_p" do it "returns true if the current thread has the GVL" do @t.ruby_thread_has_gvl_p.should be_true diff --git a/spec/ruby/security/cve_2020_10663_spec.rb b/spec/ruby/security/cve_2020_10663_spec.rb index 80e860348b10ad..c44a13a0dd4b5d 100644 --- a/spec/ruby/security/cve_2020_10663_spec.rb +++ b/spec/ruby/security/cve_2020_10663_spec.rb @@ -1,6 +1,6 @@ require_relative '../spec_helper' -ruby_version_is ""..."3.5" do +ruby_version_is ""..."4.0" do require 'json' module JSONSpecs diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 8432c835946d6e..2be06ea797aa6d 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -141,7 +141,7 @@ def e.exception end end - ruby_version_is "3.5" do + ruby_version_is "4.0" do it "allows cause keyword argument" do cause = StandardError.new("original error") result = nil @@ -272,7 +272,7 @@ def e.exception end describe :kernel_raise_across_contexts, shared: true do - ruby_version_is "3.5" do + ruby_version_is "4.0" do describe "with cause keyword argument" do it "uses the cause from the calling context" do original_cause = nil From d487e396bdeb33d0069bab2475103aa9b7109607 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 18 Nov 2025 18:47:31 -0500 Subject: [PATCH 1239/2435] ZJIT: [DOC] Comment copy-editing --- zjit/src/hir.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 982400db5030cd..8e6241f0dafe0f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1485,8 +1485,7 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq return false; } - // Check argument count against callee's parameters. Note that correctness for this calculation - // relies on rejecting features above. + // Because we exclude e.g. post parameters above, they are also excluded from the sum below. let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; can_send = c_int::try_from(args.len()) @@ -4545,8 +4544,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let jit_entry_insns = jit_entry_insns(iseq); let BytecodeInfo { jump_targets, has_blockiseq } = compute_bytecode_info(iseq, &jit_entry_insns); - // Make all empty basic blocks. The ordering of the BBs matters as it is taken as a schedule - // in the backend without a scheduling pass. TODO: Higher quality scheduling during lowering. + // Make all empty basic blocks. The ordering of the BBs matters for getting fallthrough jumps + // in good places, but it's not necessary for correctness. TODO: Higher quality scheduling during lowering. let mut insn_idx_to_block = HashMap::new(); // Make blocks for optionals first, and put them right next to their JIT entrypoint for insn_idx in jit_entry_insns.iter().copied() { From 63a6290ce0bf1a7145c545632b22a5dfa170ea6a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 19 Nov 2025 22:03:11 +0000 Subject: [PATCH 1240/2435] [DOC] Update yjit.md to use a different email --- doc/yjit/yjit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index 73db7237109322..24aa163e60d299 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -14,7 +14,7 @@ This project is open source and falls under the same license as CRuby.

If you're using YJIT in production, please - share your success stories with us! + share your success stories with us!

If you wish to learn more about the approach taken, here are some conference talks and publications: From 2ed287da177ea792e0673eaf7764cc7ca1fca8a1 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Wed, 19 Nov 2025 18:00:44 -0500 Subject: [PATCH 1241/2435] ZJIT: Add Iongraph compatibility (#14999) ## Components This PR adds functionality to visualize HIR using the [Iongraph](https://spidermonkey.dev/blog/2025/10/28/iongraph-web.html) tool first created for use with Spidermonkey. ## Justification Iongraph's viewer is (as mentioned in the article above) a few notches above graphviz for viewing large CFGs. It also allows easily inspecting different compiler optimization passes and multiple functions in the same browser window. Since Spidermonkey is using this format, it may be beneficial to use it for our own JIT development. The requirement for JSON is downstream from that of the Iongraph format. As for writing the implementation myself, ZJIT leans towards having fewer dependencies, so this is the preferred approach. ## How does it look? image image Right now, it's aesthetically minimal, but is fairly robust. ## Functionality Using `--zjit-dump-hir-iongraph` will dump all compiled functions into a directory named `/tmp/zjit-iongraph-{PROCESS_PID}`. Each file will be named `func_{ZJIT_FUNC_NAME}.json`. In order to use them in the Iongraph viewer, you'll need to use `jq` to collate them to a single file. An example invocation of `jq` is shown below for reference. The name of the file created does not matter to my understanding. `jq --slurp --null-input '.functions=inputs | .version=2' /tmp/zjit-iongraph-{PROCESS_PID}/func*.json > ~/Downloads/foo.json` From there, you can use https://mozilla-spidermonkey.github.io/iongraph/ to view your trace. ### Caveats - The upstream Iongraph viewer doesn't allow you to click arguments to an instruction to find the instruction that they originate from when using the format that this PR generates. (I have made a small fork at https://github.com/aidenfoxivey/iongraph that fixes that functionality via https://github.com/aidenfoxivey/iongraph/commit/9e9c29b41c4dbb35cf66cb6161e5b19c8b796379.patch) - The upstream Iongraph viewer can sometimes show "exiting edges" in the CFG as being not attached to the box representing its basic block. image (Image courtesy of @tekknolagi) This is because the original tool was (to our understanding) written for an SSA format that does not use extended basic blocks. (Extended basic blocks let you put a jump instruction, conditional or otherwise, anywhere in the basic block.) This means that our format may generate more outgoing edges than the viewer is written to handle. --- doc/zjit.md | 8 + zjit/src/hir.rs | 466 +++++++++++++++++++++++- zjit/src/hir/tests.rs | 818 ++++++++++++++++++++++++++++++++++++++++++ zjit/src/options.rs | 6 + 4 files changed, 1281 insertions(+), 17 deletions(-) diff --git a/doc/zjit.md b/doc/zjit.md index 3d7ee33abfa438..bb20b9f6924bac 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -162,6 +162,14 @@ A file called `zjit_exits_{pid}.dump` will be created in the same directory as ` stackprof path/to/zjit_exits_{pid}.dump ``` +### Viewing HIR in Iongraph + +Using `--zjit-dump-hir-iongraph` will dump all compiled functions into a directory named `/tmp/zjit-iongraph-{PROCESS_PID}`. Each file will be named `func_{ZJIT_FUNC_NAME}.json`. In order to use them in the Iongraph viewer, you'll need to use `jq` to collate them to a single file. An example invocation of `jq` is shown below for reference. + +`jq --slurp --null-input '.functions=inputs | .version=2' /tmp/zjit-iongraph-{PROCESS_PID}/func*.json > ~/Downloads/ion.json` + +From there, you can use https://mozilla-spidermonkey.github.io/iongraph/ to view your trace. + ### Printing ZJIT Errors `--zjit-debug` prints ZJIT compilation errors and other diagnostics: diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8e6241f0dafe0f..2640507e33fab5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6,7 +6,7 @@ #![allow(clippy::if_same_then_else)] #![allow(clippy::match_like_matches_macro)] use crate::{ - cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState + cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json }; use std::{ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter @@ -39,7 +39,7 @@ impl std::fmt::Display for InsnId { } /// The index of a [`Block`], which effectively acts like a pointer. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] pub struct BlockId(pub usize); impl From for usize { @@ -3686,23 +3686,171 @@ impl Function { } } + /// Helper function to make an Iongraph JSON "instruction". + /// `uses`, `memInputs` and `attributes` are left empty for now, but may be populated + /// in the future. + fn make_iongraph_instr(id: InsnId, inputs: Vec, opcode: &str, ty: &str) -> Json { + Json::object() + // Add an offset of 0x1000 to avoid the `ptr` being 0x0, which iongraph rejects. + .insert("ptr", id.0 + 0x1000) + .insert("id", id.0) + .insert("opcode", opcode) + .insert("attributes", Json::empty_array()) + .insert("inputs", Json::Array(inputs)) + .insert("uses", Json::empty_array()) + .insert("memInputs", Json::empty_array()) + .insert("type", ty) + .build() + } + + /// Helper function to make an Iongraph JSON "block". + fn make_iongraph_block(id: BlockId, predecessors: Vec, successors: Vec, instructions: Vec, attributes: Vec<&str>, loop_depth: u32) -> Json { + Json::object() + // Add an offset of 0x1000 to avoid the `ptr` being 0x0, which iongraph rejects. + .insert("ptr", id.0 + 0x1000) + .insert("id", id.0) + .insert("loopDepth", loop_depth) + .insert("attributes", Json::array(attributes)) + .insert("predecessors", Json::array(predecessors.iter().map(|x| x.0).collect::>())) + .insert("successors", Json::array(successors.iter().map(|x| x.0).collect::>())) + .insert("instructions", Json::array(instructions)) + .build() + } + + /// Helper function to make an Iongraph JSON "function". + /// Note that `lir` is unpopulated right now as ZJIT doesn't use its functionality. + fn make_iongraph_function(pass_name: &str, hir_blocks: Vec) -> Json { + Json::object() + .insert("name", pass_name) + .insert("mir", Json::object() + .insert("blocks", Json::array(hir_blocks)) + .build() + ) + .insert("lir", Json::object() + .insert("blocks", Json::empty_array()) + .build() + ) + .build() + } + + /// Generate an iongraph JSON pass representation for this function. + pub fn to_iongraph_pass(&self, pass_name: &str) -> Json { + let mut ptr_map = PtrPrintMap::identity(); + if cfg!(test) { + ptr_map.map_ptrs = true; + } + + let mut hir_blocks = Vec::new(); + let cfi = ControlFlowInfo::new(self); + let dominators = Dominators::new(self); + let loop_info = LoopInfo::new(&cfi, &dominators); + + // Push each block from the iteration in reverse post order to `hir_blocks`. + for block_id in self.rpo() { + // Create the block with instructions. + let block = &self.blocks[block_id.0]; + let predecessors = cfi.predecessors(block_id).collect(); + let successors = cfi.successors(block_id).collect(); + let mut instructions = Vec::new(); + + // Process all instructions (parameters and body instructions). + // Parameters are currently guaranteed to be Parameter instructions, but in the future + // they might be refined to other instruction kinds by the optimizer. + for insn_id in block.params.iter().chain(block.insns.iter()) { + let insn_id = self.union_find.borrow().find_const(*insn_id); + let insn = self.find(insn_id); + + // Snapshots are not serialized, so skip them. + if matches!(insn, Insn::Snapshot {..}) { + continue; + } + + // Instructions with no output or an empty type should have an empty type field. + let type_str = if insn.has_output() { + let insn_type = self.type_of(insn_id); + if insn_type.is_subtype(types::Empty) { + String::new() + } else { + insn_type.print(&ptr_map).to_string() + } + } else { + String::new() + }; + + + let opcode = insn.print(&ptr_map).to_string(); + + // Traverse the worklist to get inputs for a given instruction. + let mut inputs = VecDeque::new(); + self.worklist_traverse_single_insn(&insn, &mut inputs); + let inputs: Vec = inputs.into_iter().map(|x| x.0.into()).collect(); + + instructions.push( + Self::make_iongraph_instr( + insn_id, + inputs, + &opcode, + &type_str + ) + ); + } + + let mut attributes = vec![]; + if loop_info.is_back_edge_source(block_id) { + attributes.push("backedge"); + } + if loop_info.is_loop_header(block_id) { + attributes.push("loopheader"); + } + let loop_depth = loop_info.loop_depth(block_id); + + hir_blocks.push(Self::make_iongraph_block( + block_id, + predecessors, + successors, + instructions, + attributes, + loop_depth, + )); + } + + Self::make_iongraph_function(pass_name, hir_blocks) + } + /// Run all the optimization passes we have. pub fn optimize(&mut self) { + let mut passes: Vec = Vec::new(); + let should_dump = get_option!(dump_hir_iongraph); + + macro_rules! run_pass { + ($name:ident) => { + self.$name(); + #[cfg(debug_assertions)] self.assert_validates(); + if should_dump { + passes.push( + self.to_iongraph_pass(stringify!($name)) + ); + } + } + } + + if should_dump { + passes.push(self.to_iongraph_pass("unoptimized")); + } + // Function is assumed to have types inferred already - self.type_specialize(); - #[cfg(debug_assertions)] self.assert_validates(); - self.inline(); - #[cfg(debug_assertions)] self.assert_validates(); - self.optimize_getivar(); - #[cfg(debug_assertions)] self.assert_validates(); - self.optimize_c_calls(); - #[cfg(debug_assertions)] self.assert_validates(); - self.fold_constants(); - #[cfg(debug_assertions)] self.assert_validates(); - self.clean_cfg(); - #[cfg(debug_assertions)] self.assert_validates(); - self.eliminate_dead_code(); - #[cfg(debug_assertions)] self.assert_validates(); + run_pass!(type_specialize); + run_pass!(inline); + run_pass!(optimize_getivar); + run_pass!(optimize_c_calls); + run_pass!(fold_constants); + run_pass!(clean_cfg); + run_pass!(eliminate_dead_code); + + if should_dump { + let iseq_name = iseq_get_location(self.iseq, 0); + self.dump_iongraph(&iseq_name, passes); + } } /// Dump HIR passed to codegen if specified by options. @@ -3723,6 +3871,32 @@ impl Function { } } + pub fn dump_iongraph(&self, function_name: &str, passes: Vec) { + fn sanitize_for_filename(name: &str) -> String { + name.chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '_' || c == '-' { + c + } else { + '_' + } + }) + .collect() + } + + use std::io::Write; + let dir = format!("/tmp/zjit-iongraph-{}", std::process::id()); + std::fs::create_dir_all(&dir).expect("Unable to create directory."); + let sanitized = sanitize_for_filename(function_name); + let path = format!("{dir}/func_{sanitized}.json"); + let mut file = std::fs::File::create(path).unwrap(); + let json = Json::object() + .insert("name", function_name) + .insert("passes", passes) + .build(); + writeln!(file, "{}", json).unwrap(); + } + /// Validates the following: /// 1. Basic block jump args match parameter arity. /// 2. Every terminator must be in the last position. @@ -4087,7 +4261,13 @@ impl Function { impl<'a> std::fmt::Display for FunctionPrinter<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let fun = &self.fun; - let iseq_name = iseq_get_location(fun.iseq, 0); + // In tests, there may not be an iseq to get location from. + let iseq_name = if fun.iseq.is_null() { + String::from("") + } else { + iseq_get_location(fun.iseq, 0) + }; + // In tests, strip the line number for builtin ISEQs to make tests stable across line changes let iseq_name = if cfg!(test) && iseq_name.contains("@ { + f: &'a Function, + dominators: Vec>, +} + +impl<'a> Dominators<'a> { + pub fn new(f: &'a Function) -> Self { + let mut cfi = ControlFlowInfo::new(f); + Self::with_cfi(f, &mut cfi) + } + + pub fn with_cfi(f: &'a Function, cfi: &mut ControlFlowInfo) -> Self { + let block_ids = f.rpo(); + let mut dominators = vec![vec![]; f.blocks.len()]; + + // Compute dominators for each node using fixed point iteration. + // Approach can be found in Figure 1 of: + // https://www.cs.tufts.edu/~nr/cs257/archive/keith-cooper/dom14.pdf + // + // Initially we set: + // + // dom(entry) = {entry} for each entry block + // dom(b != entry) = {all nodes} + // + // Iteratively, apply: + // + // dom(b) = {b} union intersect(dom(p) for p in predecessors(b)) + // + // When we've run the algorithm and the dominator set no longer changes + // between iterations, then we have found the dominator sets. + + // Set up entry blocks. + // Entry blocks are only dominated by themselves. + for entry_block in &f.entry_blocks() { + dominators[entry_block.0] = vec![*entry_block]; + } + + // Setup the initial dominator sets. + for block_id in &block_ids { + if !f.entry_blocks().contains(block_id) { + // Non entry blocks are initially dominated by all other blocks. + dominators[block_id.0] = block_ids.clone(); + } + } + + let mut changed = true; + while changed { + changed = false; + + for block_id in &block_ids { + if *block_id == f.entry_block { + continue; + } + + // Get all predecessors for a given block. + let block_preds: Vec = cfi.predecessors(*block_id).collect(); + if block_preds.is_empty() { + continue; + } + + let mut new_doms = dominators[block_preds[0].0].clone(); + + // Compute the intersection of predecessor dominator sets into `new_doms`. + for pred_id in &block_preds[1..] { + let pred_doms = &dominators[pred_id.0]; + // Only keep a dominator in `new_doms` if it is also found in pred_doms + new_doms.retain(|d| pred_doms.contains(d)); + } + + // Insert sorted into `new_doms`. + match new_doms.binary_search(block_id) { + Ok(_) => {} + Err(pos) => new_doms.insert(pos, *block_id) + } + + // If we have computed a new dominator set, then we can update + // the dominators and mark that we need another iteration. + if dominators[block_id.0] != new_doms { + dominators[block_id.0] = new_doms; + changed = true; + } + } + } + + Self { f, dominators } + } + + + pub fn is_dominated_by(&self, left: BlockId, right: BlockId) -> bool { + self.dominators(left).any(|&b| b == right) + } + + pub fn dominators(&self, block: BlockId) -> Iter<'_, BlockId> { + self.dominators[block.0].iter() + } +} + +pub struct ControlFlowInfo<'a> { + function: &'a Function, + successor_map: HashMap>, + predecessor_map: HashMap>, +} + +impl<'a> ControlFlowInfo<'a> { + pub fn new(function: &'a Function) -> Self { + let mut successor_map: HashMap> = HashMap::new(); + let mut predecessor_map: HashMap> = HashMap::new(); + let uf = function.union_find.borrow(); + + for block_id in function.rpo() { + let block = &function.blocks[block_id.0]; + + // Since ZJIT uses extended basic blocks, one must check all instructions + // for their ability to jump to another basic block, rather than just + // the instructions at the end of a given basic block. + let successors: Vec = block + .insns + .iter() + .map(|&insn_id| uf.find_const(insn_id)) + .filter_map(|insn_id| { + Self::extract_jump_target(&function.insns[insn_id.0]) + }) + .collect(); + + // Update predecessors for successor blocks. + for &succ_id in &successors { + predecessor_map + .entry(succ_id) + .or_default() + .push(block_id); + } + + // Store successors for this block. + successor_map.insert(block_id, successors); + } + + Self { + function, + successor_map, + predecessor_map, + } + } + + pub fn is_succeeded_by(&self, left: BlockId, right: BlockId) -> bool { + self.successor_map.get(&right).is_some_and(|set| set.contains(&left)) + } + + pub fn is_preceded_by(&self, left: BlockId, right: BlockId) -> bool { + self.predecessor_map.get(&right).is_some_and(|set| set.contains(&left)) + } + + pub fn predecessors(&self, block: BlockId) -> impl Iterator { + self.predecessor_map.get(&block).into_iter().flatten().copied() + } + + pub fn successors(&self, block: BlockId) -> impl Iterator { + self.successor_map.get(&block).into_iter().flatten().copied() + } + + /// Helper function to extract the target of a jump instruction. + fn extract_jump_target(insn: &Insn) -> Option { + match insn { + Insn::Jump(target) + | Insn::IfTrue { target, .. } + | Insn::IfFalse { target, .. } => Some(target.target), + _ => None, + } + } +} + +pub struct LoopInfo<'a> { + cfi: &'a ControlFlowInfo<'a>, + dominators: &'a Dominators<'a>, + loop_depths: HashMap, + loop_headers: BlockSet, + back_edge_sources: BlockSet, +} + +impl<'a> LoopInfo<'a> { + pub fn new(cfi: &'a ControlFlowInfo<'a>, dominators: &'a Dominators<'a>) -> Self { + let mut loop_headers: BlockSet = BlockSet::with_capacity(cfi.function.num_blocks()); + let mut loop_depths: HashMap = HashMap::new(); + let mut back_edge_sources: BlockSet = BlockSet::with_capacity(cfi.function.num_blocks()); + let rpo = cfi.function.rpo(); + + for &block in &rpo { + loop_depths.insert(block, 0); + } + + // Collect loop headers. + for &block in &rpo { + // Initialize the loop depths. + for predecessor in cfi.predecessors(block) { + if dominators.is_dominated_by(predecessor, block) { + // Found a loop header, so then identify the natural loop. + loop_headers.insert(block); + back_edge_sources.insert(predecessor); + let loop_blocks = Self::find_natural_loop(cfi, block, predecessor); + // Increment the loop depth. + for loop_block in &loop_blocks { + *loop_depths.get_mut(loop_block).expect("Loop block should be populated.") += 1; + } + } + } + } + + Self { + cfi, + dominators, + loop_depths, + loop_headers, + back_edge_sources, + } + } + + fn find_natural_loop( + cfi: &ControlFlowInfo, + header: BlockId, + back_edge_source: BlockId, + ) -> HashSet { + // todo(aidenfoxivey): Reimplement using BlockSet + let mut loop_blocks = HashSet::new(); + let mut stack = vec![back_edge_source]; + + loop_blocks.insert(header); + loop_blocks.insert(back_edge_source); + + while let Some(block) = stack.pop() { + for pred in cfi.predecessors(block) { + // Pushes to stack only if `pred` wasn't already in `loop_blocks`. + if loop_blocks.insert(pred) { + stack.push(pred) + } + } + } + + loop_blocks + } + + pub fn loop_depth(&self, block: BlockId) -> u32 { + self.loop_depths.get(&block).copied().unwrap_or(0) + } + + pub fn is_back_edge_source(&self, block: BlockId) -> bool { + self.back_edge_sources.get(block) + } + + pub fn is_loop_header(&self, block: BlockId) -> bool { + self.loop_headers.get(block) + } +} + #[cfg(test)] mod union_find_tests { use super::UnionFind; diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index abf2f9497c2875..a00ca97e85a8b0 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3425,3 +3425,821 @@ pub mod hir_build_tests { "); } } + + /// Test successor and predecessor set computations. + #[cfg(test)] + mod control_flow_info_tests { + use super::*; + + fn edge(target: BlockId) -> BranchEdge { + BranchEdge { target, args: vec![] } + } + + #[test] + fn test_linked_list() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb1))); + function.push_insn(bb1, Insn::Jump(edge(bb2))); + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb3, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + + assert!(cfi.is_preceded_by(bb1, bb2)); + assert!(cfi.is_succeeded_by(bb2, bb1)); + assert!(cfi.predecessors(bb3).eq([bb2])); + } + + #[test] + fn test_diamond() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + + let v1 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::IfTrue { val: v1, target: edge(bb2)}); + function.push_insn(bb0, Insn::Jump(edge(bb1))); + function.push_insn(bb1, Insn::Jump(edge(bb3))); + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb3, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + + assert!(cfi.is_preceded_by(bb2, bb3)); + assert!(cfi.is_preceded_by(bb1, bb3)); + assert!(!cfi.is_preceded_by(bb0, bb3)); + assert!(cfi.is_succeeded_by(bb1, bb0)); + assert!(cfi.is_succeeded_by(bb3, bb1)); + } + } + + /// Test dominator set computations. + #[cfg(test)] + mod dom_tests { + use super::*; + use insta::assert_snapshot; + + fn edge(target: BlockId) -> BranchEdge { + BranchEdge { target, args: vec![] } + } + + fn assert_dominators_contains_self(function: &Function, dominators: &Dominators) { + for (i, _) in function.blocks.iter().enumerate() { + // Ensure that each dominating set contains the block itself. + assert!(dominators.is_dominated_by(BlockId(i), BlockId(i))); + } + } + + #[test] + fn test_linked_list() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb1))); + function.push_insn(bb1, Insn::Jump(edge(bb2))); + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb3, Insn::Return { val: retval }); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb1() + bb1(): + Jump bb2() + bb2(): + Jump bb3() + bb3(): + v3:Any = Const CBool(true) + Return v3 + "); + + let dominators = Dominators::new(&function); + assert_dominators_contains_self(&function, &dominators); + assert!(dominators.dominators(bb0).eq([bb0].iter())); + assert!(dominators.dominators(bb1).eq([bb0, bb1].iter())); + assert!(dominators.dominators(bb2).eq([bb0, bb1, bb2].iter())); + assert!(dominators.dominators(bb3).eq([bb0, bb1, bb2, bb3].iter())); + } + + #[test] + fn test_diamond() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + + let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::IfTrue { val, target: edge(bb1)}); + function.push_insn(bb0, Insn::Jump(edge(bb2))); + + function.push_insn(bb2, Insn::Jump(edge(bb3))); + function.push_insn(bb1, Insn::Jump(edge(bb3))); + + let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb3, Insn::Return { val: retval }); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + v0:Any = Const Value(false) + IfTrue v0, bb1() + Jump bb2() + bb1(): + Jump bb3() + bb2(): + Jump bb3() + bb3(): + v5:Any = Const CBool(true) + Return v5 + "); + + let dominators = Dominators::new(&function); + assert_dominators_contains_self(&function, &dominators); + assert!(dominators.dominators(bb0).eq([bb0].iter())); + assert!(dominators.dominators(bb1).eq([bb0, bb1].iter())); + assert!(dominators.dominators(bb2).eq([bb0, bb2].iter())); + assert!(dominators.dominators(bb3).eq([bb0, bb3].iter())); + } + + #[test] + fn test_complex_cfg() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + let bb4 = function.new_block(0); + let bb5 = function.new_block(0); + let bb6 = function.new_block(0); + let bb7 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb1))); + + let v0 = function.push_insn(bb1, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb1, Insn::IfTrue { val: v0, target: edge(bb2)}); + function.push_insn(bb1, Insn::Jump(edge(bb4))); + + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + let v1 = function.push_insn(bb3, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb3, Insn::IfTrue { val: v1, target: edge(bb5)}); + function.push_insn(bb3, Insn::Jump(edge(bb7))); + + function.push_insn(bb4, Insn::Jump(edge(bb5))); + + function.push_insn(bb5, Insn::Jump(edge(bb6))); + + function.push_insn(bb6, Insn::Jump(edge(bb7))); + + let retval = function.push_insn(bb7, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb7, Insn::Return { val: retval }); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb1() + bb1(): + v1:Any = Const Value(false) + IfTrue v1, bb2() + Jump bb4() + bb2(): + Jump bb3() + bb3(): + v5:Any = Const Value(false) + IfTrue v5, bb5() + Jump bb7() + bb4(): + Jump bb5() + bb5(): + Jump bb6() + bb6(): + Jump bb7() + bb7(): + v11:Any = Const CBool(true) + Return v11 + "); + + let dominators = Dominators::new(&function); + assert_dominators_contains_self(&function, &dominators); + assert!(dominators.dominators(bb0).eq([bb0].iter())); + assert!(dominators.dominators(bb1).eq([bb0, bb1].iter())); + assert!(dominators.dominators(bb2).eq([bb0, bb1, bb2].iter())); + assert!(dominators.dominators(bb3).eq([bb0, bb1, bb2, bb3].iter())); + assert!(dominators.dominators(bb4).eq([bb0, bb1, bb4].iter())); + assert!(dominators.dominators(bb5).eq([bb0, bb1, bb5].iter())); + assert!(dominators.dominators(bb6).eq([bb0, bb1, bb5, bb6].iter())); + assert!(dominators.dominators(bb7).eq([bb0, bb1, bb7].iter())); + } + + #[test] + fn test_back_edges() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + let bb4 = function.new_block(0); + let bb5 = function.new_block(0); + + let v0 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::IfTrue { val: v0, target: edge(bb1)}); + function.push_insn(bb0, Insn::Jump(edge(bb4))); + + let v1 = function.push_insn(bb1, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb1, Insn::IfTrue { val: v1, target: edge(bb2)}); + function.push_insn(bb1, Insn::Jump(edge(bb3))); + + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + function.push_insn(bb4, Insn::Jump(edge(bb5))); + + let v2 = function.push_insn(bb5, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb5, Insn::IfTrue { val: v2, target: edge(bb3)}); + function.push_insn(bb5, Insn::Jump(edge(bb4))); + + let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb3, Insn::Return { val: retval }); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + v0:Any = Const Value(false) + IfTrue v0, bb1() + Jump bb4() + bb1(): + v3:Any = Const Value(false) + IfTrue v3, bb2() + Jump bb3() + bb2(): + Jump bb3() + bb4(): + Jump bb5() + bb5(): + v8:Any = Const Value(false) + IfTrue v8, bb3() + Jump bb4() + bb3(): + v11:Any = Const CBool(true) + Return v11 + "); + + let dominators = Dominators::new(&function); + assert_dominators_contains_self(&function, &dominators); + assert!(dominators.dominators(bb0).eq([bb0].iter())); + assert!(dominators.dominators(bb1).eq([bb0, bb1].iter())); + assert!(dominators.dominators(bb2).eq([bb0, bb1, bb2].iter())); + assert!(dominators.dominators(bb3).eq([bb0, bb3].iter())); + assert!(dominators.dominators(bb4).eq([bb0, bb4].iter())); + assert!(dominators.dominators(bb5).eq([bb0, bb4, bb5].iter())); + } + + #[test] + fn test_multiple_entry_blocks() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + function.jit_entry_blocks.push(bb1); + let bb2 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb2))); + + function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb2, Insn::Return { val: retval }); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb2() + bb1(): + Jump bb2() + bb2(): + v2:Any = Const CBool(true) + Return v2 + "); + + let dominators = Dominators::new(&function); + assert_dominators_contains_self(&function, &dominators); + + assert!(dominators.dominators(bb1).eq([bb1].iter())); + assert!(dominators.dominators(bb2).eq([bb2].iter())); + + assert!(!dominators.is_dominated_by(bb1, bb2)); + } + } + + /// Test loop information computation. +#[cfg(test)] +mod loop_info_tests { + use super::*; + use insta::assert_snapshot; + + fn edge(target: BlockId) -> BranchEdge { + BranchEdge { target, args: vec![] } + } + + #[test] + fn test_loop_depth() { + // ┌─────┐ + // │ bb0 │ + // └──┬──┘ + // │ + // ┌──▼──┐ ┌─────┐ + // │ bb2 ◄──────┼ bb1 ◄─┐ + // └──┬──┘ └─────┘ │ + // └─────────────────┘ + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb2))); + + let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb2, Insn::IfTrue { val, target: edge(bb1)}); + let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) }); + let _ = function.push_insn(bb2, Insn::Return { val: retval }); + + function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let cfi = ControlFlowInfo::new(&function); + let dominators = Dominators::new(&function); + let loop_info = LoopInfo::new(&cfi, &dominators); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb2() + v1:Any = Const Value(false) + bb2(): + IfTrue v1, bb1() + v3:Any = Const CBool(true) + Return v3 + bb1(): + Jump bb2() + "); + + assert!(loop_info.is_loop_header(bb2)); + assert!(loop_info.is_back_edge_source(bb1)); + assert_eq!(loop_info.loop_depth(bb1), 1); + } + + #[test] + fn test_nested_loops() { + // ┌─────┐ + // │ bb0 ◄─────┐ + // └──┬──┘ │ + // │ │ + // ┌──▼──┐ │ + // │ bb1 ◄───┐ │ + // └──┬──┘ │ │ + // │ │ │ + // ┌──▼──┐ │ │ + // │ bb2 ┼───┘ │ + // └──┬──┘ │ + // │ │ + // ┌──▼──┐ │ + // │ bb3 ┼─────┘ + // └──┬──┘ + // │ + // ┌──▼──┐ + // │ bb4 │ + // └─────┘ + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + let bb4 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb1))); + + function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let cond = function.push_insn(bb2, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb2, Insn::IfTrue { val: cond, target: edge(bb1) }); + function.push_insn(bb2, Insn::Jump(edge(bb3))); + + let cond = function.push_insn(bb3, Insn::Const { val: Const::Value(Qtrue) }); + let _ = function.push_insn(bb3, Insn::IfTrue { val: cond, target: edge(bb0) }); + function.push_insn(bb3, Insn::Jump(edge(bb4))); + + let retval = function.push_insn(bb4, Insn::Const { val: Const::CBool(true) }); + let _ = function.push_insn(bb4, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + let dominators = Dominators::new(&function); + let loop_info = LoopInfo::new(&cfi, &dominators); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb1() + bb1(): + Jump bb2() + bb2(): + v2:Any = Const Value(false) + IfTrue v2, bb1() + Jump bb3() + bb3(): + v5:Any = Const Value(true) + IfTrue v5, bb0() + Jump bb4() + bb4(): + v8:Any = Const CBool(true) + Return v8 + "); + + assert!(loop_info.is_loop_header(bb0)); + assert!(loop_info.is_loop_header(bb1)); + + assert_eq!(loop_info.loop_depth(bb0), 1); + assert_eq!(loop_info.loop_depth(bb1), 2); + assert_eq!(loop_info.loop_depth(bb2), 2); + assert_eq!(loop_info.loop_depth(bb3), 1); + assert_eq!(loop_info.loop_depth(bb4), 0); + + assert!(loop_info.is_back_edge_source(bb2)); + assert!(loop_info.is_back_edge_source(bb3)); + } + + #[test] + fn test_complex_loops() { + // ┌─────┐ + // ┌──────► bb0 │ + // │ └──┬──┘ + // │ ┌────┴────┐ + // │ ┌──▼──┐ ┌──▼──┐ + // │ │ bb1 ◄─┐ │ bb3 ◄─┐ + // │ └──┬──┘ │ └──┬──┘ │ + // │ │ │ │ │ + // │ ┌──▼──┐ │ ┌──▼──┐ │ + // │ │ bb2 ┼─┘ │ bb4 ┼─┘ + // │ └──┬──┘ └──┬──┘ + // │ └────┬────┘ + // │ ┌──▼──┐ + // └──────┼ bb5 │ + // └──┬──┘ + // │ + // ┌──▼──┐ + // │ bb6 │ + // └─────┘ + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + let bb4 = function.new_block(0); + let bb5 = function.new_block(0); + let bb6 = function.new_block(0); + + let cond = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) }); + function.push_insn(bb0, Insn::Jump(edge(bb3))); + + function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let _ = function.push_insn(bb2, Insn::IfTrue { val: cond, target: edge(bb1) }); + function.push_insn(bb2, Insn::Jump(edge(bb5))); + + function.push_insn(bb3, Insn::Jump(edge(bb4))); + + let _ = function.push_insn(bb4, Insn::IfTrue { val: cond, target: edge(bb3) }); + function.push_insn(bb4, Insn::Jump(edge(bb5))); + + let _ = function.push_insn(bb5, Insn::IfTrue { val: cond, target: edge(bb0) }); + function.push_insn(bb5, Insn::Jump(edge(bb6))); + + let retval = function.push_insn(bb6, Insn::Const { val: Const::CBool(true) }); + let _ = function.push_insn(bb6, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + let dominators = Dominators::new(&function); + let loop_info = LoopInfo::new(&cfi, &dominators); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + v0:Any = Const Value(false) + IfTrue v0, bb1() + Jump bb3() + bb1(): + Jump bb2() + bb2(): + IfTrue v0, bb1() + Jump bb5() + bb3(): + Jump bb4() + bb4(): + IfTrue v0, bb3() + Jump bb5() + bb5(): + IfTrue v0, bb0() + Jump bb6() + bb6(): + v11:Any = Const CBool(true) + Return v11 + "); + + assert!(loop_info.is_loop_header(bb0)); + assert!(loop_info.is_loop_header(bb1)); + assert!(!loop_info.is_loop_header(bb2)); + assert!(loop_info.is_loop_header(bb3)); + assert!(!loop_info.is_loop_header(bb5)); + assert!(!loop_info.is_loop_header(bb4)); + assert!(!loop_info.is_loop_header(bb6)); + + assert_eq!(loop_info.loop_depth(bb0), 1); + assert_eq!(loop_info.loop_depth(bb1), 2); + assert_eq!(loop_info.loop_depth(bb2), 2); + assert_eq!(loop_info.loop_depth(bb3), 2); + assert_eq!(loop_info.loop_depth(bb4), 2); + assert_eq!(loop_info.loop_depth(bb5), 1); + assert_eq!(loop_info.loop_depth(bb6), 0); + + assert!(loop_info.is_back_edge_source(bb2)); + assert!(loop_info.is_back_edge_source(bb4)); + assert!(loop_info.is_back_edge_source(bb5)); + } + + #[test] + fn linked_list_non_loop() { + // ┌─────┐ + // │ bb0 │ + // └──┬──┘ + // │ + // ┌──▼──┐ + // │ bb1 │ + // └──┬──┘ + // │ + // ┌──▼──┐ + // │ bb2 │ + // └─────┘ + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + + let _ = function.push_insn(bb0, Insn::Jump(edge(bb1))); + let _ = function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) }); + let _ = function.push_insn(bb2, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + let dominators = Dominators::new(&function); + let loop_info = LoopInfo::new(&cfi, &dominators); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + Jump bb1() + bb1(): + Jump bb2() + bb2(): + v2:Any = Const CBool(true) + Return v2 + "); + + assert!(!loop_info.is_loop_header(bb0)); + assert!(!loop_info.is_loop_header(bb1)); + assert!(!loop_info.is_loop_header(bb2)); + + assert!(!loop_info.is_back_edge_source(bb0)); + assert!(!loop_info.is_back_edge_source(bb1)); + assert!(!loop_info.is_back_edge_source(bb2)); + + assert_eq!(loop_info.loop_depth(bb0), 0); + assert_eq!(loop_info.loop_depth(bb1), 0); + assert_eq!(loop_info.loop_depth(bb2), 0); + } + + #[test] + fn triple_nested_loop() { + // ┌─────┐ + // │ bb0 ◄──┐ + // └──┬──┘ │ + // │ │ + // ┌──▼──┐ │ + // │ bb1 ◄─┐│ + // └──┬──┘ ││ + // │ ││ + // ┌──▼──┐ ││ + // │ bb2 ◄┐││ + // └──┬──┘│││ + // │ │││ + // ┌──▼──┐│││ + // │ bb3 ┼┘││ + // └──┬──┘ ││ + // │ ││ + // ┌──▼──┐ ││ + // │ bb4 ┼─┘│ + // └──┬──┘ │ + // │ │ + // ┌──▼──┐ │ + // │ bb5 ┼──┘ + // └─────┘ + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + let bb3 = function.new_block(0); + let bb4 = function.new_block(0); + let bb5 = function.new_block(0); + + let cond = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::Jump(edge(bb1))); + let _ = function.push_insn(bb1, Insn::Jump(edge(bb2))); + let _ = function.push_insn(bb2, Insn::Jump(edge(bb3))); + let _ = function.push_insn(bb3, Insn::Jump(edge(bb4))); + let _ = function.push_insn(bb3, Insn::IfTrue {val: cond, target: edge(bb2)}); + let _ = function.push_insn(bb4, Insn::Jump(edge(bb5))); + let _ = function.push_insn(bb4, Insn::IfTrue {val: cond, target: edge(bb1)}); + let _ = function.push_insn(bb5, Insn::IfTrue {val: cond, target: edge(bb0)}); + + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + fn : + bb0(): + v0:Any = Const Value(false) + Jump bb1() + bb1(): + Jump bb2() + bb2(): + Jump bb3() + bb3(): + Jump bb4() + IfTrue v0, bb2() + bb4(): + Jump bb5() + IfTrue v0, bb1() + bb5(): + IfTrue v0, bb0() + "); + + let cfi = ControlFlowInfo::new(&function); + let dominators = Dominators::new(&function); + let loop_info = LoopInfo::new(&cfi, &dominators); + + assert!(!loop_info.is_back_edge_source(bb0)); + assert!(!loop_info.is_back_edge_source(bb1)); + assert!(!loop_info.is_back_edge_source(bb2)); + assert!(loop_info.is_back_edge_source(bb3)); + assert!(loop_info.is_back_edge_source(bb4)); + assert!(loop_info.is_back_edge_source(bb5)); + + assert_eq!(loop_info.loop_depth(bb0), 1); + assert_eq!(loop_info.loop_depth(bb1), 2); + assert_eq!(loop_info.loop_depth(bb2), 3); + assert_eq!(loop_info.loop_depth(bb3), 3); + assert_eq!(loop_info.loop_depth(bb4), 2); + assert_eq!(loop_info.loop_depth(bb5), 1); + + assert!(loop_info.is_loop_header(bb0)); + assert!(loop_info.is_loop_header(bb1)); + assert!(loop_info.is_loop_header(bb2)); + assert!(!loop_info.is_loop_header(bb3)); + assert!(!loop_info.is_loop_header(bb4)); + assert!(!loop_info.is_loop_header(bb5)); + } + } + +/// Test dumping to iongraph format. +#[cfg(test)] +mod iongraph_tests { + use super::*; + use insta::assert_snapshot; + + fn edge(target: BlockId) -> BranchEdge { + BranchEdge { target, args: vec![] } + } + + #[test] + fn test_simple_function() { + let mut function = Function::new(std::ptr::null()); + let bb0 = function.entry_block; + + let retval = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb0, Insn::Return { val: retval }); + + let json = function.to_iongraph_pass("simple"); + assert_snapshot!(json.to_string(), @r#"{"name":"simple", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"Return v0", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + + #[test] + fn test_two_blocks() { + let mut function = Function::new(std::ptr::null()); + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb1))); + + let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(false) }); + function.push_insn(bb1, Insn::Return { val: retval }); + + let json = function.to_iongraph_pass("two_blocks"); + assert_snapshot!(json.to_string(), @r#"{"name":"two_blocks", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4096, "id":0, "opcode":"Jump bb1()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4097, "id":1, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4098, "id":2, "opcode":"Return v1", "attributes":[], "inputs":[1], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + + #[test] + fn test_multiple_instructions() { + let mut function = Function::new(std::ptr::null()); + let bb0 = function.entry_block; + + let val1 = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb0, Insn::Return { val: val1 }); + + let json = function.to_iongraph_pass("multiple_instructions"); + assert_snapshot!(json.to_string(), @r#"{"name":"multiple_instructions", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"Return v0", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + + #[test] + fn test_conditional_branch() { + let mut function = Function::new(std::ptr::null()); + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + + let cond = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) }); + + let retval1 = function.push_insn(bb0, Insn::Const { val: Const::CBool(false) }); + function.push_insn(bb0, Insn::Return { val: retval1 }); + + let retval2 = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb1, Insn::Return { val: retval2 }); + + let json = function.to_iongraph_pass("conditional_branch"); + assert_snapshot!(json.to_string(), @r#"{"name":"conditional_branch", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"IfTrue v0, bb1()", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}, {"ptr":4098, "id":2, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4099, "id":3, "opcode":"Return v2", "attributes":[], "inputs":[2], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4100, "id":4, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4101, "id":5, "opcode":"Return v4", "attributes":[], "inputs":[4], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + + #[test] + fn test_loop_structure() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + + function.push_insn(bb0, Insn::Jump(edge(bb2))); + + let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb2, Insn::IfTrue { val, target: edge(bb1)}); + let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) }); + let _ = function.push_insn(bb2, Insn::Return { val: retval }); + + function.push_insn(bb1, Insn::Jump(edge(bb2))); + + let json = function.to_iongraph_pass("loop_structure"); + assert_snapshot!(json.to_string(), @r#"{"name":"loop_structure", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[2], "instructions":[{"ptr":4096, "id":0, "opcode":"Jump bb2()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}, {"ptr":4097, "id":1, "opcode":"Const Value(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}]}, {"ptr":4098, "id":2, "loopDepth":1, "attributes":["loopheader"], "predecessors":[0, 1], "successors":[1], "instructions":[{"ptr":4098, "id":2, "opcode":"IfTrue v1, bb1()", "attributes":[], "inputs":[1], "uses":[], "memInputs":[], "type":""}, {"ptr":4099, "id":3, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4100, "id":4, "opcode":"Return v3", "attributes":[], "inputs":[3], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":1, "attributes":["backedge"], "predecessors":[2], "successors":[2], "instructions":[{"ptr":4101, "id":5, "opcode":"Jump bb2()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + + #[test] + fn test_multiple_successors() { + let mut function = Function::new(std::ptr::null()); + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + let bb2 = function.new_block(0); + + let cond = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) }); + function.push_insn(bb0, Insn::Jump(edge(bb2))); + + let retval1 = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb1, Insn::Return { val: retval1 }); + + let retval2 = function.push_insn(bb2, Insn::Const { val: Const::CBool(false) }); + function.push_insn(bb2, Insn::Return { val: retval2 }); + + let json = function.to_iongraph_pass("multiple_successors"); + assert_snapshot!(json.to_string(), @r#"{"name":"multiple_successors", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1, 2], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"IfTrue v0, bb1()", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}, {"ptr":4098, "id":2, "opcode":"Jump bb2()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4099, "id":3, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4100, "id":4, "opcode":"Return v3", "attributes":[], "inputs":[3], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4098, "id":2, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4101, "id":5, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4102, "id":6, "opcode":"Return v5", "attributes":[], "inputs":[5], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#); + } + } diff --git a/zjit/src/options.rs b/zjit/src/options.rs index c165035eaa1af0..b7e2c71cefcd65 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -70,6 +70,9 @@ pub struct Options { /// Dump High-level IR to the given file in Graphviz format after optimization pub dump_hir_graphviz: Option, + /// Dump High-level IR in Iongraph JSON format after optimization to /tmp/zjit-iongraph-{$PID} + pub dump_hir_iongraph: bool, + /// Dump low-level IR pub dump_lir: Option>, @@ -106,6 +109,7 @@ impl Default for Options { dump_hir_init: None, dump_hir_opt: None, dump_hir_graphviz: None, + dump_hir_iongraph: false, dump_lir: None, dump_disasm: false, trace_side_exits: None, @@ -353,6 +357,8 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.dump_hir_graphviz = Some(opt_val); } + ("dump-hir-iongraph", "") => options.dump_hir_iongraph = true, + ("dump-lir", "") => options.dump_lir = Some(HashSet::from([DumpLIR::init])), ("dump-lir", filters) => { let mut dump_lirs = HashSet::new(); From 4107a41020003d7106883b78891560e05d299310 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 18 Nov 2025 17:45:27 -0500 Subject: [PATCH 1242/2435] ZJIT: Re-link the test binary when only miniruby changes --- zjit/build.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zjit/build.rs b/zjit/build.rs index 6aec5407f62af7..4ee3d65b33062e 100644 --- a/zjit/build.rs +++ b/zjit/build.rs @@ -5,9 +5,11 @@ fn main() { // option_env! automatically registers a rerun-if-env-changed if let Some(ruby_build_dir) = option_env!("RUBY_BUILD_DIR") { - // Link against libminiruby + // Link against libminiruby.a println!("cargo:rustc-link-search=native={ruby_build_dir}"); println!("cargo:rustc-link-lib=static:-bundle=miniruby"); + // Re-link when libminiruby.a changes + println!("cargo:rerun-if-changed={ruby_build_dir}/libminiruby.a"); // System libraries that libminiruby needs. Has to be // ordered after -lminiruby above. From 2cd792a1cfefeaee948b321bbc14cb86acc2d456 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 18 Nov 2025 17:46:52 -0500 Subject: [PATCH 1243/2435] ZJIT: Fix assertion failure when profiling VM_BLOCK_HANDLER_NONE As can be seen in vm_block_handler_verify(), VM_BLOCK_HANDLER_NONE is not a valid argument for vm_block_handler(). Store nil in the profiler when seen instead of crashing. --- vm_insnhelper.c | 3 +++ zjit/src/profile.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 8495ee59ef438e..7626d461352c8d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6041,11 +6041,14 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv } // Return the untagged block handler: +// * If it's VM_BLOCK_HANDLER_NONE, return nil // * If it's an ISEQ or an IFUNC, fetch it from its rb_captured_block // * If it's a PROC or SYMBOL, return it as is static VALUE rb_vm_untag_block_handler(VALUE block_handler) { + if (VM_BLOCK_HANDLER_NONE == block_handler) return Qnil; + switch (vm_block_handler_type(block_handler)) { case block_handler_type_iseq: case block_handler_type_ifunc: { diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 47bae3ac633a86..8c8190609d7fae 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -361,3 +361,17 @@ impl IseqProfile { } } } + +#[cfg(test)] +mod tests { + use crate::cruby::*; + + #[test] + fn can_profile_block_handler() { + with_rubyvm(|| eval(" + def foo = yield + foo rescue 0 + foo rescue 0 + ")); + } +} From fa02d7a01f5e7516de8eb3c7f92ec75c50c06e3f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 19 Nov 2025 17:09:39 -0500 Subject: [PATCH 1244/2435] Implement heap_live_slots in GC.stat_heap [Feature #20408] --- gc/default/default.c | 3 +++ test/ruby/test_gc.rb | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 82741458bb8ff3..7bd9b3c74b0377 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -7592,6 +7592,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) enum gc_stat_heap_sym { gc_stat_heap_sym_slot_size, + gc_stat_heap_sym_heap_live_slots, gc_stat_heap_sym_heap_eden_pages, gc_stat_heap_sym_heap_eden_slots, gc_stat_heap_sym_total_allocated_pages, @@ -7610,6 +7611,7 @@ setup_gc_stat_heap_symbols(void) if (gc_stat_heap_symbols[0] == 0) { #define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s)) S(slot_size); + S(heap_live_slots); S(heap_eden_pages); S(heap_eden_slots); S(total_allocated_pages); @@ -7631,6 +7633,7 @@ stat_one_heap(rb_heap_t *heap, VALUE hash, VALUE key) rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr)); SET(slot_size, heap->slot_size); + SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count); SET(heap_eden_pages, heap->total_pages); SET(heap_eden_slots, heap->total_slots); SET(total_allocated_pages, heap->total_allocated_pages); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 7695fd33cf9945..38e91366adbff3 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -231,6 +231,7 @@ def test_stat_heap end assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] + assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] assert_operator stat_heap[:heap_eden_slots], :>=, 0 assert_operator stat_heap[:total_allocated_pages], :>=, 0 @@ -261,7 +262,7 @@ def test_stat_heap_all GC.stat_heap(i, stat_heap) # Remove keys that can vary between invocations - %i(total_allocated_objects).each do |sym| + %i(total_allocated_objects heap_live_slots).each do |sym| stat_heap[sym] = stat_heap_all[i][sym] = 0 end @@ -286,6 +287,7 @@ def test_stat_heap_constraints hash.each { |k, v| stat_heap_sum[k] += v } end + assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots] assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects] From 83bf05427d882b9d5b9adf500abe3471eef14dd1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 19 Nov 2025 17:13:47 -0500 Subject: [PATCH 1245/2435] Implement heap_free_slots in GC.stat_heap [Feature #20408] --- gc/default/default.c | 3 +++ test/ruby/test_gc.rb | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 7bd9b3c74b0377..35ca2ec1078dc2 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -7593,6 +7593,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) enum gc_stat_heap_sym { gc_stat_heap_sym_slot_size, gc_stat_heap_sym_heap_live_slots, + gc_stat_heap_sym_heap_free_slots, gc_stat_heap_sym_heap_eden_pages, gc_stat_heap_sym_heap_eden_slots, gc_stat_heap_sym_total_allocated_pages, @@ -7612,6 +7613,7 @@ setup_gc_stat_heap_symbols(void) #define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s)) S(slot_size); S(heap_live_slots); + S(heap_free_slots); S(heap_eden_pages); S(heap_eden_slots); S(total_allocated_pages); @@ -7634,6 +7636,7 @@ stat_one_heap(rb_heap_t *heap, VALUE hash, VALUE key) SET(slot_size, heap->slot_size); SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count); + SET(heap_free_slots, heap->total_slots - (heap->total_allocated_objects - heap->total_freed_objects)); SET(heap_eden_pages, heap->total_pages); SET(heap_eden_slots, heap->total_slots); SET(total_allocated_pages, heap->total_allocated_pages); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 38e91366adbff3..06bacf1b1a131c 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -232,6 +232,7 @@ def test_stat_heap assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] + assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] assert_operator stat_heap[:heap_eden_slots], :>=, 0 assert_operator stat_heap[:total_allocated_pages], :>=, 0 @@ -262,7 +263,7 @@ def test_stat_heap_all GC.stat_heap(i, stat_heap) # Remove keys that can vary between invocations - %i(total_allocated_objects heap_live_slots).each do |sym| + %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym| stat_heap[sym] = stat_heap_all[i][sym] = 0 end @@ -288,6 +289,7 @@ def test_stat_heap_constraints end assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots] + assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots] assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects] From f5f69d41146d5e17e93ec5b219ae6f5ecd59e38b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 19 Nov 2025 17:15:23 -0500 Subject: [PATCH 1246/2435] Implement heap_final_slots in GC.stat_heap [Feature #20408] --- gc/default/default.c | 3 +++ test/ruby/test_gc.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/gc/default/default.c b/gc/default/default.c index 35ca2ec1078dc2..42561543d1a7c7 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -7594,6 +7594,7 @@ enum gc_stat_heap_sym { gc_stat_heap_sym_slot_size, gc_stat_heap_sym_heap_live_slots, gc_stat_heap_sym_heap_free_slots, + gc_stat_heap_sym_heap_final_slots, gc_stat_heap_sym_heap_eden_pages, gc_stat_heap_sym_heap_eden_slots, gc_stat_heap_sym_total_allocated_pages, @@ -7614,6 +7615,7 @@ setup_gc_stat_heap_symbols(void) S(slot_size); S(heap_live_slots); S(heap_free_slots); + S(heap_final_slots); S(heap_eden_pages); S(heap_eden_slots); S(total_allocated_pages); @@ -7637,6 +7639,7 @@ stat_one_heap(rb_heap_t *heap, VALUE hash, VALUE key) SET(slot_size, heap->slot_size); SET(heap_live_slots, heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count); SET(heap_free_slots, heap->total_slots - (heap->total_allocated_objects - heap->total_freed_objects)); + SET(heap_final_slots, heap->final_slots_count); SET(heap_eden_pages, heap->total_pages); SET(heap_eden_slots, heap->total_slots); SET(total_allocated_pages, heap->total_allocated_pages); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 06bacf1b1a131c..6639013a54ca32 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -233,6 +233,7 @@ def test_stat_heap assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] + assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots] assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] assert_operator stat_heap[:heap_eden_slots], :>=, 0 assert_operator stat_heap[:total_allocated_pages], :>=, 0 @@ -290,6 +291,7 @@ def test_stat_heap_constraints assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots] assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots] + assert_equal stat[:heap_final_slots], stat_heap_sum[:heap_final_slots] assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects] From 167c3dbaa052d7ad34c374fc6c5f2cceab76b3ac Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 19 Nov 2025 19:03:23 -0500 Subject: [PATCH 1247/2435] Omit a test on s390x linux tripping over a git bug This test has been reliably failing on recent trunk versions. See: --- tool/test/test_sync_default_gems.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index cdbbb0c5394dc3..4a2688850be12a 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -319,6 +319,9 @@ def test_delete_after_conflict end def test_squash_merge + if RUBY_PLATFORM =~ /s390x/ + omit("git 2.43.0 bug on s390x ubuntu 24.04: BUG: log-tree.c:1058: did a remerge diff without remerge_objdir?!?") + end # 2---. <- branch # / \ # 1---3---3'<- merge commit with conflict resolution From 0653a01ada3c3c286de128074b58b32e7bf9eeca Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 20 Nov 2025 10:05:16 +0900 Subject: [PATCH 1248/2435] [ruby/rubygems] Bump up to 4.0.0.beta1 https://github.com/ruby/rubygems/commit/9be811c01a --- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 0715f6dfee58ba..4acf10c6153eaa 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0.dev".freeze + VERSION = "4.0.0.beta1".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index db1da659f9d5a8..c398c985f58556 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0.dev" + VERSION = "4.0.0.beta1" end require_relative "rubygems/defaults" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 2a6c3178ef0529..4a96e12169b72e 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 7ba3fe68befa00..8a13873f01b2fc 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index dbb6218f9ca9ac..29e0a574bba80a 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 84ce9bd4fda5c6..10a81012e4d6c1 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index e8a2cbd53e8b27..8f4eba83c51b5a 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index ffcfb7a3e1295c..8c8c9b77436542 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -100,4 +100,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0.dev + 4.0.0.beta1 From 4c7525082c353bfa717cb17f9ae75d940cb69d20 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 20 Nov 2025 02:42:50 +0000 Subject: [PATCH 1249/2435] Update default gems list at 0653a01ada3c3c286de128074b58b3 [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index bd84c081ac4d07..9d47bb774ef626 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,8 +186,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.0.dev -* bundler 4.0.0.dev +* RubyGems 4.0.0.beta1 +* bundler 4.0.0.beta1 * date 3.5.0 * digest 3.2.1 * english 0.8.1 From 2c42f7c60115d78436e599cf4f56aaf05bc7ac79 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 18 Nov 2025 19:34:28 +0900 Subject: [PATCH 1250/2435] [ruby/resolv] Move minimal registry access to the extension Now win32/registry depends on fiddle, and its conversion is complex and too generic for the purpose of resolv. https://github.com/ruby/resolv/commit/bd24870d2d --- ext/win32/lib/win32/resolv.rb | 138 +++++++-------------- ext/win32/resolv/extconf.rb | 1 + ext/win32/resolv/resolv.c | 206 +++++++++++++++++++++++++++++-- test/resolv/test_win32_config.rb | 110 +++-------------- 4 files changed, 260 insertions(+), 195 deletions(-) diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index 43ec10cf98acd3..9a74a753517ab3 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -4,8 +4,23 @@ =end +require 'win32/resolv.so' + module Win32 module Resolv + # Error at Win32 API + class Error < StandardError + # +code+ Win32 Error code + # +message+ Formatted message for +code+ + def initialize(code, message) + super(message) + @code = code + end + + # Win32 error code + attr_reader :code + end + def self.get_hosts_path path = get_hosts_dir path = File.expand_path('hosts', path) @@ -29,121 +44,62 @@ def self.get_resolv_info end [ search, nameserver ] end - end -end - -begin - require 'win32/resolv.so' -rescue LoadError -end - -module Win32 -#==================================================================== -# Windows NT -#==================================================================== - module Resolv - begin - require 'win32/registry' - module SZ - refine Registry do - # ad hoc workaround for broken registry - def read_s(key) - type, str = read(key) - unless type == Registry::REG_SZ - warn "Broken registry, #{name}\\#{key} was #{Registry.type2name(type)}, ignored" - return String.new - end - str - end - end - end - using SZ - rescue LoadError, Gem::LoadError - require "open3" - end - - TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' class << self private def get_hosts_dir - get_item_property(TCPIP_NT, 'DataBasePath', expand: true) + tcpip_params do |params| + params.value('DataBasePath') + end end def get_info search = nil nameserver = get_dns_server_list - slist = get_item_property(TCPIP_NT, 'SearchList') - search = slist.split(/,\s*/) unless slist.empty? + tcpip_params do |params| + slist = params.value('SearchList') + search = slist.split(/,\s*/) if slist and !slist.empty? - if add_search = search.nil? - search = [] - nvdom = get_item_property(TCPIP_NT, 'NV Domain') + if add_search = search.nil? + search = [] + nvdom = params.value('NV Domain') - unless nvdom.empty? - search = [ nvdom ] - udmnd = get_item_property(TCPIP_NT, 'UseDomainNameDevolution', dword: true) - if udmnd != 0 - if /^\w+\./ =~ nvdom - devo = $' + if nvdom and !nvdom.empty? + search = [ nvdom ] + udmnd = params.value('UseDomainNameDevolution') + if udmnd&.nonzero? + if /^\w+\./ =~ nvdom + devo = $' + end end end end - end - ifs = if defined?(Win32::Registry) - Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT + '\Interfaces') do |reg| - reg.keys - rescue Registry::Error - [] - end - else - cmd = "Get-ChildItem 'HKLM:\\#{TCPIP_NT}\\Interfaces' | ForEach-Object { $_.PSChildName }" - output, _ = Open3.capture2('powershell', '-Command', cmd) - output.split(/\n+/) + params.open('Interfaces') do |reg| + reg.each_key do |iface| + next unless ns = %w[NameServer DhcpNameServer].find do |key| + ns = iface.value(key) + break ns.split(/[,\s]\s*/) if ns and !ns.empty? end - ifs.each do |iface| - next unless ns = %w[NameServer DhcpNameServer].find do |key| - ns = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key) - break ns.split(/[,\s]\s*/) unless ns.empty? - end - - next if (nameserver & ns).empty? + next if (nameserver & ns).empty? - if add_search - [ 'Domain', 'DhcpDomain' ].each do |key| - dom = get_item_property(TCPIP_NT + '\Interfaces' + "\\#{iface}", key) - unless dom.empty? - search.concat(dom.split(/,\s*/)) - break + if add_search + [ 'Domain', 'DhcpDomain' ].each do |key| + dom = iface.value(key) + if dom and !dom.empty? + search.concat(dom.split(/,\s*/)) + break + end + end end end end - end - search << devo if add_search and devo - [ search.uniq, nameserver.uniq ] - end - def get_item_property(path, name, expand: false, dword: false) - if defined?(Win32::Registry) - begin - Registry::HKEY_LOCAL_MACHINE.open(path) do |reg| - if dword - reg.read_i(name) - else - expand ? reg.read_s_expand(name) : reg.read_s(name) - end - end - rescue Registry::Error - dword ? 0 : "" - end - else - cmd = "Get-ItemProperty -Path 'HKLM:\\#{path}' -Name '#{name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty '#{name}'" - output, _ = Open3.capture2('powershell', '-Command', cmd) - dword ? output.strip.to_i : output.strip + search << devo if add_search and devo end + [ search.uniq, nameserver.uniq ] end end end diff --git a/ext/win32/resolv/extconf.rb b/ext/win32/resolv/extconf.rb index a5f8cc279da830..5ee4c0d7c4da06 100644 --- a/ext/win32/resolv/extconf.rb +++ b/ext/win32/resolv/extconf.rb @@ -1,5 +1,6 @@ require 'mkmf' if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams', ['windows.h', 'iphlpapi.h']) + have_library('advapi32', 'RegGetValueW', ['windows.h']) create_makefile('win32/resolv') else File.write('Makefile', "all clean install:\n\t@echo Done: $(@)\n") diff --git a/ext/win32/resolv/resolv.c b/ext/win32/resolv/resolv.c index af678e1f178dda..066856dd984df0 100644 --- a/ext/win32/resolv/resolv.c +++ b/ext/win32/resolv/resolv.c @@ -1,24 +1,56 @@ #include #include #include +#include #ifndef NTDDI_VERSION #define NTDDI_VERSION 0x06000000 #endif #include +#ifndef numberof +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#endif + static VALUE w32error_make_error(DWORD e) { - VALUE code = ULONG2NUM(e); - return rb_class_new_instance(1, &code, rb_path2class("Win32::Resolv::Error")); + char buffer[512], *p; + DWORD source = 0; + VALUE args[2]; + if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, &source, e, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + buffer, sizeof(buffer), NULL)) { + snprintf(buffer, sizeof(buffer), "Unknown Error %u", (unsigned long)e); + } + p = buffer; + while ((p = strpbrk(p, "\r\n")) != NULL) { + memmove(p, p + 1, strlen(p)); + if (!p[1]) { + p[0] = '\0'; + break; + } + } + args[0] = ULONG2NUM(e); + args[1] = rb_str_new_cstr(buffer); + return rb_class_new_instance(2, args, rb_path2class("Win32::Resolv::Error")); } -NORETURN(static void w32error_raise(DWORD e)); - static void -w32error_raise(DWORD e) +w32error_check(DWORD e) +{ + if (e != NO_ERROR) { + rb_exc_raise(w32error_make_error(e)); + } +} + +static VALUE +wchar_to_utf8(const WCHAR *w, int n) { - rb_exc_raise(w32error_make_error(e)); + int clen = WideCharToMultiByte(CP_UTF8, 0, w, n, NULL, 0, NULL, NULL); + VALUE str = rb_enc_str_new(NULL, clen, rb_utf8_encoding()); + WideCharToMultiByte(CP_UTF8, 0, w, n, RSTRING_PTR(str), clen, NULL, NULL); + return str; } static VALUE @@ -30,9 +62,7 @@ get_dns_server_list(VALUE self) VALUE buf, nameservers = Qnil; ret = GetNetworkParams(NULL, &buflen); - if (ret != NO_ERROR && ret != ERROR_BUFFER_OVERFLOW) { - w32error_raise(ret); - } + if (ret != ERROR_BUFFER_OVERFLOW) w32error_check(ret); fixedinfo = ALLOCV(buf, buflen); ret = GetNetworkParams(fixedinfo, &buflen); if (ret == NO_ERROR) { @@ -46,18 +76,174 @@ get_dns_server_list(VALUE self) } while ((ipaddr = ipaddr->Next) != NULL); } ALLOCV_END(buf); - if (ret != NO_ERROR) w32error_raise(ret); + w32error_check(ret); return nameservers; } + +static const WCHAR TCPIP_Params[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; + +static void +hkey_finalize(void *p) +{ + RegCloseKey((HKEY)p); +} + +static const rb_data_type_t hkey_type = { + "RegKey", + {0, hkey_finalize}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE +hkey_close(VALUE self) +{ + RegCloseKey((HKEY)DATA_PTR(self)); + DATA_PTR(self) = 0; + return self; +} + +static VALUE reg_key_class; + +static VALUE +reg_open_key(VALUE klass, HKEY hkey, const WCHAR *wname) +{ + VALUE k = TypedData_Wrap_Struct(klass, &hkey_type, NULL); + DWORD e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k)); + if (e == ERROR_FILE_NOT_FOUND) return Qnil; + w32error_check(e); + return rb_ensure(rb_yield, k, hkey_close, k); +} + +static VALUE +tcpip_params_open(VALUE klass) +{ + return reg_open_key(reg_key_class, HKEY_LOCAL_MACHINE, TCPIP_Params); +} + +static int +to_wname(VALUE *name, WCHAR *wname, int wlen) +{ + const char *n = StringValueCStr(*name); + int nlen = RSTRING_LEN(*name); + int len = MultiByteToWideChar(CP_UTF8, 0, n, nlen, wname, wlen - 1); + if (len == 0) w32error_check(GetLastError()); + if (len >= wlen) rb_raise(rb_eArgError, "too long name"); + wname[len] = L'\0'; + return len; +} + +static VALUE +reg_open(VALUE self, VALUE name) +{ + HKEY hkey = DATA_PTR(self); + WCHAR wname[256]; + to_wname(&name, wname, numberof(wname)); + return reg_open_key(CLASS_OF(self), hkey, wname); +} + + +static VALUE +reg_each_key(VALUE self) +{ + WCHAR wname[256]; + HKEY hkey = DATA_PTR(self); + rb_encoding *utf8 = rb_utf8_encoding(); + VALUE k = TypedData_Wrap_Struct(CLASS_OF(self), &hkey_type, NULL); + DWORD i, e, n; + for (i = 0; n = numberof(wname), (e = RegEnumKeyExW(hkey, i, wname, &n, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS; i++) { + e = RegOpenKeyExW(hkey, wname, 0, KEY_READ, (HKEY *)&DATA_PTR(k)); + w32error_check(e); + rb_ensure(rb_yield, k, hkey_close, k); + } + if (e != ERROR_NO_MORE_ITEMS) w32error_check(e); + return self; +} + +static inline DWORD +swap_dw(DWORD x) +{ +#if defined(_MSC_VER) + return _byteswap_ulong(x); +#else + return __builtin_bswap32(x); +#endif +} + +static VALUE +reg_value(VALUE self, VALUE name) +{ + HKEY hkey = DATA_PTR(self); + DWORD type = 0, size = 0, e; + VALUE result, value_buffer; + void *buffer; + WCHAR wname[256]; + to_wname(&name, wname, numberof(wname)); + e = RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, NULL, &size); + if (e == ERROR_FILE_NOT_FOUND) return Qnil; + w32error_check(e); + switch (type) { + case REG_DWORD: case REG_DWORD_BIG_ENDIAN: + { + DWORD d; + if (size != sizeof(d)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_DWORD, &type, &d, &size)); + if (type == REG_DWORD_BIG_ENDIAN) d = swap_dw(d); + return ULONG2NUM(d); + } + case REG_QWORD: + { + QWORD q; + if (size != sizeof(q)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_QWORD, &type, &q, &size)); + return ULL2NUM(q); + } + case REG_SZ: case REG_MULTI_SZ: case REG_EXPAND_SZ: + if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + buffer = ALLOCV_N(char, value_buffer, size); + break; + default: + result = rb_str_new(0, size); + buffer = RSTRING_PTR(result); + } + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, buffer, &size)); + switch (type) { + case REG_MULTI_SZ: { + const WCHAR *w = (WCHAR *)buffer; + rb_encoding *utf8 = rb_utf8_encoding(); + result = rb_ary_new(); + size /= sizeof(WCHAR); + size -= 1; + for (size_t i = 0; i < size; ++i) { + int n = lstrlenW(w+i); + rb_ary_push(result, wchar_to_utf8(w+i, n)); + i += n; + } + return result; + } + case REG_SZ: case REG_EXPAND_SZ: + return wchar_to_utf8((WCHAR *)buffer, lstrlenW((WCHAR *)buffer)); + default: + return result; + } +} + void InitVM_resolv(void) { VALUE mWin32 = rb_define_module("Win32"); VALUE resolv = rb_define_module_under(mWin32, "Resolv"); VALUE singl = rb_singleton_class(resolv); + VALUE regkey = rb_define_class_under(resolv, "registry key", rb_cObject); + + reg_key_class = regkey; + rb_undef_alloc_func(regkey); rb_define_private_method(singl, "get_dns_server_list", get_dns_server_list, 0); + rb_define_private_method(singl, "tcpip_params", tcpip_params_open, 0); + rb_define_method(regkey, "open", reg_open, 1); + rb_define_method(regkey, "each_key", reg_each_key, 0); + rb_define_method(regkey, "value", reg_value, 1); } void diff --git a/test/resolv/test_win32_config.rb b/test/resolv/test_win32_config.rb index f44d19544ac5b4..6167af6605d84a 100644 --- a/test/resolv/test_win32_config.rb +++ b/test/resolv/test_win32_config.rb @@ -3,102 +3,24 @@ require 'test/unit' require 'resolv' -class TestWin32Config < Test::Unit::TestCase - def setup - omit 'Win32::Resolv tests only run on Windows' unless RUBY_PLATFORM =~ /mswin|mingw|cygwin/ - end - - def test_get_item_property_string - # Test reading a string registry value - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'DataBasePath') - - # Should return a string (empty or with a path) - assert_instance_of String, result - end - - def test_get_item_property_with_expand - # Test reading an expandable string registry value - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'DataBasePath', - expand: true) - - # Should return a string with environment variables expanded - assert_instance_of String, result - end +if defined?(Win32::Resolve) + class TestWin32Config < Test::Unit::TestCase + def test_get_item_property_string + # Test reading a string registry value + result = Win32::Resolv.send(:get_hosts_dir) - def test_get_item_property_dword - # Test reading a DWORD registry value - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'UseDomainNameDevolution', - dword: true) + # Should return a string (empty or with a path) + assert_instance_of String, result + end - # Should return an integer (0 or 1 typically) - assert_kind_of Integer, result - end - - def test_get_item_property_nonexistent_key # Test reading a non-existent registry key - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'NonExistentKeyThatShouldNotExist') - - # Should return empty string for non-existent string values - assert_equal '', result - end - - def test_get_item_property_nonexistent_key_dword - # Test reading a non-existent registry key as DWORD - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'NonExistentKeyThatShouldNotExist', - dword: true) - - # Should return 0 for non-existent DWORD values - assert_equal 0, result - end - - def test_get_item_property_search_list - # Test reading SearchList which may exist in the registry - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'SearchList') - - # Should return a string (may be empty if not configured) - assert_instance_of String, result - end - - def test_get_item_property_nv_domain - # Test reading NV Domain which may exist in the registry - result = Win32::Resolv.send(:get_item_property, - Win32::Resolv::TCPIP_NT, - 'NV Domain') - - # Should return a string (may be empty if not configured) - assert_instance_of String, result - end - - def test_get_item_property_with_invalid_path - # Test with an invalid registry path - result = Win32::Resolv.send(:get_item_property, - 'SYSTEM\NonExistent\Path', - 'SomeKey') - - # Should return empty string for invalid path - assert_equal '', result - end - - def test_get_item_property_with_invalid_path_dword - # Test with an invalid registry path as DWORD - result = Win32::Resolv.send(:get_item_property, - 'SYSTEM\NonExistent\Path', - 'SomeKey', - dword: true) - - # Should return 0 for invalid path - assert_equal 0, result + def test_nonexistent_key + assert_nil(Win32::Resolv.send(:tcpip_params) {|reg| reg.open('NonExistentKeyThatShouldNotExist')}) + end + + # Test reading a non-existent registry value + def test_nonexistent_value + assert_nil(Win32::Resolv.send(:tcpip_params) {|reg| reg.value('NonExistentKeyThatShouldNotExist')}) + end end end From d755052a921a3706cb591b3d7288a0b503b430be Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 20 Nov 2025 17:42:17 +0900 Subject: [PATCH 1251/2435] Remove wrong test `realloc` is not guaranteed to return the same address when shrinking. --- spec/ruby/core/io/buffer/valid_spec.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spec/ruby/core/io/buffer/valid_spec.rb b/spec/ruby/core/io/buffer/valid_spec.rb index 695415dff6332f..680a35ae9ad716 100644 --- a/spec/ruby/core/io/buffer/valid_spec.rb +++ b/spec/ruby/core/io/buffer/valid_spec.rb @@ -51,15 +51,6 @@ end context "when buffer is resized" do - platform_is_not :windows do - it "is true when slice is still inside the buffer" do - @buffer = IO::Buffer.new(4) - slice = @buffer.slice(1, 2) - @buffer.resize(3) - slice.valid?.should be_true - end - end - it "is false when slice becomes outside the buffer" do @buffer = IO::Buffer.new(4) slice = @buffer.slice(2, 2) From a24922a68040593598791c9d8b790282d36a15c9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 20 Nov 2025 17:56:51 +0900 Subject: [PATCH 1252/2435] Remove stale declaration `rb_zjit_option_enabled_p` seems no longer used/defined since ruby/ruby#f84bbb423836d9d0d018b8ab71ecceb5868fd5be. --- vm.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vm.c b/vm.c index 0452c4caddcc80..fd8c923649cb65 100644 --- a/vm.c +++ b/vm.c @@ -4714,12 +4714,6 @@ Init_vm_objects(void) vm->cc_refinement_table = rb_set_init_numtable(); } -#if USE_ZJIT -extern VALUE rb_zjit_option_enabled_p(rb_execution_context_t *ec, VALUE self); -#else -static VALUE rb_zjit_option_enabled_p(rb_execution_context_t *ec, VALUE self) { return Qfalse; } -#endif - // Whether JIT is enabled or not, we need to load/undef `#with_jit` for other builtins. #include "jit_hook.rbinc" #include "jit_undef.rbinc" From 41b8e440e7f2c5d3d1c1a9644de4bdc06a343724 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 17 Nov 2025 17:58:26 -0800 Subject: [PATCH 1253/2435] Support backwards compatibility for Set subclasses For subclasses from Set, require `set/subclass_compatible`, and extend the subclass and include a module in it that makes it more backwards compatible with the pure Ruby Set implementation used before Ruby 4. The module included in the subclass contains a near-copy of the previous Set implementation, with the following changes: * Accesses to `@hash` are generally replaced with `super` calls. In some cases, they are replaced with a call to another instance method. * Some methods that only accessed `@hash` and nothing else are not defined, so they inherit behavior from core Set. * The previous `Set#divide` implementation is not used, to avoid depending on tsort. This fixes the following two issues: * [Bug #21375] Set[] does not call #initialize * [Bug #21396] Set#initialize should call Set#add on items passed in It should also fix the vast majority of backwards compatibility issues in other cases where code subclassed Set and depended on implementation details (such as which methods call which other methods). This does not affect Set internals, so Set itself remains fast. For users who want to subclass Set but do not need to worry about backwards compatibility, they can subclass from Set::CoreSet, a Set subclass that does not have the backward compatibility layer included. --- lib/set/subclass_compatible.rb | 353 +++++++++++++++++++++++++++++++++ set.c | 22 ++ test/ruby/test_set.rb | 62 ++++-- 3 files changed, 421 insertions(+), 16 deletions(-) create mode 100644 lib/set/subclass_compatible.rb diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb new file mode 100644 index 00000000000000..ab0aedc0e5bafd --- /dev/null +++ b/lib/set/subclass_compatible.rb @@ -0,0 +1,353 @@ +# frozen_string_literal: true + +# :markup: markdown +# +# set/subclass_compatible.rb - Provides compatibility for set subclasses +# +# Copyright (c) 2002-2024 Akinori MUSHA +# +# Documentation by Akinori MUSHA and Gavin Sinclair. +# +# All rights reserved. You can redistribute and/or modify it under the same +# terms as Ruby. + + +class Set + # This module is automatically included in subclasses of Set, to + # make them backwards compatible with the pure-Ruby set implementation + # used before Ruby 4. Users who want to use Set subclasses without + # this compatibility layer should subclass from Set::CoreSet. + # + # Note that Set subclasses that access `@hash` are not compatible even + # with this support. Such subclasses must be updated to support Ruby 4. + module SubclassCompatible + module ClassMethods + def [](*ary) + new(ary) + end + end + + # Creates a new set containing the elements of the given enumerable + # object. + # + # If a block is given, the elements of enum are preprocessed by the + # given block. + # + # Set.new([1, 2]) #=> # + # Set.new([1, 2, 1]) #=> # + # Set.new([1, 'c', :s]) #=> # + # Set.new(1..5) #=> # + # Set.new([1, 2, 3]) { |x| x * x } #=> # + def initialize(enum = nil, &block) # :yields: o + enum.nil? and return + + if block + do_with_enum(enum) { |o| add(block[o]) } + else + merge(enum) + end + end + + def do_with_enum(enum, &block) # :nodoc: + if enum.respond_to?(:each_entry) + enum.each_entry(&block) if block + elsif enum.respond_to?(:each) + enum.each(&block) if block + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + + def replace(enum) + if enum.instance_of?(self.class) + super + else + do_with_enum(enum) # make sure enum is enumerable before calling clear + clear + merge(enum) + end + end + + def to_set(*args, &block) + klass = if args.empty? + Set + else + warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 + args.shift + end + return self if instance_of?(Set) && klass == Set && block.nil? && args.empty? + klass.new(self, *args, &block) + end + + def flatten_merge(set, seen = {}) # :nodoc: + set.each { |e| + if e.is_a?(Set) + case seen[e_id = e.object_id] + when true + raise ArgumentError, "tried to flatten recursive Set" + when false + next + end + + seen[e_id] = true + flatten_merge(e, seen) + seen[e_id] = false + else + add(e) + end + } + + self + end + protected :flatten_merge + + def flatten + self.class.new.flatten_merge(self) + end + + def flatten! + replace(flatten()) if any?(Set) + end + + def superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size >= set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias >= superset? + + def proper_superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size > set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias > proper_superset? + + def subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size <= set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias <= subset? + + def proper_subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size < set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias < proper_subset? + + def <=>(set) + return unless set.is_a?(Set) + + case size <=> set.size + when -1 then -1 if proper_subset?(set) + when +1 then +1 if proper_superset?(set) + else 0 if self.==(set) + end + end + + def intersect?(set) + case set + when Set + if size < set.size + any?(set) + else + set.any?(self) + end + when Enumerable + set.any?(self) + else + raise ArgumentError, "value must be enumerable" + end + end + + def disjoint?(set) + !intersect?(set) + end + + def add?(o) + add(o) unless include?(o) + end + + def delete?(o) + delete(o) if include?(o) + end + + def delete_if(&block) + block_given? or return enum_for(__method__) { size } + select(&block).each { |o| delete(o) } + self + end + + def keep_if(&block) + block_given? or return enum_for(__method__) { size } + reject(&block).each { |o| delete(o) } + self + end + + def collect! + block_given? or return enum_for(__method__) { size } + set = self.class.new + each { |o| set << yield(o) } + replace(set) + end + alias map! collect! + + def reject!(&block) + block_given? or return enum_for(__method__) { size } + n = size + delete_if(&block) + self if size != n + end + + def select!(&block) + block_given? or return enum_for(__method__) { size } + n = size + keep_if(&block) + self if size != n + end + + alias filter! select! + + def merge(*enums, **nil) + enums.each do |enum| + if enum.instance_of?(self.class) + super(enum) + else + do_with_enum(enum) { |o| add(o) } + end + end + + self + end + + def subtract(enum) + do_with_enum(enum) { |o| delete(o) } + self + end + + def |(enum) + dup.merge(enum) + end + alias + | + alias union | + + def -(enum) + dup.subtract(enum) + end + alias difference - + + def &(enum) + n = self.class.new + if enum.is_a?(Set) + if enum.size > size + each { |o| n.add(o) if enum.include?(o) } + else + enum.each { |o| n.add(o) if include?(o) } + end + else + do_with_enum(enum) { |o| n.add(o) if include?(o) } + end + n + end + alias intersection & + + def ^(enum) + n = self.class.new(enum) + each { |o| n.add(o) unless n.delete?(o) } + n + end + + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + super + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end + end + + def eql?(o) # :nodoc: + return false unless o.is_a?(Set) + super + end + + def classify + block_given? or return enum_for(__method__) { size } + + h = {} + + each { |i| + (h[yield(i)] ||= self.class.new).add(i) + } + + h + end + + def join(separator=nil) + to_a.join(separator) + end + + InspectKey = :__inspect_key__ # :nodoc: + + # Returns a string containing a human-readable representation of the + # set ("#"). + def inspect + ids = (Thread.current[InspectKey] ||= []) + + if ids.include?(object_id) + return sprintf('#<%s: {...}>', self.class.name) + end + + ids << object_id + begin + return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) + ensure + ids.pop + end + end + + alias to_s inspect + + def pretty_print(pp) # :nodoc: + pp.group(1, sprintf('#<%s:', self.class.name), '>') { + pp.breakable + pp.group(1, '{', '}') { + pp.seplist(self) { |o| + pp.pp o + } + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') + end + end + private_constant :SubclassCompatible +end diff --git a/set.c b/set.c index 08b7831a7ee10c..7ef7c11d45de56 100644 --- a/set.c +++ b/set.c @@ -102,6 +102,8 @@ static ID id_any_p; static ID id_new; static ID id_i_hash; static ID id_set_iter_lev; +static ID id_subclass_compatible; +static ID id_class_methods; #define RSET_INITIALIZED FL_USER1 #define RSET_LEV_MASK (FL_USER13 | FL_USER14 | FL_USER15 | /* FL 13..19 */ \ @@ -424,6 +426,19 @@ set_s_create(int argc, VALUE *argv, VALUE klass) return set; } +static VALUE +set_s_inherited(VALUE klass, VALUE subclass) +{ + if (klass == rb_cSet) { + // When subclassing directly from Set, include the compatibility layer + rb_require("set/subclass_compatible.rb"); + VALUE subclass_compatible = rb_const_get(klass, id_subclass_compatible); + rb_include_module(subclass, subclass_compatible); + rb_extend_object(subclass, rb_const_get(subclass_compatible, id_class_methods)); + } + return Qnil; +} + static void check_set(VALUE arg) { @@ -2187,6 +2202,8 @@ Init_Set(void) id_any_p = rb_intern_const("any?"); id_new = rb_intern_const("new"); id_i_hash = rb_intern_const("@hash"); + id_subclass_compatible = rb_intern_const("SubclassCompatible"); + id_class_methods = rb_intern_const("ClassMethods"); id_set_iter_lev = rb_make_internal_id(); rb_define_alloc_func(rb_cSet, set_s_alloc); @@ -2257,5 +2274,10 @@ Init_Set(void) VALUE compat = rb_define_class_under(rb_cSet, "compatible", rb_cObject); rb_marshal_define_compat(rb_cSet, compat, compat_dumper, compat_loader); + // Create Set::CoreSet before defining inherited, so it does not include + // the backwards compatibility layer. + rb_define_class_under(rb_cSet, "CoreSet", rb_cSet); + rb_define_private_method(rb_singleton_class(rb_cSet), "inherited", set_s_inherited, 1); + rb_provide("set.rb"); } diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 73103a1a123a0b..6dec0d41ae9a48 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -3,8 +3,11 @@ require 'set' class TC_Set < Test::Unit::TestCase - class Set2 < Set + class SetSubclass < Set end + class CoreSetSubclass < Set::CoreSet + end + ALL_SET_CLASSES = [Set, SetSubclass, CoreSetSubclass].freeze def test_marshal set = Set[1, 2, 3] @@ -264,7 +267,7 @@ def test_superset? set.superset?([2]) } - [Set, Set2].each { |klass| + ALL_SET_CLASSES.each { |klass| assert_equal(true, set.superset?(klass[]), klass.name) assert_equal(true, set.superset?(klass[1,2]), klass.name) assert_equal(true, set.superset?(klass[1,2,3]), klass.name) @@ -293,7 +296,7 @@ def test_proper_superset? set.proper_superset?([2]) } - [Set, Set2].each { |klass| + ALL_SET_CLASSES.each { |klass| assert_equal(true, set.proper_superset?(klass[]), klass.name) assert_equal(true, set.proper_superset?(klass[1,2]), klass.name) assert_equal(false, set.proper_superset?(klass[1,2,3]), klass.name) @@ -322,7 +325,7 @@ def test_subset? set.subset?([2]) } - [Set, Set2].each { |klass| + ALL_SET_CLASSES.each { |klass| assert_equal(true, set.subset?(klass[1,2,3,4]), klass.name) assert_equal(true, set.subset?(klass[1,2,3]), klass.name) assert_equal(false, set.subset?(klass[1,2]), klass.name) @@ -351,7 +354,7 @@ def test_proper_subset? set.proper_subset?([2]) } - [Set, Set2].each { |klass| + ALL_SET_CLASSES.each { |klass| assert_equal(true, set.proper_subset?(klass[1,2,3,4]), klass.name) assert_equal(false, set.proper_subset?(klass[1,2,3]), klass.name) assert_equal(false, set.proper_subset?(klass[1,2]), klass.name) @@ -371,7 +374,7 @@ def test_spacecraft_operator assert_nil(set <=> set.to_a) - [Set, Set2].each { |klass| + ALL_SET_CLASSES.each { |klass| assert_equal(-1, set <=> klass[1,2,3,4], klass.name) assert_equal( 0, set <=> klass[3,2,1] , klass.name) assert_equal(nil, set <=> klass[1,2,4] , klass.name) @@ -687,15 +690,17 @@ def test_and end def test_xor - set = Set[1,2,3,4] - ret = set ^ [2,4,5,5] - assert_not_same(set, ret) - assert_equal(Set[1,3,5], ret) - - set2 = Set2[1,2,3,4] - ret2 = set2 ^ [2,4,5,5] - assert_instance_of(Set2, ret2) - assert_equal(Set2[1,3,5], ret2) + ALL_SET_CLASSES.each { |klass| + set = klass[1,2,3,4] + ret = set ^ [2,4,5,5] + assert_not_same(set, ret) + assert_equal(klass[1,3,5], ret) + + set2 = klass[1,2,3,4] + ret2 = set2 ^ [2,4,5,5] + assert_instance_of(klass, ret2) + assert_equal(klass[1,3,5], ret2) + } end def test_eq @@ -847,9 +852,13 @@ def test_inspect set1.add(set2) assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect) - c = Class.new(Set) + c = Class.new(Set::CoreSet) c.set_temporary_name("_MySet") assert_equal('_MySet[1, 2]', c[1, 2].inspect) + + c = Class.new(Set) + c.set_temporary_name("_MySet") + assert_equal('#<_MySet: {1, 2}>', c[1, 2].inspect) end def test_to_s @@ -922,6 +931,27 @@ def test_larger_sets assert_includes set, i end end + + def test_subclass_new_calls_add + c = Class.new(Set) do + def add(o) + super + super(o+1) + end + end + assert_equal([1, 2], c.new([1]).to_a) + end + + def test_subclass_aref_calls_initialize + c = Class.new(Set) do + def initialize(enum) + super + add(1) + end + end + assert_equal([2, 1], c[2].to_a) + end + end class TC_Enumerable < Test::Unit::TestCase From 0b6daad624e36d755f7a6919af2c2eee11353da1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 20 Nov 2025 10:13:04 -0500 Subject: [PATCH 1254/2435] ZJIT: Fix pointer types for SetInstanceVariable --- zjit/src/codegen.rs | 2 +- zjit/src/hir.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 18266b46933e6c..84b3bbd13673ef 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -895,7 +895,7 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: } /// Emit an uncached instance variable store using the interpreter inline cache -fn gen_set_instance_variable(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_constant_cache, val: Opnd, state: &FrameState) { +fn gen_set_instance_variable(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) { gen_incr_counter(asm, Counter::dynamic_setivar_count); // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. gen_prepare_non_leaf_call(jit, asm, state); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2640507e33fab5..5db44025b9cf07 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -712,7 +712,7 @@ pub enum Insn { /// Set `self_val`'s instance variable `id` to `val` SetIvar { self_val: InsnId, id: ID, val: InsnId, state: InsnId }, /// Set `self_val`'s instance variable `id` to `val` using the interpreter inline cache - SetInstanceVariable { self_val: InsnId, id: ID, ic: *const iseq_inline_constant_cache, val: InsnId, state: InsnId }, + SetInstanceVariable { self_val: InsnId, id: ID, ic: *const iseq_inline_iv_cache_entry, val: InsnId, state: InsnId }, /// Check whether an instance variable exists on `self_val` DefinedIvar { self_val: InsnId, id: ID, pushval: VALUE, state: InsnId }, From f8cb9f320ceddbfe6feffb6410bb6212ed27673c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 20 Nov 2025 10:28:23 -0500 Subject: [PATCH 1255/2435] ZJIT: Put optional interpreter cache on both GetIvar and SetIvar --- vm_insnhelper.c | 6 ++++++ zjit/src/codegen.rs | 32 ++++++++++++++++---------------- zjit/src/cruby.rs | 1 + zjit/src/hir.rs | 35 +++++++++++++++-------------------- zjit/src/hir/opt_tests.rs | 2 +- zjit/src/hir/tests.rs | 2 +- 6 files changed, 40 insertions(+), 38 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 7626d461352c8d..ff686d047a163d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1723,6 +1723,12 @@ rb_vm_setinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, VALUE val, IV vm_setinstancevariable(iseq, obj, id, val, ic); } +VALUE +rb_vm_getinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, IVC ic) +{ + return vm_getinstancevariable(iseq, obj, id, ic); +} + static VALUE vm_throw_continue(const rb_execution_context_t *ec, VALUE err) { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 84b3bbd13673ef..8838a72fc887bd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -428,7 +428,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::CCallVariadic { cfunc, recv, args, name, cme, state, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } - Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), + Insn::GetIvar { self_val, id, ic, state: _ } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)), &Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp), @@ -436,8 +436,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)), Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), - Insn::SetIvar { self_val, id, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, opnd!(val), &function.frame_state(*state))), - Insn::SetInstanceVariable { self_val, id, ic, val, state } => no_output!(gen_set_instance_variable(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))), + Insn::SetIvar { self_val, id, ic, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))), Insn::FixnumBitCheck { val, index } => gen_fixnum_bit_check(asm, opnd!(val), *index), Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), @@ -881,26 +880,27 @@ fn gen_ccall_variadic( } /// Emit an uncached instance variable lookup -fn gen_getivar(asm: &mut Assembler, recv: Opnd, id: ID) -> Opnd { +fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry) -> Opnd { gen_incr_counter(asm, Counter::dynamic_getivar_count); - asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) + if ic.is_null() { + asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) + } else { + let iseq = Opnd::Value(jit.iseq.into()); + asm_ccall!(asm, rb_vm_getinstancevariable, iseq, recv, id.0.into(), Opnd::const_ptr(ic)) + } } /// Emit an uncached instance variable store -fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd, state: &FrameState) { +fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) { gen_incr_counter(asm, Counter::dynamic_setivar_count); // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. gen_prepare_non_leaf_call(jit, asm, state); - asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); -} - -/// Emit an uncached instance variable store using the interpreter inline cache -fn gen_set_instance_variable(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) { - gen_incr_counter(asm, Counter::dynamic_setivar_count); - // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. - gen_prepare_non_leaf_call(jit, asm, state); - let iseq = Opnd::Value(jit.iseq.into()); - asm_ccall!(asm, rb_vm_setinstancevariable, iseq, recv, id.0.into(), val, Opnd::const_ptr(ic)); + if ic.is_null() { + asm_ccall!(asm, rb_ivar_set, recv, id.0.into(), val); + } else { + let iseq = Opnd::Value(jit.iseq.into()); + asm_ccall!(asm, rb_vm_setinstancevariable, iseq, recv, id.0.into(), val, Opnd::const_ptr(ic)); + } } fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 61c25a4092bdc4..a854f2e07c0667 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -147,6 +147,7 @@ unsafe extern "C" { ) -> bool; pub fn rb_vm_set_ivar_id(obj: VALUE, idx: u32, val: VALUE) -> VALUE; pub fn rb_vm_setinstancevariable(iseq: IseqPtr, obj: VALUE, id: ID, val: VALUE, ic: IVC); + pub fn rb_vm_getinstancevariable(iseq: IseqPtr, obj: VALUE, id: ID, ic: IVC) -> VALUE; pub fn rb_aliased_callable_method_entry( me: *const rb_callable_method_entry_t, ) -> *const rb_callable_method_entry_t; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5db44025b9cf07..bbe5dd3435b75b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -707,12 +707,10 @@ pub enum Insn { SetGlobal { id: ID, val: InsnId, state: InsnId }, //NewObject? - /// Get an instance variable `id` from `self_val` - GetIvar { self_val: InsnId, id: ID, state: InsnId }, - /// Set `self_val`'s instance variable `id` to `val` - SetIvar { self_val: InsnId, id: ID, val: InsnId, state: InsnId }, - /// Set `self_val`'s instance variable `id` to `val` using the interpreter inline cache - SetInstanceVariable { self_val: InsnId, id: ID, ic: *const iseq_inline_iv_cache_entry, val: InsnId, state: InsnId }, + /// Get an instance variable `id` from `self_val`, using the inline cache `ic` if present + GetIvar { self_val: InsnId, id: ID, ic: *const iseq_inline_iv_cache_entry, state: InsnId }, + /// Set `self_val`'s instance variable `id` to `val`, using the inline cache `ic` if present + SetIvar { self_val: InsnId, id: ID, val: InsnId, ic: *const iseq_inline_iv_cache_entry, state: InsnId }, /// Check whether an instance variable exists on `self_val` DefinedIvar { self_val: InsnId, id: ID, pushval: VALUE, state: InsnId }, @@ -912,7 +910,7 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::SetInstanceVariable { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => false, _ => true, } } @@ -1248,7 +1246,6 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:p}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), &Insn::WriteBarrier { recv, val } => write!(f, "WriteBarrier {recv}, {val}"), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), - Insn::SetInstanceVariable { self_val, id, val, .. } => write!(f, "SetInstanceVariable {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()), &Insn::GetLocal { level, ep_offset, use_sp: true, rest_param } => write!(f, "GetLocal l{level}, SP@{}{}", ep_offset + 1, if rest_param { ", *" } else { "" }), @@ -1891,12 +1888,11 @@ impl Function { &ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) }, &DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, - &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, + &GetIvar { self_val, id, ic, state } => GetIvar { self_val: find!(self_val), id, ic, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, &StoreField { recv, id, offset, val } => StoreField { recv: find!(recv), id, offset, val: find!(val) }, &WriteBarrier { recv, val } => WriteBarrier { recv: find!(recv), val: find!(val) }, - &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state }, - &SetInstanceVariable { self_val, id, ic, val, state } => SetInstanceVariable { self_val: find!(self_val), id, ic, val: find!(val), state }, + &SetIvar { self_val, id, ic, val, state } => SetIvar { self_val: find!(self_val), id, ic, val: find!(val), state }, &GetClassVar { id, ic, state } => GetClassVar { id, ic, state }, &SetClassVar { id, val, ic, state } => SetClassVar { id, val: find!(val), ic, state }, &SetLocal { val, ep_offset, level } => SetLocal { val: find!(val), ep_offset, level }, @@ -1951,7 +1947,7 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } - | Insn::SetInstanceVariable { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -2450,7 +2446,7 @@ impl Function { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); } } - let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state }); + let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, ic: std::ptr::null(), state }); self.make_equal_to(insn_id, getivar); } else if let (VM_METHOD_TYPE_ATTRSET, &[val]) = (def_type, args.as_slice()) { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -2467,7 +2463,7 @@ impl Function { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); } } - self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state }); + self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state }); self.make_equal_to(insn_id, val); } else if def_type == VM_METHOD_TYPE_OPTIMIZED { let opt_type: OptimizedMethodType = unsafe { get_cme_def_body_optimized_type(cme) }.into(); @@ -2750,7 +2746,7 @@ impl Function { assert!(self.blocks[block.0].insns.is_empty()); for insn_id in old_insns { match self.find(insn_id) { - Insn::GetIvar { self_val, id, state } => { + Insn::GetIvar { self_val, id, ic: _, state } => { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info @@ -3503,8 +3499,7 @@ impl Function { worklist.push_back(self_val); worklist.push_back(state); } - &Insn::SetIvar { self_val, val, state, .. } - | &Insn::SetInstanceVariable { self_val, val, state, .. } => { + &Insn::SetIvar { self_val, val, state, .. } => { worklist.push_back(self_val); worklist.push_back(val); worklist.push_back(state); @@ -4082,7 +4077,6 @@ impl Function { } // Instructions with 2 Ruby object operands Insn::SetIvar { self_val: left, val: right, .. } - | Insn::SetInstanceVariable { self_val: left, val: right, .. } | Insn::NewRange { low: left, high: right, .. } | Insn::AnyToString { val: left, str: right, .. } | Insn::WriteBarrier { recv: left, val: right } => { @@ -5450,11 +5444,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_getinstancevariable => { let id = ID(get_arg(pc, 0).as_u64()); + let ic = get_arg(pc, 1).as_ptr(); // ic is in arg 1 // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_getivar // TODO: We only really need this if self_val is a class/module fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state: exit_id }); - let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, state: exit_id }); + let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); state.stack_push(result); } YARVINSN_setinstancevariable => { @@ -5464,7 +5459,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // TODO: We only really need this if self_val is a class/module fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state: exit_id }); let val = state.stack_pop()?; - fun.push_insn(block, Insn::SetInstanceVariable { self_val: self_param, id, ic, val, state: exit_id }); + fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); } YARVINSN_getclassvariable => { let id = ID(get_arg(pc, 0).as_u64()); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 19f0e91b47e82b..70efa000761acf 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3367,7 +3367,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - SetInstanceVariable v6, :@foo, v10 + SetIvar v6, :@foo, v10 CheckInterrupts Return v10 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index a00ca97e85a8b0..5e6ec118922b02 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2241,7 +2241,7 @@ pub mod hir_build_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - SetInstanceVariable v6, :@foo, v10 + SetIvar v6, :@foo, v10 CheckInterrupts Return v10 "); From 48027256cf9d3e3bf12603184448cf88406b92d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 20 Nov 2025 16:41:45 +0100 Subject: [PATCH 1256/2435] [ruby/json] Remove unused symbols https://github.com/ruby/json/commit/9364d0c761 --- ext/json/generator/generator.c | 6 +----- ext/json/parser/parser.c | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 8930213b939a0d..fb8424dd86d736 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -38,7 +38,7 @@ typedef struct JSON_Generator_StateStruct { static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8; -static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode; +static ID i_to_s, i_to_json, i_new, i_encode; static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key, sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json; @@ -2173,10 +2173,6 @@ void Init_generator(void) i_to_s = rb_intern("to_s"); i_to_json = rb_intern("to_json"); i_new = rb_intern("new"); - i_pack = rb_intern("pack"); - i_unpack = rb_intern("unpack"); - i_create_id = rb_intern("create_id"); - i_extend = rb_intern("extend"); i_encode = rb_intern("encode"); sym_indent = ID2SYM(rb_intern("indent")); diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 62ee1f24e71c40..a1d0265a919370 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -5,8 +5,7 @@ static VALUE mJSON, eNestingError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity; -static ID i_chr, i_aset, i_aref, - i_leftshift, i_new, i_try_convert, i_uminus, i_encode; +static ID i_new, i_try_convert, i_uminus, i_encode; static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, sym_decimal_class, sym_on_load, sym_allow_duplicate_key; @@ -1612,10 +1611,6 @@ void Init_parser(void) sym_decimal_class = ID2SYM(rb_intern("decimal_class")); sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); - i_chr = rb_intern("chr"); - i_aset = rb_intern("[]="); - i_aref = rb_intern("[]"); - i_leftshift = rb_intern("<<"); i_new = rb_intern("new"); i_try_convert = rb_intern("try_convert"); i_uminus = rb_intern("-@"); From a8f269a2c665c0a471ecb28b3265cb4eb8a7ca2e Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 20 Nov 2025 14:47:01 -0500 Subject: [PATCH 1257/2435] ZJIT: Deduplicate successor and predecessor sets (#15263) Fixes https://github.com/Shopify/ruby/issues/877 I didn't consider the ability to have the successor or predecessor sets having duplicates when originally crafting the Iongraph support PR, but have added this to prevent that happening in the future. I don't think it interferes with the underlying Iongraph implementation, but it doesn't really make sense. I think this kind of behaviour happens when there are multiple jump instructions that go to the same basic block within a given block. --- zjit/src/hir.rs | 13 ++++++++++--- zjit/src/hir/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bbe5dd3435b75b..face61f1f67a7b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -9,7 +9,7 @@ use crate::{ cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json }; use std::{ - cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter + cell::RefCell, collections::{BTreeSet, HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter }; use crate::hir_type::{Type, types}; use crate::bitset::BitSet; @@ -5849,7 +5849,13 @@ impl<'a> ControlFlowInfo<'a> { // Since ZJIT uses extended basic blocks, one must check all instructions // for their ability to jump to another basic block, rather than just // the instructions at the end of a given basic block. - let successors: Vec = block + // + // Use BTreeSet to avoid duplicates and maintain an ordering. Also + // `BTreeSet` provides conversion trivially back to an `Vec`. + // Ordering is important so that the expect tests that serialize the predecessors + // and successors don't fail intermittently. + // todo(aidenfoxivey): Use `BlockSet` in lieu of `BTreeSet` + let successors: BTreeSet = block .insns .iter() .map(|&insn_id| uf.find_const(insn_id)) @@ -5867,7 +5873,8 @@ impl<'a> ControlFlowInfo<'a> { } // Store successors for this block. - successor_map.insert(block_id, successors); + // Convert successors from a `BTreeSet` to a `Vec`. + successor_map.insert(block_id, successors.iter().copied().collect()); } Self { diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 5e6ec118922b02..b487352748601a 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3484,6 +3484,27 @@ pub mod hir_build_tests { assert!(cfi.is_succeeded_by(bb1, bb0)); assert!(cfi.is_succeeded_by(bb3, bb1)); } + + #[test] + fn test_cfi_deduplicated_successors_and_predecessors() { + let mut function = Function::new(std::ptr::null()); + + let bb0 = function.entry_block; + let bb1 = function.new_block(0); + + // Construct two separate jump instructions. + let v1 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) }); + let _ = function.push_insn(bb0, Insn::IfTrue { val: v1, target: edge(bb1)}); + function.push_insn(bb0, Insn::Jump(edge(bb1))); + + let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) }); + function.push_insn(bb1, Insn::Return { val: retval }); + + let cfi = ControlFlowInfo::new(&function); + + assert_eq!(cfi.predecessors(bb1).collect::>().len(), 1); + assert_eq!(cfi.successors(bb0).collect::>().len(), 1); + } } /// Test dominator set computations. From 0b4420bfd5f32fd715704f9adcf2320971f05ab3 Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Thu, 20 Nov 2025 11:02:33 +0000 Subject: [PATCH 1258/2435] [ruby/prism] Use memmove for overlapping memory ranges Fixes https://github.com/ruby/prism/pull/3736. https://github.com/ruby/prism/commit/1f5f192ab7 --- prism/prism.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index a87932f1b77a00..45817cdd8c05b2 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13467,7 +13467,7 @@ parse_target_implicit_parameter(pm_parser_t *parser, pm_node_t *node) { // remaining nodes down to fill the gap. This is extremely unlikely // to happen. if (index != implicit_parameters->size - 1) { - memcpy(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); + memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); } implicit_parameters->size--; From ba47c2f03305be10b7c28f7ff6783a533b57ef15 Mon Sep 17 00:00:00 2001 From: Thiago Araujo Date: Wed, 19 Nov 2025 21:05:29 -0700 Subject: [PATCH 1259/2435] [ruby/prism] Add tests to `regexp_encoding_option_mismatch` related to #2667 https://github.com/ruby/prism/commit/44f075bae4 --- test/prism/errors_test.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 9abed9265212d0..bd7a8a638166c5 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -64,6 +64,24 @@ def test_invalid_message_name assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name end + def test_regexp_encoding_option_mismatch_error + # UTF-8 char with ASCII-8BIT modifier + result = Prism.parse('/Ȃ/n') + assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch + + # UTF-8 char with EUC-JP modifier + result = Prism.parse('/Ȃ/e') + assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch + + # UTF-8 char with Windows-31J modifier + result = Prism.parse('/Ȃ/s') + assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch + + # UTF-8 char with UTF-8 modifier + result = Prism.parse('/Ȃ/u') + assert_empty result.errors + end + private def assert_errors(filepath, version) From cb9c7a6a0a451fc158055145d0bce4891b62e32f Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Nov 2025 12:08:43 -0500 Subject: [PATCH 1260/2435] [ruby/rubygems] Improve error messages and handling in tests This is a first pass to improve the way errors are handled and raised in bundler's tests. The goal is to clean up tests and modernize them - these were some obvious areas that could be cleaned up. - Instead of raising "ZOMG" in the load error tests, it now tests for the actual error and gem raising. - Improve error messages where applicable. - All errors raise a specific error class, rather than falling back to a default and just setting a message. - Removed arguments and `bundle_dir` option from `TheBundle` class as it wasn't actually used so therefore we don't need to raise an error for extra arguments. - Removed error from `BundlerBuilder`, as it won't work if it's not `bundler`, also it never uses `name`. The only reaon `name` is passed in is because of metaprogramming on loading the right builder. I think that should eventually be refactored. - Replaced and removed `update_repo3` and `update_repo4` in favor of just `build_repo3` and `build_repo4`. Rather than tell someone writing tests to use a different method, automatically use the right method. https://github.com/ruby/rubygems/commit/68c39c8451 --- spec/bundler/bundler/retry_spec.rb | 2 +- spec/bundler/commands/exec_spec.rb | 4 +-- spec/bundler/commands/install_spec.rb | 7 ++-- spec/bundler/commands/lock_spec.rb | 4 +-- spec/bundler/commands/update_spec.rb | 6 ++-- spec/bundler/install/gemfile/gemspec_spec.rb | 7 ++-- spec/bundler/install/gemfile/groups_spec.rb | 6 ++-- spec/bundler/install/gemfile/sources_spec.rb | 2 +- .../install/gems/compact_index_spec.rb | 6 ++-- .../install/gems/native_extensions_spec.rb | 8 ++--- spec/bundler/install/gems/standalone_spec.rb | 8 ++--- spec/bundler/plugins/install_spec.rb | 2 +- spec/bundler/realworld/edgecases_spec.rb | 2 +- spec/bundler/runtime/setup_spec.rb | 32 +++++++++++-------- .../artifice/compact_index_etag_match.rb | 2 +- spec/bundler/support/builders.rb | 26 ++++++++------- spec/bundler/support/helpers.rb | 12 +++---- spec/bundler/support/matchers.rb | 2 +- spec/bundler/support/the_bundle.rb | 8 ++--- 19 files changed, 76 insertions(+), 70 deletions(-) diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb index ffbc07807429e6..7481622a967d9d 100644 --- a/spec/bundler/bundler/retry_spec.rb +++ b/spec/bundler/bundler/retry_spec.rb @@ -12,7 +12,7 @@ end it "returns the first valid result" do - jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }] + jobs = [proc { raise "job 1 failed" }, proc { :bar }, proc { raise "job 2 failed" }] attempts = 0 result = Bundler::Retry.new(nil, nil, 3).attempt do attempts += 1 diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 03f1d839c76c9a..1ac308bdda4bfc 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -1034,7 +1034,7 @@ def bin_path(a,b,c) puts 'Started' # For process sync STDOUT.flush sleep 1 # ignore quality_spec - raise "Didn't receive INT at all" + raise RuntimeError, "Didn't receive expected INT" end.join rescue Interrupt puts "foo" @@ -1218,7 +1218,7 @@ def require(path) build_repo4 do build_gem "openssl", openssl_version do |s| s.write("lib/openssl.rb", <<-RUBY) - raise "custom openssl should not be loaded, it's not in the gemfile!" + raise ArgumentError, "custom openssl should not be loaded" RUBY end end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 3ab7d4ab880059..0dbe950f87b6d7 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -688,13 +688,12 @@ it "fails gracefully when downloading an invalid specification from the full index" do build_repo2(build_compact_index: false) do build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s| - bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] + invalid_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] s. instance_variable_get(:@spec). - instance_variable_set(:@dependencies, bad_deps) - - raise "failed to set bad deps" unless s.dependencies == bad_deps + instance_variable_set(:@dependencies, invalid_deps) end + build_gem "ruby-ajp", "1.0.0" end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 6ba69d466a9b8c..ab1926734c1e78 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -834,7 +834,7 @@ bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } expect(lockfile).to end_with("BUNDLED WITH\n 55\n") - update_repo4 do + build_repo4 do build_gem "bundler", "99" end @@ -1456,7 +1456,7 @@ before do gemfile_with_rails_weakling_and_foo_from_repo4 - update_repo4 do + build_repo4 do build_gem "foo", "2.0" end diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 61d8ece2798a75..cdaeb75c4a3399 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -247,7 +247,7 @@ expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") - update_repo4 do + build_repo4 do build_gem "slim", "4.0.0" do |s| s.add_dependency "tilt", [">= 2.0.6", "< 2.1"] end @@ -572,7 +572,7 @@ expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") - update_repo4 do + build_repo4 do build_gem "b", "2.0" do |s| s.add_dependency "c", "< 2" end @@ -976,7 +976,7 @@ bundle "update", all: true expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) - update_repo4 do + build_repo4 do build_gem "foo", "2.0" end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index daa977fc9b7f9f..3d9766d21ff66f 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -420,12 +420,13 @@ end build_lib "foo", path: bundled_app do |s| - if platform_specific_type == :runtime + case platform_specific_type + when :runtime s.add_runtime_dependency dependency - elsif platform_specific_type == :development + when :development s.add_development_dependency dependency else - raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime" + raise ArgumentError, "wrong dependency type #{platform_specific_type}, can only be :development or :runtime" end end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index 32de3f2be2dcc3..4727d5ef9b02cf 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -25,7 +25,7 @@ puts ACTIVESUPPORT R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- activesupport/) end it "installs gems with inline :groups into those groups" do @@ -36,7 +36,7 @@ puts THIN R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- thin/) end it "sets up everything if Bundler.setup is used with no groups" do @@ -57,7 +57,7 @@ puts THIN RUBY - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- thin/) end it "sets up old groups when they have previously been removed" do diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index bdc61c2b2694e1..c0b4d98f1c5cc6 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -570,7 +570,7 @@ bundle :install, artifice: "compact_index" # And then we add some new versions... - update_repo4 do + build_repo4 do build_gem "foo", "0.2" build_gem "bar", "0.3" end diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index cd64f85f9295a7..bb4d4011f5b94f 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -770,7 +770,7 @@ def start gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end @@ -811,7 +811,7 @@ def start gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end @@ -833,7 +833,7 @@ def start gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index 874818fa874f92..7f230d132b9eab 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -9,7 +9,7 @@ require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" + raise ArgumentError unless with_config("c_extension") == "hello" create_makefile(name) E @@ -53,7 +53,7 @@ require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" + raise ArgumentError unless with_config("c_extension") == "hello" create_makefile(name) E @@ -97,7 +97,7 @@ require "mkmf" name = "c_extension_bundle_#{n}" dir_config(name) - raise "OMG" unless with_config("c_extension_#{n}") == "#{n}" + raise ArgumentError unless with_config("c_extension_#{n}") == "#{n}" create_makefile(name) E @@ -149,7 +149,7 @@ require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola" + raise ArgumentError unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola" create_makefile(name) E diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index d5f6c896cd6642..37997ffe482409 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -385,7 +385,7 @@ RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end it "allows `without` configuration to limit the groups used in a standalone" do @@ -403,7 +403,7 @@ RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end it "allows `path` configuration to change the location of the standalone bundle" do @@ -437,7 +437,7 @@ RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end end @@ -519,6 +519,6 @@ RUBY expect(out).to eq("1.0.0") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 0cddeb09185bc7..6cace961f5228b 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -168,7 +168,7 @@ def exec(command, args) build_repo2 do build_plugin "chaplin" do |s| s.write "plugins.rb", <<-RUBY - raise "I got you man" + raise RuntimeError, "threw exception on load" RUBY end end diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index 86b4c91a073685..391aa0cef6575a 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -16,7 +16,7 @@ def rubygems_version(name, requirement) index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last end if rubygem.nil? - raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ + raise ArgumentError, "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ "Found specs:\n\#{index.send(:specs).inspect}" end puts "#{name} (\#{rubygem.version})" diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 9268d9d1cc4545..1ffaffef0ed20e 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -728,46 +728,52 @@ def clean_load_path(lp) G run <<-R - File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f| + File.open(File.join(Gem.dir, "specifications", "invalid.gemspec"), "w") do |f| f.write <<-RUBY # -*- encoding: utf-8 -*- -# stub: broken 1.0.0 ruby lib +# stub: invalid 1.0.0 ruby lib Gem::Specification.new do |s| - s.name = "broken" + s.name = "invalid" s.version = "1.0.0" - raise "BROKEN GEMSPEC" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.add_dependency "nonexistent-gem", "~> 999.999.999" + s.validate! end RUBY end R run <<-R - File.open(File.join(Gem.dir, "specifications", "broken-ext.gemspec"), "w") do |f| + File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f| f.write <<-RUBY # -*- encoding: utf-8 -*- -# stub: broken-ext 1.0.0 ruby lib +# stub: invalid-ext 1.0.0 ruby lib # stub: a.ext\\0b.ext Gem::Specification.new do |s| - s.name = "broken-ext" + s.name = "invalid-ext" s.version = "1.0.0" - raise "BROKEN GEMSPEC EXT" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.required_ruby_version = "~> 0.8.0" + s.validate! end RUBY end # Need to write the gem.build_complete file, # otherwise the full spec is loaded to check the installed_by_version extensions_dir = Gem.default_ext_dir_for(Gem.dir) || File.join(Gem.dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version) - Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "broken-ext-1.0.0")) - File.open(File.join(extensions_dir, "broken-ext-1.0.0", "gem.build_complete"), "w") {} + Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "invalid-ext-1.0.0")) + File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {} R run <<-R - puts "WIN" + puts "Success" R - expect(out).to eq("WIN") + expect(out).to eq("Success") end it "ignores empty gem paths" do @@ -1151,7 +1157,7 @@ def clean_load_path(lp) bundler_module = class << Bundler; self; end bundler_module.send(:remove_method, :require) def Bundler.require(path) - raise "LOSE" + raise StandardError, "didn't use binding from top level" end Bundler.load RUBY diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb index 08d7b5ec539129..6c621660513b1f 100644 --- a/spec/bundler/support/artifice/compact_index_etag_match.rb +++ b/spec/bundler/support/artifice/compact_index_etag_match.rb @@ -4,7 +4,7 @@ class CompactIndexEtagMatch < CompactIndexAPI get "/versions" do - raise "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] + raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] headers "ETag" => env["HTTP_IF_NONE_MATCH"] status 304 body "" diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 31d4f30a3b96c0..6087ea8cc8c652 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -187,17 +187,25 @@ def build_repo2(**kwargs, &blk) # A repo that has no pre-installed gems included. (The caller completely # determines the contents with the block.) + # + # If the repo already exists, `#update_repo` will be called. def build_repo3(**kwargs, &blk) - raise "gem_repo3 already exists -- use update_repo3 instead" if File.exist?(gem_repo3) - build_repo gem_repo3, **kwargs, &blk + if File.exist?(gem_repo3) + update_repo(gem_repo3, &blk) + else + build_repo gem_repo3, **kwargs, &blk + end end # Like build_repo3, this is a repo that has no pre-installed gems included. - # We have two different methods for situations where two different empty - # sources are needed. + # + # If the repo already exists, `#udpate_repo` will be called def build_repo4(**kwargs, &blk) - raise "gem_repo4 already exists -- use update_repo4 instead" if File.exist?(gem_repo4) - build_repo gem_repo4, **kwargs, &blk + if File.exist?(gem_repo4) + update_repo gem_repo4, &blk + else + build_repo gem_repo4, **kwargs, &blk + end end def update_repo2(**kwargs, &blk) @@ -208,10 +216,6 @@ def update_repo3(&blk) update_repo(gem_repo3, &blk) end - def update_repo4(&blk) - update_repo(gem_repo4, &blk) - end - def build_security_repo build_repo security_repo do build_gem "myrack" @@ -420,8 +424,6 @@ def required_ruby_version=(*reqs) class BundlerBuilder def initialize(context, name, version) - raise "can only build bundler" unless name == "bundler" - @context = context @spec = Spec::Path.loaded_gemspec.dup @spec.version = version || Bundler::VERSION diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 12ff09b714a0fb..52e6ff5d9a3140 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -28,8 +28,8 @@ def reset! Gem.clear_paths end - def the_bundle(*args) - TheBundle.new(*args) + def the_bundle + TheBundle.new end MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ @@ -54,7 +54,7 @@ def load_error_run(ruby, name, *args) begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end RUBY opts = args.last.is_a?(Hash) ? args.pop : {} @@ -132,7 +132,7 @@ def load_error_ruby(ruby, name, opts = {}) begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end R end @@ -324,7 +324,7 @@ def self.install_dev_bundler end def install_gem(path, install_dir, default = false) - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" @@ -415,7 +415,7 @@ def cache_gems(*gems, gem_repo: gem_repo1) gems.each do |g| path = "#{gem_repo}/gems/#{g}.gem" - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) FileUtils.cp(path, "#{bundled_app}/vendor/cache") end end diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index 9f311fc0d77c39..5a3c38a4db361e 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -52,7 +52,7 @@ def failing_matcher end def self.define_compound_matcher(matcher, preconditions, &declarations) - raise "Must have preconditions to define a compound matcher" if preconditions.empty? + raise ArgumentError, "Must have preconditions to define a compound matcher" if preconditions.empty? define_method(matcher) do |*expected, &block_arg| Precondition.new( RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg), diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb index bda717f3b00bf3..452abd7d410171 100644 --- a/spec/bundler/support/the_bundle.rb +++ b/spec/bundler/support/the_bundle.rb @@ -8,10 +8,8 @@ class TheBundle attr_accessor :bundle_dir - def initialize(opts = {}) - opts = opts.dup - @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app }) - raise "Too many options! #{opts}" unless opts.empty? + def initialize + @bundle_dir = Pathname.new(bundled_app) end def to_s @@ -28,7 +26,7 @@ def lockfile end def locked_gems - raise "Cannot read lockfile if it doesn't exist" unless locked? + raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked? Bundler::LockfileParser.new(lockfile.read) end From 59e0489248036f5923e04fdf16ce3d8244ed038d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 20 Nov 2025 16:03:58 -0600 Subject: [PATCH 1261/2435] [DOC] Tweaks for String#upcase (#15244) --- doc/string/upcase.rdoc | 20 ++++++++++++++++++++ string.c | 11 +---------- 2 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 doc/string/upcase.rdoc diff --git a/doc/string/upcase.rdoc b/doc/string/upcase.rdoc new file mode 100644 index 00000000000000..5a9cce217f7972 --- /dev/null +++ b/doc/string/upcase.rdoc @@ -0,0 +1,20 @@ +Returns a new string containing the upcased characters in +self+: + + 'Hello, World!'.upcase # => "HELLO, WORLD!" + +The sizes of +self+ and the upcased result may differ: + + s = 'Straße' + s.size # => 6 + s.upcase # => "STRASSE" + s.upcase.size # => 7 + +Some characters (and some character sets) do not have upcased and downcased versions: + + s = 'よろしくお願いします' + s.upcase == s # => true + +The casing may be affected by the given +mapping+; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index d78d7320be2b2b..c4ac00c3423e74 100644 --- a/string.c +++ b/string.c @@ -8001,16 +8001,7 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) * call-seq: * upcase(mapping) -> string * - * Returns a string containing the upcased characters in +self+: - * - * s = 'Hello World!' # => "Hello World!" - * s.upcase # => "HELLO WORLD!" - * - * The casing may be affected by the given +mapping+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#upcase!, String#downcase, String#downcase!. - * + * :include: doc/string/upcase.rdoc */ static VALUE From a4a99a24e8299f9595cb41591f3252e6082d744f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Wed, 19 Nov 2025 18:29:01 +0000 Subject: [PATCH 1262/2435] [DOC] TWeaks for String#upcase! --- string.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/string.c b/string.c index c4ac00c3423e74..9f5cd83c0da72e 100644 --- a/string.c +++ b/string.c @@ -7959,19 +7959,12 @@ upcase_single(VALUE str) * call-seq: * upcase!(mapping) -> self or nil * - * Upcases the characters in +self+; - * returns +self+ if any changes were made, +nil+ otherwise: + * Like String#upcase, except that: * - * s = 'Hello World!' # => "Hello World!" - * s.upcase! # => "HELLO WORLD!" - * s # => "HELLO WORLD!" - * s.upcase! # => nil - * - * The casing may be affected by the given +mapping+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#upcase, String#downcase, String#downcase!. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From 9b87a0b9b667ce12e9e6245d4eda40b5dfdeb5f9 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 4 Nov 2025 17:15:59 -0800 Subject: [PATCH 1263/2435] Fix missing write barrier on namespace classext Found by wbcheck It seems like here the classext was associated with the class, but it already had Ruby objects attached. rb_gc_writebarrier_remember works around that issue, but I suspect if we enabled autocompaction the values copied into the classext before it was attached could be broken. --- class.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/class.c b/class.c index 34bc7d100538e7..2f94b801471104 100644 --- a/class.c +++ b/class.c @@ -183,6 +183,12 @@ rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext) }; st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, rb_class_set_box_classext_update, (st_data_t)&args); + + // FIXME: This is done here because this is the first time the objects in + // the classext are exposed via this class. It's likely that if GC + // compaction occurred between the VALUEs being copied in and this + // writebarrier trigger the values will be stale. + rb_gc_writebarrier_remember(obj); } RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; From bd60600d00f13234cad83c9c5af5b6607a4b0fba Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Wed, 19 Nov 2025 15:59:08 +0100 Subject: [PATCH 1264/2435] [ruby/rubygems] Run git operations in parallel to speed things up: - ### Problem When you have a Gemfile that contains git gems, each repository will be fetched one by one. This is extremelly slow. A simple Gemfile with 5 git gems (small repositories) can take up to 10 seconds just to fetch the repos. We can speed this up by running multiple git process and fetching repositories silmutaneously. ### Solution The repositories are fetched in Bundler when `Source::Git#specs` is called. The problem is that `source.specs` is called in various places depending on Gemfile. I think the issue is that calling `source.specs` feels like that as a "side effect" we are going to clone repositories. I believe that fetching repositories should be an explicit call. For instance: ```ruby source "https://rubygems.org" gem "foo", github: "foo/foo" # The repository foo will be fetched as a side effect to the call to `source.spec_names` # https://github.com/ruby/rubygems/blob/6cc7d71dac3d0275c9727cf200c7acfbf6c78d37/bundler/lib/bundler/source_map.rb#L21 ``` ```ruby source "https://rubygems.org" gem "bar", source: "https://example.org" gem "foo", github: "foo/foo" # The repository foo will be fetched on a different codepath # https://github.com/ruby/rubygems/blob/6cc7d71dac3d0275c9727cf200c7acfbf6c78d37/bundler/lib/bundler/source/rubygems_aggregate.rb#L35 # That is because the gem "bar" has a source that doesn't have the `/dependencies` API # endpoint and therefore Bundler enters a different branch condition. ``` I opted to add a self explanatory call to fetch the git source repositories just before we start the resolution, and *just* before any other calls to `source.specs` is performed. https://github.com/ruby/rubygems/commit/f0ef526f23 --- lib/bundler/definition.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 3c8c13b1303171..21f3760e6d9d3f 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "lockfile_parser" +require_relative "worker" module Bundler class Definition @@ -1100,7 +1101,23 @@ def source_requirements @source_requirements ||= find_source_requirements end + def preload_git_source_worker + @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs }) + end + + def preload_git_sources + sources.git_sources.each {|source| preload_git_source_worker.enq(source) } + ensure + preload_git_source_worker.stop + end + def find_source_requirements + if Gem.ruby_version >= "3.3" + # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to + # fetch git repositories one by one. + preload_git_sources + end + # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) From 409c004affa30efbbfd384a9cd645f7969ccc11a Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Wed, 19 Nov 2025 23:15:41 +0100 Subject: [PATCH 1265/2435] [ruby/rubygems] Make the Bundler logger thread safe: - The Logger is not thread safe when calling `with_level`. This now becomes problematic because we are using multiple threads during the resolution phase in order to fetch git gems. https://github.com/ruby/rubygems/commit/380653ae74 --- lib/bundler/ui/shell.rb | 16 +++++++++------ spec/bundler/bundler/ui/shell_spec.rb | 28 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index 6f080b64598f02..b836208da8e30c 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -17,6 +17,7 @@ def initialize(options = {}) @level = ENV["DEBUG"] ? "debug" : "info" @warning_history = [] @output_stream = :stdout + @thread_safe_logger_key = "logger_level_#{object_id}" end def add_color(string, *color) @@ -97,11 +98,13 @@ def level=(level) end def level(name = nil) - return @level unless name + current_level = Thread.current.thread_variable_get(@thread_safe_logger_key) || @level + return current_level unless name + unless index = LEVELS.index(name) raise "#{name.inspect} is not a valid level" end - index <= LEVELS.index(@level) + index <= LEVELS.index(current_level) end def output_stream=(symbol) @@ -167,12 +170,13 @@ def word_wrap(text, line_width = Thor::Terminal.terminal_width) end * "\n" end - def with_level(level) - original = @level - @level = level + def with_level(desired_level) + old_level = level + Thread.current.thread_variable_set(@thread_safe_logger_key, desired_level) + yield ensure - @level = original + Thread.current.thread_variable_set(@thread_safe_logger_key, old_level) end def with_output_stream(symbol) diff --git a/spec/bundler/bundler/ui/shell_spec.rb b/spec/bundler/bundler/ui/shell_spec.rb index 422c850a6536e6..83f147191ef139 100644 --- a/spec/bundler/bundler/ui/shell_spec.rb +++ b/spec/bundler/bundler/ui/shell_spec.rb @@ -81,4 +81,32 @@ end end end + + describe "threads" do + it "is thread safe when using with_level" do + stop_thr1 = false + stop_thr2 = false + + expect(subject.level).to eq("debug") + + thr1 = Thread.new do + subject.silence do + sleep(0.1) until stop_thr1 + end + + stop_thr2 = true + end + + thr2 = Thread.new do + subject.silence do + stop_thr1 = true + sleep(0.1) until stop_thr2 + end + end + + [thr1, thr2].each(&:join) + + expect(subject.level).to eq("debug") + end + end end From 8b71234a4877b4bd2058cca2766ea9794fbcee41 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 20 Nov 2025 01:57:21 +0100 Subject: [PATCH 1266/2435] [ruby/rubygems] Change the logger instance for this spec: - With the logger change that is now threadsafe, such code no longer behaves the same: ```ruby Bundler.ui.silence do Bundler.ui.level = 'info' Bundler.ui.info("foo") # This used to output something. Now it doesn't. end ``` IMHO this is the right behaviour since we are in a silence block, changing the level should have no effect. And fortunately it seems that we only need to change this spec. The call to `Bundler.ui.silence` is done in a `around` block https://github.com/ruby/rubygems/blob/4a13684f07ebb1dea5501e3f826fab414f96bf47/bundler/spec/spec_helper.rb#L119 https://github.com/ruby/rubygems/commit/e716adb6c9 --- spec/bundler/commands/ssl_spec.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/bundler/commands/ssl_spec.rb b/spec/bundler/commands/ssl_spec.rb index b4aca55194e23f..4220731b697073 100644 --- a/spec/bundler/commands/ssl_spec.rb +++ b/spec/bundler/commands/ssl_spec.rb @@ -16,16 +16,17 @@ end end - @previous_level = Bundler.ui.level - Bundler.ui.instance_variable_get(:@warning_history).clear - @previous_client = Gem::Request::ConnectionPools.client + @previous_ui = Bundler.ui + Bundler.ui = Bundler::UI::Shell.new Bundler.ui.level = "info" + + @previous_client = Gem::Request::ConnectionPools.client Artifice.activate_with(@dummy_endpoint) Gem::Request::ConnectionPools.client = Gem::Net::HTTP end after(:each) do - Bundler.ui.level = @previous_level + Bundler.ui = @previous_ui Artifice.deactivate Gem::Request::ConnectionPools.client = @previous_client end From d1b11592af75a5eee9199951a0c330eb8caa2825 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Wed, 19 Nov 2025 19:02:58 +0000 Subject: [PATCH 1267/2435] [DOC] Tweaks for String#upto --- doc/string/upto.rdoc | 38 ++++++++++++++++++++++++++++++++++++++ string.c | 28 +--------------------------- 2 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 doc/string/upto.rdoc diff --git a/doc/string/upto.rdoc b/doc/string/upto.rdoc new file mode 100644 index 00000000000000..f860fe84fe6485 --- /dev/null +++ b/doc/string/upto.rdoc @@ -0,0 +1,38 @@ +With a block given, calls the block with each +String+ value +returned by successive calls to String#succ; +the first value is +self+, the next is self.succ, and so on; +the sequence terminates when value +other_string+ is reached; +returns +self+: + + a = [] + 'a'.upto('f') {|c| a.push(c) } + a # => ["a", "b", "c", "d", "e", "f"] + + a = [] + 'Ж'.upto('П') {|c| a.push(c) } + a # => ["Ж", "З", "И", "Й", "К", "Л", "М", "Н", "О", "П"] + + a = [] + 'よ'.upto('ろ') {|c| a.push(c) } + a # => ["よ", "ら", "り", "る", "れ", "ろ"] + + a = [] + 'a8'.upto('b6') {|c| a.push(c) } + a # => ["a8", "a9", "b0", "b1", "b2", "b3", "b4", "b5", "b6"] + +If argument +exclusive+ is given as a truthy object, the last value is omitted: + + a = [] + 'a'.upto('f', true) {|c| a.push(c) } + a # => ["a", "b", "c", "d", "e"] + +If +other_string+ would not be reached, does not call the block: + + '25'.upto('5') {|s| fail s } + 'aa'.upto('a') {|s| fail s } + +With no block given, returns a new Enumerator: + + 'a8'.upto('b6') # => # + +Related: see {Iterating}[rdoc-ref:String@Iterating]. diff --git a/string.c b/string.c index 9f5cd83c0da72e..ebf36a7e08ebde 100644 --- a/string.c +++ b/string.c @@ -5463,33 +5463,7 @@ str_upto_i(VALUE str, VALUE arg) * upto(other_string, exclusive = false) {|string| ... } -> self * upto(other_string, exclusive = false) -> new_enumerator * - * With a block given, calls the block with each +String+ value - * returned by successive calls to String#succ; - * the first value is +self+, the next is self.succ, and so on; - * the sequence terminates when value +other_string+ is reached; - * returns +self+: - * - * 'a8'.upto('b6') {|s| print s, ' ' } # => "a8" - * Output: - * - * a8 a9 b0 b1 b2 b3 b4 b5 b6 - * - * If argument +exclusive+ is given as a truthy object, the last value is omitted: - * - * 'a8'.upto('b6', true) {|s| print s, ' ' } # => "a8" - * - * Output: - * - * a8 a9 b0 b1 b2 b3 b4 b5 - * - * If +other_string+ would not be reached, does not call the block: - * - * '25'.upto('5') {|s| fail s } - * 'aa'.upto('a') {|s| fail s } - * - * With no block given, returns a new Enumerator: - * - * 'a8'.upto('b6') # => # + * :include: doc/string/upto.rdoc * */ From ff1d23eccba3ab37e77bf2d2222cad9d6f99a0ab Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 5 Nov 2025 12:27:26 -0800 Subject: [PATCH 1268/2435] Use a serial to keep track of Mutex-owning Fiber Previously this held a pointer to the Fiber itself, which requires marking it (which was only implemented recently, prior to that it was buggy). Using a monotonically increasing integer instead allows us to avoid having a free function and keeps everything simpler. My main motivations in making this change are that the root fiber lazily allocates self, which makes the writebarrier implementation challenging to do correctly, and wanting to avoid sending Mutexes to the remembered set when locked by a short-lived Fiber. --- cont.c | 18 +++++++++++++ internal/cont.h | 1 + ruby_atomic.h | 23 +++++++++++++++++ thread.c | 14 +++++----- thread_sync.c | 69 ++++++++++++++++--------------------------------- 5 files changed, 71 insertions(+), 54 deletions(-) diff --git a/cont.c b/cont.c index f885cdb1095032..d167685f13bec3 100644 --- a/cont.c +++ b/cont.c @@ -268,6 +268,8 @@ struct rb_fiber_struct { unsigned int killed : 1; + rb_serial_t serial; + struct coroutine_context context; struct fiber_pool_stack stack; }; @@ -1010,6 +1012,13 @@ rb_fiber_threadptr(const rb_fiber_t *fiber) return fiber->cont.saved_ec.thread_ptr; } +rb_serial_t +rb_fiber_serial(const rb_fiber_t *fiber) +{ + VM_ASSERT(fiber->serial >= 1); + return fiber->serial; +} + static VALUE cont_thread_value(const rb_context_t *cont) { @@ -1995,6 +2004,13 @@ fiber_alloc(VALUE klass) return TypedData_Wrap_Struct(klass, &fiber_data_type, 0); } +static rb_serial_t +next_fiber_serial(void) +{ + static rbimpl_atomic_uint64_t fiber_serial = 1; + return (rb_serial_t)ATOMIC_U64_FETCH_ADD(fiber_serial, 1); +} + static rb_fiber_t* fiber_t_alloc(VALUE fiber_value, unsigned int blocking) { @@ -2011,6 +2027,7 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) fiber->cont.type = FIBER_CONTEXT; fiber->blocking = blocking; fiber->killed = 0; + fiber->serial = next_fiber_serial(); cont_init(&fiber->cont, th); fiber->cont.saved_ec.fiber_ptr = fiber; @@ -2563,6 +2580,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) fiber->cont.saved_ec.thread_ptr = th; fiber->blocking = 1; fiber->killed = 0; + fiber->serial = next_fiber_serial(); fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ th->ec = &fiber->cont.saved_ec; cont_init_jit_cont(&fiber->cont); diff --git a/internal/cont.h b/internal/cont.h index 3c2528a02a6e77..21a054f37c1294 100644 --- a/internal/cont.h +++ b/internal/cont.h @@ -31,5 +31,6 @@ VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber); unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber); struct rb_execution_context_struct * rb_fiberptr_get_ec(struct rb_fiber_struct *fiber); +rb_serial_t rb_fiber_serial(const struct rb_fiber_struct *fiber); #endif /* INTERNAL_CONT_H */ diff --git a/ruby_atomic.h b/ruby_atomic.h index ad53356f069ce2..9eaa5a9651f96a 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -63,4 +63,27 @@ rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t } #define ATOMIC_U64_SET_RELAXED(var, val) rbimpl_atomic_u64_set_relaxed(&(var), val) +static inline uint64_t +rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) +{ +#if defined(HAVE_GCC_ATOMIC_BUILTINS_64) + return __atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST); +#elif defined(_WIN32) + return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); +#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) + return atomic_add_64_nv(ptr, val) - val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); +#else + // Fallback using mutex for platforms without 64-bit atomics + static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; + rb_native_mutex_lock(&lock); + uint64_t old = *ptr; + *ptr = old + val; + rb_native_mutex_unlock(&lock); + return old; +#endif +} +#define ATOMIC_U64_FETCH_ADD(var, val) rbimpl_atomic_u64_fetch_add(&(var), val) + #endif diff --git a/thread.c b/thread.c index 5d75bf41228d3c..3e9bf3192d0c62 100644 --- a/thread.c +++ b/thread.c @@ -442,8 +442,8 @@ rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th) th->keeping_mutexes = mutex->next_mutex; // rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th); - VM_ASSERT(mutex->fiber); - const char *error_message = rb_mutex_unlock_th(mutex, th, mutex->fiber); + VM_ASSERT(mutex->fiber_serial); + const char *error_message = rb_mutex_unlock_th(mutex, th, NULL); if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message); } } @@ -5263,7 +5263,7 @@ rb_thread_shield_owned(VALUE self) rb_mutex_t *m = mutex_ptr(mutex); - return m->fiber == GET_EC()->fiber_ptr; + return m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr); } /* @@ -5282,7 +5282,7 @@ rb_thread_shield_wait(VALUE self) if (!mutex) return Qfalse; m = mutex_ptr(mutex); - if (m->fiber == GET_EC()->fiber_ptr) return Qnil; + if (m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr)) return Qnil; rb_thread_shield_waiting_inc(self); rb_mutex_lock(mutex); rb_thread_shield_waiting_dec(self); @@ -5799,8 +5799,8 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg) if (th->locking_mutex) { rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); - rb_str_catf(msg, " mutex:%p cond:%"PRIuSIZE, - (void *)mutex->fiber, rb_mutex_num_waiting(mutex)); + rb_str_catf(msg, " mutex:%llu cond:%"PRIuSIZE, + (unsigned long long)mutex->fiber_serial, rb_mutex_num_waiting(mutex)); } { @@ -5840,7 +5840,7 @@ rb_check_deadlock(rb_ractor_t *r) } else if (th->locking_mutex) { rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); - if (mutex->fiber == th->ec->fiber_ptr || (!mutex->fiber && !ccan_list_empty(&mutex->waitq))) { + if (mutex->fiber_serial == rb_fiber_serial(th->ec->fiber_ptr) || (!mutex->fiber_serial && !ccan_list_empty(&mutex->waitq))) { found = 1; } } diff --git a/thread_sync.c b/thread_sync.c index 0fc70224ff90ed..6cc23f7d87b32d 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -7,7 +7,7 @@ static VALUE rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { - rb_fiber_t *fiber; + rb_serial_t fiber_serial; VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free struct rb_mutex_struct *next_mutex; struct ccan_list_head waitq; /* protected by GVL */ @@ -125,28 +125,7 @@ rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber); static bool locked_p(rb_mutex_t *mutex) { - return mutex->fiber != 0; -} - -static void -mutex_mark(void *ptr) -{ - rb_mutex_t *mutex = ptr; - VALUE fiber; - if (locked_p(mutex)) { - fiber = rb_fiberptr_self(mutex->fiber); // rb_fiber_t* doesn't move along with fiber object - if (fiber) rb_gc_mark_movable(fiber); - rb_gc_mark_movable(mutex->thread); - } -} - -static void -mutex_compact(void *ptr) -{ - rb_mutex_t *mutex = ptr; - if (locked_p(mutex)) { - mutex->thread = rb_gc_location(mutex->thread); - } + return mutex->fiber_serial != 0; } static void @@ -154,7 +133,7 @@ mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; if (locked_p(mutex)) { - const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), mutex->fiber); + const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), NULL); if (err) rb_bug("%s", err); } ruby_xfree(ptr); @@ -168,8 +147,8 @@ mutex_memsize(const void *ptr) static const rb_data_type_t mutex_data_type = { "mutex", - {mutex_mark, mutex_free, mutex_memsize, mutex_compact,}, - 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY + {NULL, mutex_free, mutex_memsize,}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; static rb_mutex_t * @@ -265,11 +244,7 @@ mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber) rb_mutex_t *mutex = mutex_ptr(self); mutex->thread = th->self; - mutex->fiber = fiber; - RB_OBJ_WRITTEN(self, Qundef, th->self); - if (fiber) { - RB_OBJ_WRITTEN(self, Qundef, rb_fiberptr_self(fiber)); - } + mutex->fiber_serial = rb_fiber_serial(fiber); } static void @@ -293,7 +268,7 @@ rb_mutex_trylock(VALUE self) { rb_mutex_t *mutex = mutex_ptr(self); - if (mutex->fiber == 0) { + if (mutex->fiber_serial == 0) { RUBY_DEBUG_LOG("%p ok", mutex); rb_fiber_t *fiber = GET_EC()->fiber_ptr; @@ -311,7 +286,7 @@ rb_mutex_trylock(VALUE self) static VALUE mutex_owned_p(rb_fiber_t *fiber, rb_mutex_t *mutex) { - return RBOOL(mutex->fiber == fiber); + return RBOOL(mutex->fiber_serial == rb_fiber_serial(fiber)); } static VALUE @@ -347,12 +322,12 @@ do_mutex_lock(VALUE self, int interruptible_p) } if (rb_mutex_trylock(self) == Qfalse) { - if (mutex->fiber == fiber) { + if (mutex->fiber_serial == rb_fiber_serial(fiber)) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } - while (mutex->fiber != fiber) { - VM_ASSERT(mutex->fiber != NULL); + while (mutex->fiber_serial != rb_fiber_serial(fiber)) { + VM_ASSERT(mutex->fiber_serial != 0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { @@ -366,12 +341,12 @@ do_mutex_lock(VALUE self, int interruptible_p) rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter); - if (!mutex->fiber) { + if (!mutex->fiber_serial) { mutex_set_owner(self, th, fiber); } } else { - if (!th->vm->thread_ignore_deadlock && rb_fiber_threadptr(mutex->fiber) == th) { + if (!th->vm->thread_ignore_deadlock && rb_thread_ptr(mutex->thread) == th) { rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread"); } @@ -407,7 +382,7 @@ do_mutex_lock(VALUE self, int interruptible_p) ccan_list_del(&sync_waiter.node); // unlocked by another thread while sleeping - if (!mutex->fiber) { + if (!mutex->fiber_serial) { mutex_set_owner(self, th, fiber); } @@ -421,12 +396,12 @@ do_mutex_lock(VALUE self, int interruptible_p) if (interruptible_p) { /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ - if (mutex->fiber == fiber) { + if (mutex->fiber_serial == rb_fiber_serial(fiber)) { mutex->thread = Qfalse; - mutex->fiber = NULL; + mutex->fiber_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ - if (!mutex->fiber) { + if (!mutex->fiber_serial) { mutex_set_owner(self, th, fiber); } } @@ -446,7 +421,7 @@ do_mutex_lock(VALUE self, int interruptible_p) } if (saved_ints) th->ec->interrupt_flag = saved_ints; - if (mutex->fiber == fiber) mutex_locked(th, fiber, self); + if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(th, fiber, self); } RUBY_DEBUG_LOG("%p locked", mutex); @@ -496,16 +471,16 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) { RUBY_DEBUG_LOG("%p", mutex); - if (mutex->fiber == 0) { + if (mutex->fiber_serial == 0) { return "Attempt to unlock a mutex which is not locked"; } - else if (mutex->fiber != fiber) { + else if (fiber && mutex->fiber_serial != rb_fiber_serial(fiber)) { return "Attempt to unlock a mutex which is locked by another thread/fiber"; } struct sync_waiter *cur = 0, *next; - mutex->fiber = 0; + mutex->fiber_serial = 0; thread_mutex_remove(th, mutex); ccan_list_for_each_safe(&mutex->waitq, cur, next, node) { @@ -583,7 +558,7 @@ rb_mutex_abandon_all(rb_mutex_t *mutexes) while (mutexes) { mutex = mutexes; mutexes = mutex->next_mutex; - mutex->fiber = 0; + mutex->fiber_serial = 0; mutex->next_mutex = 0; ccan_list_head_init(&mutex->waitq); } From 826e91a7e2c427f604f47f775d156d1d398dadc6 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 20 Nov 2025 20:57:34 +0000 Subject: [PATCH 1269/2435] [DOC] Harmonize mod methods --- numeric.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/numeric.c b/numeric.c index 3d6648f7d6fbd7..0d5f3f26f617e3 100644 --- a/numeric.c +++ b/numeric.c @@ -633,7 +633,7 @@ num_div(VALUE x, VALUE y) * call-seq: * self % other -> real_numeric * - * Returns +self+ modulo +other+ as a real number. + * Returns +self+ modulo +other+ as a real numeric (\Integer, \Float, or \Rational). * * Of the Core and Standard Library classes, * only Rational uses this implementation. @@ -1358,7 +1358,7 @@ ruby_float_mod(double x, double y) * call-seq: * self % other -> float * - * Returns +self+ modulo +other+ as a float. + * Returns +self+ modulo +other+ as a \Float. * * For float +f+ and real number +r+, these expressions are equivalent: * @@ -4316,9 +4316,9 @@ fix_mod(VALUE x, VALUE y) /* * call-seq: - * self % other -> real_number + * self % other -> real_numeric * - * Returns +self+ modulo +other+ as a real number. + * Returns +self+ modulo +other+ as a real numeric (\Integer, \Float, or \Rational). * * For integer +n+ and real number +r+, these expressions are equivalent: * From d5368fc515788e03a11fc42b0d9a42c7c1837d2d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 20 Nov 2025 16:07:44 -0600 Subject: [PATCH 1270/2435] [DOC] Tweaks for String#valid_encoding? --- doc/string/valid_encoding_p.rdoc | 8 ++++++++ string.c | 5 +---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 doc/string/valid_encoding_p.rdoc diff --git a/doc/string/valid_encoding_p.rdoc b/doc/string/valid_encoding_p.rdoc new file mode 100644 index 00000000000000..e1db55174a5428 --- /dev/null +++ b/doc/string/valid_encoding_p.rdoc @@ -0,0 +1,8 @@ +Returns whether +self+ is encoded correctly: + + s = 'Straße' + s.valid_encoding? # => true + s.encoding # => # + s.force_encoding(Encoding::ASCII).valid_encoding? # => false + +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/string.c b/string.c index ebf36a7e08ebde..b1db9cb528fdfe 100644 --- a/string.c +++ b/string.c @@ -11469,11 +11469,8 @@ rb_str_b(VALUE str) * call-seq: * valid_encoding? -> true or false * - * Returns +true+ if +self+ is encoded correctly, +false+ otherwise: + * :include: doc/string/valid_encoding_p.rdoc * - * "\xc2\xa1".force_encoding(Encoding::UTF_8).valid_encoding? # => true - * "\xc2".force_encoding(Encoding::UTF_8).valid_encoding? # => false - * "\x80".force_encoding(Encoding::UTF_8).valid_encoding? # => false */ static VALUE From 55938a45e8c6872df258b3ef52bba94a2cda846d Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 20 Nov 2025 21:50:00 +0000 Subject: [PATCH 1271/2435] [DOC] Sort some methods in What's Here --- doc/string.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/string.rb b/doc/string.rb index b37cb5d324ec95..304ab60c298967 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -194,10 +194,10 @@ # # _Counts_ # -# - #length (aliased as #size): Returns the count of characters (not bytes). -# - #empty?: Returns whether the length of +self+ is zero. # - #bytesize: Returns the count of bytes. # - #count: Returns the count of substrings matching given strings. +# - #empty?: Returns whether the length of +self+ is zero. +# - #length (aliased as #size): Returns the count of characters (not bytes). # # _Substrings_ # From 2447380894e8ab4968d745f6844d2dc5278ebd6b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 18 Nov 2025 17:07:21 -0500 Subject: [PATCH 1272/2435] Run rb_gc_before_fork after before_exec before_exec stops the timer thread, which requires locking the Ractor scheduler lock. This may deadlock if rb_gc_before_fork locks the VM. --- process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process.c b/process.c index 2871596ceae14f..46c55abcce42c6 100644 --- a/process.c +++ b/process.c @@ -1575,8 +1575,8 @@ after_exec(void) static void before_fork_ruby(void) { - rb_gc_before_fork(); before_exec(); + rb_gc_before_fork(); } static void From 604fc059618b8f1f94b19efa51d468d827a766d1 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Thu, 13 Nov 2025 11:56:23 -0500 Subject: [PATCH 1273/2435] ZJIT: Rename array length reference to make the code easier to follow --- vm_insnhelper.c | 58 ++++++++++++++++++++++----------------------- zjit/src/codegen.rs | 4 ++-- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index ff686d047a163d..97e63387bb7b55 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6350,15 +6350,15 @@ rb_vm_opt_duparray_include_p(rb_execution_context_t *ec, const VALUE ary, VALUE } static VALUE -vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { if (BASIC_OP_UNREDEFINED_P(BOP_MAX, ARRAY_REDEFINED_OP_FLAG)) { - if (num == 0) { + if (array_len == 0) { return Qnil; } else { VALUE result = *ptr; - rb_snum_t i = num - 1; + rb_snum_t i = array_len - 1; while (i-- > 0) { const VALUE v = *++ptr; if (OPTIMIZED_CMP(v, result) > 0) { @@ -6369,26 +6369,26 @@ vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) } } else { - return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idMax, 0, NULL, RB_NO_KEYWORDS); + return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idMax, 0, NULL, RB_NO_KEYWORDS); } } VALUE -rb_vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +rb_vm_opt_newarray_max(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { - return vm_opt_newarray_max(ec, num, ptr); + return vm_opt_newarray_max(ec, array_len, ptr); } static VALUE -vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { if (BASIC_OP_UNREDEFINED_P(BOP_MIN, ARRAY_REDEFINED_OP_FLAG)) { - if (num == 0) { + if (array_len == 0) { return Qnil; } else { VALUE result = *ptr; - rb_snum_t i = num - 1; + rb_snum_t i = array_len - 1; while (i-- > 0) { const VALUE v = *++ptr; if (OPTIMIZED_CMP(v, result) < 0) { @@ -6399,63 +6399,63 @@ vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) } } else { - return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idMin, 0, NULL, RB_NO_KEYWORDS); + return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idMin, 0, NULL, RB_NO_KEYWORDS); } } VALUE -rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { - return vm_opt_newarray_min(ec, num, ptr); + return vm_opt_newarray_min(ec, array_len, ptr); } static VALUE -vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { // If Array#hash is _not_ monkeypatched, use the optimized call if (BASIC_OP_UNREDEFINED_P(BOP_HASH, ARRAY_REDEFINED_OP_FLAG)) { - return rb_ary_hash_values(num, ptr); + return rb_ary_hash_values(array_len, ptr); } else { - return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idHash, 0, NULL, RB_NO_KEYWORDS); + return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idHash, 0, NULL, RB_NO_KEYWORDS); } } VALUE -rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr) +rb_vm_opt_newarray_hash(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr) { - return vm_opt_newarray_hash(ec, num, ptr); + return vm_opt_newarray_hash(ec, array_len, ptr); } VALUE rb_setup_fake_ary(struct RArray *fake_ary, const VALUE *list, long len); VALUE rb_ec_pack_ary(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer); static VALUE -vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE target) +vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE target) { if (BASIC_OP_UNREDEFINED_P(BOP_INCLUDE_P, ARRAY_REDEFINED_OP_FLAG)) { struct RArray fake_ary = {RBASIC_INIT}; - VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num); + VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, array_len); return rb_ary_includes(ary, target); } else { VALUE args[1] = {target}; - return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idIncludeP, 1, args, RB_NO_KEYWORDS); + return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idIncludeP, 1, args, RB_NO_KEYWORDS); } } VALUE -rb_vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE target) +rb_vm_opt_newarray_include_p(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE target) { - return vm_opt_newarray_include_p(ec, num, ptr, target); + return vm_opt_newarray_include_p(ec, array_len, ptr, target); } static VALUE -vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt, VALUE buffer) +vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt, VALUE buffer) { if (BASIC_OP_UNREDEFINED_P(BOP_PACK, ARRAY_REDEFINED_OP_FLAG)) { struct RArray fake_ary = {RBASIC_INIT}; - VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, num); + VALUE ary = rb_setup_fake_ary(&fake_ary, ptr, array_len); return rb_ec_pack_ary(ec, ary, fmt, (UNDEF_P(buffer) ? Qnil : buffer)); } else { @@ -6473,20 +6473,20 @@ vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALU argc++; } - return rb_vm_call_with_refinements(ec, rb_ary_new4(num, ptr), idPack, argc, args, kw_splat); + return rb_vm_call_with_refinements(ec, rb_ary_new4(array_len, ptr), idPack, argc, args, kw_splat); } } VALUE -rb_vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt, VALUE buffer) +rb_vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt, VALUE buffer) { - return vm_opt_newarray_pack_buffer(ec, num, ptr, fmt, buffer); + return vm_opt_newarray_pack_buffer(ec, array_len, ptr, fmt, buffer); } VALUE -rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr, VALUE fmt) +rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t array_len, const VALUE *ptr, VALUE fmt) { - return vm_opt_newarray_pack_buffer(ec, num, ptr, fmt, Qundef); + return vm_opt_newarray_pack_buffer(ec, array_len, ptr, fmt, Qundef); } #undef id_cmp diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8838a72fc887bd..71e40c640f42a9 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1436,7 +1436,7 @@ fn gen_array_include( ) -> lir::Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let num: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); + let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack. // The elements are at the bottom of the virtual stack, followed by the target. @@ -1450,7 +1450,7 @@ fn gen_array_include( asm_ccall!( asm, rb_vm_opt_newarray_include_p, - EC, num.into(), elements_ptr, target + EC, array_len.into(), elements_ptr, target ) } From b06dd644da0ae87f33649ade6fc827a301b55b0c Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Fri, 14 Nov 2025 14:43:57 -0500 Subject: [PATCH 1274/2435] ZJIT: Compile the VM_OPT_NEWARRAY_SEND_HASH variant of opt_newarray_send --- test/ruby/test_zjit.rb | 20 +++++++++++++++++++ zjit/src/codegen.rs | 28 +++++++++++++++++++++++++++ zjit/src/hir.rs | 16 +++++++++++++++ zjit/src/hir/tests.rs | 44 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 64372c231cf26f..ee046ad9bf1637 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1049,6 +1049,26 @@ def test(x) }, insns: [:opt_duparray_send], call_threshold: 1 end + def test_opt_newarray_send_hash + assert_compiles 'Integer', %q{ + def test(x) + [1, 2, x].hash + end + test(20).class + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_hash_redefinition + assert_compiles '42', %q{ + Array.class_eval { def hash = 42 } + + def test(x) + [1, 2, x].hash + end + test(20) + }, insns: [:opt_newarray_send], call_threshold: 1 + end + def test_new_hash_empty assert_compiles '{}', %q{ def test = {} diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 71e40c640f42a9..082db3fae44108 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -464,6 +464,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::IsBlockGiven => gen_is_block_given(jit, asm), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), + Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } @@ -1427,6 +1428,33 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm_ccall!(asm, rb_jit_array_len, array) } +/// Compile opt_newarray_hash - create a hash from array elements +fn gen_opt_newarray_hash( + jit: &JITState, + asm: &mut Assembler, + elements: Vec, + state: &FrameState, +) -> lir::Opnd { + // `Array#hash` will hash the elements of the array. + gen_prepare_non_leaf_call(jit, asm, state); + + let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); + + // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack. + // Get a pointer to the first element on the Ruby stack. + let stack_bottom = state.stack().len() - elements.len(); + let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32)); + + unsafe extern "C" { + fn rb_vm_opt_newarray_hash(ec: EcPtr, array_len: u32, elts: *const VALUE) -> VALUE; + } + + asm.ccall( + rb_vm_opt_newarray_hash as *const u8, + vec![EC, (array_len as u32).into(), elements_ptr], + ) +} + fn gen_array_include( jit: &JITState, asm: &mut Assembler, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index face61f1f67a7b..4232410f23d521 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -231,6 +231,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { BOP_FREEZE => write!(f, "BOP_FREEZE")?, BOP_UMINUS => write!(f, "BOP_UMINUS")?, BOP_MAX => write!(f, "BOP_MAX")?, + BOP_HASH => write!(f, "BOP_HASH")?, BOP_AREF => write!(f, "BOP_AREF")?, _ => write!(f, "{bop}")?, } @@ -650,6 +651,7 @@ pub enum Insn { NewRange { low: InsnId, high: InsnId, flag: RangeType, state: InsnId }, NewRangeFixnum { low: InsnId, high: InsnId, flag: RangeType, state: InsnId }, ArrayDup { val: InsnId, state: InsnId }, + ArrayHash { elements: Vec, state: InsnId }, ArrayMax { elements: Vec, state: InsnId }, ArrayInclude { elements: Vec, target: InsnId, state: InsnId }, DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId }, @@ -1040,6 +1042,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } + Insn::ArrayHash { elements, .. } => { + write!(f, "ArrayHash")?; + let mut prefix = " "; + for element in elements { + write!(f, "{prefix}{element}")?; + prefix = ", "; + } + Ok(()) + } Insn::ArrayInclude { elements, target, .. } => { write!(f, "ArrayInclude")?; let mut prefix = " "; @@ -1887,6 +1898,7 @@ impl Function { &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) }, &DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) }, + &ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, ic, state } => GetIvar { self_val: find!(self_val), id, ic, state }, &LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type }, @@ -2032,6 +2044,7 @@ impl Function { Insn::ArrayMax { .. } => types::BasicObject, Insn::ArrayInclude { .. } => types::BoolExact, Insn::DupArrayInclude { .. } => types::BoolExact, + Insn::ArrayHash { .. } => types::Fixnum, Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, @@ -3346,6 +3359,7 @@ impl Function { worklist.push_back(val) } &Insn::ArrayMax { ref elements, state } + | &Insn::ArrayHash { ref elements, state } | &Insn::NewHash { ref elements, state } | &Insn::NewArray { ref elements, state } => { worklist.extend(elements); @@ -4102,6 +4116,7 @@ impl Function { | Insn::InvokeBuiltin { ref args, .. } | Insn::InvokeBlock { ref args, .. } | Insn::NewArray { elements: ref args, .. } + | Insn::ArrayHash { elements: ref args, .. } | Insn::ArrayMax { elements: ref args, .. } => { for &arg in args { self.assert_subtype(insn_id, arg, types::BasicObject)?; @@ -4908,6 +4923,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let elements = state.stack_pop_n(count)?; let (bop, insn) = match method { VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), + VM_OPT_NEWARRAY_SEND_HASH => (BOP_HASH, Insn::ArrayHash { elements, state: exit_id }), VM_OPT_NEWARRAY_SEND_INCLUDE_P => { let target = elements[elements.len() - 1]; let array_elements = elements[..elements.len() - 1].to_vec(); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index b487352748601a..1e058ce11adecf 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2013,7 +2013,49 @@ pub mod hir_build_tests { Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnhandledNewarraySend(HASH) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH) + v32:Fixnum = ArrayHash v15, v16 + PatchPoint NoEPEscape(test) + v39:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v40:ArrayExact = ArrayDup v39 + v42:BasicObject = SendWithoutBlock v14, :puts, v40 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_opt_newarray_send_hash_redefined() { + eval(" + Array.class_eval { def hash = 42 } + + def test(a,b) + sum = a+b + result = [a,b].hash + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH)) "); } From 7d2f9ab9e1992dded620fe221421c9c247dcd408 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Mon, 17 Nov 2025 11:41:30 -0500 Subject: [PATCH 1275/2435] ZJIT: Handle display formatting for all defined bops --- zjit/src/hir.rs | 50 +++++++++++++++++++++++++++++-------------- zjit/src/hir/tests.rs | 6 +++--- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4232410f23d521..172b177c456f3b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -217,22 +217,40 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { } write!(f, ", ")?; match bop { - BOP_PLUS => write!(f, "BOP_PLUS")?, - BOP_MINUS => write!(f, "BOP_MINUS")?, - BOP_MULT => write!(f, "BOP_MULT")?, - BOP_DIV => write!(f, "BOP_DIV")?, - BOP_MOD => write!(f, "BOP_MOD")?, - BOP_EQ => write!(f, "BOP_EQ")?, - BOP_NEQ => write!(f, "BOP_NEQ")?, - BOP_LT => write!(f, "BOP_LT")?, - BOP_LE => write!(f, "BOP_LE")?, - BOP_GT => write!(f, "BOP_GT")?, - BOP_GE => write!(f, "BOP_GE")?, - BOP_FREEZE => write!(f, "BOP_FREEZE")?, - BOP_UMINUS => write!(f, "BOP_UMINUS")?, - BOP_MAX => write!(f, "BOP_MAX")?, - BOP_HASH => write!(f, "BOP_HASH")?, - BOP_AREF => write!(f, "BOP_AREF")?, + BOP_PLUS => write!(f, "BOP_PLUS")?, + BOP_MINUS => write!(f, "BOP_MINUS")?, + BOP_MULT => write!(f, "BOP_MULT")?, + BOP_DIV => write!(f, "BOP_DIV")?, + BOP_MOD => write!(f, "BOP_MOD")?, + BOP_EQ => write!(f, "BOP_EQ")?, + BOP_EQQ => write!(f, "BOP_EQQ")?, + BOP_LT => write!(f, "BOP_LT")?, + BOP_LE => write!(f, "BOP_LE")?, + BOP_LTLT => write!(f, "BOP_LTLT")?, + BOP_AREF => write!(f, "BOP_AREF")?, + BOP_ASET => write!(f, "BOP_ASET")?, + BOP_LENGTH => write!(f, "BOP_LENGTH")?, + BOP_SIZE => write!(f, "BOP_SIZE")?, + BOP_EMPTY_P => write!(f, "BOP_EMPTY_P")?, + BOP_NIL_P => write!(f, "BOP_NIL_P")?, + BOP_SUCC => write!(f, "BOP_SUCC")?, + BOP_GT => write!(f, "BOP_GT")?, + BOP_GE => write!(f, "BOP_GE")?, + BOP_NOT => write!(f, "BOP_NOT")?, + BOP_NEQ => write!(f, "BOP_NEQ")?, + BOP_MATCH => write!(f, "BOP_MATCH")?, + BOP_FREEZE => write!(f, "BOP_FREEZE")?, + BOP_UMINUS => write!(f, "BOP_UMINUS")?, + BOP_MAX => write!(f, "BOP_MAX")?, + BOP_MIN => write!(f, "BOP_MIN")?, + BOP_HASH => write!(f, "BOP_HASH")?, + BOP_CALL => write!(f, "BOP_CALL")?, + BOP_AND => write!(f, "BOP_AND")?, + BOP_OR => write!(f, "BOP_OR")?, + BOP_CMP => write!(f, "BOP_CMP")?, + BOP_DEFAULT => write!(f, "BOP_DEFAULT")?, + BOP_PACK => write!(f, "BOP_PACK")?, + BOP_INCLUDE_P => write!(f, "BOP_INCLUDE_P")?, _ => write!(f, "{bop}")?, } write!(f, ")") diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 1e058ce11adecf..d0fc582548b1eb 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2123,7 +2123,7 @@ pub mod hir_build_tests { Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): v25:BasicObject = SendWithoutBlock v15, :+, v16 - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P) v33:BoolExact = ArrayInclude v15, v16 | v16 PatchPoint NoEPEscape(test) v40:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) @@ -2154,7 +2154,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P) v15:BoolExact = DupArrayInclude VALUE(0x1000) | v9 CheckInterrupts Return v15 @@ -2186,7 +2186,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, 33)) + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P)) "); } From 36f1ab967f4b837dfb9856e1c29dd7ebeed8c3d3 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Thu, 20 Nov 2025 16:49:26 -0500 Subject: [PATCH 1276/2435] ZJIT: Add tests for `opt_newarray_send` with target methods redefined --- test/ruby/test_zjit.rb | 53 ++++++++++++++++++++++++++++++++ zjit/src/hir/tests.rs | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index ee046ad9bf1637..5611aea08e3658 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1040,6 +1040,22 @@ def test(x) }, insns: [:opt_newarray_send], call_threshold: 1 end + def test_opt_newarray_send_include_p_redefinition + assert_compiles '[true, false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) + end + end + + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + def test_opt_duparray_send_include_p assert_compiles '[true, false]', %q{ def test(x) @@ -1049,6 +1065,22 @@ def test(x) }, insns: [:opt_duparray_send], call_threshold: 1 end + def test_opt_duparray_send_include_p_redefinition + assert_compiles '[true, false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) + end + end + + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + def test_opt_newarray_send_hash assert_compiles 'Integer', %q{ def test(x) @@ -1069,6 +1101,27 @@ def test(x) }, insns: [:opt_newarray_send], call_threshold: 1 end + def test_opt_newarray_send_max + assert_compiles '[20, 40]', %q{ + def test(a,b) = [a,b].max + [test(10, 20), test(40, 30)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_max_redefinition + assert_compiles '[60, 90]', %q{ + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + + def test(a,b) = [a,b].max + [test(15, 30), test(45, 35)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + def test_new_hash_empty assert_compiles '{}', %q{ def test = {} diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index d0fc582548b1eb..76a74c75eb4613 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1953,6 +1953,35 @@ pub mod hir_build_tests { "); } + #[test] + fn test_opt_newarray_send_max_redefined() { + eval(" + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + + def test(a,b) = [a,b].max + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:9: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX)) + "); + } + #[test] fn test_opt_newarray_send_min() { eval(" @@ -2135,6 +2164,45 @@ pub mod hir_build_tests { "); } + #[test] + fn test_opt_newarray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) + end + end + + def test(a,b) + sum = a+b + result = [a,b].include? b + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P)) + "); + } + #[test] fn test_opt_duparray_send_include_p() { eval(" From 447989e5980510b0bcde34c58363b425c0e78224 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Thu, 20 Nov 2025 16:57:46 -0500 Subject: [PATCH 1277/2435] ZJIT: Update test names to use the same convention as the HIR tests --- test/ruby/test_zjit.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 5611aea08e3658..4e962ac1f50b03 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -983,7 +983,7 @@ def test = Foo.new }, insns: [:opt_new] end - def test_opt_new_with_redefinition + def test_opt_new_with_redefined assert_compiles '"foo"', %q{ class Foo def self.new = "foo" @@ -1040,7 +1040,7 @@ def test(x) }, insns: [:opt_newarray_send], call_threshold: 1 end - def test_opt_newarray_send_include_p_redefinition + def test_opt_newarray_send_include_p_redefined assert_compiles '[true, false]', %q{ class Array alias_method :old_include?, :include? @@ -1065,7 +1065,7 @@ def test(x) }, insns: [:opt_duparray_send], call_threshold: 1 end - def test_opt_duparray_send_include_p_redefinition + def test_opt_duparray_send_include_p_redefined assert_compiles '[true, false]', %q{ class Array alias_method :old_include?, :include? @@ -1090,7 +1090,7 @@ def test(x) }, insns: [:opt_newarray_send], call_threshold: 1 end - def test_opt_newarray_send_hash_redefinition + def test_opt_newarray_send_hash_redefined assert_compiles '42', %q{ Array.class_eval { def hash = 42 } @@ -1108,7 +1108,7 @@ def test(a,b) = [a,b].max }, insns: [:opt_newarray_send], call_threshold: 1 end - def test_opt_newarray_send_max_redefinition + def test_opt_newarray_send_max_redefined assert_compiles '[60, 90]', %q{ class Array alias_method :old_max, :max @@ -2488,7 +2488,7 @@ def entry(flag) }, call_threshold: 2 end - def test_bop_redefinition + def test_bop_redefined assert_runs '[3, :+, 100]', %q{ def test 1 + 2 @@ -2499,7 +2499,7 @@ def test }, call_threshold: 2 end - def test_bop_redefinition_with_adjacent_patch_points + def test_bop_redefined_with_adjacent_patch_points assert_runs '[15, :+, 100]', %q{ def test 1 + 2 + 3 + 4 + 5 @@ -2512,7 +2512,7 @@ def test # ZJIT currently only generates a MethodRedefined patch point when the method # is called on the top-level self. - def test_method_redefinition_with_top_self + def test_method_redefined_with_top_self assert_runs '["original", "redefined"]', %q{ def foo "original" @@ -2535,7 +2535,7 @@ def foo }, call_threshold: 2 end - def test_method_redefinition_with_module + def test_method_redefined_with_module assert_runs '["original", "redefined"]', %q{ module Foo def self.foo = "original" From fb28d4748dc96d581592b0d4c186ca0a8d49fa26 Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Thu, 20 Nov 2025 17:04:45 -0500 Subject: [PATCH 1278/2435] ZJIT: Change the output on redefined method tests to verify the new definition is used --- test/ruby/test_zjit.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 4e962ac1f50b03..7472ff77156b95 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1041,11 +1041,11 @@ def test(x) end def test_opt_newarray_send_include_p_redefined - assert_compiles '[true, false]', %q{ + assert_compiles '[:true, :false]', %q{ class Array alias_method :old_include?, :include? def include?(x) - old_include?(x) + old_include?(x) ? :true : :false end end @@ -1066,11 +1066,11 @@ def test(x) end def test_opt_duparray_send_include_p_redefined - assert_compiles '[true, false]', %q{ + assert_compiles '[:true, :false]', %q{ class Array alias_method :old_include?, :include? def include?(x) - old_include?(x) + old_include?(x) ? :true : :false end end From 82d8d24e7cdd26123ed4ad478ce6a0bb81d7abb5 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 8 Nov 2025 16:00:57 -0800 Subject: [PATCH 1279/2435] [ruby/rubygems] Add support for lockfile in Gemfile This allows you to specify the lockfile to use. This is useful if you want to use different lockfiles for different ruby versions or platforms. You can also skip writing the lockfile by using a false value. https://github.com/ruby/rubygems/commit/2896aa3fc2 Co-authored-by: Colby Swandale <996377+colby-swandale@users.noreply.github.com> --- lib/bundler/definition.rb | 4 +++- lib/bundler/dsl.rb | 14 +++++++++++++- lib/bundler/man/gemfile.5 | 19 ++++++++++++++++++- lib/bundler/man/gemfile.5.ronn | 17 +++++++++++++++++ spec/bundler/commands/install_spec.rb | 22 ++++++++++++++++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 21f3760e6d9d3f..ca41d7953d8ea2 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -10,6 +10,8 @@ class << self attr_accessor :no_lock end + attr_writer :lockfile + attr_reader( :dependencies, :locked_checksums, @@ -380,7 +382,7 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or end def write_lock(file, preserve_unknown_sections) - return if Definition.no_lock || file.nil? + return if Definition.no_lock || !lockfile || file.nil? contents = to_lock diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 998a8134f8e12b..13e0783a436eff 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -9,8 +9,9 @@ class Dsl def self.evaluate(gemfile, lockfile, unlock) builder = new + builder.lockfile(lockfile) builder.eval_gemfile(gemfile) - builder.to_definition(lockfile, unlock) + builder.to_definition(builder.lockfile_path, unlock) end VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze @@ -38,6 +39,7 @@ def initialize @gemspecs = [] @gemfile = nil @gemfiles = [] + @lockfile = nil add_git_sources end @@ -101,6 +103,15 @@ def gem(name, *args) add_dependency(name, version, options) end + # For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile. + def lockfile_path + @lockfile + end + + def lockfile(file) + @lockfile = file + end + def source(source, *args, &blk) options = args.last.is_a?(Hash) ? args.pop.dup : {} options = normalize_hash(options) @@ -175,6 +186,7 @@ def github(repo, options = {}) def to_definition(lockfile, unlock) check_primary_source_safety + lockfile = @lockfile unless @lockfile.nil? Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles) end diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 4e1a63578076e2..1dbb2618afba5e 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -469,4 +469,21 @@ For implicit gems (dependencies of explicit gems), any source, git, or path repo .IP "3." 4 If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\. .IP "" 0 - +.SH "LOCKFILE" +By default, Bundler will create a lockfile by adding \fB\.lock\fR to the end of the Gemfile name\. To change this, use the \fBlockfile\fR method: +.IP "" 4 +.nf +lockfile "/path/to/lockfile\.lock" +.fi +.IP "" 0 +.P +This is useful when you want to use different lockfiles per ruby version or platform\. +.P +To avoid writing a lock file, use \fBfalse\fR as the argument: +.IP "" 4 +.nf +lockfile false +.fi +.IP "" 0 +.P +This is useful for library development and other situations where the code is expected to work with a range of dependency versions\. diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 802549737e38e4..619151aa8801d7 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -556,3 +556,20 @@ bundler uses the following priority order: If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1.13, so Bundler prints a warning and will abort with an error in the future. + +## LOCKFILE + +By default, Bundler will create a lockfile by adding `.lock` to the end of the +Gemfile name. To change this, use the `lockfile` method: + + lockfile "/path/to/lockfile.lock" + +This is useful when you want to use different lockfiles per ruby version or +platform. + +To avoid writing a lock file, use `false` as the argument: + + lockfile false + +This is useful for library development and other situations where the code is +expected to work with a range of dependency versions. diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 0dbe950f87b6d7..68232d92de9b95 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -29,6 +29,28 @@ expect(bundled_app_lock).to exist end + it "creates lockfile based on the lockfile method in Gemfile" do + install_gemfile <<-G + lockfile "OmgFile.lock" + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install" + + expect(bundled_app("OmgFile.lock")).to exist + end + + it "does not make a lockfile if lockfile false is used in Gemfile" do + install_gemfile <<-G + lockfile false + source "https://gem.repo1" + gem 'myrack' + G + + expect(bundled_app_lock).not_to exist + end + it "does not create ./.bundle by default" do install_gemfile <<-G source "https://gem.repo1" From 1562803e5103dea3949027e61672fa82f26782fd Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 8 Nov 2025 16:03:07 -0800 Subject: [PATCH 1280/2435] [ruby/rubygems] Add support for bundle install --no-lock This allows for the same behavior as including `lockfile false` in the Gemfile. This allows you to get the behavior without modifying the Gemfile, which is useful if you do not control the Gemfile. This is similar to the --no-lock option already supported by `gem install -g Gemfile`. https://github.com/ruby/rubygems/commit/6c94623881 Co-authored-by: Colby Swandale <996377+colby-swandale@users.noreply.github.com> --- lib/bundler/cli.rb | 1 + lib/bundler/cli/install.rb | 1 + lib/bundler/man/bundle-install.1 | 7 ++++++- lib/bundler/man/bundle-install.1.ronn | 10 ++++++++++ spec/bundler/commands/install_spec.rb | 11 +++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 79f93a7784734c..48178965697667 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -234,6 +234,7 @@ def remove(*gems) method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." + method_option "no-lock", type: :boolean, banner: "Don't create a lockfile." method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 20e22155de8ee9..85b303eee60900 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -44,6 +44,7 @@ def run # (rather than some optimizations we perform at app runtime). definition = Bundler.definition(strict: true) definition.validate_runtime! + definition.lockfile = false if options["no-lock"] installer = Installer.install(Bundler.root, definition, options) diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 2d7ef96b4490fd..1acbe430585e22 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] +\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -34,6 +34,11 @@ Force using locally installed gems, or gems already present in Rubygems' cache o \fB\-\-no\-cache\fR Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\. .TP +\fB\-\-no\-lock\fR +Do not create a lockfile\. Useful if you want to install dependencies but not lock versions of gems\. Recommended for library development, and other situations where the code is expected to work with a range of dependency versions\. +.IP +This has the same effect as using \fBlockfile false\fR in the Gemfile\. See gemfile(5) for more information\. +.TP \fB\-\-quiet\fR Do not print progress information to the standard output\. .TP diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index b946cbf8322917..adb47490d74b2d 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -9,6 +9,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile [--jobs=NUMBER] [--local] [--no-cache] + [--no-lock] [--prefer-local] [--quiet] [--retry=NUMBER] @@ -71,6 +72,15 @@ update process below under [CONSERVATIVE UPDATING][]. does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install. +* `--no-lock`: + Do not create a lockfile. Useful if you want to install dependencies but not + lock versions of gems. Recommended for library development, and other + situations where the code is expected to work with a range of dependency + versions. + + This has the same effect as using `lockfile false` in the Gemfile. + See gemfile(5) for more information. + * `--quiet`: Do not print progress information to the standard output. diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 68232d92de9b95..69d9a860996197 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -89,6 +89,17 @@ expect(bundled_app("OmgFile.lock")).to exist end + it "doesn't create a lockfile if --no-lock option is given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --no-lock" + + expect(bundled_app("OmgFile.lock")).not_to exist + end + it "doesn't delete the lockfile if one already exists" do install_gemfile <<-G source "https://gem.repo1" From 010b23a7cfc4a20371d74406f9f0563331a233fd Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 10 Nov 2025 18:23:58 -0800 Subject: [PATCH 1281/2435] [ruby/rubygems] Add support for BUNDLE_LOCKFILE environment variable This specifies the lockfile location. This allows for easy support of different lockfiles per Ruby version or platform. https://github.com/ruby/rubygems/commit/b54d65bc0a Co-authored-by: Sutou Kouhei Co-authored-by: Colby Swandale <996377+colby-swandale@users.noreply.github.com> --- lib/bundler/environment_preserver.rb | 1 + lib/bundler/inline.rb | 8 +++++++ lib/bundler/man/bundle-config.1 | 3 +++ lib/bundler/man/bundle-config.1.ronn | 4 ++++ lib/bundler/man/gemfile.5 | 12 +++++++++++ lib/bundler/man/gemfile.5.ronn | 10 +++++++++ lib/bundler/settings.rb | 1 + lib/bundler/shared_helpers.rb | 4 ++++ lib/rubygems/bundler_version_finder.rb | 9 +++++--- spec/bundler/commands/config_spec.rb | 17 +++++++++++++++ spec/bundler/install/gemfile_spec.rb | 29 ++++++++++++++++++++++++++ 11 files changed, 95 insertions(+), 3 deletions(-) diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index 444ab6fd373d86..bf9478a2990689 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -6,6 +6,7 @@ class EnvironmentPreserver BUNDLER_KEYS = %w[ BUNDLE_BIN_PATH BUNDLE_GEMFILE + BUNDLE_LOCKFILE BUNDLER_VERSION BUNDLER_SETUP GEM_HOME diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index 4e4b51e7a5dfb4..c861bee1496738 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -44,12 +44,14 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? old_gemfile = ENV["BUNDLE_GEMFILE"] + old_lockfile = ENV["BUNDLE_LOCKFILE"] Bundler.unbundle_env! begin Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock" Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins] builder = Bundler::Dsl.new @@ -94,5 +96,11 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) else ENV["BUNDLE_GEMFILE"] = "" end + + if old_lockfile + ENV["BUNDLE_LOCKFILE"] = old_lockfile + else + ENV["BUNDLE_LOCKFILE"] = "" + end end end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index b44891f6e92374..05c13e2d0f3213 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -145,6 +145,9 @@ Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init \fBjobs\fR (\fBBUNDLE_JOBS\fR) The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. .TP +\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR) +The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\. +.TP \fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR) Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 281ab2da0cd124..7c34f1d1afb26e 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -189,6 +189,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `jobs` (`BUNDLE_JOBS`): The number of gems Bundler can install in parallel. Defaults to the number of available processors. +* `lockfile` (`BUNDLE_LOCKFILE`): + The path to the lockfile that bundler should use. By default, Bundler adds + `.lock` to the end of the `gemfile` entry. Can be set to `false` in the + Gemfile to disable lockfile creation entirely (see gemfile(5)). * `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true. * `no_install` (`BUNDLE_NO_INSTALL`): diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 1dbb2618afba5e..f345580ed77edb 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -487,3 +487,15 @@ lockfile false .IP "" 0 .P This is useful for library development and other situations where the code is expected to work with a range of dependency versions\. +.SS "LOCKFILE PRECEDENCE" +When determining path to the lockfile or whether to create a lockfile, the following precedence is used: +.IP "1." 4 +The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\. +.IP "2." 4 +The \fBlockfile\fR method in the Gemfile\. +.IP "3." 4 +The \fBBUNDLE_LOCKFILE\fR environment variable\. +.IP "4." 4 +The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\. +.IP "" 0 + diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 619151aa8801d7..3dea29cb3c6aaf 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -573,3 +573,13 @@ To avoid writing a lock file, use `false` as the argument: This is useful for library development and other situations where the code is expected to work with a range of dependency versions. + +### LOCKFILE PRECEDENCE + +When determining path to the lockfile or whether to create a lockfile, the +following precedence is used: + +1. The `bundle install` `--no-lock` option (which disables lockfile creation). +2. The `lockfile` method in the Gemfile. +3. The `BUNDLE_LOCKFILE` environment variable. +4. The default behavior of adding `.lock` to the end of the Gemfile name. diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index fb1b875b264500..d00a4bb916f692 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -65,6 +65,7 @@ class Settings gem.rubocop gem.test gemfile + lockfile path shebang simulate_version diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 4c914eb1a447a4..6419e4299760b7 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -23,6 +23,9 @@ def default_gemfile end def default_lockfile + given = ENV["BUNDLE_LOCKFILE"] + return Pathname.new(given) if given && !given.empty? + gemfile = default_gemfile case gemfile.basename.to_s @@ -297,6 +300,7 @@ def set_env(key, value) def set_bundle_variables Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) end diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index ac8988dea577bb..602e00c1d866fb 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -65,9 +65,12 @@ def self.lockfile_contents return unless gemfile - lockfile = case gemfile - when "gems.rb" then "gems.locked" - else "#{gemfile}.lock" + lockfile = ENV["BUNDLE_LOCKFILE"] + lockfile = nil if lockfile&.empty? + + lockfile ||= case gemfile + when "gems.rb" then "gems.locked" + else "#{gemfile}.lock" end return unless File.file?(lockfile) diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb index 1392b1731574a3..954cae09d8464a 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -592,3 +592,20 @@ end end end + +RSpec.describe "setting lockfile via config" do + it "persists the lockfile location to .bundle/config" do + gemfile bundled_app("NotGemfile"), <<-G + source "https://gem.repo1" + gem 'myrack' + G + + bundle "config set --local gemfile #{bundled_app("NotGemfile")}" + bundle "config set --local lockfile #{bundled_app("ReallyNotGemfile.lock")}" + expect(File.exist?(bundled_app(".bundle/config"))).to eq(true) + + bundle "config list" + expect(out).to include("NotGemfile") + expect(out).to include("ReallyNotGemfile.lock") + end +end diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index 0e3b8477678388..87326f67af693c 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -27,6 +27,35 @@ ENV["BUNDLE_GEMFILE"] = "NotGemfile" expect(the_bundle).to include_gems "myrack 1.0.0" end + + it "respects lockfile and BUNDLE_LOCKFILE" do + gemfile bundled_app("NotGemfile"), <<-G + lockfile "ReallyNotGemfile.lock" + source "https://gem.repo1" + gem 'myrack' + G + + bundle :install, gemfile: bundled_app("NotGemfile") + + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "respects BUNDLE_LOCKFILE during bundle install" do + ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock" + + gemfile bundled_app("NotGemfile"), <<-G + source "https://gem.repo1" + gem 'myrack' + G + + bundle :install, gemfile: bundled_app("NotGemfile") + expect(bundled_app("ReallyNotGemfile.lock")).to exist + + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + expect(the_bundle).to include_gems "myrack 1.0.0" + end end context "with gemfile set via config" do From 3ec44a99954a3c4c131f374020b7addcba9bc5f2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 17 Nov 2025 21:53:30 -0500 Subject: [PATCH 1282/2435] Add a Ractor test case that causes MMTk to deadlock This was a test case for Ractors discovered that causes MMTk to deadlock. There is a fix for it in https://github.com/ruby/mmtk/pull/49. --- bootstraptest/test_ractor.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 834eb627ef0e90..ed80dd08628e9d 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -2350,3 +2350,23 @@ def call_test(obj) end :ok RUBY + +assert_equal 'ok', <<~'RUBY' + begin + 100.times do |i| + Ractor.new(i) do |j| + 1000.times do |i| + "#{j}-#{i}" + end + Ractor.receive + end + pid = fork { } + _, status = Process.waitpid2 pid + raise unless status.success? + end + + :ok + rescue NotImplementedError + :ok + end +RUBY From e15b4e1c56e826655e6fd10e32bdf974edf4b980 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 20 Nov 2025 16:58:36 -0500 Subject: [PATCH 1283/2435] Bump default compiler to clang-20 in CI clang-18 has a bug that causes the latest Ractor btest to crash. --- .github/actions/compilers/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/compilers/action.yml b/.github/actions/compilers/action.yml index daad5b694766f9..ab5b56a889672a 100644 --- a/.github/actions/compilers/action.yml +++ b/.github/actions/compilers/action.yml @@ -5,7 +5,7 @@ description: >- inputs: tag: required: false - default: clang-18 + default: clang-20 description: >- container image tag to use in this run. From 29d8a50d264be0c9cf1ddfc9fc2ce37724755b38 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 20 Nov 2025 20:02:29 +0900 Subject: [PATCH 1284/2435] [ruby/rubygems] Keep legacy windows platform, not removed them https://github.com/ruby/rubygems/commit/f360af8e3b --- lib/bundler/dsl.rb | 4 ++-- spec/bundler/bundler/dsl_spec.rb | 8 ++++---- spec/bundler/commands/cache_spec.rb | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 13e0783a436eff..6f06c4e918797d 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -427,8 +427,8 @@ def normalize_options(name, version, opts) windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } if windows_platforms.any? windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") - removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead." - Bundler::SharedHelpers.feature_removed! removed_message + deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead." + Bundler::SharedHelpers.feature_deprecated! deprecated_message end # Save sources passed in a key diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index 286dfa71151877..a19f251be5d946 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -221,8 +221,8 @@ to raise_error(Bundler::GemfileError, /is not a valid platform/) end - it "raises an error for legacy windows platforms" do - expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin, :x64_mingw has been removed/) + it "warn for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin, :x64_mingw will be removed in the future./) subject.gem("foo", platforms: [:mswin, :jruby, :x64_mingw]) end @@ -291,8 +291,8 @@ end describe "#platforms" do - it "raises an error for legacy windows platforms" do - expect(Bundler::SharedHelpers).to receive(:feature_removed!).with(/\APlatform :mswin64, :mingw has been removed/) + it "warn for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin64, :mingw will be removed in the future./) subject.platforms(:mswin64, :jruby, :mingw) do subject.gem("foo") end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 2414e1ca02e439..bd92a84e185315 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -207,15 +207,15 @@ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "prints an error when using legacy windows rubies" do + it "prints a warn when using legacy windows rubies" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] D bundle "cache --all-platforms", raise_on_error: false - expect(err).to include("removed") - expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).not_to exist + expect(err).to include("will be removed in the future") + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "does not attempt to install gems in without groups" do From aa9e15cb1ecb0de598f816e44a09e016aaa8ef5c Mon Sep 17 00:00:00 2001 From: Alexander Bulancov <6594487+trinistr@users.noreply.github.com> Date: Fri, 21 Nov 2025 03:30:42 +0300 Subject: [PATCH 1285/2435] Fix multiple bugs in `IO::Buffer.map` and update its documentation. (#15264) - Buffer's size did not account for offset when mapping the file, leading to possible crashes. - Size and offset were not checked properly, leading to many situations raising EINVAL errors with generic messages. - Documentation was wrong. --- io_buffer.c | 64 +++++++++++++++++++++++++------------ test/ruby/test_io_buffer.rb | 54 ++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 87e392b79181d6..abe7832bee829a 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -667,18 +667,25 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags * call-seq: IO::Buffer.map(file, [size, [offset, [flags]]]) -> io_buffer * * Create an IO::Buffer for reading from +file+ by memory-mapping the file. - * +file_io+ should be a +File+ instance, opened for reading. + * +file+ should be a +File+ instance, opened for reading or reading and writing. * * Optional +size+ and +offset+ of mapping can be specified. + * Trying to map an empty file or specify +size+ of 0 will raise an error. + * Valid values for +offset+ are system-dependent. * - * By default, the buffer would be immutable (read only); to create a writable - * mapping, you need to open a file in read-write mode, and explicitly pass - * +flags+ argument without IO::Buffer::IMMUTABLE. + * By default, the buffer is writable and expects the file to be writable. + * It is also shared, so several processes can use the same mapping. + * + * You can pass IO::Buffer::READONLY in +flags+ argument to make a read-only buffer; + * this allows to work with files opened only for reading. + * Specifying IO::Buffer::PRIVATE in +flags+ creates a private mapping, + * which will not impact other processes or the underlying file. + * It also allows updating a buffer created from a read-only file. * * File.write('test.txt', 'test') * * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) - * # => # + * # => # * * buffer.readonly? # => true * @@ -686,7 +693,7 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags * # => "test" * * buffer.set_string('b', 0) - * # `set_string': Buffer is not writable! (IO::Buffer::AccessError) + * # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError) * * # create read/write mapping: length 4 bytes, offset 0, flags 0 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) @@ -708,31 +715,48 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) // We might like to handle a string path? VALUE io = argv[0]; + rb_off_t file_size = rb_file_size(io); + // Compiler can confirm that we handled file_size <= 0 case: + if (UNLIKELY(file_size <= 0)) { + rb_raise(rb_eArgError, "Invalid negative or zero file size!"); + } + // Here, we assume that file_size is positive: + else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) { + rb_raise(rb_eArgError, "File larger than address space!"); + } + size_t size; if (argc >= 2 && !RB_NIL_P(argv[1])) { size = io_buffer_extract_size(argv[1]); - } - else { - rb_off_t file_size = rb_file_size(io); - - // Compiler can confirm that we handled file_size < 0 case: - if (file_size < 0) { - rb_raise(rb_eArgError, "Invalid negative file size!"); + if (UNLIKELY(size == 0)) { + rb_raise(rb_eArgError, "Size can't be zero!"); } - // Here, we assume that file_size is positive: - else if ((uintmax_t)file_size > SIZE_MAX) { - rb_raise(rb_eArgError, "File larger than address space!"); - } - else { - // This conversion should be safe: - size = (size_t)file_size; + if (UNLIKELY(size > (size_t)file_size)) { + rb_raise(rb_eArgError, "Size can't be larger than file size!"); } } + else { + // This conversion should be safe: + size = (size_t)file_size; + } // This is the file offset, not the buffer offset: rb_off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); + if (UNLIKELY(offset < 0)) { + rb_raise(rb_eArgError, "Offset can't be negative!"); + } + if (UNLIKELY(offset >= file_size)) { + rb_raise(rb_eArgError, "Offset too large!"); + } + if (RB_NIL_P(argv[1])) { + // Decrease size if it's set from the actual file size: + size = (size_t)(file_size - offset); + } + else if (UNLIKELY((size_t)(file_size - offset) < size)) { + rb_raise(rb_eArgError, "Offset too large!"); + } } enum rb_io_buffer_flags flags = 0; diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 62c46678882a10..997ed52640fb0d 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -73,12 +73,64 @@ def test_new_readonly def test_file_mapped buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)} - contents = buffer.get_string + assert_equal File.size(__FILE__), buffer.size + contents = buffer.get_string assert_include contents, "Hello World" assert_equal Encoding::BINARY, contents.encoding end + def test_file_mapped_with_size + buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)} + assert_equal 30, buffer.size + + contents = buffer.get_string + assert_equal "# frozen_string_literal: false", contents + assert_equal Encoding::BINARY, contents.encoding + end + + def test_file_mapped_size_too_large + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)} + end + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_size_just_enough + File.open(__FILE__) {|file| + assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size + } + end + + def test_file_mapped_offset_too_large + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + end + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_zero_size + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_negative_size + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_negative_offset + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)} + end + end + def test_file_mapped_invalid assert_raise TypeError do IO::Buffer.map("foobar") From 7a09df45f2619a672eaab763c2e2a0c5199d5daa Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 19 Nov 2025 16:08:55 -0500 Subject: [PATCH 1286/2435] Name the `iseq->body->param` struct and update bindings for JITs This will make reading the parameters nicer for the JITs. Should be no-op for the C side. --- vm_core.h | 2 +- yjit/src/cruby_bindings.inc.rs | 8 +- zjit/bindgen/src/main.rs | 8 +- zjit/src/cruby.rs | 9 +- zjit/src/cruby_bindings.inc.rs | 776 ++++++++++++++++++++++++++++++++- 5 files changed, 787 insertions(+), 16 deletions(-) diff --git a/vm_core.h b/vm_core.h index 0f83f2b2e9e015..28d585deb2ab01 100644 --- a/vm_core.h +++ b/vm_core.h @@ -431,7 +431,7 @@ struct rb_iseq_constant_body { * size = M+N+O+(*1)+K+(&1)+(**1) // parameter size. */ - struct { + struct rb_iseq_parameters { struct { unsigned int has_lead : 1; unsigned int has_opt : 1; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index a6aef48313ad71..66d4e5111d7bbd 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -490,7 +490,7 @@ pub const BUILTIN_ATTR_C_TRACE: rb_builtin_attr = 8; pub type rb_builtin_attr = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { +pub struct rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword { pub num: ::std::os::raw::c_int, pub required_num: ::std::os::raw::c_int, pub bits_start: ::std::os::raw::c_int, @@ -942,12 +942,14 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_seq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub type jit_bindgen_constants = u32; -pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_iseq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 7873a209777605..bac17f4a6d4452 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -424,8 +424,12 @@ fn main() { .blocklist_type("VALUE") .blocklist_type("ID") - .opaque_type("rb_iseq_t") - .blocklist_type("rb_iseq_t") + // Avoid binding to stuff we don't use + .blocklist_item("rb_thread_struct.*") + .opaque_type("rb_thread_struct.*") + .blocklist_item("iseq_inline_storage_entry_.*") + .opaque_type("iseq_inline_storage_entry") + .opaque_type("iseq_compile_data") // Finish the builder and generate the bindings. .generate() diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index a854f2e07c0667..771f256037ef4f 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -104,6 +104,7 @@ pub type RedefinitionFlag = u32; #[allow(unsafe_op_in_unsafe_fn)] #[allow(dead_code)] +#[allow(unnecessary_transmutes)] // https://github.com/rust-lang/rust-bindgen/issues/2807 #[allow(clippy::all)] // warning meant to help with reading; not useful for generated code mod autogened { use super::*; @@ -246,14 +247,6 @@ pub fn insn_len(opcode: usize) -> u32 { } } -/// Opaque iseq type for opaque iseq pointers from vm_core.h -/// See: -#[repr(C)] -pub struct rb_iseq_t { - _data: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - /// An object handle similar to VALUE in the C code. Our methods assume /// that this is a handle. Sometimes the C code briefly uses VALUE as /// an unsigned integer type and don't necessarily store valid handles but diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 0fde4e3ab70a93..66126b627c0fd3 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -410,6 +410,11 @@ pub const BOP_INCLUDE_P: ruby_basic_operators = 33; pub const BOP_LAST_: ruby_basic_operators = 34; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_id_table { + _unused: [u8; 0], +} pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1; pub const imemo_svar: imemo_type = 2; @@ -475,6 +480,7 @@ pub const VM_METHOD_TYPE_OPTIMIZED: rb_method_type_t = 9; pub const VM_METHOD_TYPE_MISSING: rb_method_type_t = 10; pub const VM_METHOD_TYPE_REFINED: rb_method_type_t = 11; pub type rb_method_type_t = u32; +pub type rb_iseq_t = rb_iseq_struct; pub type rb_cfunc_t = ::std::option::Option VALUE>; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -497,7 +503,22 @@ pub const OPTIMIZED_METHOD_TYPE_STRUCT_AREF: method_optimized_type = 3; pub const OPTIMIZED_METHOD_TYPE_STRUCT_ASET: method_optimized_type = 4; pub const OPTIMIZED_METHOD_TYPE__MAX: method_optimized_type = 5; pub type method_optimized_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_code_position_struct { + pub lineno: ::std::os::raw::c_int, + pub column: ::std::os::raw::c_int, +} +pub type rb_code_position_t = rb_code_position_struct; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_code_location_struct { + pub beg_pos: rb_code_position_t, + pub end_pos: rb_code_position_t, +} +pub type rb_code_location_t = rb_code_location_struct; pub type rb_num_t = ::std::os::raw::c_ulong; +pub type rb_snum_t = ::std::os::raw::c_long; pub const RUBY_TAG_NONE: ruby_tag_type = 0; pub const RUBY_TAG_RETURN: ruby_tag_type = 1; pub const RUBY_TAG_BREAK: ruby_tag_type = 2; @@ -534,6 +555,23 @@ pub struct iseq_inline_iv_cache_entry { pub struct iseq_inline_cvar_cache_entry { pub entry: *mut rb_cvar_class_tbl_entry, } +#[repr(C)] +#[repr(align(8))] +#[derive(Copy, Clone)] +pub struct iseq_inline_storage_entry { + pub _bindgen_opaque_blob: [u64; 2usize], +} +#[repr(C)] +pub struct rb_iseq_location_struct { + pub pathobj: VALUE, + pub base_label: VALUE, + pub label: VALUE, + pub first_lineno: ::std::os::raw::c_int, + pub node_id: ::std::os::raw::c_int, + pub code_location: rb_code_location_t, +} +pub type rb_iseq_location_t = rb_iseq_location_struct; +pub type iseq_bits_t = usize; pub const ISEQ_TYPE_TOP: rb_iseq_type = 0; pub const ISEQ_TYPE_METHOD: rb_iseq_type = 1; pub const ISEQ_TYPE_BLOCK: rb_iseq_type = 2; @@ -549,9 +587,611 @@ pub const BUILTIN_ATTR_SINGLE_NOARG_LEAF: rb_builtin_attr = 2; pub const BUILTIN_ATTR_INLINE_BLOCK: rb_builtin_attr = 4; pub const BUILTIN_ATTR_C_TRACE: rb_builtin_attr = 8; pub type rb_builtin_attr = u32; +pub type rb_jit_func_t = ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut rb_execution_context_struct, + arg2: *mut rb_control_frame_struct, + ) -> VALUE, +>; +#[repr(C)] +pub struct rb_iseq_constant_body { + pub type_: rb_iseq_type, + pub iseq_size: ::std::os::raw::c_uint, + pub iseq_encoded: *mut VALUE, + pub param: rb_iseq_constant_body_rb_iseq_parameters, + pub location: rb_iseq_location_t, + pub insns_info: rb_iseq_constant_body_iseq_insn_info, + pub local_table: *const ID, + pub lvar_states: *mut rb_iseq_constant_body_lvar_state, + pub catch_table: *mut iseq_catch_table, + pub parent_iseq: *const rb_iseq_struct, + pub local_iseq: *mut rb_iseq_struct, + pub is_entries: *mut iseq_inline_storage_entry, + pub call_data: *mut rb_call_data, + pub variable: rb_iseq_constant_body__bindgen_ty_1, + pub local_table_size: ::std::os::raw::c_uint, + pub ic_size: ::std::os::raw::c_uint, + pub ise_size: ::std::os::raw::c_uint, + pub ivc_size: ::std::os::raw::c_uint, + pub icvarc_size: ::std::os::raw::c_uint, + pub ci_size: ::std::os::raw::c_uint, + pub stack_max: ::std::os::raw::c_uint, + pub builtin_attrs: ::std::os::raw::c_uint, + pub prism: bool, + pub mark_bits: rb_iseq_constant_body__bindgen_ty_2, + pub outer_variables: *mut rb_id_table, + pub mandatory_only_iseq: *const rb_iseq_t, + pub jit_entry: rb_jit_func_t, + pub jit_entry_calls: ::std::os::raw::c_ulong, + pub jit_exception: rb_jit_func_t, + pub jit_exception_calls: ::std::os::raw::c_ulong, + pub zjit_payload: *mut ::std::os::raw::c_void, +} #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { +pub struct rb_iseq_constant_body_rb_iseq_parameters { + pub flags: rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1, + pub size: ::std::os::raw::c_uint, + pub lead_num: ::std::os::raw::c_int, + pub opt_num: ::std::os::raw::c_int, + pub rest_start: ::std::os::raw::c_int, + pub post_start: ::std::os::raw::c_int, + pub post_num: ::std::os::raw::c_int, + pub block_start: ::std::os::raw::c_int, + pub opt_table: *const VALUE, + pub keyword: *const rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword, +} +#[repr(C)] +#[repr(align(4))] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1 { + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub __bindgen_padding_0: u16, +} +impl rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1 { + #[inline] + pub fn has_lead(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_lead(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_lead_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 0usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_lead_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 0usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_opt(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_opt(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(1usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_opt_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 1usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_opt_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 1usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_rest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_rest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(2usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_rest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 2usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_rest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 2usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_post(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_post(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(3usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_post_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 3usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_post_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 3usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_kw(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_kw(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(4usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_kw_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 4usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_kw_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 4usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_kwrest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_kwrest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(5usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_kwrest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 5usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_kwrest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 5usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_block(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(6usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_block(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(6usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_block_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 6usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_block_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 6usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn ambiguous_param0(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u32) } + } + #[inline] + pub fn set_ambiguous_param0(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(7usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn ambiguous_param0_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 7usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_ambiguous_param0_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 7usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn accepts_no_kwarg(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u32) } + } + #[inline] + pub fn set_accepts_no_kwarg(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(8usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn accepts_no_kwarg_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 8usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_accepts_no_kwarg_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 8usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn ruby2_keywords(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u32) } + } + #[inline] + pub fn set_ruby2_keywords(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(9usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn ruby2_keywords_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 9usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_ruby2_keywords_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 9usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn anon_rest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u32) } + } + #[inline] + pub fn set_anon_rest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(10usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn anon_rest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 10usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_anon_rest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 10usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn anon_kwrest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u32) } + } + #[inline] + pub fn set_anon_kwrest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(11usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn anon_kwrest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 11usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_anon_kwrest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 11usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn use_block(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(12usize, 1u8) as u32) } + } + #[inline] + pub fn set_use_block(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(12usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn use_block_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 12usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_use_block_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 12usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn forwardable(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(13usize, 1u8) as u32) } + } + #[inline] + pub fn set_forwardable(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(13usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn forwardable_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 13usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_forwardable_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 13usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn new_bitfield_1( + has_lead: ::std::os::raw::c_uint, + has_opt: ::std::os::raw::c_uint, + has_rest: ::std::os::raw::c_uint, + has_post: ::std::os::raw::c_uint, + has_kw: ::std::os::raw::c_uint, + has_kwrest: ::std::os::raw::c_uint, + has_block: ::std::os::raw::c_uint, + ambiguous_param0: ::std::os::raw::c_uint, + accepts_no_kwarg: ::std::os::raw::c_uint, + ruby2_keywords: ::std::os::raw::c_uint, + anon_rest: ::std::os::raw::c_uint, + anon_kwrest: ::std::os::raw::c_uint, + use_block: ::std::os::raw::c_uint, + forwardable: ::std::os::raw::c_uint, + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 1u8, { + let has_lead: u32 = unsafe { ::std::mem::transmute(has_lead) }; + has_lead as u64 + }); + __bindgen_bitfield_unit.set(1usize, 1u8, { + let has_opt: u32 = unsafe { ::std::mem::transmute(has_opt) }; + has_opt as u64 + }); + __bindgen_bitfield_unit.set(2usize, 1u8, { + let has_rest: u32 = unsafe { ::std::mem::transmute(has_rest) }; + has_rest as u64 + }); + __bindgen_bitfield_unit.set(3usize, 1u8, { + let has_post: u32 = unsafe { ::std::mem::transmute(has_post) }; + has_post as u64 + }); + __bindgen_bitfield_unit.set(4usize, 1u8, { + let has_kw: u32 = unsafe { ::std::mem::transmute(has_kw) }; + has_kw as u64 + }); + __bindgen_bitfield_unit.set(5usize, 1u8, { + let has_kwrest: u32 = unsafe { ::std::mem::transmute(has_kwrest) }; + has_kwrest as u64 + }); + __bindgen_bitfield_unit.set(6usize, 1u8, { + let has_block: u32 = unsafe { ::std::mem::transmute(has_block) }; + has_block as u64 + }); + __bindgen_bitfield_unit.set(7usize, 1u8, { + let ambiguous_param0: u32 = unsafe { ::std::mem::transmute(ambiguous_param0) }; + ambiguous_param0 as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { + let accepts_no_kwarg: u32 = unsafe { ::std::mem::transmute(accepts_no_kwarg) }; + accepts_no_kwarg as u64 + }); + __bindgen_bitfield_unit.set(9usize, 1u8, { + let ruby2_keywords: u32 = unsafe { ::std::mem::transmute(ruby2_keywords) }; + ruby2_keywords as u64 + }); + __bindgen_bitfield_unit.set(10usize, 1u8, { + let anon_rest: u32 = unsafe { ::std::mem::transmute(anon_rest) }; + anon_rest as u64 + }); + __bindgen_bitfield_unit.set(11usize, 1u8, { + let anon_kwrest: u32 = unsafe { ::std::mem::transmute(anon_kwrest) }; + anon_kwrest as u64 + }); + __bindgen_bitfield_unit.set(12usize, 1u8, { + let use_block: u32 = unsafe { ::std::mem::transmute(use_block) }; + use_block as u64 + }); + __bindgen_bitfield_unit.set(13usize, 1u8, { + let forwardable: u32 = unsafe { ::std::mem::transmute(forwardable) }; + forwardable as u64 + }); + __bindgen_bitfield_unit + } +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword { pub num: ::std::os::raw::c_int, pub required_num: ::std::os::raw::c_int, pub bits_start: ::std::os::raw::c_int, @@ -560,6 +1200,66 @@ pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { pub default_values: *mut VALUE, } #[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_iseq_insn_info { + pub body: *const iseq_insn_info_entry, + pub positions: *mut ::std::os::raw::c_uint, + pub size: ::std::os::raw::c_uint, + pub succ_index_table: *mut succ_index_table, +} +pub const lvar_uninitialized: rb_iseq_constant_body_lvar_state = 0; +pub const lvar_initialized: rb_iseq_constant_body_lvar_state = 1; +pub const lvar_reassigned: rb_iseq_constant_body_lvar_state = 2; +pub type rb_iseq_constant_body_lvar_state = u32; +#[repr(C)] +pub struct rb_iseq_constant_body__bindgen_ty_1 { + pub flip_count: rb_snum_t, + pub script_lines: VALUE, + pub coverage: VALUE, + pub pc2branchindex: VALUE, + pub original_iseq: *mut VALUE, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union rb_iseq_constant_body__bindgen_ty_2 { + pub list: *mut iseq_bits_t, + pub single: iseq_bits_t, +} +#[repr(C)] +pub struct rb_iseq_struct { + pub flags: VALUE, + pub wrapper: VALUE, + pub body: *mut rb_iseq_constant_body, + pub aux: rb_iseq_struct__bindgen_ty_1, +} +#[repr(C)] +pub struct rb_iseq_struct__bindgen_ty_1 { + pub compile_data: __BindgenUnionField<*mut iseq_compile_data>, + pub loader: __BindgenUnionField, + pub exec: __BindgenUnionField, + pub bindgen_union_field: [u64; 2usize], +} +#[repr(C)] +pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_1 { + pub obj: VALUE, + pub index: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_2 { + pub local_hooks: *mut rb_hook_list_struct, + pub global_trace_events: rb_event_flag_t, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_hook_list_struct { + pub hooks: *mut rb_event_hook_struct, + pub events: rb_event_flag_t, + pub running: ::std::os::raw::c_uint, + pub need_clean: bool, + pub is_local: bool, +} +#[repr(C)] pub struct rb_captured_block { pub self_: VALUE, pub ep: *const VALUE, @@ -1076,6 +1776,67 @@ pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), >; +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data { + pub _bindgen_opaque_blob: [u64; 24usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union iseq_compile_data__bindgen_ty_1 { + pub list: *mut iseq_bits_t, + pub single: iseq_bits_t, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data__bindgen_ty_2 { + pub storage_head: *mut iseq_compile_data_storage, + pub storage_current: *mut iseq_compile_data_storage, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data__bindgen_ty_3 { + pub storage_head: *mut iseq_compile_data_storage, + pub storage_current: *mut iseq_compile_data_storage, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_insn_info_entry { + pub line_no: ::std::os::raw::c_int, + pub node_id: ::std::os::raw::c_int, + pub events: rb_event_flag_t, +} +pub const CATCH_TYPE_RESCUE: rb_catch_type = 3; +pub const CATCH_TYPE_ENSURE: rb_catch_type = 5; +pub const CATCH_TYPE_RETRY: rb_catch_type = 7; +pub const CATCH_TYPE_BREAK: rb_catch_type = 9; +pub const CATCH_TYPE_REDO: rb_catch_type = 11; +pub const CATCH_TYPE_NEXT: rb_catch_type = 13; +pub type rb_catch_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_catch_table_entry { + pub type_: rb_catch_type, + pub iseq: *mut rb_iseq_t, + pub start: ::std::os::raw::c_uint, + pub end: ::std::os::raw::c_uint, + pub cont: ::std::os::raw::c_uint, + pub sp: ::std::os::raw::c_uint, +} +#[repr(C, packed)] +pub struct iseq_catch_table { + pub size: ::std::os::raw::c_uint, + pub entries: __IncompleteArrayField, +} +#[repr(C)] +#[derive(Debug)] +pub struct iseq_compile_data_storage { + pub next: *mut iseq_compile_data_storage, + pub pos: ::std::os::raw::c_uint, + pub size: ::std::os::raw::c_uint, + pub buff: __IncompleteArrayField<::std::os::raw::c_char>, +} pub const DEFINED_NOT_DEFINED: defined_type = 0; pub const DEFINED_NIL: defined_type = 1; pub const DEFINED_IVAR: defined_type = 2; @@ -1100,7 +1861,18 @@ pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub type jit_bindgen_constants = u32; pub const rb_invalid_shape_id: shape_id_t = 4294967295; -pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_iseq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct succ_index_table { + pub _address: u8, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_event_hook_struct { + pub _address: u8, +} unsafe extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; From 07ddb0ed73b1ea61cb717f4424f7b2909d8c0ab3 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 19 Nov 2025 16:39:29 -0500 Subject: [PATCH 1287/2435] ZJIT: Read `iseq->body->param` directly instead of through FFI Going through a call to a C function just to read a bitfield was a little extreme. We did it to be super conservative since bitfields have historically been the trigger of many bugs and surprises. Let's try directly accessing them with code from rust-bindgen. If this ends up causing issues, we can use the FFI approach behind nicer wrappers. In any case, directly access regular struct fields such as `lead_num` and `opt_num` to remove boilerplate. --- zjit/src/codegen.rs | 9 +++++---- zjit/src/cruby.rs | 37 ++++++++++++++++++------------------- zjit/src/hir.rs | 39 ++++++++++++++++++++++----------------- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 082db3fae44108..d00ab500d72796 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1293,10 +1293,11 @@ fn gen_send_without_block_direct( let mut c_args = vec![recv]; c_args.extend(&args); - let num_optionals_passed = if unsafe { get_iseq_flags_has_opt(iseq) } { + let params = unsafe { iseq.params() }; + let num_optionals_passed = if params.flags.has_opt() != 0 { // See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) } as u32; - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) } as u32; + let lead_num = params.lead_num as u32; + let opt_num = params.opt_num as u32; assert!(args.len() as u32 <= lead_num + opt_num); let num_optionals_passed = args.len() as u32 - lead_num; num_optionals_passed @@ -2212,7 +2213,7 @@ c_callable! { // Fill nils to uninitialized (non-argument) locals let local_size = get_iseq_body_local_table_size(iseq).to_usize(); - let num_params = get_iseq_body_param_size(iseq).to_usize(); + let num_params = iseq.params().size.to_usize(); let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 771f256037ef4f..443ed0d86e3a99 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -192,21 +192,7 @@ pub use rb_get_iseq_body_local_iseq as get_iseq_body_local_iseq; pub use rb_get_iseq_body_iseq_encoded as get_iseq_body_iseq_encoded; pub use rb_get_iseq_body_stack_max as get_iseq_body_stack_max; pub use rb_get_iseq_body_type as get_iseq_body_type; -pub use rb_get_iseq_flags_has_lead as get_iseq_flags_has_lead; -pub use rb_get_iseq_flags_has_opt as get_iseq_flags_has_opt; -pub use rb_get_iseq_flags_has_kw as get_iseq_flags_has_kw; -pub use rb_get_iseq_flags_has_rest as get_iseq_flags_has_rest; -pub use rb_get_iseq_flags_has_post as get_iseq_flags_has_post; -pub use rb_get_iseq_flags_has_kwrest as get_iseq_flags_has_kwrest; -pub use rb_get_iseq_flags_has_block as get_iseq_flags_has_block; -pub use rb_get_iseq_flags_ambiguous_param0 as get_iseq_flags_ambiguous_param0; -pub use rb_get_iseq_flags_accepts_no_kwarg as get_iseq_flags_accepts_no_kwarg; pub use rb_get_iseq_body_local_table_size as get_iseq_body_local_table_size; -pub use rb_get_iseq_body_param_keyword as get_iseq_body_param_keyword; -pub use rb_get_iseq_body_param_size as get_iseq_body_param_size; -pub use rb_get_iseq_body_param_lead_num as get_iseq_body_param_lead_num; -pub use rb_get_iseq_body_param_opt_num as get_iseq_body_param_opt_num; -pub use rb_get_iseq_body_param_opt_table as get_iseq_body_param_opt_table; pub use rb_get_cikw_keyword_len as get_cikw_keyword_len; pub use rb_get_cikw_keywords_idx as get_cikw_keywords_idx; pub use rb_get_call_data_ci as get_call_data_ci; @@ -306,11 +292,10 @@ pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { } /// Index of the local variable that has a rest parameter if any -pub fn iseq_rest_param_idx(iseq: IseqPtr) -> Option { - if !iseq.is_null() && unsafe { get_iseq_flags_has_rest(iseq) } { - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - Some(opt_num + lead_num) +pub fn iseq_rest_param_idx(params: &IseqParameters) -> Option { + // TODO(alan): replace with `params.rest_start` + if params.flags.has_rest() != 0 { + Some(params.opt_num + params.lead_num) } else { None } @@ -686,6 +671,20 @@ impl VALUE { } } +pub type IseqParameters = rb_iseq_constant_body_rb_iseq_parameters; + +/// Extension trait to enable method calls on [`IseqPtr`] +pub trait IseqAccess { + unsafe fn params<'a>(self) -> &'a IseqParameters; +} + +impl IseqAccess for IseqPtr { + /// Get a description of the ISEQ's signature. Analogous to `ISEQ_BODY(iseq)->param` in C. + unsafe fn params<'a>(self) -> &'a IseqParameters { + unsafe { &(*(*self).body).param } + } +} + impl From for VALUE { /// For `.into()` convenience fn from(iseq: IseqPtr) -> Self { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 172b177c456f3b..961fe1c1428825 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1497,14 +1497,15 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq can_send = false; function.push_insn(block, Insn::IncrCounter(counter)); }; + let params = unsafe { iseq.params() }; use Counter::*; - if unsafe { rb_get_iseq_flags_has_rest(iseq) } { count_failure(complex_arg_pass_param_rest) } - if unsafe { rb_get_iseq_flags_has_post(iseq) } { count_failure(complex_arg_pass_param_post) } - if unsafe { rb_get_iseq_flags_has_kw(iseq) } { count_failure(complex_arg_pass_param_kw) } - if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { count_failure(complex_arg_pass_param_kwrest) } - if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(complex_arg_pass_param_block) } - if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(complex_arg_pass_param_forwardable) } + if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) } + if 0 != params.flags.has_post() { count_failure(complex_arg_pass_param_post) } + if 0 != params.flags.has_kw() { count_failure(complex_arg_pass_param_kw) } + if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } + if 0 != params.flags.has_block() { count_failure(complex_arg_pass_param_block) } + if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) } if !can_send { function.set_dynamic_send_reason(send_insn, ComplexArgPass); @@ -1512,8 +1513,8 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq } // Because we exclude e.g. post parameters above, they are also excluded from the sum below. - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; + let lead_num = params.lead_num; + let opt_num = params.opt_num; can_send = c_int::try_from(args.len()) .as_ref() .map(|argc| (lead_num..=lead_num + opt_num).contains(argc)) @@ -2086,8 +2087,9 @@ impl Function { /// Set self.param_types. They are copied to the param types of jit_entry_blocks. fn set_param_types(&mut self) { let iseq = self.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); - let rest_param_idx = iseq_rest_param_idx(iseq); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let rest_param_idx = iseq_rest_param_idx(params); self.param_types.push(types::BasicObject); // self for local_idx in 0..param_size { @@ -4596,11 +4598,12 @@ fn insn_idx_at_offset(idx: u32, offset: i64) -> u32 { /// List of insn_idx that starts a JIT entry block pub fn jit_entry_insns(iseq: IseqPtr) -> Vec { // TODO(alan): Make an iterator type for this instead of copying all of the opt_table each call - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; + let params = unsafe { iseq.params() }; + let opt_num = params.opt_num; if opt_num > 0 { let mut result = vec![]; - let opt_table = unsafe { get_iseq_body_param_opt_table(iseq) }; // `opt_num + 1` entries + let opt_table = params.opt_table; // `opt_num + 1` entries for opt_idx in 0..=opt_num as isize { let insn_idx = unsafe { opt_table.offset(opt_idx).read().as_u32() }; result.push(insn_idx); @@ -5715,8 +5718,9 @@ fn compile_entry_state(fun: &mut Function) -> (InsnId, FrameState) { fun.push_insn(entry_block, Insn::EntryPoint { jit_entry_idx: None }); let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); - let rest_param_idx = iseq_rest_param_idx(iseq); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let rest_param_idx = iseq_rest_param_idx(params); let self_param = fun.push_insn(entry_block, Insn::LoadSelf); let mut entry_state = FrameState::new(iseq); @@ -5748,9 +5752,10 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc /// Compile params and initial locals for a jit_entry_block fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId, jit_entry_idx: usize) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); - let opt_num: usize = unsafe { get_iseq_body_param_opt_num(iseq) }.try_into().expect("iseq param opt_num >= 0"); - let lead_num: usize = unsafe { get_iseq_body_param_lead_num(iseq) }.try_into().expect("iseq param lead_num >= 0"); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let opt_num: usize = params.opt_num.try_into().expect("iseq param opt_num >= 0"); + let lead_num: usize = params.lead_num.try_into().expect("iseq param lead_num >= 0"); let passed_opt_num = jit_entry_idx; let self_param = fun.push_insn(jit_entry_block, Insn::Param); From 9764306c48619e2d168235a4864c43f9e0db78e0 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 28 May 2025 13:26:10 -0700 Subject: [PATCH 1288/2435] Accurate GC.stat under multi-Ractor mode --- gc.c | 2 +- gc/default/default.c | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/gc.c b/gc.c index c9fa18f42b8e8e..26afb4e71817bc 100644 --- a/gc.c +++ b/gc.c @@ -180,13 +180,13 @@ rb_gc_vm_barrier(void) rb_vm_barrier(); } -#if USE_MODULAR_GC void * rb_gc_get_ractor_newobj_cache(void) { return GET_RACTOR()->newobj_cache; } +#if USE_MODULAR_GC void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context) { diff --git a/gc/default/default.c b/gc/default/default.c index 42561543d1a7c7..3d40b3dddfdbf8 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2216,6 +2216,17 @@ rb_gc_impl_size_allocatable_p(size_t size) } static const size_t ALLOCATED_COUNT_STEP = 1024; +static void +ractor_cache_flush_count(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache) +{ + for (int heap_idx = 0; heap_idx < HEAP_COUNT; heap_idx++) { + rb_ractor_newobj_heap_cache_t *heap_cache = &cache->heap_caches[heap_idx]; + + rb_heap_t *heap = &heaps[heap_idx]; + RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); + heap_cache->allocated_objects_count = 0; + } +} static inline VALUE ractor_cache_allocate_slot(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, @@ -2240,19 +2251,11 @@ ractor_cache_allocate_slot(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *ca rb_asan_unpoison_object(obj, true); heap_cache->freelist = p->next; - if (rb_gc_multi_ractor_p()) { - heap_cache->allocated_objects_count++; - rb_heap_t *heap = &heaps[heap_idx]; - if (heap_cache->allocated_objects_count >= ALLOCATED_COUNT_STEP) { - RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); - heap_cache->allocated_objects_count = 0; - } - } - else { - rb_heap_t *heap = &heaps[heap_idx]; - heap->total_allocated_objects++; - GC_ASSERT(heap->total_slots >= - (heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count)); + heap_cache->allocated_objects_count++; + rb_heap_t *heap = &heaps[heap_idx]; + if (heap_cache->allocated_objects_count >= ALLOCATED_COUNT_STEP) { + RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); + heap_cache->allocated_objects_count = 0; } #if RGENGC_CHECK_MODE @@ -5172,6 +5175,8 @@ gc_verify_internal_consistency_(rb_objspace_t *objspace) /* check counters */ + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + if (!is_lazy_sweeping(objspace) && !finalizing && !rb_gc_multi_ractor_p()) { @@ -7510,6 +7515,8 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) setup_gc_stat_symbols(); + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + if (RB_TYPE_P(hash_or_sym, T_HASH)) { hash = hash_or_sym; } @@ -7662,6 +7669,8 @@ rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym) { rb_objspace_t *objspace = objspace_ptr; + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + setup_gc_stat_heap_symbols(); if (NIL_P(heap_name)) { From 9d04fb52aff73ba2b73753f6d172c2d21322a3bc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 21 Nov 2025 10:51:29 +0900 Subject: [PATCH 1289/2435] CI: cmake in scoop seems unused --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f52c76c8eaa6c4..3dce96def3e92e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -81,7 +81,7 @@ jobs: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser iwr -useb get.scoop.sh | iex Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH - scoop install vcpkg uutils-coreutils cmake@3.31.6 + scoop install vcpkg uutils-coreutils shell: pwsh - name: Restore vcpkg artifact From a26f8235283b27dc1c5018addd0dfac209aaaa17 Mon Sep 17 00:00:00 2001 From: sue445 Date: Thu, 20 Nov 2025 22:34:01 +0900 Subject: [PATCH 1290/2435] [ruby/rubygems] Add go_gem/rake_task for Go native extention gem skeleton https://github.com/ruby/rubygems/commit/64f92d2da0 --- lib/bundler/templates/newgem/Rakefile.tt | 6 ++++++ spec/bundler/commands/newgem_spec.rb | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 172183d4b410d8..dfb9edaa39a13f 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -59,6 +59,12 @@ Rake::ExtensionTask.new("<%= config[:underscored_name] %>", GEMSPEC) do |ext| end <% end -%> +<% if config[:ext] == "go" -%> +require "go_gem/rake_task" + +GoGem::RakeTask.new("<%= config[:underscored_name] %>") +<% end -%> + <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 7a837bd08f0112..1d158726bed6a0 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -1829,6 +1829,14 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}") end + it "includes go_gem extension in Rakefile" do + expect(bundled_app("#{gem_name}/Rakefile").read).to include(<<~RUBY) + require "go_gem/rake_task" + + GoGem::RakeTask.new("#{gem_name}") + RUBY + end + context "with --no-ci" do let(:flags) { "--ext=go --no-ci" } From 9aa09b4620fd633ef2ffaff4bdae5e344b173ee6 Mon Sep 17 00:00:00 2001 From: sue445 Date: Fri, 21 Nov 2025 09:50:27 +0900 Subject: [PATCH 1291/2435] [ruby/rubygems] Fixed RuboCop offense in Rakefile generated by `bundle gem` ``` Offenses: Rakefile:18:1: C: [Correctable] Layout/EmptyLines: Extra blank line detected. Diff: @@ -11,4 +11,5 @@ ext.lib_dir = "lib/test_gem" end + task default: :compile https://github.com/ruby/rubygems/commit/8c414729df --- lib/bundler/templates/newgem/Rakefile.tt | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index dfb9edaa39a13f..83f10009c7042e 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -64,7 +64,6 @@ require "go_gem/rake_task" GoGem::RakeTask.new("<%= config[:underscored_name] %>") <% end -%> - <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> From 8b116ee8b982f838d8464b7ee310f37bc3282efb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 16 Nov 2025 12:28:31 -0800 Subject: [PATCH 1292/2435] [ruby/rubygems] create a gem version instead of comparing with a string https://github.com/ruby/rubygems/commit/c1e3d4d63b --- lib/rubygems.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index c398c985f58556..8530a2a893cb5d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -287,7 +287,7 @@ def self.activate_and_load_bin_path(name, exec_name = nil, *requirements) # RubyGems now uses this new `Gem.activate_and_load_bin_path` helper in # binstubs, which is of course not overridden in Bundler since it didn't # exist at the time. So, include the override here to workaround that. - load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= "2.5.22" + load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= Gem::Version.create("2.5.22") # Make sure there's no version of Bundler in `$LOAD_PATH` that's different # from the version we just activated. If that was the case (it happens @@ -666,7 +666,7 @@ def self.load_safe_marshal # warnings in platform constants def self.load_bundler_extensions(version) - return unless version <= "2.6.9" + return unless version <= Gem::Version.create("2.6.9") previous_platforms = {} From 917e77be5e5bf13b22009bec5568aa031138a605 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Nov 2025 17:26:40 -0800 Subject: [PATCH 1293/2435] [ruby/rubygems] Deprecate comparing Gem::Version objects with strings Comparing version objects is a huge bottleneck in dependency solvers (like inside Bundler). I would like to make comparing version objects cheaper. Right now we support comparing version objects with strings by trying to coerce the string to a version. So for example: ```ruby Gem::Version.new("1") <=> "12" ``` I would like to deprecate and remove support for this feature so that we can reduce the overhead of `def <=>`. I'm not sure what version of RubyGems we could remove this from though. https://github.com/ruby/rubygems/commit/81b7602183 --- lib/rubygems/version.rb | 12 +++++++++--- test/rubygems/test_gem_version.rb | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index c9fffc1cb7ca93..43a0e4e78375d5 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -339,11 +339,17 @@ def approximate_recommendation ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this - # one. Attempts to compare to something that's not a - # Gem::Version or a valid version String return +nil+. + # one. +other+ must be an instance of Gem::Version, comparing with + # other types may raise an exception. def <=>(other) - return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) + if String === other + unless Gem::Deprecate.skip + warn "comparing version objects with strings is deprecated and will be removed" + end + return unless self.class.correct?(other) + return self <=> self.class.new(other) + end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 1d963daa65bac6..ad2a11c631803f 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -154,11 +154,20 @@ def test_spaceship assert_equal(-1, v("5.a") <=> v("5.0.0.rc2")) assert_equal(1, v("5.x") <=> v("5.0.0.rc2")) - assert_equal(0, v("1.9.3") <=> "1.9.3") - assert_equal(1, v("1.9.3") <=> "1.9.2.99") - assert_equal(-1, v("1.9.3") <=> "1.9.3.1") - - assert_nil v("1.0") <=> "whatever" + [ + [0, "1.9.3"], + [1, "1.9.2.99"], + [-1, "1.9.3.1"], + [nil, "whatever"], + ].each do |cmp, string_ver| + expected = "comparing version objects with strings is deprecated and will be removed\n" + + actual_stdout, actual_stderr = capture_output do + assert_equal(cmp, v("1.9.3") <=> string_ver) + end + assert_empty actual_stdout + assert_equal expected, actual_stderr + end end def test_approximate_recommendation From ee002a5ee061e82da639e499a36810a747998747 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 21 Nov 2025 11:22:01 +0900 Subject: [PATCH 1294/2435] [ruby/rubygems] Respect `BUNDLE_VERSION` config at Gem::BundlerVersionFinder If we use "system" variable in BUNDLE_VERSION on Bundler configuration, we can use bundler version provided by system installation. But the current logic returns the first activated version of bundler like 2.7.2. It makes to confuse users. https://github.com/ruby/rubygems/commit/4eb66d9549 --- lib/rubygems/bundler_version_finder.rb | 31 ++++++++ test/rubygems/helper.rb | 3 + .../test_gem_bundler_version_finder.rb | 70 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index 602e00c1d866fb..c930c2e19c264f 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,6 +2,8 @@ module Gem::BundlerVersionFinder def self.bundler_version + return if bundle_config_version == "system" + v = ENV["BUNDLER_VERSION"] v = nil if v&.empty? @@ -78,4 +80,33 @@ def self.lockfile_contents File.read(lockfile) end private_class_method :lockfile_contents + + def self.bundle_config_version + config_file = bundler_config_file + return unless config_file && File.file?(config_file) + + contents = File.read(config_file) + contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/ + + $1 + end + private_class_method :bundle_config_version + + def self.bundler_config_file + # see Bundler::Settings#global_config_file and local_config_file + # global + if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? + ENV["BUNDLE_CONFIG"] + elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? + ENV["BUNDLE_USER_CONFIG"] + elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? + ENV["BUNDLE_USER_HOME"] + "config" + elsif Gem.user_home && !Gem.user_home.empty? + Gem.user_home + ".bundle/config" + else + # local + "config" + end + end + private_class_method :bundler_config_file end diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 53bed0b4151b97..6e0be10ef53044 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -335,6 +335,9 @@ def setup ENV["XDG_STATE_HOME"] = nil ENV["SOURCE_DATE_EPOCH"] = nil ENV["BUNDLER_VERSION"] = nil + ENV["BUNDLE_CONFIG"] = nil + ENV["BUNDLE_USER_CONFIG"] = nil + ENV["BUNDLE_USER_HOME"] = nil ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true" @current_dir = Dir.pwd diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 908f9278323c09..a773d6249b5967 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -2,6 +2,7 @@ require_relative "helper" require "rubygems/bundler_version_finder" +require "tempfile" class TestGemBundlerVersionFinder < Gem::TestCase def setup @@ -56,6 +57,75 @@ def test_bundler_version_with_bundle_update_bundler assert_nil bvf.bundler_version end + def test_bundler_version_with_bundle_config + config_content = <<~CONFIG + BUNDLE_VERSION: "system" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_single_quoted + config_with_single_quoted_version = <<~CONFIG + BUNDLE_VERSION: 'system' + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_with_single_quoted_version) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_version + ENV["BUNDLER_VERSION"] = "1.1.1.1" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_non_existent_file + bvf.stub(:bundler_config_file, "/non/existent/path") do + assert_nil bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_config_without_version + config_without_version = <<~CONFIG + BUNDLE_JOBS: "8" + BUNDLE_GEM__TEST: "minitest" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_without_version) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + def test_bundler_version_with_lockfile bvf.stub(:lockfile_contents, "") do assert_nil bvf.bundler_version From 451c12099472764f1190df8db451dbeaedd1b1c8 Mon Sep 17 00:00:00 2001 From: Philip Arndt Date: Fri, 21 Nov 2025 17:33:57 +1300 Subject: [PATCH 1295/2435] [ruby/rubygems] Check for file existence before deletion from cache (https://github.com/ruby/rubygems/pull/9095) * Rescue when deleting a non-existent cached gem file When a gem was in the cache, but another process deletes it first, this delete command fails. To work around this, I'm rescuing from Errno::ENOENT and swalling the error. The file is gone, and we can move on. * Apply suggestion from @kou Co-authored-by: Sutou Kouhei --------- https://github.com/ruby/rubygems/commit/b30bcbc648 Co-authored-by: Hiroshi SHIBATA Co-authored-by: Sutou Kouhei --- lib/bundler/runtime.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 9b2416402bb74c..5eb827dcb2a8ed 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -240,7 +240,11 @@ def prune_gem_cache(resolve, cache_path) cached.each do |path| Bundler.ui.info " * #{File.basename(path)}" - File.delete(path) + + begin + File.delete(path) + rescue Errno::ENOENT + end end end end From 1d160ed0591fbaabe1ae6d76920da409e238b396 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 21 Nov 2025 14:39:51 +0900 Subject: [PATCH 1296/2435] Fixed warning for String comparison of Gem::Version --- lib/bundler/definition.rb | 2 +- lib/rubygems/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index ca41d7953d8ea2..437390f3ec448b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1114,7 +1114,7 @@ def preload_git_sources end def find_source_requirements - if Gem.ruby_version >= "3.3" + if Gem.ruby_version >= Gem::Version.new("3.3") # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to # fetch git repositories one by one. preload_git_sources diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index 43a0e4e78375d5..a88a4a49ee2b94 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -345,7 +345,7 @@ def approximate_recommendation def <=>(other) if String === other unless Gem::Deprecate.skip - warn "comparing version objects with strings is deprecated and will be removed" + warn "comparing version objects with strings is deprecated and will be removed", uplevel: 1 end return unless self.class.correct?(other) return self <=> self.class.new(other) From bcc7b2049c45356b11992e095e2f114c02736e24 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 21 Nov 2025 14:52:46 +0900 Subject: [PATCH 1297/2435] Use assert_match for uplevel option --- test/rubygems/test_gem_version.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index ad2a11c631803f..567a4eb4874333 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -160,13 +160,11 @@ def test_spaceship [-1, "1.9.3.1"], [nil, "whatever"], ].each do |cmp, string_ver| - expected = "comparing version objects with strings is deprecated and will be removed\n" - actual_stdout, actual_stderr = capture_output do assert_equal(cmp, v("1.9.3") <=> string_ver) end assert_empty actual_stdout - assert_equal expected, actual_stderr + assert_match /comparing version objects with strings is deprecated and will be removed/, actual_stderr end end From eb11b40bb54b11456f88564957ec22c8c08c203c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 21 Nov 2025 16:20:35 +0900 Subject: [PATCH 1298/2435] [ruby/rubygems] bin/rubocop -a https://github.com/ruby/rubygems/commit/fbf6fb667e --- test/rubygems/test_gem_version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 567a4eb4874333..ce38a59113d2d2 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -164,7 +164,7 @@ def test_spaceship assert_equal(cmp, v("1.9.3") <=> string_ver) end assert_empty actual_stdout - assert_match /comparing version objects with strings is deprecated and will be removed/, actual_stderr + assert_match(/comparing version objects with strings is deprecated and will be removed/, actual_stderr) end end From d4e1f9e1b8621ef92caa5061dbc11e1860fa5eed Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 21 Nov 2025 13:18:54 +0900 Subject: [PATCH 1299/2435] Win: quote equal sign in command line `cmd.exe` splits the command line also by equal signs, not only by space characters. --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 648dd00b0252b6..70d61231f278c0 100644 --- a/common.mk +++ b/common.mk @@ -46,7 +46,7 @@ RUN_OPTS = --disable-gems GIT_IN_SRC = $(GIT) -C $(srcdir) GIT_LOG = $(GIT_IN_SRC) log --no-show-signature -GIT_LOG_FORMAT = $(GIT_LOG) --pretty=format: +GIT_LOG_FORMAT = $(GIT_LOG) "--pretty=format:" # GITPULLOPTIONS = --no-tags From b9a213f791eb79372d10f6ba07b3803140b3eb59 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 21 Nov 2025 13:26:43 +0900 Subject: [PATCH 1300/2435] Fix timezone of `yesterday` Set the `TZ environment variable. `git log` does not recognize UTC offset in `--before` option, unless full datetime is given. --- template/Makefile.in | 2 +- win32/Makefile.sub | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/template/Makefile.in b/template/Makefile.in index bf5281e0df8498..2987c86488cf2f 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -748,4 +748,4 @@ yes-test-syntax-suggest: $(PREPARE_SYNTAX_SUGGEST) no-test-syntax-suggest: yesterday: - $(GIT_IN_SRC) reset --hard `$(GIT_LOG_FORMAT):%H -1 --before=00:00+0900` + $(GIT_IN_SRC) reset --hard `TZ=UTC-9 $(GIT_LOG_FORMAT):%H -1 --before=00:00` diff --git a/win32/Makefile.sub b/win32/Makefile.sub index f95b583fb82a5e..04ec2873275dd5 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1391,6 +1391,7 @@ rubyspec-capiext: $(RUBYSPEC_CAPIEXT_EXTS) exts: rubyspec-capiext yesterday: + (set TZ=UTC-9) && \ for /f "usebackq" %H in \ - (`$(GIT_LOG_FORMAT):%H -1 "--before=00:00+0900"`) do \ + (`$(GIT_LOG_FORMAT):%H -1 "--before=00:00"`) do \ $(GIT_IN_SRC) reset --hard %%H From 5f5da2c293766b98e543b3215b1af7ad6950977d Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 20 Nov 2025 21:50:11 -0800 Subject: [PATCH 1301/2435] Fix stdatomic case in rbimpl_atomic_u64_fetch_add This was failing on crossruby, likely because HAVE_GCC_ATOMIC_BUILTINS was true, but HAVE_GCC_ATOMIC_BUILTINS_64 was false. We probably should have feature detection of 64-bit stdatomics like we do for GCC, but for now let's keep rbimpl_atomic_u64_fetch_add in sync with load/set. --- ruby_atomic.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ruby_atomic.h b/ruby_atomic.h index 9eaa5a9651f96a..c194f7ec3b82fc 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -43,6 +43,8 @@ rbimpl_atomic_u64_load_relaxed(const volatile rbimpl_atomic_uint64_t *value) uint64_t val = *value; return atomic_cas_64(value, val, val); #else + // TODO: stdatomic + return *value; #endif } @@ -58,6 +60,8 @@ rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_swap_64(address, value); #else + // TODO: stdatomic + *address = value; #endif } @@ -72,9 +76,9 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_add_64_nv(ptr, val) - val; -#elif defined(HAVE_STDATOMIC_H) - return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); #else + // TODO: stdatomic + // Fallback using mutex for platforms without 64-bit atomics static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; rb_native_mutex_lock(&lock); From 7ae0809c7c03b9d31a57fb18e9b0d173eead6f74 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 21 Nov 2025 10:00:39 -0500 Subject: [PATCH 1302/2435] Remove clang-18 from compilers CI clang-18 has a bug that causes ruby_current_ec to sometimes be null when using Ractors and crashes like this: :700: [BUG] Segmentation fault at 0x0000000000000030 ruby 4.0.0dev (2025-11-21T06:49:14Z master bcc7b2049c) +PRISM [x86_64-linux] -- Control frame information ----------------------------------------------- c:0004 p:0003 s:0015 e:000014 l:y b:0001 METHOD :700 me: called_id: receive, type: iseq owner class: 0x00007ff462dda500 T_CLASS/Ractor::Port self: 0x00007ff46146d068 ractor/port/Ractor::Port ractor/port c:0003 p:0008 s:0011 e:000010 l:y b:0001 METHOD :311 me: called_id: receive, type: iseq owner class: 0x00007ff462ddae60 T_CLASS/(anon) self: 0x00007ff462ddaf00 T_CLASS/Ractor c:0002 p:0010 s:0007 e:000006 l:n b:---- BLOCK bootstraptest.test_ractor.rb_2354_1323.rb:9 [FINISH] self: 0x00007ff46146d090 ractor/Ractor r:68 lvars: j: T_FIXNUM 66 c:0001 p:---- s:0003 e:000002 l:y b:---- DUMMY [FINISH] self: T_NIL -- Ruby level backtrace information ---------------------------------------- bootstraptest.test_ractor.rb_2354_1323.rb:9:in 'block (2 levels) in
' :311:in 'receive' :700:in 'receive' -- Threading information --------------------------------------------------- Total ractor count: 7 Ruby thread count for this ractor: 1 -- Machine register context ------------------------------------------------ RIP: 0x00007ff47c7df5f0 RBP: 0x000055d77ea5b4f0 RSP: 0x00007ff445fa3af0 RAX: 0x0000000000000000 RBX: 0x000055d77e9fd068 RCX: 0x000055d77e9fd040 RDX: 0x000055d77eb2ac40 RDI: 0x00007ff47cbe7700 RSI: 0x0000000000000000 R8: 0x0000000000000000 R9: 0x0000000000000000 R10: 0x000055d77e9fc830 R11: 0x93ba1054e59bfb14 R12: 0x000055d77ea5b4f0 R13: 0x00007ff445f82f20 R14: 0x00007ff4614cf668 R15: 0x000055d77e9fd040 EFL: 0x0000000000010246 -- C level backtrace information ------------------------------------------- libruby.so.4.0(rb_print_backtrace+0x14) [0x7ff47c8cbd18] vm_dump.c:1105 libruby.so.4.0(rb_vm_bugreport) vm_dump.c:1450 libruby.so.4.0(rb_bug_for_fatal_signal+0x162) [0x7ff47c70ce02] error.c:1131 libruby.so.4.0(sigsegv+0x4a) [0x7ff47c82f20a] signal.c:948 /lib/x86_64-linux-gnu/libc.so.6(0x7ff47c34a330) [0x7ff47c34a330] libruby.so.4.0(rb_ec_thread_ptr+0x0) [0x7ff47c7df5f0] vm_core.h:2092 libruby.so.4.0(rb_ec_ractor_ptr) vm_core.h:2041 libruby.so.4.0(rb_current_execution_context) vm_core.h:2110 libruby.so.4.0(rb_current_ractor_raw) vm_core.h:2109 libruby.so.4.0(rb_current_ractor) vm_core.h:2117 libruby.so.4.0(ractor_unlock) ractor.c:110 libruby.so.4.0(ractor_unlock_self) ractor.c:125 libruby.so.4.0(ractor_wait) ractor_sync.c:1054 libruby.so.4.0(ractor_wait_receive) ractor_sync.c:1113 libruby.so.4.0(ractor_receive+0x25) [0x7ff47c7ded08] ractor_sync.c:1166 libruby.so.4.0(ractor_port_receive) ractor_sync.c:143 libruby.so.4.0(builtin_inline_class_700) ractor.rb:701 libruby.so.4.0(invoke_bf+0x4) [0x7ff47c8a2060] vm_insnhelper.c:7534 libruby.so.4.0(vm_invoke_builtin_delegate) vm_insnhelper.c:0 libruby.so.4.0(vm_exec_core) insns.def:1674 libruby.so.4.0(vm_exec_loop+0x0) [0x7ff47c89b868] vm.c:2784 libruby.so.4.0(rb_vm_exec) vm.c:2787 libruby.so.4.0(vm_invoke_proc+0x344) [0x7ff47c8b03f4] vm.c:1814 libruby.so.4.0(thread_do_start_proc+0x17a) [0x7ff47c870bba] thread.c:593 libruby.so.4.0(thread_do_start+0x162) [0x7ff47c87042f] thread.c:635 libruby.so.4.0(thread_start_func_2) thread.c:686 libruby.so.4.0(rb_native_mutex_lock+0x0) [0x7ff47c870fd1] thread_pthread.c:2238 libruby.so.4.0(thread_sched_lock_) thread_pthread.c:403 libruby.so.4.0(call_thread_start_func_2) thread_pthread_mn.c:466 libruby.so.4.0(co_start) thread_pthread_mn.c:464 --- .github/workflows/compilers.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 8c73729a5c9bfe..ffddbe81e3b263 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -109,7 +109,8 @@ jobs: - { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' } } - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' } } - { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' } } - - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } } + # clang-18 has a bug causing ruby_current_ec to sometimes be null + # - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } } - { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' } } - { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' } } - { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' } } From bdca2a9975c7859f2e1702a517d59bb6cb254acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 18 Nov 2025 11:44:06 +0100 Subject: [PATCH 1303/2435] [ruby/json] Ractor-shareable JSON::Coder https://github.com/ruby/json/commit/58d60d6b76 --- ext/json/generator/generator.c | 14 ++++++++ ext/json/lib/json/common.rb | 2 +- ext/json/parser/parser.c | 8 ++++- test/json/json_generator_test.rb | 14 ++++++++ test/json/json_parser_test.rb | 7 ++++ test/json/ractor_test.rb | 59 ++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 2 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index fb8424dd86d736..2fb00ee2787539 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1630,6 +1630,7 @@ static VALUE string_config(VALUE config) */ static VALUE cState_indent_set(VALUE self, VALUE indent) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->indent, string_config(indent)); return Qnil; @@ -1655,6 +1656,7 @@ static VALUE cState_space(VALUE self) */ static VALUE cState_space_set(VALUE self, VALUE space) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space, string_config(space)); return Qnil; @@ -1678,6 +1680,7 @@ static VALUE cState_space_before(VALUE self) */ static VALUE cState_space_before_set(VALUE self, VALUE space_before) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space_before, string_config(space_before)); return Qnil; @@ -1703,6 +1706,7 @@ static VALUE cState_object_nl(VALUE self) */ static VALUE cState_object_nl_set(VALUE self, VALUE object_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl)); return Qnil; @@ -1726,6 +1730,7 @@ static VALUE cState_array_nl(VALUE self) */ static VALUE cState_array_nl_set(VALUE self, VALUE array_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl)); return Qnil; @@ -1749,6 +1754,7 @@ static VALUE cState_as_json(VALUE self) */ static VALUE cState_as_json_set(VALUE self, VALUE as_json) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc")); return Qnil; @@ -1791,6 +1797,7 @@ static long long_config(VALUE num) */ static VALUE cState_max_nesting_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); state->max_nesting = long_config(depth); return Qnil; @@ -1816,6 +1823,7 @@ static VALUE cState_script_safe(VALUE self) */ static VALUE cState_script_safe_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->script_safe = RTEST(enable); return Qnil; @@ -1847,6 +1855,7 @@ static VALUE cState_strict(VALUE self) */ static VALUE cState_strict_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->strict = RTEST(enable); return Qnil; @@ -1871,6 +1880,7 @@ static VALUE cState_allow_nan_p(VALUE self) */ static VALUE cState_allow_nan_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->allow_nan = RTEST(enable); return Qnil; @@ -1895,6 +1905,7 @@ static VALUE cState_ascii_only_p(VALUE self) */ static VALUE cState_ascii_only_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->ascii_only = RTEST(enable); return Qnil; @@ -1932,6 +1943,7 @@ static VALUE cState_depth(VALUE self) */ static VALUE cState_depth_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); state->depth = long_config(depth); return Qnil; @@ -1965,6 +1977,7 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_ */ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length) { + rb_check_frozen(self); GET_STATE(self); buffer_initial_length_set(state, buffer_initial_length); return Qnil; @@ -2031,6 +2044,7 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con static VALUE cState_configure(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_STATE(self); configure_state(state, self, opts); return self; diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index f22d911f552ec5..233b8c7e62d628 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -1048,7 +1048,7 @@ def initialize(options = nil, &as_json) options[:as_json] = as_json if as_json @state = State.new(options).freeze - @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)) + @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze end # call-seq: diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index a1d0265a919370..e23945d0e56fb3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1466,6 +1466,7 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts) */ static VALUE cParserConfig_initialize(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_PARSER_CONFIG; parser_config_init(config, opts); @@ -1553,6 +1554,11 @@ static size_t JSON_ParserConfig_memsize(const void *ptr) return sizeof(JSON_ParserConfig); } +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + static const rb_data_type_t JSON_ParserConfig_type = { "JSON::Ext::Parser/ParserConfig", { @@ -1561,7 +1567,7 @@ static const rb_data_type_t JSON_ParserConfig_type = { JSON_ParserConfig_memsize, }, 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, + RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; static VALUE cJSON_parser_s_allocate(VALUE klass) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 1b3c702c982f94..54a2ec61409eeb 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -901,4 +901,18 @@ def test_generate_duplicate_keys_disallowed end assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message end + + def test_frozen + state = JSON::State.new.freeze + assert_raise(FrozenError) do + state.configure(max_nesting: 1) + end + setters = state.methods.grep(/\w=$/) + assert_not_empty setters + setters.each do |setter| + assert_raise(FrozenError) do + state.send(setter, 1) + end + end + end end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 6315c3e667be85..544a8b564f670c 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -820,6 +820,13 @@ def test_parse_whitespace_after_newline assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]") end + def test_frozen + parser_config = JSON::Parser::Config.new({}).freeze + assert_raise FrozenError do + parser_config.send(:initialize, {}) + end + end + private def assert_equal_float(expected, actual, delta = 1e-2) diff --git a/test/json/ractor_test.rb b/test/json/ractor_test.rb index 53e1099ce9b76c..e53c405a74440f 100644 --- a/test/json/ractor_test.rb +++ b/test/json/ractor_test.rb @@ -52,4 +52,63 @@ def test_generate _, status = Process.waitpid2(pid) assert_predicate status, :success? end + + def test_coder + coder = JSON::Coder.new.freeze + assert Ractor.shareable?(coder) + pid = fork do + Warning[:experimental] = false + r = Ractor.new(coder) do |coder| + json = coder.dump({ + 'a' => 2, + 'b' => 3.141, + 'c' => 'c', + 'd' => [ 1, "b", 3.14 ], + 'e' => { 'foo' => 'bar' }, + 'g' => "\"\0\037", + 'h' => 1000.0, + 'i' => 0.001 + }) + coder.load(json) + end + expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' + + '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}') + actual_json = r.value + + if expected_json == actual_json + exit 0 + else + puts "Expected:" + puts expected_json + puts "Actual:" + puts actual_json + puts + exit 1 + end + end + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end + + class NonNative + def initialize(value) + @value = value + end + end + + def test_coder_proc + block = Ractor.shareable_proc { |value| value.as_json } + coder = JSON::Coder.new(&block).freeze + assert Ractor.shareable?(coder) + + pid = fork do + Warning[:experimental] = false + assert_equal [{}], Ractor.new(coder) { |coder| + coder.load('[{}]') + }.value + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end if Ractor.respond_to?(:shareable_proc) end if defined?(Ractor) && Process.respond_to?(:fork) From 419efd5ca5064e8f492660a898be36fe4f79b84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 21 Nov 2025 11:53:17 +0100 Subject: [PATCH 1304/2435] [ruby/json] Skip test failing with JRuby in CI https://github.com/ruby/json/commit/305d3832db --- test/json/json_parser_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 544a8b564f670c..a5b4763618c192 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -822,6 +822,7 @@ def test_parse_whitespace_after_newline def test_frozen parser_config = JSON::Parser::Config.new({}).freeze + omit "JRuby failure in CI" if RUBY_ENGINE == "jruby" assert_raise FrozenError do parser_config.send(:initialize, {}) end From ffa105c27f943bf4170247137733ff7640cf24d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 21 Nov 2025 12:48:21 +0100 Subject: [PATCH 1305/2435] [ruby/json] Move RUBY_TYPED_FROZEN_SHAREABLE macro to json.h https://github.com/ruby/json/commit/2a4ebe8250 --- ext/json/generator/generator.c | 5 ----- ext/json/json.h | 5 +++++ ext/json/parser/parser.c | 5 ----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 2fb00ee2787539..8d04bef53f1638 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -926,11 +926,6 @@ static size_t State_memsize(const void *ptr) return sizeof(JSON_Generator_State); } -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# undef RUBY_TYPED_FROZEN_SHAREABLE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 -#endif - static const rb_data_type_t JSON_Generator_State_type = { "JSON/Generator/State", { diff --git a/ext/json/json.h b/ext/json/json.h index 01fe0cd034e7e3..28efa04c257a94 100644 --- a/ext/json/json.h +++ b/ext/json/json.h @@ -45,6 +45,11 @@ typedef unsigned char _Bool; #endif #endif +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + #ifndef NORETURN #define NORETURN(x) x #endif diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index e23945d0e56fb3..2dcd1012b1270f 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -1554,11 +1554,6 @@ static size_t JSON_ParserConfig_memsize(const void *ptr) return sizeof(JSON_ParserConfig); } -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# undef RUBY_TYPED_FROZEN_SHAREABLE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 -#endif - static const rb_data_type_t JSON_ParserConfig_type = { "JSON::Ext::Parser/ParserConfig", { From d3b6f835d565ec1590059773fc87589ddf8adc37 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 22 Nov 2025 00:54:58 +0900 Subject: [PATCH 1306/2435] Fix stdatomic case in `rbimpl_atomic_u64_fetch_add` On some platoforms, 64bit atomic operations need the dedicated helper library. --- configure.ac | 1 + ruby_atomic.h | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 339ee3b2f2e66e..1e406ec56cff8a 100644 --- a/configure.ac +++ b/configure.ac @@ -1746,6 +1746,7 @@ AS_IF([test "$GCC" = yes], [ [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS) + AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include uint64_t atomic_var;]], diff --git a/ruby_atomic.h b/ruby_atomic.h index c194f7ec3b82fc..3a541d92082824 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -2,6 +2,9 @@ #define INTERNAL_ATOMIC_H #include "ruby/atomic.h" +#ifdef HAVE_STDATOMIC_H +# include +#endif #define RUBY_ATOMIC_VALUE_LOAD(x) rbimpl_atomic_value_load(&(x), RBIMPL_ATOMIC_SEQ_CST) @@ -76,9 +79,9 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_add_64_nv(ptr, val) - val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); #else - // TODO: stdatomic - // Fallback using mutex for platforms without 64-bit atomics static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; rb_native_mutex_lock(&lock); From e5e8ac51496d8240f2c7a65aa9a9f300454d41b6 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 08:48:18 -0800 Subject: [PATCH 1307/2435] ZJIT: Inline String#empty? (#15283) Don't emit a CCall. --- zjit/src/cruby_methods.rs | 16 +++++++++++++++- zjit/src/hir.rs | 2 ++ zjit/src/hir/opt_tests.rs | 7 +++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 7a4a11a8e18bfd..190ac52eaceccb 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -200,7 +200,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "setbyte", inline_string_setbyte); - annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cString, "empty?", inline_string_empty_p, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cString, "==", inline_string_eq); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); @@ -376,6 +376,20 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir } } +fn inline_string_empty_p(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[] = args else { return None; }; + let len = fun.push_insn(block, hir::Insn::LoadField { + recv, + id: ID!(len), + offset: RUBY_OFFSET_RSTRING_LEN as i32, + return_type: types::CInt64, + }); + let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); + let is_zero = fun.push_insn(block, hir::Insn::IsBitEqual { left: len, right: zero }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: is_zero }); + Some(result) +} + fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[other] = args else { return None; }; // Inline only StringExact << String, which matches original type check from diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 961fe1c1428825..3866da52b995ed 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -992,6 +992,8 @@ impl Insn { Insn::StringGetbyteFixnum { .. } => false, Insn::IsBlockGiven => false, Insn::BoxFixnum { .. } => false, + Insn::BoxBool { .. } => false, + Insn::IsBitEqual { .. } => false, _ => true, } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 70efa000761acf..083820c0da3450 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6263,10 +6263,13 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact + v24:CInt64 = LoadField v23, :len@0x1038 + v25:CInt64[0] = Const CInt64(0) + v26:CBool = IsBitEqual v24, v25 + v27:BoolExact = BoxBool v26 IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall String#empty?@0x1038, v23 CheckInterrupts - Return v25 + Return v27 "); } From f52edf172db0afa4b3867723f75d617291070d63 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 08:48:36 -0800 Subject: [PATCH 1308/2435] ZJIT: Specialize monomorphic DefinedIvar (#15281) This lets us constant-fold common monomorphic cases. --- insns.def | 1 + zjit/src/cruby_bindings.inc.rs | 57 +++++++++--------- zjit/src/hir.rs | 34 +++++++++++ zjit/src/hir/opt_tests.rs | 107 +++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 1 + 5 files changed, 172 insertions(+), 28 deletions(-) diff --git a/insns.def b/insns.def index f282b5a8e7084c..3d13f4cb646d39 100644 --- a/insns.def +++ b/insns.def @@ -745,6 +745,7 @@ definedivar () (VALUE val) // attr bool leaf = false; +// attr bool zjit_profile = true; { val = Qnil; if (!UNDEF_P(vm_getivar(GET_SELF(), id, GET_ISEQ(), ic, NULL, FALSE, Qundef))) { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 66126b627c0fd3..fe2055d4cc0486 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1744,34 +1744,35 @@ pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 215; pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 216; pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 217; pub const YARVINSN_zjit_getinstancevariable: ruby_vminsn_type = 218; -pub const YARVINSN_zjit_send: ruby_vminsn_type = 219; -pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 220; -pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 221; -pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 222; -pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 223; -pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 224; -pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 225; -pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 226; -pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 227; -pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 228; -pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 229; -pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 230; -pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 231; -pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 232; -pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 233; -pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 236; -pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 237; -pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 238; -pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 239; -pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 240; -pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 241; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 242; -pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 243; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 244; -pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 245; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 246; +pub const YARVINSN_zjit_definedivar: ruby_vminsn_type = 219; +pub const YARVINSN_zjit_send: ruby_vminsn_type = 220; +pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 221; +pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 222; +pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 223; +pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 224; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 225; +pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 226; +pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 227; +pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 228; +pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 229; +pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 230; +pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 231; +pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 232; +pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 244; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 245; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 246; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 247; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3866da52b995ed..40c6092e56c471 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2820,6 +2820,38 @@ impl Function { }; self.make_equal_to(insn_id, replacement); } + Insn::DefinedIvar { self_val, id, pushval, state } => { + let frame_state = self.frame_state(state); + let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { + // No (monomorphic/skewed polymorphic) profile info + self.push_insn_id(block, insn_id); continue; + }; + if recv_type.flags().is_immediate() { + // Instance variable lookups on immediate values are always nil + self.push_insn_id(block, insn_id); continue; + } + assert!(recv_type.shape().is_valid()); + if !recv_type.flags().is_t_object() { + // Check if the receiver is a T_OBJECT + self.push_insn_id(block, insn_id); continue; + } + if recv_type.shape().is_too_complex() { + // too-complex shapes can't use index access + self.push_insn_id(block, insn_id); continue; + } + let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); + let _ = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); + let mut ivar_index: u16 = 0; + let replacement = if unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { + self.push_insn(block, Insn::Const { val: Const::Value(pushval) }) + } else { + // If there is no IVAR index, then the ivar was undefined when we + // entered the compiler. That means we can just return nil for this + // shape + iv name + self.push_insn(block, Insn::Const { val: Const::Value(Qnil) }) + }; + self.make_equal_to(insn_id, replacement); + } _ => { self.push_insn_id(block, insn_id); } } } @@ -4839,6 +4871,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // profiled cfp->self. if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable { profiles.profile_self(&exit_state, self_param); + } else if opcode == YARVINSN_definedivar || opcode == YARVINSN_trace_definedivar { + profiles.profile_self(&exit_state, self_param); } else if opcode == YARVINSN_invokeblock || opcode == YARVINSN_trace_invokeblock { if get_option!(stats) { let iseq_insn_idx = exit_state.insn_idx; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 083820c0da3450..b1ab8a06050d82 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3373,6 +3373,113 @@ mod hir_opt_tests { "); } + #[test] + fn test_specialize_monomorphic_definedivar_true() { + eval(" + @foo = 4 + def test = defined?(@foo) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v15:HeapBasicObject = GuardType v6, HeapBasicObject + v16:HeapBasicObject = GuardShape v15, 0x1000 + v17:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_monomorphic_definedivar_false() { + eval(" + def test = defined?(@foo) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v15:HeapBasicObject = GuardType v6, HeapBasicObject + v16:HeapBasicObject = GuardShape v15, 0x1000 + v17:NilClass = Const Value(nil) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_dont_specialize_definedivar_with_t_data() { + eval(" + class C < Range + def test = defined?(@a) + end + obj = C.new 0, 1 + obj.instance_variable_set(:@a, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact|NilClass = DefinedIvar v6, :@a + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_dont_specialize_polymorphic_definedivar() { + set_call_threshold(3); + eval(" + class C + def test = defined?(@a) + end + obj = C.new + obj.instance_variable_set(:@a, 1) + obj.test + obj = C.new + obj.instance_variable_set(:@b, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact|NilClass = DefinedIvar v6, :@a + CheckInterrupts + Return v10 + "); + } + #[test] fn test_elide_freeze_with_frozen_hash() { eval(" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 8c8190609d7fae..10afdf2cc6475c 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -82,6 +82,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_aset => profile_operands(profiler, profile, 3), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), + YARVINSN_definedivar => profile_self(profiler, profile), YARVINSN_opt_regexpmatch2 => profile_operands(profiler, profile, 2), YARVINSN_objtostring => profile_operands(profiler, profile, 1), YARVINSN_opt_length => profile_operands(profiler, profile, 1), From 6cebbf4037376f28d9792cecf38d4f770bcdcaac Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 20 Nov 2025 10:43:08 -0500 Subject: [PATCH 1309/2435] ZJIT: Split CSel memory reads on x86_64 Fix https://github.com/Shopify/ruby/issues/876 --- zjit/src/backend/x86_64/mod.rs | 44 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 11876eb89437a6..d17286f51fda25 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -542,14 +542,23 @@ impl Assembler { *opnds = vec![]; asm.push_insn(insn); } - Insn::CSelZ { out, .. } | - Insn::CSelNZ { out, .. } | - Insn::CSelE { out, .. } | - Insn::CSelNE { out, .. } | - Insn::CSelL { out, .. } | - Insn::CSelLE { out, .. } | - Insn::CSelG { out, .. } | - Insn::CSelGE { out, .. } | + Insn::CSelZ { truthy: left, falsy: right, out } | + Insn::CSelNZ { truthy: left, falsy: right, out } | + Insn::CSelE { truthy: left, falsy: right, out } | + Insn::CSelNE { truthy: left, falsy: right, out } | + Insn::CSelL { truthy: left, falsy: right, out } | + Insn::CSelLE { truthy: left, falsy: right, out } | + Insn::CSelG { truthy: left, falsy: right, out } | + Insn::CSelGE { truthy: left, falsy: right, out } => { + *left = split_stack_membase(asm, *left, SCRATCH1_OPND, &stack_state); + *right = split_stack_membase(asm, *right, SCRATCH0_OPND, &stack_state); + *right = split_if_both_memory(asm, *right, *left, SCRATCH0_OPND); + let mem_out = split_memory_write(out, SCRATCH0_OPND); + asm.push_insn(insn); + if let Some(mem_out) = mem_out { + asm.store(mem_out, SCRATCH0_OPND); + } + } Insn::Lea { out, .. } => { let mem_out = split_memory_write(out, SCRATCH0_OPND); asm.push_insn(insn); @@ -1776,4 +1785,23 @@ mod tests { "); assert_snapshot!(cb.hexdump(), @"49bb00100000000000004c891b"); } + + #[test] + fn test_csel_split_memory_read() { + let (mut asm, mut cb) = setup_asm(); + + let left = Opnd::Mem(Mem { base: MemBase::Stack { stack_idx: 0, num_bits: 64 }, disp: 0, num_bits: 64 }); + let right = Opnd::Mem(Mem { base: MemBase::Stack { stack_idx: 1, num_bits: 64 }, disp: 2, num_bits: 64 }); + let _ = asm.csel_e(left, right); + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @" + 0x0: mov r10, qword ptr [rbp - 8] + 0x4: mov r11, qword ptr [rbp - 0x10] + 0x8: mov r11, qword ptr [r11 + 2] + 0xc: cmove r11, qword ptr [r10] + 0x10: mov qword ptr [rbp - 8], r11 + "); + assert_snapshot!(cb.hexdump(), @"4c8b55f84c8b5df04d8b5b024d0f441a4c895df8"); + } } From 8090988f878c71c2aaefbb3123ac13e3753c93da Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 19 Nov 2025 20:53:41 -0500 Subject: [PATCH 1310/2435] ZJIT: Inline ArrayLength into LIR --- zjit/src/codegen.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d00ab500d72796..06f991c738e869 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1426,7 +1426,14 @@ fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::O } fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { - asm_ccall!(asm, rb_jit_array_len, array) + let array = asm.load(array); + let flags = Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RBASIC_FLAGS); + let embedded_len = asm.and(flags, (RARRAY_EMBED_LEN_MASK as u64).into()); + let embedded_len = asm.rshift(embedded_len, (RARRAY_EMBED_LEN_SHIFT as u64).into()); + // cmov between the embedded length and heap length depending on the embed flag + asm.test(flags, (RARRAY_EMBED_FLAG as u64).into()); + let heap_len = Opnd::mem(c_long::BITS as u8, array, RUBY_OFFSET_RARRAY_AS_HEAP_LEN); + asm.csel_nz(embedded_len, heap_len) } /// Compile opt_newarray_hash - create a hash from array elements From 8728406c418f1a200cda02a259ba164d185a8ebd Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 08:49:57 -0800 Subject: [PATCH 1311/2435] ZJIT: Inline Thread.current (#15272) Add `LoadEC` then it's just two `LoadField`. --- zjit/src/codegen.rs | 5 +++++ zjit/src/cruby.rs | 2 ++ zjit/src/cruby_methods.rs | 24 +++++++++++++++++++++++- zjit/src/hir.rs | 8 ++++++++ zjit/src/hir/opt_tests.rs | 6 ++++-- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 06f991c738e869..b95d1372221bbd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -457,6 +457,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) }, &Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)), Insn::LoadPC => gen_load_pc(asm), + Insn::LoadEC => gen_load_ec(), Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val))), @@ -1041,6 +1042,10 @@ fn gen_load_pc(asm: &mut Assembler) -> Opnd { asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC)) } +fn gen_load_ec() -> Opnd { + EC +} + fn gen_load_self() -> Opnd { Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF) } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 443ed0d86e3a99..83919e5369e5d9 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1377,6 +1377,8 @@ pub(crate) mod ids { name: aref content: b"[]" name: len name: _as_heap + name: thread_ptr + name: self_ content: b"self" } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 190ac52eaceccb..d1f76e8da05dde 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -158,6 +158,7 @@ pub fn init() -> Annotations { ($module:ident, $method_name:literal, $inline:ident) => { let mut props = FnProperties::default(); props.inline = $inline; + #[allow(unused_unsafe)] annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { @@ -167,6 +168,7 @@ pub fn init() -> Annotations { $( props.$properties = true; )* + #[allow(unused_unsafe)] annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { @@ -240,7 +242,7 @@ pub fn init() -> Annotations { annotate!(rb_cInteger, "<=", inline_integer_le); annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; - annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); + annotate!(thread_singleton, "current", inline_thread_current, types::BasicObject, no_gc, leaf); annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); @@ -269,6 +271,26 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I None } +fn inline_thread_current(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[] = args else { return None; }; + let ec = fun.push_insn(block, hir::Insn::LoadEC); + let thread_ptr = fun.push_insn(block, hir::Insn::LoadField { + recv: ec, + id: ID!(thread_ptr), + offset: RUBY_OFFSET_EC_THREAD_PTR as i32, + return_type: types::CPtr, + }); + let thread_self = fun.push_insn(block, hir::Insn::LoadField { + recv: thread_ptr, + id: ID!(self_), + offset: RUBY_OFFSET_THREAD_SELF as i32, + // TODO(max): Add Thread type. But Thread.current is not guaranteed to be an exact Thread. + // You can make subclasses... + return_type: types::BasicObject, + }); + Some(thread_self) +} + fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if args.is_empty() { // No need to coerce the receiver; that is done by the SendWithoutBlock rewriting. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 40c6092e56c471..333d5e5bff1837 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -736,6 +736,8 @@ pub enum Insn { /// Load cfp->pc LoadPC, + /// Load EC + LoadEC, /// Load cfp->self LoadSelf, LoadField { recv: InsnId, id: ID, offset: i32, return_type: Type }, @@ -980,6 +982,7 @@ impl Insn { Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::LoadPC => false, + Insn::LoadEC => false, Insn::LoadSelf => false, Insn::LoadField { .. } => false, Insn::CCall { elidable, .. } => !elidable, @@ -1272,6 +1275,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::DefinedIvar { self_val, id, .. } => write!(f, "DefinedIvar {self_val}, :{}", id.contents_lossy()), Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy()), Insn::LoadPC => write!(f, "LoadPC"), + Insn::LoadEC => write!(f, "LoadEC"), Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:p}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), @@ -1775,6 +1779,7 @@ impl Function { | SideExit {..} | EntryPoint {..} | LoadPC + | LoadEC | LoadSelf | IncrCounterPtr {..} | IncrCounter(_)) => result.clone(), @@ -2069,6 +2074,7 @@ impl Function { Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, + Insn::LoadEC => types::CPtr, Insn::LoadSelf => types::BasicObject, &Insn::LoadField { return_type, .. } => return_type, Insn::GetSpecialSymbol { .. } => types::BasicObject, @@ -3397,6 +3403,7 @@ impl Function { | &Insn::Param | &Insn::EntryPoint { .. } | &Insn::LoadPC + | &Insn::LoadEC | &Insn::LoadSelf | &Insn::GetLocal { .. } | &Insn::PutSpecialObject { .. } @@ -4101,6 +4108,7 @@ impl Function { | Insn::IsBlockGiven | Insn::GetGlobal { .. } | Insn::LoadPC + | Insn::LoadEC | Insn::LoadSelf | Insn::Snapshot { .. } | Insn::Jump { .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index b1ab8a06050d82..82f54f611a884c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5965,10 +5965,12 @@ mod hir_opt_tests { v20:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) + v24:CPtr = LoadEC + v25:CPtr = LoadField v24, :thread_ptr@0x1048 + v26:BasicObject = LoadField v25, :self@0x1049 IncrCounter inline_cfunc_optimized_send_count - v25:BasicObject = CCall Thread.current@0x1048, v20 CheckInterrupts - Return v25 + Return v26 "); } From e0bb3fb1cda2238d0c98afcdec2fe282c29994aa Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 08:57:26 -0800 Subject: [PATCH 1312/2435] ZJIT: Inline Integer#<< for constant rhs (#15258) This is good for protoboeuf and other binary parsing --- zjit/src/asm/arm64/mod.rs | 2 +- zjit/src/codegen.rs | 20 +++++++ zjit/src/cruby_methods.rs | 10 ++++ zjit/src/hir.rs | 10 +++- zjit/src/hir/opt_tests.rs | 109 ++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 2 + 6 files changed, 151 insertions(+), 2 deletions(-) diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index a4459117312f89..4094d101fbc32c 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -649,7 +649,7 @@ pub fn lsl(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, shift: A64Opnd) { ShiftImm::lsl(rd.reg_no, rn.reg_no, uimm as u8, rd.num_bits).into() }, - _ => panic!("Invalid operands combination to lsl instruction") + _ => panic!("Invalid operands combination {rd:?} {rn:?} {shift:?} to lsl instruction") }; cb.write_bytes(&bytes); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b95d1372221bbd..9f74838c11fe87 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -402,6 +402,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumAnd { left, right } => gen_fixnum_and(asm, opnd!(left), opnd!(right)), Insn::FixnumOr { left, right } => gen_fixnum_or(asm, opnd!(left), opnd!(right)), Insn::FixnumXor { left, right } => gen_fixnum_xor(asm, opnd!(left), opnd!(right)), + &Insn::FixnumLShift { left, right, state } => { + // We only create FixnumLShift when we know the shift amount statically and it's in [0, + // 63]. + let shift_amount = function.type_of(right).fixnum_value().unwrap() as u64; + gen_fixnum_lshift(jit, asm, opnd!(left), shift_amount, &function.frame_state(state)) + } &Insn::FixnumMod { left, right, state } => gen_fixnum_mod(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), @@ -1700,6 +1706,20 @@ fn gen_fixnum_xor(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir asm.add(out_val, Opnd::UImm(1)) } +/// Compile Fixnum << Fixnum +fn gen_fixnum_lshift(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, shift_amount: u64, state: &FrameState) -> lir::Opnd { + // Shift amount is known statically to be in the range [0, 63] + assert!(shift_amount < 64); + let in_val = asm.sub(left, Opnd::UImm(1)); // Drop tag bit + let out_val = asm.lshift(in_val, shift_amount.into()); + let unshifted = asm.rshift(out_val, shift_amount.into()); + asm.cmp(in_val, unshifted); + asm.jne(side_exit(jit, state, FixnumLShiftOverflow)); + // Re-tag the output value + let out_val = asm.add(out_val, 1.into()); + out_val +} + fn gen_fixnum_mod(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { // Check for left % 0, which raises ZeroDivisionError asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index d1f76e8da05dde..3999ef0a10f1d1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -240,6 +240,7 @@ pub fn init() -> Annotations { annotate!(rb_cInteger, ">=", inline_integer_ge); annotate!(rb_cInteger, "<", inline_integer_lt); annotate!(rb_cInteger, "<=", inline_integer_le); + annotate!(rb_cInteger, "<<", inline_integer_lshift); annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", inline_thread_current, types::BasicObject, no_gc, leaf); @@ -546,6 +547,15 @@ fn inline_integer_le(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumLe { left, right }, BOP_LE, recv, other, state) } +fn inline_integer_lshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + // Only convert to FixnumLShift if we know the shift amount is known at compile-time and could + // plausibly create a fixnum. + let Some(other_value) = fun.type_of(other).fixnum_value() else { return None; }; + if other_value < 0 || other_value > 63 { return None; } + try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumLShift { left, right, state }, BOP_LTLT, recv, other, state) +} + fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[other] = args else { return None; }; let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 333d5e5bff1837..00014c575897e3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -485,6 +485,7 @@ pub enum SideExitReason { FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, + FixnumLShiftOverflow, GuardType(Type), GuardTypeNot(Type), GuardShape(ShapeId), @@ -868,7 +869,7 @@ pub enum Insn { /// Non-local control flow. See the throw YARV instruction Throw { throw_state: u32, val: InsnId, state: InsnId }, - /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^ + /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, << FixnumAdd { left: InsnId, right: InsnId, state: InsnId }, FixnumSub { left: InsnId, right: InsnId, state: InsnId }, FixnumMult { left: InsnId, right: InsnId, state: InsnId }, @@ -883,6 +884,7 @@ pub enum Insn { FixnumAnd { left: InsnId, right: InsnId }, FixnumOr { left: InsnId, right: InsnId }, FixnumXor { left: InsnId, right: InsnId }, + FixnumLShift { left: InsnId, right: InsnId, state: InsnId }, // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined ObjToString { val: InsnId, cd: *const rb_call_data, state: InsnId }, @@ -979,6 +981,7 @@ impl Insn { Insn::FixnumAnd { .. } => false, Insn::FixnumOr { .. } => false, Insn::FixnumXor { .. } => false, + Insn::FixnumLShift { .. } => false, Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::LoadPC => false, @@ -1218,6 +1221,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumAnd { left, right, .. } => { write!(f, "FixnumAnd {left}, {right}") }, Insn::FixnumOr { left, right, .. } => { write!(f, "FixnumOr {left}, {right}") }, Insn::FixnumXor { left, right, .. } => { write!(f, "FixnumXor {left}, {right}") }, + Insn::FixnumLShift { left, right, .. } => { write!(f, "FixnumLShift {left}, {right}") }, Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardTypeNot { val, guard_type, .. } => { write!(f, "GuardTypeNot {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, @@ -1836,6 +1840,7 @@ impl Function { &FixnumAnd { left, right } => FixnumAnd { left: find!(left), right: find!(right) }, &FixnumOr { left, right } => FixnumOr { left: find!(left), right: find!(right) }, &FixnumXor { left, right } => FixnumXor { left: find!(left), right: find!(right) }, + &FixnumLShift { left, right, state } => FixnumLShift { left: find!(left), right: find!(right), state }, &ObjToString { val, cd, state } => ObjToString { val: find!(val), cd, @@ -2054,6 +2059,7 @@ impl Function { Insn::FixnumAnd { .. } => types::Fixnum, Insn::FixnumOr { .. } => types::Fixnum, Insn::FixnumXor { .. } => types::Fixnum, + Insn::FixnumLShift { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, Insn::SendWithoutBlock { .. } => types::BasicObject, Insn::SendWithoutBlockDirect { .. } => types::BasicObject, @@ -3506,6 +3512,7 @@ impl Function { | &Insn::FixnumDiv { left, right, state } | &Insn::FixnumMod { left, right, state } | &Insn::ArrayExtend { left, right, state } + | &Insn::FixnumLShift { left, right, state } => { worklist.push_back(left); worklist.push_back(right); @@ -4271,6 +4278,7 @@ impl Function { | Insn::FixnumAnd { left, right } | Insn::FixnumOr { left, right } | Insn::FixnumXor { left, right } + | Insn::FixnumLShift { left, right, .. } | Insn::NewRangeFixnum { low: left, high: right, .. } => { self.assert_subtype(insn_id, left, types::Fixnum)?; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 82f54f611a884c..19c0ce66e36235 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6466,6 +6466,115 @@ mod hir_opt_tests { "); } + #[test] + fn test_inline_integer_ltlt_with_known_fixnum() { + eval(" + def test(x) = x << 5 + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_ltlt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumLShift v24, v14 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_inline_integer_ltlt_with_negative() { + eval(" + def test(x) = x << -5 + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_ltlt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[-5] = Const Value(-5) + PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:BasicObject = CCallWithFrame Integer#<<@0x1038, v24, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_inline_integer_ltlt_with_out_of_range() { + eval(" + def test(x) = x << 64 + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_ltlt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[64] = Const Value(64) + PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:BasicObject = CCallWithFrame Integer#<<@0x1038, v24, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_inline_integer_ltlt_with_unknown_fixnum() { + eval(" + def test(x, y) = x << y + test(4, 5) + "); + assert_contains_opcode("test", YARVINSN_opt_ltlt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) + v26:Fixnum = GuardType v11, Fixnum + v27:BasicObject = CCallWithFrame Integer#<<@0x1038, v26, v12 + CheckInterrupts + Return v27 + "); + } + #[test] fn test_optimize_string_append() { eval(r#" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index df172997ce9793..1277db5b7e9e7d 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -142,6 +142,7 @@ make_counters! { exit_fixnum_add_overflow, exit_fixnum_sub_overflow, exit_fixnum_mult_overflow, + exit_fixnum_lshift_overflow, exit_fixnum_mod_by_zero, exit_box_fixnum_overflow, exit_guard_type_failure, @@ -423,6 +424,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { FixnumAddOverflow => exit_fixnum_add_overflow, FixnumSubOverflow => exit_fixnum_sub_overflow, FixnumMultOverflow => exit_fixnum_mult_overflow, + FixnumLShiftOverflow => exit_fixnum_lshift_overflow, FixnumModByZero => exit_fixnum_mod_by_zero, BoxFixnumOverflow => exit_box_fixnum_overflow, GuardType(_) => exit_guard_type_failure, From ff89e470e21e9d021c6739d83eddda4bd8c071fe Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 19 Nov 2025 16:37:45 -0500 Subject: [PATCH 1313/2435] ZJIT: Specialize Module#=== and Kernel#is_a? into IsA --- zjit/src/codegen.rs | 5 ++ zjit/src/cruby_methods.rs | 21 ++++- zjit/src/hir.rs | 15 ++++ zjit/src/hir/opt_tests.rs | 185 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 9f74838c11fe87..4c865dcd8a1b34 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -472,6 +472,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), + &Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)), &Insn::ArrayMax { state, .. } | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } @@ -1520,6 +1521,10 @@ fn gen_dup_array_include( ) } +fn gen_is_a(asm: &mut Assembler, obj: Opnd, class: Opnd) -> lir::Opnd { + asm_ccall!(asm, rb_obj_is_kind_of, obj, class) +} + /// Compile a new hash instruction fn gen_new_hash( jit: &mut JITState, diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 3999ef0a10f1d1..6d09b5e5a7995f 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -197,6 +197,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); annotate!(rb_mKernel, "===", inline_eqq); + annotate!(rb_mKernel, "is_a?", inline_kernel_is_a_p); annotate!(rb_cString, "bytesize", inline_string_bytesize); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); @@ -206,7 +207,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cString, "==", inline_string_eq); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); - annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); + annotate!(rb_cModule, "===", inline_module_eqq, types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); @@ -447,6 +448,15 @@ fn inline_string_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins None } +fn inline_module_eqq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + if fun.is_a(recv, types::Class) { + let result = fun.push_insn(block, hir::Insn::IsA { val: other, class: recv }); + return Some(result); + } + None +} + fn inline_integer_succ(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if !args.is_empty() { return None; } if fun.likely_a(recv, types::Fixnum, state) { @@ -613,6 +623,15 @@ fn inline_eqq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, a Some(result) } +fn inline_kernel_is_a_p(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + if fun.is_a(other, types::Class) { + let result = fun.push_insn(block, hir::Insn::IsA { val: recv, class: other }); + return Some(result); + } + None +} + fn inline_kernel_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 00014c575897e3..3a69dd6610e514 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -721,6 +721,9 @@ pub enum Insn { /// Test the bit at index of val, a Fixnum. /// Return Qtrue if the bit is set, else Qfalse. FixnumBitCheck { val: InsnId, index: u8 }, + /// Return Qtrue if `val` is an instance of `class`, else Qfalse. + /// Equivalent to `class_search_ancestor(CLASS_OF(val), class)`. + IsA { val: InsnId, class: InsnId }, /// Get a global variable named `id` GetGlobal { id: ID, state: InsnId }, @@ -1000,6 +1003,7 @@ impl Insn { Insn::BoxFixnum { .. } => false, Insn::BoxBool { .. } => false, Insn::IsBitEqual { .. } => false, + Insn::IsA { .. } => false, _ => true, } } @@ -1324,6 +1328,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Insn::IncrCounter(counter) => write!(f, "IncrCounter {counter:?}"), Insn::CheckInterrupts { .. } => write!(f, "CheckInterrupts"), + Insn::IsA { val, class } => write!(f, "IsA {val}, {class}"), } } } @@ -1946,6 +1951,7 @@ impl Function { &ArrayExtend { left, right, state } => ArrayExtend { left: find!(left), right: find!(right), state }, &ArrayPush { array, val, state } => ArrayPush { array: find!(array), val: find!(val), state }, &CheckInterrupts { state } => CheckInterrupts { state }, + &IsA { val, class } => IsA { val: find!(val), class: find!(class) }, } } @@ -2095,6 +2101,7 @@ impl Function { // The type of Snapshot doesn't really matter; it's never materialized. It's used only // as a reference for FrameState, which we use to generate side-exit code. Insn::Snapshot { .. } => types::Any, + Insn::IsA { .. } => types::BoolExact, } } @@ -3622,6 +3629,10 @@ impl Function { &Insn::ObjectAllocClass { state, .. } | &Insn::SideExit { state, .. } => worklist.push_back(state), &Insn::UnboxFixnum { val } => worklist.push_back(val), + &Insn::IsA { val, class } => { + worklist.push_back(val); + worklist.push_back(class); + } } } @@ -4314,6 +4325,10 @@ impl Function { self.assert_subtype(insn_id, index, types::Fixnum)?; self.assert_subtype(insn_id, value, types::Fixnum) } + Insn::IsA { val, class } => { + self.assert_subtype(insn_id, val, types::BasicObject)?; + self.assert_subtype(insn_id, class, types::Class) + } } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 19c0ce66e36235..9704afcf6e770c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -7983,6 +7983,191 @@ mod hir_opt_tests { "); } + #[test] + fn test_specialize_class_eqq() { + eval(r#" + def test(o) = String === o + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v26:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint NoEPEscape(test) + PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + v30:BoolExact = IsA v9, v26 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_dont_specialize_module_eqq() { + eval(r#" + def test(o) = Kernel === o + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Kernel) + v26:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint NoEPEscape(test) + PatchPoint MethodRedefined(Module@0x1010, ===@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Module@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v31:BoolExact = CCall Module#===@0x1048, v26, v9 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_specialize_is_a_class() { + eval(r#" + def test(o) = o.is_a?(String) + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v24:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1008, is_a?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v28:StringExact = GuardType v9, StringExact + v29:BoolExact = IsA v28, v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_dont_specialize_is_a_module() { + eval(r#" + def test(o) = o.is_a?(Kernel) + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Kernel) + v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v28:StringExact = GuardType v9, StringExact + v29:BasicObject = CCallWithFrame Kernel#is_a?@0x1048, v28, v24 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_elide_is_a() { + eval(r#" + def test(o) + o.is_a?(Integer) + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Integer) + v28:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v32:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_elide_class_eqq() { + eval(r#" + def test(o) + Integer === o + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Integer) + v30:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint NoEPEscape(test) + PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v23:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v23 + "); + } + #[test] fn counting_complex_feature_use_for_fallback() { eval(" From 14e34fa7c0f02402b322bc9bbdd34fec446b70e9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 20 Nov 2025 17:56:17 -0500 Subject: [PATCH 1314/2435] ZJIT: Print class objects more nicely in HIR --- zjit/src/hir/opt_tests.rs | 86 +++++++++++++++++++-------------------- zjit/src/hir_type/mod.rs | 2 + 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 9704afcf6e770c..70ba333b1be5d5 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2299,7 +2299,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v18:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:Class[C@0x1008] = Const Value(VALUE(0x1008)) CheckInterrupts Return v18 "); @@ -2323,16 +2323,16 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v29:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v29:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, Class) - v32:Class[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v32:Class[Class@0x1018] = Const Value(VALUE(0x1018)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1020, Module) - v35:Class[VALUE(0x1028)] = Const Value(VALUE(0x1028)) + v35:Class[Module@0x1028] = Const Value(VALUE(0x1028)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1030, BasicObject) - v38:Class[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + v38:Class[BasicObject@0x1038] = Const Value(VALUE(0x1038)) v22:ArrayExact = NewArray v29, v32, v35, v38 CheckInterrupts Return v22 @@ -2954,7 +2954,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v18:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:Class[Foo::Bar::C@0x1008] = Const Value(VALUE(0x1008)) CheckInterrupts Return v18 "); @@ -2979,11 +2979,11 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[C@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) v46:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(C@0x1008) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -3016,14 +3016,14 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v46:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v46:Class[C@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) v16:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) v49:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(C@0x1008) - v52:BasicObject = SendWithoutBlockDirect v49, :initialize (0x1070), v16 + v52:BasicObject = SendWithoutBlockDirect v49, :initialize (0x1068), v16 CheckInterrupts CheckInterrupts Return v49 @@ -3048,11 +3048,11 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Object) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[Object@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Object@0x1008, new@0x1009, cme:0x1010) v46:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) - PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint MethodRedefined(Object@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(Object@0x1008) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -3080,11 +3080,11 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, BasicObject) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[BasicObject@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1009, cme:0x1010) v46:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) - PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(BasicObject@0x1008) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -3112,9 +3112,9 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Hash) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[Hash@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010) v46:HashExact = ObjectAllocClass Hash:VALUE(0x1008) IncrCounter complex_arg_pass_param_kw IncrCounter complex_arg_pass_param_block @@ -3144,13 +3144,13 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Array) - v46:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v46:Class[Array@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) v16:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v57:BasicObject = CCallVariadic Array.new@0x1048, v46, v16 + PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010) + PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) + PatchPoint NoSingletonClass(Class@0x1038) + v57:BasicObject = CCallVariadic Array.new@0x1040, v46, v16 CheckInterrupts Return v57 "); @@ -3174,14 +3174,14 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Set) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[Set@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Set@0x1008, new@0x1009, cme:0x1010) v18:HeapBasicObject = ObjectAlloc v43 - PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint MethodRedefined(Set@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(Set@0x1008) v49:SetExact = GuardType v18, SetExact - v50:BasicObject = CCallVariadic Set#initialize@0x1070, v49 + v50:BasicObject = CCallVariadic Set#initialize@0x1068, v49 CheckInterrupts CheckInterrupts Return v18 @@ -3206,12 +3206,12 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v43:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v43:Class[String@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) - PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v54:BasicObject = CCallVariadic String.new@0x1048, v43 + PatchPoint MethodRedefined(String@0x1008, new@0x1009, cme:0x1010) + PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) + PatchPoint NoSingletonClass(Class@0x1038) + v54:BasicObject = CCallVariadic String.new@0x1040, v43 CheckInterrupts Return v54 "); @@ -3235,7 +3235,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Regexp) - v47:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v47:Class[Regexp@0x1008] = Const Value(VALUE(0x1008)) v13:NilClass = Const Value(nil) v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) v17:StringExact = StringCopy v16 @@ -4406,7 +4406,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo) - v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v22:Class[Foo@0x1008] = Const Value(VALUE(0x1008)) v13:Fixnum[100] = Const Value(100) PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) @@ -5962,7 +5962,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Thread) - v20:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:Class[Thread@0x1008] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) v24:CPtr = LoadEC @@ -8002,7 +8002,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v26:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v26:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) @@ -8062,8 +8062,8 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v24:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(String@0x1008, is_a?@0x1010, cme:0x1018) + v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1008, is_a?@0x1009, cme:0x1010) PatchPoint NoSingletonClass(String@0x1008) v28:StringExact = GuardType v9, StringExact v29:BoolExact = IsA v28, v24 @@ -8124,7 +8124,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) - v28:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) PatchPoint NoSingletonClass(String@0x1010) v32:StringExact = GuardType v9, StringExact @@ -8157,7 +8157,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) - v30:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v30:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 7c10ef4425c6fd..8e862d74e71aba 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -77,6 +77,8 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R Specialization::Object(val) if val == unsafe { rb_mRubyVMFrozenCore } => write!(f, "[VMFrozenCore]"), Specialization::Object(val) if val == unsafe { rb_block_param_proxy } => write!(f, "[BlockParamProxy]"), Specialization::Object(val) if ty.is_subtype(types::Symbol) => write!(f, "[:{}]", ruby_sym_to_rust_string(val)), + Specialization::Object(val) if ty.is_subtype(types::Class) => + write!(f, "[{}@{:p}]", get_class_name(val), printer.ptr_map.map_ptr(val.0 as *const std::ffi::c_void)), Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)), // TODO(max): Ensure singleton classes never have Type specialization Specialization::Type(val) if unsafe { rb_zjit_singleton_class_p(val) } => From 1959fcacb357ec548ed8a000c6dc6e5f39a3fb55 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 11:53:35 -0500 Subject: [PATCH 1315/2435] ZJIT: Add tests for Kernel#kind_of? --- zjit/src/hir/opt_tests.rs | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 70ba333b1be5d5..c99f01d088ef86 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -8168,6 +8168,98 @@ mod hir_opt_tests { "); } + #[test] + fn test_specialize_kind_of_class() { + eval(r#" + def test(o) = o.kind_of?(String) + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1008, kind_of?@0x1009, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1008) + v28:StringExact = GuardType v9, StringExact + v29:BoolExact = IsA v28, v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_dont_specialize_kind_of_module() { + eval(r#" + def test(o) = o.kind_of?(Kernel) + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Kernel) + v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v28:StringExact = GuardType v9, StringExact + v29:BasicObject = CCallWithFrame Kernel#kind_of?@0x1048, v28, v24 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_elide_kind_of() { + eval(r#" + def test(o) + o.kind_of?(Integer) + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Integer) + v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v32:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + #[test] fn counting_complex_feature_use_for_fallback() { eval(" From e31c2569079d09cf63d6822558b053b8b8cc9937 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 19 Nov 2025 18:17:43 -0500 Subject: [PATCH 1316/2435] ZJIT: Inline GuardNotFrozen into LIR No sense calling a C function. --- zjit/src/codegen.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4c865dcd8a1b34..944f9504a25fb9 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -654,10 +654,20 @@ fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, } fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { - let ret = asm_ccall!(asm, rb_obj_frozen_p, recv); - asm_comment!(asm, "side-exit if rb_obj_frozen_p returns Qtrue"); - asm.cmp(ret, Qtrue.into()); - asm.je(side_exit(jit, state, GuardNotFrozen)); + let side_exit = side_exit(jit, state, GuardNotFrozen); + let recv = asm.load(recv); + // Side-exit if recv is false + assert_eq!(Qfalse.as_i64(), 0); + asm.test(recv, recv); + asm.jz(side_exit.clone()); + // Side-exit if recv is immediate + asm.test(recv, (RUBY_IMMEDIATE_MASK as u64).into()); + asm.jnz(side_exit.clone()); + // It's a heap object, so check the frozen flag + let flags = asm.load(Opnd::mem(64, recv, RUBY_OFFSET_RBASIC_FLAGS)); + asm.test(flags, (RUBY_FL_FREEZE as u64).into()); + // Side-exit if frozen + asm.jnz(side_exit.clone()); recv } From 6e2906f60da20d6cd057aa8ad4b84f8c988406d9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 21 Nov 2025 13:43:52 -0500 Subject: [PATCH 1317/2435] ZJIT: Don't make GuardNotFrozen consider immediates --- zjit/src/codegen.rs | 10 +--------- zjit/src/cruby_methods.rs | 2 ++ zjit/src/hir.rs | 8 ++++++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 944f9504a25fb9..3300219ccda07a 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -654,20 +654,12 @@ fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, } fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { - let side_exit = side_exit(jit, state, GuardNotFrozen); let recv = asm.load(recv); - // Side-exit if recv is false - assert_eq!(Qfalse.as_i64(), 0); - asm.test(recv, recv); - asm.jz(side_exit.clone()); - // Side-exit if recv is immediate - asm.test(recv, (RUBY_IMMEDIATE_MASK as u64).into()); - asm.jnz(side_exit.clone()); // It's a heap object, so check the frozen flag let flags = asm.load(Opnd::mem(64, recv, RUBY_OFFSET_RBASIC_FLAGS)); asm.test(flags, (RUBY_FL_FREEZE as u64).into()); // Side-exit if frozen - asm.jnz(side_exit.clone()); + asm.jnz(side_exit(jit, state, GuardNotFrozen)); recv } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 6d09b5e5a7995f..23c05e6d58fb54 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -330,6 +330,7 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { // Only inline the case of no arguments. let &[] = args else { return None; }; + // We know that all Array are HeapObject, so no need to insert a GuardType(HeapObject). let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state })) } @@ -391,6 +392,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); + // We know that all String are HeapObject, so no need to insert a GuardType(HeapObject). let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); // String#setbyte returns the fixnum provided as its `value` argument back to the caller. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3a69dd6610e514..d6ccf9160e7eeb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -903,7 +903,8 @@ pub enum Insn { /// Side-exit if the block param has been modified or the block handler for the frame /// is neither ISEQ nor ifunc, which makes it incompatible with rb_block_param_proxy. GuardBlockParamProxy { level: u32, state: InsnId }, - /// Side-exit if val is frozen. + /// Side-exit if val is frozen. Does *not* check if the val is an immediate; assumes that it is + /// a heap object. GuardNotFrozen { recv: InsnId, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, @@ -2553,6 +2554,7 @@ impl Function { // // No need for a GuardShape. if let OptimizedMethodType::StructAset = opt_type { + // We know that all Struct are HeapObject, so no need to insert a GuardType(HeapObject). recv = self.push_insn(block, Insn::GuardNotFrozen { recv, state }); } @@ -4150,7 +4152,6 @@ impl Function { | Insn::IsNil { val } | Insn::IsMethodCfunc { val, .. } | Insn::GuardShape { val, .. } - | Insn::GuardNotFrozen { recv: val, .. } | Insn::SetGlobal { val, .. } | Insn::SetLocal { val, .. } | Insn::SetClassVar { val, .. } @@ -4169,6 +4170,9 @@ impl Function { | Insn::DefinedIvar { self_val: val, .. } => { self.assert_subtype(insn_id, val, types::BasicObject) } + Insn::GuardNotFrozen { recv, .. } => { + self.assert_subtype(insn_id, recv, types::HeapBasicObject) + } // Instructions with 2 Ruby object operands Insn::SetIvar { self_val: left, val: right, .. } | Insn::NewRange { low: left, high: right, .. } From 2289961b485b1cbf7b1012693722c16a6cdb4cda Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 21 Nov 2025 19:30:04 +0100 Subject: [PATCH 1318/2435] [ruby/rubygems] Undeprecate Gem::Version#<=> against strings This pattern is extremely common across the ecosystem, I don't think it's reasonable to deprecate it. I understand the performance argument, but perhaps the dependency resolution algorithm can use another method that is private API and only works with two `Version` instance. https://github.com/ruby/rubygems/commit/024b4b547a --- lib/rubygems/version.rb | 3 --- test/rubygems/test_gem_version.rb | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index a88a4a49ee2b94..90fe1b3c24324a 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -344,9 +344,6 @@ def approximate_recommendation def <=>(other) if String === other - unless Gem::Deprecate.skip - warn "comparing version objects with strings is deprecated and will be removed", uplevel: 1 - end return unless self.class.correct?(other) return self <=> self.class.new(other) end diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index ce38a59113d2d2..3987c620d0a74d 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -160,11 +160,7 @@ def test_spaceship [-1, "1.9.3.1"], [nil, "whatever"], ].each do |cmp, string_ver| - actual_stdout, actual_stderr = capture_output do - assert_equal(cmp, v("1.9.3") <=> string_ver) - end - assert_empty actual_stdout - assert_match(/comparing version objects with strings is deprecated and will be removed/, actual_stderr) + assert_equal(cmp, v("1.9.3") <=> string_ver) end end From b47b37c12cf30b06ec5afc365f3739b0744b3f4c Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 12:46:03 +0000 Subject: [PATCH 1319/2435] [DOC] Harmonize #* methods --- complex.c | 9 +++++---- numeric.c | 10 ++++++---- rational.c | 16 +++++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/complex.c b/complex.c index 54c8cd0d6e8ad5..d69b0a65817497 100644 --- a/complex.c +++ b/complex.c @@ -913,15 +913,16 @@ comp_mul(VALUE areal, VALUE aimag, VALUE breal, VALUE bimag, VALUE *real, VALUE /* * call-seq: - * complex * numeric -> new_complex + * self * other -> numeric * - * Returns the product of +self+ and +numeric+: + * Returns the numeric product of +self+ and +other+: * + * Complex.rect(9, 8) * 4 # => (36+32i) + * Complex.rect(20, 9) * 9.8 # => (196.0+88.2i) * Complex.rect(2, 3) * Complex.rect(2, 3) # => (-5+12i) * Complex.rect(900) * Complex.rect(1) # => (900+0i) * Complex.rect(-2, 9) * Complex.rect(-9, 2) # => (0-85i) - * Complex.rect(9, 8) * 4 # => (36+32i) - * Complex.rect(20, 9) * 9.8 # => (196.0+88.2i) + * Complex.rect(9, 8) * Rational(2, 3) # => ((6/1)+(16/3)*i) * */ VALUE diff --git a/numeric.c b/numeric.c index 0d5f3f26f617e3..a9e5fa78663bc2 100644 --- a/numeric.c +++ b/numeric.c @@ -1195,13 +1195,14 @@ rb_float_minus(VALUE x, VALUE y) * call-seq: * self * other -> numeric * - * Returns a new \Float which is the product of +self+ and +other+: + * Returns the numeric product of +self+ and +other+: * * f = 3.14 * f * 2 # => 6.28 * f * 2.0 # => 6.28 * f * Rational(1, 2) # => 1.57 * f * Complex(2, 0) # => (6.28+0.0i) + * */ VALUE @@ -4098,16 +4099,17 @@ fix_mul(VALUE x, VALUE y) /* * call-seq: - * self * numeric -> numeric_result + * self * other -> numeric * - * Performs multiplication: + * Returns the numeric product of +self+ and +other+: * * 4 * 2 # => 8 - * 4 * -2 # => -8 * -4 * 2 # => -8 + * 4 * -2 # => -8 * 4 * 2.0 # => 8.0 * 4 * Rational(1, 3) # => (4/3) * 4 * Complex(2, 0) # => (8+0i) + * */ VALUE diff --git a/rational.c b/rational.c index 28f116580fb945..77b1b175d08185 100644 --- a/rational.c +++ b/rational.c @@ -853,15 +853,17 @@ f_muldiv(VALUE self, VALUE anum, VALUE aden, VALUE bnum, VALUE bden, int k) /* * call-seq: - * rat * numeric -> numeric + * self * other -> numeric * - * Performs multiplication. + * Returns the numeric product of +self+ and +other+: + * + * Rational(9, 8) * 4 #=> (9/2) + * Rational(20, 9) * 9.8 #=> 21.77777777777778 + * Rational(9, 8) * Complex(1, 2) # => ((9/8)+(9/4)*i) + * Rational(2, 3) * Rational(2, 3) #=> (4/9) + * Rational(900) * Rational(1) #=> (900/1) + * Rational(-2, 9) * Rational(-9, 2) #=> (1/1) * - * Rational(2, 3) * Rational(2, 3) #=> (4/9) - * Rational(900) * Rational(1) #=> (900/1) - * Rational(-2, 9) * Rational(-9, 2) #=> (1/1) - * Rational(9, 8) * 4 #=> (9/2) - * Rational(20, 9) * 9.8 #=> 21.77777777777778 */ VALUE rb_rational_mul(VALUE self, VALUE other) From 7789aaca3736fdac2c00b141b12f127168c485cd Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 16:44:55 +0000 Subject: [PATCH 1320/2435] [DOC] Tweaks for Integer#** --- numeric.c | 53 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/numeric.c b/numeric.c index a9e5fa78663bc2..a2c91d87a57db3 100644 --- a/numeric.c +++ b/numeric.c @@ -4592,17 +4592,48 @@ fix_pow(VALUE x, VALUE y) /* * call-seq: - * self ** numeric -> numeric_result - * - * Raises +self+ to the power of +numeric+: - * - * 2 ** 3 # => 8 - * 2 ** -3 # => (1/8) - * -2 ** 3 # => -8 - * -2 ** -3 # => (-1/8) - * 2 ** 3.3 # => 9.849155306759329 - * 2 ** Rational(3, 1) # => (8/1) - * 2 ** Complex(3, 0) # => (8+0i) + * self ** exponent -> numeric + * + * Returns the value of base +self+ raised to the power +exponent+; + * see {Exponentiation}[https://en.wikipedia.org/wiki/Exponentiation]: + * + * # Result for non-negative Integer exponent is Integer. + * 2 ** 0 # => 1 + * 2 ** 1 # => 2 + * 2 ** 2 # => 4 + * 2 ** 3 # => 8 + * -2 ** 3 # => -8 + * # Result for negative Integer exponent is Rational, not Float. + * 2 ** -3 # => (1/8) + * -2 ** -3 # => (-1/8) + * + * # Result for Float exponent is Float. + * 2 ** 0.0 # => 1.0 + * 2 ** 1.0 # => 2.0 + * 2 ** 2.0 # => 4.0 + * 2 ** 3.0 # => 8.0 + * -2 ** 3.0 # => -8.0 + * 2 ** -3.0 # => 0.125 + * -2 ** -3.0 # => -0.125 + * + * # Result for non-negative Complex exponent is Complex with Integer parts. + * 2 ** Complex(0, 0) # => (1+0i) + * 2 ** Complex(1, 0) # => (2+0i) + * 2 ** Complex(2, 0) # => (4+0i) + * 2 ** Complex(3, 0) # => (8+0i) + * -2 ** Complex(3, 0) # => (-8+0i) + * # Result for negative Complex exponent is Complex with Rational parts. + * 2 ** Complex(-3, 0) # => ((1/8)+(0/1)*i) + * -2 ** Complex(-3, 0) # => ((-1/8)+(0/1)*i) + * + * # Result for Rational exponent is Rational. + * 2 ** Rational(0, 1) # => (1/1) + * 2 ** Rational(1, 1) # => (2/1) + * 2 ** Rational(2, 1) # => (4/1) + * 2 ** Rational(3, 1) # => (8/1) + * -2 ** Rational(3, 1) # => (-8/1) + * 2 ** Rational(-3, 1) # => (1/8) + * -2 ** Rational(-3, 1) # => (-1/8) * */ VALUE From ec296f63e33e78a95a20378b689efc0bf74f271a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 22 Nov 2025 10:32:16 +0100 Subject: [PATCH 1321/2435] [ruby/json] Use booleans in string_scan https://github.com/ruby/json/commit/256cad5def --- ext/json/parser/parser.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 2dcd1012b1270f..cb22648dbc1dfc 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -937,7 +937,7 @@ static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) uint64_t mask = 0; if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros64(mask) >> 2; - return 1; + return true; } #elif defined(HAVE_SIMD_SSE2) @@ -945,7 +945,7 @@ static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) int mask = 0; if (string_scan_simd_sse2(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros(mask); - return 1; + return true; } } #endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */ @@ -953,11 +953,11 @@ static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) while (!eos(state)) { if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) { - return 1; + return true; } state->cursor++; } - return 0; + return false; } static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) From 735762747d818563555e13932b8e49f3801a8c7f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 22 Nov 2025 12:59:42 +0100 Subject: [PATCH 1322/2435] [ruby/json] Fix the parser to not accept invalid escapes Only `"\/bfnrtu` are valid after a backslash. https://github.com/ruby/json/commit/f7f8f552ed --- ext/json/parser/parser.c | 53 ++++++++++--------- .../fixtures/{pass15.json => fail15.json} | 0 .../fixtures/{pass16.json => fail16.json} | 0 .../fixtures/{pass17.json => fail17.json} | 0 .../fixtures/{pass26.json => fail26.json} | 0 test/json/json_fixtures_test.rb | 2 + test/json/json_parser_test.rb | 4 +- 7 files changed, 31 insertions(+), 28 deletions(-) rename test/json/fixtures/{pass15.json => fail15.json} (100%) rename test/json/fixtures/{pass16.json => fail16.json} (100%) rename test/json/fixtures/{pass17.json => fail17.json} (100%) rename test/json/fixtures/{pass26.json => fail26.json} (100%) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index cb22648dbc1dfc..2f584a51a932f4 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -639,44 +639,43 @@ static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *st static VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) { size_t bufferSize = stringEnd - string; - const char *p = string, *pe = string, *unescape, *bufferStart; + const char *p = string, *pe = string, *bufferStart; char *buffer; - int unescape_len; - char buf[4]; VALUE result = rb_str_buf_new(bufferSize); rb_enc_associate_index(result, utf8_encindex); buffer = RSTRING_PTR(result); bufferStart = buffer; +#define APPEND_CHAR(chr) *buffer++ = chr; p = ++pe; + while (pe < stringEnd && (pe = memchr(pe, '\\', stringEnd - pe))) { - unescape = (char *) "?"; - unescape_len = 1; if (pe > p) { MEMCPY(buffer, p, char, pe - p); buffer += pe - p; } switch (*++pe) { + case '"': + case '/': + p = pe; // nothing to unescape just need to skip the backslash + break; + case '\\': + APPEND_CHAR('\\'); + break; case 'n': - unescape = (char *) "\n"; + APPEND_CHAR('\n'); break; case 'r': - unescape = (char *) "\r"; + APPEND_CHAR('\r'); break; case 't': - unescape = (char *) "\t"; - break; - case '"': - unescape = (char *) "\""; - break; - case '\\': - unescape = (char *) "\\"; + APPEND_CHAR('\t'); break; case 'b': - unescape = (char *) "\b"; + APPEND_CHAR('\b'); break; case 'f': - unescape = (char *) "\f"; + APPEND_CHAR('\f'); break; case 'u': if (pe > stringEnd - 5) { @@ -714,18 +713,23 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c break; } } - unescape_len = convert_UTF32_to_UTF8(buf, ch); - unescape = buf; + + char buf[4]; + int unescape_len = convert_UTF32_to_UTF8(buf, ch); + MEMCPY(buffer, buf, char, unescape_len); + buffer += unescape_len; + p = ++pe; } break; default: - p = pe; - continue; + if ((unsigned char)*pe < 0x20) { + raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); + } + raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); + break; } - MEMCPY(buffer, unescape, char, unescape_len); - buffer += unescape_len; - p = ++pe; } +#undef APPEND_CHAR if (stringEnd > p) { MEMCPY(buffer, p, char, stringEnd - p); @@ -976,9 +980,6 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig case '\\': { state->cursor++; escaped = true; - if ((unsigned char)*state->cursor < 0x20) { - raise_parse_error("invalid ASCII control character in string: %s", state); - } break; } default: diff --git a/test/json/fixtures/pass15.json b/test/json/fixtures/fail15.json similarity index 100% rename from test/json/fixtures/pass15.json rename to test/json/fixtures/fail15.json diff --git a/test/json/fixtures/pass16.json b/test/json/fixtures/fail16.json similarity index 100% rename from test/json/fixtures/pass16.json rename to test/json/fixtures/fail16.json diff --git a/test/json/fixtures/pass17.json b/test/json/fixtures/fail17.json similarity index 100% rename from test/json/fixtures/pass17.json rename to test/json/fixtures/fail17.json diff --git a/test/json/fixtures/pass26.json b/test/json/fixtures/fail26.json similarity index 100% rename from test/json/fixtures/pass26.json rename to test/json/fixtures/fail26.json diff --git a/test/json/json_fixtures_test.rb b/test/json/json_fixtures_test.rb index c153ebef7cbed1..c0d10379393f7b 100644 --- a/test/json/json_fixtures_test.rb +++ b/test/json/json_fixtures_test.rb @@ -10,6 +10,8 @@ class JSONFixturesTest < Test::Unit::TestCase source = File.read(f) define_method("test_#{name}") do assert JSON.parse(source), "Did not pass for fixture '#{File.basename(f)}': #{source.inspect}" + rescue JSON::ParserError + raise "#{File.basename(f)} parsing failure" end end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index a5b4763618c192..3e662bda324ba5 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -510,8 +510,8 @@ def test_backslash data = ['"'] assert_equal data, parse(json) # - json = '["\\\'"]' - data = ["'"] + json = '["\\/"]' + data = ["/"] assert_equal data, parse(json) json = '["\/"]' From c7a84ae0bb8c6dab3463076d7e5ca9b6f89880f4 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 22 Nov 2025 14:13:30 +0100 Subject: [PATCH 1323/2435] [ruby/json] parser.c: Record escape positions while parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can then pass them to the decoder to save having to parse the string again. ``` == Parsing activitypub.json (58160 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 1.275k i/100ms Calculating ------------------------------------- after 12.774k (± 0.8%) i/s (78.29 μs/i) - 65.025k in 5.090834s Comparison: before: 12314.3 i/s after: 12773.8 i/s - 1.04x faster == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 143.000 i/100ms Calculating ------------------------------------- after 1.441k (± 0.2%) i/s (693.86 μs/i) - 7.293k in 5.060345s Comparison: before: 1430.1 i/s after: 1441.2 i/s - 1.01x faster == Parsing citm_catalog.json (1727030 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 69.000 i/100ms Calculating ------------------------------------- after 695.919 (± 0.4%) i/s (1.44 ms/i) - 3.519k in 5.056691s Comparison: before: 687.8 i/s after: 695.9 i/s - 1.01x faster ``` https://github.com/ruby/json/commit/4f4551f993 --- ext/json/parser/parser.c | 88 ++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 2f584a51a932f4..0216eef987e3f3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -616,8 +616,10 @@ static inline bool json_string_cacheable_p(const char *string, size_t length) return length <= JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH && rb_isalpha(string[0]); } -static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) +static inline VALUE json_string_fastpath(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name) { + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; size_t bufferSize = stringEnd - string; if (is_name && state->in_array && RB_LIKELY(json_string_cacheable_p(string, bufferSize))) { @@ -636,8 +638,33 @@ static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *st return build_string(string, stringEnd, intern, symbolize); } -static VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) +#define JSON_MAX_UNESCAPE_POSITIONS 16 +typedef struct _json_unescape_positions { + long size; + const char **positions; + bool has_more; +} JSON_UnescapePositions; + +static inline const char *json_next_backslash(const char *pe, const char *stringEnd, JSON_UnescapePositions *positions) +{ + while (positions->size) { + positions->size--; + const char *next_position = positions->positions[0]; + positions->positions++; + return next_position; + } + + if (positions->has_more) { + return memchr(pe, '\\', stringEnd - pe); + } + + return NULL; +} + +static NOINLINE() VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions) { + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; size_t bufferSize = stringEnd - string; const char *p = string, *pe = string, *bufferStart; char *buffer; @@ -649,7 +676,7 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c #define APPEND_CHAR(chr) *buffer++ = chr; p = ++pe; - while (pe < stringEnd && (pe = memchr(pe, '\\', stringEnd - pe))) { + while (pe < stringEnd && (pe = json_next_backslash(pe, stringEnd, positions))) { if (pe > p) { MEMCPY(buffer, p, char, pe - p); buffer += pe - p; @@ -893,20 +920,6 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi return object; } -static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name) -{ - VALUE string; - bool intern = is_name || config->freeze; - bool symbolize = is_name && config->symbolize_names; - if (escaped) { - string = json_string_unescape(state, start, end, is_name, intern, symbolize); - } else { - string = json_string_fastpath(state, start, end, is_name, intern, symbolize); - } - - return string; -} - static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *config, VALUE value) { if (RB_UNLIKELY(config->on_load_proc)) { @@ -964,22 +977,30 @@ static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) return false; } -static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name, const char *start) { - state->cursor++; - const char *start = state->cursor; - bool escaped = false; + const char *backslashes[JSON_MAX_UNESCAPE_POSITIONS]; + JSON_UnescapePositions positions = { + .size = 0, + .positions = backslashes, + .has_more = false, + }; - while (RB_UNLIKELY(string_scan(state))) { + do { switch (*state->cursor) { case '"': { - VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); + VALUE string = json_string_unescape(state, config, start, state->cursor, is_name, &positions); state->cursor++; return json_push_value(state, config, string); } case '\\': { + if (RB_LIKELY(positions.size < JSON_MAX_UNESCAPE_POSITIONS)) { + backslashes[positions.size] = state->cursor; + positions.size++; + } else { + positions.has_more = true; + } state->cursor++; - escaped = true; break; } default: @@ -988,12 +1009,29 @@ static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig } state->cursor++; - } + } while (string_scan(state)); raise_parse_error("unexpected end of input, expected closing \"", state); return Qfalse; } +static ALWAYS_INLINE() VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +{ + state->cursor++; + const char *start = state->cursor; + + if (RB_UNLIKELY(!string_scan(state))) { + raise_parse_error("unexpected end of input, expected closing \"", state); + } + + if (RB_LIKELY(*state->cursor == '"')) { + VALUE string = json_string_fastpath(state, config, start, state->cursor, is_name); + state->cursor++; + return json_push_value(state, config, string); + } + return json_parse_escaped_string(state, config, is_name, start); +} + #if JSON_CPU_LITTLE_ENDIAN_64BITS // From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/ // Additional References: From f9efa0cc0468692739770e754c12edf46cdf7c8e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 22 Nov 2025 22:11:31 +0900 Subject: [PATCH 1324/2435] [ruby/openssl] pkey/ec: fix OpenSSL::PKey::EC::Group#curve_name for unknown curves EC_GROUP_get_curve_name() returns NID_undef when OpenSSL does not recognize the curve and there is no associated OID. Handle this case explicitly and return nil instead of the string "UNDEF", which should not be exposed outside the extension. https://github.com/ruby/openssl/commit/2c16821c07 --- ext/openssl/ossl_pkey_ec.c | 16 +++++++--------- test/openssl/test_pkey_ec.rb | 12 ++++++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index c063450a4f2ef3..a9133062081c05 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -849,25 +849,23 @@ static VALUE ossl_ec_group_get_cofactor(VALUE self) /* * call-seq: - * group.curve_name => String + * group.curve_name -> string or nil * - * Returns the curve name (sn). + * Returns the curve name (short name) corresponding to this group, or +nil+ + * if \OpenSSL does not have an OID associated with the group. * * See the OpenSSL documentation for EC_GROUP_get_curve_name() */ static VALUE ossl_ec_group_get_curve_name(VALUE self) { - EC_GROUP *group = NULL; + EC_GROUP *group; int nid; GetECGroup(self, group); - if (group == NULL) - return Qnil; - nid = EC_GROUP_get_curve_name(group); - -/* BUG: an nid or asn1 object should be returned, maybe. */ - return rb_str_new2(OBJ_nid2sn(nid)); + if (nid == NID_undef) + return Qnil; + return rb_str_new_cstr(OBJ_nid2sn(nid)); } /* diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index df91a1be255f07..88085bc68c7ef5 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -369,18 +369,26 @@ def test_ec_point point2.to_octet_string(:uncompressed) assert_equal point2.to_octet_string(:uncompressed), point3.to_octet_string(:uncompressed) + end + def test_small_curve begin group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2) group.point_conversion_form = :uncompressed generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 })) group.set_generator(generator, 19, 1) - point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 })) rescue OpenSSL::PKey::EC::Group::Error pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message raise end - + assert_equal 17.to_bn.num_bits, group.degree + assert_equal B(%w{ 04 05 01 }), + group.generator.to_octet_string(:uncompressed) + assert_equal 19.to_bn, group.order + assert_equal 1.to_bn, group.cofactor + assert_nil group.curve_name + + point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 })) assert_equal 0x040603.to_bn, point.to_bn assert_equal 0x040603.to_bn, point.to_bn(:uncompressed) assert_equal 0x0306.to_bn, point.to_bn(:compressed) From 424499dd10aa01b3d84957761b19dde63b28113c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 17 May 2025 18:42:28 +0900 Subject: [PATCH 1325/2435] [ruby/openssl] ts: refactor converting string to ASN1_OBJECT obj_to_asn1obj() in ossl_ts.c and ossl_asn1.c are identical. Let's remove one in ossl_ts.c. eASN1Error can now be made static to ossl_asn1.c. https://github.com/ruby/openssl/commit/dcb05c40c2 --- ext/openssl/ossl_asn1.c | 10 +++++----- ext/openssl/ossl_asn1.h | 6 +++++- ext/openssl/ossl_ts.c | 19 +++---------------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 186679da4c6b55..58efe96f2a8ec2 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -160,7 +160,7 @@ asn1integer_to_num_i(VALUE arg) #define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) VALUE mASN1; -VALUE eASN1Error; +static VALUE eASN1Error; VALUE cASN1Data; static VALUE cASN1Primitive; @@ -247,8 +247,8 @@ obj_to_asn1null(VALUE obj) return null; } -static ASN1_OBJECT* -obj_to_asn1obj(VALUE obj) +ASN1_OBJECT * +ossl_to_asn1obj(VALUE obj) { ASN1_OBJECT *a1obj; @@ -544,7 +544,7 @@ ossl_asn1_get_asn1type(VALUE obj) free_func = (free_func_type *)ASN1_STRING_free; break; case V_ASN1_OBJECT: - ptr = obj_to_asn1obj(value); + ptr = ossl_to_asn1obj(value); free_func = (free_func_type *)ASN1_OBJECT_free; break; case V_ASN1_UTCTIME: @@ -1205,7 +1205,7 @@ ossl_asn1obj_get_oid(VALUE self) ASN1_OBJECT *a1obj; int state; - a1obj = obj_to_asn1obj(ossl_asn1_get_value(self)); + a1obj = ossl_to_asn1obj(ossl_asn1_get_value(self)); str = rb_protect(asn1obj_get_oid_i, (VALUE)a1obj, &state); ASN1_OBJECT_free(a1obj); if (state) diff --git a/ext/openssl/ossl_asn1.h b/ext/openssl/ossl_asn1.h index 3b689c0ee7ded5..a723b06f602624 100644 --- a/ext/openssl/ossl_asn1.h +++ b/ext/openssl/ossl_asn1.h @@ -31,11 +31,15 @@ VALUE asn1str_to_str(const ASN1_STRING *); VALUE asn1integer_to_num(const ASN1_INTEGER *); ASN1_INTEGER *num_to_asn1integer(VALUE, ASN1_INTEGER *); +/* + * ASN1_OBJECT conversions + */ +ASN1_OBJECT *ossl_to_asn1obj(VALUE obj); + /* * ASN1 module */ extern VALUE mASN1; -extern VALUE eASN1Error; extern VALUE cASN1Data; diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 3c505b64a9f6a5..57d4b2a70e4ae6 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -132,23 +132,10 @@ asn1_to_der(void *template, int (*i2d)(void *template, unsigned char **pp)) return str; } -static ASN1_OBJECT* -obj_to_asn1obj(VALUE obj) -{ - ASN1_OBJECT *a1obj; - - StringValue(obj); - a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0); - if(!a1obj) a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1); - if(!a1obj) ossl_raise(eASN1Error, "invalid OBJECT ID"); - - return a1obj; -} - static VALUE obj_to_asn1obj_i(VALUE obj) { - return (VALUE)obj_to_asn1obj(obj); + return (VALUE)ossl_to_asn1obj(obj); } static VALUE @@ -264,7 +251,7 @@ ossl_ts_req_set_algorithm(VALUE self, VALUE algo) X509_ALGOR *algor; GetTSRequest(self, req); - obj = obj_to_asn1obj(algo); + obj = ossl_to_asn1obj(algo); mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); if (!X509_ALGOR_set0(algor, obj, V_ASN1_NULL, NULL)) { @@ -394,7 +381,7 @@ ossl_ts_req_set_policy_id(VALUE self, VALUE oid) int ok; GetTSRequest(self, req); - obj = obj_to_asn1obj(oid); + obj = ossl_to_asn1obj(oid); ok = TS_REQ_set_policy_id(req, obj); ASN1_OBJECT_free(obj); if (!ok) From dd489ee9c48fc8c2b499b80f3ebcd053de33bb0a Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 19 Nov 2025 01:41:35 +0900 Subject: [PATCH 1326/2435] [ruby/openssl] asn1: refactor converting ASN1_OBJECT to string ruby/openssl exposes OIDs to Ruby as strings in many places, but the conversion logic has been duplicated and the behavior is inconsistent. There are mainly two patterns: - Returns the short name associated with the OID/NID, or the dotted decimal notation if it is unknown to OpenSSL. - Returns the long name, or the dotted decimal notation. These patterns are implemented using different OpenSSL APIs and that caused subtle differences. Add helper functions ossl_asn1obj_to_string() and ossl_asn1obj_to_string_long_name() to unify the logic. Also, document the current behaviors where it is not yet done. The inconsistency was likely unintentional, but since it dates back to the original implementations, standardizing it now would cause more issues than it resolves. https://github.com/ruby/openssl/commit/2ea36c21a4 --- ext/openssl/ossl_asn1.c | 91 +++++++++++++++++++++-------------- ext/openssl/ossl_asn1.h | 10 ++++ ext/openssl/ossl_ocsp.c | 11 +---- ext/openssl/ossl_ts.c | 29 ++--------- ext/openssl/ossl_x509attr.c | 21 ++------ ext/openssl/ossl_x509cert.c | 20 +++----- ext/openssl/ossl_x509crl.c | 20 ++++---- ext/openssl/ossl_x509ext.c | 23 +++------ ext/openssl/ossl_x509name.c | 36 +++++--------- ext/openssl/ossl_x509req.c | 21 ++++---- test/openssl/test_asn1.rb | 7 ++- test/openssl/test_ts.rb | 3 +- test/openssl/test_x509cert.rb | 1 + test/openssl/test_x509crl.rb | 1 + test/openssl/test_x509req.rb | 5 ++ 15 files changed, 139 insertions(+), 160 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 58efe96f2a8ec2..1309811c742e31 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -147,6 +147,48 @@ asn1integer_to_num_i(VALUE arg) return asn1integer_to_num((ASN1_INTEGER *)arg); } +/* + * ASN1_OBJECT conversions + */ +VALUE +ossl_asn1obj_to_string_oid(const ASN1_OBJECT *a1obj) +{ + VALUE str; + int len; + + str = rb_usascii_str_new(NULL, 127); + len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); + if (len <= 0 || len == INT_MAX) + ossl_raise(eOSSLError, "OBJ_obj2txt"); + if (len > RSTRING_LEN(str)) { + /* +1 is for the \0 terminator added by OBJ_obj2txt() */ + rb_str_resize(str, len + 1); + len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); + if (len <= 0) + ossl_raise(eOSSLError, "OBJ_obj2txt"); + } + rb_str_set_len(str, len); + return str; +} + +VALUE +ossl_asn1obj_to_string(const ASN1_OBJECT *obj) +{ + int nid = OBJ_obj2nid(obj); + if (nid != NID_undef) + return rb_str_new_cstr(OBJ_nid2sn(nid)); + return ossl_asn1obj_to_string_oid(obj); +} + +VALUE +ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *obj) +{ + int nid = OBJ_obj2nid(obj); + if (nid != NID_undef) + return rb_str_new_cstr(OBJ_nid2ln(nid)); + return ossl_asn1obj_to_string_oid(obj); +} + /********/ /* * ASN1 module @@ -393,32 +435,27 @@ decode_null(unsigned char* der, long length) return Qnil; } +VALUE +asn1obj_to_string_i(VALUE arg) +{ + return ossl_asn1obj_to_string((const ASN1_OBJECT *)arg); +} + static VALUE decode_obj(unsigned char* der, long length) { ASN1_OBJECT *obj; const unsigned char *p; VALUE ret; - int nid; - BIO *bio; + int state; p = der; - if(!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); - if((nid = OBJ_obj2nid(obj)) != NID_undef){ - ASN1_OBJECT_free(obj); - ret = rb_str_new2(OBJ_nid2sn(nid)); - } - else{ - if(!(bio = BIO_new(BIO_s_mem()))){ - ASN1_OBJECT_free(obj); - ossl_raise(eASN1Error, NULL); - } - i2a_ASN1_OBJECT(bio, obj); - ASN1_OBJECT_free(obj); - ret = ossl_membio2str(bio); - } - + if (!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) + ossl_raise(eASN1Error, "d2i_ASN1_OBJECT"); + ret = rb_protect(asn1obj_to_string_i, (VALUE)obj, &state); + ASN1_OBJECT_free(obj); + if (state) + rb_jump_tag(state); return ret; } @@ -1172,23 +1209,7 @@ ossl_asn1obj_get_ln(VALUE self) static VALUE asn1obj_get_oid_i(VALUE vobj) { - ASN1_OBJECT *a1obj = (void *)vobj; - VALUE str; - int len; - - str = rb_usascii_str_new(NULL, 127); - len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); - if (len <= 0 || len == INT_MAX) - ossl_raise(eASN1Error, "OBJ_obj2txt"); - if (len > RSTRING_LEN(str)) { - /* +1 is for the \0 terminator added by OBJ_obj2txt() */ - rb_str_resize(str, len + 1); - len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); - if (len <= 0) - ossl_raise(eASN1Error, "OBJ_obj2txt"); - } - rb_str_set_len(str, len); - return str; + return ossl_asn1obj_to_string_oid((const ASN1_OBJECT *)vobj); } /* diff --git a/ext/openssl/ossl_asn1.h b/ext/openssl/ossl_asn1.h index a723b06f602624..b605df8f3f8ce2 100644 --- a/ext/openssl/ossl_asn1.h +++ b/ext/openssl/ossl_asn1.h @@ -35,6 +35,16 @@ ASN1_INTEGER *num_to_asn1integer(VALUE, ASN1_INTEGER *); * ASN1_OBJECT conversions */ ASN1_OBJECT *ossl_to_asn1obj(VALUE obj); +/* + * Returns the short name if available, the dotted decimal notation otherwise. + * This is the most common way to return ASN1_OBJECT to Ruby. + */ +VALUE ossl_asn1obj_to_string(const ASN1_OBJECT *a1obj); +/* + * However, some places use long names instead. This is likely unintentional, + * but we keep the current behavior in existing methods. + */ +VALUE ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *a1obj); /* * ASN1 module diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 84d38760e5c0de..390215a8e3d67c 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1591,19 +1591,10 @@ ossl_ocspcid_get_hash_algorithm(VALUE self) { OCSP_CERTID *id; ASN1_OBJECT *oid; - BIO *out; GetOCSPCertId(self, id); OCSP_id_get0_info(NULL, &oid, NULL, NULL, id); - - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eOCSPError, "BIO_new"); - - if (!i2a_ASN1_OBJECT(out, oid)) { - BIO_free(out); - ossl_raise(eOCSPError, "i2a_ASN1_OBJECT"); - } - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(oid); } /* diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 57d4b2a70e4ae6..575418ccc6ce46 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -138,27 +138,6 @@ obj_to_asn1obj_i(VALUE obj) return (VALUE)ossl_to_asn1obj(obj); } -static VALUE -get_asn1obj(const ASN1_OBJECT *obj) -{ - BIO *out; - VALUE ret; - int nid; - if ((nid = OBJ_obj2nid(obj)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eTimestampError, "BIO_new(BIO_s_mem())"); - if (i2a_ASN1_OBJECT(out, obj) <= 0) { - BIO_free(out); - ossl_raise(eTimestampError, "i2a_ASN1_OBJECT"); - } - ret = ossl_membio2str(out); - } - - return ret; -} - static VALUE ossl_ts_req_alloc(VALUE klass) { @@ -229,7 +208,7 @@ ossl_ts_req_get_algorithm(VALUE self) mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algor); - return get_asn1obj(obj); + return ossl_asn1obj_to_string(obj); } /* @@ -358,7 +337,7 @@ ossl_ts_req_get_policy_id(VALUE self) GetTSRequest(self, req); if (!TS_REQ_get_policy_id(req)) return Qnil; - return get_asn1obj(TS_REQ_get_policy_id(req)); + return ossl_asn1obj_to_string(TS_REQ_get_policy_id(req)); } /* @@ -948,7 +927,7 @@ ossl_ts_token_info_get_policy_id(VALUE self) TS_TST_INFO *info; GetTSTokenInfo(self, info); - return get_asn1obj(TS_TST_INFO_get_policy_id(info)); + return ossl_asn1obj_to_string(TS_TST_INFO_get_policy_id(info)); } /* @@ -976,7 +955,7 @@ ossl_ts_token_info_get_algorithm(VALUE self) mi = TS_TST_INFO_get_msg_imprint(info); algo = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algo); - return get_asn1obj(obj); + return ossl_asn1obj_to_string(obj); } /* diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index d983af59686946..9beb15f4a0ca71 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -164,29 +164,18 @@ ossl_x509attr_set_oid(VALUE self, VALUE oid) /* * call-seq: - * attr.oid => string + * attr.oid -> string + * + * Returns the OID of the attribute. Returns the short name or the dotted + * decimal notation. */ static VALUE ossl_x509attr_get_oid(VALUE self) { X509_ATTRIBUTE *attr; - ASN1_OBJECT *oid; - BIO *out; - VALUE ret; - int nid; GetX509Attr(self, attr); - oid = X509_ATTRIBUTE_get0_object(attr); - if ((nid = OBJ_obj2nid(oid)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509AttrError, NULL); - i2a_ASN1_OBJECT(out, oid); - ret = ossl_membio2str(out); - } - - return ret; + return ossl_asn1obj_to_string(X509_ATTRIBUTE_get0_object(attr)); } /* diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index c7653031b4bc1f..cf00eb78bf9c03 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -318,27 +318,23 @@ ossl_x509_set_serial(VALUE self, VALUE num) /* * call-seq: * cert.signature_algorithm => string + * + * Returns the signature algorithm used to sign this certificate. This returns + * the algorithm name found in the TBSCertificate structure, not the outer + * \Certificate structure. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. */ static VALUE ossl_x509_get_signature_algorithm(VALUE self) { X509 *x509; - BIO *out; const ASN1_OBJECT *obj; - VALUE str; GetX509(self, x509); - out = BIO_new(BIO_s_mem()); - if (!out) ossl_raise(eX509CertError, NULL); - X509_ALGOR_get0(&obj, NULL, NULL, X509_get0_tbs_sigalg(x509)); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); - } - str = ossl_membio2str(out); - - return str; + return ossl_asn1obj_to_string_long_name(obj); } /* diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index b9ee5f05692b52..af4803f47d3cc9 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -166,26 +166,26 @@ ossl_x509crl_set_version(VALUE self, VALUE version) return version; } +/* + * call-seq: + * crl.signature_algorithm -> string + * + * Returns the signature algorithm used to sign this CRL. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. + */ static VALUE ossl_x509crl_get_signature_algorithm(VALUE self) { X509_CRL *crl; const X509_ALGOR *alg; const ASN1_OBJECT *obj; - BIO *out; GetX509CRL(self, crl); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509CRLError, NULL); - } X509_CRL_get0_signature(crl, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509CRLError, NULL); - } - - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(obj); } static VALUE diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 01aa3a8f51cd17..01b342b5be7a32 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -359,27 +359,20 @@ ossl_x509ext_set_critical(VALUE self, VALUE flag) return flag; } +/* + * call-seq: + * ext.oid -> string + * + * Returns the OID of the extension. Returns the short name or the dotted + * decimal notation. + */ static VALUE ossl_x509ext_get_oid(VALUE obj) { X509_EXTENSION *ext; - ASN1_OBJECT *extobj; - BIO *out; - VALUE ret; - int nid; GetX509Ext(obj, ext); - extobj = X509_EXTENSION_get_object(ext); - if ((nid = OBJ_obj2nid(extobj)) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); - else{ - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509ExtError, NULL); - i2a_ASN1_OBJECT(out, extobj); - ret = ossl_membio2str(out); - } - - return ret; + return ossl_asn1obj_to_string(X509_EXTENSION_get_object(ext)); } static VALUE diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 7d0fd35247f5cb..5483450aa7606b 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -341,34 +341,22 @@ static VALUE ossl_x509name_to_a(VALUE self) { X509_NAME *name; - X509_NAME_ENTRY *entry; - int i,entries,nid; - char long_name[512]; - const char *short_name; - VALUE ary, vname, ret; - ASN1_STRING *value; + int entries; + VALUE ret; GetX509Name(self, name); entries = X509_NAME_entry_count(name); ret = rb_ary_new_capa(entries); - for (i=0; itype)); - rb_ary_push(ret, ary); + for (int i = 0; i < entries; i++) { + const X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); + if (!entry) + ossl_raise(eX509NameError, "X509_NAME_get_entry"); + const ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); + VALUE vname = ossl_asn1obj_to_string(obj); + const ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry); + VALUE vdata = asn1str_to_str(data); + VALUE type = INT2NUM(ASN1_STRING_type(data)); + rb_ary_push(ret, rb_ary_new_from_args(3, vname, vdata, type)); } return ret; } diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index eae57969241954..1ae0fd56a6e612 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -255,27 +255,26 @@ ossl_x509req_set_subject(VALUE self, VALUE subject) return subject; } +/* + * call-seq: + * req.signature_algorithm -> string + * + * Returns the signature algorithm used to sign this request. + * + * Returns the long name of the signature algorithm, or the dotted decimal + * notation if \OpenSSL does not define a long name for it. + */ static VALUE ossl_x509req_get_signature_algorithm(VALUE self) { X509_REQ *req; const X509_ALGOR *alg; const ASN1_OBJECT *obj; - BIO *out; GetX509Req(self, req); - - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); - } X509_REQ_get0_signature(req, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); - if (!i2a_ASN1_OBJECT(out, obj)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); - } - - return ossl_membio2str(out); + return ossl_asn1obj_to_string_long_name(obj); } static VALUE diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index 501e35151fb5e4..df8b0accb36c84 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -306,7 +306,11 @@ def test_null end def test_object_identifier - encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b) + obj = encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b) + assert_equal "0.0", obj.oid + assert_nil obj.sn + assert_nil obj.ln + assert_equal obj.oid, obj.value encode_decode_test B(%w{ 06 01 28 }), OpenSSL::ASN1::ObjectId.new("1.0".b) encode_decode_test B(%w{ 06 03 88 37 03 }), OpenSSL::ASN1::ObjectId.new("2.999.3".b) encode_decode_test B(%w{ 06 05 2A 22 83 BB 55 }), OpenSSL::ASN1::ObjectId.new("1.2.34.56789".b) @@ -314,6 +318,7 @@ def test_object_identifier assert_equal "2.16.840.1.101.3.4.2.1", obj.oid assert_equal "SHA256", obj.sn assert_equal "sha256", obj.ln + assert_equal obj.sn, obj.value assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 06 00 })) } diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb index 7b154d1c37994e..cca7898bc13d86 100644 --- a/test/openssl/test_ts.rb +++ b/test/openssl/test_ts.rb @@ -88,8 +88,9 @@ def test_request_assignment assert_raise(TypeError) { req.version = nil } assert_raise(TypeError) { req.version = "foo" } - req.algorithm = "SHA1" + req.algorithm = "sha1" assert_equal("SHA1", req.algorithm) + assert_equal("SHA1", OpenSSL::ASN1.ObjectId("SHA1").sn) assert_raise(TypeError) { req.algorithm = nil } assert_raise(OpenSSL::ASN1::ASN1Error) { req.algorithm = "xxx" } diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb index 55481690e9a49a..877eac69ce5e34 100644 --- a/test/openssl/test_x509cert.rb +++ b/test/openssl/test_x509cert.rb @@ -236,6 +236,7 @@ def test_invalid_extension def test_sign_and_verify cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, digest: "SHA256") + assert_equal("sha256WithRSAEncryption", cert.signature_algorithm) # ln assert_equal(true, cert.verify(@rsa1)) assert_equal(false, cert.verify(@rsa2)) assert_equal(false, certificate_error_returns_false { cert.verify(@ec1) }) diff --git a/test/openssl/test_x509crl.rb b/test/openssl/test_x509crl.rb index 3c364f57d535e1..81c9247df2c9d6 100644 --- a/test/openssl/test_x509crl.rb +++ b/test/openssl/test_x509crl.rb @@ -20,6 +20,7 @@ def test_basic assert_equal(cert.issuer.to_der, crl.issuer.to_der) assert_equal(now, crl.last_update) assert_equal(now+1600, crl.next_update) + assert_equal("sha256WithRSAEncryption", crl.signature_algorithm) # ln crl = OpenSSL::X509::CRL.new(crl.to_der) assert_equal(1, crl.version) diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb index 0a2df47bcac7eb..b198a1185aba59 100644 --- a/test/openssl/test_x509req.rb +++ b/test/openssl/test_x509req.rb @@ -42,6 +42,11 @@ def test_subject assert_equal(@dn.to_der, req.subject.to_der) end + def test_signature_algorithm + req = issue_csr(0, @dn, @rsa1, "SHA256") + assert_equal("sha256WithRSAEncryption", req.signature_algorithm) # ln + end + def create_ext_req(exts) ef = OpenSSL::X509::ExtensionFactory.new exts = exts.collect{|e| ef.create_extension(*e) } From a7f948c32c169ceabb5719f0328e594759c73cd8 Mon Sep 17 00:00:00 2001 From: Brandon Zylstra <9854+brandonzylstra@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:35:15 -0500 Subject: [PATCH 1327/2435] [DOC] Fix a typo in enum.c documentation fix a minor misspelling in the documentation comments --- enum.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enum.c b/enum.c index cbf74df484bbcd..ba0dc94744cc3b 100644 --- a/enum.c +++ b/enum.c @@ -3946,7 +3946,7 @@ chunk_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator)) * ["F", 6860] * * You can use the special symbol :_alone to force an element - * into its own separate chuck: + * into its own separate chunk: * * a = [0, 0, 1, 1] * e = a.chunk{|i| i.even? ? :_alone : true } From beb85e7eeee4163cd45b69645a60cdb942f72c05 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 13:55:15 +0900 Subject: [PATCH 1328/2435] [Bug #21705] Fix segfaults on Windows It should check the type of the argument and coercion before converting the encoding. --- ext/socket/unixsocket.c | 3 ++- test/socket/test_unix.rb | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index 31e2acee04e896..2ec93760746c02 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -42,11 +42,12 @@ unixsock_path_value(VALUE path) } } #endif + path = rb_get_path(path); #ifdef _WIN32 /* UNIXSocket requires UTF-8 per spec. */ path = rb_str_export_to_enc(path, rb_utf8_encoding()); #endif - return rb_get_path(path); + return path; } VALUE diff --git a/test/socket/test_unix.rb b/test/socket/test_unix.rb index 0d284b29264618..e239e3935b84d9 100644 --- a/test/socket/test_unix.rb +++ b/test/socket/test_unix.rb @@ -293,14 +293,18 @@ def bound_unix_socket(klass) File.unlink path if path && File.socket?(path) end - def test_open_nul_byte - tmpfile = Tempfile.new("s") - path = tmpfile.path - tmpfile.close(true) - assert_raise(ArgumentError) {UNIXServer.open(path+"\0")} - assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")} - ensure - File.unlink path if path && File.socket?(path) + def test_open_argument + assert_raise(TypeError) {UNIXServer.new(nil)} + assert_raise(TypeError) {UNIXServer.new(1)} + Tempfile.create("s") do |s| + path = s.path + s.close + File.unlink(path) + assert_raise(ArgumentError) {UNIXServer.open(path+"\0")} + assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")} + arg = Struct.new(:to_path).new(path) + assert_equal(path, UNIXServer.open(arg) { |server| server.path }) + end end def test_addr From fed528a995a4c29dc75961a99ef6bdf0094296e0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 17:56:11 +0900 Subject: [PATCH 1329/2435] [DOC] Remove the name from same file references --- doc/encodings.rdoc | 6 +-- doc/format_specifications.rdoc | 26 ++++++------- doc/packed_data.rdoc | 2 +- doc/ruby/options.md | 70 +++++++++++++++++----------------- doc/strftime_formatting.rdoc | 7 ++-- doc/syntax/assignment.rdoc | 2 +- 6 files changed, 56 insertions(+), 57 deletions(-) diff --git a/doc/encodings.rdoc b/doc/encodings.rdoc index bd87c38e9e7df1..ee7f224f2ba550 100644 --- a/doc/encodings.rdoc +++ b/doc/encodings.rdoc @@ -138,7 +138,7 @@ A Ruby String object has an encoding that is an instance of class \Encoding. The encoding may be retrieved by method String#encoding. The default encoding for a string literal is the script encoding; -see {Script Encoding}[rdoc-ref:encodings.rdoc@Script+Encoding]. +see {Script Encoding}[rdoc-ref:@Script+Encoding]. 's'.encoding # => # @@ -147,7 +147,7 @@ The default encoding for a string created with method String.new is: - For no argument, ASCII-8BIT. - For a \String object argument, the encoding of that string. - For a string literal, the script encoding; - see {Script Encoding}[rdoc-ref:encodings.rdoc@Script+Encoding]. + see {Script Encoding}[rdoc-ref:@Script+Encoding]. In either case, any encoding may be specified: @@ -193,7 +193,7 @@ The default encoding for these, however, is: - US-ASCII, if all characters are US-ASCII. - The script encoding, otherwise; - see (Script Encoding)[rdoc-ref:encodings.rdoc@Script+Encoding]. + see (Script Encoding)[rdoc-ref:@Script+Encoding]. == Filesystem \Encoding diff --git a/doc/format_specifications.rdoc b/doc/format_specifications.rdoc index a2755256df3994..a59f2103775ec8 100644 --- a/doc/format_specifications.rdoc +++ b/doc/format_specifications.rdoc @@ -46,42 +46,42 @@ The links lead to the details and examples. === \Integer Type Specifiers - +b+ or +B+: Format +argument+ as a binary integer. - See {Specifiers b and B}[rdoc-ref:format_specifications.rdoc@Specifiers+b+and+B]. + See {Specifiers b and B}[rdoc-ref:@Specifiers+b+and+B]. - +d+, +i+, or +u+ (all are identical): Format +argument+ as a decimal integer. - See {Specifier d}[rdoc-ref:format_specifications.rdoc@Specifier+d]. + See {Specifier d}[rdoc-ref:@Specifier+d]. - +o+: Format +argument+ as an octal integer. - See {Specifier o}[rdoc-ref:format_specifications.rdoc@Specifier+o]. + See {Specifier o}[rdoc-ref:@Specifier+o]. - +x+ or +X+: Format +argument+ as a hexadecimal integer. - See {Specifiers x and X}[rdoc-ref:format_specifications.rdoc@Specifiers+x+and+X]. + See {Specifiers x and X}[rdoc-ref:@Specifiers+x+and+X]. === Floating-Point Type Specifiers - +a+ or +A+: Format +argument+ as hexadecimal floating-point number. - See {Specifiers a and A}[rdoc-ref:format_specifications.rdoc@Specifiers+a+and+A]. + See {Specifiers a and A}[rdoc-ref:@Specifiers+a+and+A]. - +e+ or +E+: Format +argument+ in scientific notation. - See {Specifiers e and E}[rdoc-ref:format_specifications.rdoc@Specifiers+e+and+E]. + See {Specifiers e and E}[rdoc-ref:@Specifiers+e+and+E]. - +f+: Format +argument+ as a decimal floating-point number. - See {Specifier f}[rdoc-ref:format_specifications.rdoc@Specifier+f]. + See {Specifier f}[rdoc-ref:@Specifier+f]. - +g+ or +G+: Format +argument+ in a "general" format. - See {Specifiers g and G}[rdoc-ref:format_specifications.rdoc@Specifiers+g+and+G]. + See {Specifiers g and G}[rdoc-ref:@Specifiers+g+and+G]. === Other Type Specifiers - +c+: Format +argument+ as a character. - See {Specifier c}[rdoc-ref:format_specifications.rdoc@Specifier+c]. + See {Specifier c}[rdoc-ref:@Specifier+c]. - +p+: Format +argument+ as a string via argument.inspect. - See {Specifier p}[rdoc-ref:format_specifications.rdoc@Specifier+p]. + See {Specifier p}[rdoc-ref:@Specifier+p]. - +s+: Format +argument+ as a string via argument.to_s. - See {Specifier s}[rdoc-ref:format_specifications.rdoc@Specifier+s]. + See {Specifier s}[rdoc-ref:@Specifier+s]. - %: Format +argument+ ('%') as a single percent character. - See {Specifier %}[rdoc-ref:format_specifications.rdoc@Specifier+-25]. + See {Specifier %}[rdoc-ref:@Specifier+-25]. == Flags The effect of a flag may vary greatly among type specifiers. These remarks are general in nature. -See {type-specific details}[rdoc-ref:format_specifications.rdoc@Type+Specifier+Details+and+Examples]. +See {type-specific details}[rdoc-ref:@Type+Specifier+Details+and+Examples]. Multiple flags may be given with single type specifier; order does not matter. diff --git a/doc/packed_data.rdoc b/doc/packed_data.rdoc index b33eed58e7e47c..3a2c8a57a5f0ed 100644 --- a/doc/packed_data.rdoc +++ b/doc/packed_data.rdoc @@ -159,7 +159,7 @@ Any directive may be followed by either of these modifiers: 'AB'.unpack('C3') # => [65, 66, nil] Note: Directives in %w[A a Z m] use +count+ differently; - see {String Directives}[rdoc-ref:packed_data.rdoc@String+Directives]. + see {String Directives}[rdoc-ref:@String+Directives]. If elements don't fit the provided directive, only least significant bits are encoded: diff --git a/doc/ruby/options.md b/doc/ruby/options.md index 943b5f967bf86e..ee6b2054bc1e80 100644 --- a/doc/ruby/options.md +++ b/doc/ruby/options.md @@ -68,15 +68,15 @@ nil See also: -- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-a`: Split Input Lines into Fields @@ -98,15 +98,15 @@ and the default field separator is `$;`. See also: -- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-c`: Check Syntax @@ -228,15 +228,15 @@ The argument must immediately follow the option See also: -- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-h`: Print Short Help Message @@ -314,15 +314,15 @@ $ ruby -ln -e 'p $_' desiderata.txt See also: -- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-n`: Run Program in `gets` Loop @@ -348,15 +348,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-p`: `-n`, with Printing @@ -377,15 +377,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. ### `-r`: Require Library @@ -434,7 +434,7 @@ $ ruby -s t.rb -foo=baz -bar=bat ``` The option may not be used with -{option -e}[rdoc-ref:ruby/options.md@e-3A+Execute+Given+Ruby+Code] +{option -e}[rdoc-ref:@e-3A+Execute+Given+Ruby+Code] ### `-S`: Search Directories in `ENV['PATH']` @@ -583,7 +583,7 @@ ruby - Copyright (C) 1993-2024 Yukihiro Matsumoto ### `--debug`: Alias for `-d` Option `--debug` is an alias for -{option -d}[rdoc-ref:ruby/options.md@d-3A+Set+-24DEBUG+to+true]. +{option -d}[rdoc-ref:@d-3A+Set+-24DEBUG+to+true]. ### `--disable`: Disable Features @@ -617,9 +617,9 @@ option was given: - `--dump=help`: Same as {option \-\-help}[options_md.html#label--help-3A+Print+Help+Message]. - `--dump=syntax`: - Same as {option -c}[rdoc-ref:ruby/options.md@c-3A+Check+Syntax]. + Same as {option -c}[rdoc-ref:@c-3A+Check+Syntax]. - `--dump=usage`: - Same as {option -h}[rdoc-ref:ruby/options.md@h-3A+Print+Short+Help+Message]. + Same as {option -h}[rdoc-ref:@h-3A+Print+Short+Help+Message]. - `--dump=version`: Same as {option \-\-version}[options_md.html#label--version-3A+Print+Ruby+Version]. @@ -641,7 +641,7 @@ see {option --disable}[options_md.html#label--disable-3A+Disable+Features]. ### `--encoding`: Alias for `-E`. Option `--encoding` is an alias for -{option -E}[rdoc-ref:ruby/options.md@E-3A+Set+Default+Encodings]. +{option -E}[rdoc-ref:@E-3A+Set+Default+Encodings]. ### `--external-encoding`: Set Default External \Encoding diff --git a/doc/strftime_formatting.rdoc b/doc/strftime_formatting.rdoc index 5c7b33155df9ec..bef343c3073a83 100644 --- a/doc/strftime_formatting.rdoc +++ b/doc/strftime_formatting.rdoc @@ -136,7 +136,7 @@ the only required part is the conversion specifier, so we begin with that. t = Time.now # => 2022-06-29 07:10:20.3230914 -0500 t.strftime('%N') # => "323091400" # Default. - Use {width specifiers}[rdoc-ref:strftime_formatting.rdoc@Width+Specifiers] + Use {width specifiers}[rdoc-ref:@Width+Specifiers] to adjust units: t.strftime('%3N') # => "323" # Milliseconds. @@ -522,6 +522,5 @@ An ISO 8601 combined date and time representation may be any ISO 8601 date and any ISO 8601 time, separated by the letter +T+. -For the relevant +strftime+ formats, see -{Dates}[rdoc-ref:strftime_formatting.rdoc@Dates] -and {Times}[rdoc-ref:strftime_formatting.rdoc@Times] above. +For the relevant +strftime+ formats, see {Dates}[rdoc-ref:@Dates] and +{Times}[rdoc-ref:@Times] above. diff --git a/doc/syntax/assignment.rdoc b/doc/syntax/assignment.rdoc index 68d4ae97be8698..9d91bd92dc00df 100644 --- a/doc/syntax/assignment.rdoc +++ b/doc/syntax/assignment.rdoc @@ -9,7 +9,7 @@ Assignment creates a local variable if the variable was not previously referenced. An assignment expression result is always the assigned value, including -{assignment methods}[rdoc-ref:syntax/assignment.rdoc@Assignment+Methods]. +{assignment methods}[rdoc-ref:@Assignment+Methods]. == Local Variable Names From 422ed7489f80cc037a07b51044eaac19ad0b423b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 18:00:13 +0900 Subject: [PATCH 1330/2435] [ruby/date] [DOC] Remove the name from same file references https://github.com/ruby/date/commit/e41082e068 --- doc/date/calendars.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/date/calendars.rdoc b/doc/date/calendars.rdoc index 4e6fd8334bda50..a2540f1c433b5a 100644 --- a/doc/date/calendars.rdoc +++ b/doc/date/calendars.rdoc @@ -31,7 +31,7 @@ See also {a concrete example here}[rdoc-ref:DateTime@When+should+you+use+DateTim === Argument +start+ Certain methods in class \Date handle differences in the -{Julian and Gregorian calendars}[rdoc-ref:date/calendars.rdoc@Julian+and+Gregorian+Calendars] +{Julian and Gregorian calendars}[rdoc-ref:@Julian+and+Gregorian+Calendars] by accepting an optional argument +start+, whose value may be: - Date::ITALY (the default): the created date is Julian From 4a0e01d7681e72919f1fae7bc9db744e5a3fbe8c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 19:01:16 +0900 Subject: [PATCH 1331/2435] [Misc #21688] Teach RDoc about the toplevel module `Ruby` Re-open the exising module by calling `rb_define_module`. RDoc (`RDoc::Parser::C#do_classes_and_modules`) does not recognize `rb_path2class` as a class/module definition. --- box.c | 2 +- version.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/box.c b/box.c index 0ea74182849269..11b07c1cb86051 100644 --- a/box.c +++ b/box.c @@ -1057,7 +1057,7 @@ Init_Box(void) tmp_dir = system_tmpdir(); tmp_dir_has_dirsep = (strcmp(tmp_dir + (strlen(tmp_dir) - strlen(DIRSEP)), DIRSEP) == 0); - VALUE mRuby = rb_path2class("Ruby"); + VALUE mRuby = rb_define_module("Ruby"); rb_cBox = rb_define_class_under(mRuby, "Box", rb_cModule); rb_define_method(rb_cBox, "initialize", box_initialize, 0); diff --git a/version.c b/version.c index 366ba1b2b5c6b7..c9a219fb1385c1 100644 --- a/version.c +++ b/version.c @@ -132,7 +132,7 @@ Init_version(void) * The constants defined here are aliased in the toplevel with * +RUBY_+ prefix. */ - VALUE mRuby = rb_path2class("Ruby"); + VALUE mRuby = rb_define_module("Ruby"); enum {ruby_patchlevel = RUBY_PATCHLEVEL}; VALUE version = MKSTR(version); From 1738255f6d6c5e9c419e6951a07f59b3a73dfaf5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 19:04:17 +0900 Subject: [PATCH 1332/2435] [Misc #21688] [DOC] Include box.md in Ruby::Box documentation --- box.c | 5 ++--- doc/.document | 4 ++-- doc/{box.md => _box.md} | 0 3 files changed, 4 insertions(+), 5 deletions(-) rename doc/{box.md => _box.md} (100%) diff --git a/box.c b/box.c index 11b07c1cb86051..e2c8f25eda7375 100644 --- a/box.c +++ b/box.c @@ -1047,9 +1047,8 @@ rb_f_dump_classext(VALUE recv, VALUE klass) /* * Document-class: Ruby::Box * - * Ruby::Box is designed to provide separated spaces in a Ruby - * process, to isolate applications and libraries. - * See {Ruby::Box}[rdoc-ref:box.md]. + * :markup: markdown + * :include: doc/_box.md */ void Init_Box(void) diff --git a/doc/.document b/doc/.document index 6e6caa83336322..1feb6934ec358f 100644 --- a/doc/.document +++ b/doc/.document @@ -1,5 +1,5 @@ -*.md -*.rb +[^_]*.md +[^_]*.rb [^_]*.rdoc contributing NEWS diff --git a/doc/box.md b/doc/_box.md similarity index 100% rename from doc/box.md rename to doc/_box.md From 32a4545ee41240cb16344936e7c1c5ed01663059 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 23 Nov 2025 21:46:51 +0900 Subject: [PATCH 1333/2435] CI: Abandon CAPI check on macos-15 `hashFiles` is very unstable on macOS runners. --- .github/workflows/macos.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9bc6d83220737b..941645e4eac6f5 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -45,7 +45,6 @@ jobs: os: macos-14 - test_task: check os: macos-15 - extra_checks: [capi] fail-fast: false env: From 190b017fc6c21ff7b61c2b5ece0294785e4a4ca2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 21 Nov 2025 17:26:08 -0500 Subject: [PATCH 1334/2435] Don't use non blocking pipes for RUBY_CRASH_REPORT [Bug #21703] RUBY_CRASH_REPORT does not work in some cases when shelling out on Linux. For example, given the following shell script dump.sh: #!/usr/bin/env bash cat > /tmp/crash And we see it fails like this: $ RUBY_CRASH_REPORT="|dump.sh" ruby -rfiddle -e "Fiddle::Pointer.new(1, 10)[0]" cat: -: Resource temporarily unavailable --- io.c | 7 ++++++- test/ruby/test_rubyoptions.rb | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/io.c b/io.c index 2605207f2bb49a..fdd6256ff854dd 100644 --- a/io.c +++ b/io.c @@ -8079,7 +8079,12 @@ ruby_popen_writer(char *const *argv, rb_pid_t *pid) int write_pair[2]; # endif - int result = rb_cloexec_pipe(write_pair); +#ifdef HAVE_PIPE2 + int result = pipe2(write_pair, O_CLOEXEC); +#else + int result = pipe(write_pair); +#endif + *pid = -1; if (result == 0) { # ifdef HAVE_WORKING_FORK diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 527208cee5219d..a057e64a4a3596 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -954,6 +954,27 @@ def test_crash_report_pipe end end + def test_crash_report_pipe_script + omit "only runs on Linux" unless RUBY_PLATFORM.include?("linux") + + Tempfile.create(["script", ".sh"]) do |script| + Tempfile.create("crash_report") do |crash_report| + script.write(<<~BASH) + #!/usr/bin/env bash + + cat > #{crash_report.path} + BASH + script.close + + FileUtils.chmod("+x", script) + + assert_crash_report("| #{script.path}") do + assert_include(File.read(crash_report.path), "[BUG] Segmentation fault at") + end + end + end + end + def test_DATA Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| t.puts "puts DATA.read.inspect" From 2870b7d7df30bc227f40a4c04c97050b90f1f25b Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:33:55 +0100 Subject: [PATCH 1335/2435] [ruby/prism] Reject `p(p a, &block => value)` and similar They were being parsed as `p((p a, &block) => value)`. When we get to this point, we must not just have parsed a command call, always consuming the `=>` is not correct. Closes [Bug #21622] https://github.com/ruby/prism/commit/796ab0edf4 --- prism/prism.c | 21 ++++++++++- test/prism/errors/command_calls_35.txt | 41 +++++++++++++++++++++ test/prism/fixtures/command_method_call.txt | 6 +++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/prism/errors/command_calls_35.txt diff --git a/prism/prism.c b/prism/prism.c index 45817cdd8c05b2..38f3a9c6ef0f41 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14232,6 +14232,25 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod return contains_keyword_splat; } +static inline bool +argument_allowed_for_bare_hash(pm_parser_t *parser, pm_node_t *argument) { + if (pm_symbol_node_label_p(argument)) { + return true; + } + + switch (PM_NODE_TYPE(argument)) { + case PM_CALL_NODE: { + pm_call_node_t *cast = (pm_call_node_t *) argument; + if (cast->opening_loc.start == NULL && cast->arguments != NULL) { + return false; + } + break; + } + default: break; + } + return accept1(parser, PM_TOKEN_EQUAL_GREATER); +} + /** * Append an argument to a list of arguments. */ @@ -14389,7 +14408,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for bool contains_keywords = false; bool contains_keyword_splat = false; - if (pm_symbol_node_label_p(argument) || accept1(parser, PM_TOKEN_EQUAL_GREATER)) { + if (argument_allowed_for_bare_hash(parser, argument)){ if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_BARE_HASH); } diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt new file mode 100644 index 00000000000000..9eb011cd86ca61 --- /dev/null +++ b/test/prism/errors/command_calls_35.txt @@ -0,0 +1,41 @@ +p(p a, x: b => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, x: => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, &block => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, *args => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, **kwargs => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p p 1, &block => 2, &block + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + ^ unexpected '&', ignoring it + +p p p 1 => 2 => 3 => 4 + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + +p[p a, x: b => value] + ^ expected a matching `]` + ^ unexpected ']', expecting end-of-input + ^ unexpected ']', ignoring it + diff --git a/test/prism/fixtures/command_method_call.txt b/test/prism/fixtures/command_method_call.txt index 182b87948b52d5..3f510efa6935d6 100644 --- a/test/prism/fixtures/command_method_call.txt +++ b/test/prism/fixtures/command_method_call.txt @@ -39,3 +39,9 @@ def foo = bar 1 !foo 1 or !bar 2 not !foo 1 + +foo(bar baz, key => value) + +foo(bar baz, KEY => value) + +foo(bar baz, :key => value) From 026140b3a209ee2608241a91bb7728a429285685 Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Thu, 20 Nov 2025 11:31:06 +0000 Subject: [PATCH 1336/2435] [ruby/prism] Avoid reading out-of-bounds in pm_strnstr Fixes https://github.com/ruby/prism/pull/3738. https://github.com/ruby/prism/commit/37bb46ff5f --- prism/prism.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 38f3a9c6ef0f41..a5475b17530907 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22639,7 +22639,7 @@ static const char * pm_strnstr(const char *big, const char *little, size_t big_length) { size_t little_length = strlen(little); - for (const char *big_end = big + big_length; big < big_end; big++) { + for (const char *max = big + big_length - little_length; big <= max; big++) { if (*big == *little && memcmp(big, little, little_length) == 0) return big; } From 390220579b7afe433731f4d2d119a57858e27911 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 23 Nov 2025 12:47:33 -0500 Subject: [PATCH 1337/2435] Replace vfork with fork for ASAN Older versions of ASAN do not support vfork. See https://github.com/google/sanitizers/issues/925 --- process.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/process.c b/process.c index 46c55abcce42c6..8d6953282aaa41 100644 --- a/process.c +++ b/process.c @@ -4011,7 +4011,10 @@ retry_fork_async_signal_safe(struct rb_process_status *status, int *ep, while (1) { prefork(); disable_child_handler_before_fork(&old); -#ifdef HAVE_WORKING_VFORK + + // Older versions of ASAN does not work with vfork + // See https://github.com/google/sanitizers/issues/925 +#if defined(HAVE_WORKING_VFORK) && !defined(RUBY_ASAN_ENABLED) if (!has_privilege()) pid = vfork(); else From 8d73a181879d3151bb5e7ecf928ec098d154d498 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 23 Nov 2025 13:58:50 -0500 Subject: [PATCH 1338/2435] [ruby/prism] Handle destroying implicit parameter Fixes https://github.com/ruby/prism/pull/3740 https://github.com/ruby/prism/commit/464a849184 --- prism/prism.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index a5475b17530907..2e0c941918bd49 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13849,6 +13849,18 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // syntax error. In this case we'll fall through to our default // handling. We need to free the value that we parsed because there // is no way for us to attach it to the tree at this point. + switch (PM_NODE_TYPE(value)) { + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_IT_LOCAL_VARIABLE_READ_NODE: + // Since it is possible for the value to be an implicit + // parameter, we need to remove it from the list of implicit + // parameters. + parse_target_implicit_parameter(parser, value); + break; + default: + break; + } + pm_node_destroy(parser, value); } PRISM_FALLTHROUGH From b75cf83a333ab4e321e89cdefb94e684f6648033 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 23 Nov 2025 16:35:17 -0500 Subject: [PATCH 1339/2435] [ruby/prism] Revert "Reject `p(p a, &block => value)` and similar" https://github.com/ruby/prism/commit/fef2c20777 --- prism/prism.c | 21 +---------- test/prism/errors/command_calls_35.txt | 41 --------------------- test/prism/fixtures/command_method_call.txt | 6 --- 3 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 test/prism/errors/command_calls_35.txt diff --git a/prism/prism.c b/prism/prism.c index 2e0c941918bd49..186cdd354c9843 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14244,25 +14244,6 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod return contains_keyword_splat; } -static inline bool -argument_allowed_for_bare_hash(pm_parser_t *parser, pm_node_t *argument) { - if (pm_symbol_node_label_p(argument)) { - return true; - } - - switch (PM_NODE_TYPE(argument)) { - case PM_CALL_NODE: { - pm_call_node_t *cast = (pm_call_node_t *) argument; - if (cast->opening_loc.start == NULL && cast->arguments != NULL) { - return false; - } - break; - } - default: break; - } - return accept1(parser, PM_TOKEN_EQUAL_GREATER); -} - /** * Append an argument to a list of arguments. */ @@ -14420,7 +14401,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for bool contains_keywords = false; bool contains_keyword_splat = false; - if (argument_allowed_for_bare_hash(parser, argument)){ + if (pm_symbol_node_label_p(argument) || accept1(parser, PM_TOKEN_EQUAL_GREATER)) { if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_BARE_HASH); } diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt deleted file mode 100644 index 9eb011cd86ca61..00000000000000 --- a/test/prism/errors/command_calls_35.txt +++ /dev/null @@ -1,41 +0,0 @@ -p(p a, x: b => value) - ^~ unexpected '=>'; expected a `)` to close the arguments - ^ unexpected ')', expecting end-of-input - ^ unexpected ')', ignoring it - -p(p a, x: => value) - ^~ unexpected '=>'; expected a `)` to close the arguments - ^ unexpected ')', expecting end-of-input - ^ unexpected ')', ignoring it - -p(p a, &block => value) - ^~ unexpected '=>'; expected a `)` to close the arguments - ^ unexpected ')', expecting end-of-input - ^ unexpected ')', ignoring it - -p(p a, *args => value) - ^~ unexpected '=>'; expected a `)` to close the arguments - ^ unexpected ')', expecting end-of-input - ^ unexpected ')', ignoring it - -p(p a, **kwargs => value) - ^~ unexpected '=>'; expected a `)` to close the arguments - ^ unexpected ')', expecting end-of-input - ^ unexpected ')', ignoring it - -p p 1, &block => 2, &block - ^~ unexpected '=>', expecting end-of-input - ^~ unexpected '=>', ignoring it - ^ unexpected ',', expecting end-of-input - ^ unexpected ',', ignoring it - ^ unexpected '&', ignoring it - -p p p 1 => 2 => 3 => 4 - ^~ unexpected '=>', expecting end-of-input - ^~ unexpected '=>', ignoring it - -p[p a, x: b => value] - ^ expected a matching `]` - ^ unexpected ']', expecting end-of-input - ^ unexpected ']', ignoring it - diff --git a/test/prism/fixtures/command_method_call.txt b/test/prism/fixtures/command_method_call.txt index 3f510efa6935d6..182b87948b52d5 100644 --- a/test/prism/fixtures/command_method_call.txt +++ b/test/prism/fixtures/command_method_call.txt @@ -39,9 +39,3 @@ def foo = bar 1 !foo 1 or !bar 2 not !foo 1 - -foo(bar baz, key => value) - -foo(bar baz, KEY => value) - -foo(bar baz, :key => value) From 45fcb9d590d36c469e7fa18790fafd51809943ee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 24 Nov 2025 17:30:47 +0900 Subject: [PATCH 1340/2435] Adjust indent [ci skip] --- process.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/process.c b/process.c index 8d6953282aaa41..e2ca6612d2d674 100644 --- a/process.c +++ b/process.c @@ -4012,8 +4012,8 @@ retry_fork_async_signal_safe(struct rb_process_status *status, int *ep, prefork(); disable_child_handler_before_fork(&old); - // Older versions of ASAN does not work with vfork - // See https://github.com/google/sanitizers/issues/925 + // Older versions of ASAN does not work with vfork + // See https://github.com/google/sanitizers/issues/925 #if defined(HAVE_WORKING_VFORK) && !defined(RUBY_ASAN_ENABLED) if (!has_privilege()) pid = vfork(); From aeb7689e696540f3f96bad87efc91ba2b4187c99 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 19 Aug 2024 15:03:43 -0700 Subject: [PATCH 1341/2435] [ruby/forwardable] Use generic argument forwarding (...) instead of ruby2_keywords on Ruby 2.7+ On Ruby 3.4+, generic argument forwarding is significantly faster as it does not allocate. https://github.com/ruby/forwardable/commit/b606c3bf0a --- lib/forwardable.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 5652e728642906..76267c2cd18c57 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -192,9 +192,7 @@ def def_instance_delegator(accessor, method, ali = method) # If it's not a class or module, it's an instance mod = Module === self ? self : singleton_class - ret = mod.module_eval(&gen) - mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + mod.module_eval(&gen) end alias delegate instance_delegate @@ -211,7 +209,8 @@ def self._delegator_method(obj, accessor, method, ali) accessor = "#{accessor}()" end - method_call = ".__send__(:#{method}, *args, &block)" + args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' + method_call = ".__send__(:#{method}, #{args})" if _valid_method?(method) loc, = caller_locations(2,1) pre = "_ =" @@ -222,7 +221,7 @@ def self._delegator_method(obj, accessor, method, ali) ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 _#{method_call} else - _.#{method}(*args, &block) + _.#{method}(#{args}) end end; end @@ -230,7 +229,7 @@ def self._delegator_method(obj, accessor, method, ali) _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) begin; proc do - def #{ali}(*args, &block) + def #{ali}(#{args}) #{pre} begin #{accessor} @@ -312,9 +311,7 @@ def def_single_delegators(accessor, *methods) def def_single_delegator(accessor, method, ali = method) gen = Forwardable._delegator_method(self, accessor, method, ali) - ret = instance_eval(&gen) - singleton_class.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + instance_eval(&gen) end alias delegate single_delegate From 8a68dc7bdd3d1c97677a6633a4f2b5e524c492ae Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 12:28:30 -0800 Subject: [PATCH 1342/2435] Revert "Fix stdatomic case in `rbimpl_atomic_u64_fetch_add`" (#15311) This reverts commit d3b6f835d565ec1590059773fc87589ddf8adc37. This broke the Docker builds and presumably also 32-bit machines that don't already have libatomic installed. --- configure.ac | 1 - ruby_atomic.h | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 1e406ec56cff8a..339ee3b2f2e66e 100644 --- a/configure.ac +++ b/configure.ac @@ -1746,7 +1746,6 @@ AS_IF([test "$GCC" = yes], [ [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS) - AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include uint64_t atomic_var;]], diff --git a/ruby_atomic.h b/ruby_atomic.h index 3a541d92082824..c194f7ec3b82fc 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -2,9 +2,6 @@ #define INTERNAL_ATOMIC_H #include "ruby/atomic.h" -#ifdef HAVE_STDATOMIC_H -# include -#endif #define RUBY_ATOMIC_VALUE_LOAD(x) rbimpl_atomic_value_load(&(x), RBIMPL_ATOMIC_SEQ_CST) @@ -79,9 +76,9 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_add_64_nv(ptr, val) - val; -#elif defined(HAVE_STDATOMIC_H) - return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); #else + // TODO: stdatomic + // Fallback using mutex for platforms without 64-bit atomics static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; rb_native_mutex_lock(&lock); From 2315349b8ac10b1b4468ed10dabcb348a10bca0a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 23 Nov 2025 17:34:44 -0500 Subject: [PATCH 1343/2435] Handle SIGABRT and output bug report SIGABRT is for abnormal termination so we should handle it to output a bug report. Specifically, glibc malloc uses it to exit when there is corruption. For example, the following script produces a double free: mem = Fiddle.malloc(10) Fiddle.free(mem) Fiddle.free(mem) Before this patch, it just outputs the following and exits: free(): double free detected in tcache 2 After this patch, it also outputs a bug report: free(): double free detected in tcache 2 test.rb:11: [BUG] Aborted at 0x000003e8000ab65c --- signal.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/signal.c b/signal.c index f6b62b30147d87..1e2b0f613248c2 100644 --- a/signal.c +++ b/signal.c @@ -949,6 +949,20 @@ sigsegv(int sig SIGINFO_ARG) } #endif +#ifdef SIGABRT + +static sighandler_t default_sigabrt_handler; +NORETURN(static ruby_sigaction_t sigabrt); + +static void +sigabrt(int sig SIGINFO_ARG) +{ + check_reserved_signal("ABRT"); + CHECK_STACK_OVERFLOW(); + rb_bug_for_fatal_signal(default_sigabrt_handler, sig, SIGINFO_CTX, "Aborted" MESSAGE_FAULT_ADDRESS); +} +#endif + #ifdef SIGILL static sighandler_t default_sigill_handler; @@ -1558,6 +1572,10 @@ Init_signal(void) RB_ALTSTACK_INIT(GET_VM()->main_altstack, rb_allocate_sigaltstack()); force_install_sighandler(SIGSEGV, (sighandler_t)sigsegv, &default_sigsegv_handler); #endif + +#ifdef SIGABRT + force_install_sighandler(SIGABRT, (sighandler_t)sigabrt, &default_sigabrt_handler); +#endif } #ifdef SIGPIPE install_sighandler(SIGPIPE, sig_do_nothing); From 35445a736f509e6edbba8a08d8e4af69c206368a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 23 Nov 2025 20:36:52 -0500 Subject: [PATCH 1344/2435] Add SIGABRT to reserved signals in bundler spec --- spec/bundler/commands/exec_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 1ac308bdda4bfc..19e836053fa141 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -1020,7 +1020,7 @@ def bin_path(a,b,c) context "signal handling" do let(:test_signals) do open3_reserved_signals = %w[CHLD CLD PIPE] - reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT] + reserved_signals = %w[SEGV BUS ILL FPE ABRT IOT VTALRM KILL STOP EXIT] bundler_signals = %w[INT] Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals) From 58faaf11dfeed157bf57987661beed29cd69673f Mon Sep 17 00:00:00 2001 From: Yuji Yaginuma Date: Sun, 23 Nov 2025 17:09:01 +0900 Subject: [PATCH 1345/2435] [ruby/uri] [DOC] Fix result of sample code in `#user=` A `password` is cleared when change a user now. https://github.com/ruby/uri/commit/af6714473c --- lib/uri/generic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 6fd0f7c42075c6..6a0f638d764173 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -466,7 +466,7 @@ def userinfo=(userinfo) # # uri = URI.parse("http://john:S3nsit1ve@my.example.com") # uri.user = "sam" - # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" + # uri.to_s #=> "http://sam@my.example.com" # def user=(user) check_user(user) From 86b210203e5e54a70782fd050ca9826eb23e9c7e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 24 Nov 2025 21:26:57 -0500 Subject: [PATCH 1346/2435] Fix style for rb_gc_impl_before_fork --- gc/default/default.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 3d40b3dddfdbf8..83a8e858f4fef2 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -9321,7 +9321,12 @@ gc_malloc_allocations(VALUE self) } #endif -void rb_gc_impl_before_fork(void *objspace_ptr) { /* no-op */ } +void +rb_gc_impl_before_fork(void *objspace_ptr) +{ + /* no-op */ +} + void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid) { if (pid == 0) { /* child process */ rb_gc_ractor_newobj_cache_foreach(gc_ractor_newobj_cache_clear, NULL); From 55892f5994f66567aa4c0c56fdb8bf31134c3972 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 24 Nov 2025 21:27:17 -0500 Subject: [PATCH 1347/2435] Fix style for rb_gc_impl_after_fork --- gc/default/default.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 83a8e858f4fef2..b6a9dfa1f57d9d 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -9327,7 +9327,9 @@ rb_gc_impl_before_fork(void *objspace_ptr) /* no-op */ } -void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid) { +void +rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid) +{ if (pid == 0) { /* child process */ rb_gc_ractor_newobj_cache_foreach(gc_ractor_newobj_cache_clear, NULL); } From f8ee06901cfec2ebb7340087f039b103e8ab51b3 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 20 Nov 2025 17:25:53 -0500 Subject: [PATCH 1348/2435] ZJIT: For JIT-to-JIT send, avoid loading uninitialized local through EP JIT-to-JIT sends don't blit locals to nil in the callee's EP memory region because HIR is aware of this initial state and memory ops are only done when necessary. Previously, we read from this initialized memory by emitting `GetLocal` in e.g. BBs that are immediate successor to an entrypoint. The entry points sets up the frame state properly and we also reload locals if necessary after an operation that potentially makes the environment escape. So, listen to the frame state when it's supposed to be up-to-date (`!local_inval`). --- zjit/src/hir.rs | 21 ++++++++----- zjit/src/hir/opt_tests.rs | 62 +++++++++++++++++++++++++++++++++++---- zjit/src/hir/tests.rs | 7 ++--- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d6ccf9160e7eeb..dd7bc953b9219e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5230,19 +5230,24 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_getlocal_WC_0 => { let ep_offset = get_arg(pc, 0).as_u32(); - if ep_escaped || has_blockiseq { // TODO: figure out how to drop has_blockiseq here + if !local_inval { + // The FrameState is the source of truth for locals until invalidated. + // In case of JIT-to-JIT send locals might never end up in EP memory. + let val = state.getlocal(ep_offset); + state.stack_push(val); + } else if ep_escaped || has_blockiseq { // TODO: figure out how to drop has_blockiseq here // Read the local using EP let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0, use_sp: false, rest_param: false }); state.setlocal(ep_offset, val); // remember the result to spill on side-exits state.stack_push(val); } else { - if local_inval { - // If there has been any non-leaf call since JIT entry or the last patch point, - // add a patch point to make sure locals have not been escaped. - let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.without_locals() }); // skip spilling locals - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: exit_id }); - local_inval = false; - } + assert!(local_inval); // if check above + // There has been some non-leaf call since JIT entry or the last patch point, + // so add a patch point to make sure locals have not been escaped. + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.without_locals() }); // skip spilling locals + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: exit_id }); + local_inval = false; + // Read the local from FrameState let val = state.getlocal(ep_offset); state.stack_push(val); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c99f01d088ef86..866d0ec06dcf23 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -780,14 +780,13 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v13:BasicObject = GetLocal l0, EP@3 PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) - v24:ArraySubclass[class_exact:C] = GuardType v13, ArraySubclass[class_exact:C] - v25:BasicObject = CCallWithFrame C#fun_new_map@0x1038, v24, block=0x1040 - v16:BasicObject = GetLocal l0, EP@3 + v23:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C] + v24:BasicObject = CCallWithFrame C#fun_new_map@0x1038, v23, block=0x1040 + v15:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v25 + Return v24 "); } @@ -8425,4 +8424,57 @@ mod hir_opt_tests { Return v32 "); } + + #[test] + fn no_load_from_ep_right_after_entrypoint() { + let formatted = eval(" + def read_nil_local(a, _b, _c) + formatted ||= a + @formatted = formatted + -> { formatted } # the environment escapes + end + + def call + puts [], [], [], [] # fill VM stack with junk + read_nil_local(true, 1, 1) # expected direct send + end + + call # profile + call # compile + @formatted + "); + assert_eq!(Qtrue, formatted, "{}", formatted.obj_info()); + assert_snapshot!(hir_string("read_nil_local"), @r" + fn read_nil_local@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:BasicObject = GetLocal l0, SP@5 + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject): + EntryPoint JIT(0) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:NilClass): + CheckInterrupts + v27:BasicObject = GetLocal l0, EP@6 + SetLocal l0, EP@3, v27 + v39:BasicObject = GetLocal l0, EP@3 + PatchPoint SingleRactorMode + SetIvar v14, :@formatted, v39 + v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Class@0x1008) + v59:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 + v48:BasicObject = GetLocal l0, EP@6 + v49:BasicObject = GetLocal l0, EP@5 + v50:BasicObject = GetLocal l0, EP@4 + v51:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v59 + "); + } } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 76a74c75eb4613..f8a6abc2bcbc86 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1500,11 +1500,10 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v13:BasicObject = GetLocal l0, EP@3 - v15:BasicObject = Send v13, 0x1000, :each - v16:BasicObject = GetLocal l0, EP@3 + v14:BasicObject = Send v9, 0x1000, :each + v15:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v15 + Return v14 "); } From bbf4bde75e4c8d2eacc40fc5e544ee73b20cf586 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Nov 2025 11:59:50 +0900 Subject: [PATCH 1349/2435] Reapply "Fix stdatomic case in `rbimpl_atomic_u64_fetch_add`" This reverts commit 8a68dc7bdd3d1c97677a6633a4f2b5e524c492ae. --- configure.ac | 1 + ruby_atomic.h | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 339ee3b2f2e66e..1e406ec56cff8a 100644 --- a/configure.ac +++ b/configure.ac @@ -1746,6 +1746,7 @@ AS_IF([test "$GCC" = yes], [ [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS) + AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include uint64_t atomic_var;]], diff --git a/ruby_atomic.h b/ruby_atomic.h index c194f7ec3b82fc..3a541d92082824 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -2,6 +2,9 @@ #define INTERNAL_ATOMIC_H #include "ruby/atomic.h" +#ifdef HAVE_STDATOMIC_H +# include +#endif #define RUBY_ATOMIC_VALUE_LOAD(x) rbimpl_atomic_value_load(&(x), RBIMPL_ATOMIC_SEQ_CST) @@ -76,9 +79,9 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_add_64_nv(ptr, val) - val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); #else - // TODO: stdatomic - // Fallback using mutex for platforms without 64-bit atomics static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; rb_native_mutex_lock(&lock); From 2957ba7079c58eb18eb3d7b6b5392fa4aa88a845 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Nov 2025 12:12:55 +0900 Subject: [PATCH 1350/2435] Try libatomic only if necessary --- configure.ac | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index 1e406ec56cff8a..9120fc25ecddf4 100644 --- a/configure.ac +++ b/configure.ac @@ -1746,19 +1746,24 @@ AS_IF([test "$GCC" = yes], [ [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS) - AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) - AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ - AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include - uint64_t atomic_var;]], - [[ - __atomic_load_n(&atomic_var, __ATOMIC_RELAXED); - __atomic_store_n(&atomic_var, 0, __ATOMIC_RELAXED); - ]])], - [rb_cv_gcc_atomic_builtins_64=yes], - [rb_cv_gcc_atomic_builtins_64=no])]) - AS_IF([test "$rb_cv_gcc_atomic_builtins_64" = yes], [ - AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS_64) - ]) + for lib in "" atomic; do + AS_IF([test "$lib" != ""], [ + AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) + ]) + AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include + uint64_t atomic_var;]], + [[ + __atomic_load_n(&atomic_var, __ATOMIC_RELAXED); + __atomic_store_n(&atomic_var, 0, __ATOMIC_RELAXED); + ]])], + [rb_cv_gcc_atomic_builtins_64=yes], + [rb_cv_gcc_atomic_builtins_64=no])]) + AS_IF([test "$rb_cv_gcc_atomic_builtins_64" = yes], [ + AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS_64) + break + ]) + done ]) AC_CACHE_CHECK([for __sync builtins], [rb_cv_gcc_sync_builtins], [ From ec05bc39a3e8340aa3dcfc5f6f9ed791af232e78 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Nov 2025 12:13:47 +0900 Subject: [PATCH 1351/2435] Fix the fallback using mutex --- ruby_atomic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby_atomic.h b/ruby_atomic.h index 3a541d92082824..b60c56cfd89048 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -83,7 +83,7 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); #else // Fallback using mutex for platforms without 64-bit atomics - static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER; + static rb_nativethread_mutex_t lock = RB_NATIVETHREAD_LOCK_INIT; rb_native_mutex_lock(&lock); uint64_t old = *ptr; *ptr = old + val; From 7ba986383cf15f84ce6914b06f8d6b5f26017033 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Nov 2025 13:53:39 +0900 Subject: [PATCH 1352/2435] CI: Add timeout to compilations [ci skip] --- .github/workflows/compilers.yml | 180 ++++++++++++++++---------------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index ffddbe81e3b263..b841b4c18be9f0 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -62,7 +62,8 @@ jobs: with_gcc: 'clang-18 -flto=auto' optflags: '-O2' enable_shared: false - - { uses: './.github/actions/compilers', name: '-O0', with: { optflags: '-O0 -march=x86-64 -mtune=generic' } } + timeout-minutes: 30 + - { uses: './.github/actions/compilers', name: '-O0', with: { optflags: '-O0 -march=x86-64 -mtune=generic' }, timeout-minutes: 5 } # - { uses: './.github/actions/compilers', name: '-O3', with: { optflags: '-O3 -march=x86-64 -mtune=generic', check: true } } compile2: @@ -83,16 +84,17 @@ jobs: with_gcc: 'gcc-15 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' optflags: '-O2' enable_shared: false - - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' } } - - { uses: './.github/actions/compilers', name: 'GCC 15', with: { tag: 'gcc-15' } } - - { uses: './.github/actions/compilers', name: 'GCC 14', with: { tag: 'gcc-14' } } - - { uses: './.github/actions/compilers', name: 'GCC 13', with: { tag: 'gcc-13' } } - - { uses: './.github/actions/compilers', name: 'GCC 12', with: { tag: 'gcc-12' } } - - { uses: './.github/actions/compilers', name: 'GCC 11', with: { tag: 'gcc-11' } } - - { uses: './.github/actions/compilers', name: 'GCC 10', with: { tag: 'gcc-10' } } - - { uses: './.github/actions/compilers', name: 'GCC 9', with: { tag: 'gcc-9' } } - - { uses: './.github/actions/compilers', name: 'GCC 8', with: { tag: 'gcc-8' } } - - { uses: './.github/actions/compilers', name: 'GCC 7', with: { tag: 'gcc-7' } } + timeout-minutes: 10 + - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 15', with: { tag: 'gcc-15' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 14', with: { tag: 'gcc-14' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 13', with: { tag: 'gcc-13' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 12', with: { tag: 'gcc-12' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 11', with: { tag: 'gcc-11' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 10', with: { tag: 'gcc-10' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 9', with: { tag: 'gcc-9' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 8', with: { tag: 'gcc-8' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GCC 7', with: { tag: 'gcc-7' }, timeout-minutes: 5 } compile3: name: 'omnibus compilations, #3' @@ -105,16 +107,16 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'clang 22', with: { tag: 'clang-22' } } - - { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' } } - - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' } } - - { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' } } + - { uses: './.github/actions/compilers', name: 'clang 22', with: { tag: 'clang-22' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' }, timeout-minutes: 5 } # clang-18 has a bug causing ruby_current_ec to sometimes be null - # - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } } - - { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' } } - - { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' } } - - { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' } } - - { uses: './.github/actions/compilers', name: 'clang 14', with: { tag: 'clang-14' } } + # - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 14', with: { tag: 'clang-14' }, timeout-minutes: 5 } compile4: name: 'omnibus compilations, #4' @@ -127,15 +129,15 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' } } - - { uses: './.github/actions/compilers', name: 'clang 12', with: { tag: 'clang-12' } } - - { uses: './.github/actions/compilers', name: 'clang 11', with: { tag: 'clang-11' } } - - { uses: './.github/actions/compilers', name: 'clang 10', with: { tag: 'clang-10' } } + - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 12', with: { tag: 'clang-12' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 11', with: { tag: 'clang-11' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 10', with: { tag: 'clang-10' }, timeout-minutes: 5 } # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o. - - { uses: './.github/actions/compilers', name: 'clang 9', with: { tag: 'clang-9', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 8', with: { tag: 'clang-8', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 7', with: { tag: 'clang-7', append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'clang 6', with: { tag: 'clang-6.0', append_configure: '--disable-yjit' } } + - { uses: './.github/actions/compilers', name: 'clang 9', with: { tag: 'clang-9', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 8', with: { tag: 'clang-8', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 7', with: { tag: 'clang-7', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 6', with: { tag: 'clang-6.0', append_configure: '--disable-yjit' }, timeout-minutes: 5 } compile5: name: 'omnibus compilations, #5' @@ -154,14 +156,14 @@ jobs: # warning generates a lot of noise from use of ANYARGS in # rb_define_method() and friends. # See: https://github.com/llvm/llvm-project/commit/11da1b53d8cd3507959022cd790d5a7ad4573d94 - - { uses: './.github/actions/compilers', name: 'C99', with: { CFLAGS: '-std=c99 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C11', with: { CFLAGS: '-std=c11 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C17', with: { CFLAGS: '-std=c17 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C23', with: { CFLAGS: '-std=c2x -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } - - { uses: './.github/actions/compilers', name: 'C++98', with: { CXXFLAGS: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++11', with: { CXXFLAGS: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++14', with: { CXXFLAGS: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++17', with: { CXXFLAGS: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } + - { uses: './.github/actions/compilers', name: 'C99', with: { CFLAGS: '-std=c99 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C11', with: { CFLAGS: '-std=c11 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C17', with: { CFLAGS: '-std=c17 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C23', with: { CFLAGS: '-std=c2x -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++98', with: { CXXFLAGS: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++11', with: { CXXFLAGS: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++14', with: { CXXFLAGS: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++17', with: { CXXFLAGS: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } compile6: name: 'omnibus compilations, #6' @@ -174,14 +176,14 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++23', with: { CXXFLAGS: '-std=c++23 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'C++26', with: { CXXFLAGS: '-std=c++26 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } - - { uses: './.github/actions/compilers', name: 'gmp', with: { append_configure: '--with-gmp', test_all: 'ruby/test_bignum.rb', test_spec: "/github/workspace/src/spec/ruby/core/integer" } } - - { uses: './.github/actions/compilers', name: 'jemalloc', with: { append_configure: '--with-jemalloc' } } - - { uses: './.github/actions/compilers', name: 'valgrind', with: { append_configure: '--with-valgrind' } } - - { uses: './.github/actions/compilers', name: 'coroutine=ucontext', with: { append_configure: '--with-coroutine=ucontext' } } - - { uses: './.github/actions/compilers', name: 'coroutine=pthread', with: { append_configure: '--with-coroutine=pthread' } } + - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++23', with: { CXXFLAGS: '-std=c++23 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'C++26', with: { CXXFLAGS: '-std=c++26 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'gmp', with: { append_configure: '--with-gmp', test_all: 'ruby/test_bignum.rb', test_spec: "/github/workspace/src/spec/ruby/core/integer" }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'jemalloc', with: { append_configure: '--with-jemalloc' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'valgrind', with: { append_configure: '--with-valgrind' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'coroutine=ucontext', with: { append_configure: '--with-coroutine=ucontext' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'coroutine=pthread', with: { append_configure: '--with-coroutine=pthread' }, timeout-minutes: 5 } compile7: name: 'omnibus compilations, #7' @@ -194,16 +196,16 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' } } - - { uses: './.github/actions/compilers', name: 'disable-yjit', with: { append_configure: '--disable-yjit' } } - - { uses: './.github/actions/compilers', name: 'disable-zjit', with: { append_configure: '--disable-zjit' } } - - { uses: './.github/actions/compilers', name: 'disable-dln', with: { append_configure: '--disable-dln' } } - - { uses: './.github/actions/compilers', name: 'enable-mkmf-verbose', with: { append_configure: '--enable-mkmf-verbose' } } - - { uses: './.github/actions/compilers', name: 'disable-rubygems', with: { append_configure: '--disable-rubygems' } } - - { uses: './.github/actions/compilers', name: 'RUBY_DEVEL', with: { append_configure: '--enable-devel' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=0', with: { cppflags: '-DOPT_THREADED_CODE=0' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=1', with: { cppflags: '-DOPT_THREADED_CODE=1' } } - - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=2', with: { cppflags: '-DOPT_THREADED_CODE=2' } } + - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'disable-yjit', with: { append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'disable-zjit', with: { append_configure: '--disable-zjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'disable-dln', with: { append_configure: '--disable-dln' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'enable-mkmf-verbose', with: { append_configure: '--enable-mkmf-verbose' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'disable-rubygems', with: { append_configure: '--disable-rubygems' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RUBY_DEVEL', with: { append_configure: '--enable-devel' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=0', with: { cppflags: '-DOPT_THREADED_CODE=0' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=1', with: { cppflags: '-DOPT_THREADED_CODE=1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'OPT_THREADED_CODE=2', with: { cppflags: '-DOPT_THREADED_CODE=2' }, timeout-minutes: 5 } compile8: name: 'omnibus compilations, #8' @@ -216,14 +218,14 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' } } - - { uses: './.github/actions/compilers', name: 'RUBY_DEBUG', with: { cppflags: '-DRUBY_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'ARRAY_DEBUG', with: { cppflags: '-DARRAY_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'BIGNUM_DEBUG', with: { cppflags: '-DBIGNUM_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'CCAN_LIST_DEBUG', with: { cppflags: '-DCCAN_LIST_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'CPDEBUG=-1', with: { cppflags: '-DCPDEBUG=-1' } } - - { uses: './.github/actions/compilers', name: 'ENC_DEBUG', with: { cppflags: '-DENC_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'GC_DEBUG', with: { cppflags: '-DGC_DEBUG' } } + - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RUBY_DEBUG', with: { cppflags: '-DRUBY_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'ARRAY_DEBUG', with: { cppflags: '-DARRAY_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'BIGNUM_DEBUG', with: { cppflags: '-DBIGNUM_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'CCAN_LIST_DEBUG', with: { cppflags: '-DCCAN_LIST_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'CPDEBUG=-1', with: { cppflags: '-DCPDEBUG=-1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'ENC_DEBUG', with: { cppflags: '-DENC_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GC_DEBUG', with: { cppflags: '-DGC_DEBUG' }, timeout-minutes: 5 } compile9: name: 'omnibus compilations, #9' @@ -236,14 +238,14 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'ID_TABLE_DEBUG', with: { cppflags: '-DID_TABLE_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_DEBUG=-1', with: { cppflags: '-DRGENGC_DEBUG=-1' } } - - { uses: './.github/actions/compilers', name: 'SYMBOL_DEBUG', with: { cppflags: '-DSYMBOL_DEBUG' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' } } - - { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' } } - - { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' } } - - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' } } + - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'ID_TABLE_DEBUG', with: { cppflags: '-DID_TABLE_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RGENGC_DEBUG=-1', with: { cppflags: '-DRGENGC_DEBUG=-1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'SYMBOL_DEBUG', with: { cppflags: '-DSYMBOL_DEBUG' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' }, timeout-minutes: 5 } compileX: name: 'omnibus compilations, #10' @@ -256,14 +258,14 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' } } - - { uses: './.github/actions/compilers', name: 'USE_SYMBOL_GC=0', with: { cppflags: '-DUSE_SYMBOL_GC=0' } } - - { uses: './.github/actions/compilers', name: 'USE_THREAD_CACHE=0', with: { cppflags: '-DUSE_THREAD_CACHE=0' } } - - { uses: './.github/actions/compilers', name: 'USE_RUBY_DEBUG_LOG=1', with: { cppflags: '-DUSE_RUBY_DEBUG_LOG=1' } } - - { uses: './.github/actions/compilers', name: 'USE_DEBUG_COUNTER', with: { cppflags: '-DUSE_DEBUG_COUNTER=1' } } - - { uses: './.github/actions/compilers', name: 'SHARABLE_MIDDLE_SUBSTRING', with: { cppflags: '-DSHARABLE_MIDDLE_SUBSTRING=1' } } - - { uses: './.github/actions/compilers', name: 'DEBUG_FIND_TIME_NUMGUESS', with: { cppflags: '-DDEBUG_FIND_TIME_NUMGUESS' } } - - { uses: './.github/actions/compilers', name: 'DEBUG_INTEGER_PACK', with: { cppflags: '-DDEBUG_INTEGER_PACK' } } + - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_SYMBOL_GC=0', with: { cppflags: '-DUSE_SYMBOL_GC=0' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_THREAD_CACHE=0', with: { cppflags: '-DUSE_THREAD_CACHE=0' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_RUBY_DEBUG_LOG=1', with: { cppflags: '-DUSE_RUBY_DEBUG_LOG=1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_DEBUG_COUNTER', with: { cppflags: '-DUSE_DEBUG_COUNTER=1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'SHARABLE_MIDDLE_SUBSTRING', with: { cppflags: '-DSHARABLE_MIDDLE_SUBSTRING=1' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'DEBUG_FIND_TIME_NUMGUESS', with: { cppflags: '-DDEBUG_FIND_TIME_NUMGUESS' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'DEBUG_INTEGER_PACK', with: { cppflags: '-DDEBUG_INTEGER_PACK' }, timeout-minutes: 5 } compileB: name: 'omnibus compilations, #11' @@ -276,13 +278,13 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' } } - - { uses: './.github/actions/compilers', name: 'GC_ENABLE_LAZY_SWEEP=0', with: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' } } - - { uses: './.github/actions/compilers', name: 'GC_PROFILE_DETAIL_MEMORY', with: { cppflags: '-DGC_PROFILE_DETAIL_MEMORY' } } - - { uses: './.github/actions/compilers', name: 'GC_PROFILE_MORE_DETAIL', with: { cppflags: '-DGC_PROFILE_MORE_DETAIL' } } - - { uses: './.github/actions/compilers', name: 'MALLOC_ALLOCATED_SIZE_CHECK', with: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_ESTIMATE_OLDMALLOC', with: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' } } - - { uses: './.github/actions/compilers', name: 'RGENGC_PROFILE', with: { cppflags: '-DRGENGC_PROFILE' } } + - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GC_ENABLE_LAZY_SWEEP=0', with: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GC_PROFILE_DETAIL_MEMORY', with: { cppflags: '-DGC_PROFILE_DETAIL_MEMORY' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'GC_PROFILE_MORE_DETAIL', with: { cppflags: '-DGC_PROFILE_MORE_DETAIL' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'MALLOC_ALLOCATED_SIZE_CHECK', with: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RGENGC_ESTIMATE_OLDMALLOC', with: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'RGENGC_PROFILE', with: { cppflags: '-DRGENGC_PROFILE' }, timeout-minutes: 5 } compileC: name: 'omnibus compilations, #12' @@ -295,10 +297,10 @@ jobs: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' } } - - { uses: './.github/actions/compilers', name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } - - { uses: './.github/actions/compilers', name: 'YJIT_FORCE_ENABLE', with: { cppflags: '-DYJIT_FORCE_ENABLE' } } - - { uses: './.github/actions/compilers', name: 'UNIVERSAL_PARSER', with: { cppflags: '-DUNIVERSAL_PARSER' } } + - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'YJIT_FORCE_ENABLE', with: { cppflags: '-DYJIT_FORCE_ENABLE' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'UNIVERSAL_PARSER', with: { cppflags: '-DUNIVERSAL_PARSER' }, timeout-minutes: 5 } compilemax: name: 'omnibus compilations, result' From e920ee32894dcd2ab0f97ff6f45c29d65024da0c Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 22 Nov 2025 20:31:14 -0800 Subject: [PATCH 1353/2435] [ruby/rubygems] Support bundle install --lockfile option This allows for specifying the lockfile to read and write. It mirrors the --gemfile option, and has higher priority than the lockfile method in the Gemfile. It also mirrors the bundle lock --lockfile option. When the --lockfile option is used, it is applied twice. First, before the Gemfile is read, to specify the lockfile to operate on, and again after the Gemfile is read, so that if the Gemfile has a lockfile method that overrides the defintion's lockfile, the --lockfile option still has higher precedence. https://github.com/ruby/rubygems/commit/17acdd4a89 --- lib/bundler/cli.rb | 17 +++++++++++++++-- lib/bundler/cli/install.rb | 1 + lib/bundler/man/bundle-install.1 | 5 ++++- lib/bundler/man/bundle-install.1.ronn | 5 +++++ lib/bundler/man/gemfile.5 | 6 ++++-- lib/bundler/man/gemfile.5.ronn | 7 ++++--- spec/bundler/commands/install_spec.rb | 23 +++++++++++++++++++++++ 7 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 48178965697667..55f656e3f3afd0 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -59,17 +59,29 @@ def self.aliases_for(command_name) def initialize(*args) super + current_cmd = args.last[:current_command].name + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] if custom_gemfile && !custom_gemfile.empty? Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) - Bundler.reset_settings_and_root! + reset_settings = true end + # lock --lockfile works differently than install --lockfile + unless current_cmd == "lock" + custom_lockfile = options[:lockfile] || Bundler.settings[:lockfile] + if custom_lockfile && !custom_lockfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) + reset_settings = true + end + end + + Bundler.reset_settings_and_root! if reset_settings + Bundler.auto_switch Bundler.settings.set_command_option_if_given :retry, options[:retry] - current_cmd = args.last[:current_command].name Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message @@ -232,6 +244,7 @@ def remove(*gems) method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel" method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default." method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." method_option "no-lock", type: :boolean, banner: "Don't create a lockfile." diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 85b303eee60900..ba1cef8434e63c 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -44,6 +44,7 @@ def run # (rather than some optimizations we perform at app runtime). definition = Bundler.definition(strict: true) definition.validate_runtime! + definition.lockfile = options["lockfile"] if options["lockfile"] definition.lockfile = false if options["no-lock"] installer = Installer.install(Bundler.root, definition, options) diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 1acbe430585e22..68530f3ebb588d 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] +\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-lockfile=LOCKFILE] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -28,6 +28,9 @@ The maximum number of parallel download and install jobs\. The default is the nu \fB\-\-local\fR Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. .TP +\fB\-\-lockfile=LOCKFILE\fR +The location of the lockfile which Bundler should use\. This defaults to the Gemfile location with \fB\.lock\fR appended\. +.TP \fB\-\-prefer\-local\fR Force using locally installed gems, or gems already present in Rubygems' cache or in \fBvendor/cache\fR, when resolving, even if newer versions are available remotely\. Only attempt to connect to \fBrubygems\.org\fR for gems that are not present locally\. .TP diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index adb47490d74b2d..c7d88bfb73c3e4 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -8,6 +8,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile [--gemfile=GEMFILE] [--jobs=NUMBER] [--local] + [--lockfile=LOCKFILE] [--no-cache] [--no-lock] [--prefer-local] @@ -61,6 +62,10 @@ update process below under [CONSERVATIVE UPDATING][]. appropriate platform-specific gem exists on `rubygems.org` it will not be found. +* `--lockfile=LOCKFILE`: + The location of the lockfile which Bundler should use. This defaults + to the Gemfile location with `.lock` appended. + * `--prefer-local`: Force using locally installed gems, or gems already present in Rubygems' cache or in `vendor/cache`, when resolving, even if newer versions are available diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index f345580ed77edb..fce7d8e178439b 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -492,10 +492,12 @@ When determining path to the lockfile or whether to create a lockfile, the follo .IP "1." 4 The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\. .IP "2." 4 -The \fBlockfile\fR method in the Gemfile\. +The \fBbundle install\fR \fB\-\-lockfile\fR option\. .IP "3." 4 -The \fBBUNDLE_LOCKFILE\fR environment variable\. +The \fBlockfile\fR method in the Gemfile\. .IP "4." 4 +The \fBBUNDLE_LOCKFILE\fR environment variable\. +.IP "5." 4 The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\. .IP "" 0 diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 3dea29cb3c6aaf..e4bc91359e3cc5 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -580,6 +580,7 @@ When determining path to the lockfile or whether to create a lockfile, the following precedence is used: 1. The `bundle install` `--no-lock` option (which disables lockfile creation). -2. The `lockfile` method in the Gemfile. -3. The `BUNDLE_LOCKFILE` environment variable. -4. The default behavior of adding `.lock` to the end of the Gemfile name. +1. The `bundle install` `--lockfile` option. +1. The `lockfile` method in the Gemfile. +1. The `BUNDLE_LOCKFILE` environment variable. +1. The default behavior of adding `.lock` to the end of the Gemfile name. diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 69d9a860996197..bacd8d64f2d6c3 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -41,6 +41,17 @@ expect(bundled_app("OmgFile.lock")).to exist end + it "creates lockfile based on --lockfile option is given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --lockfile ReallyOmgFile.lock" + + expect(bundled_app("ReallyOmgFile.lock")).to exist + end + it "does not make a lockfile if lockfile false is used in Gemfile" do install_gemfile <<-G lockfile false @@ -100,6 +111,18 @@ expect(bundled_app("OmgFile.lock")).not_to exist end + it "doesn't create a lockfile if --no-lock and --lockfile options are given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --no-lock --lockfile ReallyOmgFile.lock" + + expect(bundled_app("OmgFile.lock")).not_to exist + expect(bundled_app("ReallyOmgFile.lock")).not_to exist + end + it "doesn't delete the lockfile if one already exists" do install_gemfile <<-G source "https://gem.repo1" From a36ebb18a6d4c4726915b6d7c16cfdbf4e5d417b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 17 Nov 2025 19:01:12 +0000 Subject: [PATCH 1354/2435] vm_cc_new: don't assume `cme` is present. [Bug #21694] `vm_search_super_method` explictly calls `vm_cc_new` with `cme=NULL` when there is no super class. --- test/ruby/test_super.rb | 15 +++++++++++++++ vm_callinfo.h | 25 +++++++++++++++++++------ vm_insnhelper.c | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index 8e973b0f7f4a08..25bad2242af994 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -759,4 +759,19 @@ def initialize inherited = inherited_class.new assert_equal 2, inherited.test # it may read index=1 while it should be index=2 end + + def test_super_in_basic_object + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class ::BasicObject + def no_super + super() + rescue ::NameError + :ok + end + end + + assert_equal :ok, "[Bug #21694]".no_super + end; + end end diff --git a/vm_callinfo.h b/vm_callinfo.h index 6701b17d761cda..2c51bf9092046e 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -302,6 +302,7 @@ struct rb_callcache { #define VM_CALLCACHE_REFINEMENT IMEMO_FL_USER3 #define VM_CALLCACHE_UNMARKABLE IMEMO_FL_USER4 #define VM_CALLCACHE_ON_STACK IMEMO_FL_USER5 +#define VM_CALLCACHE_INVALID_SUPER IMEMO_FL_USER6 enum vm_cc_type { cc_type_normal, // chained from ccs @@ -344,8 +345,6 @@ vm_cc_new(VALUE klass, *((struct rb_callable_method_entry_struct **)&cc->cme_) = (struct rb_callable_method_entry_struct *)cme; *((vm_call_handler *)&cc->call_) = call; - VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_ICLASS)); - switch (type) { case cc_type_normal: break; @@ -358,8 +357,13 @@ vm_cc_new(VALUE klass, break; } - if (cme->def->type == VM_METHOD_TYPE_ATTRSET || cme->def->type == VM_METHOD_TYPE_IVAR) { - vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID); + if (cme) { + if (cme->def->type == VM_METHOD_TYPE_ATTRSET || cme->def->type == VM_METHOD_TYPE_IVAR) { + vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID); + } + } + else { + *(VALUE *)&cc->flags |= VM_CALLCACHE_INVALID_SUPER; } RB_DEBUG_COUNTER_INC(cc_new); @@ -405,6 +409,14 @@ vm_cc_markable(const struct rb_callcache *cc) return FL_TEST_RAW((VALUE)cc, VM_CALLCACHE_UNMARKABLE) == 0; } +static inline bool +vm_cc_invalid_super(const struct rb_callcache *cc) +{ + VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); + // Set when calling super and there is no superclass. + return FL_TEST_RAW((VALUE)cc, VM_CALLCACHE_INVALID_SUPER); +} + static inline bool vm_cc_valid(const struct rb_callcache *cc) { @@ -418,10 +430,11 @@ static inline const struct rb_callable_method_entry_struct * vm_cc_cme(const struct rb_callcache *cc) { VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); - VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc)); + VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc) || vm_cc_invalid_super(cc)); VM_ASSERT(cc_check_class(cc->klass)); VM_ASSERT(cc->call_ == NULL || // not initialized yet !vm_cc_markable(cc) || + vm_cc_invalid_super(cc) || cc->cme_ != NULL); return cc->cme_; @@ -432,7 +445,7 @@ vm_cc_call(const struct rb_callcache *cc) { VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); VM_ASSERT(cc->call_ != NULL); - VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc)); + VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc) || vm_cc_invalid_super(cc)); VM_ASSERT(cc_check_class(cc->klass)); return cc->call_; } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 97e63387bb7b55..c9913a92bbd748 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5164,7 +5164,7 @@ vm_search_super_method(const rb_control_frame_t *reg_cfp, struct rb_call_data *c if (!klass) { /* bound instance method of module */ - cc = vm_cc_new(klass, NULL, vm_call_method_missing, cc_type_super); + cc = vm_cc_new(Qundef, NULL, vm_call_method_missing, cc_type_super); RB_OBJ_WRITE(reg_cfp->iseq, &cd->cc, cc); } else { From 0eb53053f00458b5b32bb66f63cb7c4c5f402ec8 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 25 Nov 2025 10:50:55 -0800 Subject: [PATCH 1355/2435] ZJIT: Specialize setinstancevariable when ivar is already in shape (#15290) Don't support shape transitions for now. --- insns.def | 1 + zjit/bindgen/src/main.rs | 1 + zjit/src/cruby.rs | 4 + zjit/src/cruby_bindings.inc.rs | 66 ++++++++------- zjit/src/hir.rs | 46 +++++++++++ zjit/src/hir/opt_tests.rs | 145 +++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 1 + 7 files changed, 235 insertions(+), 29 deletions(-) diff --git a/insns.def b/insns.def index 3d13f4cb646d39..7df36726157455 100644 --- a/insns.def +++ b/insns.def @@ -224,6 +224,7 @@ setinstancevariable (VALUE val) () // attr bool leaf = false; /* has rb_check_frozen() */ +// attr bool zjit_profile = true; { vm_setinstancevariable(GET_ISEQ(), GET_SELF(), id, val, ic); } diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index bac17f4a6d4452..fe082504b82302 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -101,6 +101,7 @@ fn main() { .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") .allowlist_var("rb_invalid_shape_id") + .allowlist_type("shape_id_fl_type") .allowlist_var("VM_KW_SPECIFIED_BITS_MAX") .allowlist_var("SHAPE_ID_NUM_BITS") .allowlist_function("rb_obj_is_kind_of") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 83919e5369e5d9..1a60e2a7fe2a06 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -263,6 +263,10 @@ impl ShapeId { pub fn is_too_complex(self) -> bool { unsafe { rb_jit_shape_too_complex_p(self.0) } } + + pub fn is_frozen(self) -> bool { + (self.0 & SHAPE_ID_FL_FROZEN) != 0 + } } // Given an ISEQ pointer, convert PC to insn_idx diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index fe2055d4cc0486..aaecfa2f89769a 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1462,6 +1462,13 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; pub type attr_index_t = u16; pub type shape_id_t = u32; +pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 29360128; +pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 33554432; +pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 67108864; +pub const SHAPE_ID_FL_TOO_COMPLEX: shape_id_fl_type = 134217728; +pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 100663296; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 264241152; +pub type shape_id_fl_type = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { pub index: u32, @@ -1744,35 +1751,36 @@ pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 215; pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 216; pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 217; pub const YARVINSN_zjit_getinstancevariable: ruby_vminsn_type = 218; -pub const YARVINSN_zjit_definedivar: ruby_vminsn_type = 219; -pub const YARVINSN_zjit_send: ruby_vminsn_type = 220; -pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 221; -pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 222; -pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 223; -pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 224; -pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 225; -pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 226; -pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 227; -pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 228; -pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 229; -pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 230; -pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 231; -pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 232; -pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 233; -pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 236; -pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 237; -pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 238; -pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 239; -pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 240; -pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 241; -pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 242; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 243; -pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 244; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 245; -pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 246; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 247; +pub const YARVINSN_zjit_setinstancevariable: ruby_vminsn_type = 219; +pub const YARVINSN_zjit_definedivar: ruby_vminsn_type = 220; +pub const YARVINSN_zjit_send: ruby_vminsn_type = 221; +pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 222; +pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 223; +pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 224; +pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 225; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 226; +pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 227; +pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 228; +pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 229; +pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 230; +pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 231; +pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 232; +pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 244; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 245; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 246; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 247; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 248; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index dd7bc953b9219e..b7149ad14c66fe 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2873,6 +2873,50 @@ impl Function { }; self.make_equal_to(insn_id, replacement); } + Insn::SetIvar { self_val, id, val, state, ic: _ } => { + let frame_state = self.frame_state(state); + let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { + // No (monomorphic/skewed polymorphic) profile info + self.push_insn_id(block, insn_id); continue; + }; + if recv_type.flags().is_immediate() { + // Instance variable lookups on immediate values are always nil + self.push_insn_id(block, insn_id); continue; + } + assert!(recv_type.shape().is_valid()); + if !recv_type.flags().is_t_object() { + // Check if the receiver is a T_OBJECT + self.push_insn_id(block, insn_id); continue; + } + if recv_type.shape().is_too_complex() { + // too-complex shapes can't use index access + self.push_insn_id(block, insn_id); continue; + } + if recv_type.shape().is_frozen() { + // Can't set ivars on frozen objects + self.push_insn_id(block, insn_id); continue; + } + let mut ivar_index: u16 = 0; + if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { + // TODO(max): Shape transition + self.push_insn_id(block, insn_id); continue; + } + let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); + let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); + // Current shape contains this ivar + let (ivar_storage, offset) = if recv_type.flags().is_embedded() { + // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h + let offset = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; + (self_val, offset) + } else { + let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: ID!(_as_heap), offset: ROBJECT_OFFSET_AS_HEAP_FIELDS as i32, return_type: types::CPtr }); + let offset = SIZEOF_VALUE_I32 * ivar_index as i32; + (as_heap, offset) + }; + let replacement = self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val }); + self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); + self.make_equal_to(insn_id, replacement); + } _ => { self.push_insn_id(block, insn_id); } } } @@ -4906,6 +4950,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // profiled cfp->self. if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable { profiles.profile_self(&exit_state, self_param); + } else if opcode == YARVINSN_setinstancevariable || opcode == YARVINSN_trace_setinstancevariable { + profiles.profile_self(&exit_state, self_param); } else if opcode == YARVINSN_definedivar || opcode == YARVINSN_trace_definedivar { profiles.profile_self(&exit_state, self_param); } else if opcode == YARVINSN_invokeblock || opcode == YARVINSN_trace_invokeblock { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 866d0ec06dcf23..c460942fc13e42 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3479,6 +3479,151 @@ mod hir_opt_tests { "); } + #[test] + fn test_specialize_monomorphic_setivar_already_in_shape() { + eval(" + @foo = 4 + def test = @foo = 5 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + v19:HeapBasicObject = GuardType v6, HeapBasicObject + v20:HeapBasicObject = GuardShape v19, 0x1000 + StoreField v20, :@foo@0x1001, v10 + WriteBarrier v20, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_dont_specialize_monomorphic_setivar_with_shape_transition() { + eval(" + def test = @foo = 5 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + SetIvar v6, :@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_dont_specialize_setivar_with_t_data() { + eval(" + class C < Range + def test = @a = 5 + end + obj = C.new 0, 1 + obj.instance_variable_set(:@a, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + SetIvar v6, :@a, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_dont_specialize_polymorphic_setivar() { + set_call_threshold(3); + eval(" + class C + def test = @a = 5 + end + obj = C.new + obj.instance_variable_set(:@a, 1) + obj.test + obj = C.new + obj.instance_variable_set(:@b, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + SetIvar v6, :@a, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_dont_specialize_complex_shape_setivar() { + eval(r#" + class C + def test = @a = 5 + end + obj = C.new + (0..1000).each do |i| + obj.instance_variable_set(:"@v#{i}", i) + end + obj.test + TEST = C.instance_method(:test) + "#); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + SetIvar v6, :@a, v10 + CheckInterrupts + Return v10 + "); + } + #[test] fn test_elide_freeze_with_frozen_hash() { eval(" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 10afdf2cc6475c..e7efbcad34a678 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -82,6 +82,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_aset => profile_operands(profiler, profile, 3), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), + YARVINSN_setinstancevariable => profile_self(profiler, profile), YARVINSN_definedivar => profile_self(profiler, profile), YARVINSN_opt_regexpmatch2 => profile_operands(profiler, profile, 2), YARVINSN_objtostring => profile_operands(profiler, profile, 1), From 4263f1d718df65bdf465552029a71b1ea9747067 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 24 Nov 2025 21:07:08 -0800 Subject: [PATCH 1356/2435] Store fiber serial as Ractor-local --- cont.c | 9 ++++----- ractor.c | 2 ++ ractor_core.h | 2 ++ ruby_atomic.h | 23 ----------------------- 4 files changed, 8 insertions(+), 28 deletions(-) diff --git a/cont.c b/cont.c index d167685f13bec3..c49256c977a5b3 100644 --- a/cont.c +++ b/cont.c @@ -2005,10 +2005,9 @@ fiber_alloc(VALUE klass) } static rb_serial_t -next_fiber_serial(void) +next_fiber_serial(rb_ractor_t *cr) { - static rbimpl_atomic_uint64_t fiber_serial = 1; - return (rb_serial_t)ATOMIC_U64_FETCH_ADD(fiber_serial, 1); + return cr->next_fiber_serial++; } static rb_fiber_t* @@ -2027,7 +2026,7 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) fiber->cont.type = FIBER_CONTEXT; fiber->blocking = blocking; fiber->killed = 0; - fiber->serial = next_fiber_serial(); + fiber->serial = next_fiber_serial(th->ractor); cont_init(&fiber->cont, th); fiber->cont.saved_ec.fiber_ptr = fiber; @@ -2580,7 +2579,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) fiber->cont.saved_ec.thread_ptr = th; fiber->blocking = 1; fiber->killed = 0; - fiber->serial = next_fiber_serial(); + fiber->serial = next_fiber_serial(th->ractor); fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ th->ec = &fiber->cont.saved_ec; cont_init_jit_cont(&fiber->cont); diff --git a/ractor.c b/ractor.c index 48fbf7cfb94404..3fc507128c1939 100644 --- a/ractor.c +++ b/ractor.c @@ -418,6 +418,7 @@ ractor_alloc(VALUE klass) VALUE rv = TypedData_Make_Struct(klass, rb_ractor_t, &ractor_data_type, r); FL_SET_RAW(rv, RUBY_FL_SHAREABLE); r->pub.self = rv; + r->next_fiber_serial = 1; VM_ASSERT(ractor_status_p(r, ractor_created)); return rv; } @@ -435,6 +436,7 @@ rb_ractor_main_alloc(void) r->name = Qnil; r->pub.self = Qnil; r->newobj_cache = rb_gc_ractor_cache_alloc(r); + r->next_fiber_serial = 1; ruby_single_main_ractor = r; return r; diff --git a/ractor_core.h b/ractor_core.h index 130ccb11189fa6..81374c0769f6ca 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -88,6 +88,8 @@ struct rb_ractor_struct { // ractor local data + rb_serial_t next_fiber_serial; + st_table *local_storage; struct rb_id_table *idkey_local_storage; VALUE local_storage_store_lock; diff --git a/ruby_atomic.h b/ruby_atomic.h index b60c56cfd89048..cbcfe682ceddb9 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -70,27 +70,4 @@ rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t } #define ATOMIC_U64_SET_RELAXED(var, val) rbimpl_atomic_u64_set_relaxed(&(var), val) -static inline uint64_t -rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val) -{ -#if defined(HAVE_GCC_ATOMIC_BUILTINS_64) - return __atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST); -#elif defined(_WIN32) - return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val); -#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) - return atomic_add_64_nv(ptr, val) - val; -#elif defined(HAVE_STDATOMIC_H) - return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst); -#else - // Fallback using mutex for platforms without 64-bit atomics - static rb_nativethread_mutex_t lock = RB_NATIVETHREAD_LOCK_INIT; - rb_native_mutex_lock(&lock); - uint64_t old = *ptr; - *ptr = old + val; - rb_native_mutex_unlock(&lock); - return old; -#endif -} -#define ATOMIC_U64_FETCH_ADD(var, val) rbimpl_atomic_u64_fetch_add(&(var), val) - #endif From 8bf333a199b5c099c2e3d3efeb2ba06f265db324 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 24 Nov 2025 22:32:07 -0500 Subject: [PATCH 1357/2435] Fix live object count for multi-Ractor forking Since we do not run a Ractor barrier before forking, it's possible that another other Ractor is halfway through allocating an object during forking. This may lead to allocated_objects_count being off by one. For example, the following script reproduces the bug: 100.times do |i| Ractor.new(i) do |j| 10000.times do |i| "#{j}-#{i}" end Ractor.receive end pid = fork { GC.verify_internal_consistency } _, status = Process.waitpid2 pid raise unless status.success? end We need to run with `taskset -c 1` to force it to use a single CPU core to more consistenly reproduce the bug: heap_pages_final_slots: 1, total_freed_objects: 16628 test.rb:8: [BUG] inconsistent live slot number: expect 19589, but 19588. ruby 4.0.0dev (2025-11-25T03:06:55Z master 55892f5994) +PRISM [x86_64-linux] -- Control frame information ----------------------------------------------- c:0007 p:---- s:0029 e:000028 l:y b:---- CFUNC :verify_internal_consistency c:0006 p:0004 s:0025 e:000024 l:n b:---- BLOCK test.rb:8 [FINISH] c:0005 p:---- s:0022 e:000021 l:y b:---- CFUNC :fork c:0004 p:0012 s:0018 E:0014c0 l:n b:---- BLOCK test.rb:8 c:0003 p:0024 s:0011 e:000010 l:y b:0001 METHOD :257 c:0002 p:0005 s:0006 E:001730 l:n b:---- EVAL test.rb:1 [FINISH] c:0001 p:0000 s:0003 E:001d20 l:y b:---- DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- test.rb:1:in '
' :257:in 'times' test.rb:8:in 'block in
' test.rb:8:in 'fork' test.rb:8:in 'block (2 levels) in
' test.rb:8:in 'verify_internal_consistency' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- ruby(rb_print_backtrace+0x14) [0x61b67ac48b60] vm_dump.c:1105 ruby(rb_vm_bugreport) vm_dump.c:1450 ruby(rb_bug_without_die_internal+0x5f) [0x61b67a818a28] error.c:1098 ruby(rb_bug) error.c:1116 ruby(gc_verify_internal_consistency_+0xbdd) [0x61b67a83d8ed] gc/default/default.c:5186 ruby(gc_verify_internal_consistency+0x2d) [0x61b67a83d960] gc/default/default.c:5241 ruby(rb_gc_verify_internal_consistency) gc/default/default.c:8950 ruby(gc_verify_internal_consistency_m) gc/default/default.c:8966 ruby(vm_call_cfunc_with_frame_+0x10d) [0x61b67a9e50fd] vm_insnhelper.c:3902 ruby(vm_sendish+0x111) [0x61b67a9eeaf1] vm_insnhelper.c:6124 ruby(vm_exec_core+0x84) [0x61b67aa07434] insns.def:903 ruby(vm_exec_loop+0xa) [0x61b67a9f8155] vm.c:2811 ruby(rb_vm_exec) vm.c:2787 ruby(vm_yield_with_cref+0x90) [0x61b67a9fd2ea] vm.c:1865 ruby(vm_yield) vm.c:1873 ruby(rb_yield) vm_eval.c:1362 ruby(rb_protect+0xef) [0x61b67a81fe6f] eval.c:1154 ruby(rb_f_fork+0x16) [0x61b67a8e98ab] process.c:4293 ruby(rb_f_fork) process.c:4284 --- bootstraptest/test_ractor.rb | 21 +++++++++++++++++++++ gc/default/default.c | 12 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index ed80dd08628e9d..6c3a45148c23e1 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -2370,3 +2370,24 @@ def call_test(obj) :ok end RUBY + +assert_equal 'ok', <<~'RUBY' + begin + 100.times do |i| + Ractor.new(i) do |j| + 1000.times do |i| + "#{j}-#{i}" + end + end + pid = fork do + GC.verify_internal_consistency + end + _, status = Process.waitpid2 pid + raise unless status.success? + end + + :ok + rescue NotImplementedError + :ok + end +RUBY diff --git a/gc/default/default.c b/gc/default/default.c index b6a9dfa1f57d9d..9a2efab562402b 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -622,6 +622,8 @@ typedef struct rb_objspace { rb_postponed_job_handle_t finalize_deferred_pjob; unsigned long live_ractor_cache_count; + + int fork_vm_lock_lev; } rb_objspace_t; #ifndef HEAP_PAGE_ALIGN_LOG @@ -9324,12 +9326,20 @@ gc_malloc_allocations(VALUE self) void rb_gc_impl_before_fork(void *objspace_ptr) { - /* no-op */ + rb_objspace_t *objspace = objspace_ptr; + + objspace->fork_vm_lock_lev = RB_GC_VM_LOCK(); + rb_gc_vm_barrier(); } void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid) { + rb_objspace_t *objspace = objspace_ptr; + + RB_GC_VM_UNLOCK(objspace->fork_vm_lock_lev); + objspace->fork_vm_lock_lev = 0; + if (pid == 0) { /* child process */ rb_gc_ractor_newobj_cache_foreach(gc_ractor_newobj_cache_clear, NULL); } From 0654bcd4f809e97bca4a099fa78db9990fa5a4ae Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 26 Nov 2025 00:21:40 +0900 Subject: [PATCH 1358/2435] Box: Add a test to drop the reference to a box --- test/ruby/test_box.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 9b87f9b5bc458a..7023ecab16aa0e 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -810,4 +810,21 @@ def test_loading_extension_libs_in_main_box assert_equal expected, 1 end; end + + def test_mark_box_object_referred_only_from_binding + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box = Ruby::Box.new + box.eval('class Integer; def +(*)=42; end') + b = box.eval('binding') + box = nil # remove direct reference to the box + + assert_equal 42, b.eval('1+2') + + GC.stress = true + GC.start + + assert_equal 42, b.eval('1+2') + end; + end end From e84b91a292f3cd94fdf5f2ef548bf2377b1cf537 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Wed, 26 Nov 2025 00:36:55 +0900 Subject: [PATCH 1359/2435] Box: mark/move Box object referred via ENV/rb_env_t --- imemo.c | 7 +++++++ vm.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/imemo.c b/imemo.c index d83c690ba5ae81..8ec58ae4a92e52 100644 --- a/imemo.c +++ b/imemo.c @@ -426,6 +426,13 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) rb_gc_mark_and_move_ptr(&env->iseq); + if (VM_ENV_LOCAL_P(env->ep) && VM_ENV_BOXED_P(env->ep)) { + const rb_box_t *box = VM_ENV_BOX(env->ep); + if (BOX_USER_P(box)) { + rb_gc_mark_and_move((VALUE *)&box->box_object); + } + } + if (reference_updating) { ((VALUE *)env->ep)[VM_ENV_DATA_INDEX_ENV] = rb_gc_location(env->ep[VM_ENV_DATA_INDEX_ENV]); } diff --git a/vm.c b/vm.c index fd8c923649cb65..f9c615c3e23c30 100644 --- a/vm.c +++ b/vm.c @@ -3677,6 +3677,13 @@ rb_execution_context_mark(const rb_execution_context_t *ec) rb_gc_mark_movable((VALUE)cfp->iseq); rb_gc_mark_movable((VALUE)cfp->block_code); + if (VM_ENV_LOCAL_P(ep) && VM_ENV_BOXED_P(ep)) { + const rb_box_t *box = VM_ENV_BOX(ep); + if (BOX_USER_P(box)) { + rb_gc_mark_movable(box->box_object); + } + } + if (!VM_ENV_LOCAL_P(ep)) { const VALUE *prev_ep = VM_ENV_PREV_EP(ep); if (VM_ENV_FLAGS(prev_ep, VM_ENV_FLAG_ESCAPED)) { From 1e6079dcaff8853d4b36cda9e9bb751139b27375 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 26 Nov 2025 01:22:39 +0000 Subject: [PATCH 1360/2435] [DOC] Use Aliki as the documentation website theme (#15319) Use Aliki as the documentation website theme --- .rdoc_options | 15 +++++++++++++++ gems/bundled_gems | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.rdoc_options b/.rdoc_options index b8b511efe67f1e..f38a0da35537dd 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -11,6 +11,8 @@ rdoc_include: exclude: - \.gemspec\z +generator_name: aliki + autolink_excluded_words: - Class - Method @@ -23,3 +25,16 @@ autolink_excluded_words: - YJIT canonical_root: https://docs.ruby-lang.org/en/master + +footer_content: + Ruby: + Documentation: index.html + Official Website: https://www.ruby-lang.org/ + Playground: https://ruby.github.io/play-ruby/ + Resources: + GitHub: https://github.com/ruby/ruby + Issue Tracker: https://bugs.ruby-lang.org/projects/ruby-master/issues + RubyGems: https://rubygems.org/ + Community: + X: https://x.com/rubylangorg + diff --git a/gems/bundled_gems b/gems/bundled_gems index 2d5c6bdaa1cea1..fbe741c335864e 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.15.1 https://github.com/ruby/rdoc +rdoc 6.16.0 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.3 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From 6354afa64a5c59de50f064a7827cbd15d29dc874 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 26 Nov 2025 01:23:33 +0000 Subject: [PATCH 1361/2435] Update bundled gems list as of 2025-11-26 --- NEWS.md | 4 ++-- gems/bundled_gems | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9d47bb774ef626..fbbc06713438cd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -167,7 +167,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.15.1 +* rdoc 6.16.0 * win32ole 1.9.2 * irb 1.15.3 * reline 0.6.3 @@ -221,7 +221,7 @@ The following bundled gems are updated. * minitest 5.26.2 * power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.1 +* test-unit 3.7.3 * rexml 3.4.4 * net-ftp 0.3.9 * net-imap 0.5.12 diff --git a/gems/bundled_gems b/gems/bundled_gems index fbe741c335864e..e1e6b01bfdda10 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.26.2 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.1 https://github.com/test-unit/test-unit +test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From 26a9e0b4e31f7b5a9cbd755e0a15823a8fa51bae Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 10:47:17 +0900 Subject: [PATCH 1362/2435] Reset the cache variable before retrying --- .rdoc_options | 15 --------------- NEWS.md | 4 ++-- configure.ac | 4 +++- gems/bundled_gems | 4 ++-- imemo.c | 7 ------- test/ruby/test_box.rb | 17 ----------------- vm.c | 7 ------- 7 files changed, 7 insertions(+), 51 deletions(-) diff --git a/.rdoc_options b/.rdoc_options index f38a0da35537dd..b8b511efe67f1e 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -11,8 +11,6 @@ rdoc_include: exclude: - \.gemspec\z -generator_name: aliki - autolink_excluded_words: - Class - Method @@ -25,16 +23,3 @@ autolink_excluded_words: - YJIT canonical_root: https://docs.ruby-lang.org/en/master - -footer_content: - Ruby: - Documentation: index.html - Official Website: https://www.ruby-lang.org/ - Playground: https://ruby.github.io/play-ruby/ - Resources: - GitHub: https://github.com/ruby/ruby - Issue Tracker: https://bugs.ruby-lang.org/projects/ruby-master/issues - RubyGems: https://rubygems.org/ - Community: - X: https://x.com/rubylangorg - diff --git a/NEWS.md b/NEWS.md index fbbc06713438cd..9d47bb774ef626 100644 --- a/NEWS.md +++ b/NEWS.md @@ -167,7 +167,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.16.0 +* rdoc 6.15.1 * win32ole 1.9.2 * irb 1.15.3 * reline 0.6.3 @@ -221,7 +221,7 @@ The following bundled gems are updated. * minitest 5.26.2 * power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.3 +* test-unit 3.7.1 * rexml 3.4.4 * net-ftp 0.3.9 * net-imap 0.5.12 diff --git a/configure.ac b/configure.ac index 9120fc25ecddf4..22baeabb32ab46 100644 --- a/configure.ac +++ b/configure.ac @@ -1749,8 +1749,10 @@ AS_IF([test "$GCC" = yes], [ for lib in "" atomic; do AS_IF([test "$lib" != ""], [ AC_CHECK_LIB([atomic], [__atomic_fetch_add_8]) + unset rb_cv_gcc_atomic_builtins_64 ]) - AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ + AC_CACHE_CHECK([for 64bit __atomic builtins${lib:+ with -l$lib}], + [rb_cv_gcc_atomic_builtins_64], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include uint64_t atomic_var;]], [[ diff --git a/gems/bundled_gems b/gems/bundled_gems index e1e6b01bfdda10..2d5c6bdaa1cea1 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.26.2 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.3 https://github.com/test-unit/test-unit +test-unit 3.7.1 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.16.0 https://github.com/ruby/rdoc +rdoc 6.15.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.3 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline diff --git a/imemo.c b/imemo.c index 8ec58ae4a92e52..d83c690ba5ae81 100644 --- a/imemo.c +++ b/imemo.c @@ -426,13 +426,6 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) rb_gc_mark_and_move_ptr(&env->iseq); - if (VM_ENV_LOCAL_P(env->ep) && VM_ENV_BOXED_P(env->ep)) { - const rb_box_t *box = VM_ENV_BOX(env->ep); - if (BOX_USER_P(box)) { - rb_gc_mark_and_move((VALUE *)&box->box_object); - } - } - if (reference_updating) { ((VALUE *)env->ep)[VM_ENV_DATA_INDEX_ENV] = rb_gc_location(env->ep[VM_ENV_DATA_INDEX_ENV]); } diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 7023ecab16aa0e..9b87f9b5bc458a 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -810,21 +810,4 @@ def test_loading_extension_libs_in_main_box assert_equal expected, 1 end; end - - def test_mark_box_object_referred_only_from_binding - assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) - begin; - box = Ruby::Box.new - box.eval('class Integer; def +(*)=42; end') - b = box.eval('binding') - box = nil # remove direct reference to the box - - assert_equal 42, b.eval('1+2') - - GC.stress = true - GC.start - - assert_equal 42, b.eval('1+2') - end; - end end diff --git a/vm.c b/vm.c index f9c615c3e23c30..fd8c923649cb65 100644 --- a/vm.c +++ b/vm.c @@ -3677,13 +3677,6 @@ rb_execution_context_mark(const rb_execution_context_t *ec) rb_gc_mark_movable((VALUE)cfp->iseq); rb_gc_mark_movable((VALUE)cfp->block_code); - if (VM_ENV_LOCAL_P(ep) && VM_ENV_BOXED_P(ep)) { - const rb_box_t *box = VM_ENV_BOX(ep); - if (BOX_USER_P(box)) { - rb_gc_mark_movable(box->box_object); - } - } - if (!VM_ENV_LOCAL_P(ep)) { const VALUE *prev_ep = VM_ENV_PREV_EP(ep); if (VM_ENV_FLAGS(prev_ep, VM_ENV_FLAG_ESCAPED)) { From 0b0c2cc4cbed72e952233953f90bc2dbdc77c11b Mon Sep 17 00:00:00 2001 From: git Date: Wed, 26 Nov 2025 02:08:43 +0000 Subject: [PATCH 1363/2435] Update bundled gems list as of 2025-11-25 --- NEWS.md | 4 ++-- gems/bundled_gems | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9d47bb774ef626..fbbc06713438cd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -167,7 +167,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.15.1 +* rdoc 6.16.0 * win32ole 1.9.2 * irb 1.15.3 * reline 0.6.3 @@ -221,7 +221,7 @@ The following bundled gems are updated. * minitest 5.26.2 * power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.1 +* test-unit 3.7.3 * rexml 3.4.4 * net-ftp 0.3.9 * net-imap 0.5.12 diff --git a/gems/bundled_gems b/gems/bundled_gems index 2d5c6bdaa1cea1..e1e6b01bfdda10 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.26.2 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.1 https://github.com/test-unit/test-unit +test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.15.1 https://github.com/ruby/rdoc +rdoc 6.16.0 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.3 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From 724e94a09c616fb71edd992e274e73ee14bee896 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 25 Nov 2025 13:10:31 -0500 Subject: [PATCH 1364/2435] ZJIT: CI: Run `btest` with call-threshold=2 --- .github/workflows/zjit-ubuntu.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 040187fca3baf7..7778c449e8e8ea 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -61,6 +61,12 @@ jobs: specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1' configure: '--enable-zjit=dev' + # The optimizer benefits from at least 1 iteration of profiling. Also, many + # regression tests in bootstraptest/test_yjit.rb assume call-threshold=2. + - test_task: 'btest' + run_opts: '--zjit-call-threshold=2' + configure: '--enable-zjit=dev' + - test_task: 'zjit-check' # zjit-test + quick feedback of test_zjit.rb configure: '--enable-yjit --enable-zjit=dev' rust_version: '1.85.0' From 2f53985da9ee593fe524d408256835667938c7d7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 11:34:33 +0900 Subject: [PATCH 1365/2435] Revert miscommit at "Reset the cache variable before retrying" This reverts commit 26a9e0b4e31f7b5a9cbd755e0a15823a8fa51bae partially. --- .rdoc_options | 15 +++++++++++++++ imemo.c | 7 +++++++ test/ruby/test_box.rb | 17 +++++++++++++++++ vm.c | 7 +++++++ 4 files changed, 46 insertions(+) diff --git a/.rdoc_options b/.rdoc_options index b8b511efe67f1e..f38a0da35537dd 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -11,6 +11,8 @@ rdoc_include: exclude: - \.gemspec\z +generator_name: aliki + autolink_excluded_words: - Class - Method @@ -23,3 +25,16 @@ autolink_excluded_words: - YJIT canonical_root: https://docs.ruby-lang.org/en/master + +footer_content: + Ruby: + Documentation: index.html + Official Website: https://www.ruby-lang.org/ + Playground: https://ruby.github.io/play-ruby/ + Resources: + GitHub: https://github.com/ruby/ruby + Issue Tracker: https://bugs.ruby-lang.org/projects/ruby-master/issues + RubyGems: https://rubygems.org/ + Community: + X: https://x.com/rubylangorg + diff --git a/imemo.c b/imemo.c index d83c690ba5ae81..8ec58ae4a92e52 100644 --- a/imemo.c +++ b/imemo.c @@ -426,6 +426,13 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) rb_gc_mark_and_move_ptr(&env->iseq); + if (VM_ENV_LOCAL_P(env->ep) && VM_ENV_BOXED_P(env->ep)) { + const rb_box_t *box = VM_ENV_BOX(env->ep); + if (BOX_USER_P(box)) { + rb_gc_mark_and_move((VALUE *)&box->box_object); + } + } + if (reference_updating) { ((VALUE *)env->ep)[VM_ENV_DATA_INDEX_ENV] = rb_gc_location(env->ep[VM_ENV_DATA_INDEX_ENV]); } diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 9b87f9b5bc458a..7023ecab16aa0e 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -810,4 +810,21 @@ def test_loading_extension_libs_in_main_box assert_equal expected, 1 end; end + + def test_mark_box_object_referred_only_from_binding + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box = Ruby::Box.new + box.eval('class Integer; def +(*)=42; end') + b = box.eval('binding') + box = nil # remove direct reference to the box + + assert_equal 42, b.eval('1+2') + + GC.stress = true + GC.start + + assert_equal 42, b.eval('1+2') + end; + end end diff --git a/vm.c b/vm.c index fd8c923649cb65..f9c615c3e23c30 100644 --- a/vm.c +++ b/vm.c @@ -3677,6 +3677,13 @@ rb_execution_context_mark(const rb_execution_context_t *ec) rb_gc_mark_movable((VALUE)cfp->iseq); rb_gc_mark_movable((VALUE)cfp->block_code); + if (VM_ENV_LOCAL_P(ep) && VM_ENV_BOXED_P(ep)) { + const rb_box_t *box = VM_ENV_BOX(ep); + if (BOX_USER_P(box)) { + rb_gc_mark_movable(box->box_object); + } + } + if (!VM_ENV_LOCAL_P(ep)) { const VALUE *prev_ep = VM_ENV_PREV_EP(ep); if (VM_ENV_FLAGS(prev_ep, VM_ENV_FLAG_ESCAPED)) { From 30fe3654cabc56314b75ec6f837a583e0b0a1577 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 11:48:36 +0900 Subject: [PATCH 1366/2435] Ignore missed commits [ci skip] --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5fb9ba5f7dd3ac..3ca28ad72c8917 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -36,3 +36,5 @@ a0f7de814ae5c299d6ce99bed5fb308a05d50ba0 d4e24021d39e1f80f0055b55d91f8d5f22e15084 7a56c316418980b8a41fcbdc94067b2bda2ad112 e90282be7ba1bc8e3119f6e1a2c80356ceb3f80a +26a9e0b4e31f7b5a9cbd755e0a15823a8fa51bae +2f53985da9ee593fe524d408256835667938c7d7 From 72eb929ec1cafa9a4439520b5ba75ac4e5ef1a82 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 11:50:24 +0900 Subject: [PATCH 1367/2435] [DOC] Exclude the word Box from RDoc's autolinking --- .rdoc_options | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rdoc_options b/.rdoc_options index f38a0da35537dd..38dca221112c53 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -14,6 +14,7 @@ exclude: generator_name: aliki autolink_excluded_words: +- Box - Class - Method - Module @@ -37,4 +38,3 @@ footer_content: RubyGems: https://rubygems.org/ Community: X: https://x.com/rubylangorg - From 43ed35de6c1759feef57dbad0a2fad045ee2751f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 15:33:56 +0900 Subject: [PATCH 1368/2435] [ruby/cgi] Skip unless `CGI::EscapeExt` methods are implemented https://github.com/ruby/cgi/commit/7b5a13952b --- test/cgi/test_cgi_escape.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cgi/test_cgi_escape.rb b/test/cgi/test_cgi_escape.rb index f6ca658934cdec..73d99e8aacda39 100644 --- a/test/cgi/test_cgi_escape.rb +++ b/test/cgi/test_cgi_escape.rb @@ -300,7 +300,7 @@ def setup remove_method :escapeHTML alias _unescapeHTML unescapeHTML remove_method :unescapeHTML - end if defined?(CGI::EscapeExt) + end if defined?(CGI::EscapeExt) and CGI::EscapeExt.method_defined?(:escapeHTML) end def teardown @@ -309,7 +309,7 @@ def teardown remove_method :_escapeHTML alias unescapeHTML _unescapeHTML remove_method :_unescapeHTML - end if defined?(CGI::EscapeExt) + end if defined?(CGI::EscapeExt) and CGI::EscapeExt.method_defined?(:_escapeHTML) end include CGIEscapeTest::UnescapeHTMLTests From 525ee3ab260039d71bc210e442686fdfc35262ee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 15:45:52 +0900 Subject: [PATCH 1369/2435] [ruby/cgi] Fix mixed declarations and code Use C90 syntax only, as far as supporting ruby 2.6 or earlier. https://github.com/ruby/cgi/commit/886c82982a --- ext/cgi/escape/escape.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/cgi/escape/escape.c b/ext/cgi/escape/escape.c index 6b00bc37c1753f..4773186603914e 100644 --- a/ext/cgi/escape/escape.c +++ b/ext/cgi/escape/escape.c @@ -45,6 +45,7 @@ escaped_length(VALUE str) static VALUE optimized_escape_html(VALUE str) { + VALUE escaped; VALUE vbuf; char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); const char *cstr = RSTRING_PTR(str); @@ -63,7 +64,6 @@ optimized_escape_html(VALUE str) } } - VALUE escaped; if (RSTRING_LEN(str) < (dest - buf)) { escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); From ecdeb90fe94af86c6d84fe343c1f95b7d801367a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 26 Nov 2025 13:39:21 +0900 Subject: [PATCH 1370/2435] [ruby/rubygems] Bump up to 4.0.0.beta2 https://github.com/ruby/rubygems/commit/b8529f48bf --- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 4acf10c6153eaa..3a5b5fd86bdabc 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0.beta1".freeze + VERSION = "4.0.0.beta2".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 8530a2a893cb5d..52ccf10159506b 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0.beta1" + VERSION = "4.0.0.beta2" end require_relative "rubygems/defaults" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 4a96e12169b72e..6f86f3192ec3a6 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 8a13873f01b2fc..42036fc23d17b4 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 29e0a574bba80a..cb2d6b4106f453 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 10a81012e4d6c1..5cd23d7ef8bb62 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 8f4eba83c51b5a..20b7d153929917 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 8c8c9b77436542..44a2cdcd969d3c 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -100,4 +100,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0.beta1 + 4.0.0.beta2 From 61f34568037f0948491bf8bf2b00c6c39ee3537b Mon Sep 17 00:00:00 2001 From: git Date: Wed, 26 Nov 2025 07:03:09 +0000 Subject: [PATCH 1371/2435] Update default gems list at ecdeb90fe94af86c6d84fe343c1f95 [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index fbbc06713438cd..fa588780955227 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,8 +186,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.0.beta1 -* bundler 4.0.0.beta1 +* RubyGems 4.0.0.beta2 +* bundler 4.0.0.beta2 * date 3.5.0 * digest 3.2.1 * english 0.8.1 From c85eb2d0d05d2175018b3e0e544f1d87da029ee7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 18:40:10 +0900 Subject: [PATCH 1372/2435] [ruby/timeout] Revert "Suppress warnings in two tests" This reverts commit https://github.com/ruby/timeout/commit/983cbf636a17, that is fixed by test-unit 3.7.3. https://github.com/ruby/timeout/commit/095207f270 --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 71d8e1f5c273c5..01156867b05609 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -250,7 +250,7 @@ def test_fork end def test_threadgroup - assert_separately(%w[-W0 -rtimeout], <<-'end;') + assert_separately(%w[-rtimeout], <<-'end;') tg = ThreadGroup.new thr = Thread.new do tg.add(Thread.current) @@ -263,7 +263,7 @@ def test_threadgroup # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup - assert_separately(%w[-W0 -rtimeout], <<-'end;') + assert_separately(%w[-rtimeout], <<-'end;') Thread.new { t = Thread.current group = ThreadGroup.new From 6ace0251ef47e669994adf268cbf4362c292bab0 Mon Sep 17 00:00:00 2001 From: Sam Westerman Date: Tue, 25 Nov 2025 17:50:49 -0800 Subject: [PATCH 1373/2435] [ruby/optparse] Put `private` before method declarations https://github.com/ruby/optparse/commit/5478354d4f --- lib/optparse.rb | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index c66c84270a1517..4140b5f0743cc2 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -562,7 +562,7 @@ def initialize(pattern = nil, conv = nil, # Parses +arg+ and returns rest of +arg+ and matched portion to the # argument pattern. Yields when the pattern doesn't match substring. # - def parse_arg(arg) # :nodoc: + private def parse_arg(arg) # :nodoc: pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) @@ -580,14 +580,13 @@ def parse_arg(arg) # :nodoc: yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end - private :parse_arg # # Parses argument, converts and returns +arg+, +block+ and result of # conversion. Yields at semi-error condition instead of raising an # exception. # - def conv_arg(arg, val = []) # :nodoc: + private def conv_arg(arg, val = []) # :nodoc: v, = *val if conv val = conv.call(*val) @@ -599,7 +598,6 @@ def conv_arg(arg, val = []) # :nodoc: end return arg, block, val end - private :conv_arg # # Produces the summary text. Each line of the summary is yielded to the @@ -883,14 +881,13 @@ def reject(t) # +lopts+:: Long style option list. # +nlopts+:: Negated long style options list. # - def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: + private def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end - private :update # # Inserts +switch+ at the head of the list, and associates short, long @@ -1459,14 +1456,13 @@ def to_a; summarize("#{banner}".split(/^/)) end # +prv+:: Previously specified argument. # +msg+:: Exception message. # - def notwice(obj, prv, msg) # :nodoc: + private def notwice(obj, prv, msg) # :nodoc: unless !prv or prv == obj raise(ArgumentError, "argument #{msg} given twice: #{obj}", ParseError.filter_backtrace(caller(2))) end obj end - private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: @@ -1733,7 +1729,7 @@ def order!(argv = default_argv, into: nil, **keywords, &nonopt) parse_in_order(argv, setter, **keywords, &nonopt) end - def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: + private def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { @@ -1824,10 +1820,9 @@ def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, argv end - private :parse_in_order # Calls callback with _val_. - def callback!(cb, max_arity, *args) # :nodoc: + private def callback!(cb, max_arity, *args) # :nodoc: args.compact! if (size = args.size) < max_arity and cb.to_proc.lambda? @@ -1837,7 +1832,6 @@ def callback!(cb, max_arity, *args) # :nodoc: end cb.call(*args) end - private :callback! # # Parses command line arguments +argv+ in permutation mode and returns @@ -1951,24 +1945,22 @@ def self.getopts(*args, symbolize_names: false) # Traverses @stack, sending each element method +id+ with +args+ and # +block+. # - def visit(id, *args, &block) # :nodoc: + private def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| el.__send__(id, *args, &block) end nil end - private :visit # # Searches +key+ in @stack for +id+ hash and returns or yields the result. # - def search(id, key) # :nodoc: + private def search(id, key) # :nodoc: block_given = block_given? visit(:search, id, key) do |k| return block_given ? yield(k) : k end end - private :search # # Completes shortened long style option switch and returns pair of @@ -1979,7 +1971,7 @@ def search(id, key) # :nodoc: # +icase+:: Search case insensitive if true. # +pat+:: Optional pattern for completion. # - def complete(typ, opt, icase = false, *pat) # :nodoc: + private def complete(typ, opt, icase = false, *pat) # :nodoc: if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end @@ -1989,7 +1981,6 @@ def complete(typ, opt, icase = false, *pat) # :nodoc: exc = ambiguous ? AmbiguousOption : InvalidOption raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end - private :complete # # Returns additional info. From 38022961ae399015c542c409948aa4885e8e56e0 Mon Sep 17 00:00:00 2001 From: Sam Westerman Date: Tue, 25 Nov 2025 18:26:06 -0800 Subject: [PATCH 1374/2435] [ruby/optparse] Remove unneeded `public` Removed public visibility from the candidate method. https://github.com/ruby/optparse/commit/9a784a89a2 --- lib/optparse.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 4140b5f0743cc2..e0b0ff011b5455 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -472,7 +472,6 @@ def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end - public def complete(key, icase = false, pat = nil) candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 From fb642b78cb89dd26ade620bda16ca78b570a907e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Sun, 23 Nov 2025 11:36:58 +0100 Subject: [PATCH 1375/2435] [ruby/json] Test that depth of unfrozen State does not change https://github.com/ruby/json/commit/9d32cf4618 --- test/json/json_generator_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 54a2ec61409eeb..f1fb72ee293525 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -915,4 +915,13 @@ def test_frozen end end end + + # The case when the State is frozen is tested in JSONCoderTest#test_nesting_recovery + def test_nesting_recovery + state = JSON::State.new + ary = [] + ary << ary + assert_raise(JSON::NestingError) { state.generate_new(ary) } + assert_equal '{"a":1}', state.generate({ a: 1 }) + end end From dd6a4ea2b9cf6f3ab1ea01e044f3d789fb6d5450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 24 Nov 2025 15:40:06 +0100 Subject: [PATCH 1376/2435] [ruby/json] Test depth https://github.com/ruby/json/commit/d02e40324a --- test/json/json_generator_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index f1fb72ee293525..0931668f3314d0 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -282,6 +282,16 @@ def test_allow_nan end def test_depth + pretty = { object_nl: "\n", array_nl: "\n", space: " ", indent: " " } + state = JSON.state.new(**pretty) + assert_equal %({\n "foo": 42\n}), JSON.generate({ foo: 42 }, pretty) + assert_equal %({\n "foo": 42\n}), state.generate(foo: 42) + state.depth = 1 + assert_equal %({\n "foo": 42\n }), JSON.generate({ foo: 42 }, pretty.merge(depth: 1)) + assert_equal %({\n "foo": 42\n }), state.generate(foo: 42) + end + + def test_depth_nesting_error ary = []; ary << ary assert_raise(JSON::NestingError) { generate(ary) } assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) } From adc0521b22968c9f580ebb5b4d5aa674bd906cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Wed, 26 Nov 2025 11:03:33 +0100 Subject: [PATCH 1377/2435] [ruby/json] Test to_json using State#depth https://github.com/ruby/json/commit/ac0a980668 --- test/json/json_generator_test.rb | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 0931668f3314d0..c01ed678fcfda4 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -92,6 +92,46 @@ def test_dump_strict assert_equal '"World"', "World".to_json(strict: true) end + def test_state_depth_to_json + depth = Object.new + def depth.to_json(state) + JSON::State.from_state(state).depth.to_s + end + + assert_equal "0", JSON.generate(depth) + assert_equal "[1]", JSON.generate([depth]) + assert_equal %({"depth":1}), JSON.generate(depth: depth) + assert_equal "[[2]]", JSON.generate([[depth]]) + assert_equal %([{"depth":2}]), JSON.generate([{depth: depth}]) + + state = JSON::State.new + assert_equal "0", state.generate(depth) + assert_equal "[1]", state.generate([depth]) + assert_equal %({"depth":1}), state.generate(depth: depth) + assert_equal "[[2]]", state.generate([[depth]]) + assert_equal %([{"depth":2}]), state.generate([{depth: depth}]) + end + + def test_state_depth_to_json_recursive + recur = Object.new + def recur.to_json(state = nil, *) + state = JSON::State.from_state(state) + if state.depth < 3 + state.generate([state.depth, self]) + else + state.generate([state.depth]) + end + end + + assert_raise(NestingError) { JSON.generate(recur, max_nesting: 3) } + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) + + state = JSON::State.new(max_nesting: 3) + assert_raise(NestingError) { state.generate(recur) } + state.max_nesting = 4 + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) + end + def test_generate_pretty json = pretty_generate({}) assert_equal('{}', json) From 4b8ec8c9fc79859456661381e3e138ecc3e8c298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Sat, 22 Nov 2025 12:30:44 +0100 Subject: [PATCH 1378/2435] [ruby/json] Add depth to struct generate_json_data Instead of incrementing JSON_Generator_State::depth, we now increment generate_json_data::depth, and only copied at the end. https://github.com/ruby/json/commit/5abd434907 --- ext/json/generator/generator.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 8d04bef53f1638..7125f44379d927 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -60,6 +60,7 @@ struct generate_json_data { JSON_Generator_State *state; VALUE obj; generator_func func; + long depth; }; static VALUE cState_from_state_s(VALUE self, VALUE opts); @@ -972,6 +973,8 @@ static inline VALUE vstate_get(struct generate_json_data *data) if (RB_UNLIKELY(!data->vstate)) { vstate_spill(data); } + GET_STATE(data->vstate); + state->depth = data->depth; return data->vstate; } @@ -1145,7 +1148,7 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) FBuffer *buffer = data->buffer; JSON_Generator_State *state = data->state; - long depth = state->depth; + long depth = data->depth; int key_type = rb_type(key); if (arg->first) { @@ -1219,9 +1222,9 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) static inline long increase_depth(struct generate_json_data *data) { JSON_Generator_State *state = data->state; - long depth = ++state->depth; + long depth = ++data->depth; if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) { - rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth); + rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --data->depth); } return depth; } @@ -1232,7 +1235,7 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat if (RHASH_SIZE(obj) == 0) { fbuffer_append(buffer, "{}", 2); - --data->state->depth; + --data->depth; return; } @@ -1245,7 +1248,7 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat }; rb_hash_foreach(obj, json_object_i, (VALUE)&arg); - depth = --data->state->depth; + depth = --data->depth; if (RB_UNLIKELY(data->state->object_nl)) { fbuffer_append_str(buffer, data->state->object_nl); if (RB_UNLIKELY(data->state->indent)) { @@ -1261,7 +1264,7 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data if (RARRAY_LEN(obj) == 0) { fbuffer_append(buffer, "[]", 2); - --data->state->depth; + --data->depth; return; } @@ -1277,7 +1280,7 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data } generate_json(buffer, data, RARRAY_AREF(obj, i)); } - data->state->depth = --depth; + data->depth = --depth; if (RB_UNLIKELY(data->state->array_nl)) { fbuffer_append_str(buffer, data->state->array_nl); if (RB_UNLIKELY(data->state->indent)) { @@ -1358,7 +1361,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data if (casted_obj != obj) { increase_depth(data); generate_json(buffer, data, casted_obj); - data->state->depth--; + data->depth--; return; } } @@ -1477,6 +1480,7 @@ static VALUE generate_json_ensure(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); + data->state->depth = data->depth; return Qundef; } @@ -1495,6 +1499,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, .buffer = &buffer, .vstate = self, .state = state, + .depth = state->depth, .obj = obj, .func = func }; @@ -1541,6 +1546,7 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) .buffer = &buffer, .vstate = Qfalse, .state = &new_state, + .depth = new_state.depth, .obj = obj, .func = generate_json }; @@ -2061,6 +2067,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) .buffer = &buffer, .vstate = Qfalse, .state = &state, + .depth = state.depth, .obj = obj, .func = generate_json, }; From 98a9667736f56336c920770095f9c367add2257b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Sat, 22 Nov 2025 13:59:16 +0100 Subject: [PATCH 1379/2435] [ruby/json] Don't write depth to JSON_Generator_State in some cases For `JSON.generate` and `JSON::State#generate_new`, don't copy generate_json_data::depth to JSON_Generator_State::depth. In `JSON.generate`, the JSON_Generator_State is on the stack and discarded anyway. In `JSON::State#generate_new`, we copy the struct to avoid mutating the original one. https://github.com/ruby/json/commit/873b29ea34 --- ext/json/generator/generator.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 7125f44379d927..8f90806259b111 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1476,7 +1476,8 @@ static VALUE generate_json_try(VALUE d) return fbuffer_finalize(data->buffer); } -static VALUE generate_json_ensure(VALUE d) +// Preserves the deprecated behavior of State#depth being set. +static VALUE generate_json_ensure_deprecated(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); @@ -1485,6 +1486,14 @@ static VALUE generate_json_ensure(VALUE d) return Qundef; } +static VALUE generate_json_ensure(VALUE d) +{ + struct generate_json_data *data = (struct generate_json_data *)d; + fbuffer_free(data->buffer); + + return Qundef; +} + static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) { GET_STATE(self); @@ -1503,7 +1512,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, .obj = obj, .func = func }; - return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure_deprecated, (VALUE)&data); } /* call-seq: From ab3b79ea3db7779a9ee9eb6e39226d23b50fd012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Sat, 22 Nov 2025 14:57:30 +0100 Subject: [PATCH 1380/2435] [ruby/json] Don't copy JSON_Generator_State in generate_new Now that the state isn't mutated in generate_new, we no longer need to copy the struct, we can just use it. https://github.com/ruby/json/commit/d7964f8892 --- ext/json/generator/generator.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 8f90806259b111..32a9b485139041 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1539,12 +1539,6 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) GET_STATE(self); - JSON_Generator_State new_state; - MEMCPY(&new_state, state, JSON_Generator_State, 1); - - // FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently. - new_state.depth = 0; - char stack_buffer[FBUFFER_STACK_SIZE]; FBuffer buffer = { .io = RTEST(io) ? io : Qfalse, @@ -1554,8 +1548,8 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) struct generate_json_data data = { .buffer = &buffer, .vstate = Qfalse, - .state = &new_state, - .depth = new_state.depth, + .state = state, + .depth = 0, .obj = obj, .func = generate_json }; From e057ff333a3b2964e52c8a12485374efa2464762 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 26 Nov 2025 18:06:58 +0100 Subject: [PATCH 1381/2435] [Doc] Fix duplicated entry in GC.stat documentation --- gc.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/gc.rb b/gc.rb index f944c653b5b467..ccad5ef2c1dbfa 100644 --- a/gc.rb +++ b/gc.rb @@ -195,7 +195,6 @@ def self.count # - +:heap_allocated_pages+: # The total number of allocated pages. # - +:heap_empty_pages+: - # - +:heap_allocated_pages+: # The number of pages with no live objects, and that could be released to the system. # - +:heap_sorted_length+: # The number of pages that can fit into the buffer that holds references to all pages. From 795e290ead63bdcc79e35d569759e07d594267ab Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 31 Jul 2025 09:10:32 -0700 Subject: [PATCH 1382/2435] Avoid extra set of age bit flags --- gc/default/default.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 9a2efab562402b..6439e3106093ab 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -839,7 +839,7 @@ RVALUE_AGE_GET(VALUE obj) } static void -RVALUE_AGE_SET(VALUE obj, int age) +RVALUE_AGE_SET_BITMAP(VALUE obj, int age) { RUBY_ASSERT(age <= RVALUE_OLD_AGE); bits_t *age_bits = GET_HEAP_PAGE(obj)->age_bits; @@ -847,6 +847,12 @@ RVALUE_AGE_SET(VALUE obj, int age) age_bits[RVALUE_AGE_BITMAP_INDEX(obj)] &= ~(RVALUE_AGE_BIT_MASK << (RVALUE_AGE_BITMAP_OFFSET(obj))); // shift the correct value in age_bits[RVALUE_AGE_BITMAP_INDEX(obj)] |= ((bits_t)age << RVALUE_AGE_BITMAP_OFFSET(obj)); +} + +static void +RVALUE_AGE_SET(VALUE obj, int age) +{ + RVALUE_AGE_SET_BITMAP(obj, age); if (age == RVALUE_OLD_AGE) { RB_FL_SET_RAW(obj, RUBY_FL_PROMOTED); } @@ -1581,7 +1587,7 @@ heap_page_add_freeobj(rb_objspace_t *objspace, struct heap_page *page, VALUE obj page->freelist = slot; asan_lock_freelist(page); - RVALUE_AGE_RESET(obj); + RVALUE_AGE_SET_BITMAP(obj, 0); if (RGENGC_CHECK_MODE && /* obj should belong to page */ @@ -6951,7 +6957,7 @@ gc_move(rb_objspace_t *objspace, VALUE src, VALUE dest, size_t src_slot_size, si } memset((void *)src, 0, src_slot_size); - RVALUE_AGE_RESET(src); + RVALUE_AGE_SET_BITMAP(src, 0); /* Set bits for object in new location */ if (remembered) { From 67a14e94c6d3f5c30221da2659cb2e5604ce5c6f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 20 Nov 2025 01:14:35 -0800 Subject: [PATCH 1383/2435] Set age bitmap outside of adding to freelist This allows us to do less work when allocating a fresh page. --- gc/default/default.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 6439e3106093ab..0858d94b89e79c 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1587,7 +1587,8 @@ heap_page_add_freeobj(rb_objspace_t *objspace, struct heap_page *page, VALUE obj page->freelist = slot; asan_lock_freelist(page); - RVALUE_AGE_SET_BITMAP(obj, 0); + // Should have already been reset + GC_ASSERT(RVALUE_AGE_GET(obj) == 0); if (RGENGC_CHECK_MODE && /* obj should belong to page */ @@ -2894,6 +2895,7 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) page->heap->final_slots_count--; page->final_slots--; page->free_slots++; + RVALUE_AGE_SET_BITMAP(zombie, 0); heap_page_add_freeobj(objspace, page, zombie); page->heap->total_freed_objects++; } @@ -3496,6 +3498,7 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit // always add free slots back to the swept pages freelist, // so that if we're compacting, we can re-use the slots (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, BASE_SLOT_SIZE); + RVALUE_AGE_SET_BITMAP(vp, 0); heap_page_add_freeobj(objspace, sweep_page, vp); gc_report(3, objspace, "page_sweep: %s is added to freelist\n", rb_obj_info(vp)); ctx->freed_slots++; @@ -3516,6 +3519,7 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit } gc_report(3, objspace, "page_sweep: %s is added to freelist\n", rb_obj_info(vp)); ctx->empty_slots++; + RVALUE_AGE_SET_BITMAP(vp, 0); heap_page_add_freeobj(objspace, sweep_page, vp); break; case T_ZOMBIE: @@ -4023,6 +4027,7 @@ invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_ struct heap_page *orig_page = GET_HEAP_PAGE(object); orig_page->free_slots++; + RVALUE_AGE_SET_BITMAP(object, 0); heap_page_add_freeobj(objspace, orig_page, object); GC_ASSERT(RVALUE_MARKED(objspace, forwarding_object)); From c42c6c27c37c8336b219678f858dabd17442737d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 26 Nov 2025 18:55:31 +0000 Subject: [PATCH 1384/2435] ZJIT: Remove dead unnecessary_transmutes allow ``` warning: unknown lint: `unnecessary_transmutes` --> zjit/src/cruby.rs:107:9 | 107 | #[allow(unnecessary_transmutes)] // https://github.com/rust-lang/rust-bindgen/issues/2807 | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unknown_lints)]` on by default ``` --- zjit/src/cruby.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1a60e2a7fe2a06..9070cb38be5f42 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -104,7 +104,6 @@ pub type RedefinitionFlag = u32; #[allow(unsafe_op_in_unsafe_fn)] #[allow(dead_code)] -#[allow(unnecessary_transmutes)] // https://github.com/rust-lang/rust-bindgen/issues/2807 #[allow(clippy::all)] // warning meant to help with reading; not useful for generated code mod autogened { use super::*; From 5f55c9c8fb8f401537e7121171747196e66c3ba0 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 25 Nov 2025 19:29:02 -0700 Subject: [PATCH 1385/2435] YJIT: Abort expandarray optimization if method_missing is defined Fixes: [Bug #21707] [AW: rewrote comments] Co-authored-by: Alan Wu --- bootstraptest/test_yjit.rb | 16 ++++++++++++++++ yjit/src/codegen.rs | 10 +++++++++- yjit/src/cruby.rs | 1 + yjit/src/stats.rs | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index f9cdca6f28c76a..bd44f3ac3ece0b 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -2680,6 +2680,22 @@ def expandarray_redefined_nilclass expandarray_redefined_nilclass } +assert_equal 'not_array', %q{ + def expandarray_not_array(obj) + a, = obj + a + end + + obj = Object.new + def obj.method_missing(m, *args, &block) + return [:not_array] if m == :to_ary + super + end + + expandarray_not_array(obj) + expandarray_not_array(obj) +} + assert_equal '[1, 2, nil]', %q{ def expandarray_rhs_too_small a, b, c = [1, 2] diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 9eeccddf6ce490..a426ad07737496 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2258,7 +2258,8 @@ fn gen_expandarray( let comptime_recv = jit.peek_at_stack(&asm.ctx, 0); - // If the comptime receiver is not an array + // If the comptime receiver is not an array, speculate for when the `rb_check_array_type()` + // conversion returns nil and without side-effects (e.g. arbitrary method calls). if !unsafe { RB_TYPE_P(comptime_recv, RUBY_T_ARRAY) } { // at compile time, ensure to_ary is not defined let target_cme = unsafe { rb_callable_method_entry_or_negative(comptime_recv.class_of(), ID!(to_ary)) }; @@ -2270,6 +2271,13 @@ fn gen_expandarray( return None; } + // Bail when method_missing is defined to avoid generating code to call it. + // Also, for simplicity, bail when BasicObject#method_missing has been removed. + if !assume_method_basic_definition(jit, asm, comptime_recv.class_of(), ID!(method_missing)) { + gen_counter_incr(jit, asm, Counter::expandarray_method_missing); + return None; + } + // invalidate compile block if to_ary is later defined jit.assume_method_lookup_stable(asm, target_cme); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 0d9e3b74dad874..cfaf48c3f0b68e 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -816,6 +816,7 @@ pub(crate) mod ids { def_ids! { name: NULL content: b"" name: respond_to_missing content: b"respond_to_missing?" + name: method_missing content: b"method_missing" name: to_ary content: b"to_ary" name: to_s content: b"to_s" name: eq content: b"==" diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 817c842cf4144d..e8ce7b8b353d63 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -496,6 +496,7 @@ make_counters! { expandarray_postarg, expandarray_not_array, expandarray_to_ary, + expandarray_method_missing, expandarray_chain_max_depth, // getblockparam From 1660b8145c30f53771671dec343fa7025a953fb5 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 26 Nov 2025 16:23:34 -0500 Subject: [PATCH 1386/2435] Eliminate redundant work and branching when marking T_OBJECT (#15274) --- gc.c | 6 ++++-- ractor.c | 2 +- shape.h | 20 ++++++++++++++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/gc.c b/gc.c index 26afb4e71817bc..4f6751316f07da 100644 --- a/gc.c +++ b/gc.c @@ -3227,19 +3227,21 @@ rb_gc_mark_children(void *objspace, VALUE obj) } case T_OBJECT: { + uint32_t len; if (rb_shape_obj_too_complex_p(obj)) { gc_mark_tbl_no_pin(ROBJECT_FIELDS_HASH(obj)); + len = ROBJECT_FIELDS_COUNT_COMPLEX(obj); } else { const VALUE * const ptr = ROBJECT_FIELDS(obj); - uint32_t len = ROBJECT_FIELDS_COUNT(obj); + len = ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); for (uint32_t i = 0; i < len; i++) { gc_mark_internal(ptr[i]); } } - attr_index_t fields_count = ROBJECT_FIELDS_COUNT(obj); + attr_index_t fields_count = (attr_index_t)len; if (fields_count) { VALUE klass = RBASIC_CLASS(obj); diff --git a/ractor.c b/ractor.c index 3fc507128c1939..41c32b79def68c 100644 --- a/ractor.c +++ b/ractor.c @@ -1796,7 +1796,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) if (d.stop) return 1; } else { - uint32_t len = ROBJECT_FIELDS_COUNT(obj); + uint32_t len = ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); VALUE *ptr = ROBJECT_FIELDS(obj); for (uint32_t i = 0; i < len; i++) { diff --git a/shape.h b/shape.h index 6e4a1f079b44f3..ec5c25b32f16f5 100644 --- a/shape.h +++ b/shape.h @@ -367,16 +367,28 @@ ROBJECT_SET_FIELDS_HASH(VALUE obj, const st_table *tbl) ROBJECT(obj)->as.heap.fields = (VALUE *)tbl; } +static inline uint32_t +ROBJECT_FIELDS_COUNT_COMPLEX(VALUE obj) +{ + return (uint32_t)rb_st_table_size(ROBJECT_FIELDS_HASH(obj)); +} + +static inline uint32_t +ROBJECT_FIELDS_COUNT_NOT_COMPLEX(VALUE obj) +{ + RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); + RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index; +} + static inline uint32_t ROBJECT_FIELDS_COUNT(VALUE obj) { if (rb_shape_obj_too_complex_p(obj)) { - return (uint32_t)rb_st_table_size(ROBJECT_FIELDS_HASH(obj)); + return ROBJECT_FIELDS_COUNT_COMPLEX(obj); } else { - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index; + return ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); } } From bee02c41bc15780a45f47986ef638e17ca323ec3 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 26 Nov 2025 17:14:55 -0500 Subject: [PATCH 1387/2435] Fix a ractor barrier issue during VM cleanup. (#15091) --- ractor.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ractor.c b/ractor.c index 41c32b79def68c..8238d9a456e233 100644 --- a/ractor.c +++ b/ractor.c @@ -834,13 +834,11 @@ rb_ractor_terminate_all(void) VM_ASSERT(cr == GET_RACTOR()); // only main-ractor's main-thread should kick it. - if (vm->ractor.cnt > 1) { - RB_VM_LOCK(); - { - ractor_terminal_interrupt_all(vm); // kill all ractors - } - RB_VM_UNLOCK(); + RB_VM_LOCK(); + { + ractor_terminal_interrupt_all(vm); // kill all ractors } + RB_VM_UNLOCK(); rb_thread_terminate_all(GET_THREAD()); // kill other threads in main-ractor and wait RB_VM_LOCK(); @@ -853,6 +851,17 @@ rb_ractor_terminate_all(void) rb_vm_ractor_blocking_cnt_inc(vm, cr, __FILE__, __LINE__); rb_del_running_thread(rb_ec_thread_ptr(cr->threads.running_ec)); rb_vm_cond_timedwait(vm, &vm->ractor.sync.terminate_cond, 1000 /* ms */); +#ifdef RUBY_THREAD_PTHREAD_H + while (vm->ractor.sched.barrier_waiting) { + // A barrier is waiting. Threads relinquish the VM lock before joining the barrier and + // since we just acquired the VM lock back, we're blocking other threads from joining it. + // We loop until the barrier is over. We can't join this barrier because our thread isn't added to + // running_threads until the call below to `rb_add_running_thread`. + RB_VM_UNLOCK(); + unsigned int lev; + RB_VM_LOCK_ENTER_LEV_NB(&lev); + } +#endif rb_add_running_thread(rb_ec_thread_ptr(cr->threads.running_ec)); rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__); From 52426a22de8ce5e2f02ce4169a1b6944e2165b46 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 27 Nov 2025 08:10:56 +0900 Subject: [PATCH 1388/2435] Remove an excess colon [ci skip] --- defs/gmake.mk | 2 +- template/Makefile.in | 2 +- win32/Makefile.sub | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index da98f70d9a7cb7..36d65c0ea0b4c8 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -275,7 +275,7 @@ pull-github: fetch-github $(call pull-github,$(PR)) define pull-github - $(eval GITHUB_MERGE_BASE := $(shell $(GIT_LOG_FORMAT):%H -1) + $(eval GITHUB_MERGE_BASE := $(shell $(GIT_LOG_FORMAT)%H -1) $(eval GITHUB_MERGE_BRANCH := $(shell $(GIT_IN_SRC) symbolic-ref --short HEAD)) $(eval GITHUB_MERGE_WORKTREE := $(shell mktemp -d "$(srcdir)/gh-$(1)-XXXXXX")) $(GIT_IN_SRC) worktree prune diff --git a/template/Makefile.in b/template/Makefile.in index 2987c86488cf2f..7bc40b9d019604 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -748,4 +748,4 @@ yes-test-syntax-suggest: $(PREPARE_SYNTAX_SUGGEST) no-test-syntax-suggest: yesterday: - $(GIT_IN_SRC) reset --hard `TZ=UTC-9 $(GIT_LOG_FORMAT):%H -1 --before=00:00` + $(GIT_IN_SRC) reset --hard `TZ=UTC-9 $(GIT_LOG_FORMAT)%H -1 --before=00:00` diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 04ec2873275dd5..edbe2a340209d3 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1393,5 +1393,5 @@ exts: rubyspec-capiext yesterday: (set TZ=UTC-9) && \ for /f "usebackq" %H in \ - (`$(GIT_LOG_FORMAT):%H -1 "--before=00:00"`) do \ + (`$(GIT_LOG_FORMAT)%H -1 "--before=00:00"`) do \ $(GIT_IN_SRC) reset --hard %%H From db94a79da432bcdb9d48517733f11ccf03c7cd5d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 26 Nov 2025 15:36:00 -0800 Subject: [PATCH 1389/2435] ZJIT: Count fallback reasons for set/get/definedivar (#15324) lobsters: ``` Top-4 setivar fallback reasons (100.0% of total 7,789,008): shape_transition: 6,074,085 (78.0%) not_monomorphic: 1,484,013 (19.1%) not_t_object: 172,629 ( 2.2%) too_complex: 58,281 ( 0.7%) Top-3 getivar fallback reasons (100.0% of total 9,348,832): not_t_object: 4,658,833 (49.8%) not_monomorphic: 4,542,316 (48.6%) too_complex: 147,683 ( 1.6%) Top-3 definedivar fallback reasons (100.0% of total 366,383): not_monomorphic: 361,389 (98.6%) too_complex: 3,062 ( 0.8%) not_t_object: 1,932 ( 0.5%) ``` railsbench: ``` Top-3 setivar fallback reasons (100.0% of total 15,119,057): shape_transition: 13,760,763 (91.0%) not_monomorphic: 982,368 ( 6.5%) not_t_object: 375,926 ( 2.5%) Top-2 getivar fallback reasons (100.0% of total 14,438,747): not_t_object: 7,643,870 (52.9%) not_monomorphic: 6,794,877 (47.1%) Top-2 definedivar fallback reasons (100.0% of total 209,613): not_monomorphic: 209,526 (100.0%) not_t_object: 87 ( 0.0%) ``` shipit: ``` Top-3 setivar fallback reasons (100.0% of total 14,516,254): shape_transition: 8,613,512 (59.3%) not_monomorphic: 5,761,398 (39.7%) not_t_object: 141,344 ( 1.0%) Top-2 getivar fallback reasons (100.0% of total 21,016,444): not_monomorphic: 11,313,482 (53.8%) not_t_object: 9,702,962 (46.2%) Top-2 definedivar fallback reasons (100.0% of total 290,382): not_monomorphic: 287,755 (99.1%) not_t_object: 2,627 ( 0.9%) ``` --- zjit.rb | 9 ++-- zjit/src/codegen.rs | 2 - zjit/src/hir.rs | 14 ++++++ zjit/src/hir/opt_tests.rs | 15 +++++- zjit/src/stats.rs | 99 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 128 insertions(+), 11 deletions(-) diff --git a/zjit.rb b/zjit.rb index fc306c19a47fba..d128adead6fe39 100644 --- a/zjit.rb +++ b/zjit.rb @@ -183,6 +183,9 @@ def stats_string print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'uncategorized_fallback_yarv_insn_', prompt: 'instructions with uncategorized fallback reason', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'setivar_fallback_', prompt: 'setivar fallback reasons', buf:, stats:, limit: 5) + print_counters_with_prefix(prefix: 'getivar_fallback_', prompt: 'getivar fallback reasons', buf:, stats:, limit: 5) + print_counters_with_prefix(prefix: 'definedivar_fallback_', prompt: 'definedivar fallback reasons', buf:, stats:, limit: 5) print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can @@ -201,6 +204,9 @@ def stats_string :send_count, :dynamic_send_count, :optimized_send_count, + :dynamic_setivar_count, + :dynamic_getivar_count, + :dynamic_definedivar_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, @@ -208,9 +214,6 @@ def stats_string :variadic_cfunc_optimized_send_count, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ - :dynamic_getivar_count, - :dynamic_setivar_count, - :compiled_iseq_count, :failed_iseq_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 3300219ccda07a..66436b2374b4ff 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -892,7 +892,6 @@ fn gen_ccall_variadic( /// Emit an uncached instance variable lookup fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry) -> Opnd { - gen_incr_counter(asm, Counter::dynamic_getivar_count); if ic.is_null() { asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) } else { @@ -903,7 +902,6 @@ fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: /// Emit an uncached instance variable store fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) { - gen_incr_counter(asm, Counter::dynamic_setivar_count); // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. gen_prepare_non_leaf_call(jit, asm, state); if ic.is_null() { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b7149ad14c66fe..fbc9d80700dcaf 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2806,19 +2806,23 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); @@ -2845,19 +2849,23 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); @@ -2877,28 +2885,34 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_frozen() { // Can't set ivars on frozen objects + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_frozen)); self.push_insn_id(block, insn_id); continue; } let mut ivar_index: u16 = 0; if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { // TODO(max): Shape transition + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c460942fc13e42..b32da5a9ebb8e5 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3343,6 +3343,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint SingleRactorMode + IncrCounter getivar_fallback_not_monomorphic v11:BasicObject = GetIvar v6, :@foo CheckInterrupts Return v11 @@ -3366,6 +3367,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_monomorphic SetIvar v6, :@foo, v10 CheckInterrupts Return v10 @@ -3442,6 +3444,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): + IncrCounter definedivar_fallback_not_t_object v10:StringExact|NilClass = DefinedIvar v6, :@a CheckInterrupts Return v10 @@ -3473,6 +3476,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): + IncrCounter definedivar_fallback_not_monomorphic v10:StringExact|NilClass = DefinedIvar v6, :@a CheckInterrupts Return v10 @@ -3525,6 +3529,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v6, :@foo, v10 CheckInterrupts Return v10 @@ -3554,6 +3559,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_t_object SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -3587,6 +3593,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_monomorphic SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -3618,6 +3625,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -5527,6 +5535,7 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter setivar_fallback_shape_transition SetIvar v26, :@foo, v16 CheckInterrupts Return v16 @@ -5558,6 +5567,7 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter setivar_fallback_shape_transition SetIvar v26, :@foo, v16 CheckInterrupts Return v16 @@ -8609,17 +8619,18 @@ mod hir_opt_tests { SetLocal l0, EP@3, v27 v39:BasicObject = GetLocal l0, EP@3 PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v14, :@formatted, v39 v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Class@0x1008) - v59:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 + v60:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 v48:BasicObject = GetLocal l0, EP@6 v49:BasicObject = GetLocal l0, EP@5 v50:BasicObject = GetLocal l0, EP@4 v51:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v59 + Return v60 "); } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 1277db5b7e9e7d..890d92bc569a28 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -24,6 +24,15 @@ macro_rules! make_counters { optimized_send { $($optimized_send_counter_name:ident,)+ } + dynamic_setivar { + $($dynamic_setivar_counter_name:ident,)+ + } + dynamic_getivar { + $($dynamic_getivar_counter_name:ident,)+ + } + dynamic_definedivar { + $($dynamic_definedivar_counter_name:ident,)+ + } $($counter_name:ident,)+ ) => { /// Struct containing the counter values @@ -33,6 +42,9 @@ macro_rules! make_counters { $(pub $exit_counter_name: u64,)+ $(pub $dynamic_send_counter_name: u64,)+ $(pub $optimized_send_counter_name: u64,)+ + $(pub $dynamic_setivar_counter_name: u64,)+ + $(pub $dynamic_getivar_counter_name: u64,)+ + $(pub $dynamic_definedivar_counter_name: u64,)+ $(pub $counter_name: u64,)+ } @@ -44,6 +56,9 @@ macro_rules! make_counters { $($exit_counter_name,)+ $($dynamic_send_counter_name,)+ $($optimized_send_counter_name,)+ + $($dynamic_setivar_counter_name,)+ + $($dynamic_getivar_counter_name,)+ + $($dynamic_definedivar_counter_name,)+ $($counter_name,)+ } @@ -54,6 +69,9 @@ macro_rules! make_counters { $( Counter::$exit_counter_name => stringify!($exit_counter_name), )+ $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name), )+ $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name), )+ + $( Counter::$dynamic_setivar_counter_name => stringify!($dynamic_setivar_counter_name), )+ + $( Counter::$dynamic_getivar_counter_name => stringify!($dynamic_getivar_counter_name), )+ + $( Counter::$dynamic_definedivar_counter_name => stringify!($dynamic_definedivar_counter_name), )+ $( Counter::$counter_name => stringify!($counter_name), )+ } } @@ -64,6 +82,9 @@ macro_rules! make_counters { $( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+ $( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+ $( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+ + $( stringify!($dynamic_setivar_counter_name) => Some(Counter::$dynamic_setivar_counter_name), )+ + $( stringify!($dynamic_getivar_counter_name) => Some(Counter::$dynamic_getivar_counter_name), )+ + $( stringify!($dynamic_definedivar_counter_name) => Some(Counter::$dynamic_definedivar_counter_name), )+ $( stringify!($counter_name) => Some(Counter::$counter_name), )+ _ => None, } @@ -77,6 +98,9 @@ macro_rules! make_counters { $( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+ $( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+ $( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+ + $( Counter::$dynamic_setivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_setivar_counter_name), )+ + $( Counter::$dynamic_getivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_getivar_counter_name), )+ + $( Counter::$dynamic_definedivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_definedivar_counter_name), )+ $( Counter::$optimized_send_counter_name => std::ptr::addr_of_mut!(counters.$optimized_send_counter_name), )+ $( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+ } @@ -103,6 +127,21 @@ macro_rules! make_counters { $( Counter::$optimized_send_counter_name, )+ ]; + /// List of other counters that are summed as dynamic_setivar_count. + pub const DYNAMIC_SETIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_setivar_counter_name, )+ + ]; + + /// List of other counters that are summed as dynamic_getivar_count. + pub const DYNAMIC_GETIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_getivar_counter_name, )+ + ]; + + /// List of other counters that are summed as dynamic_definedivar_count. + pub const DYNAMIC_DEFINEDIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_definedivar_counter_name, )+ + ]; + /// List of other counters that are available only for --zjit-stats. pub const OTHER_COUNTERS: &'static [Counter] = &[ $( Counter::$counter_name, )+ @@ -207,6 +246,35 @@ make_counters! { variadic_cfunc_optimized_send_count, } + // Ivar fallback counters that are summed as dynamic_setivar_count + dynamic_setivar { + // setivar_fallback_: Fallback reasons for dynamic setivar instructions + setivar_fallback_not_monomorphic, + setivar_fallback_immediate, + setivar_fallback_not_t_object, + setivar_fallback_too_complex, + setivar_fallback_frozen, + setivar_fallback_shape_transition, + } + + // Ivar fallback counters that are summed as dynamic_getivar_count + dynamic_getivar { + // getivar_fallback_: Fallback reasons for dynamic getivar instructions + getivar_fallback_not_monomorphic, + getivar_fallback_immediate, + getivar_fallback_not_t_object, + getivar_fallback_too_complex, + } + + // Ivar fallback counters that are summed as dynamic_definedivar_count + dynamic_definedivar { + // definedivar_fallback_: Fallback reasons for dynamic definedivar instructions + definedivar_fallback_not_monomorphic, + definedivar_fallback_immediate, + definedivar_fallback_not_t_object, + definedivar_fallback_too_complex, + } + // compile_error_: Compile error reasons compile_error_iseq_stack_too_large, compile_error_exception_handler, @@ -236,10 +304,6 @@ make_counters! { // The number of times YARV instructions are executed on JIT code zjit_insn_count, - // The number of times we do a dynamic ivar lookup from JIT code - dynamic_getivar_count, - dynamic_setivar_count, - // Method call def_type related to send without block fallback to dynamic dispatch unspecialized_send_without_block_def_type_iseq, unspecialized_send_without_block_def_type_cfunc, @@ -657,6 +721,33 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, "optimized_send_count", optimized_send_count); set_stat_usize!(hash, "send_count", dynamic_send_count + optimized_send_count); + // Set send fallback counters for each setivar fallback reason + let mut dynamic_setivar_count = 0; + for &counter in DYNAMIC_SETIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_setivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_setivar_count", dynamic_setivar_count); + + // Set send fallback counters for each getivar fallback reason + let mut dynamic_getivar_count = 0; + for &counter in DYNAMIC_GETIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_getivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_getivar_count", dynamic_getivar_count); + + // Set send fallback counters for each definedivar fallback reason + let mut dynamic_definedivar_count = 0; + for &counter in DYNAMIC_DEFINEDIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_definedivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_definedivar_count", dynamic_definedivar_count); + // Set send fallback counters for Uncategorized let send_fallback_counters = ZJITState::get_send_fallback_counters(); for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) { From 970b18e9a94ff3e6496fb7324cff0798ffec6f24 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 26 Nov 2025 13:33:10 -0800 Subject: [PATCH 1390/2435] Clear fields obj when removing This fixes a bug where the gen_fields_cache could become invalid when the last ivar was removed. Also adds more assertions. --- test/ruby/test_variable.rb | 14 ++++++++++++++ variable.c | 21 ++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 68434e0b6c479b..d4ba0f9fc74b6c 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -515,6 +515,20 @@ def test_genivar_cache assert_equal 4, instance.instance_variable_get(:@a4), bug21547 end + def test_genivar_cache_free + str = +"hello" + str.instance_variable_set(:@x, :old_value) + + str.instance_variable_get(:@x) # populate cache + + Fiber.new { + str.remove_instance_variable(:@x) + str.instance_variable_set(:@x, :new_value) + }.resume + + assert_equal :new_value, str.instance_variable_get(:@x) + end + private def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:) local_variables diff --git a/variable.c b/variable.c index d7e04265c4276b..ae122136eeb7ad 100644 --- a/variable.c +++ b/variable.c @@ -1236,6 +1236,18 @@ rb_mark_generic_ivar(VALUE obj) } } +VALUE +rb_obj_fields_generic_uncached(VALUE obj) +{ + VALUE fields_obj = 0; + RB_VM_LOCKING() { + if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) { + rb_bug("Object is missing entry in generic_fields_tbl"); + } + } + return fields_obj; +} + VALUE rb_obj_fields(VALUE obj, ID field_name) { @@ -1263,13 +1275,10 @@ rb_obj_fields(VALUE obj, ID field_name) rb_execution_context_t *ec = GET_EC(); if (ec->gen_fields_cache.obj == obj && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { fields_obj = ec->gen_fields_cache.fields_obj; + RUBY_ASSERT(fields_obj == rb_obj_fields_generic_uncached(obj)); } else { - RB_VM_LOCKING() { - if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) { - rb_bug("Object is missing entry in generic_fields_tbl"); - } - } + fields_obj = rb_obj_fields_generic_uncached(obj); ec->gen_fields_cache.fields_obj = fields_obj; ec->gen_fields_cache.obj = obj; } @@ -1320,7 +1329,9 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie ivar_ractor_check(obj, field_name); if (!fields_obj) { + RUBY_ASSERT(original_fields_obj); rb_free_generic_ivar(obj); + rb_imemo_fields_clear(original_fields_obj); return; } From 5bef7577b3b336a709935dd39e69abcf397c4684 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 26 Nov 2025 14:02:17 -0800 Subject: [PATCH 1391/2435] Ensure we don't dereference fields_obj as Qundef We rely on the GC to clear this when the GC is run on another EC than the cache. --- test/ruby/test_variable.rb | 16 ++++++++++++++++ variable.c | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index d4ba0f9fc74b6c..13b8a7905f2b46 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -529,6 +529,22 @@ def test_genivar_cache_free assert_equal :new_value, str.instance_variable_get(:@x) end + def test_genivar_cache_invalidated_by_gc + str = +"hello" + str.instance_variable_set(:@x, :old_value) + + str.instance_variable_get(:@x) # populate cache + + Fiber.new { + str.remove_instance_variable(:@x) + str.instance_variable_set(:@x, :new_value) + }.resume + + GC.start + + assert_equal :new_value, str.instance_variable_get(:@x) + end + private def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:) local_variables diff --git a/variable.c b/variable.c index ae122136eeb7ad..f7ee56122024b7 100644 --- a/variable.c +++ b/variable.c @@ -1273,7 +1273,7 @@ rb_obj_fields(VALUE obj, ID field_name) generic_fields: { rb_execution_context_t *ec = GET_EC(); - if (ec->gen_fields_cache.obj == obj && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { + if (ec->gen_fields_cache.obj == obj && !UNDEF_P(ec->gen_fields_cache.fields_obj) && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { fields_obj = ec->gen_fields_cache.fields_obj; RUBY_ASSERT(fields_obj == rb_obj_fields_generic_uncached(obj)); } @@ -1309,6 +1309,8 @@ rb_free_generic_ivar(VALUE obj) default: generic_fields: { + // Other EC may have stale caches, so fields_obj should be + // invalidated and the GC will replace with Qundef rb_execution_context_t *ec = GET_EC(); if (ec->gen_fields_cache.obj == obj) { ec->gen_fields_cache.obj = Qundef; From a60cd8787cb65a6077f609652f6042143d81b227 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 26 Nov 2025 14:37:30 -0800 Subject: [PATCH 1392/2435] Error if deleting a nonexistent obj from geniv I don't think this ever happened, but we should raise the same error we'd raise on lookup --- variable.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/variable.c b/variable.c index f7ee56122024b7..d4c5d91e25d6dc 100644 --- a/variable.c +++ b/variable.c @@ -1317,7 +1317,9 @@ rb_free_generic_ivar(VALUE obj) ec->gen_fields_cache.fields_obj = Qundef; } RB_VM_LOCKING() { - st_delete(generic_fields_tbl_no_ractor_check(), &key, &value); + if (!st_delete(generic_fields_tbl_no_ractor_check(), &key, &value)) { + rb_bug("Object is missing entry in generic_fields_tbl"); + } } } } From 2e770cdf773d79327cfdeb8178a1cb9b340f4560 Mon Sep 17 00:00:00 2001 From: TOMITA Masahiro Date: Thu, 27 Nov 2025 11:10:43 +0900 Subject: [PATCH 1393/2435] Fix argument handling in `IO::Buffer#each_byte` (#15309) The method incorrectly ignored its first argument and treated the second argument as offset and the third as count. This change makes the first argument be treated as offset and the second as count. Also fix incorrect block parameter in comments. --- io_buffer.c | 4 ++-- test/ruby/test_io_buffer.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index abe7832bee829a..89f169176f48b5 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -2231,7 +2231,7 @@ io_buffer_values(int argc, VALUE *argv, VALUE self) /* * call-seq: - * each_byte([offset, [count]]) {|offset, byte| ...} -> self + * each_byte([offset, [count]]) {|byte| ...} -> self * each_byte([offset, [count]]) -> enumerator * * Iterates over the buffer, yielding each byte starting from +offset+. @@ -2255,7 +2255,7 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); size_t offset, count; - io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc-1, argv+1, &offset, &count); + io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count); for (size_t i = 0; i < count; i++) { unsigned char *value = (unsigned char *)base + i + offset; diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 997ed52640fb0d..1e4a6e2fd86c40 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -473,6 +473,7 @@ def test_each_byte buffer = IO::Buffer.for(string) assert_equal string.bytes, buffer.each_byte.to_a + assert_equal string.bytes[3, 5], buffer.each_byte(3, 5).to_a end def test_zero_length_each_byte From 10522ca19739978911a490b0f948865d42e560f8 Mon Sep 17 00:00:00 2001 From: NAITOH Jun Date: Thu, 27 Nov 2025 14:07:54 +0900 Subject: [PATCH 1394/2435] [DOC] Include strscan/lib --- ext/.document | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/.document b/ext/.document index 0d6c97ff73e0d8..374abe65807482 100644 --- a/ext/.document +++ b/ext/.document @@ -90,6 +90,7 @@ readline/readline.c ripper/lib socket stringio/stringio.c +strscan/lib strscan/strscan.c syslog/syslog.c syslog/lib From 3e926cd5dd2dfb2176b7d65f802519586813284c Mon Sep 17 00:00:00 2001 From: Alex Clink Date: Fri, 14 Nov 2025 09:51:22 -0500 Subject: [PATCH 1395/2435] NEWS.md: fix wording about Ractor::Port --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index fa588780955227..41c87bf9478c71 100644 --- a/NEWS.md +++ b/NEWS.md @@ -242,7 +242,7 @@ The following bundled gems are updated. ## Compatibility issues -* The following methods were removed from Ractor due because of `Ractor::Port`: +* The following methods were removed from Ractor due to the addition of `Ractor::Port`: * `Ractor.yield` * `Ractor#take` From a2c12d3ffeea99c957ee317fc613c7e0897a04f9 Mon Sep 17 00:00:00 2001 From: Sebastian Dufner <13749199+der-scheme@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:39:45 +0100 Subject: [PATCH 1396/2435] Documentation: Added the `|`(pipe)/OR operator to syntax/methods. It was not documented as an operator that can be overriden, which is confusing because it makes you think it can't be. --- doc/syntax/methods.rdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc index 8dafa6bb0cf894..14810a188f06d8 100644 --- a/doc/syntax/methods.rdoc +++ b/doc/syntax/methods.rdoc @@ -100,6 +100,7 @@ operators. / :: divide % :: modulus division, String#% & :: AND +| :: OR ^ :: XOR (exclusive OR) >> :: right-shift << :: left-shift, append From 0f45d0c4847aded77c3aa42ae8b4da8299fa0a12 Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Wed, 6 Nov 2024 10:47:15 +0900 Subject: [PATCH 1397/2435] Update man and help: -d option set $VERBOSE to true --- man/ruby.1 | 2 ++ ruby.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/man/ruby.1 b/man/ruby.1 index 28d7ddadfa0d51..d19cea99ca3c5b 100644 --- a/man/ruby.1 +++ b/man/ruby.1 @@ -287,6 +287,8 @@ to the standard output. .It Fl -debug Turns on debug mode. .Li "$DEBUG" +and +.Li "$VERBOSE" will be set to true. .Pp .It Fl e Ar command diff --git a/ruby.c b/ruby.c index f1089ca41e9173..11376cbe7066a9 100644 --- a/ruby.c +++ b/ruby.c @@ -326,7 +326,7 @@ usage(const char *name, int help, int highlight, int columns) M("-a", "", "Split each input line ($_) into fields ($F)."), M("-c", "", "Check syntax (no execution)."), M("-Cdirpath", "", "Execute program in specified directory."), - M("-d", ", --debug", "Set debugging flag ($DEBUG) to true."), + M("-d", ", --debug", "Set debugging flag ($DEBUG) and $VERBOSE to true."), M("-e 'code'", "", "Execute given Ruby code; multiple -e allowed."), M("-Eex[:in]", ", --encoding=ex[:in]", "Set default external and internal encodings."), M("-Fpattern", "", "Set input field separator ($;); used with -a."), From 4917f2335d60f5ff48843542939e3227d8c9d9b8 Mon Sep 17 00:00:00 2001 From: Tommaso Barbato Date: Wed, 12 Nov 2025 10:25:03 +0100 Subject: [PATCH 1398/2435] Store snapshot info date as Date instead of String --- tool/make-snapshot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 2b9a5006e032ca..8251fa6324f970 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -10,6 +10,7 @@ require 'fileutils' require 'shellwords' require 'tmpdir' require 'pathname' +require 'date' require 'yaml' require 'json' require File.expand_path("../lib/vcs", __FILE__) @@ -641,7 +642,7 @@ revisions.collect {|rev| package(vcs, rev, destdir, tmp)}.flatten.each do |name| key = basename[/\A(.*)\.(?:tar|zip)/, 1] info[key] ||= Hash.new{|h,k|h[k]={}} info[key]['version'] = version if version - info[key]['date'] = release_date.strftime('%Y-%m-%d') + info[key]['date'] = release_date.to_date if version info[key]['post'] = "/en/news/#{release_date.strftime('%Y/%m/%d')}/ruby-#{version.tr('.', '-')}-released/" info[key]['url'][extname] = "https://cache.ruby-lang.org/pub/ruby/#{version[/\A\d+\.\d+/]}/#{basename}" From 3ebb5b9c7811c3f551f61111c8495f859ad1861c Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Sat, 12 Jul 2025 22:23:26 +0900 Subject: [PATCH 1399/2435] Remove unneeded trailing semicolons --- thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thread.c b/thread.c index 3e9bf3192d0c62..f545a5377d8ed6 100644 --- a/thread.c +++ b/thread.c @@ -1013,7 +1013,7 @@ rb_thread_create_ractor(rb_ractor_t *r, VALUE args, VALUE proc) .args = args, .proc = proc, }; - return thread_create_core(rb_thread_alloc(rb_cThread), ¶ms);; + return thread_create_core(rb_thread_alloc(rb_cThread), ¶ms); } From cdf4634dc1359194d27cb93779d30b47fe466a01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:44:34 +0000 Subject: [PATCH 1400/2435] Bump actions/checkout in /.github/actions/setup/directories Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index f93733a919066c..b64227e4359c71 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -88,7 +88,7 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} From 3b09db3b3586c1080343185b2885423b650abda4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:44:03 +0000 Subject: [PATCH 1401/2435] Bump actions/checkout from 5.0.1 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/compilers.yml | 26 ++++++++++++------------ .github/workflows/cygwin.yml | 2 +- .github/workflows/default_gems_list.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/mingw.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/post_push.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/rust-warnings.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-macos.yml | 4 ++-- .github/workflows/yjit-ubuntu.yml | 6 +++--- .github/workflows/zjit-macos.yml | 6 +++--- .github/workflows/zjit-ubuntu.yml | 8 ++++---- 27 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index a23a380c171449..c1d227181a7c6b 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -61,7 +61,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 48ccf2c1bbf57c..34081cfdf7bc7f 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5.0.1 + uses: actions/checkout@v6.0.0 - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 08303b043bfb22..68d65dd71cbb3a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -53,7 +53,7 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler: none - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ./.github/actions/setup/ubuntu diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 3ae5b85a54a536..58742c1ce18134 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index e6b6e25428c01a..ee17217f274bcc 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -30,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index d619493d4faf56..d181759fc1320a 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} @@ -65,7 +65,7 @@ jobs: echo RDOC='ruby -W0 --disable-gems tool/rdoc-srcdir -q' >> $GITHUB_ENV - name: Checkout rdoc - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: repository: ruby/rdoc ref: ${{ steps.rdoc.outputs.ref }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d4c29594a29bc2..62fc319b19d1dd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install libraries if: ${{ contains(matrix.os, 'macos') }} diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index b841b4c18be9f0..f8ff22fe10fa0a 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -51,7 +51,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } # Set fetch-depth: 10 so that Launchable can receive commits information. - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } @@ -74,7 +74,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - name: 'GCC 15 LTO' @@ -104,7 +104,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 22', with: { tag: 'clang-22' }, timeout-minutes: 5 } @@ -126,7 +126,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' }, timeout-minutes: 5 } @@ -147,7 +147,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } # -Wno-strict-prototypes is necessary with current clang-15 since @@ -173,7 +173,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } @@ -193,7 +193,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } @@ -215,7 +215,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' }, timeout-minutes: 5 } @@ -235,7 +235,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' }, timeout-minutes: 5 } @@ -255,7 +255,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' }, timeout-minutes: 5 } @@ -275,7 +275,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' }, timeout-minutes: 5 } @@ -294,7 +294,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' }, timeout-minutes: 5 } @@ -320,7 +320,7 @@ jobs: - 'compileB' - 'compileC' steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - uses: ./.github/actions/slack with: diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 20713516f1568d..8b97cc195ff8c8 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -40,7 +40,7 @@ jobs: steps: - run: git config --global core.autocrlf input - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Cygwin uses: cygwin/cygwin-install-action@master diff --git a/.github/workflows/default_gems_list.yml b/.github/workflows/default_gems_list.yml index 727828b02d642e..63671382f94941 100644 --- a/.github/workflows/default_gems_list.yml +++ b/.github/workflows/default_gems_list.yml @@ -20,7 +20,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 941645e4eac6f5..9a1feb06358463 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -61,7 +61,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index f7e25526e05f43..192f71649a9a35 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -166,7 +166,7 @@ jobs: [ ${#failed[@]} -eq 0 ] shell: sh - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 0dfa024f3740f9..8b62ccd111b72a 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -48,7 +48,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 64feb546176bf7..337973a54d07a6 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -51,7 +51,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 11987a16654dc9..188b4d94d0056e 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -28,7 +28,7 @@ jobs: REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 500 # for notify-slack-commits token: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b2d1286205cc4..24bdec5eb9d257 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5.0.1 + - uses: actions/checkout@v6.0.0 - uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml index 37d016dada8700..f05b35346e8703 100644 --- a/.github/workflows/rust-warnings.yml +++ b/.github/workflows/rust-warnings.yml @@ -36,7 +36,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Rust run: rustup default beta diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e7a9721b06841a..6233f06661d557 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -34,7 +34,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 50b921b1a4547c..20b3367c832a75 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -45,7 +45,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 6c1f01806f2adb..26144cc49689eb 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 name: Check out ruby/ruby with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 7e12c56dd94dfd..9ec59324900094 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -60,7 +60,7 @@ jobs: )}} steps: &make-steps - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 2115d1abec8921..082d1984e50620 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -59,7 +59,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3dce96def3e92e..4659fb80f8434d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -66,7 +66,7 @@ jobs: bundler: none windows-toolchain: none - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 0927134aa10d5e..148dd7a4a3268d 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - run: RUST_BACKTRACE=1 cargo test working-directory: yjit @@ -83,7 +83,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 922363108e18bf..5303ab1c794f09 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -36,7 +36,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 # For now we can't run cargo test --offline because it complains about the # capstone dependency, even though the dependency is optional @@ -68,7 +68,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 # Check that we don't have linting errors in release mode, too - run: cargo clippy --all-targets --all-features @@ -120,7 +120,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 7965440da5d3b1..fc7bf459d89de5 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -68,7 +68,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -172,7 +172,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ./.github/actions/setup/macos @@ -192,7 +192,7 @@ jobs: run: echo "MAKEFLAGS=" >> "$GITHUB_ENV" - name: Checkout ruby-bench - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: repository: ruby/ruby-bench path: ruby-bench diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 7778c449e8e8ea..ac400d410d6cf6 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - run: cargo clippy --all-targets --all-features working-directory: zjit @@ -103,7 +103,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -228,7 +228,7 @@ jobs: )}} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ./.github/actions/setup/ubuntu @@ -244,7 +244,7 @@ jobs: - run: make install - name: Checkout ruby-bench - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: repository: ruby/ruby-bench path: ruby-bench From aebd391f9c31151a963b407a0429a1d57bd59c28 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 13:40:28 +0900 Subject: [PATCH 1402/2435] Clean prism directory --- common.mk | 14 +++++++++++--- prism/srcs.mk | 8 +++++++- prism/srcs.mk.in | 8 +++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/common.mk b/common.mk index 70d61231f278c0..e5f9c7c3711d7a 100644 --- a/common.mk +++ b/common.mk @@ -706,7 +706,8 @@ distclean-local:: clean-local $(Q)$(RM) config.cache config.status config.status.lineno $(Q)$(RM) *~ *.bak *.stackdump core *.core gmon.out $(PREP) -$(Q)$(RMALL) $(srcdir)/autom4te.cache -distclean-ext:: PHONY +distclean-local:: distclean-srcs-local +distclean-ext:: distclean-srcs-ext distclean-golf: clean-golf distclean-rdoc: clean-rdoc distclean-html: clean-html @@ -721,6 +722,7 @@ realclean:: realclean-ext realclean-local realclean-enc realclean-golf realclean realclean-local:: distclean-local realclean-srcs-local clean-srcs:: clean-srcs-local clean-srcs-ext +distclean-srcs:: distclean-srcs-local distclean-srcs-ext realclean-srcs:: realclean-srcs-local realclean-srcs-ext clean-srcs-local:: @@ -728,7 +730,9 @@ clean-srcs-local:: $(Q)$(RM) id.c id.h probes.dmyh probes.h $(Q)$(RM) encdb.h transdb.h verconf.h ruby-runner.h -realclean-srcs-local:: clean-srcs-local +distclean-srcs-local:: clean-srcs-local + +realclean-srcs-local:: distclean-srcs-local $(Q)$(CHDIR) $(srcdir) && $(RM) \ parse.c parse.h lex.c enc/trans/newline.c $(PRELUDES) revision.h \ id.c id.h probes.dmyh configure aclocal.m4 tool/config.guess tool/config.sub \ @@ -736,7 +740,8 @@ realclean-srcs-local:: clean-srcs-local || $(NULLCMD) clean-srcs-ext:: -realclean-srcs-ext:: clean-srcs-ext +distclean-srcs-ext:: clean-srcs-ext +realclean-srcs-ext:: distclean-srcs-ext realclean-ext:: PHONY realclean-golf: distclean-golf @@ -1007,8 +1012,11 @@ $(ENC_MK): $(srcdir)/enc/make_encmake.rb $(srcdir)/enc/Makefile.in $(srcdir)/enc .PHONY: test install install-nodoc install-doc dist .PHONY: loadpath golf capi rdoc install-prereq clear-installed-list .PHONY: clean clean-ext clean-local clean-enc clean-golf clean-rdoc clean-html clean-extout +.PHONY: clean-srcs clean-srcs-local clean-srcs-ext .PHONY: distclean distclean-ext distclean-local distclean-enc distclean-golf distclean-extout +.PHONY: distclean-srcs distclean-srcs-local distclean-srcs-ext .PHONY: realclean realclean-ext realclean-local realclean-enc realclean-golf realclean-extout +.PHONY: realclean-srcs realclean-srcs-local realclean-srcs-ext .PHONY: exam check test test-short test-all btest btest-ruby test-basic test-knownbug .PHONY: run runruby parse benchmark gdb gdb-ruby .PHONY: update-mspec update-rubyspec test-rubyspec test-spec diff --git a/prism/srcs.mk b/prism/srcs.mk index ff4ddff0722e32..167aa4dbe07e89 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -12,7 +12,13 @@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/srcs.mk.in $(BASERUBY) $(tooldir)/generic_erb.rb -c -t$@ -o $(PRISM_SRCDIR)/srcs.mk $(PRISM_SRCDIR)/srcs.mk.in -realclean-prism-srcs:: +distclean-prism-srcs:: + $(RM) prism/.srcs.mk.time + $(RMDIR) prism + +distclean-srcs-local:: distclean-prism-srcs + +realclean-prism-srcs:: distclean-prism-srcs $(RM) $(PRISM_SRCDIR)/srcs.mk realclean-srcs-local:: realclean-prism-srcs diff --git a/prism/srcs.mk.in b/prism/srcs.mk.in index e337eff8ea00e5..b67ce993709ac3 100644 --- a/prism/srcs.mk.in +++ b/prism/srcs.mk.in @@ -20,7 +20,13 @@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ $(PRISM_SRCDIR)/<%=%><%=script%> $(BASERUBY) $(tooldir)/generic_erb.rb -c -t$@ -o $(PRISM_SRCDIR)/<%=%><%=srcs%> $(PRISM_SRCDIR)/<%=%><%=script%> -realclean-prism-srcs:: +distclean-prism-srcs:: + $(RM) prism/.srcs.mk.time + $(RMDIR) prism + +distclean-srcs-local:: distclean-prism-srcs + +realclean-prism-srcs:: distclean-prism-srcs $(RM) $(PRISM_SRCDIR)/<%=%><%=srcs%> realclean-srcs-local:: realclean-prism-srcs From 68a7edaa81ccf09871227bf3e06e18cd19eb4a3e Mon Sep 17 00:00:00 2001 From: Kazuhiro NISHIYAMA Date: Thu, 27 Nov 2025 21:05:45 +0900 Subject: [PATCH 1403/2435] [DOC] Fix a link in NEWS.md --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 41c87bf9478c71..6fea49923f1a96 100644 --- a/NEWS.md +++ b/NEWS.md @@ -335,6 +335,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21219]: https://bugs.ruby-lang.org/issues/21219 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 +[Feature #21275]: https://bugs.ruby-lang.org/issues/21275 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 From 78aa8d5b1d62a04d7cb5492f653266c99f4c55a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 24 Nov 2025 17:36:01 +0100 Subject: [PATCH 1404/2435] [ruby/json] Test current behavior regarding depth for Coder Coder currently ignores its depth and always resets it to 0 when generating a new JSON document. https://github.com/ruby/json/commit/cca1f38316 --- test/json/json_coder_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index 83b89a3b13dea9..1f9bc814e3f08f 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -132,6 +132,11 @@ def test_json_coder_string_invalid_encoding assert_equal 2, calls end + def test_depth + coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1) + assert_equal %({\n "foo": 42\n}), coder.dump(foo: 42) + end + def test_nesting_recovery coder = JSON::Coder.new ary = [] From 2f192c73cccdfd81e4ac2206feadc48b2757c19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 24 Nov 2025 17:41:33 +0100 Subject: [PATCH 1405/2435] [ruby/json] Respect Coder depth when generating https://github.com/ruby/json/commit/9c36681b17 --- ext/json/generator/generator.c | 2 +- test/json/json_coder_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 32a9b485139041..6ece9f05385491 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1549,7 +1549,7 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) .buffer = &buffer, .vstate = Qfalse, .state = state, - .depth = 0, + .depth = state->depth, .obj = obj, .func = generate_json }; diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index 1f9bc814e3f08f..47e12ff919d660 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -134,7 +134,7 @@ def test_json_coder_string_invalid_encoding def test_depth coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1) - assert_equal %({\n "foo": 42\n}), coder.dump(foo: 42) + assert_equal %({\n "foo": 42\n }), coder.dump(foo: 42) end def test_nesting_recovery From 4cd6661e1853930c8002174c4ccd14f927fcd33b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 27 Nov 2025 20:12:24 +0000 Subject: [PATCH 1406/2435] Reorganize page documentations (#15154) Re-organize page docs --- array.c | 4 +- box.c | 4 +- common.mk | 6 +- doc/.document | 7 +- doc/_regexp.rdoc | 4 +- doc/{ => contributing}/bug_triaging.rdoc | 0 doc/contributing/building_ruby.md | 2 +- doc/{ => contributing}/dtrace_probes.rdoc | 0 doc/{ => contributing}/memory_view.md | 0 doc/{ => distribution}/distribution.md | 0 doc/{ => distribution}/windows.md | 0 doc/{yjit => jit}/yjit.md | 0 doc/{ => jit}/zjit.md | 0 doc/{_box.md => language/box.md} | 0 doc/{ => language}/bsearch.rdoc | 0 doc/{date => language}/calendars.rdoc | 2 +- doc/{ => language}/case_mapping.rdoc | 0 doc/{ => language}/character_selectors.rdoc | 0 doc/{ => language}/dig_methods.rdoc | 0 doc/{ => language}/encodings.rdoc | 0 doc/{ => language}/exceptions.md | 0 doc/{ => language}/fiber.md | 0 doc/{ => language}/format_specifications.rdoc | 26 +++--- doc/{ => language}/globals.md | 0 doc/{ => language}/hash_inclusion.rdoc | 0 doc/{ => language}/implicit_conversion.rdoc | 0 doc/{ => language}/marshal.rdoc | 0 doc/{ruby => language}/option_dump.md | 0 doc/{ruby => language}/options.md | 72 ++++++++-------- doc/{ => language}/packed_data.rdoc | 2 +- doc/{ => language}/ractor.md | 0 doc/{ => language}/regexp/methods.rdoc | 0 .../regexp/unicode_properties.rdoc | 0 doc/{ => language}/signals.rdoc | 0 doc/{ => language}/strftime_formatting.rdoc | 7 +- doc/{ => security}/command_injection.rdoc | 0 doc/{ => security}/security.rdoc | 0 doc/string/partition.rdoc | 4 +- doc/string/rpartition.rdoc | 4 +- doc/syntax/assignment.rdoc | 2 +- ext/date/date_core.c | 86 +++++++++---------- hash.c | 8 +- io.c | 22 ++--- load.c | 2 +- object.c | 2 +- pack.rb | 6 +- process.c | 6 +- range.c | 2 +- string.c | 2 +- test/ruby/test_box.rb | 2 +- yjit.rb | 4 +- 51 files changed, 145 insertions(+), 143 deletions(-) rename doc/{ => contributing}/bug_triaging.rdoc (100%) rename doc/{ => contributing}/dtrace_probes.rdoc (100%) rename doc/{ => contributing}/memory_view.md (100%) rename doc/{ => distribution}/distribution.md (100%) rename doc/{ => distribution}/windows.md (100%) rename doc/{yjit => jit}/yjit.md (100%) rename doc/{ => jit}/zjit.md (100%) rename doc/{_box.md => language/box.md} (100%) rename doc/{ => language}/bsearch.rdoc (100%) rename doc/{date => language}/calendars.rdoc (95%) rename doc/{ => language}/case_mapping.rdoc (100%) rename doc/{ => language}/character_selectors.rdoc (100%) rename doc/{ => language}/dig_methods.rdoc (100%) rename doc/{ => language}/encodings.rdoc (100%) rename doc/{ => language}/exceptions.md (100%) rename doc/{ => language}/fiber.md (100%) rename doc/{ => language}/format_specifications.rdoc (89%) rename doc/{ => language}/globals.md (100%) rename doc/{ => language}/hash_inclusion.rdoc (100%) rename doc/{ => language}/implicit_conversion.rdoc (100%) rename doc/{ => language}/marshal.rdoc (100%) rename doc/{ruby => language}/option_dump.md (100%) rename doc/{ruby => language}/options.md (85%) rename doc/{ => language}/packed_data.rdoc (99%) rename doc/{ => language}/ractor.md (100%) rename doc/{ => language}/regexp/methods.rdoc (100%) rename doc/{ => language}/regexp/unicode_properties.rdoc (100%) rename doc/{ => language}/signals.rdoc (100%) rename doc/{ => language}/strftime_formatting.rdoc (98%) rename doc/{ => security}/command_injection.rdoc (100%) rename doc/{ => security}/security.rdoc (100%) diff --git a/array.c b/array.c index b71123532d19cc..2ea6b55a7f69a3 100644 --- a/array.c +++ b/array.c @@ -3521,7 +3521,7 @@ static VALUE rb_ary_bsearch_index(VALUE ary); * Returns the element from +self+ found by a binary search, * or +nil+ if the search found no suitable element. * - * See {Binary Searching}[rdoc-ref:bsearch.rdoc]. + * See {Binary Searching}[rdoc-ref:language/bsearch.rdoc]. * * Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. */ @@ -3545,7 +3545,7 @@ rb_ary_bsearch(VALUE ary) * Returns the integer index of the element from +self+ found by a binary search, * or +nil+ if the search found no suitable element. * - * See {Binary Searching}[rdoc-ref:bsearch.rdoc]. + * See {Binary Searching}[rdoc-ref:language/bsearch.rdoc]. * * Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. */ diff --git a/box.c b/box.c index e2c8f25eda7375..42ee967c509c84 100644 --- a/box.c +++ b/box.c @@ -796,7 +796,7 @@ rb_initialize_main_box(void) if (!box_experimental_warned) { rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, "Ruby::Box is experimental, and the behavior may change in the future!\n" - "See doc/box.md for known issues, etc."); + "See doc/language/box.md for known issues, etc."); box_experimental_warned = 1; } @@ -1048,7 +1048,7 @@ rb_f_dump_classext(VALUE recv, VALUE klass) * Document-class: Ruby::Box * * :markup: markdown - * :include: doc/_box.md + * :include: doc/language/box.md */ void Init_Box(void) diff --git a/common.mk b/common.mk index e5f9c7c3711d7a..eb9b75ca7e3dd0 100644 --- a/common.mk +++ b/common.mk @@ -1822,11 +1822,11 @@ $(UNICODE_HDR_DIR)/name2ctype.h: $(UNICODE_SRC_DATA_DIR) $(UNICODE_SRC_EMOJI_DATA_DIR) > $@.new $(MV) $@.new $@ -srcs-doc: $(srcdir)/doc/regexp/unicode_properties.rdoc -$(srcdir)/doc/regexp/$(ALWAYS_UPDATE_UNICODE:yes=unicode_properties.rdoc): \ +srcs-doc: $(srcdir)/doc/language/regexp/unicode_properties.rdoc +$(srcdir)/doc/language/regexp/$(ALWAYS_UPDATE_UNICODE:yes=unicode_properties.rdoc): \ $(UNICODE_HDR_DIR)/name2ctype.h $(UNICODE_PROPERTY_FILES) -$(srcdir)/doc/regexp/unicode_properties.rdoc: +$(srcdir)/doc/language/regexp/unicode_properties.rdoc: $(Q) $(BOOTSTRAPRUBY) $(tooldir)/generic_erb.rb -c -o $@ \ $(srcdir)/template/unicode_properties.rdoc.tmpl \ $(UNICODE_SRC_DATA_DIR) $(UNICODE_HDR_DIR)/name2ctype.h || \ diff --git a/doc/.document b/doc/.document index 1feb6934ec358f..3a95c9617b4db5 100644 --- a/doc/.document +++ b/doc/.document @@ -2,11 +2,12 @@ [^_]*.rb [^_]*.rdoc contributing +distribution NEWS syntax optparse date rdoc -regexp -yjit -ruby +jit +security +language diff --git a/doc/_regexp.rdoc b/doc/_regexp.rdoc index 76ac3a859e5b15..301a3fe11b5ce7 100644 --- a/doc/_regexp.rdoc +++ b/doc/_regexp.rdoc @@ -39,7 +39,7 @@ A regexp may be used: most such methods accept an argument that may be either a string or the (much more powerful) regexp. - See {Regexp Methods}[rdoc-ref:regexp/methods.rdoc]. + See {Regexp Methods}[rdoc-ref:language/regexp/methods.rdoc]. == \Regexp Objects @@ -821,7 +821,7 @@ Or by using \P (uppercase +P+): /\P{Alpha}/.match('1') # => # /\P{Alpha}/.match('a') # => nil -See {Unicode Properties}[rdoc-ref:regexp/unicode_properties.rdoc] +See {Unicode Properties}[rdoc-ref:language/regexp/unicode_properties.rdoc] for regexps based on the numerous properties. Some commonly-used properties correspond to POSIX bracket expressions: diff --git a/doc/bug_triaging.rdoc b/doc/contributing/bug_triaging.rdoc similarity index 100% rename from doc/bug_triaging.rdoc rename to doc/contributing/bug_triaging.rdoc diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 4c69515684afbc..286bd1f1161635 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -184,7 +184,7 @@ cause build failures. ## Building on Windows The documentation for building on Windows can be found in [the separated -file](../windows.md). +file](../distribution/windows.md). ## More details diff --git a/doc/dtrace_probes.rdoc b/doc/contributing/dtrace_probes.rdoc similarity index 100% rename from doc/dtrace_probes.rdoc rename to doc/contributing/dtrace_probes.rdoc diff --git a/doc/memory_view.md b/doc/contributing/memory_view.md similarity index 100% rename from doc/memory_view.md rename to doc/contributing/memory_view.md diff --git a/doc/distribution.md b/doc/distribution/distribution.md similarity index 100% rename from doc/distribution.md rename to doc/distribution/distribution.md diff --git a/doc/windows.md b/doc/distribution/windows.md similarity index 100% rename from doc/windows.md rename to doc/distribution/windows.md diff --git a/doc/yjit/yjit.md b/doc/jit/yjit.md similarity index 100% rename from doc/yjit/yjit.md rename to doc/jit/yjit.md diff --git a/doc/zjit.md b/doc/jit/zjit.md similarity index 100% rename from doc/zjit.md rename to doc/jit/zjit.md diff --git a/doc/_box.md b/doc/language/box.md similarity index 100% rename from doc/_box.md rename to doc/language/box.md diff --git a/doc/bsearch.rdoc b/doc/language/bsearch.rdoc similarity index 100% rename from doc/bsearch.rdoc rename to doc/language/bsearch.rdoc diff --git a/doc/date/calendars.rdoc b/doc/language/calendars.rdoc similarity index 95% rename from doc/date/calendars.rdoc rename to doc/language/calendars.rdoc index a2540f1c433b5a..eb9638f55231ca 100644 --- a/doc/date/calendars.rdoc +++ b/doc/language/calendars.rdoc @@ -31,7 +31,7 @@ See also {a concrete example here}[rdoc-ref:DateTime@When+should+you+use+DateTim === Argument +start+ Certain methods in class \Date handle differences in the -{Julian and Gregorian calendars}[rdoc-ref:@Julian+and+Gregorian+Calendars] +{Julian and Gregorian calendars}[rdoc-ref:language/calendars.rdoc@Julian+and+Gregorian+Calendars] by accepting an optional argument +start+, whose value may be: - Date::ITALY (the default): the created date is Julian diff --git a/doc/case_mapping.rdoc b/doc/language/case_mapping.rdoc similarity index 100% rename from doc/case_mapping.rdoc rename to doc/language/case_mapping.rdoc diff --git a/doc/character_selectors.rdoc b/doc/language/character_selectors.rdoc similarity index 100% rename from doc/character_selectors.rdoc rename to doc/language/character_selectors.rdoc diff --git a/doc/dig_methods.rdoc b/doc/language/dig_methods.rdoc similarity index 100% rename from doc/dig_methods.rdoc rename to doc/language/dig_methods.rdoc diff --git a/doc/encodings.rdoc b/doc/language/encodings.rdoc similarity index 100% rename from doc/encodings.rdoc rename to doc/language/encodings.rdoc diff --git a/doc/exceptions.md b/doc/language/exceptions.md similarity index 100% rename from doc/exceptions.md rename to doc/language/exceptions.md diff --git a/doc/fiber.md b/doc/language/fiber.md similarity index 100% rename from doc/fiber.md rename to doc/language/fiber.md diff --git a/doc/format_specifications.rdoc b/doc/language/format_specifications.rdoc similarity index 89% rename from doc/format_specifications.rdoc rename to doc/language/format_specifications.rdoc index a59f2103775ec8..61eaefec4af179 100644 --- a/doc/format_specifications.rdoc +++ b/doc/language/format_specifications.rdoc @@ -46,42 +46,42 @@ The links lead to the details and examples. === \Integer Type Specifiers - +b+ or +B+: Format +argument+ as a binary integer. - See {Specifiers b and B}[rdoc-ref:@Specifiers+b+and+B]. + See {Specifiers b and B}[rdoc-ref:language/format_specifications.rdoc@Specifiers+b+and+B]. - +d+, +i+, or +u+ (all are identical): Format +argument+ as a decimal integer. - See {Specifier d}[rdoc-ref:@Specifier+d]. + See {Specifier d}[rdoc-ref:language/format_specifications.rdoc@Specifier+d]. - +o+: Format +argument+ as an octal integer. - See {Specifier o}[rdoc-ref:@Specifier+o]. + See {Specifier o}[rdoc-ref:language/format_specifications.rdoc@Specifier+o]. - +x+ or +X+: Format +argument+ as a hexadecimal integer. - See {Specifiers x and X}[rdoc-ref:@Specifiers+x+and+X]. + See {Specifiers x and X}[rdoc-ref:language/format_specifications.rdoc@Specifiers+x+and+X]. === Floating-Point Type Specifiers - +a+ or +A+: Format +argument+ as hexadecimal floating-point number. - See {Specifiers a and A}[rdoc-ref:@Specifiers+a+and+A]. + See {Specifiers a and A}[rdoc-ref:language/format_specifications.rdoc@Specifiers+a+and+A]. - +e+ or +E+: Format +argument+ in scientific notation. - See {Specifiers e and E}[rdoc-ref:@Specifiers+e+and+E]. + See {Specifiers e and E}[rdoc-ref:language/format_specifications.rdoc@Specifiers+e+and+E]. - +f+: Format +argument+ as a decimal floating-point number. - See {Specifier f}[rdoc-ref:@Specifier+f]. + See {Specifier f}[rdoc-ref:language/format_specifications.rdoc@Specifier+f]. - +g+ or +G+: Format +argument+ in a "general" format. - See {Specifiers g and G}[rdoc-ref:@Specifiers+g+and+G]. + See {Specifiers g and G}[rdoc-ref:language/format_specifications.rdoc@Specifiers+g+and+G]. === Other Type Specifiers - +c+: Format +argument+ as a character. - See {Specifier c}[rdoc-ref:@Specifier+c]. + See {Specifier c}[rdoc-ref:language/format_specifications.rdoc@Specifier+c]. - +p+: Format +argument+ as a string via argument.inspect. - See {Specifier p}[rdoc-ref:@Specifier+p]. + See {Specifier p}[rdoc-ref:language/format_specifications.rdoc@Specifier+p]. - +s+: Format +argument+ as a string via argument.to_s. - See {Specifier s}[rdoc-ref:@Specifier+s]. + See {Specifier s}[rdoc-ref:language/format_specifications.rdoc@Specifier+s]. - %: Format +argument+ ('%') as a single percent character. - See {Specifier %}[rdoc-ref:@Specifier+-25]. + See {Specifier %}[rdoc-ref:language/format_specifications.rdoc@Specifier+-25]. == Flags The effect of a flag may vary greatly among type specifiers. These remarks are general in nature. -See {type-specific details}[rdoc-ref:@Type+Specifier+Details+and+Examples]. +See {type-specific details}[rdoc-ref:language/format_specifications.rdoc@Type+Specifier+Details+and+Examples]. Multiple flags may be given with single type specifier; order does not matter. diff --git a/doc/globals.md b/doc/language/globals.md similarity index 100% rename from doc/globals.md rename to doc/language/globals.md diff --git a/doc/hash_inclusion.rdoc b/doc/language/hash_inclusion.rdoc similarity index 100% rename from doc/hash_inclusion.rdoc rename to doc/language/hash_inclusion.rdoc diff --git a/doc/implicit_conversion.rdoc b/doc/language/implicit_conversion.rdoc similarity index 100% rename from doc/implicit_conversion.rdoc rename to doc/language/implicit_conversion.rdoc diff --git a/doc/marshal.rdoc b/doc/language/marshal.rdoc similarity index 100% rename from doc/marshal.rdoc rename to doc/language/marshal.rdoc diff --git a/doc/ruby/option_dump.md b/doc/language/option_dump.md similarity index 100% rename from doc/ruby/option_dump.md rename to doc/language/option_dump.md diff --git a/doc/ruby/options.md b/doc/language/options.md similarity index 85% rename from doc/ruby/options.md rename to doc/language/options.md index ee6b2054bc1e80..ededb67f8d8dbf 100644 --- a/doc/ruby/options.md +++ b/doc/language/options.md @@ -68,15 +68,15 @@ nil See also: -- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-a`: Split Input Lines into Fields @@ -98,15 +98,15 @@ and the default field separator is `$;`. See also: -- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-c`: Check Syntax @@ -228,15 +228,15 @@ The argument must immediately follow the option See also: -- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-h`: Print Short Help Message @@ -314,15 +314,15 @@ $ ruby -ln -e 'p $_' desiderata.txt See also: -- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-n`: Run Program in `gets` Loop @@ -348,15 +348,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-p`: `-n`, with Printing @@ -377,15 +377,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. ### `-r`: Require Library @@ -434,7 +434,7 @@ $ ruby -s t.rb -foo=baz -bar=bat ``` The option may not be used with -{option -e}[rdoc-ref:@e-3A+Execute+Given+Ruby+Code] +{option -e}[rdoc-ref:language/options.md@e-3A+Execute+Given+Ruby+Code] ### `-S`: Search Directories in `ENV['PATH']` @@ -583,7 +583,7 @@ ruby - Copyright (C) 1993-2024 Yukihiro Matsumoto ### `--debug`: Alias for `-d` Option `--debug` is an alias for -{option -d}[rdoc-ref:@d-3A+Set+-24DEBUG+to+true]. +{option -d}[rdoc-ref:language/options.md@d-3A+Set+-24DEBUG+to+true]. ### `--disable`: Disable Features @@ -617,9 +617,9 @@ option was given: - `--dump=help`: Same as {option \-\-help}[options_md.html#label--help-3A+Print+Help+Message]. - `--dump=syntax`: - Same as {option -c}[rdoc-ref:@c-3A+Check+Syntax]. + Same as {option -c}[rdoc-ref:language/options.md@c-3A+Check+Syntax]. - `--dump=usage`: - Same as {option -h}[rdoc-ref:@h-3A+Print+Short+Help+Message]. + Same as {option -h}[rdoc-ref:language/options.md@h-3A+Print+Short+Help+Message]. - `--dump=version`: Same as {option \-\-version}[options_md.html#label--version-3A+Print+Ruby+Version]. @@ -641,7 +641,7 @@ see {option --disable}[options_md.html#label--disable-3A+Disable+Features]. ### `--encoding`: Alias for `-E`. Option `--encoding` is an alias for -{option -E}[rdoc-ref:@E-3A+Set+Default+Encodings]. +{option -E}[rdoc-ref:language/options.md@E-3A+Set+Default+Encodings]. ### `--external-encoding`: Set Default External \Encoding @@ -682,7 +682,7 @@ CESU-8 ### `--jit` Option `--jit` is an alias for option `--yjit`, which enables YJIT; -see additional YJIT options in the [YJIT documentation](rdoc-ref:yjit/yjit.md). +see additional YJIT options in the [YJIT documentation](rdoc-ref:jit/yjit.md). ### `--verbose`: Set `$VERBOSE` diff --git a/doc/packed_data.rdoc b/doc/language/packed_data.rdoc similarity index 99% rename from doc/packed_data.rdoc rename to doc/language/packed_data.rdoc index 3a2c8a57a5f0ed..3a762c03829a74 100644 --- a/doc/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -159,7 +159,7 @@ Any directive may be followed by either of these modifiers: 'AB'.unpack('C3') # => [65, 66, nil] Note: Directives in %w[A a Z m] use +count+ differently; - see {String Directives}[rdoc-ref:@String+Directives]. + see {String Directives}[rdoc-ref:language/packed_data.rdoc@String+Directives]. If elements don't fit the provided directive, only least significant bits are encoded: diff --git a/doc/ractor.md b/doc/language/ractor.md similarity index 100% rename from doc/ractor.md rename to doc/language/ractor.md diff --git a/doc/regexp/methods.rdoc b/doc/language/regexp/methods.rdoc similarity index 100% rename from doc/regexp/methods.rdoc rename to doc/language/regexp/methods.rdoc diff --git a/doc/regexp/unicode_properties.rdoc b/doc/language/regexp/unicode_properties.rdoc similarity index 100% rename from doc/regexp/unicode_properties.rdoc rename to doc/language/regexp/unicode_properties.rdoc diff --git a/doc/signals.rdoc b/doc/language/signals.rdoc similarity index 100% rename from doc/signals.rdoc rename to doc/language/signals.rdoc diff --git a/doc/strftime_formatting.rdoc b/doc/language/strftime_formatting.rdoc similarity index 98% rename from doc/strftime_formatting.rdoc rename to doc/language/strftime_formatting.rdoc index bef343c3073a83..991a708fa4cb3c 100644 --- a/doc/strftime_formatting.rdoc +++ b/doc/language/strftime_formatting.rdoc @@ -136,7 +136,7 @@ the only required part is the conversion specifier, so we begin with that. t = Time.now # => 2022-06-29 07:10:20.3230914 -0500 t.strftime('%N') # => "323091400" # Default. - Use {width specifiers}[rdoc-ref:@Width+Specifiers] + Use {width specifiers}[rdoc-ref:language/strftime_formatting.rdoc@Width+Specifiers] to adjust units: t.strftime('%3N') # => "323" # Milliseconds. @@ -522,5 +522,6 @@ An ISO 8601 combined date and time representation may be any ISO 8601 date and any ISO 8601 time, separated by the letter +T+. -For the relevant +strftime+ formats, see {Dates}[rdoc-ref:@Dates] and -{Times}[rdoc-ref:@Times] above. +For the relevant +strftime+ formats, see +{Dates}[rdoc-ref:language/strftime_formatting.rdoc@Dates] +and {Times}[rdoc-ref:language/strftime_formatting.rdoc@Times] above. diff --git a/doc/command_injection.rdoc b/doc/security/command_injection.rdoc similarity index 100% rename from doc/command_injection.rdoc rename to doc/security/command_injection.rdoc diff --git a/doc/security.rdoc b/doc/security/security.rdoc similarity index 100% rename from doc/security.rdoc rename to doc/security/security.rdoc diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index ee445bd21f32ba..330e6b03987fcf 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -17,7 +17,7 @@ Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, performs the equivalent of self.match(pattern) -(also setting {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): +(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): 'hello'.partition(/h/) # => ["", "h", "ello"] 'hello'.partition(/l/) # => ["he", "l", "lo"] @@ -30,7 +30,7 @@ If +pattern+ is a Regexp, performs the equivalent of self.match(pattern)self.index(pattern) -(and does _not_ set {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): +(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): 'hello'.partition('h') # => ["", "h", "ello"] 'hello'.partition('l') # => ["he", "l", "lo"] diff --git a/doc/string/rpartition.rdoc b/doc/string/rpartition.rdoc index 56eb9ddb95cab3..11b0571bfb2ba7 100644 --- a/doc/string/rpartition.rdoc +++ b/doc/string/rpartition.rdoc @@ -23,7 +23,7 @@ The pattern used is: Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, searches for the last matching substring -(also setting {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): +(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): 'hello'.rpartition(/l/) # => ["hel", "l", "o"] 'hello'.rpartition(/ll/) # => ["he", "ll", "o"] @@ -36,7 +36,7 @@ If +pattern+ is a Regexp, searches for the last matching substring If +pattern+ is not a Regexp, converts it to a string (if it is not already one), then searches for the last matching substring -(and does _not_ set {pattern-matching global variables}[rdoc-ref:globals.md@Pattern+Matching]): +(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): 'hello'.rpartition('l') # => ["hel", "l", "o"] 'hello'.rpartition('ll') # => ["he", "ll", "o"] diff --git a/doc/syntax/assignment.rdoc b/doc/syntax/assignment.rdoc index 9d91bd92dc00df..3988f82e5fc74f 100644 --- a/doc/syntax/assignment.rdoc +++ b/doc/syntax/assignment.rdoc @@ -279,7 +279,7 @@ An uninitialized global variable has a value of +nil+. Ruby has some special globals that behave differently depending on context such as the regular expression match variables or that have a side-effect when -assigned to. See the {global variables documentation}[rdoc-ref:globals.md] +assigned to. See the {global variables documentation}[rdoc-ref:language/globals.md] for details. == Assignment Methods diff --git a/ext/date/date_core.c b/ext/date/date_core.c index e4021051731714..cf8ea3c0a44990 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -2498,7 +2498,7 @@ date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) * * Date.valid_jd?(2451944) # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd. */ @@ -2592,7 +2592,7 @@ date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) * Date.valid_date?(2001, 2, 29) # => false * Date.valid_date?(2001, 2, -1) # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new. */ @@ -2680,7 +2680,7 @@ date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) * Date.valid_ordinal?(2001, 34) # => true * Date.valid_ordinal?(2001, 366) # => false * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.ordinal. */ @@ -2770,7 +2770,7 @@ date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) * * See Date.commercial. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.commercial. */ @@ -3350,7 +3350,7 @@ static VALUE d_lite_plus(VALUE, VALUE); * * Date.jd(Date::ITALY - 1).julian? # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.new. */ @@ -3415,7 +3415,7 @@ date_s_jd(int argc, VALUE *argv, VALUE klass) * * Raises an exception if +yday+ is zero or out of range. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new. */ @@ -3492,7 +3492,7 @@ date_s_civil(int argc, VALUE *argv, VALUE klass) * where +n+ is the number of days in the month; * when the argument is negative, counts backward from the end of the month. * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd. */ @@ -3598,7 +3598,7 @@ date_initialize(int argc, VALUE *argv, VALUE self) * Date.commercial(2020, 1, 1).to_s # => "2019-12-30" Date.commercial(2020, 1, 7).to_s # => "2020-01-05" * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * Related: Date.jd, Date.new, Date.ordinal. */ @@ -3783,7 +3783,7 @@ static void set_sg(union DateData *, double); * * Date.today.to_s # => "2022-07-06" * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -4383,7 +4383,7 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. @@ -4412,10 +4412,10 @@ date_s__strptime(int argc, VALUE *argv, VALUE klass) * Date.strptime('sat3feb01', '%a%d%b%y') # => # * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * (Unlike Date.strftime, does not support flags and width.) * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. * @@ -4504,7 +4504,7 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * * If +string+ does not specify a valid date, * the result is unpredictable; @@ -4539,7 +4539,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * This method recognizes many forms in +string+, * but it is not a validator. * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * If +string+ does not specify a valid date, * the result is unpredictable; * consider using Date._strptime instead. @@ -4559,7 +4559,7 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._parse (returns a hash). @@ -4603,7 +4603,7 @@ VALUE date__jisx0301(VALUE); * Date._iso8601(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should contain - * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4630,7 +4630,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should contain - * an {ISO 8601 formatted date}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: * * d = Date.new(2001, 2, 3) * s = d.iso8601 # => "2001-02-03" @@ -4638,7 +4638,7 @@ date_s__iso8601(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._iso8601 (returns a hash). @@ -4672,7 +4672,7 @@ date_s_iso8601(int argc, VALUE *argv, VALUE klass) * Date._rfc3339(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4700,7 +4700,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 3339 format}[rdoc-ref:strftime_formatting.rdoc@RFC+3339+Format]: + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" @@ -4708,7 +4708,7 @@ date_s__rfc3339(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._rfc3339 (returns a hash). @@ -4776,7 +4776,7 @@ date_s__xmlschema(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._xmlschema (returns a hash). @@ -4810,7 +4810,7 @@ date_s_xmlschema(int argc, VALUE *argv, VALUE klass) * Date._rfc2822(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4838,7 +4838,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {RFC 2822 date format}[rdoc-ref:strftime_formatting.rdoc@RFC+2822+Format]: + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: * * d = Date.new(2001, 2, 3) * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" @@ -4846,7 +4846,7 @@ date_s__rfc2822(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._rfc2822 (returns a hash). @@ -4880,7 +4880,7 @@ date_s_rfc2822(int argc, VALUE *argv, VALUE klass) * Date._httpdate(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4906,7 +4906,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) * * Returns a new \Date object with values parsed from +string+, * which should be a valid - * {HTTP date format}[rdoc-ref:strftime_formatting.rdoc@HTTP+Format]: + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: * * d = Date.new(2001, 2, 3) s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" @@ -4914,7 +4914,7 @@ date_s__httpdate(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._httpdate (returns a hash). @@ -4948,7 +4948,7 @@ date_s_httpdate(int argc, VALUE *argv, VALUE klass) * Date._jisx0301(string, limit: 128) -> hash * * Returns a hash of values parsed from +string+, which should be a valid - * {JIS X 0301 date format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: + * {JIS X 0301 date format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -4974,7 +4974,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * * Returns a new \Date object with values parsed from +string+, - * which should be a valid {JIS X 0301 format}[rdoc-ref:strftime_formatting.rdoc@JIS+X+0301+Format]: + * which should be a valid {JIS X 0301 format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: * * d = Date.new(2001, 2, 3) * s = d.jisx0301 # => "H13.02.03" @@ -4986,7 +4986,7 @@ date_s__jisx0301(int argc, VALUE *argv, VALUE klass) * * See: * - * - Argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - Argument {limit}[rdoc-ref:Date@Argument+limit]. * * Related: Date._jisx0301 (returns a hash). @@ -5744,7 +5744,7 @@ d_lite_leap_p(VALUE self) * Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity * Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -5819,7 +5819,7 @@ dup_obj_with_new_start(VALUE obj, double sg) * d1 = d0.new_start(Date::JULIAN) * d1.julian? # => true * - * See argument {start}[rdoc-ref:date/calendars.rdoc@Argument+start]. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * */ static VALUE @@ -6967,7 +6967,7 @@ static VALUE strftimev(const char *, VALUE, * to_s -> string * * Returns a string representation of the date in +self+ - * in {ISO 8601 extended date format}[rdoc-ref:strftime_formatting.rdoc@ISO+8601+Format+Specifications] + * in {ISO 8601 extended date format}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications] * ('%Y-%m-%d'): * * Date.new(2001, 2, 3).to_s # => "2001-02-03" @@ -7248,7 +7248,7 @@ date_strftime_internal(int argc, VALUE *argv, VALUE self, * Date.new(2001, 2, 3).strftime # => "2001-02-03" * * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]. + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. * */ static VALUE @@ -7280,7 +7280,7 @@ strftimev(const char *fmt, VALUE self, * asctime -> string * * Equivalent to #strftime with argument '%a %b %e %T %Y' - * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * '%c'): * * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" @@ -7299,7 +7299,7 @@ d_lite_asctime(VALUE self) * iso8601 -> string * * Equivalent to #strftime with argument '%Y-%m-%d' - * (or its {shorthand form}[rdoc-ref:strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] * '%F'); * * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" @@ -7316,7 +7316,7 @@ d_lite_iso8601(VALUE self) * rfc3339 -> string * * Equivalent to #strftime with argument '%FT%T%:z'; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" * @@ -7332,7 +7332,7 @@ d_lite_rfc3339(VALUE self) * rfc2822 -> string * * Equivalent to #strftime with argument '%a, %-d %b %Y %T %z'; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" * @@ -7348,7 +7348,7 @@ d_lite_rfc2822(VALUE self) * httpdate -> string * * Equivalent to #strftime with argument '%a, %d %b %Y %T GMT'; - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" * @@ -8717,7 +8717,7 @@ dt_lite_to_s(VALUE self) * DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" * * For other formats, - * see {Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc]: + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: * */ static VALUE @@ -9524,7 +9524,7 @@ Init_date_core(void) * * - You need both dates and times; \Date handles only dates. * - You need only Gregorian dates (and not Julian dates); - * see {Julian and Gregorian Calendars}[rdoc-ref:date/calendars.rdoc]. + * see {Julian and Gregorian Calendars}[rdoc-ref:language/calendars.rdoc]. * * A \Date object, once created, is immutable, and cannot be modified. * @@ -9571,7 +9571,7 @@ Init_date_core(void) * Date.strptime('fri31dec99', '%a%d%b%y') # => # * * See also the specialized methods in - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:strftime_formatting.rdoc@Specialized+Format+Strings] + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * * == Argument +limit+ * diff --git a/hash.c b/hash.c index a8be0bad75f675..0b98b68d169b44 100644 --- a/hash.c +++ b/hash.c @@ -4899,7 +4899,7 @@ hash_le(VALUE hash1, VALUE hash2) * h0 <= h1 # => true * h1 <= h0 # => false * - * See {Hash Inclusion}[rdoc-ref:hash_inclusion.rdoc]. + * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc]. * * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash. * @@ -4928,7 +4928,7 @@ rb_hash_le(VALUE hash, VALUE other) * h < {foo: 0, bar: 1, baz: 2} # => false # Different key. * h < {foo: 0, bar: 1, baz: 2} # => false # Different value. * - * See {Hash Inclusion}[rdoc-ref:hash_inclusion.rdoc]. + * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc]. * * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash. * @@ -4955,7 +4955,7 @@ rb_hash_lt(VALUE hash, VALUE other) * h0 >= h0 # => true * h1 >= h0 # => false * - * See {Hash Inclusion}[rdoc-ref:hash_inclusion.rdoc]. + * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc]. * * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash. * @@ -4984,7 +4984,7 @@ rb_hash_ge(VALUE hash, VALUE other) * h > {foo: 0, bar: 1} # => false # Different key. * h > {foo: 0, bar: 1} # => false # Different value. * - * See {Hash Inclusion}[rdoc-ref:hash_inclusion.rdoc]. + * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc]. * * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash. * diff --git a/io.c b/io.c index fdd6256ff854dd..a5a8139f2914c9 100644 --- a/io.c +++ b/io.c @@ -7820,7 +7820,7 @@ static VALUE popen_finish(VALUE port, VALUE klass); * whose $stdin and $stdout are connected to a new stream +io+. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * If no block is given, returns the new stream, * which depending on given +mode+ may be open for reading, writing, or both. @@ -8224,7 +8224,7 @@ rb_io_s_sysopen(int argc, VALUE *argv, VALUE _) * Creates an IO object connected to the given file. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * With no block given, file stream is returned: * @@ -8577,7 +8577,7 @@ rb_io_init_copy(VALUE dest, VALUE io) * Formats and writes +objects+ to the stream. * * For details on +format_string+, see - * {Format Specifications}[rdoc-ref:format_specifications.rdoc]. + * {Format Specifications}[rdoc-ref:language/format_specifications.rdoc]. * */ @@ -8598,7 +8598,7 @@ rb_io_printf(int argc, const VALUE *argv, VALUE out) * io.write(sprintf(format_string, *objects)) * * For details on +format_string+, see - * {Format Specifications}[rdoc-ref:format_specifications.rdoc]. + * {Format Specifications}[rdoc-ref:language/format_specifications.rdoc]. * * With the single argument +format_string+, formats +objects+ into the string, * then writes the formatted string to $stdout: @@ -10621,7 +10621,7 @@ argf_readlines(int argc, VALUE *argv, VALUE argf) * sets global variable $? to the process status. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * Examples: * @@ -12035,7 +12035,7 @@ io_s_foreach(VALUE v) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * The first argument must be a string that is the path to a file. * @@ -12138,7 +12138,7 @@ io_s_readlines(VALUE v) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * The first argument must be a string that is the path to a file. * @@ -12227,7 +12227,7 @@ seek_before_access(VALUE argp) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * The first argument must be a string that is the path to a file. * @@ -12298,7 +12298,7 @@ rb_io_s_read(int argc, VALUE *argv, VALUE io) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * */ @@ -12402,7 +12402,7 @@ io_s_write(int argc, VALUE *argv, VALUE klass, int binary) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * The first argument must be a string that is the path to a file. * @@ -12452,7 +12452,7 @@ rb_io_s_write(int argc, VALUE *argv, VALUE io) * * When called from class \IO (but not subclasses of \IO), * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * */ diff --git a/load.c b/load.c index 02b75ceb2bd4e3..c519efe2a13f8d 100644 --- a/load.c +++ b/load.c @@ -1234,7 +1234,7 @@ no_feature_p(const rb_box_t *box, const char *feature, const char *ext, int rb, return 0; } -// Documented in doc/globals.md +// Documented in doc/language/globals.md VALUE rb_resolve_feature_path(VALUE klass, VALUE fname) { diff --git a/object.c b/object.c index c8d59601925e59..097199479e241a 100644 --- a/object.c +++ b/object.c @@ -4094,7 +4094,7 @@ rb_obj_dig(int argc, VALUE *argv, VALUE obj, VALUE notfound) * into +format_string+. * * For details on +format_string+, see - * {Format Specifications}[rdoc-ref:format_specifications.rdoc]. + * {Format Specifications}[rdoc-ref:language/format_specifications.rdoc]. */ static VALUE diff --git a/pack.rb b/pack.rb index d57788b222a126..a8b9e74514fdf5 100644 --- a/pack.rb +++ b/pack.rb @@ -3,7 +3,7 @@ class Array # pack(template, buffer: nil) -> string # # Formats each element in +self+ into a binary string; returns that string. - # See {Packed Data}[rdoc-ref:packed_data.rdoc]. + # See {Packed Data}[rdoc-ref:language/packed_data.rdoc]. def pack(fmt, buffer: nil) Primitive.pack_pack(fmt, buffer) end @@ -15,7 +15,7 @@ class String # unpack(template, offset: 0) -> array # # Extracts data from +self+ to form new objects; - # see {Packed Data}[rdoc-ref:packed_data.rdoc]. + # see {Packed Data}[rdoc-ref:language/packed_data.rdoc]. # # With a block given, calls the block with each unpacked object. # @@ -31,7 +31,7 @@ def unpack(fmt, offset: 0) # unpack1(template, offset: 0) -> object # # Like String#unpack with no block, but unpacks and returns only the first extracted object. - # See {Packed Data}[rdoc-ref:packed_data.rdoc]. + # See {Packed Data}[rdoc-ref:language/packed_data.rdoc]. # # Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. def unpack1(fmt, offset: 0) diff --git a/process.c b/process.c index e2ca6612d2d674..19f3172cb82ec6 100644 --- a/process.c +++ b/process.c @@ -2957,7 +2957,7 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _)); * - Invoking the executable at +exe_path+. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * The new process is created using the * {exec system call}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/execve.html]; @@ -4642,7 +4642,7 @@ rb_spawn(int argc, const VALUE *argv) * - Invoking the executable at +exe_path+. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * Returns: * @@ -4822,7 +4822,7 @@ rb_f_system(int argc, VALUE *argv, VALUE _) * - Invoking the executable at +exe_path+. * * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:command_injection.rdoc]. + * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. * * Returns the process ID (pid) of the new process, * without waiting for it to complete. diff --git a/range.c b/range.c index f26efe57ba810d..f82d1a316ddd7d 100644 --- a/range.c +++ b/range.c @@ -784,7 +784,7 @@ bsearch_integer_range(VALUE beg, VALUE end, int excl) * * Returns an element from +self+ selected by a binary search. * - * See {Binary Searching}[rdoc-ref:bsearch.rdoc]. + * See {Binary Searching}[rdoc-ref:language/bsearch.rdoc]. * */ diff --git a/string.c b/string.c index b1db9cb528fdfe..2dec3a11e61246 100644 --- a/string.c +++ b/string.c @@ -2618,7 +2618,7 @@ rb_str_times(VALUE str, VALUE times) * * Returns the result of formatting +object+ into the format specifications * contained in +self+ - * (see {Format Specifications}[rdoc-ref:format_specifications.rdoc]): + * (see {Format Specifications}[rdoc-ref:language/format_specifications.rdoc]): * * '%05d' % 123 # => "00123" * diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 7023ecab16aa0e..c52c7564ae682e 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -5,7 +5,7 @@ class TestBox < Test::Unit::TestCase EXPERIMENTAL_WARNINGS = [ "warning: Ruby::Box is experimental, and the behavior may change in the future!", - "See doc/box.md for known issues, etc." + "See doc/language/box.md for known issues, etc." ].join("\n") ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} diff --git a/yjit.rb b/yjit.rb index 859c335d229638..9e9a79fa731581 100644 --- a/yjit.rb +++ b/yjit.rb @@ -219,7 +219,7 @@ def self.disasm(iseq) # :nodoc: if !self.enabled? warn( "YJIT needs to be enabled to produce disasm output, e.g.\n" + - "ruby --yjit-call-threshold=1 my_script.rb (see doc/yjit/yjit.md)" + "ruby --yjit-call-threshold=1 my_script.rb (see doc/jit/yjit.md)" ) return nil end @@ -229,7 +229,7 @@ def self.disasm(iseq) # :nodoc: if !disasm_str warn( "YJIT disasm is only available when YJIT is built in dev mode, i.e.\n" + - "./configure --enable-yjit=dev (see doc/yjit/yjit.md)\n" + "./configure --enable-yjit=dev (see doc/jit/yjit.md)\n" ) return nil end From 5b1ed1ef2076b3a89ba28836eee6072cb16124dc Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 27 Nov 2025 23:31:32 +0000 Subject: [PATCH 1407/2435] [DOC] Remove unneeded filename from rdoc-ref links (#15339) --- doc/language/calendars.rdoc | 2 +- doc/language/format_specifications.rdoc | 26 ++++----- doc/language/options.md | 70 ++++++++++++------------- doc/language/strftime_formatting.rdoc | 6 +-- 4 files changed, 51 insertions(+), 53 deletions(-) diff --git a/doc/language/calendars.rdoc b/doc/language/calendars.rdoc index eb9638f55231ca..a2540f1c433b5a 100644 --- a/doc/language/calendars.rdoc +++ b/doc/language/calendars.rdoc @@ -31,7 +31,7 @@ See also {a concrete example here}[rdoc-ref:DateTime@When+should+you+use+DateTim === Argument +start+ Certain methods in class \Date handle differences in the -{Julian and Gregorian calendars}[rdoc-ref:language/calendars.rdoc@Julian+and+Gregorian+Calendars] +{Julian and Gregorian calendars}[rdoc-ref:@Julian+and+Gregorian+Calendars] by accepting an optional argument +start+, whose value may be: - Date::ITALY (the default): the created date is Julian diff --git a/doc/language/format_specifications.rdoc b/doc/language/format_specifications.rdoc index 61eaefec4af179..a59f2103775ec8 100644 --- a/doc/language/format_specifications.rdoc +++ b/doc/language/format_specifications.rdoc @@ -46,42 +46,42 @@ The links lead to the details and examples. === \Integer Type Specifiers - +b+ or +B+: Format +argument+ as a binary integer. - See {Specifiers b and B}[rdoc-ref:language/format_specifications.rdoc@Specifiers+b+and+B]. + See {Specifiers b and B}[rdoc-ref:@Specifiers+b+and+B]. - +d+, +i+, or +u+ (all are identical): Format +argument+ as a decimal integer. - See {Specifier d}[rdoc-ref:language/format_specifications.rdoc@Specifier+d]. + See {Specifier d}[rdoc-ref:@Specifier+d]. - +o+: Format +argument+ as an octal integer. - See {Specifier o}[rdoc-ref:language/format_specifications.rdoc@Specifier+o]. + See {Specifier o}[rdoc-ref:@Specifier+o]. - +x+ or +X+: Format +argument+ as a hexadecimal integer. - See {Specifiers x and X}[rdoc-ref:language/format_specifications.rdoc@Specifiers+x+and+X]. + See {Specifiers x and X}[rdoc-ref:@Specifiers+x+and+X]. === Floating-Point Type Specifiers - +a+ or +A+: Format +argument+ as hexadecimal floating-point number. - See {Specifiers a and A}[rdoc-ref:language/format_specifications.rdoc@Specifiers+a+and+A]. + See {Specifiers a and A}[rdoc-ref:@Specifiers+a+and+A]. - +e+ or +E+: Format +argument+ in scientific notation. - See {Specifiers e and E}[rdoc-ref:language/format_specifications.rdoc@Specifiers+e+and+E]. + See {Specifiers e and E}[rdoc-ref:@Specifiers+e+and+E]. - +f+: Format +argument+ as a decimal floating-point number. - See {Specifier f}[rdoc-ref:language/format_specifications.rdoc@Specifier+f]. + See {Specifier f}[rdoc-ref:@Specifier+f]. - +g+ or +G+: Format +argument+ in a "general" format. - See {Specifiers g and G}[rdoc-ref:language/format_specifications.rdoc@Specifiers+g+and+G]. + See {Specifiers g and G}[rdoc-ref:@Specifiers+g+and+G]. === Other Type Specifiers - +c+: Format +argument+ as a character. - See {Specifier c}[rdoc-ref:language/format_specifications.rdoc@Specifier+c]. + See {Specifier c}[rdoc-ref:@Specifier+c]. - +p+: Format +argument+ as a string via argument.inspect. - See {Specifier p}[rdoc-ref:language/format_specifications.rdoc@Specifier+p]. + See {Specifier p}[rdoc-ref:@Specifier+p]. - +s+: Format +argument+ as a string via argument.to_s. - See {Specifier s}[rdoc-ref:language/format_specifications.rdoc@Specifier+s]. + See {Specifier s}[rdoc-ref:@Specifier+s]. - %: Format +argument+ ('%') as a single percent character. - See {Specifier %}[rdoc-ref:language/format_specifications.rdoc@Specifier+-25]. + See {Specifier %}[rdoc-ref:@Specifier+-25]. == Flags The effect of a flag may vary greatly among type specifiers. These remarks are general in nature. -See {type-specific details}[rdoc-ref:language/format_specifications.rdoc@Type+Specifier+Details+and+Examples]. +See {type-specific details}[rdoc-ref:@Type+Specifier+Details+and+Examples]. Multiple flags may be given with single type specifier; order does not matter. diff --git a/doc/language/options.md b/doc/language/options.md index ededb67f8d8dbf..c805c7dd65c2c0 100644 --- a/doc/language/options.md +++ b/doc/language/options.md @@ -68,15 +68,15 @@ nil See also: -- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-a`: Split Input Lines into Fields @@ -98,15 +98,15 @@ and the default field separator is `$;`. See also: -- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-c`: Check Syntax @@ -228,15 +228,15 @@ The argument must immediately follow the option See also: -- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-h`: Print Short Help Message @@ -314,15 +314,15 @@ $ ruby -ln -e 'p $_' desiderata.txt See also: -- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. -- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-n`: Run Program in `gets` Loop @@ -348,15 +348,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -p}[rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing]: +- {Option -p}[rdoc-ref:@p-3A+-n-2C+with+Printing]: `-n`, with printing. ### `-p`: `-n`, with Printing @@ -377,15 +377,15 @@ be on good terms with all persons. See also: -- {Option -0}[rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: +- {Option -0}[rdoc-ref:@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: Set `$/` (input record separator). -- {Option -a}[rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields]: +- {Option -a}[rdoc-ref:@a-3A+Split+Input+Lines+into+Fields]: Split input lines into fields. -- {Option -F}[rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator]: +- {Option -F}[rdoc-ref:@F-3A+Set+Input+Field+Separator]: Set input field separator. -- {Option -l}[rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: +- {Option -l}[rdoc-ref:@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: Set output record separator; chop lines. -- {Option -n}[rdoc-ref:language/options.md@n-3A+Run+Program+in+gets+Loop]: +- {Option -n}[rdoc-ref:@n-3A+Run+Program+in+gets+Loop]: Run program in `gets` loop. ### `-r`: Require Library @@ -434,7 +434,7 @@ $ ruby -s t.rb -foo=baz -bar=bat ``` The option may not be used with -{option -e}[rdoc-ref:language/options.md@e-3A+Execute+Given+Ruby+Code] +{option -e}[rdoc-ref:@e-3A+Execute+Given+Ruby+Code] ### `-S`: Search Directories in `ENV['PATH']` @@ -583,7 +583,7 @@ ruby - Copyright (C) 1993-2024 Yukihiro Matsumoto ### `--debug`: Alias for `-d` Option `--debug` is an alias for -{option -d}[rdoc-ref:language/options.md@d-3A+Set+-24DEBUG+to+true]. +{option -d}[rdoc-ref:@d-3A+Set+-24DEBUG+to+true]. ### `--disable`: Disable Features @@ -617,9 +617,9 @@ option was given: - `--dump=help`: Same as {option \-\-help}[options_md.html#label--help-3A+Print+Help+Message]. - `--dump=syntax`: - Same as {option -c}[rdoc-ref:language/options.md@c-3A+Check+Syntax]. + Same as {option -c}[rdoc-ref:@c-3A+Check+Syntax]. - `--dump=usage`: - Same as {option -h}[rdoc-ref:language/options.md@h-3A+Print+Short+Help+Message]. + Same as {option -h}[rdoc-ref:@h-3A+Print+Short+Help+Message]. - `--dump=version`: Same as {option \-\-version}[options_md.html#label--version-3A+Print+Ruby+Version]. @@ -641,7 +641,7 @@ see {option --disable}[options_md.html#label--disable-3A+Disable+Features]. ### `--encoding`: Alias for `-E`. Option `--encoding` is an alias for -{option -E}[rdoc-ref:language/options.md@E-3A+Set+Default+Encodings]. +{option -E}[rdoc-ref:@E-3A+Set+Default+Encodings]. ### `--external-encoding`: Set Default External \Encoding diff --git a/doc/language/strftime_formatting.rdoc b/doc/language/strftime_formatting.rdoc index 991a708fa4cb3c..2bfa6b975e3153 100644 --- a/doc/language/strftime_formatting.rdoc +++ b/doc/language/strftime_formatting.rdoc @@ -136,7 +136,7 @@ the only required part is the conversion specifier, so we begin with that. t = Time.now # => 2022-06-29 07:10:20.3230914 -0500 t.strftime('%N') # => "323091400" # Default. - Use {width specifiers}[rdoc-ref:language/strftime_formatting.rdoc@Width+Specifiers] + Use {width specifiers}[rdoc-ref:@Width+Specifiers] to adjust units: t.strftime('%3N') # => "323" # Milliseconds. @@ -522,6 +522,4 @@ An ISO 8601 combined date and time representation may be any ISO 8601 date and any ISO 8601 time, separated by the letter +T+. -For the relevant +strftime+ formats, see -{Dates}[rdoc-ref:language/strftime_formatting.rdoc@Dates] -and {Times}[rdoc-ref:language/strftime_formatting.rdoc@Times] above. +For the relevant +strftime+ formats, see {Dates}[rdoc-ref:@Dates] and {Times}[rdoc-ref:@Times] above. From 9929dc4440a76b57f362ad676a9b8b64c309a6d8 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 20 Nov 2025 02:42:42 -0800 Subject: [PATCH 1408/2435] Mask off unused VWA bits --- gc/default/default.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gc/default/default.c b/gc/default/default.c index 0858d94b89e79c..7f568aaccc1815 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -426,6 +426,7 @@ typedef int (*gc_compact_compare_func)(const void *l, const void *r, void *d); typedef struct rb_heap_struct { short slot_size; + bits_t slot_bits_mask; /* Basic statistics */ size_t total_allocated_pages; @@ -3570,9 +3571,12 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context GC_ASSERT(bitmap_plane_count == HEAP_PAGE_BITMAP_LIMIT - 1 || bitmap_plane_count == HEAP_PAGE_BITMAP_LIMIT); + bits_t slot_mask = heap->slot_bits_mask; + // Skip out of range slots at the head of the page bitset = ~bits[0]; bitset >>= NUM_IN_PAGE(p); + bitset &= slot_mask; if (bitset) { gc_sweep_plane(objspace, heap, p, bitset, ctx); } @@ -3580,6 +3584,7 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context for (int i = 1; i < bitmap_plane_count; i++) { bitset = ~bits[i]; + bitset &= slot_mask; if (bitset) { gc_sweep_plane(objspace, heap, p, bitset, ctx); } @@ -9435,6 +9440,17 @@ rb_gc_impl_objspace_init(void *objspace_ptr) heap->slot_size = (1 << i) * BASE_SLOT_SIZE; + // Bitmask with every (1 << i)th bit set, representing aligned slot positions + static const bits_t slot_bits_masks[] = { + ~(bits_t)0, // i=0: every 1st bit + (bits_t)0x5555555555555555ULL, // i=1: every 2nd bit + (bits_t)0x1111111111111111ULL, // i=2: every 4th bit + (bits_t)0x0101010101010101ULL, // i=3: every 8th bit + (bits_t)0x0001000100010001ULL, // i=4: every 16th bit + }; + GC_ASSERT(i < numberof(slot_bits_masks)); + heap->slot_bits_mask = slot_bits_masks[i]; + ccan_list_head_init(&heap->pages); } From 5e2e45fc242b79e844754691ad106c463ebb6251 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 27 Nov 2025 00:49:28 -0800 Subject: [PATCH 1409/2435] Fix for modgc --- gc/default/default.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 7f568aaccc1815..530fc4df891acb 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -9448,7 +9448,7 @@ rb_gc_impl_objspace_init(void *objspace_ptr) (bits_t)0x0101010101010101ULL, // i=3: every 8th bit (bits_t)0x0001000100010001ULL, // i=4: every 16th bit }; - GC_ASSERT(i < numberof(slot_bits_masks)); + GC_ASSERT(i < sizeof(slot_bits_masks) / sizeof(slot_bits_masks[0])); heap->slot_bits_mask = slot_bits_masks[i]; ccan_list_head_init(&heap->pages); From 404e6aa9b5e56b094b50f54c00487e5a7bfdf142 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 27 Nov 2025 21:14:07 -0500 Subject: [PATCH 1410/2435] [DOC] Fix typo in rb_debug_inspector_current_depth --- include/ruby/debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ruby/debug.h b/include/ruby/debug.h index 0a9f693951dc1f..547d5d94c45f5b 100644 --- a/include/ruby/debug.h +++ b/include/ruby/debug.h @@ -297,7 +297,7 @@ VALUE rb_debug_inspector_frame_depth(const rb_debug_inspector_t *dc, long index) #define RB_DEBUG_INSPECTOR_FRAME_DEPTH(dc, index) rb_debug_inspector_frame_depth(dc, index) /** - * Return current frmae depth. + * Return current frame depth. * * @retval The depth of the current frame in Integer. */ From 5a82880ea98617ab6894cd771ea3c3899f9521bc Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 28 Nov 2025 10:48:53 +0000 Subject: [PATCH 1411/2435] Bump RDoc version to 6.16.1 (#15344) --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index e1e6b01bfdda10..180e1dffcd4dae 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.16.0 https://github.com/ruby/rdoc +rdoc 6.16.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.3 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From dcb9e17f467683b32f429736cf7598b1ce89cdc5 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 28 Nov 2025 10:49:46 +0000 Subject: [PATCH 1412/2435] [DOC] Update bundled gems list at 5a82880ea98617ab6894cd771ea3c3 --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6fea49923f1a96..18265f7c7cada5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -167,7 +167,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.16.0 +* rdoc 6.16.1 * win32ole 1.9.2 * irb 1.15.3 * reline 0.6.3 From 8eaefd93951143661b1f515a8c9cda12263d2b56 Mon Sep 17 00:00:00 2001 From: qraqras Date: Fri, 28 Nov 2025 09:58:38 +0900 Subject: [PATCH 1413/2435] [ruby/prism] Fix invalid Ruby code example in ClassNode comment https://github.com/ruby/prism/commit/5b7456c8f6 --- prism/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 69a46de628e63c..3acab9f58d5d44 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1860,7 +1860,7 @@ nodes: comment: | Represents the location of the `class` keyword. - class Foo end + class Foo; end ^^^^^ - name: constant_path type: node @@ -1899,19 +1899,19 @@ nodes: comment: | Represents the location of the `end` keyword. - class Foo end - ^^^ + class Foo; end + ^^^ - name: name type: constant comment: | The name of the class. - class Foo end # name `:Foo` + class Foo; end # name `:Foo` comment: | Represents a class declaration involving the `class` keyword. - class Foo end - ^^^^^^^^^^^^^ + class Foo; end + ^^^^^^^^^^^^^^ - name: ClassVariableAndWriteNode fields: - name: name From 191bfcb9c505ba3f5771f7ac67d6131aeb6b6837 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 28 Nov 2025 16:17:06 +0100 Subject: [PATCH 1414/2435] Define Kernel#instance_variables_to_inspect [Bug #21718] Otherwise objects that don't define it, but define a fairly liberal `method_missing` method will run into errors that are hard to understand: ```ruby class Foo def method_missing(name, ...) name end end p Foo.new.inspect ``` ``` 'Kernel#inspect': wrong argument type Symbol (expected Array) (TypeError) from ../test.rb:7:in '
' ``` --- object.c | 21 ++++++++++++++++--- spec/ruby/core/kernel/inspect_spec.rb | 29 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/object.c b/object.c index 097199479e241a..4b73fcefa8a646 100644 --- a/object.c +++ b/object.c @@ -854,14 +854,21 @@ rb_obj_inspect(VALUE obj) { VALUE ivars = rb_check_funcall(obj, id_instance_variables_to_inspect, 0, 0); st_index_t n = 0; - if (UNDEF_P(ivars)) { + if (UNDEF_P(ivars) || NIL_P(ivars)) { n = rb_ivar_count(obj); ivars = Qnil; } - else if (!NIL_P(ivars)) { - Check_Type(ivars, T_ARRAY); + else if (RB_TYPE_P(ivars, T_ARRAY)) { n = RARRAY_LEN(ivars); } + else { + rb_raise( + rb_eTypeError, + "Expected #instance_variables_to_inspect to return an Array or nil, but it returned %"PRIsVALUE, + rb_obj_class(ivars) + ); + } + if (n > 0) { VALUE c = rb_class_name(CLASS_OF(obj)); VALUE args[2] = { @@ -875,6 +882,13 @@ rb_obj_inspect(VALUE obj) } } +/* :nodoc: */ +static VALUE +rb_obj_instance_variables_to_inspect(VALUE obj) +{ + return Qnil; +} + static VALUE class_or_module_required(VALUE c) { @@ -4535,6 +4549,7 @@ InitVM_Object(void) rb_define_method(rb_mKernel, "to_s", rb_any_to_s, 0); rb_define_method(rb_mKernel, "inspect", rb_obj_inspect, 0); + rb_define_private_method(rb_mKernel, "instance_variables_to_inspect", rb_obj_instance_variables_to_inspect, 0); rb_define_method(rb_mKernel, "methods", rb_obj_methods, -1); /* in class.c */ rb_define_method(rb_mKernel, "singleton_methods", rb_obj_singleton_methods, -1); /* in class.c */ rb_define_method(rb_mKernel, "protected_methods", rb_obj_protected_methods, -1); /* in class.c */ diff --git a/spec/ruby/core/kernel/inspect_spec.rb b/spec/ruby/core/kernel/inspect_spec.rb index 6ecf1e1c8c86d1..1fa66cab98a979 100644 --- a/spec/ruby/core/kernel/inspect_spec.rb +++ b/spec/ruby/core/kernel/inspect_spec.rb @@ -57,5 +57,34 @@ class << obj inspected = obj.inspect.sub(/^#" end + + it "displays all instance variables if #instance_variables_to_inspect returns nil" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = nil + end + + inspected = obj.inspect.sub(/^#} + end + + it "raises an error if #instance_variables_to_inspect returns an invalid value" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = {} + end + + ->{ obj.inspect }.should raise_error(TypeError, "Expected #instance_variables_to_inspect to return an Array or nil, but it returned Hash") + end end end From 8c70def42c4596a02d45464d0d694d0c234fb1d7 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 27 Nov 2025 22:57:48 -0800 Subject: [PATCH 1415/2435] Use ALWAYS_INLINE for vm_getinstancevariable Recently rb_vm_getinstancevariable was introduced exposing this method to ZJIT. On clang specifically this ended up causing the compiler not to inline into vm_exec_core and cause a significant performance regression in optcarrot for the interpreter. Co-authored-by: Luke Gruber --- vm_insnhelper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index c9913a92bbd748..60e1b647ae5d8d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1680,6 +1680,7 @@ rb_vm_setclassvariable(const rb_iseq_t *iseq, const rb_control_frame_t *cfp, ID vm_setclassvariable(iseq, cfp, id, val, ic); } +ALWAYS_INLINE(static VALUE vm_getinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, IVC ic)); static inline VALUE vm_getinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, IVC ic) { From 413d15a3440abf570fc4bff24fe4b5ee8b7880a5 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Fri, 28 Nov 2025 02:08:21 +0100 Subject: [PATCH 1416/2435] [ruby/rubygems] Add `MAKEFLAGS=-j` by default before compiling: - Depending on the native extension, it can greatly reduce compilation time when executing recipes simultaneously. For example on Prism: ``` # Before time gem install prism Building native extensions. This could take a while... Successfully installed prism-1.6.0 1 gem installed gem install prism 3.61s user 0.80s system 95% cpu 4.595 total ``` ``` # After time MAKEFLAGS="-j" gem install prism Building native extensions. This could take a while... Successfully installed prism-1.6.0 1 gem installed MAKEFLAGS="-j" gem install prism 4.47s user 1.27s system 246% cpu 2.330 total ``` I don't think adding `-j` as a default is harmful, but I'm admitedly not very knowledgable when it comes to compiler. https://github.com/ruby/rubygems/commit/61340081c6 Co-authored-by: Aaron Patterson --- lib/rubygems/ext/ext_conf_builder.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index ec2fa59412df64..021273ef12e3f0 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -40,6 +40,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil + ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig From 7bd80e7e3e3dbb724616c038e6e0944128d6f905 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 29 Nov 2025 07:06:24 +0900 Subject: [PATCH 1417/2435] nmake didn't support -j flag --- lib/rubygems/ext/ext_conf_builder.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 021273ef12e3f0..745cf08b6c8b45 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -40,7 +40,9 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" + unless RbConfig::CONFIG["MAKE"] =~ /nmake/ + ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" + end make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig From bb2e4d58cce135d3609dba1ee17ed614522a88bf Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 28 Nov 2025 16:57:15 +0900 Subject: [PATCH 1418/2435] [ruby/rubygems] Fixed checksums generation issue when no source is specified https://github.com/ruby/rubygems/commit/bb4d791cb4 --- lib/bundler/definition.rb | 2 ++ spec/bundler/commands/lock_spec.rb | 50 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 437390f3ec448b..2fa7d0d277943b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -539,6 +539,8 @@ def unlocking? end def add_checksums + require "rubygems/package" + @locked_checksums = true setup_domain!(add_checksums: true) diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index ab1926734c1e78..c8af9c8dd404fb 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -2035,6 +2035,56 @@ L end + it "adds checksums when source is not specified" do + system_gems(%w[myrack-1.0.0], path: default_bundle_path) + + gemfile <<-G + gem "myrack" + G + + lockfile <<~L + GEM + specs: + myrack (1.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "lock --add-checksums" + end + + # myrack is coming from gem_repo1 + # but it's simulated to install in the system gems path + checksums = checksums_section do |c| + c.checksum gem_repo1, "myrack", "1.0.0" + end + + expect(lockfile).to eq <<~L + GEM + specs: + myrack (1.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + it "adds checksums to an existing lockfile, when gems are already installed" do build_repo4 do build_gem "nokogiri", "1.14.2" From f18bedaf96d950a69ae18df9a7eeea6754224c20 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 29 Nov 2025 07:18:43 +0900 Subject: [PATCH 1419/2435] [ruby/rubygems] Use String#include? with suggested by Performance/StringInclude cop https://github.com/ruby/rubygems/commit/fdd3419144 --- lib/rubygems/ext/ext_conf_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 745cf08b6c8b45..bc70ed67fb8604 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -40,7 +40,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - unless RbConfig::CONFIG["MAKE"] =~ /nmake/ + unless RbConfig::CONFIG["MAKE"]&.include?("nmake") ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" end From 7dae2a1f488afc4133dfbd6ea243aaeb71107f71 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 26 Nov 2025 18:58:42 +0100 Subject: [PATCH 1420/2435] [ruby/rubygems] Restore `install` as default command Fix: https://github.com/ruby/rubygems/issues/9124 This behavior is a deeply entrenched convention and changing it will annoy lots of developers with unclear gains. https://github.com/ruby/rubygems/commit/628e0ede46 --- lib/bundler/cli.rb | 12 +----------- spec/bundler/bundler/cli_spec.rb | 3 +-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 55f656e3f3afd0..b7829f2a0db248 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -123,17 +123,7 @@ def cli_help def self.default_command(meth = nil) return super if meth - default_cli_command = Bundler.settings[:default_cli_command] - return default_cli_command if default_cli_command - - Bundler.ui.warn(<<~MSG) - In the next version of Bundler, running `bundle` without argument will no longer run `bundle install`. - Instead, the `help` command will be displayed. - - If you'd like to keep the previous behaviour please run `bundle config set default_cli_command install --global`. - MSG - - "install" + Bundler.settings[:default_cli_command] || "install" end class_option "no-color", type: :boolean, desc: "Disable colorization in output" diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index ee3c0d0fd50ff5..611c1985e277b3 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,9 +87,8 @@ def out_with_macos_man_workaround end context "with no arguments" do - it "installs and log a warning by default" do + it "installs by default" do bundle "", raise_on_error: false - expect(err).to include("running `bundle` without argument will no longer run `bundle install`.") expect(err).to include("Could not locate Gemfile") end From 69293f52550032cbda8d92188407b8700f4c3bb1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 26 Nov 2025 20:03:13 +0100 Subject: [PATCH 1421/2435] [ruby/rubygems] Print help summary when the default command fail As mentioned in https://github.com/ruby/rubygems/issues/9124, the intent for changing the default command was to be more welcoming. I think we can acheive that by attempting to install, but to print that same help message if there is no Gemfile. That should address both concerns. https://github.com/ruby/rubygems/commit/f3f505c02a --- lib/bundler/cli.rb | 23 ++++++++++++++++++++++- lib/bundler/vendor/thor/lib/thor.rb | 2 +- spec/bundler/bundler/cli_spec.rb | 13 +------------ spec/bundler/other/cli_dispatch_spec.rb | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index b7829f2a0db248..d3c948c7ce22ce 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -120,10 +120,18 @@ def cli_help self.class.send(:class_options_help, shell) end + desc "install_or_cli_help", "Tries to run bundle install but prints a summary of bundler commands if there is no Gemfile", hide: true + def install_or_cli_help + invoke_other_command("install") + rescue GemfileNotFound => error + Bundler.ui.error error.message, wrap: true + invoke_other_command("cli_help") + end + def self.default_command(meth = nil) return super if meth - Bundler.settings[:default_cli_command] || "install" + Bundler.settings[:default_cli_command] || "install_or_cli_help" end class_option "no-color", type: :boolean, desc: "Disable colorization in output" @@ -713,6 +721,19 @@ def current_command config[:current_command] end + def invoke_other_command(name) + _, _, config = @_initializer + original_command = config[:current_command] + command = self.class.all_commands[name] + config[:current_command] = command + send(name) + ensure + config[:current_command] = original_command + end + + def current_command=(command) + end + def print_command return unless Bundler.ui.debug? cmd = current_command diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb index bfd9f5c91412ab..945bdbd5515fe5 100644 --- a/lib/bundler/vendor/thor/lib/thor.rb +++ b/lib/bundler/vendor/thor/lib/thor.rb @@ -625,7 +625,7 @@ def normalize_command_name(meth) #:nodoc: # alias name. def find_command_possibilities(meth) len = meth.to_s.length - possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort + possibilities = all_commands.reject { |_k, c| c.hidden? }.merge(map).keys.select { |n| meth == n[0, len] }.sort unique_possibilities = possibilities.map { |k| map[k] || k }.uniq if possibilities.include?(meth) diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 611c1985e277b3..33a23a75def180 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,27 +87,16 @@ def out_with_macos_man_workaround end context "with no arguments" do - it "installs by default" do + it "tries to installs by default but print help on missing Gemfile" do bundle "", raise_on_error: false expect(err).to include("Could not locate Gemfile") - end - it "prints a concise help message when default_cli_command set to cli_help" do - bundle "config set default_cli_command cli_help" - bundle "" - expect(err).to be_empty expect(out).to include("Bundler version #{Bundler::VERSION}"). and include("\n\nBundler commands:\n\n"). and include("\n\n Primary commands:\n"). and include("\n\n Utilities:\n"). and include("\n\nOptions:\n") end - - it "runs bundle install when default_cli_command set to install" do - bundle "config set default_cli_command install" - bundle "", raise_on_error: false - expect(err).to include("Could not locate Gemfile") - end end context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb index 1039737b993983..a2c745b0703d24 100644 --- a/spec/bundler/other/cli_dispatch_spec.rb +++ b/spec/bundler/other/cli_dispatch_spec.rb @@ -15,6 +15,6 @@ it "print a friendly error when ambiguous" do bundle "in", raise_on_error: false - expect(err).to eq("Ambiguous command in matches [info, init, inject, install]") + expect(err).to eq("Ambiguous command in matches [info, init, install]") end end From a9d2a46d64ead4dae8491c2f93b9e86416006fd4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 28 Nov 2025 19:23:50 +0900 Subject: [PATCH 1422/2435] [ruby/rubygems] Add informational message when default_cli_command is unset. https://github.com/ruby/rubygems/commit/9e44b5ebc4 --- lib/bundler/cli.rb | 10 ++++++++++ spec/bundler/bundler/cli_spec.rb | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index d3c948c7ce22ce..f5977863ddcc26 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -131,6 +131,16 @@ def install_or_cli_help def self.default_command(meth = nil) return super if meth + unless Bundler.settings[:default_cli_command] + Bundler.ui.info <<-MSG + In the feature version of Bundler, running `bundle` without argument will no longer run `bundle install`. + Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. + If you wish to use feature behavior now with `bundle config set default_cli_command cli_help --global` + or you can continue to use the old behavior with `bundle config set default_cli_command install_or_cli_help --global`. + This message will be removed after a default_cli_command value is set. + MSG + end + Bundler.settings[:default_cli_command] || "install_or_cli_help" end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 33a23a75def180..ac6b77ad7483bf 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -90,6 +90,7 @@ def out_with_macos_man_workaround it "tries to installs by default but print help on missing Gemfile" do bundle "", raise_on_error: false expect(err).to include("Could not locate Gemfile") + expect(out).to include("In the feature version of Bundler") expect(out).to include("Bundler version #{Bundler::VERSION}"). and include("\n\nBundler commands:\n\n"). @@ -97,6 +98,13 @@ def out_with_macos_man_workaround and include("\n\n Utilities:\n"). and include("\n\nOptions:\n") end + + it "runs bundle install when default_cli_command set to install" do + bundle "config set default_cli_command install_or_cli_help" + bundle "", raise_on_error: false + expect(out).to_not include("In the feature version of Bundler") + expect(err).to include("Could not locate Gemfile") + end end context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do From 34290048be4a480437bbfff3dc5d8bf93e495d9b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 29 Nov 2025 09:05:14 +0900 Subject: [PATCH 1423/2435] Fixup with mswin and nmake build for -j flag --- lib/rubygems/ext/ext_conf_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index bc70ed67fb8604..5bc8b01f3be9f8 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -40,7 +40,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - unless RbConfig::CONFIG["MAKE"]&.include?("nmake") + unless RUBY_PLATFORM =~ /mswin/ && RbConfig::CONFIG["configure_args"]&.include?("nmake") ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" end From 688350dacc4bab436043bd2435fc64633a1408ce Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 25 Nov 2025 05:11:52 +0000 Subject: [PATCH 1424/2435] [DOC] Avoid term 'derived'; use 'subclass' --- numeric.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/numeric.c b/numeric.c index a2c91d87a57db3..54c9649a87fa55 100644 --- a/numeric.c +++ b/numeric.c @@ -595,8 +595,8 @@ num_uminus(VALUE num) * fdiv(other) -> float * * Returns the quotient self/other as a float, - * using method +/+ in the derived class of +self+. - * (\Numeric itself does not define method +/+.) + * using method +/+ as defined in the subclass of \Numeric. + * (\Numeric itself does not define +/+.) * * Of the Core and Standard Library classes, * only BigDecimal uses this implementation. @@ -614,8 +614,8 @@ num_fdiv(VALUE x, VALUE y) * div(other) -> integer * * Returns the quotient self/other as an integer (via +floor+), - * using method +/+ in the derived class of +self+. - * (\Numeric itself does not define method +/+.) + * using method +/+ as defined in the subclass of \Numeric. + * (\Numeric itself does not define +/+.) * * Of the Core and Standard Library classes, * Only Float and Rational use this implementation. @@ -847,7 +847,8 @@ num_nonzero_p(VALUE num) * to_int -> integer * * Returns +self+ as an integer; - * converts using method +to_i+ in the derived class. + * converts using method +to_i+ in the subclass of \Numeric. + * (\Numeric itself does not define +to_i+.) * * Of the Core and Standard Library classes, * only Rational and Complex use this implementation. From 005fba0713947a8241b7560ec4a6f052f49067c0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 28 Nov 2025 19:27:35 -0600 Subject: [PATCH 1425/2435] [DOC] Tweaks for Module#<=> --- object.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/object.c b/object.c index 4b73fcefa8a646..e960f1855b0c80 100644 --- a/object.c +++ b/object.c @@ -2012,14 +2012,26 @@ rb_mod_gt(VALUE mod, VALUE arg) /* * call-seq: - * module <=> other_module -> -1, 0, +1, or nil + * self <=> object -> -1, 0, +1, or nil * - * Comparison---Returns -1, 0, +1 or nil depending on whether +module+ - * includes +other_module+, they are the same, or if +module+ is included by - * +other_module+. + * Returns: + * + * - +-1+, if +self+ includes +object+, if or +self+ is a subclass of +object+. + * - +0+, if +self+ and +object+ are the same. + * - +1+, if +object+ includes +self+, or if +object+ is a subclass of +self+. + * - +nil+, if none of the above is true. + * + * Examples: + * + * # Class Array includes module Enumerable. + * Array <=> Enumerable # => -1 + * Enumerable <=> Enumerable # => 0 + * Enumerable <=> Array # => 1 + * # Class File is a subclass of class IO. + * File <=> IO # => -1 + * IO <=> File # => 1 + * File <=> File # => 0 * - * Returns +nil+ if +module+ has no relationship with +other_module+, if - * +other_module+ is not a module, or if the two values are incomparable. */ static VALUE From 0f80d0ee05a2cb9daad426a7aa9aad7c4f2c3401 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 28 Nov 2025 20:39:24 -0500 Subject: [PATCH 1426/2435] [DOC] Fix backticks in InstructionSequence docs --- iseq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iseq.c b/iseq.c index 9bf56ad6557545..b53362b2444709 100644 --- a/iseq.c +++ b/iseq.c @@ -1652,7 +1652,7 @@ iseqw_s_compile_parser(int argc, VALUE *argv, VALUE self, bool prism) * real path and first line number of the ruby code in +source+ which are * metadata attached to the returned +iseq+. * - * +file+ is used for `__FILE__` and exception backtrace. +path+ is used for + * +file+ is used for +__FILE__+ and exception backtrace. +path+ is used for * +require_relative+ base. It is recommended these should be the same full * path. * @@ -1694,7 +1694,7 @@ iseqw_s_compile(int argc, VALUE *argv, VALUE self) * real path and first line number of the ruby code in +source+ which are * metadata attached to the returned +iseq+. * - * +file+ is used for `__FILE__` and exception backtrace. +path+ is used for + * +file+ is used for +__FILE__+ and exception backtrace. +path+ is used for * +require_relative+ base. It is recommended these should be the same full * path. * @@ -1736,7 +1736,7 @@ iseqw_s_compile_parsey(int argc, VALUE *argv, VALUE self) * real path and first line number of the ruby code in +source+ which are * metadata attached to the returned +iseq+. * - * +file+ is used for `__FILE__` and exception backtrace. +path+ is used for + * +file+ is used for +__FILE__+ and exception backtrace. +path+ is used for * +require_relative+ base. It is recommended these should be the same full * path. * From 82b91ec7e55cb5ef4acd61213843614542bea3b3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 29 Nov 2025 10:06:03 +0900 Subject: [PATCH 1427/2435] [ruby/rubygems] Also use String#include? for RUBY_PLATFORM https://github.com/ruby/rubygems/commit/5fd95f38d3 --- lib/rubygems/ext/ext_conf_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 5bc8b01f3be9f8..81491eac79abbc 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -40,7 +40,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - unless RUBY_PLATFORM =~ /mswin/ && RbConfig::CONFIG["configure_args"]&.include?("nmake") + unless RUBY_PLATFORM.include?("mswin") && RbConfig::CONFIG["configure_args"]&.include?("nmake") ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" end From e13ad22274ff2432f705536832f03f360a5b4af5 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 28 Nov 2025 20:28:42 -0500 Subject: [PATCH 1428/2435] Re-enable clang-18 The issue might have been fixed in 8bf333a. --- .github/workflows/compilers.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index f8ff22fe10fa0a..3ecc749c0456a0 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -111,8 +111,7 @@ jobs: - { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' }, timeout-minutes: 5 } - # clang-18 has a bug causing ruby_current_ec to sometimes be null - # - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' }, timeout-minutes: 5 } From c8bfbd5700bc00ab06e715235818c099a0063436 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 30 Nov 2025 01:29:01 +0900 Subject: [PATCH 1429/2435] [ruby/openssl] ts: fix docs for attrs on OpenSSL::Timestamp::Factory Move attribute documentation out of the class-level section and into the appropriate sections so that they attach correctly. https://github.com/ruby/openssl/commit/61410acc50 --- ext/openssl/ossl_ts.c | 76 ++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 51 deletions(-) diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 575418ccc6ce46..9dd9c5c765baea 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -1511,65 +1511,39 @@ Init_ossl_ts(void) * fac.default_policy_id = '1.2.3.4.5' * fac.additional_certificates = [ inter1, inter2 ] * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) - * - * ==Attributes - * - * ===default_policy_id + */ + cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); + /* + * The list of digest algorithms that the factory is allowed + * create timestamps for. Known vulnerable or weak algorithms should not be + * allowed where possible. Must be an Array of String or OpenSSL::Digest + * subclass instances. + */ + rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); + /* + * A String representing the default policy object identifier, or +nil+. * * Request#policy_id will always be preferred over this if present in the - * Request, only if Request#policy_id is nil default_policy will be used. + * Request, only if Request#policy_id is +nil+ default_policy will be used. * If none of both is present, a TimestampError will be raised when trying * to create a Response. - * - * call-seq: - * factory.default_policy_id = "string" -> string - * factory.default_policy_id -> string or nil - * - * ===serial_number - * - * Sets or retrieves the serial number to be used for timestamp creation. - * Must be present for timestamp creation. - * - * call-seq: - * factory.serial_number = number -> number - * factory.serial_number -> number or nil - * - * ===gen_time - * - * Sets or retrieves the Time value to be used in the Response. Must be - * present for timestamp creation. - * - * call-seq: - * factory.gen_time = Time -> Time - * factory.gen_time -> Time or nil - * - * ===additional_certs - * - * Sets or retrieves additional certificates apart from the timestamp - * certificate (e.g. intermediate certificates) to be added to the Response. - * Must be an Array of OpenSSL::X509::Certificate. - * - * call-seq: - * factory.additional_certs = [cert1, cert2] -> [ cert1, cert2 ] - * factory.additional_certs -> array or nil - * - * ===allowed_digests - * - * Sets or retrieves the digest algorithms that the factory is allowed - * create timestamps for. Known vulnerable or weak algorithms should not be - * allowed where possible. - * Must be an Array of String or OpenSSL::Digest subclass instances. - * - * call-seq: - * factory.allowed_digests = ["sha1", OpenSSL::Digest.new('SHA256').new] -> [ "sha1", OpenSSL::Digest) ] - * factory.allowed_digests -> array or nil - * */ - cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); - rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("default_policy_id"), 1, 1, 0); + /* + * The serial number to be used for timestamp creation. Must be present for + * timestamp creation. Must be an instance of OpenSSL::BN or Integer. + */ rb_attr(cTimestampFactory, rb_intern_const("serial_number"), 1, 1, 0); + /* + * The Time value to be used in the Response. Must be present for timestamp + * creation. + */ rb_attr(cTimestampFactory, rb_intern_const("gen_time"), 1, 1, 0); + /* + * Additional certificates apart from the timestamp certificate (e.g. + * intermediate certificates) to be added to the Response. + * Must be an Array of OpenSSL::X509::Certificate, or +nil+. + */ rb_attr(cTimestampFactory, rb_intern_const("additional_certs"), 1, 1, 0); rb_define_method(cTimestampFactory, "create_timestamp", ossl_tsfac_create_ts, 3); } From bae06ce22c5ab6a4a3085300274f258d55858e90 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 30 Nov 2025 01:47:26 +0900 Subject: [PATCH 1430/2435] [ruby/openssl] Remove dummy declarations for mOSSL and eOSSLError These declarations were added to every source file because older versions of RDoc did not resolve ancestor tree across files. Since RDoc 6.9.0 supports this, this workaround is no longer needed. https://redirect.github.com/ruby/rdoc/pull/1217 https://github.com/ruby/openssl/commit/6491ce63be --- ext/openssl/ossl_asn1.c | 5 ----- ext/openssl/ossl_bn.c | 5 ----- ext/openssl/ossl_cipher.c | 5 ----- ext/openssl/ossl_config.c | 5 ----- ext/openssl/ossl_digest.c | 5 ----- ext/openssl/ossl_engine.c | 5 ----- ext/openssl/ossl_hmac.c | 5 ----- ext/openssl/ossl_kdf.c | 5 ----- ext/openssl/ossl_ns_spki.c | 5 ----- ext/openssl/ossl_ocsp.c | 5 ----- ext/openssl/ossl_pkcs12.c | 5 ----- ext/openssl/ossl_pkcs7.c | 5 ----- ext/openssl/ossl_pkey.c | 5 ----- ext/openssl/ossl_pkey_dh.c | 6 ------ ext/openssl/ossl_pkey_dsa.c | 6 ------ ext/openssl/ossl_pkey_ec.c | 7 ------- ext/openssl/ossl_pkey_rsa.c | 6 ------ ext/openssl/ossl_provider.c | 5 ----- ext/openssl/ossl_rand.c | 5 ----- ext/openssl/ossl_ssl.c | 2 -- ext/openssl/ossl_ssl_session.c | 5 ----- ext/openssl/ossl_ts.c | 4 ---- ext/openssl/ossl_x509.c | 4 ---- ext/openssl/ossl_x509attr.c | 6 ------ ext/openssl/ossl_x509cert.c | 6 ------ ext/openssl/ossl_x509crl.c | 6 ------ ext/openssl/ossl_x509ext.c | 6 ------ ext/openssl/ossl_x509name.c | 6 ------ ext/openssl/ossl_x509req.c | 6 ------ ext/openssl/ossl_x509revoked.c | 6 ------ ext/openssl/ossl_x509store.c | 6 ------ 31 files changed, 163 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 1309811c742e31..38397ebda87c86 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -1288,11 +1288,6 @@ Init_ossl_asn1(void) VALUE ary; int i; -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - sym_UNIVERSAL = ID2SYM(rb_intern_const("UNIVERSAL")); sym_CONTEXT_SPECIFIC = ID2SYM(rb_intern_const("CONTEXT_SPECIFIC")); sym_APPLICATION = ID2SYM(rb_intern_const("APPLICATION")); diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index a6fa2c1daba3e9..0ac8ae248ef9e4 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -1202,11 +1202,6 @@ ossl_bn_set_flags(VALUE self, VALUE arg) void Init_ossl_bn(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - #ifdef HAVE_RB_EXT_RACTOR_SAFE ossl_bn_ctx_key = rb_ractor_local_storage_ptr_newkey(&ossl_bn_ctx_key_type); #else diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index a52518291124b3..f7b2c01a36d933 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -901,11 +901,6 @@ ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) void Init_ossl_cipher(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Cipher * * Provides symmetric algorithms for encryption and decryption. The diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c index 9be5fe6f6320dc..274875a97830d0 100644 --- a/ext/openssl/ossl_config.c +++ b/ext/openssl/ossl_config.c @@ -413,11 +413,6 @@ Init_ossl_config(void) char *path; VALUE path_str; -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Config * * Configuration for the openssl library. diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index e2f1af7e61aa8f..8c1b825cf3909c 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -365,11 +365,6 @@ ossl_digest_block_length(VALUE self) void Init_ossl_digest(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-class: OpenSSL::Digest * * OpenSSL::Digest allows you to compute message digests (sometimes diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index 1565068743802e..e40f0ff68de37f 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -466,11 +466,6 @@ ossl_engine_inspect(VALUE self) void Init_ossl_engine(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cEngine = rb_define_class_under(mOSSL, "Engine", rb_cObject); eEngineError = rb_define_class_under(cEngine, "EngineError", eOSSLError); diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index 250b427bdcc8f6..8153f64fc57d65 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -256,11 +256,6 @@ ossl_hmac_reset(VALUE self) void Init_ossl_hmac(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Document-class: OpenSSL::HMAC * diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index e7429a76881c7d..9450612ee2d375 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -239,11 +239,6 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) void Init_ossl_kdf(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Document-module: OpenSSL::KDF * diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index 51ec8532c3ee8c..e1589fea0e0b24 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -378,11 +378,6 @@ ossl_spki_verify(VALUE self, VALUE key) void Init_ossl_ns_spki(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - mNetscape = rb_define_module_under(mOSSL, "Netscape"); eSPKIError = rb_define_class_under(mNetscape, "SPKIError", eOSSLError); diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 390215a8e3d67c..b19b51a4ffbb85 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1626,11 +1626,6 @@ ossl_ocspcid_to_der(VALUE self) void Init_ossl_ocsp(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * OpenSSL::OCSP implements Online Certificate Status Protocol requests * and responses. diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index f76e1625f596a2..8aff77a6dad934 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -300,11 +300,6 @@ void Init_ossl_pkcs12(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* * Defines a file format commonly used to store private keys with * accompanying public key certificates, protected with a password-based diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 0fcae1971cfa4b..7bd6c18f92acbb 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -1099,11 +1099,6 @@ void Init_ossl_pkcs7(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cPKCS7 = rb_define_class_under(mOSSL, "PKCS7", rb_cObject); ePKCS7Error = rb_define_class_under(cPKCS7, "PKCS7Error", eOSSLError); rb_define_singleton_method(cPKCS7, "read_smime", ossl_pkcs7_s_read_smime, 1); diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 2d66c6ce625d34..61ff472aadd1b5 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -1658,11 +1658,6 @@ void Init_ossl_pkey(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - /* Document-module: OpenSSL::PKey * * == Asymmetric Public Key Algorithms diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 79509bef3d2f82..60cd20a857fc73 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -357,12 +357,6 @@ OSSL_PKEY_BN_DEF2(dh, DH, key, pub_key, priv_key) void Init_ossl_dh(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - /* Document-class: OpenSSL::PKey::DH * * An implementation of the Diffie-Hellman key exchange protocol based on diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 34e1c7052165be..8b141afab775b1 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -334,12 +334,6 @@ OSSL_PKEY_BN_DEF2(dsa, DSA, key, pub_key, priv_key) void Init_ossl_dsa(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - /* Document-class: OpenSSL::PKey::DSA * * DSA, the Digital Signature Algorithm, is specified in NIST's diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index a9133062081c05..bf77e0fa0556d3 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -1526,13 +1526,6 @@ static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self) void Init_ossl_ec(void) { #undef rb_intern -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - /* * Document-class: OpenSSL::PKey::EC * diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index ed65121acc8849..9cc0bfa37933f9 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -536,12 +536,6 @@ OSSL_PKEY_BN_DEF3(rsa, RSA, crt_params, dmp1, dmq1, iqmp) void Init_ossl_rsa(void) { -#if 0 - mPKey = rb_define_module_under(mOSSL, "PKey"); - cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); - ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError); -#endif - /* Document-class: OpenSSL::PKey::RSA * * RSA is an asymmetric public key algorithm that has been formalized in diff --git a/ext/openssl/ossl_provider.c b/ext/openssl/ossl_provider.c index 529a5e1c72ab0a..ea5abb8e4830f4 100644 --- a/ext/openssl/ossl_provider.c +++ b/ext/openssl/ossl_provider.c @@ -185,11 +185,6 @@ ossl_provider_inspect(VALUE self) void Init_ossl_provider(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - cProvider = rb_define_class_under(mOSSL, "Provider", rb_cObject); eProviderError = rb_define_class_under(cProvider, "ProviderError", eOSSLError); diff --git a/ext/openssl/ossl_rand.c b/ext/openssl/ossl_rand.c index 764900dfc656ba..97d01903e276ae 100644 --- a/ext/openssl/ossl_rand.c +++ b/ext/openssl/ossl_rand.c @@ -175,11 +175,6 @@ ossl_rand_status(VALUE self) void Init_ossl_rand(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - mRandom = rb_define_module_under(mOSSL, "Random"); eRandomError = rb_define_class_under(mRandom, "RandomError", eOSSLError); diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 583396ebfc6f3b..454f3de4a4b9ed 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -2704,8 +2704,6 @@ void Init_ossl_ssl(void) { #if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); rb_mWaitReadable = rb_define_module_under(rb_cIO, "WaitReadable"); rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #endif diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c index 55b9d6c6d57e88..010adf20ac9618 100644 --- a/ext/openssl/ossl_ssl_session.c +++ b/ext/openssl/ossl_ssl_session.c @@ -305,11 +305,6 @@ static VALUE ossl_ssl_session_to_text(VALUE self) void Init_ossl_ssl_session(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - mSSL = rb_define_module_under(mOSSL, "SSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif #ifndef OPENSSL_NO_SOCK cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 9dd9c5c765baea..e071ee84811c75 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -1298,10 +1298,6 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) void Init_ossl_ts(void) { - #if 0 - mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ - #endif - /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server rejects the message imprint algorithm used in the diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c index 2d552d78478b5b..04adfab960799a 100644 --- a/ext/openssl/ossl_x509.c +++ b/ext/openssl/ossl_x509.c @@ -29,10 +29,6 @@ ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) void Init_ossl_x509(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); -#endif - mX509 = rb_define_module_under(mOSSL, "X509"); Init_ossl_x509attr(); diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index 9beb15f4a0ca71..998f87b0e8649b 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -288,12 +288,6 @@ ossl_x509attr_to_der(VALUE self) void Init_ossl_x509attr(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509AttrError = rb_define_class_under(mX509, "AttributeError", eOSSLError); cX509Attr = rb_define_class_under(mX509, "Attribute", rb_cObject); diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index cf00eb78bf9c03..dca28395935a12 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -886,12 +886,6 @@ ossl_x509_load(VALUE klass, VALUE buffer) void Init_ossl_x509cert(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509CertError = rb_define_class_under(mX509, "CertificateError", eOSSLError); /* Document-class: OpenSSL::X509::Certificate diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index af4803f47d3cc9..19f08053e15c82 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -506,12 +506,6 @@ ossl_x509crl_add_extension(VALUE self, VALUE extension) void Init_ossl_x509crl(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509CRLError = rb_define_class_under(mX509, "CRLError", eOSSLError); cX509CRL = rb_define_class_under(mX509, "CRL", rb_cObject); diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 01b342b5be7a32..a982efd798b939 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -441,12 +441,6 @@ void Init_ossl_x509ext(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509ExtError = rb_define_class_under(mX509, "ExtensionError", eOSSLError); cX509ExtFactory = rb_define_class_under(mX509, "ExtensionFactory", rb_cObject); diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 5483450aa7606b..3c0c6aca2905be 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -496,12 +496,6 @@ Init_ossl_x509name(void) #undef rb_intern VALUE utf8str, ptrstr, ia5str, hash; -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - id_aref = rb_intern("[]"); eX509NameError = rb_define_class_under(mX509, "NameError", eOSSLError); cX509Name = rb_define_class_under(mX509, "Name", rb_cObject); diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index 1ae0fd56a6e612..d7252379cdda4d 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -414,12 +414,6 @@ ossl_x509req_add_attribute(VALUE self, VALUE attr) void Init_ossl_x509req(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509ReqError = rb_define_class_under(mX509, "RequestError", eOSSLError); cX509Req = rb_define_class_under(mX509, "Request", rb_cObject); diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c index 9496c4bf1b49fc..e6bd1d8e1b1c16 100644 --- a/ext/openssl/ossl_x509revoked.c +++ b/ext/openssl/ossl_x509revoked.c @@ -267,12 +267,6 @@ ossl_x509revoked_to_der(VALUE self) void Init_ossl_x509revoked(void) { -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - eX509RevError = rb_define_class_under(mX509, "RevokedError", eOSSLError); cX509Rev = rb_define_class_under(mX509, "Revoked", rb_cObject); diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index c18596cbf5be73..3c7da66a2efc62 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -859,12 +859,6 @@ void Init_ossl_x509store(void) { #undef rb_intern -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); - mX509 = rb_define_module_under(mOSSL, "X509"); -#endif - /* Register ext_data slot for verify callback Proc */ stctx_ex_verify_cb_idx = X509_STORE_CTX_get_ex_new_index(0, (void *)"stctx_ex_verify_cb_idx", 0, 0, 0); if (stctx_ex_verify_cb_idx < 0) From d9093eab625c7a7029888e538c5474430fb1c110 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 29 Nov 2025 13:40:05 -0500 Subject: [PATCH 1431/2435] [ruby/tempfile] [DOC] Fix monofont for Tempfile.create https://github.com/ruby/tempfile/commit/96361e9e42 --- lib/tempfile.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tempfile.rb b/lib/tempfile.rb index 27ebb96439c7f3..eeed2534a917f7 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -550,7 +550,7 @@ def open(*args, **kw) # # Implementation note: # -# The keyword argument +anonymous=true+ is implemented using FILE_SHARE_DELETE on Windows. +# The keyword argument anonymous=true is implemented using FILE_SHARE_DELETE on Windows. # O_TMPFILE is used on Linux. # # Related: Tempfile.new. From 8a0ae3a71a8b12a5e5929a565ba98fdf7c16233b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 29 Nov 2025 13:42:16 -0500 Subject: [PATCH 1432/2435] [ruby/tempfile] [DOC] Monofont some text in Tempfile.create https://github.com/ruby/tempfile/commit/7fa7436baa --- lib/tempfile.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tempfile.rb b/lib/tempfile.rb index eeed2534a917f7..cd512bb1c5224b 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -550,8 +550,8 @@ def open(*args, **kw) # # Implementation note: # -# The keyword argument anonymous=true is implemented using FILE_SHARE_DELETE on Windows. -# O_TMPFILE is used on Linux. +# The keyword argument anonymous=true is implemented using +FILE_SHARE_DELETE+ on Windows. +# +O_TMPFILE+ is used on Linux. # # Related: Tempfile.new. # From 48a73303e45b1dbaa3422e14e35c7834db98be4d Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:13:04 +0100 Subject: [PATCH 1433/2435] [ruby/prism] Optimize `Prism::Source#find_line` This is more concise and ruby does a better job performance-wise. This used to be `bsearch_index` already but https://github.com/ruby/prism/commit/6d8358c08395438d5924777c1fc3001a5ebf0aa3 changed it. https://github.com/ruby/prism/pull/1733#discussion_r1373702087 said: > Yeah the edge case was that the value matched an element exactly But surely there would be a test to show this behaviour? Gets called as part of pretty-printing nodes. Further reduces the time for `SnapshotsTest` by ~16% for me. https://github.com/ruby/prism/commit/f448e2b995 --- lib/prism/parse_result.rb | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 05c14e33f5c99e..3570af136a4fb7 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -155,21 +155,8 @@ def deep_freeze # Binary search through the offsets to find the line number for the given # byte offset. def find_line(byte_offset) - left = 0 - right = offsets.length - 1 - - while left <= right - mid = left + (right - left) / 2 - return mid if (offset = offsets[mid]) == byte_offset - - if offset < byte_offset - left = mid + 1 - else - right = mid - 1 - end - end - - left - 1 + index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length + index - 1 end end From f2a6cb2dc88ca12938b45f35bc6e64d72060a959 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 14:14:48 -0500 Subject: [PATCH 1434/2435] [ruby/prism] Handle invalid string pattern key When a pattern match is using a string as a hash pattern key and is using it incorrectly, we were previously assuming it was a symbol. In the case of an error, that's not the case. So we need to add a missing node in this case. https://github.com/ruby/prism/commit/f0b06d6269 --- prism/prism.c | 6 +++++- test/prism/errors/pattern_string_key.txt | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/prism/errors/pattern_string_key.txt diff --git a/prism/prism.c b/prism/prism.c index 186cdd354c9843..3485a58c28ee90 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -17336,7 +17336,11 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node pm_node_t *value = NULL; if (match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { - value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key); + if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) { + value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key); + } else { + value = (pm_node_t *) pm_missing_node_create(parser, key->location.end, key->location.end); + } } else { value = parse_pattern(parser, captures, PM_PARSE_PATTERN_SINGLE, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY, (uint16_t) (depth + 1)); } diff --git a/test/prism/errors/pattern_string_key.txt b/test/prism/errors/pattern_string_key.txt new file mode 100644 index 00000000000000..9f28feddb96dea --- /dev/null +++ b/test/prism/errors/pattern_string_key.txt @@ -0,0 +1,8 @@ +case:a +in b:"","#{}" + ^~~~~ expected a label after the `,` in the hash pattern + ^ expected a pattern expression after the key + ^ expected a delimiter after the patterns of an `in` clause + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `case` statement + From f0f2a45f2d46fa4a0323ba5f29fba895b9e78f4b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 14:20:06 -0500 Subject: [PATCH 1435/2435] [ruby/prism] Fix out-of-bounds read after utf-8 BOM https://github.com/ruby/prism/commit/198080c106 Co-authored-by: Steven Johnstone --- prism/prism.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 3485a58c28ee90..7d7072be79d76d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22865,8 +22865,8 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm // If the shebang does not include "ruby" and this is the main script being // parsed, then we will start searching the file for a shebang that does // contain "ruby" as if -x were passed on the command line. - const uint8_t *newline = next_newline(parser->start, parser->end - parser->start); - size_t length = (size_t) ((newline != NULL ? newline : parser->end) - parser->start); + const uint8_t *newline = next_newline(parser->current.end, parser->end - parser->current.end); + size_t length = (size_t) ((newline != NULL ? newline : parser->end) - parser->current.end); if (length > 2 && parser->current.end[0] == '#' && parser->current.end[1] == '!') { const char *engine; From 7c2eb946e04940901ee1957c3fb43897c661568e Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 14:28:36 -0500 Subject: [PATCH 1436/2435] [ruby/prism] Fix label interpolated string https://github.com/ruby/prism/commit/e3e2b1ed04 --- prism/config.yml | 3 +++ prism/prism.c | 6 ++++-- test/prism/errors/label_in_interpolated_string.txt | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 test/prism/errors/label_in_interpolated_string.txt diff --git a/prism/config.yml b/prism/config.yml index 3acab9f58d5d44..36becff28de508 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3306,6 +3306,9 @@ nodes: - EmbeddedVariableNode - InterpolatedStringNode # `"a" "#{b}"` - on error: XStringNode # `<<`FOO` "bar" + - on error: InterpolatedXStringNode + - on error: SymbolNode + - on error: InterpolatedSymbolNode - name: closing_loc type: location? newline: parts diff --git a/prism/prism.c b/prism/prism.c index 7d7072be79d76d..6d10fcb1e46522 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5344,8 +5344,10 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ break; case PM_X_STRING_NODE: case PM_INTERPOLATED_X_STRING_NODE: - // If this is an x string, then this is a syntax error. But we want - // to handle it here so that we don't fail the assertion. + case PM_SYMBOL_NODE: + case PM_INTERPOLATED_SYMBOL_NODE: + // These will only happen in error cases. But we want to handle it + // here so that we don't fail the assertion. CLEAR_FLAGS(node); break; default: diff --git a/test/prism/errors/label_in_interpolated_string.txt b/test/prism/errors/label_in_interpolated_string.txt new file mode 100644 index 00000000000000..e8f40dd2a8aa69 --- /dev/null +++ b/test/prism/errors/label_in_interpolated_string.txt @@ -0,0 +1,14 @@ +case in el""Q +^~~~ expected a predicate for a case matching statement + ^ expected a delimiter after the patterns of an `in` clause + ^ unexpected constant, expecting end-of-input + !"""#{in el"":Q + ^~ unexpected 'in', assuming it is closing the parent 'in' clause + ^ expected a `}` to close the embedded expression + ^~ cannot parse the string part + ^~ cannot parse the string part + ^ cannot parse the string part + ^~~~~~~~~~~ unexpected label + ^~~~~~~~~~~ expected a string for concatenation + ^ expected an `end` to close the `case` statement + From 748dc9ac8d68b7979936f171179c475c7f174953 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 14:31:34 -0500 Subject: [PATCH 1437/2435] [ruby/prism] Fix out-of-bounds read in parser_lex_magic_comment https://github.com/ruby/prism/commit/e24e701f3a Co-authored-by: Steven Johnstone --- prism/prism.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 6d10fcb1e46522..3d60e008c48a0a 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8445,7 +8445,7 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) { if (*cursor == '\\' && (cursor + 1 < end)) cursor++; } value_end = cursor; - if (*cursor == '"') cursor++; + if (cursor < end && *cursor == '"') cursor++; } else { value_start = cursor; while (cursor < end && *cursor != '"' && *cursor != ';' && !pm_char_is_whitespace(*cursor)) cursor++; From e3bc1852b3a7bcacf3e18d1644fbea7839abe019 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 15:51:06 -0500 Subject: [PATCH 1438/2435] [ruby/prism] Revert "Fix invalid Ruby code example in ClassNode comment" https://github.com/ruby/prism/commit/b960079559 --- prism/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 36becff28de508..5e29d6fa181c34 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1860,7 +1860,7 @@ nodes: comment: | Represents the location of the `class` keyword. - class Foo; end + class Foo end ^^^^^ - name: constant_path type: node @@ -1899,19 +1899,19 @@ nodes: comment: | Represents the location of the `end` keyword. - class Foo; end - ^^^ + class Foo end + ^^^ - name: name type: constant comment: | The name of the class. - class Foo; end # name `:Foo` + class Foo end # name `:Foo` comment: | Represents a class declaration involving the `class` keyword. - class Foo; end - ^^^^^^^^^^^^^^ + class Foo end + ^^^^^^^^^^^^^ - name: ClassVariableAndWriteNode fields: - name: name From b1314f159ec501f4fdc1c055293799b33c9ad134 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 15:36:53 -0500 Subject: [PATCH 1439/2435] [ruby/prism] Fully destroy call operator write arguments If we are about to delete a call operator write argument, it needs to be removed from the list of block exits as well. https://github.com/ruby/prism/commit/ebc91c2e39 --- prism/prism.c | 38 +++++++++++++++++++ .../destroy_call_operator_write_arguments.txt | 11 ++++++ 2 files changed, 49 insertions(+) create mode 100644 test/prism/errors/destroy_call_operator_write_arguments.txt diff --git a/prism/prism.c b/prism/prism.c index 3d60e008c48a0a..60d45cbcd23ae6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21062,6 +21062,42 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding return value; } +static bool +parse_call_operator_write_block_exits_each(const pm_node_t *node, void *data) { + pm_parser_t *parser = (pm_parser_t *) data; + size_t index = 0; + + while (index < parser->current_block_exits->size) { + pm_node_t *block_exit = parser->current_block_exits->nodes[index]; + + if (block_exit == node) { + if (index + 1 < parser->current_block_exits->size) { + memmove( + &parser->current_block_exits->nodes[index], + &parser->current_block_exits->nodes[index + 1], + (parser->current_block_exits->size - index - 1) * sizeof(pm_node_t *) + ); + } + parser->current_block_exits->size--; + return false; + } + + index++; + } + + return true; +} + +/** + * When we are about to destroy a set of nodes that could potentially contain + * block exits for the current scope, we need to check if they are contained in + * the list of block exits and remove them if they are. + */ +static void +parse_call_operator_write_block_exits(pm_parser_t *parser, const pm_node_t *node) { + pm_visit_node(node, parse_call_operator_write_block_exits_each, parser); +} + /** * Ensure a call node that is about to become a call operator node does not * have arguments or a block attached. If it does, then we'll need to add an @@ -21073,12 +21109,14 @@ static void parse_call_operator_write(pm_parser_t *parser, pm_call_node_t *call_node, const pm_token_t *operator) { if (call_node->arguments != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_ARGUMENTS); + parse_call_operator_write_block_exits(parser, (pm_node_t *) call_node->arguments); pm_node_destroy(parser, (pm_node_t *) call_node->arguments); call_node->arguments = NULL; } if (call_node->block != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_BLOCK); + parse_call_operator_write_block_exits(parser, (pm_node_t *) call_node->block); pm_node_destroy(parser, (pm_node_t *) call_node->block); call_node->block = NULL; } diff --git a/test/prism/errors/destroy_call_operator_write_arguments.txt b/test/prism/errors/destroy_call_operator_write_arguments.txt new file mode 100644 index 00000000000000..c3c72f92260d02 --- /dev/null +++ b/test/prism/errors/destroy_call_operator_write_arguments.txt @@ -0,0 +1,11 @@ +t next&&do end&= + ^~ unexpected 'do'; expected an expression after the operator + ^~~~ unexpected void value expression + ^~~~ unexpected void value expression +^~~~~~~~~~~~~~ unexpected write target + ^~ unexpected operator after a call with arguments + ^~ unexpected operator after a call with a block +''while= + ^~~~~ expected a predicate expression for the `while` statement + ^ unexpected '='; target cannot be written + From c4b5fa419ee266c973be9b676caa2cfad0400d58 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 15:25:35 -0500 Subject: [PATCH 1440/2435] [ruby/prism] Ensure implicit parameter nodes are destroyed. When we are about to destroy a node because of a syntax error, we need to check if it is potentially containing an implicit parameter in its subtree. https://github.com/ruby/prism/commit/1531433e02 --- prism/prism.c | 65 +++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 60d45cbcd23ae6..8c886bb050b06a 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13454,28 +13454,43 @@ parse_unwriteable_target(pm_parser_t *parser, pm_node_t *target) { return (pm_node_t *) result; } +static bool +parse_target_implicit_parameter_each(const pm_node_t *node, void *data) { + switch (PM_NODE_TYPE(node)) { + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_IT_LOCAL_VARIABLE_READ_NODE: { + pm_parser_t *parser = (pm_parser_t *) data; + pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters; + + for (size_t index = 0; index < implicit_parameters->size; index++) { + if (implicit_parameters->nodes[index] == node) { + // If the node is not the last one in the list, we need to + // shift the remaining nodes down to fill the gap. This is + // extremely unlikely to happen. + if (index != implicit_parameters->size - 1) { + memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); + } + + implicit_parameters->size--; + break; + } + } + + return false; + } + default: + return true; + } +} + /** * When an implicit local variable is written to or targeted, it becomes a * regular, named local variable. This function removes it from the list of * implicit parameters when that happens. */ static void -parse_target_implicit_parameter(pm_parser_t *parser, pm_node_t *node) { - pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters; - - for (size_t index = 0; index < implicit_parameters->size; index++) { - if (implicit_parameters->nodes[index] == node) { - // If the node is not the last one in the list, we need to shift the - // remaining nodes down to fill the gap. This is extremely unlikely - // to happen. - if (index != implicit_parameters->size - 1) { - memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); - } - - implicit_parameters->size--; - break; - } - } +parse_target_implicit_parameter(pm_parser_t *parser, const pm_node_t *node) { + pm_visit_node(node, parse_target_implicit_parameter_each, parser); } /** @@ -13851,18 +13866,12 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // syntax error. In this case we'll fall through to our default // handling. We need to free the value that we parsed because there // is no way for us to attach it to the tree at this point. - switch (PM_NODE_TYPE(value)) { - case PM_LOCAL_VARIABLE_READ_NODE: - case PM_IT_LOCAL_VARIABLE_READ_NODE: - // Since it is possible for the value to be an implicit - // parameter, we need to remove it from the list of implicit - // parameters. - parse_target_implicit_parameter(parser, value); - break; - default: - break; - } - + // + // Since it is possible for the value to contain an implicit + // parameter somewhere in its subtree, we need to walk it and remove + // any implicit parameters from the list of implicit parameters for + // the current scope. + parse_target_implicit_parameter(parser, value); pm_node_destroy(parser, value); } PRISM_FALLTHROUGH From 3bd9583a52e9c3f55adb2097a84c4adb09cf74a3 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 29 Nov 2025 15:58:10 -0500 Subject: [PATCH 1441/2435] [ruby/prism] Update unicode tables to match that of CRuby The unicode version has been updated upstream, which means new codepoints mapped to alpha/alnum/isupper flags. We need to update our tables to match. I'm purposefully not adding a version check here, since that is such a large amount of code. It's possible that we could include different tables depending on a macro (like UNICODE_VERSION) or something to that effect, but it's such a minimal impact on the running of the actual parser that I don't think it's necessary. https://github.com/ruby/prism/commit/78925fe5b6 --- prism/encoding.c | 155 +++++++++++++++++++------- test/prism/encoding/encodings_test.rb | 18 +-- 2 files changed, 118 insertions(+), 55 deletions(-) diff --git a/prism/encoding.c b/prism/encoding.c index a4aeed104f89b9..424f149b8f833f 100644 --- a/prism/encoding.c +++ b/prism/encoding.c @@ -2,7 +2,7 @@ typedef uint32_t pm_unicode_codepoint_t; -#define UNICODE_ALPHA_CODEPOINTS_LENGTH 1450 +#define UNICODE_ALPHA_CODEPOINTS_LENGTH 1508 static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEPOINTS_LENGTH] = { 0x100, 0x2C1, 0x2C6, 0x2D1, @@ -10,7 +10,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x345, 0x345, - 0x370, 0x374, + 0x363, 0x374, 0x376, 0x377, 0x37A, 0x37D, 0x37F, 0x37F, @@ -50,7 +50,8 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, - 0x889, 0x88E, + 0x889, 0x88F, + 0x897, 0x897, 0x8A0, 0x8C9, 0x8D4, 0x8DF, 0x8E3, 0x8E9, @@ -140,7 +141,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0xC4A, 0xC4C, 0xC55, 0xC56, 0xC58, 0xC5A, - 0xC5D, 0xC5D, + 0xC5C, 0xC5D, 0xC60, 0xC63, 0xC80, 0xC83, 0xC85, 0xC8C, @@ -152,7 +153,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0xCC6, 0xCC8, 0xCCA, 0xCCC, 0xCD5, 0xCD6, - 0xCDD, 0xCDE, + 0xCDC, 0xCDE, 0xCE0, 0xCE3, 0xCF1, 0xCF3, 0xD00, 0xD0C, @@ -264,7 +265,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x1C00, 0x1C36, 0x1C4D, 0x1C4F, 0x1C5A, 0x1C7D, - 0x1C80, 0x1C88, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, @@ -272,7 +273,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, - 0x1DE7, 0x1DF4, + 0x1DD3, 0x1DF4, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, @@ -352,11 +353,8 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0xA67F, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, - 0xA78B, 0xA7CA, - 0xA7D0, 0xA7D1, - 0xA7D3, 0xA7D3, - 0xA7D5, 0xA7D9, - 0xA7F2, 0xA805, + 0xA78B, 0xA7DC, + 0xA7F1, 0xA805, 0xA807, 0xA827, 0xA840, 0xA873, 0xA880, 0xA8C3, @@ -446,6 +444,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, @@ -464,6 +463,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x108F4, 0x108F5, 0x10900, 0x10915, 0x10920, 0x10939, + 0x10940, 0x10959, 0x10980, 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A03, @@ -483,9 +483,14 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10D00, 0x10D27, + 0x10D4A, 0x10D65, + 0x10D69, 0x10D69, + 0x10D6F, 0x10D85, 0x10E80, 0x10EA9, 0x10EAB, 0x10EAC, 0x10EB0, 0x10EB1, + 0x10EC2, 0x10EC7, + 0x10EFA, 0x10EFC, 0x10F00, 0x10F1C, 0x10F27, 0x10F27, 0x10F30, 0x10F45, @@ -529,6 +534,17 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x11350, 0x11350, 0x11357, 0x11357, 0x1135D, 0x11363, + 0x11380, 0x11389, + 0x1138B, 0x1138B, + 0x1138E, 0x1138E, + 0x11390, 0x113B5, + 0x113B7, 0x113C0, + 0x113C2, 0x113C2, + 0x113C5, 0x113C5, + 0x113C7, 0x113CA, + 0x113CC, 0x113CD, + 0x113D1, 0x113D1, + 0x113D3, 0x113D3, 0x11400, 0x11441, 0x11443, 0x11445, 0x11447, 0x1144A, @@ -567,6 +583,8 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x11A50, 0x11A97, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, + 0x11B60, 0x11B67, + 0x11BC0, 0x11BE0, 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C3E, @@ -588,6 +606,7 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x11D90, 0x11D91, 0x11D93, 0x11D96, 0x11D98, 0x11D98, + 0x11DB0, 0x11DDB, 0x11EE0, 0x11EF6, 0x11F00, 0x11F10, 0x11F12, 0x11F3A, @@ -599,7 +618,9 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, + 0x13460, 0x143FA, 0x14400, 0x14646, + 0x16100, 0x1612E, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A70, 0x16ABE, @@ -608,16 +629,19 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x16B40, 0x16B43, 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, + 0x16D40, 0x16D6C, 0x16E40, 0x16E7F, + 0x16EA0, 0x16EB8, + 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, 0x16F4F, 0x16F87, 0x16F8F, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, - 0x16FF0, 0x16FF1, - 0x17000, 0x187F7, - 0x18800, 0x18CD5, - 0x18D00, 0x18D08, + 0x16FF0, 0x16FF6, + 0x17000, 0x18CD5, + 0x18CFF, 0x18D1E, + 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, @@ -677,6 +701,11 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E4D0, 0x1E4EB, + 0x1E5D0, 0x1E5ED, + 0x1E5F0, 0x1E5F0, + 0x1E6C0, 0x1E6DE, + 0x1E6E0, 0x1E6F5, + 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, @@ -722,16 +751,16 @@ static const pm_unicode_codepoint_t unicode_alpha_codepoints[UNICODE_ALPHA_CODEP 0x1F150, 0x1F169, 0x1F170, 0x1F189, 0x20000, 0x2A6DF, - 0x2A700, 0x2B739, - 0x2B740, 0x2B81D, - 0x2B820, 0x2CEA1, + 0x2A700, 0x2B81D, + 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, + 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, - 0x31350, 0x323AF, + 0x31350, 0x33479, }; -#define UNICODE_ALNUM_CODEPOINTS_LENGTH 1528 +#define UNICODE_ALNUM_CODEPOINTS_LENGTH 1598 static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEPOINTS_LENGTH] = { 0x100, 0x2C1, 0x2C6, 0x2D1, @@ -739,7 +768,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x345, 0x345, - 0x370, 0x374, + 0x363, 0x374, 0x376, 0x377, 0x37A, 0x37D, 0x37F, 0x37F, @@ -778,7 +807,8 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, - 0x889, 0x88E, + 0x889, 0x88F, + 0x897, 0x897, 0x8A0, 0x8C9, 0x8D4, 0x8DF, 0x8E3, 0x8E9, @@ -872,7 +902,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0xC4A, 0xC4C, 0xC55, 0xC56, 0xC58, 0xC5A, - 0xC5D, 0xC5D, + 0xC5C, 0xC5D, 0xC60, 0xC63, 0xC66, 0xC6F, 0xC80, 0xC83, @@ -885,7 +915,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0xCC6, 0xCC8, 0xCCA, 0xCCC, 0xCD5, 0xCD6, - 0xCDD, 0xCDE, + 0xCDC, 0xCDE, 0xCE0, 0xCE3, 0xCE6, 0xCEF, 0xCF1, 0xCF3, @@ -1007,7 +1037,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x1C00, 0x1C36, 0x1C40, 0x1C49, 0x1C4D, 0x1C7D, - 0x1C80, 0x1C88, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, @@ -1015,7 +1045,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, - 0x1DE7, 0x1DF4, + 0x1DD3, 0x1DF4, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, @@ -1094,11 +1124,8 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0xA67F, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, - 0xA78B, 0xA7CA, - 0xA7D0, 0xA7D1, - 0xA7D3, 0xA7D3, - 0xA7D5, 0xA7D9, - 0xA7F2, 0xA805, + 0xA78B, 0xA7DC, + 0xA7F1, 0xA805, 0xA807, 0xA827, 0xA840, 0xA873, 0xA880, 0xA8C3, @@ -1191,6 +1218,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, @@ -1209,6 +1237,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x108F4, 0x108F5, 0x10900, 0x10915, 0x10920, 0x10939, + 0x10940, 0x10959, 0x10980, 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A03, @@ -1229,9 +1258,14 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x10CC0, 0x10CF2, 0x10D00, 0x10D27, 0x10D30, 0x10D39, + 0x10D40, 0x10D65, + 0x10D69, 0x10D69, + 0x10D6F, 0x10D85, 0x10E80, 0x10EA9, 0x10EAB, 0x10EAC, 0x10EB0, 0x10EB1, + 0x10EC2, 0x10EC7, + 0x10EFA, 0x10EFC, 0x10F00, 0x10F1C, 0x10F27, 0x10F27, 0x10F30, 0x10F45, @@ -1278,6 +1312,17 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x11350, 0x11350, 0x11357, 0x11357, 0x1135D, 0x11363, + 0x11380, 0x11389, + 0x1138B, 0x1138B, + 0x1138E, 0x1138E, + 0x11390, 0x113B5, + 0x113B7, 0x113C0, + 0x113C2, 0x113C2, + 0x113C5, 0x113C5, + 0x113C7, 0x113CA, + 0x113CC, 0x113CD, + 0x113D1, 0x113D1, + 0x113D3, 0x113D3, 0x11400, 0x11441, 0x11443, 0x11445, 0x11447, 0x1144A, @@ -1297,6 +1342,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x11680, 0x116B5, 0x116B8, 0x116B8, 0x116C0, 0x116C9, + 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x1171D, 0x1172A, 0x11730, 0x11739, @@ -1322,6 +1368,9 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x11A50, 0x11A97, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, + 0x11B60, 0x11B67, + 0x11BC0, 0x11BE0, + 0x11BF0, 0x11BF9, 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C3E, @@ -1346,6 +1395,8 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x11D93, 0x11D96, 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, + 0x11DB0, 0x11DDB, + 0x11DE0, 0x11DE9, 0x11EE0, 0x11EF6, 0x11F00, 0x11F10, 0x11F12, 0x11F3A, @@ -1358,7 +1409,10 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, + 0x13460, 0x143FA, 0x14400, 0x14646, + 0x16100, 0x1612E, + 0x16130, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, @@ -1370,16 +1424,20 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x16B50, 0x16B59, 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, + 0x16D40, 0x16D6C, + 0x16D70, 0x16D79, 0x16E40, 0x16E7F, + 0x16EA0, 0x16EB8, + 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, 0x16F4F, 0x16F87, 0x16F8F, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, - 0x16FF0, 0x16FF1, - 0x17000, 0x187F7, - 0x18800, 0x18CD5, - 0x18D00, 0x18D08, + 0x16FF0, 0x16FF6, + 0x17000, 0x18CD5, + 0x18CFF, 0x18D1E, + 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, @@ -1394,6 +1452,7 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, 0x1BC9E, 0x1BC9E, + 0x1CCF0, 0x1CCF9, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, @@ -1443,6 +1502,11 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x1E2F0, 0x1E2F9, 0x1E4D0, 0x1E4EB, 0x1E4F0, 0x1E4F9, + 0x1E5D0, 0x1E5ED, + 0x1E5F0, 0x1E5FA, + 0x1E6C0, 0x1E6DE, + 0x1E6E0, 0x1E6F5, + 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, @@ -1490,16 +1554,16 @@ static const pm_unicode_codepoint_t unicode_alnum_codepoints[UNICODE_ALNUM_CODEP 0x1F170, 0x1F189, 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, - 0x2A700, 0x2B739, - 0x2B740, 0x2B81D, - 0x2B820, 0x2CEA1, + 0x2A700, 0x2B81D, + 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, + 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, - 0x31350, 0x323AF, + 0x31350, 0x33479, }; -#define UNICODE_ISUPPER_CODEPOINTS_LENGTH 1302 +#define UNICODE_ISUPPER_CODEPOINTS_LENGTH 1320 static const pm_unicode_codepoint_t unicode_isupper_codepoints[UNICODE_ISUPPER_CODEPOINTS_LENGTH] = { 0x100, 0x100, 0x102, 0x102, @@ -1774,6 +1838,7 @@ static const pm_unicode_codepoint_t unicode_isupper_codepoints[UNICODE_ISUPPER_C 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x13A0, 0x13F5, + 0x1C89, 0x1C89, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1E00, 0x1E00, @@ -2103,9 +2168,15 @@ static const pm_unicode_codepoint_t unicode_isupper_codepoints[UNICODE_ISUPPER_C 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, 0xA7C9, 0xA7C9, + 0xA7CB, 0xA7CC, + 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, + 0xA7D2, 0xA7D2, + 0xA7D4, 0xA7D4, 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, + 0xA7DA, 0xA7DA, + 0xA7DC, 0xA7DC, 0xA7F5, 0xA7F5, 0xFF21, 0xFF3A, 0x10400, 0x10427, @@ -2115,8 +2186,10 @@ static const pm_unicode_codepoint_t unicode_isupper_codepoints[UNICODE_ISUPPER_C 0x1058C, 0x10592, 0x10594, 0x10595, 0x10C80, 0x10CB2, + 0x10D50, 0x10D65, 0x118A0, 0x118BF, 0x16E40, 0x16E5F, + 0x16EA0, 0x16EB8, 0x1D400, 0x1D419, 0x1D434, 0x1D44D, 0x1D468, 0x1D481, diff --git a/test/prism/encoding/encodings_test.rb b/test/prism/encoding/encodings_test.rb index 4ad2b465cc1842..b008fc3fa10238 100644 --- a/test/prism/encoding/encodings_test.rb +++ b/test/prism/encoding/encodings_test.rb @@ -56,21 +56,11 @@ def assert_encoding_identifier(name, character) # Check that we can properly parse every codepoint in the given encoding. def assert_encoding(encoding, name, range) - # I'm not entirely sure, but I believe these codepoints are incorrect in - # their parsing in CRuby. They all report as matching `[[:lower:]]` but - # then they are parsed as constants. This is because CRuby determines if - # an identifier is a constant or not by case folding it down to lowercase - # and checking if there is a difference. And even though they report - # themselves as lowercase, their case fold is different. I have reported - # this bug upstream. + unicode = false + case encoding when Encoding::UTF_8, Encoding::UTF_8_MAC, Encoding::UTF8_DoCoMo, Encoding::UTF8_KDDI, Encoding::UTF8_SoftBank, Encoding::CESU_8 - range = range.to_a - [ - 0x01c5, 0x01c8, 0x01cb, 0x01f2, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, - 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, - 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, - 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fbc, 0x1fcc, 0x1ffc, - ] + unicode = true when Encoding::Windows_1253 range = range.to_a - [0xb5] end @@ -79,7 +69,7 @@ def assert_encoding(encoding, name, range) character = codepoint.chr(encoding) if character.match?(/[[:alpha:]]/) - if character.match?(/[[:upper:]]/) + if character.match?(/[[:upper:]]/) || (unicode && character.match?(Regexp.new("\\p{Lt}".encode(encoding)))) assert_encoding_constant(name, character) else assert_encoding_identifier(name, character) From 806e554cc027b71cf0970fd4eb8f9ee5f1ccf128 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Nov 2025 14:14:03 +0900 Subject: [PATCH 1442/2435] Compare with the upper bound of the loop variable Fix sign-compare warning --- gc/default/default.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 530fc4df891acb..2d862b0b212489 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -9448,7 +9448,7 @@ rb_gc_impl_objspace_init(void *objspace_ptr) (bits_t)0x0101010101010101ULL, // i=3: every 8th bit (bits_t)0x0001000100010001ULL, // i=4: every 16th bit }; - GC_ASSERT(i < sizeof(slot_bits_masks) / sizeof(slot_bits_masks[0])); + GC_ASSERT(HEAP_COUNT == sizeof(slot_bits_masks) / sizeof(slot_bits_masks[0])); heap->slot_bits_mask = slot_bits_masks[i]; ccan_list_head_init(&heap->pages); From d7cfd275f881bbd29d95dfdba585f055dd89ba44 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Nov 2025 14:31:34 +0900 Subject: [PATCH 1443/2435] Set DESTDIR if relative loading When relative loading, `prefix` makes no sense actually. Use the given (or default) path as `DESTDIR` instead. This change affects only when the relative loading is enabled and the destdir is not given, and does not change the final installation path, but makes the configuration options simpler a little. --- configure.ac | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configure.ac b/configure.ac index 22baeabb32ab46..21f2e5a466e382 100644 --- a/configure.ac +++ b/configure.ac @@ -4753,6 +4753,11 @@ AC_ARG_WITH(destdir, [DESTDIR="$withval"]) AC_SUBST(DESTDIR) +AS_IF([test "x$load_relative:$DESTDIR" = xyes:], [ + AS_IF([test "x$prefix" = xNONE], [DESTDIR="$ac_default_prefix"], [DESTDIR="$prefix"]) + prefix=/. +]) + AC_OUTPUT } From 9aa9cf8ea02f9860d735103b0e3ccfde62c68f95 Mon Sep 17 00:00:00 2001 From: "Daisuke Fujimura (fd0)" Date: Sun, 30 Nov 2025 12:01:44 +0900 Subject: [PATCH 1444/2435] Fix switch fall-through in copy_ext_file_error --- box.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/box.c b/box.c index 42ee967c509c84..81f285b3212ccd 100644 --- a/box.c +++ b/box.c @@ -514,16 +514,22 @@ copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *s switch (copy_retvalue) { case 1: snprintf(message, size, "can't open the extension path: %s", src_path); + break; case 2: snprintf(message, size, "can't open the file to write: %s", dst_path); + break; case 3: snprintf(message, size, "failed to read the extension path: %s", src_path); + break; case 4: snprintf(message, size, "failed to write the extension path: %s", dst_path); + break; case 5: snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path); + break; case 6: snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path); + break; default: rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue); } From a0cd81e005d185be37b3a0323e4ee61b7b4360a6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Nov 2025 21:07:33 +0900 Subject: [PATCH 1445/2435] Remove stale check Any objects with `call` method can be accepted as trap handlers, and the check for `Proc` type is useless since ruby/ruby@29f5911cf545. --- signal.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/signal.c b/signal.c index 1e2b0f613248c2..5971e12ee9b00f 100644 --- a/signal.c +++ b/signal.c @@ -1252,11 +1252,6 @@ trap_handler(VALUE *cmd, int sig) break; } } - else { - rb_proc_t *proc; - GetProcPtr(*cmd, proc); - (void)proc; - } } return func; From f92ff9a86b4de5cd6e368be62077741aa40abc64 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 1 Dec 2025 07:15:36 +0900 Subject: [PATCH 1446/2435] Remove an excess semicolon in a macro --- array.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/array.c b/array.c index 2ea6b55a7f69a3..2acaafee03d833 100644 --- a/array.c +++ b/array.c @@ -109,11 +109,11 @@ should_be_T_ARRAY(VALUE ary) #define FL_UNSET_SHARED(ary) FL_UNSET((ary), RARRAY_SHARED_FLAG) #define ARY_SET_PTR_FORCE(ary, p) \ - RARRAY(ary)->as.heap.ptr = (p); + (RARRAY(ary)->as.heap.ptr = (p)) #define ARY_SET_PTR(ary, p) do { \ RUBY_ASSERT(!ARY_EMBED_P(ary)); \ RUBY_ASSERT(!OBJ_FROZEN(ary)); \ - ARY_SET_PTR_FORCE(ary, p); \ + ARY_SET_PTR_FORCE(ary, p); \ } while (0) #define ARY_SET_EMBED_LEN(ary, n) do { \ long tmp_n = (n); \ From bcdad7f2867c5955ce06cd6fabd3b3c06361eb47 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 30 Nov 2025 20:25:55 -0500 Subject: [PATCH 1447/2435] [ruby/prism] Properly remove references https://github.com/ruby/prism/commit/17b246fd6a --- prism/prism.c | 183 +++++++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 8c886bb050b06a..58a6a9987db237 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13404,6 +13404,78 @@ parse_starred_expression(pm_parser_t *parser, pm_binding_power_t binding_power, return parse_value_expression(parser, binding_power, accepts_command_call, false, diag_id, depth); } +static bool +pm_node_unreference_each(const pm_node_t *node, void *data) { + switch (PM_NODE_TYPE(node)) { + /* When we are about to destroy a set of nodes that could potentially + * contain block exits for the current scope, we need to check if they + * are contained in the list of block exits and remove them if they are. + */ + case PM_BREAK_NODE: + case PM_NEXT_NODE: + case PM_REDO_NODE: { + pm_parser_t *parser = (pm_parser_t *) data; + size_t index = 0; + + while (index < parser->current_block_exits->size) { + pm_node_t *block_exit = parser->current_block_exits->nodes[index]; + + if (block_exit == node) { + if (index + 1 < parser->current_block_exits->size) { + memmove( + &parser->current_block_exits->nodes[index], + &parser->current_block_exits->nodes[index + 1], + (parser->current_block_exits->size - index - 1) * sizeof(pm_node_t *) + ); + } + parser->current_block_exits->size--; + return false; + } + + index++; + } + + return true; + } + /* When an implicit local variable is written to or targeted, it becomes + * a regular, named local variable. This branch removes it from the list + * of implicit parameters when that happens. */ + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_IT_LOCAL_VARIABLE_READ_NODE: { + pm_parser_t *parser = (pm_parser_t *) data; + pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters; + + for (size_t index = 0; index < implicit_parameters->size; index++) { + if (implicit_parameters->nodes[index] == node) { + /* If the node is not the last one in the list, we need to + * shift the remaining nodes down to fill the gap. This is + * extremely unlikely to happen. */ + if (index != implicit_parameters->size - 1) { + memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); + } + + implicit_parameters->size--; + break; + } + } + + return false; + } + default: + return true; + } +} + +/** + * When we are about to destroy a set of nodes that could potentially be + * referenced by one or more lists on the parser, then remove them from those + * lists so we don't get a use-after-free. + */ +static void +pm_node_unreference(pm_parser_t *parser, const pm_node_t *node) { + pm_visit_node(node, pm_node_unreference_each, parser); +} + /** * Convert the name of a method into the corresponding write method name. For * example, foo would be turned into foo=. @@ -13454,45 +13526,6 @@ parse_unwriteable_target(pm_parser_t *parser, pm_node_t *target) { return (pm_node_t *) result; } -static bool -parse_target_implicit_parameter_each(const pm_node_t *node, void *data) { - switch (PM_NODE_TYPE(node)) { - case PM_LOCAL_VARIABLE_READ_NODE: - case PM_IT_LOCAL_VARIABLE_READ_NODE: { - pm_parser_t *parser = (pm_parser_t *) data; - pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters; - - for (size_t index = 0; index < implicit_parameters->size; index++) { - if (implicit_parameters->nodes[index] == node) { - // If the node is not the last one in the list, we need to - // shift the remaining nodes down to fill the gap. This is - // extremely unlikely to happen. - if (index != implicit_parameters->size - 1) { - memmove(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *)); - } - - implicit_parameters->size--; - break; - } - } - - return false; - } - default: - return true; - } -} - -/** - * When an implicit local variable is written to or targeted, it becomes a - * regular, named local variable. This function removes it from the list of - * implicit parameters when that happens. - */ -static void -parse_target_implicit_parameter(pm_parser_t *parser, const pm_node_t *node) { - pm_visit_node(node, parse_target_implicit_parameter_each, parser); -} - /** * Convert the given node into a valid target node. * @@ -13550,7 +13583,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) { PM_PARSER_ERR_FORMAT(parser, target->location.start, target->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, target->location.start); - parse_target_implicit_parameter(parser, target); + pm_node_unreference(parser, target); } const pm_local_variable_read_node_t *cast = (const pm_local_variable_read_node_t *) target; @@ -13567,7 +13600,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); pm_node_t *node = (pm_node_t *) pm_local_variable_target_node_create(parser, &target->location, name, 0); - parse_target_implicit_parameter(parser, target); + pm_node_unreference(parser, target); pm_node_destroy(parser, target); return node; @@ -13742,7 +13775,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) { pm_diagnostic_id_t diag_id = (scope->parameters & PM_SCOPE_PARAMETERS_NUMBERED_FOUND) ? PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED : PM_ERR_PARAMETER_NUMBERED_RESERVED; PM_PARSER_ERR_FORMAT(parser, target->location.start, target->location.end, diag_id, target->location.start); - parse_target_implicit_parameter(parser, target); + pm_node_unreference(parser, target); } pm_locals_unread(&scope->locals, name); @@ -13754,7 +13787,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); pm_node_t *node = (pm_node_t *) pm_local_variable_write_node_create(parser, name, 0, value, &target->location, operator); - parse_target_implicit_parameter(parser, target); + pm_node_unreference(parser, target); pm_node_destroy(parser, target); return node; @@ -13861,9 +13894,9 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod return target; } - // If there are arguments on the call node, then it can't be a method - // call ending with = or a local variable write, so it must be a - // syntax error. In this case we'll fall through to our default + // If there are arguments on the call node, then it can't be a + // method call ending with = or a local variable write, so it must + // be a syntax error. In this case we'll fall through to our default // handling. We need to free the value that we parsed because there // is no way for us to attach it to the tree at this point. // @@ -13871,7 +13904,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // parameter somewhere in its subtree, we need to walk it and remove // any implicit parameters from the list of implicit parameters for // the current scope. - parse_target_implicit_parameter(parser, value); + pm_node_unreference(parser, value); pm_node_destroy(parser, value); } PRISM_FALLTHROUGH @@ -18772,7 +18805,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we're about to convert an 'it' implicit local // variable read into a method call, we need to remove // it from the list of implicit local variables. - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); } else { // Otherwise, we're about to convert a regular local // variable read into a method call, in which case we @@ -18781,7 +18814,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(PM_NODE_TYPE_P(node, PM_LOCAL_VARIABLE_READ_NODE)); if (pm_token_is_numbered_parameter(identifier.start, identifier.end)) { - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); } else { pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; pm_locals_unread(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name); @@ -21071,42 +21104,6 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding return value; } -static bool -parse_call_operator_write_block_exits_each(const pm_node_t *node, void *data) { - pm_parser_t *parser = (pm_parser_t *) data; - size_t index = 0; - - while (index < parser->current_block_exits->size) { - pm_node_t *block_exit = parser->current_block_exits->nodes[index]; - - if (block_exit == node) { - if (index + 1 < parser->current_block_exits->size) { - memmove( - &parser->current_block_exits->nodes[index], - &parser->current_block_exits->nodes[index + 1], - (parser->current_block_exits->size - index - 1) * sizeof(pm_node_t *) - ); - } - parser->current_block_exits->size--; - return false; - } - - index++; - } - - return true; -} - -/** - * When we are about to destroy a set of nodes that could potentially contain - * block exits for the current scope, we need to check if they are contained in - * the list of block exits and remove them if they are. - */ -static void -parse_call_operator_write_block_exits(pm_parser_t *parser, const pm_node_t *node) { - pm_visit_node(node, parse_call_operator_write_block_exits_each, parser); -} - /** * Ensure a call node that is about to become a call operator node does not * have arguments or a block attached. If it does, then we'll need to add an @@ -21118,14 +21115,14 @@ static void parse_call_operator_write(pm_parser_t *parser, pm_call_node_t *call_node, const pm_token_t *operator) { if (call_node->arguments != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_ARGUMENTS); - parse_call_operator_write_block_exits(parser, (pm_node_t *) call_node->arguments); + pm_node_unreference(parser, (pm_node_t *) call_node->arguments); pm_node_destroy(parser, (pm_node_t *) call_node->arguments); call_node->arguments = NULL; } if (call_node->block != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_BLOCK); - parse_call_operator_write_block_exits(parser, (pm_node_t *) call_node->block); + pm_node_unreference(parser, (pm_node_t *) call_node->block); pm_node_destroy(parser, (pm_node_t *) call_node->block); call_node->block = NULL; } @@ -21517,14 +21514,14 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, node, &token, value, name, 0); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); } pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; @@ -21651,14 +21648,14 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, node, &token, value, name, 0); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); } pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; @@ -21795,14 +21792,14 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, node, &token, value, name, 0); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) { PM_PARSER_ERR_FORMAT(parser, node->location.start, node->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, node->location.start); - parse_target_implicit_parameter(parser, node); + pm_node_unreference(parser, node); } pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node; From 4c56001d27c4b2431fee65e6e9426319c475c45d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 30 Nov 2025 20:45:44 -0500 Subject: [PATCH 1448/2435] [ruby/prism] Fix up newlines in newline-delimited-literals When you have a %-literal that is delimited by newlines, and you are also interpolating a heredoc into that literal, then both concepts will attempt to add the same newline to the newline list. https://github.com/ruby/prism/commit/c831abb888 --- prism/prism.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 58a6a9987db237..793346f855ea4d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12249,7 +12249,13 @@ parser_lex(pm_parser_t *parser) { size_t eol_length = match_eol_at(parser, breakpoint); if (eol_length) { parser->current.end = breakpoint + eol_length; - pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + + // Track the newline if we're not in a heredoc that + // would have already have added the newline to the + // list. + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + } } else { parser->current.end = breakpoint + 1; } @@ -12503,7 +12509,13 @@ parser_lex(pm_parser_t *parser) { size_t eol_length = match_eol_at(parser, breakpoint); if (eol_length) { parser->current.end = breakpoint + eol_length; - pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + + // Track the newline if we're not in a heredoc that + // would have already have added the newline to the + // list. + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + } } else { parser->current.end = breakpoint + 1; } From 8eea9a502031e866f210accc7d02347fc55f65c9 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 1 Dec 2025 17:19:42 +1300 Subject: [PATCH 1449/2435] Nullify scheduler during `terminate_atfork_i`. (#15354) --- thread.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/thread.c b/thread.c index f545a5377d8ed6..6e5c8e89ff075a 100644 --- a/thread.c +++ b/thread.c @@ -4979,6 +4979,9 @@ static void terminate_atfork_i(rb_thread_t *th, const rb_thread_t *current_th) { if (th != current_th) { + // Clear the scheduler as it is no longer operational: + th->scheduler = Qnil; + rb_native_mutex_initialize(&th->interrupt_lock); rb_mutex_abandon_keeping_mutexes(th); rb_mutex_abandon_locking_mutex(th); From bc9ea585bee075480b4f12f84c8ab99315766595 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 1 Dec 2025 17:49:31 +1300 Subject: [PATCH 1450/2435] Add `rb_ec_close` function to manage execution context cleanup. (#15253) --- cont.c | 1 + vm.c | 7 +++++++ vm_core.h | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/cont.c b/cont.c index c49256c977a5b3..50e8ffb3498ec4 100644 --- a/cont.c +++ b/cont.c @@ -2907,6 +2907,7 @@ void rb_fiber_close(rb_fiber_t *fiber) { fiber_status_set(fiber, FIBER_TERMINATED); + rb_ec_close(&fiber->cont.saved_ec); } static void diff --git a/vm.c b/vm.c index f9c615c3e23c30..1a8a6d059b569b 100644 --- a/vm.c +++ b/vm.c @@ -3890,6 +3890,13 @@ rb_ec_clear_vm_stack(rb_execution_context_t *ec) rb_ec_set_vm_stack(ec, NULL, 0); } +void +rb_ec_close(rb_execution_context_t *ec) +{ + // Fiber storage is not accessible from outside the running fiber, so it is safe to clear it here. + ec->storage = Qnil; +} + static void th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) { diff --git a/vm_core.h b/vm_core.h index 28d585deb2ab01..9d11457966ed71 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1104,6 +1104,10 @@ void rb_ec_initialize_vm_stack(rb_execution_context_t *ec, VALUE *stack, size_t // @param ec the execution context to update. void rb_ec_clear_vm_stack(rb_execution_context_t *ec); +// Close an execution context and free related resources that are no longer needed. +// @param ec the execution context to close. +void rb_ec_close(rb_execution_context_t *ec); + struct rb_ext_config { bool ractor_safe; }; From 8dc5822b007937f75eb0b156c9d9dcc7b16f9de8 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 1 Dec 2025 09:22:51 -0500 Subject: [PATCH 1451/2435] [ruby/prism] PM_NODE_INIT Hide the initialization of the base node inside the node initializer lists by a macro. As such, consistently enforce flags are set properly. https://github.com/ruby/prism/commit/c7b3d66d84 --- prism/prism.c | 1404 ++++++++----------------------------------------- 1 file changed, 214 insertions(+), 1190 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 793346f855ea4d..ac6fbd9f6b461a 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1928,8 +1928,13 @@ pm_node_alloc(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, size_t size) { return memory; } -#define PM_NODE_ALLOC(parser, type) (type *) pm_node_alloc(parser, sizeof(type)) -#define PM_NODE_IDENTIFY(parser) (++parser->node_id) +#define PM_NODE_ALLOC(parser_, type_) (type_ *) pm_node_alloc(parser_, sizeof(type_)) +#define PM_NODE_INIT(parser_, type_, flags_, start_, end_) (pm_node_t) { \ + .type = (type_), \ + .flags = (flags_), \ + .node_id = ++(parser_)->node_id, \ + .location = { .start = (start_), .end = (end_) } \ +} /** * Allocate a new MissingNode node. @@ -1938,11 +1943,9 @@ static pm_missing_node_t * pm_missing_node_create(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) { pm_missing_node_t *node = PM_NODE_ALLOC(parser, pm_missing_node_t); - *node = (pm_missing_node_t) {{ - .type = PM_MISSING_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = start, .end = end } - }}; + *node = (pm_missing_node_t) { + .base = PM_NODE_INIT(parser, PM_MISSING_NODE, 0, start, end) + }; return node; } @@ -1956,14 +1959,7 @@ pm_alias_global_variable_node_create(pm_parser_t *parser, const pm_token_t *keyw pm_alias_global_variable_node_t *node = PM_NODE_ALLOC(parser, pm_alias_global_variable_node_t); *node = (pm_alias_global_variable_node_t) { - { - .type = PM_ALIAS_GLOBAL_VARIABLE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = old_name->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_ALIAS_GLOBAL_VARIABLE_NODE, 0, keyword->start, old_name->location.end), .new_name = new_name, .old_name = old_name, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) @@ -1981,14 +1977,7 @@ pm_alias_method_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_n pm_alias_method_node_t *node = PM_NODE_ALLOC(parser, pm_alias_method_node_t); *node = (pm_alias_method_node_t) { - { - .type = PM_ALIAS_METHOD_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = old_name->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_ALIAS_METHOD_NODE, 0, keyword->start, old_name->location.end), .new_name = new_name, .old_name = old_name, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) @@ -2005,14 +1994,7 @@ pm_alternation_pattern_node_create(pm_parser_t *parser, pm_node_t *left, pm_node pm_alternation_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_alternation_pattern_node_t); *node = (pm_alternation_pattern_node_t) { - { - .type = PM_ALTERNATION_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = left->location.start, - .end = right->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_ALTERNATION_PATTERN_NODE, 0, left->location.start, right->location.end), .left = left, .right = right, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -2031,14 +2013,7 @@ pm_and_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *opera pm_and_node_t *node = PM_NODE_ALLOC(parser, pm_and_node_t); *node = (pm_and_node_t) { - { - .type = PM_AND_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = left->location.start, - .end = right->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_AND_NODE, 0, left->location.start, right->location.end), .left = left, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .right = right @@ -2055,11 +2030,7 @@ pm_arguments_node_create(pm_parser_t *parser) { pm_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_arguments_node_t); *node = (pm_arguments_node_t) { - { - .type = PM_ARGUMENTS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_ARGUMENTS_NODE, 0, parser->start, parser->start), .arguments = { 0 } }; @@ -2103,12 +2074,7 @@ pm_array_node_create(pm_parser_t *parser, const pm_token_t *opening) { pm_array_node_t *node = PM_NODE_ALLOC(parser, pm_array_node_t); *node = (pm_array_node_t) { - { - .type = PM_ARRAY_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(opening) - }, + .base = PM_NODE_INIT(parser, PM_ARRAY_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, opening->end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .elements = { 0 } @@ -2159,14 +2125,7 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - { - .type = PM_ARRAY_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = nodes->nodes[0]->location.start, - .end = nodes->nodes[nodes->size - 1]->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, nodes->nodes[0]->location.start, nodes->nodes[nodes->size - 1]->location.end), .constant = NULL, .rest = NULL, .requireds = { 0 }, @@ -2202,11 +2161,7 @@ pm_array_pattern_node_rest_create(pm_parser_t *parser, pm_node_t *rest) { pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - { - .type = PM_ARRAY_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = rest->location, - }, + .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, rest->location.start, rest->location.end), .constant = NULL, .rest = rest, .requireds = { 0 }, @@ -2227,14 +2182,7 @@ pm_array_pattern_node_constant_create(pm_parser_t *parser, pm_node_t *constant, pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - { - .type = PM_ARRAY_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = constant->location.start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, constant->location.start, closing->end), .constant = constant, .rest = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -2255,14 +2203,7 @@ pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *openin pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - { - .type = PM_ARRAY_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, opening->start, closing->end), .constant = NULL, .rest = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -2313,15 +2254,7 @@ pm_assoc_node_create(pm_parser_t *parser, pm_node_t *key, const pm_token_t *oper } *node = (pm_assoc_node_t) { - { - .type = PM_ASSOC_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = key->location.start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_ASSOC_NODE, flags, key->location.start, end), .key = key, .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), .value = value @@ -2339,14 +2272,7 @@ pm_assoc_splat_node_create(pm_parser_t *parser, pm_node_t *value, const pm_token pm_assoc_splat_node_t *node = PM_NODE_ALLOC(parser, pm_assoc_splat_node_t); *node = (pm_assoc_splat_node_t) { - { - .type = PM_ASSOC_SPLAT_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = value == NULL ? operator->end : value->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_ASSOC_SPLAT_NODE, 0, operator->start, value == NULL ? operator->end : value->location.end), .value = value, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -2363,11 +2289,7 @@ pm_back_reference_read_node_create(pm_parser_t *parser, const pm_token_t *name) pm_back_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_back_reference_read_node_t); *node = (pm_back_reference_read_node_t) { - { - .type = PM_BACK_REFERENCE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name), - }, + .base = PM_NODE_INIT(parser, PM_BACK_REFERENCE_READ_NODE, 0, name->start, name->end), .name = pm_parser_constant_id_token(parser, name) }; @@ -2382,14 +2304,7 @@ pm_begin_node_create(pm_parser_t *parser, const pm_token_t *begin_keyword, pm_st pm_begin_node_t *node = PM_NODE_ALLOC(parser, pm_begin_node_t); *node = (pm_begin_node_t) { - { - .type = PM_BEGIN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = begin_keyword->start, - .end = statements == NULL ? begin_keyword->end : statements->base.location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_BEGIN_NODE, 0, begin_keyword->start, statements == NULL ? begin_keyword->end : statements->base.location.end), .begin_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(begin_keyword), .statements = statements, .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE @@ -2448,14 +2363,7 @@ pm_block_argument_node_create(pm_parser_t *parser, const pm_token_t *operator, p pm_block_argument_node_t *node = PM_NODE_ALLOC(parser, pm_block_argument_node_t); *node = (pm_block_argument_node_t) { - { - .type = PM_BLOCK_ARGUMENT_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = expression == NULL ? operator->end : expression->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_BLOCK_ARGUMENT_NODE, 0, operator->start, expression == NULL ? operator->end : expression->location.end), .expression = expression, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -2471,11 +2379,7 @@ pm_block_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p pm_block_node_t *node = PM_NODE_ALLOC(parser, pm_block_node_t); *node = (pm_block_node_t) { - { - .type = PM_BLOCK_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = opening->start, .end = closing->end }, - }, + .base = PM_NODE_INIT(parser, PM_BLOCK_NODE, 0, opening->start, closing->end), .locals = *locals, .parameters = parameters, .body = body, @@ -2495,14 +2399,7 @@ pm_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, cons pm_block_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_block_parameter_node_t); *node = (pm_block_parameter_node_t) { - { - .type = PM_BLOCK_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end) - }, - }, + .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETER_NODE, 0, operator->start, name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -2537,14 +2434,7 @@ pm_block_parameters_node_create(pm_parser_t *parser, pm_parameters_node_t *param } *node = (pm_block_parameters_node_t) { - { - .type = PM_BLOCK_PARAMETERS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = start, - .end = end - } - }, + .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETERS_NODE, 0, start, end), .parameters = parameters, .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -2573,11 +2463,7 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) pm_block_local_variable_node_t *node = PM_NODE_ALLOC(parser, pm_block_local_variable_node_t); *node = (pm_block_local_variable_node_t) { - { - .type = PM_BLOCK_LOCAL_VARIABLE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name), - }, + .base = PM_NODE_INIT(parser, PM_BLOCK_LOCAL_VARIABLE_NODE, 0, name->start, name->end), .name = pm_parser_constant_id_token(parser, name) }; @@ -2604,14 +2490,7 @@ pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument pm_break_node_t *node = PM_NODE_ALLOC(parser, pm_break_node_t); *node = (pm_break_node_t) { - { - .type = PM_BREAK_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = (arguments == NULL ? keyword->end : arguments->base.location.end) - }, - }, + .base = PM_NODE_INIT(parser, PM_BREAK_NODE, 0, keyword->start, arguments == NULL ? keyword->end : arguments->base.location.end), .arguments = arguments, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) }; @@ -2638,12 +2517,7 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { pm_call_node_t *node = PM_NODE_ALLOC(parser, pm_call_node_t); *node = (pm_call_node_t) { - { - .type = PM_CALL_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser), - }, + .base = PM_NODE_INIT(parser, PM_CALL_NODE, flags, parser->start, parser->start), .receiver = NULL, .call_operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .message_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -2950,15 +2824,7 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_and_write_node_t); *node = (pm_call_and_write_node_t) { - { - .type = PM_CALL_AND_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CALL_AND_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -3013,15 +2879,7 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_and_write_node_t) { - { - .type = PM_INDEX_AND_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INDEX_AND_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -3049,15 +2907,7 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, pm_call_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_operator_write_node_t); *node = (pm_call_operator_write_node_t) { - { - .type = PM_CALL_OPERATOR_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CALL_OPERATOR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -3089,15 +2939,7 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_operator_write_node_t) { - { - .type = PM_INDEX_OPERATOR_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INDEX_OPERATOR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -3127,15 +2969,7 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_or_write_node_t); *node = (pm_call_or_write_node_t) { - { - .type = PM_CALL_OR_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CALL_OR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -3167,15 +3001,7 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_or_write_node_t) { - { - .type = PM_INDEX_OR_WRITE_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INDEX_OR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -3203,12 +3029,7 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { pm_call_target_node_t *node = PM_NODE_ALLOC(parser, pm_call_target_node_t); *node = (pm_call_target_node_t) { - { - .type = PM_CALL_TARGET_NODE, - .flags = target->base.flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = target->base.location - }, + .base = PM_NODE_INIT(parser, PM_CALL_TARGET_NODE, target->base.flags, target->base.location.start, target->base.location.end), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .name = target->name, @@ -3236,12 +3057,7 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_target_node_t) { - { - .type = PM_INDEX_TARGET_NODE, - .flags = flags | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = target->base.location - }, + .base = PM_NODE_INIT(parser, PM_INDEX_TARGET_NODE, flags | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, target->base.location.start, target->base.location.end), .receiver = target->receiver, .opening_loc = target->opening_loc, .arguments = target->arguments, @@ -3265,14 +3081,7 @@ pm_capture_pattern_node_create(pm_parser_t *parser, pm_node_t *value, pm_local_v pm_capture_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_capture_pattern_node_t); *node = (pm_capture_pattern_node_t) { - { - .type = PM_CAPTURE_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = value->location.start, - .end = target->base.location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_CAPTURE_PATTERN_NODE, 0, value->location.start, target->base.location.end), .value = value, .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -3289,14 +3098,7 @@ pm_case_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, pm_node pm_case_node_t *node = PM_NODE_ALLOC(parser, pm_case_node_t); *node = (pm_case_node_t) { - { - .type = PM_CASE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = case_keyword->start, - .end = end_keyword->end - }, - }, + .base = PM_NODE_INIT(parser, PM_CASE_NODE, 0, case_keyword->start, end_keyword->end), .predicate = predicate, .else_clause = NULL, .case_keyword_loc = PM_LOCATION_TOKEN_VALUE(case_keyword), @@ -3344,14 +3146,7 @@ pm_case_match_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, p pm_case_match_node_t *node = PM_NODE_ALLOC(parser, pm_case_match_node_t); *node = (pm_case_match_node_t) { - { - .type = PM_CASE_MATCH_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = case_keyword->start, - .end = end_keyword->end - }, - }, + .base = PM_NODE_INIT(parser, PM_CASE_MATCH_NODE, 0, case_keyword->start, end_keyword->end), .predicate = predicate, .else_clause = NULL, .case_keyword_loc = PM_LOCATION_TOKEN_VALUE(case_keyword), @@ -3399,11 +3194,7 @@ pm_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p pm_class_node_t *node = PM_NODE_ALLOC(parser, pm_class_node_t); *node = (pm_class_node_t) { - { - .type = PM_CLASS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = class_keyword->start, .end = end_keyword->end }, - }, + .base = PM_NODE_INIT(parser, PM_CLASS_NODE, 0, class_keyword->start, end_keyword->end), .locals = *locals, .class_keyword_loc = PM_LOCATION_TOKEN_VALUE(class_keyword), .constant_path = constant_path, @@ -3426,14 +3217,7 @@ pm_class_variable_and_write_node_create(pm_parser_t *parser, pm_class_variable_r pm_class_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_and_write_node_t); *node = (pm_class_variable_and_write_node_t) { - { - .type = PM_CLASS_VARIABLE_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3451,14 +3235,7 @@ pm_class_variable_operator_write_node_create(pm_parser_t *parser, pm_class_varia pm_class_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_operator_write_node_t); *node = (pm_class_variable_operator_write_node_t) { - { - .type = PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3478,14 +3255,7 @@ pm_class_variable_or_write_node_create(pm_parser_t *parser, pm_class_variable_re pm_class_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_or_write_node_t); *node = (pm_class_variable_or_write_node_t) { - { - .type = PM_CLASS_VARIABLE_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3504,11 +3274,7 @@ pm_class_variable_read_node_create(pm_parser_t *parser, const pm_token_t *token) pm_class_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_read_node_t); *node = (pm_class_variable_read_node_t) { - { - .type = PM_CLASS_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_READ_NODE, 0, token->start, token->end), .name = pm_parser_constant_id_token(parser, token) }; @@ -3535,17 +3301,10 @@ pm_implicit_array_write_flags(const pm_node_t *node, pm_node_flags_t flags) { static pm_class_variable_write_node_t * pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_node_t *read_node, pm_token_t *operator, pm_node_t *value) { pm_class_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_class_variable_write_node_t) { - { - .type = PM_CLASS_VARIABLE_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = read_node->base.location.start, - .end = value->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_WRITE_NODE, flags, read_node->base.location.start, value->location.end), .name = read_node->name, .name_loc = PM_LOCATION_NODE_VALUE((pm_node_t *) read_node), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3564,14 +3323,7 @@ pm_constant_path_and_write_node_create(pm_parser_t *parser, pm_constant_path_nod pm_constant_path_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_and_write_node_t); *node = (pm_constant_path_and_write_node_t) { - { - .type = PM_CONSTANT_PATH_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3588,14 +3340,7 @@ pm_constant_path_operator_write_node_create(pm_parser_t *parser, pm_constant_pat pm_constant_path_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_operator_write_node_t); *node = (pm_constant_path_operator_write_node_t) { - { - .type = PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), .target = target, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -3614,14 +3359,7 @@ pm_constant_path_or_write_node_create(pm_parser_t *parser, pm_constant_path_node pm_constant_path_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_or_write_node_t); *node = (pm_constant_path_or_write_node_t) { - { - .type = PM_CONSTANT_PATH_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3644,14 +3382,7 @@ pm_constant_path_node_create(pm_parser_t *parser, pm_node_t *parent, const pm_to } *node = (pm_constant_path_node_t) { - { - .type = PM_CONSTANT_PATH_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = parent == NULL ? delimiter->start : parent->location.start, - .end = name_token->end - }, - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_NODE, 0, parent == NULL ? delimiter->start : parent->location.start, name_token->end), .parent = parent, .name = name, .delimiter_loc = PM_LOCATION_TOKEN_VALUE(delimiter), @@ -3667,17 +3398,10 @@ pm_constant_path_node_create(pm_parser_t *parser, pm_node_t *parent, const pm_to static pm_constant_path_write_node_t * pm_constant_path_write_node_create(pm_parser_t *parser, pm_constant_path_node_t *target, const pm_token_t *operator, pm_node_t *value) { pm_constant_path_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_constant_path_write_node_t) { - { - .type = PM_CONSTANT_PATH_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_WRITE_NODE, flags, target->base.location.start, value->location.end), .target = target, .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3695,14 +3419,7 @@ pm_constant_and_write_node_create(pm_parser_t *parser, pm_constant_read_node_t * pm_constant_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_and_write_node_t); *node = (pm_constant_and_write_node_t) { - { - .type = PM_CONSTANT_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3720,14 +3437,7 @@ pm_constant_operator_write_node_create(pm_parser_t *parser, pm_constant_read_nod pm_constant_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_operator_write_node_t); *node = (pm_constant_operator_write_node_t) { - { - .type = PM_CONSTANT_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3747,14 +3457,7 @@ pm_constant_or_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *t pm_constant_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_or_write_node_t); *node = (pm_constant_or_write_node_t) { - { - .type = PM_CONSTANT_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3773,11 +3476,7 @@ pm_constant_read_node_create(pm_parser_t *parser, const pm_token_t *name) { pm_constant_read_node_t *node = PM_NODE_ALLOC(parser, pm_constant_read_node_t); *node = (pm_constant_read_node_t) { - { - .type = PM_CONSTANT_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name) - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_READ_NODE, 0, name->start, name->end), .name = pm_parser_constant_id_token(parser, name) }; @@ -3790,17 +3489,10 @@ pm_constant_read_node_create(pm_parser_t *parser, const pm_token_t *name) { static pm_constant_write_node_t * pm_constant_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { pm_constant_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_constant_write_node_t) { - { - .type = PM_CONSTANT_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_CONSTANT_WRITE_NODE, flags, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -3887,11 +3579,7 @@ pm_def_node_create( } *node = (pm_def_node_t) { - { - .type = PM_DEF_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = def_keyword->start, .end = end }, - }, + .base = PM_NODE_INIT(parser, PM_DEF_NODE, 0, def_keyword->start, end), .name = name, .name_loc = PM_LOCATION_TOKEN_VALUE(name_loc), .receiver = receiver, @@ -3915,16 +3603,10 @@ pm_def_node_create( static pm_defined_node_t * pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t *value, const pm_token_t *rparen, const pm_location_t *keyword_loc) { pm_defined_node_t *node = PM_NODE_ALLOC(parser, pm_defined_node_t); + const uint8_t *end = rparen->type == PM_TOKEN_NOT_PROVIDED ? value->location.end : rparen->end; *node = (pm_defined_node_t) { - { - .type = PM_DEFINED_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword_loc->start, - .end = (rparen->type == PM_TOKEN_NOT_PROVIDED ? value->location.end : rparen->end) - }, - }, + .base = PM_NODE_INIT(parser, PM_DEFINED_NODE, 0, keyword_loc->start, end), .lparen_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(lparen), .value = value, .rparen_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(rparen), @@ -3948,14 +3630,7 @@ pm_else_node_create(pm_parser_t *parser, const pm_token_t *else_keyword, pm_stat } *node = (pm_else_node_t) { - { - .type = PM_ELSE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = else_keyword->start, - .end = end, - }, - }, + .base = PM_NODE_INIT(parser, PM_ELSE_NODE, 0, else_keyword->start, end), .else_keyword_loc = PM_LOCATION_TOKEN_VALUE(else_keyword), .statements = statements, .end_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(end_keyword) @@ -3972,14 +3647,7 @@ pm_embedded_statements_node_create(pm_parser_t *parser, const pm_token_t *openin pm_embedded_statements_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_statements_node_t); *node = (pm_embedded_statements_node_t) { - { - .type = PM_EMBEDDED_STATEMENTS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - } - }, + .base = PM_NODE_INIT(parser, PM_EMBEDDED_STATEMENTS_NODE, 0, opening->start, closing->end), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .statements = statements, .closing_loc = PM_LOCATION_TOKEN_VALUE(closing) @@ -3996,14 +3664,7 @@ pm_embedded_variable_node_create(pm_parser_t *parser, const pm_token_t *operator pm_embedded_variable_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_variable_node_t); *node = (pm_embedded_variable_node_t) { - { - .type = PM_EMBEDDED_VARIABLE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = variable->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_EMBEDDED_VARIABLE_NODE, 0, operator->start, variable->location.end), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .variable = variable }; @@ -4019,14 +3680,7 @@ pm_ensure_node_create(pm_parser_t *parser, const pm_token_t *ensure_keyword, pm_ pm_ensure_node_t *node = PM_NODE_ALLOC(parser, pm_ensure_node_t); *node = (pm_ensure_node_t) { - { - .type = PM_ENSURE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = ensure_keyword->start, - .end = end_keyword->end - }, - }, + .base = PM_NODE_INIT(parser, PM_ENSURE_NODE, 0, ensure_keyword->start, end_keyword->end), .ensure_keyword_loc = PM_LOCATION_TOKEN_VALUE(ensure_keyword), .statements = statements, .end_keyword_loc = PM_LOCATION_TOKEN_VALUE(end_keyword) @@ -4043,12 +3697,9 @@ pm_false_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_FALSE); pm_false_node_t *node = PM_NODE_ALLOC(parser, pm_false_node_t); - *node = (pm_false_node_t) {{ - .type = PM_FALSE_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_false_node_t) { + .base = PM_NODE_INIT(parser, PM_FALSE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + }; return node; } @@ -4082,14 +3733,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { pm_node_t *right_splat_node = right; #endif *node = (pm_find_pattern_node_t) { - { - .type = PM_FIND_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = left->location.start, - .end = right->location.end, - }, - }, + .base = PM_NODE_INIT(parser, PM_FIND_PATTERN_NODE, 0, left->location.start, right->location.end), .constant = NULL, .left = left_splat_node, .right = right_splat_node, @@ -4190,12 +3834,7 @@ pm_float_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_float_node_t *node = PM_NODE_ALLOC(parser, pm_float_node_t); *node = (pm_float_node_t) { - { - .type = PM_FLOAT_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_FLOAT_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .value = pm_double_parse(parser, token) }; @@ -4211,12 +3850,7 @@ pm_float_node_imaginary_create(pm_parser_t *parser, const pm_token_t *token) { pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - { - .type = PM_IMAGINARY_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numeric = (pm_node_t *) pm_float_node_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT, .start = token->start, @@ -4236,12 +3870,7 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); *node = (pm_rational_node_t) { - { - .type = PM_RATIONAL_NODE, - .flags = PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numerator = { 0 }, .denominator = { 0 } }; @@ -4290,12 +3919,7 @@ pm_float_node_rational_imaginary_create(pm_parser_t *parser, const pm_token_t *t pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - { - .type = PM_IMAGINARY_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numeric = (pm_node_t *) pm_float_node_rational_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT_RATIONAL, .start = token->start, @@ -4323,14 +3947,7 @@ pm_for_node_create( pm_for_node_t *node = PM_NODE_ALLOC(parser, pm_for_node_t); *node = (pm_for_node_t) { - { - .type = PM_FOR_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = for_keyword->start, - .end = end_keyword->end - }, - }, + .base = PM_NODE_INIT(parser, PM_FOR_NODE, 0, for_keyword->start, end_keyword->end), .index = index, .collection = collection, .statements = statements, @@ -4351,11 +3968,9 @@ pm_forwarding_arguments_node_create(pm_parser_t *parser, const pm_token_t *token assert(token->type == PM_TOKEN_UDOT_DOT_DOT); pm_forwarding_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_arguments_node_t); - *node = (pm_forwarding_arguments_node_t) {{ - .type = PM_FORWARDING_ARGUMENTS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_forwarding_arguments_node_t) { + .base = PM_NODE_INIT(parser, PM_FORWARDING_ARGUMENTS_NODE, 0, token->start, token->end) + }; return node; } @@ -4368,11 +3983,9 @@ pm_forwarding_parameter_node_create(pm_parser_t *parser, const pm_token_t *token assert(token->type == PM_TOKEN_UDOT_DOT_DOT); pm_forwarding_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_parameter_node_t); - *node = (pm_forwarding_parameter_node_t) {{ - .type = PM_FORWARDING_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_forwarding_parameter_node_t) { + .base = PM_NODE_INIT(parser, PM_FORWARDING_PARAMETER_NODE, 0, token->start, token->end) + }; return node; } @@ -4391,15 +4004,9 @@ pm_forwarding_super_node_create(pm_parser_t *parser, const pm_token_t *token, pm block = (pm_block_node_t *) arguments->block; } + const uint8_t *end = block != NULL ? block->base.location.end : token->end; *node = (pm_forwarding_super_node_t) { - { - .type = PM_FORWARDING_SUPER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = token->start, - .end = block != NULL ? block->base.location.end : token->end - }, - }, + .base = PM_NODE_INIT(parser, PM_FORWARDING_SUPER_NODE, 0, token->start, end), .block = block }; @@ -4415,14 +4022,7 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening pm_hash_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_hash_pattern_node_t); *node = (pm_hash_pattern_node_t) { - { - .type = PM_HASH_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_HASH_PATTERN_NODE, 0, opening->start, closing->end), .constant = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), @@ -4458,14 +4058,7 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme } *node = (pm_hash_pattern_node_t) { - { - .type = PM_HASH_PATTERN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_HASH_PATTERN_NODE, 0, start, end), .constant = NULL, .elements = { 0 }, .rest = rest, @@ -4510,14 +4103,7 @@ pm_global_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_global_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_and_write_node_t); *node = (pm_global_variable_and_write_node_t) { - { - .type = PM_GLOBAL_VARIABLE_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_AND_WRITE_NODE, 0, target->location.start, value->location.end), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4535,14 +4121,7 @@ pm_global_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *ta pm_global_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_operator_write_node_t); *node = (pm_global_variable_operator_write_node_t) { - { - .type = PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target->location.start, value->location.end), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4562,14 +4141,7 @@ pm_global_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_global_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_or_write_node_t); *node = (pm_global_variable_or_write_node_t) { - { - .type = PM_GLOBAL_VARIABLE_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OR_WRITE_NODE, 0, target->location.start, value->location.end), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4587,11 +4159,7 @@ pm_global_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); *node = (pm_global_variable_read_node_t) { - { - .type = PM_GLOBAL_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name), - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, name->start, name->end), .name = pm_parser_constant_id_token(parser, name) }; @@ -4606,11 +4174,7 @@ pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); *node = (pm_global_variable_read_node_t) { - { - .type = PM_GLOBAL_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, parser->start, parser->start), .name = name }; @@ -4623,17 +4187,10 @@ pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant static pm_global_variable_write_node_t * pm_global_variable_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value) { pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_global_variable_write_node_t) { - { - .type = PM_GLOBAL_VARIABLE_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .location = { - .start = target->location.start, - .end = value->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, flags, target->location.start, value->location.end), .name = pm_global_variable_write_name(parser, target), .name_loc = PM_LOCATION_NODE_VALUE(target), .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -4651,11 +4208,7 @@ pm_global_variable_write_node_synthesized_create(pm_parser_t *parser, pm_constan pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); *node = (pm_global_variable_write_node_t) { - { - .type = PM_GLOBAL_VARIABLE_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0, parser->start, parser->start), .name = name, .name_loc = PM_LOCATION_NULL_VALUE(parser), .operator_loc = PM_LOCATION_NULL_VALUE(parser), @@ -4674,12 +4227,7 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) { pm_hash_node_t *node = PM_NODE_ALLOC(parser, pm_hash_node_t); *node = (pm_hash_node_t) { - { - .type = PM_HASH_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(opening) - }, + .base = PM_NODE_INIT(parser, PM_HASH_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, opening->end), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_NULL_VALUE(parser), .elements = { 0 } @@ -4741,15 +4289,7 @@ pm_if_node_create(pm_parser_t *parser, } *node = (pm_if_node_t) { - { - .type = PM_IF_NODE, - .flags = PM_NODE_FLAG_NEWLINE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = if_keyword->start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, if_keyword->start, end), .if_keyword_loc = PM_LOCATION_TOKEN_VALUE(if_keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(then_keyword), @@ -4773,15 +4313,7 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t pm_statements_node_body_append(parser, statements, statement, true); *node = (pm_if_node_t) { - { - .type = PM_IF_NODE, - .flags = PM_NODE_FLAG_NEWLINE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = statement->location.start, - .end = predicate->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, statement->location.start, predicate->location.end), .if_keyword_loc = PM_LOCATION_TOKEN_VALUE(if_keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -4813,15 +4345,7 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to pm_if_node_t *node = PM_NODE_ALLOC(parser, pm_if_node_t); *node = (pm_if_node_t) { - { - .type = PM_IF_NODE, - .flags = PM_NODE_FLAG_NEWLINE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = predicate->location.start, - .end = false_expression->location.end, - }, - }, + .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, predicate->location.start, false_expression->location.end), .if_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .predicate = predicate, .then_keyword_loc = PM_LOCATION_TOKEN_VALUE(qmark), @@ -4854,11 +4378,7 @@ pm_implicit_node_create(pm_parser_t *parser, pm_node_t *value) { pm_implicit_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_node_t); *node = (pm_implicit_node_t) { - { - .type = PM_IMPLICIT_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = value->location - }, + .base = PM_NODE_INIT(parser, PM_IMPLICIT_NODE, 0, value->location.start, value->location.end), .value = value }; @@ -4875,11 +4395,7 @@ pm_implicit_rest_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_implicit_rest_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_rest_node_t); *node = (pm_implicit_rest_node_t) { - { - .type = PM_IMPLICIT_REST_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - } + .base = PM_NODE_INIT(parser, PM_IMPLICIT_REST_NODE, 0, token->start, token->end) }; return node; @@ -4894,12 +4410,7 @@ pm_integer_node_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token pm_integer_node_t *node = PM_NODE_ALLOC(parser, pm_integer_node_t); *node = (pm_integer_node_t) { - { - .type = PM_INTEGER_NODE, - .flags = base | PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_INTEGER_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .value = { 0 } }; @@ -4926,12 +4437,7 @@ pm_integer_node_imaginary_create(pm_parser_t *parser, pm_node_flags_t base, cons pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - { - .type = PM_IMAGINARY_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numeric = (pm_node_t *) pm_integer_node_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER, .start = token->start, @@ -4952,12 +4458,7 @@ pm_integer_node_rational_create(pm_parser_t *parser, pm_node_flags_t base, const pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); *node = (pm_rational_node_t) { - { - .type = PM_RATIONAL_NODE, - .flags = base | PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numerator = { 0 }, .denominator = { .value = 1, 0 } }; @@ -4986,12 +4487,7 @@ pm_integer_node_rational_imaginary_create(pm_parser_t *parser, pm_node_flags_t b pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - { - .type = PM_IMAGINARY_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), .numeric = (pm_node_t *) pm_integer_node_rational_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER_RATIONAL, .start = token->start, @@ -5019,14 +4515,7 @@ pm_in_node_create(pm_parser_t *parser, pm_node_t *pattern, pm_statements_node_t } *node = (pm_in_node_t) { - { - .type = PM_IN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = in_keyword->start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_IN_NODE, 0, in_keyword->start, end), .pattern = pattern, .statements = statements, .in_loc = PM_LOCATION_TOKEN_VALUE(in_keyword), @@ -5045,14 +4534,7 @@ pm_instance_variable_and_write_node_create(pm_parser_t *parser, pm_instance_vari pm_instance_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_and_write_node_t); *node = (pm_instance_variable_and_write_node_t) { - { - .type = PM_INSTANCE_VARIABLE_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -5070,14 +4552,7 @@ pm_instance_variable_operator_write_node_create(pm_parser_t *parser, pm_instance pm_instance_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_operator_write_node_t); *node = (pm_instance_variable_operator_write_node_t) { - { - .type = PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -5097,14 +4572,7 @@ pm_instance_variable_or_write_node_create(pm_parser_t *parser, pm_instance_varia pm_instance_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_or_write_node_t); *node = (pm_instance_variable_or_write_node_t) { - { - .type = PM_INSTANCE_VARIABLE_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -5123,11 +4591,7 @@ pm_instance_variable_read_node_create(pm_parser_t *parser, const pm_token_t *tok pm_instance_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_read_node_t); *node = (pm_instance_variable_read_node_t) { - { - .type = PM_INSTANCE_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_READ_NODE, 0, token->start, token->end), .name = pm_parser_constant_id_token(parser, token) }; @@ -5141,16 +4605,10 @@ pm_instance_variable_read_node_create(pm_parser_t *parser, const pm_token_t *tok static pm_instance_variable_write_node_t * pm_instance_variable_write_node_create(pm_parser_t *parser, pm_instance_variable_read_node_t *read_node, pm_token_t *operator, pm_node_t *value) { pm_instance_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); + *node = (pm_instance_variable_write_node_t) { - { - .type = PM_INSTANCE_VARIABLE_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = read_node->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_WRITE_NODE, flags, read_node->base.location.start, value->location.end), .name = read_node->name, .name_loc = PM_LOCATION_NODE_BASE_VALUE(read_node), .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -5212,15 +4670,7 @@ pm_interpolated_regular_expression_node_create(pm_parser_t *parser, const pm_tok pm_interpolated_regular_expression_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_regular_expression_node_t); *node = (pm_interpolated_regular_expression_node_t) { - { - .type = PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = NULL, - }, - }, + .base = PM_NODE_INIT(parser, PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, NULL), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(opening), .parts = { 0 } @@ -5379,15 +4829,7 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin } *node = (pm_interpolated_string_node_t) { - { - .type = PM_INTERPOLATED_STRING_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end, - }, - }, + .base = PM_NODE_INIT(parser, PM_INTERPOLATED_STRING_NODE, flags, opening->start, closing->end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -5436,15 +4878,7 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin pm_interpolated_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_symbol_node_t); *node = (pm_interpolated_symbol_node_t) { - { - .type = PM_INTERPOLATED_SYMBOL_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end, - }, - }, + .base = PM_NODE_INIT(parser, PM_INTERPOLATED_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, closing->end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -5468,14 +4902,7 @@ pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *openi pm_interpolated_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_x_string_node_t); *node = (pm_interpolated_x_string_node_t) { - { - .type = PM_INTERPOLATED_X_STRING_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_INTERPOLATED_X_STRING_NODE, 0, opening->start, closing->end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -5504,11 +4931,7 @@ pm_it_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *nam pm_it_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_it_local_variable_read_node_t); *node = (pm_it_local_variable_read_node_t) { - { - .type = PM_IT_LOCAL_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name) - } + .base = PM_NODE_INIT(parser, PM_IT_LOCAL_VARIABLE_READ_NODE, 0, name->start, name->end), }; return node; @@ -5522,14 +4945,7 @@ pm_it_parameters_node_create(pm_parser_t *parser, const pm_token_t *opening, con pm_it_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_it_parameters_node_t); *node = (pm_it_parameters_node_t) { - { - .type = PM_IT_PARAMETERS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - } - } + .base = PM_NODE_INIT(parser, PM_IT_PARAMETERS_NODE, 0, opening->start, closing->end), }; return node; @@ -5543,12 +4959,7 @@ pm_keyword_hash_node_create(pm_parser_t *parser) { pm_keyword_hash_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_hash_node_t); *node = (pm_keyword_hash_node_t) { - .base = { - .type = PM_KEYWORD_HASH_NODE, - .flags = PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE - }, + .base = PM_NODE_INIT(parser, PM_KEYWORD_HASH_NODE, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS, NULL, NULL), .elements = { 0 } }; @@ -5581,14 +4992,7 @@ pm_required_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t pm_required_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_keyword_parameter_node_t); *node = (pm_required_keyword_parameter_node_t) { - { - .type = PM_REQUIRED_KEYWORD_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = name->start, - .end = name->end - }, - }, + .base = PM_NODE_INIT(parser, PM_REQUIRED_KEYWORD_PARAMETER_NODE, 0, name->start, name->end), .name = pm_parser_constant_id_location(parser, name->start, name->end - 1), .name_loc = PM_LOCATION_TOKEN_VALUE(name), }; @@ -5604,14 +5008,7 @@ pm_optional_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t pm_optional_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_keyword_parameter_node_t); *node = (pm_optional_keyword_parameter_node_t) { - { - .type = PM_OPTIONAL_KEYWORD_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = name->start, - .end = value->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_OPTIONAL_KEYWORD_PARAMETER_NODE, 0, name->start, value->location.end), .name = pm_parser_constant_id_location(parser, name->start, name->end - 1), .name_loc = PM_LOCATION_TOKEN_VALUE(name), .value = value @@ -5628,14 +5025,7 @@ pm_keyword_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *ope pm_keyword_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_rest_parameter_node_t); *node = (pm_keyword_rest_parameter_node_t) { - { - .type = PM_KEYWORD_REST_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end) - }, - }, + .base = PM_NODE_INIT(parser, PM_KEYWORD_REST_PARAMETER_NODE, 0, operator->start, (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end)), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5660,14 +5050,7 @@ pm_lambda_node_create( pm_lambda_node_t *node = PM_NODE_ALLOC(parser, pm_lambda_node_t); *node = (pm_lambda_node_t) { - { - .type = PM_LAMBDA_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_LAMBDA_NODE, 0, operator->start, closing->end), .locals = *locals, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -5689,14 +5072,7 @@ pm_local_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_local_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_and_write_node_t); *node = (pm_local_variable_and_write_node_t) { - { - .type = PM_LOCAL_VARIABLE_AND_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_AND_WRITE_NODE, 0, target->location.start, value->location.end), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5715,14 +5091,7 @@ pm_local_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *tar pm_local_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_operator_write_node_t); *node = (pm_local_variable_operator_write_node_t) { - { - .type = PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target->location.start, value->location.end), .name_loc = target->location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5744,14 +5113,7 @@ pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, c pm_local_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_or_write_node_t); *node = (pm_local_variable_or_write_node_t) { - { - .type = PM_LOCAL_VARIABLE_OR_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OR_WRITE_NODE, 0, target->location.start, value->location.end), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5772,11 +5134,7 @@ pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_tok pm_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_read_node_t); *node = (pm_local_variable_read_node_t) { - { - .type = PM_LOCAL_VARIABLE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name) - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_READ_NODE, 0, name->start, name->end), .name = name_id, .depth = depth }; @@ -5809,17 +5167,10 @@ pm_local_variable_read_node_missing_create(pm_parser_t *parser, const pm_token_t static pm_local_variable_write_node_t * pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name, uint32_t depth, pm_node_t *value, const pm_location_t *name_loc, const pm_token_t *operator) { pm_local_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_local_variable_write_node_t) { - { - .type = PM_LOCAL_VARIABLE_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = name_loc->start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_WRITE_NODE, flags, name_loc->start, value->location.end), .name = name, .depth = depth, .value = value, @@ -5868,11 +5219,7 @@ pm_local_variable_target_node_create(pm_parser_t *parser, const pm_location_t *l pm_local_variable_target_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_target_node_t); *node = (pm_local_variable_target_node_t) { - { - .type = PM_LOCAL_VARIABLE_TARGET_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = *location - }, + .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_TARGET_NODE, 0, location->start, location->end), .name = name, .depth = depth }; @@ -5890,14 +5237,7 @@ pm_match_predicate_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t pm_match_predicate_node_t *node = PM_NODE_ALLOC(parser, pm_match_predicate_node_t); *node = (pm_match_predicate_node_t) { - { - .type = PM_MATCH_PREDICATE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = value->location.start, - .end = pattern->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_MATCH_PREDICATE_NODE, 0, value->location.start, pattern->location.end), .value = value, .pattern = pattern, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5916,14 +5256,7 @@ pm_match_required_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t * pm_match_required_node_t *node = PM_NODE_ALLOC(parser, pm_match_required_node_t); *node = (pm_match_required_node_t) { - { - .type = PM_MATCH_REQUIRED_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = value->location.start, - .end = pattern->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_MATCH_REQUIRED_NODE, 0, value->location.start, pattern->location.end), .value = value, .pattern = pattern, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5940,11 +5273,7 @@ pm_match_write_node_create(pm_parser_t *parser, pm_call_node_t *call) { pm_match_write_node_t *node = PM_NODE_ALLOC(parser, pm_match_write_node_t); *node = (pm_match_write_node_t) { - { - .type = PM_MATCH_WRITE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = call->base.location - }, + .base = PM_NODE_INIT(parser, PM_MATCH_WRITE_NODE, 0, call->base.location.start, call->base.location.end), .call = call, .targets = { 0 } }; @@ -5960,14 +5289,7 @@ pm_module_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_module_node_t *node = PM_NODE_ALLOC(parser, pm_module_node_t); *node = (pm_module_node_t) { - { - .type = PM_MODULE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = module_keyword->start, - .end = end_keyword->end - } - }, + .base = PM_NODE_INIT(parser, PM_MODULE_NODE, 0, module_keyword->start, end_keyword->end), .locals = (locals == NULL ? ((pm_constant_id_list_t) { .ids = NULL, .size = 0, .capacity = 0 }) : *locals), .module_keyword_loc = PM_LOCATION_TOKEN_VALUE(module_keyword), .constant_path = constant_path, @@ -5987,11 +5309,7 @@ pm_multi_target_node_create(pm_parser_t *parser) { pm_multi_target_node_t *node = PM_NODE_ALLOC(parser, pm_multi_target_node_t); *node = (pm_multi_target_node_t) { - { - .type = PM_MULTI_TARGET_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = NULL, .end = NULL } - }, + .base = PM_NODE_INIT(parser, PM_MULTI_TARGET_NODE, 0, NULL, NULL), .lefts = { 0 }, .rest = NULL, .rights = { 0 }, @@ -6060,17 +5378,10 @@ pm_multi_target_node_closing_set(pm_multi_target_node_t *node, const pm_token_t static pm_multi_write_node_t * pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, const pm_token_t *operator, pm_node_t *value) { pm_multi_write_node_t *node = PM_NODE_ALLOC(parser, pm_multi_write_node_t); + pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_multi_write_node_t) { - { - .type = PM_MULTI_WRITE_NODE, - .flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = target->base.location.start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_MULTI_WRITE_NODE, flags, target->base.location.start, value->location.end), .lefts = target->lefts, .rest = target->rest, .rights = target->rights, @@ -6096,14 +5407,7 @@ pm_next_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments pm_next_node_t *node = PM_NODE_ALLOC(parser, pm_next_node_t); *node = (pm_next_node_t) { - { - .type = PM_NEXT_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = (arguments == NULL ? keyword->end : arguments->base.location.end) - } - }, + .base = PM_NODE_INIT(parser, PM_NEXT_NODE, 0, keyword->start, (arguments == NULL ? keyword->end : arguments->base.location.end)), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .arguments = arguments }; @@ -6119,12 +5423,9 @@ pm_nil_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_NIL); pm_nil_node_t *node = PM_NODE_ALLOC(parser, pm_nil_node_t); - *node = (pm_nil_node_t) {{ - .type = PM_NIL_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_nil_node_t) { + .base = PM_NODE_INIT(parser, PM_NIL_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + }; return node; } @@ -6139,14 +5440,7 @@ pm_no_keywords_parameter_node_create(pm_parser_t *parser, const pm_token_t *oper pm_no_keywords_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_no_keywords_parameter_node_t); *node = (pm_no_keywords_parameter_node_t) { - { - .type = PM_NO_KEYWORDS_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = keyword->end - } - }, + .base = PM_NODE_INIT(parser, PM_NO_KEYWORDS_PARAMETER_NODE, 0, operator->start, keyword->end), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) }; @@ -6162,11 +5456,7 @@ pm_numbered_parameters_node_create(pm_parser_t *parser, const pm_location_t *loc pm_numbered_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_parameters_node_t); *node = (pm_numbered_parameters_node_t) { - { - .type = PM_NUMBERED_PARAMETERS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = *location - }, + .base = PM_NODE_INIT(parser, PM_NUMBERED_PARAMETERS_NODE, 0, location->start, location->end), .maximum = maximum }; @@ -6231,11 +5521,7 @@ pm_numbered_reference_read_node_create(pm_parser_t *parser, const pm_token_t *na pm_numbered_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_reference_read_node_t); *node = (pm_numbered_reference_read_node_t) { - { - .type = PM_NUMBERED_REFERENCE_READ_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(name), - }, + .base = PM_NODE_INIT(parser, PM_NUMBERED_REFERENCE_READ_NODE, 0, name->start, name->end), .number = pm_numbered_reference_read_node_number(parser, name) }; @@ -6250,14 +5536,7 @@ pm_optional_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, c pm_optional_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_parameter_node_t); *node = (pm_optional_parameter_node_t) { - { - .type = PM_OPTIONAL_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = name->start, - .end = value->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_OPTIONAL_PARAMETER_NODE, 0, name->start, value->location.end), .name = pm_parser_constant_id_token(parser, name), .name_loc = PM_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -6277,14 +5556,7 @@ pm_or_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operat pm_or_node_t *node = PM_NODE_ALLOC(parser, pm_or_node_t); *node = (pm_or_node_t) { - { - .type = PM_OR_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = left->location.start, - .end = right->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_OR_NODE, 0, left->location.start, right->location.end), .left = left, .right = right, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -6301,11 +5573,7 @@ pm_parameters_node_create(pm_parser_t *parser) { pm_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_parameters_node_t); *node = (pm_parameters_node_t) { - { - .type = PM_PARAMETERS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(&parser->current) - }, + .base = PM_NODE_INIT(parser, PM_PARAMETERS_NODE, 0, NULL, NULL), .rest = NULL, .keyword_rest = NULL, .block = NULL, @@ -6408,15 +5676,11 @@ static pm_program_node_t * pm_program_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, pm_statements_node_t *statements) { pm_program_node_t *node = PM_NODE_ALLOC(parser, pm_program_node_t); + const uint8_t *start = statements == NULL ? parser->start : statements->base.location.start; + const uint8_t *end = statements == NULL ? parser->end : statements->base.location.end; + *node = (pm_program_node_t) { - { - .type = PM_PROGRAM_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = statements == NULL ? parser->start : statements->base.location.start, - .end = statements == NULL ? parser->end : statements->base.location.end - } - }, + .base = PM_NODE_INIT(parser, PM_PROGRAM_NODE, 0, start, end), .locals = *locals, .statements = statements }; @@ -6432,15 +5696,7 @@ pm_parentheses_node_create(pm_parser_t *parser, const pm_token_t *opening, pm_no pm_parentheses_node_t *node = PM_NODE_ALLOC(parser, pm_parentheses_node_t); *node = (pm_parentheses_node_t) { - { - .type = PM_PARENTHESES_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - } - }, + .base = PM_NODE_INIT(parser, PM_PARENTHESES_NODE, flags, opening->start, closing->end), .body = body, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing) @@ -6457,14 +5713,7 @@ pm_pinned_expression_node_create(pm_parser_t *parser, pm_node_t *expression, con pm_pinned_expression_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_expression_node_t); *node = (pm_pinned_expression_node_t) { - { - .type = PM_PINNED_EXPRESSION_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = rparen->end - } - }, + .base = PM_NODE_INIT(parser, PM_PINNED_EXPRESSION_NODE, 0, operator->start, rparen->end), .expression = expression, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .lparen_loc = PM_LOCATION_TOKEN_VALUE(lparen), @@ -6482,14 +5731,7 @@ pm_pinned_variable_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_pinned_variable_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_variable_node_t); *node = (pm_pinned_variable_node_t) { - { - .type = PM_PINNED_VARIABLE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = variable->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_PINNED_VARIABLE_NODE, 0, operator->start, variable->location.end), .variable = variable, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -6505,14 +5747,7 @@ pm_post_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, co pm_post_execution_node_t *node = PM_NODE_ALLOC(parser, pm_post_execution_node_t); *node = (pm_post_execution_node_t) { - { - .type = PM_POST_EXECUTION_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = closing->end - } - }, + .base = PM_NODE_INIT(parser, PM_POST_EXECUTION_NODE, 0, keyword->start, closing->end), .statements = statements, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -6530,14 +5765,7 @@ pm_pre_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, con pm_pre_execution_node_t *node = PM_NODE_ALLOC(parser, pm_pre_execution_node_t); *node = (pm_pre_execution_node_t) { - { - .type = PM_PRE_EXECUTION_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = closing->end - } - }, + .base = PM_NODE_INIT(parser, PM_PRE_EXECUTION_NODE, 0, keyword->start, closing->end), .statements = statements, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -6574,15 +5802,7 @@ pm_range_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *ope } *node = (pm_range_node_t) { - { - .type = PM_RANGE_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = (left == NULL ? operator->start : left->location.start), - .end = (right == NULL ? operator->end : right->location.end) - } - }, + .base = PM_NODE_INIT(parser, PM_RANGE_NODE, flags, (left == NULL ? operator->start : left->location.start), (right == NULL ? operator->end : right->location.end)), .left = left, .right = right, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -6599,11 +5819,9 @@ pm_redo_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_REDO); pm_redo_node_t *node = PM_NODE_ALLOC(parser, pm_redo_node_t); - *node = (pm_redo_node_t) {{ - .type = PM_REDO_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_redo_node_t) { + .base = PM_NODE_INIT(parser, PM_REDO_NODE, 0, token->start, token->end) + }; return node; } @@ -6615,17 +5833,10 @@ pm_redo_node_create(pm_parser_t *parser, const pm_token_t *token) { static pm_regular_expression_node_t * pm_regular_expression_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing, const pm_string_t *unescaped) { pm_regular_expression_node_t *node = PM_NODE_ALLOC(parser, pm_regular_expression_node_t); + pm_node_flags_t flags = pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL; *node = (pm_regular_expression_node_t) { - { - .type = PM_REGULAR_EXPRESSION_NODE, - .flags = pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = MIN(opening->start, closing->start), - .end = MAX(opening->end, closing->end) - } - }, + .base = PM_NODE_INIT(parser, PM_REGULAR_EXPRESSION_NODE, flags, MIN(opening->start, closing->start), MAX(opening->end, closing->end)), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .content_loc = PM_LOCATION_TOKEN_VALUE(content), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), @@ -6651,11 +5862,7 @@ pm_required_parameter_node_create(pm_parser_t *parser, const pm_token_t *token) pm_required_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_parameter_node_t); *node = (pm_required_parameter_node_t) { - { - .type = PM_REQUIRED_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }, + .base = PM_NODE_INIT(parser, PM_REQUIRED_PARAMETER_NODE, 0, token->start, token->end), .name = pm_parser_constant_id_token(parser, token) }; @@ -6670,14 +5877,7 @@ pm_rescue_modifier_node_create(pm_parser_t *parser, pm_node_t *expression, const pm_rescue_modifier_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_modifier_node_t); *node = (pm_rescue_modifier_node_t) { - { - .type = PM_RESCUE_MODIFIER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = expression->location.start, - .end = rescue_expression->location.end - } - }, + .base = PM_NODE_INIT(parser, PM_RESCUE_MODIFIER_NODE, 0, expression->location.start, rescue_expression->location.end), .expression = expression, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .rescue_expression = rescue_expression @@ -6694,11 +5894,7 @@ pm_rescue_node_create(pm_parser_t *parser, const pm_token_t *keyword) { pm_rescue_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_node_t); *node = (pm_rescue_node_t) { - { - .type = PM_RESCUE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(keyword) - }, + .base = PM_NODE_INIT(parser, PM_RESCUE_NODE, 0, keyword->start, keyword->end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -6762,14 +5958,7 @@ pm_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, c pm_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_rest_parameter_node_t); *node = (pm_rest_parameter_node_t) { - { - .type = PM_REST_PARAMETER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end) - } - }, + .base = PM_NODE_INIT(parser, PM_REST_PARAMETER_NODE, 0, operator->start, (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end)), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -6786,11 +5975,9 @@ pm_retry_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_RETRY); pm_retry_node_t *node = PM_NODE_ALLOC(parser, pm_retry_node_t); - *node = (pm_retry_node_t) {{ - .type = PM_RETRY_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_retry_node_t) { + .base = PM_NODE_INIT(parser, PM_RETRY_NODE, 0, token->start, token->end) + }; return node; } @@ -6803,14 +5990,7 @@ pm_return_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argumen pm_return_node_t *node = PM_NODE_ALLOC(parser, pm_return_node_t); *node = (pm_return_node_t) { - { - .type = PM_RETURN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = (arguments == NULL ? keyword->end : arguments->base.location.end) - } - }, + .base = PM_NODE_INIT(parser, PM_RETURN_NODE, 0, keyword->start, (arguments == NULL ? keyword->end : arguments->base.location.end)), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .arguments = arguments }; @@ -6826,11 +6006,9 @@ pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_SELF); pm_self_node_t *node = PM_NODE_ALLOC(parser, pm_self_node_t); - *node = (pm_self_node_t) {{ - .type = PM_SELF_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_self_node_t) { + .base = PM_NODE_INIT(parser, PM_SELF_NODE, 0, token->start, token->end) + }; return node; } @@ -6843,12 +6021,7 @@ pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shar pm_shareable_constant_node_t *node = PM_NODE_ALLOC(parser, pm_shareable_constant_node_t); *node = (pm_shareable_constant_node_t) { - { - .type = PM_SHAREABLE_CONSTANT_NODE, - .flags = (pm_node_flags_t) value, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NODE_VALUE(write) - }, + .base = PM_NODE_INIT(parser, PM_SHAREABLE_CONSTANT_NODE, (pm_node_flags_t) value, write->location.start, write->location.end), .write = write }; @@ -6863,14 +6036,7 @@ pm_singleton_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *local pm_singleton_class_node_t *node = PM_NODE_ALLOC(parser, pm_singleton_class_node_t); *node = (pm_singleton_class_node_t) { - { - .type = PM_SINGLETON_CLASS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = class_keyword->start, - .end = end_keyword->end - } - }, + .base = PM_NODE_INIT(parser, PM_SINGLETON_CLASS_NODE, 0, class_keyword->start, end_keyword->end), .locals = *locals, .class_keyword_loc = PM_LOCATION_TOKEN_VALUE(class_keyword), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -6890,12 +6056,9 @@ pm_source_encoding_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD___ENCODING__); pm_source_encoding_node_t *node = PM_NODE_ALLOC(parser, pm_source_encoding_node_t); - *node = (pm_source_encoding_node_t) {{ - .type = PM_SOURCE_ENCODING_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_source_encoding_node_t) { + .base = PM_NODE_INIT(parser, PM_SOURCE_ENCODING_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + }; return node; } @@ -6920,12 +6083,7 @@ pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) } *node = (pm_source_file_node_t) { - { - .type = PM_SOURCE_FILE_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(file_keyword), - }, + .base = PM_NODE_INIT(parser, PM_SOURCE_FILE_NODE, flags, file_keyword->start, file_keyword->end), .filepath = parser->filepath }; @@ -6940,12 +6098,9 @@ pm_source_line_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD___LINE__); pm_source_line_node_t *node = PM_NODE_ALLOC(parser, pm_source_line_node_t); - *node = (pm_source_line_node_t) {{ - .type = PM_SOURCE_LINE_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_source_line_node_t) { + .base = PM_NODE_INIT(parser, PM_SOURCE_LINE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + }; return node; } @@ -6958,14 +6113,7 @@ pm_splat_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t pm_splat_node_t *node = PM_NODE_ALLOC(parser, pm_splat_node_t); *node = (pm_splat_node_t) { - { - .type = PM_SPLAT_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = operator->start, - .end = (expression == NULL ? operator->end : expression->location.end) - } - }, + .base = PM_NODE_INIT(parser, PM_SPLAT_NODE, 0, operator->start, (expression == NULL ? operator->end : expression->location.end)), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .expression = expression }; @@ -6981,11 +6129,7 @@ pm_statements_node_create(pm_parser_t *parser) { pm_statements_node_t *node = PM_NODE_ALLOC(parser, pm_statements_node_t); *node = (pm_statements_node_t) { - { - .type = PM_STATEMENTS_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_STATEMENTS_NODE, 0, parser->start, parser->start), .body = { 0 } }; @@ -7077,16 +6221,11 @@ pm_string_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, break; } + const uint8_t *start = (opening->type == PM_TOKEN_NOT_PROVIDED ? content->start : opening->start); + const uint8_t *end = (closing->type == PM_TOKEN_NOT_PROVIDED ? content->end : closing->end); + *node = (pm_string_node_t) { - { - .type = PM_STRING_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = (opening->type == PM_TOKEN_NOT_PROVIDED ? content->start : opening->start), - .end = (closing->type == PM_TOKEN_NOT_PROVIDED ? content->end : closing->end) - } - }, + .base = PM_NODE_INIT(parser, PM_STRING_NODE, flags, start, end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .content_loc = PM_LOCATION_TOKEN_VALUE(content), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -7129,14 +6268,7 @@ pm_super_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument } *node = (pm_super_node_t) { - { - .type = PM_SUPER_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = end, - } - }, + .base = PM_NODE_INIT(parser, PM_SUPER_NODE, 0, keyword->start, end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .lparen_loc = arguments->opening_loc, .arguments = arguments->arguments, @@ -7365,16 +6497,11 @@ static pm_symbol_node_t * pm_symbol_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *value, const pm_token_t *closing, const pm_string_t *unescaped, pm_node_flags_t flags) { pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); + const uint8_t *start = (opening->type == PM_TOKEN_NOT_PROVIDED ? value->start : opening->start); + const uint8_t *end = (closing->type == PM_TOKEN_NOT_PROVIDED ? value->end : closing->end); + *node = (pm_symbol_node_t) { - { - .type = PM_SYMBOL_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL | flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = (opening->type == PM_TOKEN_NOT_PROVIDED ? value->start : opening->start), - .end = (closing->type == PM_TOKEN_NOT_PROVIDED ? value->end : closing->end) - } - }, + .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | flags, start, end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .value_loc = PM_LOCATION_TOKEN_VALUE(value), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -7448,12 +6575,7 @@ pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) { pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); *node = (pm_symbol_node_t) { - { - .type = PM_SYMBOL_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING, parser->start, parser->start), .value_loc = PM_LOCATION_NULL_VALUE(parser), .unescaped = { 0 } }; @@ -7491,15 +6613,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const pm_symbol_node_t *new_node = PM_NODE_ALLOC(parser, pm_symbol_node_t); *new_node = (pm_symbol_node_t) { - { - .type = PM_SYMBOL_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - } - }, + .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, closing->end), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .value_loc = node->content_loc, .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -7535,12 +6649,7 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { } *new_node = (pm_string_node_t) { - { - .type = PM_STRING_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = node->base.location - }, + .base = PM_NODE_INIT(parser, PM_STRING_NODE, flags, node->base.location.start, node->base.location.end), .opening_loc = node->opening_loc, .content_loc = node->value_loc, .closing_loc = node->closing_loc, @@ -7563,12 +6672,9 @@ pm_true_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_TRUE); pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); - *node = (pm_true_node_t) {{ - .type = PM_TRUE_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token) - }}; + *node = (pm_true_node_t) { + .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + }; return node; } @@ -7580,12 +6686,9 @@ static pm_true_node_t * pm_true_node_synthesized_create(pm_parser_t *parser) { pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); - *node = (pm_true_node_t) {{ - .type = PM_TRUE_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { .start = parser->start, .end = parser->end } - }}; + *node = (pm_true_node_t) { + .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, parser->start, parser->end) + }; return node; } @@ -7599,11 +6702,7 @@ pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_undef_node_t *node = PM_NODE_ALLOC(parser, pm_undef_node_t); *node = (pm_undef_node_t) { - { - .type = PM_UNDEF_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_TOKEN_VALUE(token), - }, + .base = PM_NODE_INIT(parser, PM_UNDEF_NODE, 0, token->start, token->end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(token), .names = { 0 } }; @@ -7636,15 +6735,7 @@ pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t } *node = (pm_unless_node_t) { - { - .type = PM_UNLESS_NODE, - .flags = PM_NODE_FLAG_NEWLINE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, keyword->start, end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(then_keyword), @@ -7668,15 +6759,7 @@ pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_statements_node_body_append(parser, statements, statement, true); *node = (pm_unless_node_t) { - { - .type = PM_UNLESS_NODE, - .flags = PM_NODE_FLAG_NEWLINE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = statement->location.start, - .end = predicate->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, statement->location.start, predicate->location.end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(unless_keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -7726,15 +6809,7 @@ pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_until_node_t) { - { - .type = PM_UNTIL_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = closing->end, - }, - }, + .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, keyword->start, closing->end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -7755,15 +6830,7 @@ pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm pm_loop_modifier_block_exits(parser, statements); *node = (pm_until_node_t) { - { - .type = PM_UNTIL_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = statements->base.location.start, - .end = predicate->location.end, - }, - }, + .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, statements->base.location.start, predicate->location.end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -7782,14 +6849,7 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) { pm_when_node_t *node = PM_NODE_ALLOC(parser, pm_when_node_t); *node = (pm_when_node_t) { - { - .type = PM_WHEN_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = NULL - } - }, + .base = PM_NODE_INIT(parser, PM_WHEN_NODE, 0, keyword->start, NULL), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .statements = NULL, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -7838,15 +6898,7 @@ pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_while_node_t) { - { - .type = PM_WHILE_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, keyword->start, closing->end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -7867,15 +6919,7 @@ pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm pm_loop_modifier_block_exits(parser, statements); *node = (pm_while_node_t) { - { - .type = PM_WHILE_NODE, - .flags = flags, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = statements->base.location.start, - .end = predicate->location.end - }, - }, + .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, statements->base.location.start, predicate->location.end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -7894,11 +6938,7 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); *node = (pm_while_node_t) { - { - .type = PM_WHILE_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = PM_LOCATION_NULL_VALUE(parser) - }, + .base = PM_NODE_INIT(parser, PM_WHILE_NODE, 0, parser->start, parser->start), .keyword_loc = PM_LOCATION_NULL_VALUE(parser), .do_keyword_loc = PM_LOCATION_NULL_VALUE(parser), .closing_loc = PM_LOCATION_NULL_VALUE(parser), @@ -7918,15 +6958,7 @@ pm_xstring_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, pm_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_x_string_node_t); *node = (pm_x_string_node_t) { - { - .type = PM_X_STRING_NODE, - .flags = PM_STRING_FLAGS_FROZEN, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = opening->start, - .end = closing->end - }, - }, + .base = PM_NODE_INIT(parser, PM_X_STRING_NODE, PM_STRING_FLAGS_FROZEN, opening->start, closing->end), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .content_loc = PM_LOCATION_TOKEN_VALUE(content), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), @@ -7963,14 +6995,7 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo } *node = (pm_yield_node_t) { - { - .type = PM_YIELD_NODE, - .node_id = PM_NODE_IDENTIFY(parser), - .location = { - .start = keyword->start, - .end = end - }, - }, + .base = PM_NODE_INIT(parser, PM_YIELD_NODE, 0, keyword->start, end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .lparen_loc = *lparen_loc, .arguments = arguments, @@ -7981,7 +7006,6 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo } #undef PM_NODE_ALLOC -#undef PM_NODE_IDENTIFY /** * Check if any of the currently visible scopes contain a local variable From c58970b57a91a10eb75f933258643d0393ce0ba8 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 1 Dec 2025 17:14:08 +0000 Subject: [PATCH 1452/2435] ZJIT: Optimize variadic cfunc `Send` calls into `CCallVariadic` (#14898) ZJIT: Optimize variadic cfunc Send calls into CCallVariadic --- test/ruby/test_zjit.rb | 15 +++++++++ zjit/src/codegen.rs | 37 ++++++++++++++++----- zjit/src/hir.rs | 69 ++++++++++++++++++++++++++++----------- zjit/src/hir/opt_tests.rs | 42 +++++++++++++++++------- 4 files changed, 125 insertions(+), 38 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 7472ff77156b95..d821d8ad5cca69 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -525,6 +525,21 @@ def test = [1, 2].map(&:to_s) } end + def test_send_variadic_with_block + assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{ + A = [1, 2, 3] + B = ["a", "b", "c"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + + test; test + }, call_threshold: 2 + end + def test_send_splat assert_runs '[1, 2]', %q{ def test(a, b) = [a, b] diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 66436b2374b4ff..5200894f878d4f 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -431,8 +431,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, name, args, cme, state, blockiseq, .. } => gen_ccall_with_frame(jit, asm, *cfunc, *name, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), - Insn::CCallVariadic { cfunc, recv, args, name, cme, state, return_type: _, elidable: _ } => { - gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) + Insn::CCallVariadic { cfunc, recv, args, name, cme, state, blockiseq, .. } => { + gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, ic, state: _ } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), @@ -845,26 +845,47 @@ fn gen_ccall_variadic( recv: Opnd, args: Vec, cme: *const rb_callable_method_entry_t, + blockiseq: Option, state: &FrameState, ) -> lir::Opnd { gen_incr_counter(asm, Counter::variadic_cfunc_optimized_send_count); + gen_stack_overflow_check(jit, asm, state, state.stack_size()); - gen_prepare_non_leaf_call(jit, asm, state); + let args_with_recv_len = args.len() + 1; - let stack_growth = state.stack_size(); - gen_stack_overflow_check(jit, asm, state, stack_growth); + // Compute the caller's stack size after consuming recv and args. + // state.stack() includes recv + args, so subtract both. + let caller_stack_size = state.stack_size() - args_with_recv_len; - gen_push_frame(asm, args.len(), state, ControlFrame { + // Can't use gen_prepare_non_leaf_call() because we need to adjust the SP + // to account for the receiver and arguments (like gen_ccall_with_frame does) + gen_prepare_call_with_gc(asm, state, false); + gen_save_sp(asm, caller_stack_size); + gen_spill_stack(jit, asm, state); + gen_spill_locals(jit, asm, state); + + let block_handler_specval = if let Some(block_iseq) = blockiseq { + // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). + // VM_CFP_TO_CAPTURED_BLOCK then turns &cfp->self into a block handler. + // rb_captured_block->code.iseq aliases with cfp->block_code. + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); + let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); + asm.or(cfp_self_addr, Opnd::Imm(1)) + } else { + VM_BLOCK_HANDLER_NONE.into() + }; + + gen_push_frame(asm, args_with_recv_len, state, ControlFrame { recv, iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, - specval: VM_BLOCK_HANDLER_NONE.into(), + specval: block_handler_specval, pc: PC_POISON, }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; + let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index fbc9d80700dcaf..8c1ec664d03a5f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -804,6 +804,7 @@ pub enum Insn { state: InsnId, return_type: Type, elidable: bool, + blockiseq: Option, }, /// Un-optimized fallback implementation (dynamic dispatch) for send-ish instructions @@ -1920,8 +1921,8 @@ impl Function { elidable, blockiseq, }, - &CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable } => CCallVariadic { - cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable + &CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable, blockiseq } => CCallVariadic { + cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable, blockiseq }, &Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, @@ -2989,9 +2990,23 @@ impl Function { return Err(()); } - // Find the `argc` (arity) of the C method, which describes the parameters it expects + let ci_flags = unsafe { vm_ci_flag(call_info) }; + + // When seeing &block argument, fall back to dynamic dispatch for now + // TODO: Support block forwarding + if unspecializable_call_type(ci_flags) { + fun.count_complex_call_features(block, ci_flags); + fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); + return Err(()); + } + + let blockiseq = if blockiseq.is_null() { None } else { Some(blockiseq) }; + let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; + // Find the `argc` (arity) of the C method, which describes the parameters it expects let cfunc_argc = unsafe { get_mct_argc(cfunc) }; + let cfunc_ptr = unsafe { get_mct_func(cfunc) }.cast(); + match cfunc_argc { 0.. => { // (self, arg0, arg1, ..., argc) form @@ -3001,16 +3016,6 @@ impl Function { return Err(()); } - let ci_flags = unsafe { vm_ci_flag(call_info) }; - - // When seeing &block argument, fall back to dynamic dispatch for now - // TODO: Support block forwarding - if unspecializable_call_type(ci_flags) { - fun.count_complex_call_features(block, ci_flags); - fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); - return Err(()); - } - // Commit to the replacement. Put PatchPoint. fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); if recv_class.instance_can_have_singleton_class() { @@ -3023,17 +3028,14 @@ impl Function { fun.insn_types[recv.0] = fun.infer_type(recv); } - let blockiseq = if blockiseq.is_null() { None } else { Some(blockiseq) }; - // Emit a call - let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, - cfunc, + cfunc: cfunc_ptr, args: cfunc_args, cme, name, @@ -3047,9 +3049,37 @@ impl Function { } // Variadic method -1 => { + // The method gets a pointer to the first argument // func(int argc, VALUE *argv, VALUE recv) - fun.set_dynamic_send_reason(send_insn_id, SendCfuncVariadic); - Err(()) + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state); + + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); + } + if let Some(profiled_type) = profiled_type { + // Guard receiver class + recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); + } + + if get_option!(stats) { + count_not_inlined_cfunc(fun, block, cme); + } + + let ccall = fun.push_insn(block, Insn::CCallVariadic { + cfunc: cfunc_ptr, + recv, + args, + cme, + name: method_id, + state, + return_type: types::BasicObject, + elidable: false, + blockiseq + }); + + fun.make_equal_to(send_insn_id, ccall); + Ok(()) } -2 => { // (self, args_ruby_array) @@ -3252,6 +3282,7 @@ impl Function { state, return_type, elidable, + blockiseq: None, }); fun.make_equal_to(send_insn_id, ccall); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index b32da5a9ebb8e5..26dad38b580f0a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5302,26 +5302,46 @@ mod hir_opt_tests { } #[test] - fn test_do_not_optimize_send_variadic_with_block() { + fn test_optimize_send_variadic_with_block() { eval(r#" - def test = [1, 2, 3].index { |x| x == 2 } + A = [1, 2, 3] + B = ["a", "b", "c"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + test; test "#); assert_snapshot!(hir_string("test"), @r" - fn test@:2: + fn test@:6: bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:ArrayExact = ArrayDup v10 - v13:BasicObject = Send v11, 0x1008, :index + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact = NewArray + SetLocal l0, EP@3, v13 + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, A) + v36:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, B) + v39:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030) + PatchPoint NoSingletonClass(Array@0x1020) + v43:BasicObject = CCallVariadic zip@0x1058, v36, v39 + v25:BasicObject = GetLocal l0, EP@3 + v29:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v13 + Return v29 "); } From e02eda194f1d1ff6998b5eb462dd2a2afc54281c Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 1 Dec 2025 12:55:39 -0500 Subject: [PATCH 1453/2435] Speedup RBASIC_FIELDS_COUNT (#15273) We know the argument is not a class, module or special const, so we can skip these checks. --- shape.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shape.h b/shape.h index ec5c25b32f16f5..d9cfe48759c333 100644 --- a/shape.h +++ b/shape.h @@ -395,7 +395,7 @@ ROBJECT_FIELDS_COUNT(VALUE obj) static inline uint32_t RBASIC_FIELDS_COUNT(VALUE obj) { - return RSHAPE(rb_obj_shape_id(obj))->next_field_index; + return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index; } static inline bool From 5f92d6da1e48ce62205858cdcc7e60108e585f5f Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 1 Dec 2025 18:01:12 +0000 Subject: [PATCH 1454/2435] ZJIT: Standardize method dispatch insns' `recv` field (#15334) ZJIT: Standardize C call related insn fields - Add `recv` field to `CCall` and `CCallWithFrame` so now all method dispatch related instructions have `recv` field, separate from `args` field. This ensures consistent pointer arithmetic when generating code for these instructions. - Standardize `recv` field's display position in send related instructions. --- zjit/src/codegen.rs | 31 ++++++----- zjit/src/cruby_methods.rs | 3 +- zjit/src/hir.rs | 58 +++++++++++---------- zjit/src/hir/opt_tests.rs | 106 +++++++++++++++++++------------------- zjit/src/hir/tests.rs | 2 +- 5 files changed, 106 insertions(+), 94 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5200894f878d4f..f77b8cc4bf2ca6 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -425,13 +425,14 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), - Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnds!(args)), - // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. - Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => + Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), + // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). + // There's no test case for this because no core cfuncs have this many parameters. But C extensions could have such methods. + Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), - Insn::CCallWithFrame { cfunc, name, args, cme, state, blockiseq, .. } => - gen_ccall_with_frame(jit, asm, *cfunc, *name, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), - Insn::CCallVariadic { cfunc, recv, args, name, cme, state, blockiseq, .. } => { + Insn::CCallWithFrame { cfunc, recv, name, args, cme, state, blockiseq, .. } => + gen_ccall_with_frame(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), + Insn::CCallVariadic { cfunc, recv, name, args, cme, state, blockiseq, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, ic, state: _ } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic), @@ -766,6 +767,7 @@ fn gen_ccall_with_frame( asm: &mut Assembler, cfunc: *const u8, name: ID, + recv: Opnd, args: Vec, cme: *const rb_callable_method_entry_t, blockiseq: Option, @@ -774,7 +776,8 @@ fn gen_ccall_with_frame( gen_incr_counter(asm, Counter::non_variadic_cfunc_optimized_send_count); gen_stack_overflow_check(jit, asm, state, state.stack_size()); - let caller_stack_size = state.stack_size() - args.len(); + let args_with_recv_len = args.len() + 1; + let caller_stack_size = state.stack().len() - args_with_recv_len; // Can't use gen_prepare_non_leaf_call() because we need to adjust the SP // to account for the receiver and arguments (and block arguments if any) @@ -794,8 +797,8 @@ fn gen_ccall_with_frame( VM_BLOCK_HANDLER_NONE.into() }; - gen_push_frame(asm, args.len(), state, ControlFrame { - recv: args[0], + gen_push_frame(asm, args_with_recv_len, state, ControlFrame { + recv, iseq: None, cme, frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, @@ -813,8 +816,10 @@ fn gen_ccall_with_frame( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + let mut cfunc_args = vec![recv]; + cfunc_args.extend(args); asm.count_call_to(&name.contents_lossy()); - let result = asm.ccall(cfunc, args); + let result = asm.ccall(cfunc, cfunc_args); asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); @@ -830,9 +835,11 @@ fn gen_ccall_with_frame( /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. -fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, args: Vec) -> lir::Opnd { +fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, recv: Opnd, args: Vec) -> lir::Opnd { + let mut cfunc_args = vec![recv]; + cfunc_args.extend(args); asm.count_call_to(&name.contents_lossy()); - asm.ccall(cfunc, args) + asm.ccall(cfunc, cfunc_args) } /// Generate code for a variadic C function call diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 23c05e6d58fb54..56d4de280bff71 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -440,7 +440,8 @@ fn inline_string_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins // TODO(max): Make StringEqual its own opcode so that we can later constant-fold StringEqual(a, a) => true let result = fun.push_insn(block, hir::Insn::CCall { cfunc: rb_yarv_str_eql_internal as *const u8, - args: vec![recv, other], + recv, + args: vec![other], name: ID!(string_eq), return_type, elidable, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8c1ec664d03a5f..e5c1f7a54293da 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -778,12 +778,13 @@ pub enum Insn { /// Call a C function without pushing a frame /// `name` is for printing purposes only - CCall { cfunc: *const u8, args: Vec, name: ID, return_type: Type, elidable: bool }, + CCall { cfunc: *const u8, recv: InsnId, args: Vec, name: ID, return_type: Type, elidable: bool }, /// Call a C function that pushes a frame CCallWithFrame { cd: *const rb_call_data, // cd for falling back to SendWithoutBlock cfunc: *const u8, + recv: InsnId, args: Vec, cme: *const rb_callable_method_entry_t, name: ID, @@ -1180,8 +1181,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } - Insn::SendForward { cd, args, blockiseq, .. } => { - write!(f, "SendForward {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?; + Insn::SendForward { recv, cd, args, blockiseq, .. } => { + write!(f, "SendForward {recv}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?; for arg in args { write!(f, ", {arg}")?; } @@ -1240,15 +1241,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, Insn::FixnumBitCheck {val, index} => { write!(f, "FixnumBitCheck {val}, {index}") }, - Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => { - write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => { + write!(f, "CCall {recv}, :{}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } Ok(()) }, - Insn::CCallWithFrame { cfunc, args, name, blockiseq, .. } => { - write!(f, "CCallWithFrame {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + Insn::CCallWithFrame { cfunc, recv, args, name, blockiseq, .. } => { + write!(f, "CCallWithFrame {recv}, :{}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } @@ -1257,8 +1258,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) }, - Insn::CCallVariadic { cfunc, recv, args, name, .. } => { - write!(f, "CCallVariadic {}@{:p}, {recv}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + Insn::CCallVariadic { cfunc, recv, args, name, .. } => { + write!(f, "CCallVariadic {recv}, :{}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } @@ -1909,10 +1910,11 @@ impl Function { &HashAref { hash, key, state } => HashAref { hash: find!(hash), key: find!(key), state }, &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, - &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, - &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable, blockiseq } => CCallWithFrame { + &CCall { cfunc, recv, ref args, name, return_type, elidable } => CCall { cfunc, recv: find!(recv), args: find_vec!(args), name, return_type, elidable }, + &CCallWithFrame { cd, cfunc, recv, ref args, cme, name, state, return_type, elidable, blockiseq } => CCallWithFrame { cd, cfunc, + recv: find!(recv), args: find_vec!(args), cme, name, @@ -2959,7 +2961,7 @@ impl Function { send: Insn, send_insn_id: InsnId, ) -> Result<(), ()> { - let Insn::Send { mut recv, cd, blockiseq, mut args, state, .. } = send else { + let Insn::Send { mut recv, cd, blockiseq, args, state, .. } = send else { return Err(()); }; @@ -3029,14 +3031,14 @@ impl Function { } // Emit a call - let mut cfunc_args = vec![recv]; - cfunc_args.append(&mut args); + let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, - cfunc: cfunc_ptr, - args: cfunc_args, + cfunc, + recv, + args, cme, name, state, @@ -3098,7 +3100,7 @@ impl Function { send: Insn, send_insn_id: InsnId, ) -> Result<(), ()> { - let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else { + let Insn::SendWithoutBlock { mut recv, cd, args, state, .. } = send else { return Err(()); }; @@ -3191,14 +3193,12 @@ impl Function { // No inlining; emit a call let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id })); - let mut cfunc_args = vec![recv]; - cfunc_args.append(&mut args); let return_type = props.return_type; let elidable = props.elidable; // Filter for a leaf and GC free function if props.leaf && props.no_gc { fun.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); - let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name, return_type, elidable }); + let ccall = fun.push_insn(block, Insn::CCall { cfunc, recv, args, name, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { if get_option!(stats) { @@ -3207,7 +3207,8 @@ impl Function { let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, - args: cfunc_args, + recv, + args, cme, name, state, @@ -3662,19 +3663,22 @@ impl Function { | &Insn::SendForward { recv, ref args, state, .. } | &Insn::SendWithoutBlock { recv, ref args, state, .. } | &Insn::CCallVariadic { recv, ref args, state, .. } + | &Insn::CCallWithFrame { recv, ref args, state, .. } | &Insn::SendWithoutBlockDirect { recv, ref args, state, .. } | &Insn::InvokeSuper { recv, ref args, state, .. } => { worklist.push_back(recv); worklist.extend(args); worklist.push_back(state); } - &Insn::CCallWithFrame { ref args, state, .. } - | &Insn::InvokeBuiltin { ref args, state, .. } + &Insn::InvokeBuiltin { ref args, state, .. } | &Insn::InvokeBlock { ref args, state, .. } => { worklist.extend(args); worklist.push_back(state) } - Insn::CCall { args, .. } => worklist.extend(args), + &Insn::CCall { recv, ref args, .. } => { + worklist.push_back(recv); + worklist.extend(args); + } &Insn::GetIvar { self_val, state, .. } | &Insn::DefinedIvar { self_val, state, .. } => { worklist.push_back(self_val); worklist.push_back(state); @@ -4228,7 +4232,6 @@ impl Function { | Insn::IncrCounter { .. } | Insn::IncrCounterPtr { .. } | Insn::CheckInterrupts { .. } - | Insn::CCall { .. } | Insn::GetClassVar { .. } | Insn::GetSpecialNumber { .. } | Insn::GetSpecialSymbol { .. } @@ -4276,6 +4279,8 @@ impl Function { | Insn::Send { recv, ref args, .. } | Insn::SendForward { recv, ref args, .. } | Insn::InvokeSuper { recv, ref args, .. } + | Insn::CCall { recv, ref args, .. } + | Insn::CCallWithFrame { recv, ref args, .. } | Insn::CCallVariadic { recv, ref args, .. } | Insn::ArrayInclude { target: recv, elements: ref args, .. } => { self.assert_subtype(insn_id, recv, types::BasicObject)?; @@ -4285,8 +4290,7 @@ impl Function { Ok(()) } // Instructions with a Vec of Ruby objects - Insn::CCallWithFrame { ref args, .. } - | Insn::InvokeBuiltin { ref args, .. } + Insn::InvokeBuiltin { ref args, .. } | Insn::InvokeBlock { ref args, .. } | Insn::NewArray { elements: ref args, .. } | Insn::ArrayHash { elements: ref args, .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 26dad38b580f0a..e8c8d50dbe9256 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -560,7 +560,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(CustomEq@0x1000, !=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(CustomEq@0x1000) v28:HeapObject[class_exact:CustomEq] = GuardType v9, HeapObject[class_exact:CustomEq] - v29:BoolExact = CCallWithFrame BasicObject#!=@0x1038, v28, v9 + v29:BoolExact = CCallWithFrame v28, :BasicObject#!=@0x1038, v9 v20:NilClass = Const Value(nil) CheckInterrupts Return v20 @@ -783,7 +783,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v23:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C] - v24:BasicObject = CCallWithFrame C#fun_new_map@0x1038, v23, block=0x1040 + v24:BasicObject = CCallWithFrame v23, :C#fun_new_map@0x1038, block=0x1040 v15:BasicObject = GetLocal l0, EP@3 CheckInterrupts Return v24 @@ -1042,7 +1042,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Object@0x1008) v22:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] - v23:BasicObject = CCallVariadic Kernel#puts@0x1040, v22, v12 + v23:BasicObject = CCallVariadic v22, :Kernel#puts@0x1040, v12 CheckInterrupts Return v23 "); @@ -2240,7 +2240,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_cfunc_optimized_send_count - v34:StringExact|NilClass = CCall Module#name@0x1048, v29 + v34:StringExact|NilClass = CCall v29, :Module#name@0x1048 PatchPoint NoEPEscape(test) v22:Fixnum[1] = Const Value(1) CheckInterrupts @@ -2272,7 +2272,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v29:Fixnum = CCall Array#length@0x1038, v13 + v29:Fixnum = CCall v13, :Array#length@0x1038 v20:Fixnum[5] = Const Value(5) CheckInterrupts Return v20 @@ -2416,7 +2416,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v29:Fixnum = CCall Array#size@0x1038, v13 + v29:Fixnum = CCall v13, :Array#size@0x1038 v20:Fixnum[5] = Const Value(5) CheckInterrupts Return v20 @@ -3149,7 +3149,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) PatchPoint NoSingletonClass(Class@0x1038) - v57:BasicObject = CCallVariadic Array.new@0x1040, v46, v16 + v57:BasicObject = CCallVariadic v46, :Array.new@0x1040, v16 CheckInterrupts Return v57 "); @@ -3180,7 +3180,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Set@0x1008, initialize@0x1038, cme:0x1040) PatchPoint NoSingletonClass(Set@0x1008) v49:SetExact = GuardType v18, SetExact - v50:BasicObject = CCallVariadic Set#initialize@0x1068, v49 + v50:BasicObject = CCallVariadic v49, :Set#initialize@0x1068 CheckInterrupts CheckInterrupts Return v18 @@ -3210,7 +3210,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, new@0x1009, cme:0x1010) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) PatchPoint NoSingletonClass(Class@0x1038) - v54:BasicObject = CCallVariadic String.new@0x1040, v43 + v54:BasicObject = CCallVariadic v43, :String.new@0x1040 CheckInterrupts Return v54 "); @@ -3242,7 +3242,7 @@ mod hir_opt_tests { v50:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) PatchPoint NoSingletonClass(Regexp@0x1008) - v54:BasicObject = CCallVariadic Regexp#initialize@0x1078, v50, v17 + v54:BasicObject = CCallVariadic v50, :Regexp#initialize@0x1078, v17 CheckInterrupts CheckInterrupts Return v50 @@ -3270,7 +3270,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v30:Fixnum = CCall Array#length@0x1038, v18 + v30:Fixnum = CCall v18, :Array#length@0x1038 CheckInterrupts Return v30 "); @@ -3297,7 +3297,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) IncrCounter inline_cfunc_optimized_send_count - v30:Fixnum = CCall Array#size@0x1038, v18 + v30:Fixnum = CCall v18, :Array#size@0x1038 CheckInterrupts Return v30 "); @@ -3717,7 +3717,7 @@ mod hir_opt_tests { v10:HashExact = NewHash PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) - v22:BasicObject = CCallWithFrame Kernel#dup@0x1038, v10 + v22:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 v14:BasicObject = SendWithoutBlock v22, :freeze CheckInterrupts Return v14 @@ -3810,7 +3810,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v22:BasicObject = CCallWithFrame Kernel#dup@0x1038, v10 + v22:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 v14:BasicObject = SendWithoutBlock v22, :freeze CheckInterrupts Return v14 @@ -3904,7 +3904,7 @@ mod hir_opt_tests { v11:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:BasicObject = CCallWithFrame String#dup@0x1040, v11 + v23:BasicObject = CCallWithFrame v11, :String#dup@0x1040 v15:BasicObject = SendWithoutBlock v23, :freeze CheckInterrupts Return v15 @@ -3999,7 +3999,7 @@ mod hir_opt_tests { v11:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:BasicObject = CCallWithFrame String#dup@0x1040, v11 + v23:BasicObject = CCallWithFrame v11, :String#dup@0x1040 v15:BasicObject = SendWithoutBlock v23, :-@ CheckInterrupts Return v15 @@ -4141,7 +4141,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallWithFrame Array#to_s@0x1040, v31 + v32:BasicObject = CCallWithFrame v31, :Array#to_s@0x1040 v19:String = AnyToString v9, str: v32 v21:StringExact = StringConcat v13, v19 CheckInterrupts @@ -5004,7 +5004,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall Array#empty?@0x1038, v23 + v25:BoolExact = CCall v23, :Array#empty?@0x1038 CheckInterrupts Return v25 "); @@ -5032,7 +5032,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v23:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count - v25:BoolExact = CCall Hash#empty?@0x1038, v23 + v25:BoolExact = CCall v23, :Hash#empty?@0x1038 CheckInterrupts Return v25 "); @@ -5295,7 +5295,7 @@ mod hir_opt_tests { v11:ArrayExact = ArrayDup v10 PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v21:BasicObject = CCallWithFrame Array#map@0x1040, v11, block=0x1048 + v21:BasicObject = CCallWithFrame v11, :Array#map@0x1040, block=0x1048 CheckInterrupts Return v21 "); @@ -5337,7 +5337,7 @@ mod hir_opt_tests { v39:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030) PatchPoint NoSingletonClass(Array@0x1020) - v43:BasicObject = CCallVariadic zip@0x1058, v36, v39 + v43:BasicObject = CCallVariadic v36, :zip@0x1058, v39 v25:BasicObject = GetLocal l0, EP@3 v29:BasicObject = GetLocal l0, EP@3 CheckInterrupts @@ -5765,7 +5765,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v20:ArrayExact = CCallWithFrame Array#reverse@0x1038, v10 + v20:ArrayExact = CCallWithFrame v10, :Array#reverse@0x1038 CheckInterrupts Return v20 "); @@ -5818,7 +5818,7 @@ mod hir_opt_tests { v13:StringExact = StringCopy v12 PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v23:StringExact = CCallVariadic Array#join@0x1040, v10, v13 + v23:StringExact = CCallVariadic v10, :Array#join@0x1040, v13 CheckInterrupts Return v23 "); @@ -6172,7 +6172,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallVariadic Array#[]=@0x1038, v31, v16, v18 + v32:BasicObject = CCallVariadic v31, :Array#[]=@0x1038, v16, v18 CheckInterrupts Return v18 "); @@ -6263,7 +6263,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v28:ArrayExact = GuardType v9, ArrayExact - v29:BasicObject = CCallVariadic Array#push@0x1038, v28, v14, v16, v18 + v29:BasicObject = CCallVariadic v28, :Array#push@0x1038, v14, v16, v18 CheckInterrupts Return v29 "); @@ -6291,7 +6291,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall Array#length@0x1038, v23 + v25:Fixnum = CCall v23, :Array#length@0x1038 CheckInterrupts Return v25 "); @@ -6319,7 +6319,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) v23:ArrayExact = GuardType v9, ArrayExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall Array#size@0x1038, v23 + v25:Fixnum = CCall v23, :Array#size@0x1038 CheckInterrupts Return v25 "); @@ -6347,7 +6347,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) v25:StringExact = GuardType v9, StringExact - v26:BasicObject = CCallWithFrame String#=~@0x1040, v25, v14 + v26:BasicObject = CCallWithFrame v25, :String#=~@0x1040, v14 CheckInterrupts Return v26 "); @@ -6518,7 +6518,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v30:StringExact = GuardType v13, StringExact - v31:BasicObject = CCallWithFrame String#setbyte@0x1038, v30, v14, v15 + v31:BasicObject = CCallWithFrame v30, :String#setbyte@0x1038, v14, v15 CheckInterrupts Return v31 "); @@ -6634,7 +6634,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) v22:Integer = GuardType v9, Integer - v23:BasicObject = CCallWithFrame Integer#succ@0x1038, v22 + v23:BasicObject = CCallWithFrame v22, :Integer#succ@0x1038 CheckInterrupts Return v23 "); @@ -6689,7 +6689,7 @@ mod hir_opt_tests { v14:Fixnum[-5] = Const Value(-5) PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) v24:Fixnum = GuardType v9, Fixnum - v25:BasicObject = CCallWithFrame Integer#<<@0x1038, v24, v14 + v25:BasicObject = CCallWithFrame v24, :Integer#<<@0x1038, v14 CheckInterrupts Return v25 "); @@ -6716,7 +6716,7 @@ mod hir_opt_tests { v14:Fixnum[64] = Const Value(64) PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) v24:Fixnum = GuardType v9, Fixnum - v25:BasicObject = CCallWithFrame Integer#<<@0x1038, v24, v14 + v25:BasicObject = CCallWithFrame v24, :Integer#<<@0x1038, v14 CheckInterrupts Return v25 "); @@ -6743,7 +6743,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, <<@0x1008, cme:0x1010) v26:Fixnum = GuardType v11, Fixnum - v27:BasicObject = CCallWithFrame Integer#<<@0x1038, v26, v12 + v27:BasicObject = CCallWithFrame v26, :Integer#<<@0x1038, v12 CheckInterrupts Return v27 "); @@ -6800,7 +6800,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v27:StringExact = GuardType v11, StringExact - v28:BasicObject = CCallWithFrame String#<<@0x1038, v27, v12 + v28:BasicObject = CCallWithFrame v27, :String#<<@0x1038, v12 CheckInterrupts Return v28 "); @@ -6860,7 +6860,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(MyString@0x1000) v27:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] - v28:BasicObject = CCallWithFrame String#<<@0x1038, v27, v12 + v28:BasicObject = CCallWithFrame v27, :String#<<@0x1038, v12 CheckInterrupts Return v28 "); @@ -7017,7 +7017,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) v25:Integer = GuardType v11, Integer - v26:BasicObject = CCallWithFrame Integer#^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame v25, :Integer#^@0x1038, v12 CheckInterrupts Return v26 "); @@ -7040,7 +7040,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) v25:Fixnum = GuardType v11, Fixnum - v26:BasicObject = CCallWithFrame Integer#^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame v25, :Integer#^@0x1038, v12 CheckInterrupts Return v26 "); @@ -7063,7 +7063,7 @@ mod hir_opt_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) v25:TrueClass = GuardType v11, TrueClass - v26:BasicObject = CCallWithFrame TrueClass#^@0x1038, v25, v12 + v26:BasicObject = CCallWithFrame v25, :TrueClass#^@0x1038, v12 CheckInterrupts Return v26 "); @@ -7113,7 +7113,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Hash@0x1000) v23:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall Hash#size@0x1038, v23 + v25:Fixnum = CCall v23, :Hash#size@0x1038 CheckInterrupts Return v25 "); @@ -7481,7 +7481,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) PatchPoint NoSingletonClass(C@0x1008) v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:BasicObject = CCallVariadic Kernel#respond_to?@0x1040, v24, v14 + v25:BasicObject = CCallVariadic v24, :Kernel#respond_to?@0x1040, v14 CheckInterrupts Return v25 "); @@ -7849,7 +7849,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v27:StringExact = GuardType v11, StringExact v28:String = GuardType v12, String - v29:BoolExact = CCall String#==@0x1038, v27, v28 + v29:BoolExact = CCall v27, :String#==@0x1038, v28 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v29 @@ -7880,7 +7880,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v27:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] v28:String = GuardType v12, String - v29:BoolExact = CCall String#==@0x1038, v27, v28 + v29:BoolExact = CCall v27, :String#==@0x1038, v28 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v29 @@ -7911,7 +7911,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v27:StringExact = GuardType v11, StringExact v28:String = GuardType v12, String - v29:BoolExact = CCall String#==@0x1038, v27, v28 + v29:BoolExact = CCall v27, :String#==@0x1038, v28 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v29 @@ -7940,7 +7940,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v26:StringExact = GuardType v11, StringExact v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 + v28:BoolExact = CCall v26, :String#==@0x1038, v27 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 @@ -7971,7 +7971,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 + v28:BoolExact = CCall v26, :String#==@0x1038, v27 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 @@ -8002,7 +8002,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v26:StringExact = GuardType v11, StringExact v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 + v28:BoolExact = CCall v26, :String#==@0x1038, v27 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v28 @@ -8032,7 +8032,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall String#size@0x1038, v23 + v25:Fixnum = CCall v23, :String#size@0x1038 CheckInterrupts Return v25 "); @@ -8151,7 +8151,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall String#length@0x1038, v23 + v25:Fixnum = CCall v23, :String#length@0x1038 CheckInterrupts Return v25 "); @@ -8211,7 +8211,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, ===@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_cfunc_optimized_send_count - v31:BoolExact = CCall Module#===@0x1048, v26, v9 + v31:BoolExact = CCall v26, :Module#===@0x1048, v9 CheckInterrupts Return v31 "); @@ -8270,7 +8270,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) PatchPoint NoSingletonClass(String@0x1010) v28:StringExact = GuardType v9, StringExact - v29:BasicObject = CCallWithFrame Kernel#is_a?@0x1048, v28, v24 + v29:BasicObject = CCallWithFrame v28, :Kernel#is_a?@0x1048, v24 CheckInterrupts Return v29 "); @@ -8395,7 +8395,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) PatchPoint NoSingletonClass(String@0x1010) v28:StringExact = GuardType v9, StringExact - v29:BasicObject = CCallWithFrame Kernel#kind_of?@0x1048, v28, v24 + v29:BasicObject = CCallWithFrame v28, :Kernel#kind_of?@0x1048, v24 CheckInterrupts Return v29 "); @@ -8594,7 +8594,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Class@0x1038) v30:ModuleSubclass[class_exact*:Class@VALUE(0x1038)] = GuardType v26, ModuleSubclass[class_exact*:Class@VALUE(0x1038)] IncrCounter inline_cfunc_optimized_send_count - v32:StringExact|NilClass = CCall Module#name@0x1070, v30 + v32:StringExact|NilClass = CCall v30, :Module#name@0x1070 CheckInterrupts Return v32 "); @@ -8644,7 +8644,7 @@ mod hir_opt_tests { v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Class@0x1008) - v60:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 + v60:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1040, block=0x1048 v48:BasicObject = GetLocal l0, EP@6 v49:BasicObject = GetLocal l0, EP@5 v50:BasicObject = GetLocal l0, EP@4 diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index f8a6abc2bcbc86..314eb7777cbf6c 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1834,7 +1834,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendForward 0x1000, :foo, v9 + v15:BasicObject = SendForward v8, 0x1000, :foo, v9 CheckInterrupts Return v15 "); From f92001344d0595bb6ef3a9576853edf1fa7588ca Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 1 Dec 2025 13:02:35 -0500 Subject: [PATCH 1455/2435] ZJIT: Fix erroneous version number for Iongraph (#15357) As per https://github.com/mozilla-spidermonkey/iongraph/blob/8d5e531305320216f86a24bfc9bc136a3627e832/src/iongraph.ts#L147, correct version number for the web-based tool is 1, rather than 2. --- doc/jit/zjit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index bb20b9f6924bac..f3b36b1fd5aaa5 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -166,7 +166,7 @@ stackprof path/to/zjit_exits_{pid}.dump Using `--zjit-dump-hir-iongraph` will dump all compiled functions into a directory named `/tmp/zjit-iongraph-{PROCESS_PID}`. Each file will be named `func_{ZJIT_FUNC_NAME}.json`. In order to use them in the Iongraph viewer, you'll need to use `jq` to collate them to a single file. An example invocation of `jq` is shown below for reference. -`jq --slurp --null-input '.functions=inputs | .version=2' /tmp/zjit-iongraph-{PROCESS_PID}/func*.json > ~/Downloads/ion.json` +`jq --slurp --null-input '.functions=inputs | .version=1' /tmp/zjit-iongraph-{PROCESS_PID}/func*.json > ~/Downloads/ion.json` From there, you can use https://mozilla-spidermonkey.github.io/iongraph/ to view your trace. From a8b49ab48703dfb8862ca28a443da0e764d692c6 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 1 Dec 2025 16:51:29 -0500 Subject: [PATCH 1456/2435] Test CC invalidation for singleton classes of objects (#15360) I made a recent change where all the tests passed but it turns out it was still wrong. We didn't have any tests for CC invalidation on singletons of objects that aren't classes or modules. --- test/ruby/test_class.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index f40817e7a1ef54..10b7655e9a5562 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -887,4 +887,34 @@ def test_method_table_assignment_just_after_class_init class C; end end; end + + def test_singleton_cc_invalidation + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class T + def hi + "hi" + end + end + + t = T.new + t.singleton_class + + def hello(t) + t.hi + end + + 5.times do + hello(t) # populate inline cache on `t.singleton_class`. + end + + class T + remove_method :hi # invalidate `t.singleton_class` ccs for `hi` + end + + assert_raise NoMethodError do + hello(t) + end + end; + end end From e68fcf111b48a25ccf465e7acef13e4e145bcfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkan=20=C3=9Cnal?= <32255826+brkn@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:02:03 +0300 Subject: [PATCH 1457/2435] [ruby/strscan] [DOC] Fix broken link to helper methods (https://github.com/ruby/strscan/pull/179) ### Helper methods link is broken at master branch To reproduce 1. go to [StringScanner docs](https://docs.ruby-lang.org/en/master/StringScanner.html) 2. Click to link at line > See examples at **helper_methods** 3. Resolved url gives 404: https://docs.ruby-lang.org/en/master/strscan/helper_methods_md.html ### Fix Currently link resolves as `href="doc/strscan/helper_methods_md.html"` Correct link should be resolved as `href="helper_methods_md.html"` https://github.com/ruby/strscan/commit/adb8678aa6 --- doc/strscan/strscan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/strscan/strscan.md b/doc/strscan/strscan.md index c0bf5413623b07..c4ab2034624947 100644 --- a/doc/strscan/strscan.md +++ b/doc/strscan/strscan.md @@ -37,7 +37,7 @@ Some examples here assume that certain helper methods are defined: - `match_values_cleared?(scanner)`: Returns whether the scanner's [match values][9] are cleared. -See examples at [helper methods](doc/strscan/helper_methods.md). +See examples at [helper methods](helper_methods.md). ## The `StringScanner` \Object From 4161c78a9d0c84f5afe6eb40b2cea6266cd59987 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 28 Nov 2025 15:01:37 -0800 Subject: [PATCH 1458/2435] Add remembered flag to heap dump This should be less common than than many of the other flags, so should not inflate the heap too much. This is desirable because reducing the number of remembered objects will improve minor GC speeds. --- gc/default/default.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index 2d862b0b212489..54b93dc8d07cf6 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6197,7 +6197,7 @@ rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj) struct rb_gc_object_metadata_names { // Must be ID only ID ID_wb_protected, ID_age, ID_old, ID_uncollectible, ID_marking, - ID_marked, ID_pinned, ID_object_id, ID_shareable; + ID_marked, ID_pinned, ID_remembered, ID_object_id, ID_shareable; }; #define RB_GC_OBJECT_METADATA_ENTRY_COUNT (sizeof(struct rb_gc_object_metadata_names) / sizeof(ID)) @@ -6219,6 +6219,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) I(marking); I(marked); I(pinned); + I(remembered); I(object_id); I(shareable); #undef I @@ -6238,6 +6239,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) if (RVALUE_MARKING(objspace, obj)) SET_ENTRY(marking, Qtrue); if (RVALUE_MARKED(objspace, obj)) SET_ENTRY(marked, Qtrue); if (RVALUE_PINNED(objspace, obj)) SET_ENTRY(pinned, Qtrue); + if (RVALUE_REMEMBERED(objspace, obj)) SET_ENTRY(remembered, Qtrue); if (rb_obj_id_p(obj)) SET_ENTRY(object_id, rb_obj_id(obj)); if (FL_TEST(obj, FL_SHAREABLE)) SET_ENTRY(shareable, Qtrue); From 8ce78821cd8713d149ff27d9fbed38799e983349 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:18:16 -0500 Subject: [PATCH 1459/2435] ZJIT: Mark Integer#to_s as returning StringExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir/opt_tests.rs | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 56d4de280bff71..f7bf92b31aa6ba 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -242,6 +242,7 @@ pub fn init() -> Annotations { annotate!(rb_cInteger, "<", inline_integer_lt); annotate!(rb_cInteger, "<=", inline_integer_le); annotate!(rb_cInteger, "<<", inline_integer_lshift); + annotate!(rb_cInteger, "to_s", types::StringExact); annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", inline_thread_current, types::BasicObject, no_gc, leaf); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e8c8d50dbe9256..4faaa402d87543 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5900,6 +5900,56 @@ mod hir_opt_tests { "); } + #[test] + fn test_fixnum_to_s_returns_string() { + eval(r#" + def test(x) = x.to_s + test 5 + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, to_s@0x1008, cme:0x1010) + v21:Fixnum = GuardType v9, Fixnum + v22:StringExact = CCallVariadic Integer#to_s@0x1038, v21 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_bignum_to_s_returns_string() { + eval(r#" + def test(x) = x.to_s + test (2**65) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, to_s@0x1008, cme:0x1010) + v21:Integer = GuardType v9, Integer + v22:StringExact = CCallVariadic Integer#to_s@0x1038, v21 + CheckInterrupts + Return v22 + "); + } + #[test] fn test_array_aref_fixnum_literal() { eval(" From fd7d17abde4943edaefac7840f7258c8283d1d8e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:18:43 -0500 Subject: [PATCH 1460/2435] ZJIT: Don't use GuardTypeNot Use actual receiver type. This gives us better method lookup. --- zjit/src/hir.rs | 4 ++-- zjit/src/hir/opt_tests.rs | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e5c1f7a54293da..bab6d1c841e136 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2677,8 +2677,8 @@ impl Function { self.insn_types[guard.0] = self.infer_type(guard); self.make_equal_to(insn_id, guard); } else { - self.push_insn(block, Insn::GuardTypeNot { val, guard_type: types::String, state}); - let send_to_s = self.push_insn(block, Insn::SendWithoutBlock { recv: val, cd, args: vec![], state, reason: ObjToStringNotString }); + let recv = self.push_insn(block, Insn::GuardType { val, guard_type: Type::from_profiled_type(recv_type), state}); + let send_to_s = self.push_insn(block, Insn::SendWithoutBlock { recv, cd, args: vec![], state, reason: ObjToStringNotString }); self.make_equal_to(insn_id, send_to_s); } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 4faaa402d87543..029b3735f565ee 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4137,12 +4137,11 @@ mod hir_opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v26:BasicObject = GuardTypeNot v9, String + v26:ArrayExact = GuardType v9, ArrayExact PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallWithFrame v31, :Array#to_s@0x1040 - v19:String = AnyToString v9, str: v32 + v31:BasicObject = CCallWithFrame v26, :Array#to_s@0x1040 + v19:String = AnyToString v9, str: v31 v21:StringExact = StringConcat v13, v19 CheckInterrupts Return v21 @@ -5919,7 +5918,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, to_s@0x1008, cme:0x1010) v21:Fixnum = GuardType v9, Fixnum - v22:StringExact = CCallVariadic Integer#to_s@0x1038, v21 + v22:StringExact = CCallVariadic v21, :Integer#to_s@0x1038 CheckInterrupts Return v22 "); @@ -5944,7 +5943,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, to_s@0x1008, cme:0x1010) v21:Integer = GuardType v9, Integer - v22:StringExact = CCallVariadic Integer#to_s@0x1038, v21 + v22:StringExact = CCallVariadic v21, :Integer#to_s@0x1038 CheckInterrupts Return v22 "); From 91432a6bad7c52859e2920e780d578adcf4ac3f3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:19:15 -0500 Subject: [PATCH 1461/2435] ZJIT: Add late pass to fold AnyToString This otherwise would miss annotations of C methods. --- zjit/src/hir.rs | 5 +++++ zjit/src/hir/opt_tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bab6d1c841e136..4bbbbd150e444b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3399,6 +3399,11 @@ impl Function { // Don't bother re-inferring the type of val; we already know it. continue; } + Insn::AnyToString { str, .. } if self.is_a(str, types::String) => { + self.make_equal_to(insn_id, str); + // Don't bother re-inferring the type of str; we already know it. + continue; + } Insn::FixnumAdd { left, right, .. } => { self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) { (Some(l), Some(r)) => l.checked_add(r), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 029b3735f565ee..90675965c2f67a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5949,6 +5949,33 @@ mod hir_opt_tests { "); } + #[test] + fn test_fold_any_to_string_with_known_string_exact() { + eval(r##" + def test(x) = "#{x}" + test 123 + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v26:Fixnum = GuardType v9, Fixnum + PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018) + v30:StringExact = CCallVariadic Integer#to_s@0x1040, v26 + v21:StringExact = StringConcat v13, v30 + CheckInterrupts + Return v21 + "); + } + #[test] fn test_array_aref_fixnum_literal() { eval(" From 8aed31103874524395912fb7ffaee88b5e59a9c3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:24:45 -0500 Subject: [PATCH 1462/2435] ZJIT: Specialize String#<< with Fixnum Append a codepoint. --- jit.c | 2 ++ string.c | 4 ++-- yjit/bindgen/src/main.rs | 2 +- yjit/src/codegen.rs | 2 +- yjit/src/cruby.rs | 1 - yjit/src/cruby_bindings.inc.rs | 1 + zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 6 ++++++ zjit/src/cruby.rs | 1 - zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/cruby_methods.rs | 11 +++++++--- zjit/src/hir.rs | 14 ++++++++++++- zjit/src/hir/opt_tests.rs | 37 +++++++++++++++++++++++++++++----- 13 files changed, 68 insertions(+), 15 deletions(-) diff --git a/jit.c b/jit.c index 43c932e5a00ffa..dd28bd6ad02aef 100644 --- a/jit.c +++ b/jit.c @@ -765,3 +765,5 @@ rb_yarv_str_eql_internal(VALUE str1, VALUE str2) // We wrap this since it's static inline return rb_str_eql_internal(str1, str2); } + +void rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint); diff --git a/string.c b/string.c index 2dec3a11e61246..c794b36748e6e6 100644 --- a/string.c +++ b/string.c @@ -12580,9 +12580,9 @@ rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc) return rb_enc_interned_str(ptr, strlen(ptr), enc); } -#if USE_YJIT +#if USE_YJIT || USE_ZJIT void -rb_yjit_str_concat_codepoint(VALUE str, VALUE codepoint) +rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint) { if (RB_LIKELY(ENCODING_GET_INLINED(str) == rb_ascii8bit_encindex())) { ssize_t code = RB_NUM2SSIZE(codepoint); diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 67a461cd16d95c..9c28177a6054b2 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -273,7 +273,7 @@ fn main() { .allowlist_function("rb_yjit_sendish_sp_pops") .allowlist_function("rb_yjit_invokeblock_sp_pops") .allowlist_function("rb_yjit_set_exception_return") - .allowlist_function("rb_yjit_str_concat_codepoint") + .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_type("rstring_offsets") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index a426ad07737496..50762c64d3eceb 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6244,7 +6244,7 @@ fn jit_rb_str_concat_codepoint( guard_object_is_fixnum(jit, asm, codepoint, StackOpnd(0)); - asm.ccall(rb_yjit_str_concat_codepoint as *const u8, vec![recv, codepoint]); + asm.ccall(rb_jit_str_concat_codepoint as *const u8, vec![recv, codepoint]); // The receiver is the return value, so we only need to pop the codepoint argument off the stack. // We can reuse the receiver slot in the stack as the return value. diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index cfaf48c3f0b68e..5562f73be26dab 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -123,7 +123,6 @@ extern "C" { pub fn rb_float_new(d: f64) -> VALUE; pub fn rb_hash_empty_p(hash: VALUE) -> VALUE; - pub fn rb_yjit_str_concat_codepoint(str: VALUE, codepoint: VALUE); pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 66d4e5111d7bbd..04ca3494acf5a7 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1277,4 +1277,5 @@ extern "C" { ); pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; + pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); } diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index fe082504b82302..6659049242983b 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -278,6 +278,7 @@ fn main() { .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_jit_array_len") .allowlist_function("rb_jit_iseq_builtin_attrs") + .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_function("rb_zjit_iseq_inspect") .allowlist_function("rb_zjit_iseq_insn_set") .allowlist_function("rb_zjit_local_id") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f77b8cc4bf2ca6..6fc85664693fa3 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -368,6 +368,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), Insn::StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum(asm, opnd!(string), opnd!(index), opnd!(value)), Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), + Insn::StringAppendCodepoint { recv, other, state } => gen_string_append_codepoint(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), Insn::Param => unreachable!("block.insns should not have Insn::Param"), @@ -2495,6 +2496,11 @@ fn gen_string_append(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: asm_ccall!(asm, rb_str_buf_append, string, val) } +fn gen_string_append_codepoint(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: Opnd, state: &FrameState) -> Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + asm_ccall!(asm, rb_jit_str_concat_codepoint, string, val) +} + /// Generate a JIT entry that just increments exit_compilation_failure and exits fn gen_compile_error_counter(cb: &mut CodeBlock, compile_error: &CompileError) -> Result { let mut asm = Assembler::new(); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 9070cb38be5f42..72c44ccc6e412f 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -130,7 +130,6 @@ unsafe extern "C" { pub fn rb_float_new(d: f64) -> VALUE; pub fn rb_hash_empty_p(hash: VALUE) -> VALUE; - pub fn rb_yjit_str_concat_codepoint(str: VALUE, codepoint: VALUE); pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index aaecfa2f89769a..2256b7e32d3979 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2224,4 +2224,5 @@ unsafe extern "C" { end: *mut ::std::os::raw::c_void, ); pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; + pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index f7bf92b31aa6ba..f86d383876fbfc 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -425,10 +425,15 @@ fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir: let recv = fun.coerce_to(block, recv, types::StringExact, state); let other = fun.coerce_to(block, other, types::String, state); let _ = fun.push_insn(block, hir::Insn::StringAppend { recv, other, state }); - Some(recv) - } else { - None + return Some(recv); } + if fun.likely_a(recv, types::StringExact, state) && fun.likely_a(other, types::Fixnum, state) { + let recv = fun.coerce_to(block, recv, types::StringExact, state); + let other = fun.coerce_to(block, other, types::Fixnum, state); + let _ = fun.push_insn(block, hir::Insn::StringAppendCodepoint { recv, other, state }); + return Some(recv); + } + None } fn inline_string_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4bbbbd150e444b..6604c52a8276e6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -652,6 +652,7 @@ pub enum Insn { StringGetbyteFixnum { string: InsnId, index: InsnId }, StringSetbyteFixnum { string: InsnId, index: InsnId, value: InsnId }, StringAppend { recv: InsnId, other: InsnId, state: InsnId }, + StringAppendCodepoint { recv: InsnId, other: InsnId, state: InsnId }, /// Combine count stack values into a regexp ToRegexp { opt: usize, values: Vec, state: InsnId }, @@ -1124,6 +1125,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::StringAppend { recv, other, .. } => { write!(f, "StringAppend {recv}, {other}") } + Insn::StringAppendCodepoint { recv, other, .. } => { + write!(f, "StringAppendCodepoint {recv}, {other}") + } Insn::ToRegexp { values, opt, .. } => { write!(f, "ToRegexp")?; let mut prefix = " "; @@ -1814,6 +1818,7 @@ impl Function { &StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) }, &StringSetbyteFixnum { string, index, value } => StringSetbyteFixnum { string: find!(string), index: find!(index), value: find!(value) }, &StringAppend { recv, other, state } => StringAppend { recv: find!(recv), other: find!(other), state: find!(state) }, + &StringAppendCodepoint { recv, other, state } => StringAppendCodepoint { recv: find!(recv), other: find!(other), state: find!(state) }, &ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state }, &Test { val } => Test { val: find!(val) }, &IsNil { val } => IsNil { val: find!(val) }, @@ -2032,6 +2037,7 @@ impl Function { Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass), Insn::StringSetbyteFixnum { .. } => types::Fixnum, Insn::StringAppend { .. } => types::StringExact, + Insn::StringAppendCodepoint { .. } => types::StringExact, Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, @@ -3564,7 +3570,9 @@ impl Function { worklist.push_back(index); worklist.push_back(value); } - &Insn::StringAppend { recv, other, state } => { + &Insn::StringAppend { recv, other, state } + | &Insn::StringAppendCodepoint { recv, other, state } + => { worklist.push_back(recv); worklist.push_back(other); worklist.push_back(state); @@ -4328,6 +4336,10 @@ impl Function { self.assert_subtype(insn_id, recv, types::StringExact)?; self.assert_subtype(insn_id, other, types::String) } + Insn::StringAppendCodepoint { recv, other, .. } => { + self.assert_subtype(insn_id, recv, types::StringExact)?; + self.assert_subtype(insn_id, other, types::Fixnum) + } // Instructions with Array operands Insn::ArrayDup { val, .. } => self.assert_subtype(insn_id, val, types::ArrayExact), Insn::ArrayExtend { left, right, .. } => { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 90675965c2f67a..60f5814973c482 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5969,7 +5969,7 @@ mod hir_opt_tests { v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v26:Fixnum = GuardType v9, Fixnum PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018) - v30:StringExact = CCallVariadic Integer#to_s@0x1040, v26 + v30:StringExact = CCallVariadic v26, :Integer#to_s@0x1040 v21:StringExact = StringConcat v13, v30 CheckInterrupts Return v21 @@ -6854,9 +6854,8 @@ mod hir_opt_tests { "); } - // TODO: This should be inlined just as in the interpreter #[test] - fn test_optimize_string_append_non_string() { + fn test_optimize_string_append_codepoint() { eval(r#" def test(x, y) = x << y test("iron", 4) @@ -6876,9 +6875,11 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v27:StringExact = GuardType v11, StringExact - v28:BasicObject = CCallWithFrame v27, :String#<<@0x1038, v12 + v28:Fixnum = GuardType v12, Fixnum + v29:StringExact = StringAppendCodepoint v27, v28 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v28 + Return v27 "); } @@ -6942,6 +6943,32 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_optimize_string_append_non_string() { + eval(r#" + def test = "iron" << :a + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:StringExact = StringCopy v10 + v13:StaticSymbol[:a] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, <<@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v24:BasicObject = CCallWithFrame v11, :String#<<@0x1048, v13 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_dont_optimize_when_passing_too_many_args() { eval(r#" From a25196395e7502e4d6faad0856c697690d8a202e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:44:15 -0500 Subject: [PATCH 1463/2435] Add BOP_GTGT This will help JITs (and maybe later the interpreter) optimize Integer#>>. --- internal/basic_operators.h | 1 + vm.c | 1 + yjit/src/cruby_bindings.inc.rs | 33 +++++++++++++++++---------------- zjit/src/cruby_bindings.inc.rs | 33 +++++++++++++++++---------------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/internal/basic_operators.h b/internal/basic_operators.h index 5dc8d7fe8d69fb..493d2fa7f733d7 100644 --- a/internal/basic_operators.h +++ b/internal/basic_operators.h @@ -24,6 +24,7 @@ enum ruby_basic_operators { BOP_SUCC, BOP_GT, BOP_GE, + BOP_GTGT, BOP_NOT, BOP_NEQ, BOP_MATCH, diff --git a/vm.c b/vm.c index 1a8a6d059b569b..b15ee0ba236b57 100644 --- a/vm.c +++ b/vm.c @@ -2444,6 +2444,7 @@ vm_init_redefined_flag(void) OP(GT, GT), (C(Integer), C(Float)); OP(GE, GE), (C(Integer), C(Float)); OP(LTLT, LTLT), (C(String), C(Array)); + OP(GTGT, GTGT), (C(Integer)); OP(AREF, AREF), (C(Array), C(Hash), C(Integer)); OP(ASET, ASET), (C(Array), C(Hash)); OP(Length, LENGTH), (C(Array), C(String), C(Hash)); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 04ca3494acf5a7..6efef9c55c39c4 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -330,22 +330,23 @@ pub const BOP_NIL_P: ruby_basic_operators = 15; pub const BOP_SUCC: ruby_basic_operators = 16; pub const BOP_GT: ruby_basic_operators = 17; pub const BOP_GE: ruby_basic_operators = 18; -pub const BOP_NOT: ruby_basic_operators = 19; -pub const BOP_NEQ: ruby_basic_operators = 20; -pub const BOP_MATCH: ruby_basic_operators = 21; -pub const BOP_FREEZE: ruby_basic_operators = 22; -pub const BOP_UMINUS: ruby_basic_operators = 23; -pub const BOP_MAX: ruby_basic_operators = 24; -pub const BOP_MIN: ruby_basic_operators = 25; -pub const BOP_HASH: ruby_basic_operators = 26; -pub const BOP_CALL: ruby_basic_operators = 27; -pub const BOP_AND: ruby_basic_operators = 28; -pub const BOP_OR: ruby_basic_operators = 29; -pub const BOP_CMP: ruby_basic_operators = 30; -pub const BOP_DEFAULT: ruby_basic_operators = 31; -pub const BOP_PACK: ruby_basic_operators = 32; -pub const BOP_INCLUDE_P: ruby_basic_operators = 33; -pub const BOP_LAST_: ruby_basic_operators = 34; +pub const BOP_GTGT: ruby_basic_operators = 19; +pub const BOP_NOT: ruby_basic_operators = 20; +pub const BOP_NEQ: ruby_basic_operators = 21; +pub const BOP_MATCH: ruby_basic_operators = 22; +pub const BOP_FREEZE: ruby_basic_operators = 23; +pub const BOP_UMINUS: ruby_basic_operators = 24; +pub const BOP_MAX: ruby_basic_operators = 25; +pub const BOP_MIN: ruby_basic_operators = 26; +pub const BOP_HASH: ruby_basic_operators = 27; +pub const BOP_CALL: ruby_basic_operators = 28; +pub const BOP_AND: ruby_basic_operators = 29; +pub const BOP_OR: ruby_basic_operators = 30; +pub const BOP_CMP: ruby_basic_operators = 31; +pub const BOP_DEFAULT: ruby_basic_operators = 32; +pub const BOP_PACK: ruby_basic_operators = 33; +pub const BOP_INCLUDE_P: ruby_basic_operators = 34; +pub const BOP_LAST_: ruby_basic_operators = 35; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; pub const imemo_env: imemo_type = 0; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2256b7e32d3979..fb329a07166914 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -392,22 +392,23 @@ pub const BOP_NIL_P: ruby_basic_operators = 15; pub const BOP_SUCC: ruby_basic_operators = 16; pub const BOP_GT: ruby_basic_operators = 17; pub const BOP_GE: ruby_basic_operators = 18; -pub const BOP_NOT: ruby_basic_operators = 19; -pub const BOP_NEQ: ruby_basic_operators = 20; -pub const BOP_MATCH: ruby_basic_operators = 21; -pub const BOP_FREEZE: ruby_basic_operators = 22; -pub const BOP_UMINUS: ruby_basic_operators = 23; -pub const BOP_MAX: ruby_basic_operators = 24; -pub const BOP_MIN: ruby_basic_operators = 25; -pub const BOP_HASH: ruby_basic_operators = 26; -pub const BOP_CALL: ruby_basic_operators = 27; -pub const BOP_AND: ruby_basic_operators = 28; -pub const BOP_OR: ruby_basic_operators = 29; -pub const BOP_CMP: ruby_basic_operators = 30; -pub const BOP_DEFAULT: ruby_basic_operators = 31; -pub const BOP_PACK: ruby_basic_operators = 32; -pub const BOP_INCLUDE_P: ruby_basic_operators = 33; -pub const BOP_LAST_: ruby_basic_operators = 34; +pub const BOP_GTGT: ruby_basic_operators = 19; +pub const BOP_NOT: ruby_basic_operators = 20; +pub const BOP_NEQ: ruby_basic_operators = 21; +pub const BOP_MATCH: ruby_basic_operators = 22; +pub const BOP_FREEZE: ruby_basic_operators = 23; +pub const BOP_UMINUS: ruby_basic_operators = 24; +pub const BOP_MAX: ruby_basic_operators = 25; +pub const BOP_MIN: ruby_basic_operators = 26; +pub const BOP_HASH: ruby_basic_operators = 27; +pub const BOP_CALL: ruby_basic_operators = 28; +pub const BOP_AND: ruby_basic_operators = 29; +pub const BOP_OR: ruby_basic_operators = 30; +pub const BOP_CMP: ruby_basic_operators = 31; +pub const BOP_DEFAULT: ruby_basic_operators = 32; +pub const BOP_PACK: ruby_basic_operators = 33; +pub const BOP_INCLUDE_P: ruby_basic_operators = 34; +pub const BOP_LAST_: ruby_basic_operators = 35; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; #[repr(C)] From 6db83a00a4272eb1089d67da83e1cd9d4e10227b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 22:44:53 -0500 Subject: [PATCH 1464/2435] ZJIT: Specialize Integer#>> Same as Integer#>>. Also add more strict type checks for both Integer#>> and Integer#<<. --- zjit/src/codegen.rs | 15 ++++++ zjit/src/cruby_methods.rs | 11 ++++ zjit/src/hir.rs | 22 +++++++- zjit/src/hir/opt_tests.rs | 105 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6fc85664693fa3..df9a9299cf2d80 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -409,6 +409,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio let shift_amount = function.type_of(right).fixnum_value().unwrap() as u64; gen_fixnum_lshift(jit, asm, opnd!(left), shift_amount, &function.frame_state(state)) } + &Insn::FixnumRShift { left, right } => { + // We only create FixnumRShift when we know the shift amount statically and it's in [0, + // 63]. + let shift_amount = function.type_of(right).fixnum_value().unwrap() as u64; + gen_fixnum_rshift(asm, opnd!(left), shift_amount) + } &Insn::FixnumMod { left, right, state } => gen_fixnum_mod(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), @@ -1754,6 +1760,15 @@ fn gen_fixnum_lshift(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, s out_val } +/// Compile Fixnum >> Fixnum +fn gen_fixnum_rshift(asm: &mut Assembler, left: lir::Opnd, shift_amount: u64) -> lir::Opnd { + // Shift amount is known statically to be in the range [0, 63] + assert!(shift_amount < 64); + let result = asm.rshift(left, shift_amount.into()); + // Re-tag the output value + asm.or(result, 1.into()) +} + fn gen_fixnum_mod(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { // Check for left % 0, which raises ZeroDivisionError asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index f86d383876fbfc..f84882101cc271 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -242,6 +242,7 @@ pub fn init() -> Annotations { annotate!(rb_cInteger, "<", inline_integer_lt); annotate!(rb_cInteger, "<=", inline_integer_le); annotate!(rb_cInteger, "<<", inline_integer_lshift); + annotate!(rb_cInteger, ">>", inline_integer_rshift); annotate!(rb_cInteger, "to_s", types::StringExact); annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; @@ -575,6 +576,16 @@ fn inline_integer_lshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumLShift { left, right, state }, BOP_LTLT, recv, other, state) } +fn inline_integer_rshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + // Only convert to FixnumLShift if we know the shift amount is known at compile-time and could + // plausibly create a fixnum. + let Some(other_value) = fun.type_of(other).fixnum_value() else { return None; }; + // TODO(max): If other_value > 63, rewrite to constant zero. + if other_value < 0 || other_value > 63 { return None; } + try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumRShift { left, right }, BOP_GTGT, recv, other, state) +} + fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[other] = args else { return None; }; let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6604c52a8276e6..a69628b8690d67 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -891,6 +891,7 @@ pub enum Insn { FixnumOr { left: InsnId, right: InsnId }, FixnumXor { left: InsnId, right: InsnId }, FixnumLShift { left: InsnId, right: InsnId, state: InsnId }, + FixnumRShift { left: InsnId, right: InsnId }, // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined ObjToString { val: InsnId, cd: *const rb_call_data, state: InsnId }, @@ -989,6 +990,7 @@ impl Insn { Insn::FixnumOr { .. } => false, Insn::FixnumXor { .. } => false, Insn::FixnumLShift { .. } => false, + Insn::FixnumRShift { .. } => false, Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::LoadPC => false, @@ -1233,6 +1235,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumOr { left, right, .. } => { write!(f, "FixnumOr {left}, {right}") }, Insn::FixnumXor { left, right, .. } => { write!(f, "FixnumXor {left}, {right}") }, Insn::FixnumLShift { left, right, .. } => { write!(f, "FixnumLShift {left}, {right}") }, + Insn::FixnumRShift { left, right, .. } => { write!(f, "FixnumRShift {left}, {right}") }, Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardTypeNot { val, guard_type, .. } => { write!(f, "GuardTypeNot {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, @@ -1854,6 +1857,7 @@ impl Function { &FixnumOr { left, right } => FixnumOr { left: find!(left), right: find!(right) }, &FixnumXor { left, right } => FixnumXor { left: find!(left), right: find!(right) }, &FixnumLShift { left, right, state } => FixnumLShift { left: find!(left), right: find!(right), state }, + &FixnumRShift { left, right } => FixnumRShift { left: find!(left), right: find!(right) }, &ObjToString { val, cd, state } => ObjToString { val: find!(val), cd, @@ -2076,6 +2080,7 @@ impl Function { Insn::FixnumOr { .. } => types::Fixnum, Insn::FixnumXor { .. } => types::Fixnum, Insn::FixnumLShift { .. } => types::Fixnum, + Insn::FixnumRShift { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, Insn::SendWithoutBlock { .. } => types::BasicObject, Insn::SendWithoutBlockDirect { .. } => types::BasicObject, @@ -3639,6 +3644,7 @@ impl Function { | &Insn::FixnumAnd { left, right } | &Insn::FixnumOr { left, right } | &Insn::FixnumXor { left, right } + | &Insn::FixnumRShift { left, right } | &Insn::IsBitEqual { left, right } | &Insn::IsBitNotEqual { left, right } => { @@ -4403,12 +4409,26 @@ impl Function { | Insn::FixnumAnd { left, right } | Insn::FixnumOr { left, right } | Insn::FixnumXor { left, right } - | Insn::FixnumLShift { left, right, .. } | Insn::NewRangeFixnum { low: left, high: right, .. } => { self.assert_subtype(insn_id, left, types::Fixnum)?; self.assert_subtype(insn_id, right, types::Fixnum) } + Insn::FixnumLShift { left, right, .. } + | Insn::FixnumRShift { left, right, .. } => { + self.assert_subtype(insn_id, left, types::Fixnum)?; + self.assert_subtype(insn_id, right, types::Fixnum)?; + let Some(obj) = self.type_of(right).fixnum_value() else { + return Err(ValidationError::MismatchedOperandType(insn_id, right, "".into(), "".into())); + }; + if obj < 0 { + return Err(ValidationError::MismatchedOperandType(insn_id, right, "".into(), format!("{obj}"))); + } + if obj > 63 { + return Err(ValidationError::MismatchedOperandType(insn_id, right, "".into(), format!("{obj}"))); + } + Ok(()) + } Insn::GuardBitEquals { val, expected, .. } => { match expected { Const::Value(_) => self.assert_subtype(insn_id, val, types::RubyValue), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 60f5814973c482..610986d62fe07f 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6825,6 +6825,111 @@ mod hir_opt_tests { "); } + #[test] + fn test_inline_integer_gtgt_with_known_fixnum() { + eval(" + def test(x) = x >> 5 + test(4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(Integer@0x1000, >>@0x1008, cme:0x1010) + v23:Fixnum = GuardType v9, Fixnum + v24:Fixnum = FixnumRShift v23, v14 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_inline_integer_gtgt_with_negative() { + eval(" + def test(x) = x >> -5 + test(4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[-5] = Const Value(-5) + PatchPoint MethodRedefined(Integer@0x1000, >>@0x1008, cme:0x1010) + v23:Fixnum = GuardType v9, Fixnum + v24:BasicObject = CCallWithFrame v23, :Integer#>>@0x1038, v14 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_inline_integer_gtgt_with_out_of_range() { + eval(" + def test(x) = x >> 64 + test(4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[64] = Const Value(64) + PatchPoint MethodRedefined(Integer@0x1000, >>@0x1008, cme:0x1010) + v23:Fixnum = GuardType v9, Fixnum + v24:BasicObject = CCallWithFrame v23, :Integer#>>@0x1038, v14 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_inline_integer_gtgt_with_unknown_fixnum() { + eval(" + def test(x, y) = x >> y + test(4, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, >>@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:BasicObject = CCallWithFrame v25, :Integer#>>@0x1038, v12 + CheckInterrupts + Return v26 + "); + } + #[test] fn test_optimize_string_append() { eval(r#" From 74e9f717200106be553e954e8ac354b54acf1c60 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 23:18:13 -0500 Subject: [PATCH 1465/2435] ZJIT: Mark String#ascii_only? as leaf --- zjit/src/cruby_methods.rs | 4 ++++ zjit/src/hir/opt_tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index f84882101cc271..155979d105ae16 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -206,6 +206,10 @@ pub fn init() -> Annotations { annotate!(rb_cString, "empty?", inline_string_empty_p, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cString, "==", inline_string_eq); + // Not elidable; has a side effect of setting the encoding if ENC_CODERANGE_UNKNOWN. + // TOOD(max): Turn this into a load/compare. Will need to side-exit or do the full call if + // ENC_CODERANGE_UNKNOWN. + annotate!(rb_cString, "ascii_only?", types::BoolExact, no_gc, leaf); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", inline_module_eqq, types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 610986d62fe07f..60135b6ac9e969 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -7100,6 +7100,33 @@ mod hir_opt_tests { "); } + #[test] + fn test_optimize_string_ascii_only_p() { + eval(r#" + def test(x) = x.ascii_only? + test("iron") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ascii_only?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v22:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v24:BoolExact = CCall v22, :String#ascii_only?@0x1038 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_dont_optimize_when_passing_too_few_args() { eval(r#" From 985a4d977f1c1a5051c88b0c2f8af8913bd93008 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 23:30:05 -0500 Subject: [PATCH 1466/2435] ZJIT: Open-code String#getbyte Don't call a C function. --- zjit/src/codegen.rs | 33 +++++++++++++++++++++++++++++---- zjit/src/cruby_methods.rs | 16 +++++++++++++++- zjit/src/hir.rs | 18 +++++++++--------- zjit/src/hir/opt_tests.rs | 14 ++++++++++++-- 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index df9a9299cf2d80..ac1c65b26e1c13 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -365,7 +365,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // If it happens we abort the compilation for now Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state), Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)), - &Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)), + &Insn::StringGetbyte { string, index } => gen_string_getbyte(asm, opnd!(string), opnd!(index)), Insn::StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum(asm, opnd!(string), opnd!(index), opnd!(value)), Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), Insn::StringAppendCodepoint { recv, other, state } => gen_string_append_codepoint(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)), @@ -2496,9 +2496,34 @@ fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec result } -fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) -> Opnd { - // TODO(max): Open-code rb_str_getbyte to avoid a call - asm_ccall!(asm, rb_str_getbyte, string, index) +// Generate RSTRING_PTR +fn get_string_ptr(asm: &mut Assembler, string: Opnd) -> Opnd { + asm_comment!(asm, "get string pointer for embedded or heap"); + let string = asm.load(string); + let flags = Opnd::mem(VALUE_BITS, string, RUBY_OFFSET_RBASIC_FLAGS); + asm.test(flags, (RSTRING_NOEMBED as u64).into()); + let heap_ptr = asm.load(Opnd::mem( + usize::BITS as u8, + string, + RUBY_OFFSET_RSTRING_AS_HEAP_PTR, + )); + // Load the address of the embedded array + // (struct RString *)(obj)->as.ary + let ary = asm.lea(Opnd::mem(VALUE_BITS, string, RUBY_OFFSET_RSTRING_AS_ARY)); + asm.csel_nz(heap_ptr, ary) +} + +fn gen_string_getbyte(asm: &mut Assembler, string: Opnd, index: Opnd) -> Opnd { + let string_ptr = get_string_ptr(asm, string); + // TODO(max): Use SIB indexing here once the backend supports it + let string_ptr = asm.add(string_ptr, index); + let byte = asm.load(Opnd::mem(8, string_ptr, 0)); + // Zero-extend the byte to 64 bits + let byte = byte.with_num_bits(64); + let byte = asm.and(byte, 0xFF.into()); + // Tag the byte + let byte = asm.lshift(byte, Opnd::UImm(1)); + asm.or(byte, Opnd::UImm(1)) } fn gen_string_setbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd, value: Opnd) -> Opnd { diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 155979d105ae16..0f5c992b88f892 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -376,7 +376,21 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir // String#getbyte with a Fixnum is leaf and nogc; otherwise it may run arbitrary Ruby code // when converting the index to a C integer. let index = fun.coerce_to(block, index, types::Fixnum, state); - let result = fun.push_insn(block, hir::Insn::StringGetbyteFixnum { string: recv, index }); + let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); + let len = fun.push_insn(block, hir::Insn::LoadField { + recv, + id: ID!(len), + offset: RUBY_OFFSET_RSTRING_LEN as i32, + return_type: types::CInt64, + }); + // TODO(max): Find a way to mark these guards as not needed for correctness... as in, once + // the data dependency is gone (say, the StringGetbyte is elided), they can also be elided. + // + // This is unlike most other guards. + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); + let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); + let result = fun.push_insn(block, hir::Insn::StringGetbyte { string: recv, index: unboxed_index }); return Some(result); } None diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a69628b8690d67..49f139b45e76a7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -649,7 +649,7 @@ pub enum Insn { StringIntern { val: InsnId, state: InsnId }, StringConcat { strings: Vec, state: InsnId }, /// Call rb_str_getbyte with known-Fixnum index - StringGetbyteFixnum { string: InsnId, index: InsnId }, + StringGetbyte { string: InsnId, index: InsnId }, StringSetbyteFixnum { string: InsnId, index: InsnId, value: InsnId }, StringAppend { recv: InsnId, other: InsnId, state: InsnId }, StringAppendCodepoint { recv: InsnId, other: InsnId, state: InsnId }, @@ -1004,7 +1004,7 @@ impl Insn { // but we don't have type information here in `impl Insn`. See rb_range_new(). Insn::NewRange { .. } => true, Insn::NewRangeFixnum { .. } => false, - Insn::StringGetbyteFixnum { .. } => false, + Insn::StringGetbyte { .. } => false, Insn::IsBlockGiven => false, Insn::BoxFixnum { .. } => false, Insn::BoxBool { .. } => false, @@ -1118,8 +1118,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) } - Insn::StringGetbyteFixnum { string, index, .. } => { - write!(f, "StringGetbyteFixnum {string}, {index}") + Insn::StringGetbyte { string, index, .. } => { + write!(f, "StringGetbyte {string}, {index}") } Insn::StringSetbyteFixnum { string, index, value, .. } => { write!(f, "StringSetbyteFixnum {string}, {index}, {value}") @@ -1818,7 +1818,7 @@ impl Function { &StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state }, &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, &StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) }, - &StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) }, + &StringGetbyte { string, index } => StringGetbyte { string: find!(string), index: find!(index) }, &StringSetbyteFixnum { string, index, value } => StringSetbyteFixnum { string: find!(string), index: find!(index), value: find!(value) }, &StringAppend { recv, other, state } => StringAppend { recv: find!(recv), other: find!(other), state: find!(state) }, &StringAppendCodepoint { recv, other, state } => StringAppendCodepoint { recv: find!(recv), other: find!(other), state: find!(state) }, @@ -2038,7 +2038,7 @@ impl Function { Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, - Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass), + Insn::StringGetbyte { .. } => types::Fixnum, Insn::StringSetbyteFixnum { .. } => types::Fixnum, Insn::StringAppend { .. } => types::StringExact, Insn::StringAppendCodepoint { .. } => types::StringExact, @@ -3566,7 +3566,7 @@ impl Function { worklist.extend(strings); worklist.push_back(state); } - &Insn::StringGetbyteFixnum { string, index } => { + &Insn::StringGetbyte { string, index } => { worklist.push_back(string); worklist.push_back(index); } @@ -4450,9 +4450,9 @@ impl Function { self.assert_subtype(insn_id, left, types::CInt64)?; self.assert_subtype(insn_id, right, types::CInt64) }, - Insn::StringGetbyteFixnum { string, index } => { + Insn::StringGetbyte { string, index } => { self.assert_subtype(insn_id, string, types::String)?; - self.assert_subtype(insn_id, index, types::Fixnum) + self.assert_subtype(insn_id, index, types::CInt64) }, Insn::StringSetbyteFixnum { string, index, value } => { self.assert_subtype(insn_id, string, types::String)?; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 60135b6ac9e969..bdc26f758e107e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6451,10 +6451,15 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v26:StringExact = GuardType v11, StringExact v27:Fixnum = GuardType v12, Fixnum - v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 + v28:CInt64 = UnboxFixnum v27 + v29:CInt64 = LoadField v26, :len@0x1038 + v30:CInt64 = GuardLess v28, v29 + v31:CInt64[0] = Const CInt64(0) + v32:CInt64 = GuardGreaterEq v30, v31 + v33:Fixnum = StringGetbyte v26, v30 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v28 + Return v33 "); } @@ -6483,6 +6488,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(String@0x1000) v30:StringExact = GuardType v11, StringExact v31:Fixnum = GuardType v12, Fixnum + v32:CInt64 = UnboxFixnum v31 + v33:CInt64 = LoadField v30, :len@0x1038 + v34:CInt64 = GuardLess v32, v33 + v35:CInt64[0] = Const CInt64(0) + v36:CInt64 = GuardGreaterEq v34, v35 IncrCounter inline_cfunc_optimized_send_count v22:Fixnum[5] = Const Value(5) CheckInterrupts From b9f1976f2334b4d9f55be5607de408eb7973c188 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 24 Nov 2025 23:37:24 -0500 Subject: [PATCH 1467/2435] ZJIT: Add HIR test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER --- zjit/src/hir/tests.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 314eb7777cbf6c..79ba1d30c53443 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2121,7 +2121,42 @@ pub mod hir_build_tests { "); } - // TODO(max): Add a test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER + #[test] + fn test_opt_newarray_send_pack_buffer() { + eval(r#" + def test(a,b) + sum = a+b + buf = "" + [a,b].pack 'C', buffer: buf + buf + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v30:StringExact = StringCopy v29 + v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v37:StringExact = StringCopy v36 + v39:BasicObject = GetLocal l0, EP@3 + SideExit UnhandledNewarraySend(PACK_BUFFER) + "); + } #[test] fn test_opt_newarray_send_include_p() { From d0bb505a04bcd6a3d86c01d7c402c1f6205e69b4 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 1 Dec 2025 11:57:11 -0800 Subject: [PATCH 1468/2435] ZJIT: Split Lea memory reads on x86_64 --- zjit/src/backend/x86_64/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index d17286f51fda25..a9bd57f368c59b 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -559,7 +559,8 @@ impl Assembler { asm.store(mem_out, SCRATCH0_OPND); } } - Insn::Lea { out, .. } => { + Insn::Lea { opnd, out } => { + *opnd = split_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state); let mem_out = split_memory_write(out, SCRATCH0_OPND); asm.push_insn(insn); if let Some(mem_out) = mem_out { @@ -1804,4 +1805,20 @@ mod tests { "); assert_snapshot!(cb.hexdump(), @"4c8b55f84c8b5df04d8b5b024d0f441a4c895df8"); } + + #[test] + fn test_lea_split_memory_read() { + let (mut asm, mut cb) = setup_asm(); + + let opnd = Opnd::Mem(Mem { base: MemBase::Stack { stack_idx: 0, num_bits: 64 }, disp: 0, num_bits: 64 }); + let _ = asm.lea(opnd); + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @" + 0x0: mov r11, qword ptr [rbp - 8] + 0x4: lea r11, [r11] + 0x7: mov qword ptr [rbp - 8], r11 + "); + assert_snapshot!(cb.hexdump(), @"4c8b5df84d8d1b4c895df8"); + } } From 2dfb8149d58694cd578dbe2222640499ac021231 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 18:26:09 +0900 Subject: [PATCH 1469/2435] Clean generated transcoders --- enc/Makefile.in | 1 + enc/depend | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/enc/Makefile.in b/enc/Makefile.in index 17888f8f9b5e10..2a3c45169fbb22 100644 --- a/enc/Makefile.in +++ b/enc/Makefile.in @@ -31,6 +31,7 @@ BUILTIN_ENCS = enc/ascii.c enc/us_ascii.c\ enc/unicode.c enc/utf_8.c BUILTIN_TRANSES = enc/trans/newline.trans +BUILTIN_TRANS_CSRCS = $(BUILTIN_TRANSES:.trans=.c) RUBY_SO_NAME = @RUBY_SO_NAME@ LIBRUBY = @LIBRUBY@ diff --git a/enc/depend b/enc/depend index a458b63887e858..4bf97dc880f195 100644 --- a/enc/depend +++ b/enc/depend @@ -157,15 +157,15 @@ clean: % end % unless inplace $(Q)$(RM) enc/unicode/*/casefold.h enc/unicode/*/name2ctype.h - $(Q)$(RM) enc/jis/props.h - -$(Q)$(RMDIR) enc/unicode<%=ignore_error%> + $(Q)$(RM) enc/trans/newline.c enc/jis/props.h + -$(Q)$(RMDIR) enc/trnas enc/unicode<%=ignore_error%> % end % workdirs.reverse_each do|d| -$(Q)$(RMDIR) <%=pathrep[d]%><%=ignore_error%> % end clean-srcs: - $(Q)$(RM) <%=pathrep['$(TRANSCSRCS)']%> + $(Q)$(RM) <%=pathrep['$(TRANSCSRCS)']%> <%=pathrep['$(BUILTIN_TRANS_CSRCS)']%> -$(Q)$(RMDIR) <%=pathrep['enc/trans']%><%=ignore_error%> $(Q)$(RM) enc/unicode/*/casefold.h enc/unicode/*/name2ctype.h $(Q)$(RM) enc/jis/props.h From 07ea9a38097c088efd6c2872f2a563dbc69a544a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 25 Nov 2025 23:26:02 +0100 Subject: [PATCH 1470/2435] ZJIT: Optimize GetIvar for non-T_OBJECT * All Invariant::SingleRactorMode PatchPoint are replaced by assume_single_ractor_mode() to fix https://github.com/Shopify/ruby/issues/875 for SingleRactorMode patchpoints. --- test/ruby/test_zjit.rb | 30 ++++++- zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 5 ++ zjit/src/cruby.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 87 +++++++++++++------- zjit/src/hir/opt_tests.rs | 142 +++++++++++++++++++++++++++++++++ 7 files changed, 236 insertions(+), 31 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index d821d8ad5cca69..6609bb2461c31d 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -3017,7 +3017,35 @@ def test test Ractor.new { test }.value - } + }, call_threshold: 2 + end + + def test_ivar_get_with_already_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.set_bar + @bar = [] # needs to be a ractor unshareable object + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + r = Ractor.new { + Ractor.receive + Foo.bar + } + + Foo.bar + Foo.bar + + r << :go + r.value + }, call_threshold: 2 end def test_ivar_set_with_multi_ractor_mode diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 6659049242983b..0860c073cd17ed 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -326,6 +326,7 @@ fn main() { .allowlist_function("rb_attr_get") .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") + .allowlist_function("rb_ivar_get_at_no_ractor_check") .allowlist_function("rb_ivar_set") .allowlist_function("rb_mod_name") .allowlist_var("rb_vm_insn_count") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index ac1c65b26e1c13..bb19d7d8209ff8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -349,6 +349,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Const { val: Const::Value(val) } => gen_const_value(val), &Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val), &Insn::Const { val: Const::CInt64(val) } => gen_const_long(val), + &Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val), Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), @@ -1154,6 +1155,10 @@ fn gen_const_long(val: i64) -> lir::Opnd { Opnd::Imm(val) } +fn gen_const_uint16(val: u16) -> lir::Opnd { + Opnd::UImm(val as u64) +} + /// Compile a basic block argument fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd { // Allocate a register or a stack slot diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 72c44ccc6e412f..9ed3a1abf79ca2 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1381,6 +1381,7 @@ pub(crate) mod ids { name: _as_heap name: thread_ptr name: self_ content: b"self" + name: rb_ivar_get_at_no_ractor_check } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index fb329a07166914..7bd392563987a3 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2034,6 +2034,7 @@ unsafe extern "C" { pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t; + pub fn rb_ivar_get_at_no_ractor_check(obj: VALUE, index: attr_index_t) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 49f139b45e76a7..5ede64d42c2d80 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1742,6 +1742,15 @@ impl Function { self.blocks.len() } + pub fn assume_single_ractor_mode(&mut self, block: BlockId, state: InsnId) -> bool { + if unsafe { rb_jit_multi_ractor_p() } { + false + } else { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); + true + } + } + /// Return a copy of the instruction where the instruction and its operands have been read from /// the union-find table (to find the current most-optimized version of this instruction). See /// [`UnionFind`] for more. @@ -2377,6 +2386,17 @@ impl Function { } } + fn is_metaclass(&self, object: VALUE) -> bool { + unsafe { + if RB_TYPE_P(object, RUBY_T_CLASS) && rb_zjit_singleton_class_p(object) { + let attached = rb_class_attached_object(object); + RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) + } else { + false + } + } + } + /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. /// Also try and inline constant caches, specialize object allocations, and more. @@ -2483,9 +2503,9 @@ impl Function { // Patch points: // Check for "defined with an un-shareable Proc in a different Ractor" - if !procv.shareable_p() { + if !procv.shareable_p() && !self.assume_single_ractor_mode(block, state) { // TODO(alan): Turn this into a ractor belonging guard to work better in multi ractor mode. - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); + self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); if klass.instance_can_have_singleton_class() { @@ -2498,6 +2518,12 @@ impl Function { let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state }); self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { + // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. + // We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode. + if self.is_metaclass(klass) && !self.assume_single_ractor_mode(block, state) { + self.push_insn_id(block, insn_id); continue; + } + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); if klass.instance_can_have_singleton_class() { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); @@ -2507,31 +2533,21 @@ impl Function { } let id = unsafe { get_cme_def_body_attr_id(cme) }; - // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. - // We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode. - if unsafe { rb_zjit_singleton_class_p(klass) } { - let attached = unsafe { rb_class_attached_object(klass) }; - if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); - } - } let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, ic: std::ptr::null(), state }); self.make_equal_to(insn_id, getivar); } else if let (VM_METHOD_TYPE_ATTRSET, &[val]) = (def_type, args.as_slice()) { + // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. + // We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode. + if self.is_metaclass(klass) && !self.assume_single_ractor_mode(block, state) { + self.push_insn_id(block, insn_id); continue; + } + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } let id = unsafe { get_cme_def_body_attr_id(cme) }; - // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. - // We omit gen_prepare_non_leaf_call on gen_setivar, so it's unsafe to raise for multi-ractor mode. - if unsafe { rb_zjit_singleton_class_p(klass) } { - let attached = unsafe { rb_class_attached_object(klass) }; - if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); - } - } self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state }); self.make_equal_to(insn_id, val); } else if def_type == VM_METHOD_TYPE_OPTIMIZED { @@ -2657,12 +2673,9 @@ impl Function { self.push_insn_id(block, insn_id); continue; } let cref_sensitive = !unsafe { (*ice).ic_cref }.is_null(); - let multi_ractor_mode = unsafe { rb_jit_multi_ractor_p() }; - if cref_sensitive || multi_ractor_mode { + if cref_sensitive || !self.assume_single_ractor_mode(block, state) { self.push_insn_id(block, insn_id); continue; } - // Assume single-ractor mode. - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state }); // Invalidate output code on any constant writes associated with constants // referenced after the PatchPoint. self.push_insn(block, Insn::PatchPoint { invariant: Invariant::StableConstantNames { idlist }, state }); @@ -2829,11 +2842,6 @@ impl Function { self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); - if !recv_type.flags().is_t_object() { - // Check if the receiver is a T_OBJECT - self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_t_object)); - self.push_insn_id(block, insn_id); continue; - } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_too_complex)); @@ -2847,6 +2855,17 @@ impl Function { // entered the compiler. That means we can just return nil for this // shape + iv name self.push_insn(block, Insn::Const { val: Const::Value(Qnil) }) + } else if !recv_type.flags().is_t_object() { + // NOTE: it's fine to use rb_ivar_get_at_no_ractor_check because + // getinstancevariable does assume_single_ractor_mode() + let ivar_index_insn: InsnId = self.push_insn(block, Insn::Const { val: Const::CUInt16(ivar_index as u16) }); + self.push_insn(block, Insn::CCall { + cfunc: rb_ivar_get_at_no_ractor_check as *const u8, + recv: self_val, + args: vec![ivar_index_insn], + name: ID!(rb_ivar_get_at_no_ractor_check), + return_type: types::BasicObject, + elidable: true }) } else if recv_type.flags().is_embedded() { // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h let offset = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; @@ -4277,6 +4296,7 @@ impl Function { | Insn::ObjectAlloc { val, .. } | Insn::DupArrayInclude { target: val, .. } | Insn::GetIvar { self_val: val, .. } + | Insn::CCall { recv: val, .. } | Insn::FixnumBitCheck { val, .. } // TODO (https://github.com/Shopify/ruby/issues/859) this should check Fixnum, but then test_checkkeyword_tests_fixnum_bit fails | Insn::DefinedIvar { self_val: val, .. } => { self.assert_subtype(insn_id, val, types::BasicObject) @@ -4298,7 +4318,6 @@ impl Function { | Insn::Send { recv, ref args, .. } | Insn::SendForward { recv, ref args, .. } | Insn::InvokeSuper { recv, ref args, .. } - | Insn::CCall { recv, ref args, .. } | Insn::CCallWithFrame { recv, ref args, .. } | Insn::CCallVariadic { recv, ref args, .. } | Insn::ArrayInclude { target: recv, elements: ref args, .. } => { @@ -5693,7 +5712,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // ic is in arg 1 // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_getivar // TODO: We only really need this if self_val is a class/module - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state: exit_id }); + if !fun.assume_single_ractor_mode(block, exit_id) { + // gen_getivar assumes single Ractor; side-exit into the interpreter + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode) }); + break; // End the block + } let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); state.stack_push(result); } @@ -5702,7 +5725,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let ic = get_arg(pc, 1).as_ptr(); // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_setivar // TODO: We only really need this if self_val is a class/module - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state: exit_id }); + if !fun.assume_single_ractor_mode(block, exit_id) { + // gen_setivar assumes single Ractor; side-exit into the interpreter + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode) }); + break; // End the block + } let val = state.stack_pop()?; fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index bdc26f758e107e..9809c8bffbee01 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5231,6 +5231,148 @@ mod hir_opt_tests { "); } + #[test] + fn test_optimize_getivar_on_module() { + eval(" + module M + @foo = 42 + def self.test = @foo + end + M.test + "); + assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v16:HeapBasicObject = GuardType v6, HeapBasicObject + v17:HeapBasicObject = GuardShape v16, 0x1000 + v18:CUInt16[0] = Const CUInt16(0) + v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_getivar_on_class() { + eval(" + class C + @foo = 42 + def self.test = @foo + end + C.test + "); + assert_snapshot!(hir_string_proc("C.method(:test)"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v16:HeapBasicObject = GuardType v6, HeapBasicObject + v17:HeapBasicObject = GuardShape v16, 0x1000 + v18:CUInt16[0] = Const CUInt16(0) + v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_getivar_on_t_data() { + eval(" + class C < Range + def test = @a + end + obj = C.new 0, 1 + obj.instance_variable_set(:@a, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v16:HeapBasicObject = GuardType v6, HeapBasicObject + v17:HeapBasicObject = GuardShape v16, 0x1000 + v18:CUInt16[0] = Const CUInt16(0) + v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_getivar_on_module_multi_ractor() { + eval(" + module M + @foo = 42 + def self.test = @foo + end + Ractor.new {}.value + M.test + "); + assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit UnhandledYARVInsn(getinstancevariable) + "); + } + + #[test] + fn test_optimize_attr_reader_on_module_multi_ractor() { + eval(" + module M + @foo = 42 + class << self + attr_reader :foo + end + def self.test = foo + end + Ractor.new {}.value + M.test + "); + assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = SendWithoutBlock v6, :foo + CheckInterrupts + Return v11 + "); + } + #[test] fn test_dont_optimize_getivar_polymorphic() { set_call_threshold(3); From 01fd348847a22cee75426373799ebc2b1b797209 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 Jan 2025 22:42:06 +0900 Subject: [PATCH 1471/2435] Win32: Fix @ in middle of commands `@` is not a command, and cannot be placed after `||`. --- win32/Makefile.sub | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index edbe2a340209d3..c27419c9905b63 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1196,12 +1196,12 @@ ext/clean.sub ext/distclean.sub ext/realclean.sub \ $(MAKE) $(MFLAGS) $(@F) & \ cd %CD% & \ $(RMDIRS) %I \ - ))) || @ + ))) || $(NULLCMD) ext/distclean ext/realclean .bundle/distclean .bundle/realclean:: $(Q)cd $(@D) 2>nul && (for /R $(EXTS) %I in (exts.mk*) \ - do $(Q)(del %I & rmdir %~dpI)) || @ - -$(Q)rmdir $(@D) 2> nul || @ + do $(Q)(del %I & rmdir %~dpI)) || $(NULLCMD) + -$(Q)rmdir $(@D) 2> nul || $(NULLCMD) .bundle/realclean:: @$(RMALL) $(tooldir)/bunlder/*.lock $(srcdir)/.bundle From 9cdee9db9b3bfc58f773912c072ce7ab78a7d724 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 10 Jan 2025 11:55:09 +0900 Subject: [PATCH 1472/2435] Win32: Fix rm.bat removing non existent file --- win32/rm.bat | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/win32/rm.bat b/win32/rm.bat index 500a4abe2e5562..8cffd663c81558 100755 --- a/win32/rm.bat +++ b/win32/rm.bat @@ -8,11 +8,12 @@ if "%1" == "--debug" (shift & set PROMPT=$E[34m+$E[m$S & echo on & goto :optloop :begin if "%1" == "" goto :end set p=%1 +shift set p=%p:/=\% -if exist "%p%" del /q "%p%" > nul +if not exist "%p%" goto :begin +del /q "%p%" > nul && goto :begin if "%recursive%" == "1" for /D %%I in (%p%) do ( rd /s /q %%I ) -shift goto :begin :end From bb76e65f5696e936f72aa2ee0d17b5087f4a26ad Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 10 Jan 2025 13:21:48 +0900 Subject: [PATCH 1473/2435] Win32: Add `DLEXT` for clean-spec --- win32/Makefile.sub | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index c27419c9905b63..a8ad28cb1a7d4e 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -435,6 +435,7 @@ EXTSTATIC = OBJEXT = obj ASMEXT = asm +DLEXT = so INSTALLED_LIST= .installed.list @@ -872,7 +873,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define THREAD_IMPL_H "$(THREAD_IMPL_H)" #define THREAD_IMPL_SRC "$(THREAD_IMPL_SRC)" #define LOAD_RELATIVE 1 -#define DLEXT ".so" +#define DLEXT ".$(DLEXT)" !if "$(libdir_basename)" != "lib" #define LIBDIR_BASENAME "$(libdir_basename)" !endif @@ -985,7 +986,7 @@ s,@STATIC@,$(STATIC),;t t s,@CCDLFLAGS@,,;t t s,@LDSHARED@,$(LDSHARED),;t t s,@SOEXT@,dll,;t t -s,@DLEXT@,so,;t t +s,@DLEXT@,$(DLEXT),;t t s,@LIBEXT@,lib,;t t s,@STRIP@,$(STRIP),;t t s,@ENCSTATIC@,$(ENCSTATIC),;t t From b0b8eb6a31fbe484319c9fd042433094e0e69c93 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 10 Jan 2025 21:17:02 +0900 Subject: [PATCH 1474/2435] Win32: Fix removing symlink Try `rd` first for symlink to a directory; `del` attemps to remove all files under the target directory, instead of the symlink itself. --- win32/rm.bat | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/win32/rm.bat b/win32/rm.bat index 8cffd663c81558..8263dadd28272e 100755 --- a/win32/rm.bat +++ b/win32/rm.bat @@ -11,7 +11,11 @@ set p=%1 shift set p=%p:/=\% if not exist "%p%" goto :begin -del /q "%p%" > nul && goto :begin + +::- Try `rd` first for symlink to a directory; `del` attemps to remove all +::- files under the target directory, instead of the symlink itself. +(rd /q "%p%" || del /q "%p%") 2> nul && goto :begin + if "%recursive%" == "1" for /D %%I in (%p%) do ( rd /s /q %%I ) From 00d05dfca5584537d4581420c26ce0864d3eff96 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 10 Jan 2025 21:19:36 +0900 Subject: [PATCH 1475/2435] Win32: Remove extra suffix for sub-make --- win32/Makefile.sub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index a8ad28cb1a7d4e..2670e775dccc41 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1193,8 +1193,8 @@ ext/clean.sub ext/distclean.sub ext/realclean.sub \ call set n=%I && \ call set n=%n:%CD%\$(@D)\=% && \ call set n=%n:\.=% && \ - call echo $(@F)ing %n:\=/% & \ - $(MAKE) $(MFLAGS) $(@F) & \ + call echo $(@F:.sub=)ing %n:\=/% & \ + $(MAKE) $(MFLAGS) $(@F:.sub=) & \ cd %CD% & \ $(RMDIRS) %I \ ))) || $(NULLCMD) From a6c5d290abaa232a60c21fa2a48ac0228de8270b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 01:06:34 +0900 Subject: [PATCH 1476/2435] Win32: Clean prism - intermediate source files - timestamp files - build directories --- win32/Makefile.sub | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 2670e775dccc41..79fbdb30c411e9 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1168,12 +1168,39 @@ clean-local:: $(Q)$(RM) miniruby.rc $(RUBY_INSTALL_NAME).rc $(RUBYW_INSTALL_NAME).rc $(RUBY_SO_NAME).rc $(Q)$(RM) *.map *.pdb *.ilk *.exp $(RUBYDEF) ext\ripper\y.output +clean-local:: clean-prism + +clean-prism: + @for /R $(PRISM_BUILD_DIR:/=\) %I in (.time) do @(del /q %I && $(RMDIRS) %~pI) 2> nul || $(NULLCMD) + distclean-local:: $(Q)$(RM) ext\config.cache $(RBCONFIG:/=\) $(CONFIG_H:/=\) -$(Q)$(RM) $(INSTALLED_LIST:/=\) $(arch_hdrdir:/=\)\ruby\config.h verconf.h -$(Q)$(RMDIRS) $(arch_hdrdir:/=\)\ruby -$(Q)$(RMDIR) win32 +distclean-local:: distclean-prism + +distclean-prism: clean-prism +!if "$(srcdir)" != "." + $(Q)for %I in ( \ + $(PRISM_SRCDIR:/=\)\templates\ext\prism\*.c.erb \ + $(PRISM_SRCDIR:/=\)\templates\include\prism\*.h.erb \ + $(PRISM_SRCDIR:/=\)\templates\src\*.c.erb \ + ) do $(Q)(del /q prism\%~nI 2> nul || $(NULLCMD)) + $(Q)for /D %I in (prism\*) do $(Q)$(RMDIRS) %I + $(Q)$(RMDIRS) prism +!endif + +realclean-prism: distclean-prism +!if "$(srcdir)" == "." + $(Q)for %I in ( \ + $(PRISM_SRCDIR:/=\)\templates\ext\prism\*.c.erb \ + $(PRISM_SRCDIR:/=\)\templates\include\prism\*.h.erb \ + $(PRISM_SRCDIR:/=\)\templates\src\*.c.erb \ + ) do $(Q)(del /q $(PRISM_SRCDIR:/=\)\%~nI 2> nul || $(NULLCMD)) +!endif + .bundle/clean:: .bundle/clean.sub .bundle/distclean:: .bundle/distclean.sub .bundle/realclean:: .bundle/realclean.sub From fa513886410a85c2229acfa558c5375e5fad278b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 01:14:23 +0900 Subject: [PATCH 1477/2435] Win32: Append `-p` option to `RMDIRS` `rmdirs.bat` may require this option explicitly to remove parent directories, in the future. --- win32/Makefile.sub | 4 ++-- win32/rmdirs.bat | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 79fbdb30c411e9..737c8f6bd6026b 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -117,7 +117,7 @@ IFCHANGE = $(COMSPEC) /C $(srcdir:/=\)\win32\ifchange.bat RM = $(COMSPEC) /C $(srcdir:/=\)\win32\rm.bat RM1 = del RMDIR = $(COMSPEC) /C $(srcdir:/=\)\win32\rmdirs.bat -RMDIRS = $(COMSPEC) /C $(srcdir:/=\)\win32\rmdirs.bat +RMDIRS = $(COMSPEC) /C $(srcdir:/=\)\win32\rmdirs.bat -p RMALL = $(COMSPEC) /C $(srcdir:/=\)\win32\rm.bat -f -r MAKEDIRS = $(COMSPEC) /E:ON /C $(srcdir:/=\)\win32\makedirs.bat TOUCH = $(BASERUBY) -run -e touch -- @@ -968,7 +968,7 @@ s,@LN_S@,$(LN_S),;t t s,@SET_MAKE@,MFLAGS = -$$(MAKEFLAGS),;t t s,@RM@,$$(COMSPEC) /C $$(top_srcdir:/=\)\win32\rm.bat,;t t s,@RMDIR@,$$(COMSPEC) /C $$(top_srcdir:/=\)\win32\rmdirs.bat,:t t -s,@RMDIRS@,$$(COMSPEC) /C $$(top_srcdir:/=\)\win32\rmdirs.bat,;t t +s,@RMDIRS@,$$(COMSPEC) /C $$(top_srcdir:/=\)\win32\rmdirs.bat -p,;t t s,@RMALL@,$$(COMSPEC) /C $$(top_srcdir:/=\)\win32\rm.bat -f -r,:t t s,@MAKEDIRS@,$$(COMSPEC) /E:ON /C $$(top_srcdir:/=\)\win32\makedirs.bat,;t t s,@LIBOBJS@,$(LIBOBJS),;t t diff --git a/win32/rmdirs.bat b/win32/rmdirs.bat index c3d7b637b3ea2a..a8abebd3839051 100755 --- a/win32/rmdirs.bat +++ b/win32/rmdirs.bat @@ -1,6 +1,9 @@ @echo off @setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 -if "%1" == "-p" shift +set parents=1 +:optloop +if "%1" == "--debug" (shift & set PROMPT=$E[34m+$E[m$S & echo on & goto :optloop) +if "%1" == "-p" (shift & (set parents=1) & goto :optloop) :begin if "%1" == "" goto :end set dir=%1 @@ -12,6 +15,7 @@ if "%1" == "" goto :end if "%dir%" == "." goto :begin if "%dir%" == ".." goto :begin rd "%dir%" 2> nul || goto :begin + if "%parents%" == "" goto :begin :trim_sep if not /%dir:~-1%/ == /\/ goto :trim_base set dir=%dir:~0,-1% From ec80e92a4789533a2ae0be3aa3709c6d6ab229d2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 11:53:03 +0900 Subject: [PATCH 1478/2435] Win32: Refine outputs - Suppress logos from sub makes. - Set the prompt for `for` command when `echo` is on. --- win32/Makefile.sub | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 737c8f6bd6026b..68de9d5cf4178b 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -13,6 +13,10 @@ PWD = $(MAKEDIR) empty = tooldir = $(srcdir)/tool +PROMPT = +$$S + +MAKEFLAGS = l$(MAKEFLAGS) + !ifndef MFLAGS MFLAGS=-l !endif @@ -1210,7 +1214,7 @@ realclean-prism: distclean-prism .bundle/realclean.sub:: ext/realclean.mk ext/clean.mk ext/distclean.mk ext/realclean.mk:: - $(Q)if exist $(EXTS_MK) $(MAKE) -k -f $(EXTS_MK) top_srcdir=$(srcdir) $(*F) + $(Q)if exist $(EXTS_MK) $(MAKE) $(MFLAGS) -k -f $(EXTS_MK) top_srcdir=$(srcdir) $(*F) ext/clean.sub ext/distclean.sub ext/realclean.sub \ .bundle/clean.sub .bundle/distclean.sub .bundle/realclean.sub:: From c4fb79caa72d667512d1ca7647ac0443b22a3d47 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 18:25:24 +0900 Subject: [PATCH 1479/2435] Win32: Clean generated sources --- win32/Makefile.sub | 3 +++ 1 file changed, 3 insertions(+) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 68de9d5cf4178b..f93f344b0d2d2d 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1183,6 +1183,9 @@ distclean-local:: -$(Q)$(RMDIRS) $(arch_hdrdir:/=\)\ruby -$(Q)$(RMDIR) win32 +distclean-local:: clean-srcs-local +distclean-ext:: clean-srcs-ext + distclean-local:: distclean-prism distclean-prism: clean-prism From b2b674562db8e922fa6a6bd0469f20f4caf0954e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 18:26:52 +0900 Subject: [PATCH 1480/2435] Win32: Support removing wildcards in middle of path --- win32/rm.bat | 71 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/win32/rm.bat b/win32/rm.bat index 8263dadd28272e..c41ebfa5ee0177 100755 --- a/win32/rm.bat +++ b/win32/rm.bat @@ -1,23 +1,64 @@ @echo off @setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 + +set prog=%~n0 +set dryrun= set recursive= +set debug= +set error=0 +set parent= + :optloop if "%1" == "-f" shift -if "%1" == "-r" (shift & set "recursive=1" & goto :optloop) -if "%1" == "--debug" (shift & set PROMPT=$E[34m+$E[m$S & echo on & goto :optloop) +if "%1" == "-n" (shift & set "dryrun=%1" & goto :optloop) +if "%1" == "-r" (shift & set "recursive=%1" & goto :optloop) +if "%1" == "--debug" (shift & set "debug=%1" & set PROMPT=$E[34m+$E[m$S & echo on & goto :optloop) :begin -if "%1" == "" goto :end -set p=%1 -shift -set p=%p:/=\% -if not exist "%p%" goto :begin +if "%1" == "" goto :EOF + set p=%1 + shift + set p=%p:/=\% + call :remove %p% +goto :begin -::- Try `rd` first for symlink to a directory; `del` attemps to remove all -::- files under the target directory, instead of the symlink itself. -(rd /q "%p%" || del /q "%p%") 2> nul && goto :begin +:remove +setlocal -if "%recursive%" == "1" for /D %%I in (%p%) do ( - rd /s /q %%I -) -goto :begin -:end +::- Split %1 by '?' and '*', wildcard characters +for /f "usebackq delims=?* tokens=1*" %%I in ('%1') do (set "par=%%I" & set "sub=%%J") +if "%sub%" == "" goto :remove_plain +if "%sub:\=%" == "%sub%" goto :remove_plain + ::- Extract the first wildcard + set "q=%1" + call set "q=%%q:%par%=%%" + set q=%q:~0,1% + + ::- `delims` chars at the beginning are removed in `for` + if "%sub:~0,1%" == "\" ( + set "sub=%sub:~1%" + set "par=%par%%q%" + ) else ( + for /f "usebackq delims=\\ tokens=1*" %%I in ('%sub%') do (set "par=%par%%q%%%I" & set "sub=%%J") + ) + + ::- Recursive search + for /d %%D in (%par%) do ( + call :remove %sub% %2%%D\ + ) +goto :remove_end +:remove_plain + set p=%2%1 + if not exist "%1" goto :remove_end + if not "%dryrun%" == "" ( + echo Removing %p:\=/% + goto :remove_end + ) + ::- Try `rd` first for symlink to a directory; `del` attemps to remove all + ::- files under the target directory, instead of the symlink itself. + (rd /q "%p%" || del /q "%p%") 2> nul && goto :remove_end + + if "%recursive%" == "-r" for /D %%I in (%p%) do ( + rd /s /q %%I || call set error=%%ERRORLEVEL%% + ) +:remove_end +endlocal & set "error=%error%" & goto :EOF From 0e222991bf11e28457819d42d751b517686c71e9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 18:28:53 +0900 Subject: [PATCH 1481/2435] Win32: Remove DLL files linked by `prepare-vcpkg` --- win32/Makefile.sub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index f93f344b0d2d2d..13e64d6e40ef27 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1170,7 +1170,7 @@ clean-local:: $(Q)$(RM) $(WINMAINOBJ) ext\extinit.c ext\extinit.$(OBJEXT) ext\vc*.pdb miniruby.lib $(Q)$(RM) $(RUBY_INSTALL_NAME).res $(RUBYW_INSTALL_NAME).res $(RUBY_SO_NAME).res $(Q)$(RM) miniruby.rc $(RUBY_INSTALL_NAME).rc $(RUBYW_INSTALL_NAME).rc $(RUBY_SO_NAME).rc - $(Q)$(RM) *.map *.pdb *.ilk *.exp $(RUBYDEF) ext\ripper\y.output + $(Q)$(RM) *.map *.pdb *.ilk *.exp *.dll $(RUBYDEF) ext\ripper\y.output clean-local:: clean-prism From b17f6a2aae96bb0faf0d379dc54eda9878541f77 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 11 Jan 2025 23:11:08 +0900 Subject: [PATCH 1482/2435] Win32: Clean miniprelude --- win32/Makefile.sub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 13e64d6e40ef27..2904147b76f22f 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1178,7 +1178,7 @@ clean-prism: @for /R $(PRISM_BUILD_DIR:/=\) %I in (.time) do @(del /q %I && $(RMDIRS) %~pI) 2> nul || $(NULLCMD) distclean-local:: - $(Q)$(RM) ext\config.cache $(RBCONFIG:/=\) $(CONFIG_H:/=\) + $(Q)$(RM) ext\config.cache $(RBCONFIG:/=\) $(CONFIG_H:/=\) miniprelude.c -$(Q)$(RM) $(INSTALLED_LIST:/=\) $(arch_hdrdir:/=\)\ruby\config.h verconf.h -$(Q)$(RMDIRS) $(arch_hdrdir:/=\)\ruby -$(Q)$(RMDIR) win32 From a9961701582d842bb5866d57d8e22cf4aa46eba8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 Jan 2025 17:24:06 +0900 Subject: [PATCH 1483/2435] Win32: Remove bundled gems directories --- win32/Makefile.sub | 3 +++ 1 file changed, 3 insertions(+) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 2904147b76f22f..da666951b2580c 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1236,6 +1236,9 @@ ext/clean.sub ext/distclean.sub ext/realclean.sub \ ext/distclean ext/realclean .bundle/distclean .bundle/realclean:: $(Q)cd $(@D) 2>nul && (for /R $(EXTS) %I in (exts.mk*) \ do $(Q)(del %I & rmdir %~dpI)) || $(NULLCMD) + +.bundle/distclean .bundle/realclean:: + $(Q)for /D %I in ($(@D)\*) do $(Q)$(RMDIRS) %I || $(NULLCMD) -$(Q)rmdir $(@D) 2> nul || $(NULLCMD) .bundle/realclean:: From 0be626e7551f9e9ed201b9c4a66aab5fcf3e6fbb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Nov 2025 17:13:21 +0900 Subject: [PATCH 1484/2435] Win32: Clean timestamp directory for platform --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index eb9b75ca7e3dd0..35c23159800160 100644 --- a/common.mk +++ b/common.mk @@ -801,7 +801,7 @@ clean-capi distclean-capi realclean-capi: clean-platform distclean-platform realclean-platform: $(Q) $(RM) $(PLATFORM_D) - -$(Q) $(RMDIR) $(PLATFORM_DIR) 2> $(NULL) || $(NULLCMD) + -$(Q) $(RMDIR) $(PLATFORM_DIR) $(TIMESTAMPDIR) 2> $(NULL) || $(NULLCMD) RUBYSPEC_CAPIEXT = spec/ruby/optional/capi/ext RUBYSPEC_CAPIEXT_SRCDIR = $(srcdir)/$(RUBYSPEC_CAPIEXT) From b563be302f635116e30e3d80812962650f5564ef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Nov 2025 17:13:56 +0900 Subject: [PATCH 1485/2435] Win32: Clean empty directories --- win32/Makefile.sub | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index da666951b2580c..c3db450abc30a0 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1234,8 +1234,7 @@ ext/clean.sub ext/distclean.sub ext/realclean.sub \ ))) || $(NULLCMD) ext/distclean ext/realclean .bundle/distclean .bundle/realclean:: - $(Q)cd $(@D) 2>nul && (for /R $(EXTS) %I in (exts.mk*) \ - do $(Q)(del %I & rmdir %~dpI)) || $(NULLCMD) + $(Q)(for /D /R $(@D) %I in (.) do $(Q)$(RMDIRS) %I) || $(NULLCMD) .bundle/distclean .bundle/realclean:: $(Q)for /D %I in ($(@D)\*) do $(Q)$(RMDIRS) %I || $(NULLCMD) From e2d176556e36ae7911af3ff6163420a48b82af92 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 2 Dec 2025 08:12:45 +0900 Subject: [PATCH 1486/2435] CI: Distclean mswin Use the given `make-command` instead of the hard-coded `make` command. TODO: Use it for `make up` as well, in the future. --- .github/actions/setup/directories/action.yml | 9 ++++++++- .github/workflows/windows.yml | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index b64227e4359c71..8389ef54b78e5e 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -19,6 +19,13 @@ inputs: Where binaries and other generated contents go. This will be created if absent. + make-command: + required: false + type: string + default: 'make' + description: >- + The command of `make`. + makeup: required: false type: boolean @@ -169,7 +176,7 @@ runs: shell: bash id: clean run: | - echo distclean='make -C ${{ inputs.builddir }} distclean' >> $GITHUB_OUTPUT + echo distclean='cd ${{ inputs.builddir }} && ${{ inputs.make-command }} distclean' >> $GITHUB_OUTPUT echo remained-files='find ${{ inputs.builddir }} -ls' >> $GITHUB_OUTPUT [ "${{ inputs.builddir }}" = "${{ inputs.srcdir }}" ] || echo final='rmdir ${{ inputs.builddir }}' >> $GITHUB_OUTPUT diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4659fb80f8434d..dd4bf0de968108 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -75,6 +75,8 @@ jobs: with: srcdir: src builddir: build + make-command: nmake + clean: true - name: Install tools with scoop run: | From 7df97983be41e893afee6d0012c6dced55ff98f8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Dec 2025 11:39:29 +0900 Subject: [PATCH 1487/2435] [ruby/rubygems] Improve banner message for the default command. Co-authored-by: Benoit Daloze https://github.com/ruby/rubygems/commit/463488b439 Co-authored-by: Patrik Ragnarsson --- lib/bundler/cli.rb | 6 +++--- spec/bundler/bundler/cli_spec.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index f5977863ddcc26..9c29751a7c31fb 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -133,10 +133,10 @@ def self.default_command(meth = nil) unless Bundler.settings[:default_cli_command] Bundler.ui.info <<-MSG - In the feature version of Bundler, running `bundle` without argument will no longer run `bundle install`. + In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. - If you wish to use feature behavior now with `bundle config set default_cli_command cli_help --global` - or you can continue to use the old behavior with `bundle config set default_cli_command install_or_cli_help --global`. + You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, + or you can continue to use the current behavior with `bundle config set default_cli_command install_or_cli_help --global`. This message will be removed after a default_cli_command value is set. MSG end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index ac6b77ad7483bf..4503cea6a0058c 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -90,7 +90,7 @@ def out_with_macos_man_workaround it "tries to installs by default but print help on missing Gemfile" do bundle "", raise_on_error: false expect(err).to include("Could not locate Gemfile") - expect(out).to include("In the feature version of Bundler") + expect(out).to include("In a future version of Bundler") expect(out).to include("Bundler version #{Bundler::VERSION}"). and include("\n\nBundler commands:\n\n"). @@ -102,7 +102,7 @@ def out_with_macos_man_workaround it "runs bundle install when default_cli_command set to install" do bundle "config set default_cli_command install_or_cli_help" bundle "", raise_on_error: false - expect(out).to_not include("In the feature version of Bundler") + expect(out).to_not include("In a future version of Bundler") expect(err).to include("Could not locate Gemfile") end end From 0e22108d60fbd0e338fb6e110ddd81a93b45b592 Mon Sep 17 00:00:00 2001 From: Sam Westerman Date: Tue, 2 Dec 2025 07:00:22 +0000 Subject: [PATCH 1488/2435] [ruby/optparse] Remove `const_set` and instead use explicit assignments https://github.com/ruby/optparse/commit/6e2709a5fd --- lib/optparse.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index e0b0ff011b5455..aae73c86b32787 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -2314,42 +2314,42 @@ def message # Raises when ambiguously completable string is encountered. # class AmbiguousOption < ParseError - const_set(:Reason, 'ambiguous option') + Reason = 'ambiguous option' # :nodoc: end # # Raises when there is an argument for a switch which takes no argument. # class NeedlessArgument < ParseError - const_set(:Reason, 'needless argument') + Reason = 'needless argument' # :nodoc: end # # Raises when a switch with mandatory argument has no argument. # class MissingArgument < ParseError - const_set(:Reason, 'missing argument') + Reason = 'missing argument' # :nodoc: end # # Raises when switch is undefined. # class InvalidOption < ParseError - const_set(:Reason, 'invalid option') + Reason = 'invalid option' # :nodoc: end # # Raises when the given argument does not match required format. # class InvalidArgument < ParseError - const_set(:Reason, 'invalid argument') + Reason = 'invalid argument' # :nodoc: end # # Raises when the given argument word can't be completed uniquely. # class AmbiguousArgument < InvalidArgument - const_set(:Reason, 'ambiguous argument') + Reason = 'ambiguous argument' # :nodoc: end # @@ -2448,9 +2448,11 @@ def initialize(*args) # :nodoc: # and DecimalNumeric. See Acceptable argument classes (in source code). # module Acceptables - const_set(:DecimalInteger, OptionParser::DecimalInteger) - const_set(:OctalInteger, OptionParser::OctalInteger) - const_set(:DecimalNumeric, OptionParser::DecimalNumeric) + # :stopdoc: + DecimalInteger = OptionParser::DecimalInteger + OctalInteger = OptionParser::OctalInteger + DecimalNumeric = OptionParser::DecimalNumeric + # :startdoc: end end From 456ba321a84d34e76c8837ac96f47a11457480cb Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 30 Nov 2025 19:16:52 -0800 Subject: [PATCH 1489/2435] [ruby/rubygems] Make BUNDLE_LOCKFILE environment variable have precedence over lockfile method in Gemfile It would be simpler to do `options[:lockfile] ||= ENV["BUNDLE_LOCKFILE"]`, but that doesn't work as `options` is frozen. Fixes https://github.com/ruby/rubygems/pull/9117 https://github.com/ruby/rubygems/commit/6e3603a0e9 --- lib/bundler/cli.rb | 6 ++++-- lib/bundler/man/gemfile.5 | 4 ++-- lib/bundler/man/gemfile.5.ronn | 2 +- spec/bundler/commands/install_spec.rb | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 9c29751a7c31fb..36ce04eb2a5d05 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -69,7 +69,7 @@ def initialize(*args) # lock --lockfile works differently than install --lockfile unless current_cmd == "lock" - custom_lockfile = options[:lockfile] || Bundler.settings[:lockfile] + custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] if custom_lockfile && !custom_lockfile.empty? Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) reset_settings = true @@ -282,8 +282,10 @@ def install end require_relative "cli/install" + options = self.options.dup + options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"] Bundler.settings.temporary(no_install: false) do - Install.new(options.dup).run + Install.new(options).run end end diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index fce7d8e178439b..a8c055a0c1a456 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -494,9 +494,9 @@ The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile crea .IP "2." 4 The \fBbundle install\fR \fB\-\-lockfile\fR option\. .IP "3." 4 -The \fBlockfile\fR method in the Gemfile\. -.IP "4." 4 The \fBBUNDLE_LOCKFILE\fR environment variable\. +.IP "4." 4 +The \fBlockfile\fR method in the Gemfile\. .IP "5." 4 The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\. .IP "" 0 diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index e4bc91359e3cc5..18d7bb826e4439 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -581,6 +581,6 @@ following precedence is used: 1. The `bundle install` `--no-lock` option (which disables lockfile creation). 1. The `bundle install` `--lockfile` option. -1. The `lockfile` method in the Gemfile. 1. The `BUNDLE_LOCKFILE` environment variable. +1. The `lockfile` method in the Gemfile. 1. The default behavior of adding `.lock` to the end of the Gemfile name. diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index bacd8d64f2d6c3..3dc8aa0dc01757 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -41,6 +41,20 @@ expect(bundled_app("OmgFile.lock")).to exist end + it "creates lockfile using BUNDLE_LOCKFILE instead of lockfile method" do + ENV["BUNDLE_LOCKFILE"] = "ReallyOmgFile.lock" + install_gemfile <<-G + lockfile "OmgFile.lock" + source "https://gem.repo1" + gem "myrack", "1.0" + G + + expect(bundled_app("ReallyOmgFile.lock")).to exist + expect(bundled_app("OmgFile.lock")).not_to exist + ensure + ENV.delete("BUNDLE_LOCKFILE") + end + it "creates lockfile based on --lockfile option is given" do gemfile bundled_app("OmgFile"), <<-G source "https://gem.repo1" From e515fa7ab1981df439a3a6b3635a0389f4216cce Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 26 Nov 2025 13:28:44 +0100 Subject: [PATCH 1490/2435] ZJIT: Improve documentation and make it easy to generate the types graph --- doc/jit/zjit.md | 49 ++++++++++++++++++++++++++----- zjit/src/hir_type/gen_hir_type.rb | 18 +++++++----- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index f3b36b1fd5aaa5..7434d44b9d7d60 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -16,6 +16,35 @@ To build ZJIT on macOS: make -j miniruby ``` +To build ZJIT on Linux: + +```bash +./autogen.sh + +./configure \ + --enable-zjit=dev \ + --prefix="$HOME"/.rubies/ruby-zjit \ + --disable-install-doc + +make -j miniruby +``` + +Note that `--enable-zjit=dev` does a lot of IR validation, which will help to catch errors early but mean compilation and warmup are significantly slower. + +The valid values for `--enable-zjit` are, from fastest to slowest: +* `--enable-zjit`: enable ZJIT in release mode for maximum performance +* `--enable-zjit=stats`: enable ZJIT in extended-stats mode +* `--enable-zjit=dev_nodebug`: enable ZJIT in development mode but without slow runtime checks +* `--enable-zjit=dev`: enable ZJIT in debug mode for development, also enables `RUBY_DEBUG` + +### Regenerate bindings + +When modifying `zjit/bindgen/src/main.rs` you need to regenerate bindings in `zjit/src/cruby_bindings.inc.rs` with: + +```bash +make zjit-bindgen +``` + ## Documentation You can generate and open the source level documentation in your browser using: @@ -24,6 +53,16 @@ You can generate and open the source level documentation in your browser using: cargo doc --document-private-items -p zjit --open ``` +### Graph of the Type System + +You can generate a graph of the ZJIT type hierarchy using: + +```bash +ruby zjit/src/hir_type/gen_hir_type.rb > zjit/src/hir_type/hir_type.inc.rs +dot -O -Tpdf zjit_types.dot +open zjit_types.dot.pdf +``` + ## Testing Note that tests link against CRuby, so directly calling `cargo test`, or `cargo nextest` should not build. All tests are instead accessed through `make`. @@ -139,14 +178,8 @@ end ### Performance Ratio -The `ratio_in_zjit` stat shows the percentage of Ruby instructions executed in JIT code vs interpreter. This metric only appears when ZJIT is built with `--enable-zjit=stats` (which enables `rb_vm_insn_count` tracking) and represents a key performance indicator for ZJIT effectiveness. - -To build with stats support: - -```bash -./configure --enable-zjit=stats -make -j -``` +The `ratio_in_zjit` stat shows the percentage of Ruby instructions executed in JIT code vs interpreter. +This metric only appears when ZJIT is built with `--enable-zjit=stats` [or more](#build-instructions) (which enables `rb_vm_insn_count` tracking) and represents a key performance indicator for ZJIT effectiveness. ### Tracing side exits diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 4e0ecc718f8f82..e51d0c04e1a324 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -25,20 +25,20 @@ def subtype name end # Helper to generate graphviz. -def to_graphviz_rec type +def to_graphviz_rec type, f type.subtypes.each {|subtype| - puts type.name + "->" + subtype.name + ";" + f.puts type.name + "->" + subtype.name + ";" } type.subtypes.each {|subtype| - to_graphviz_rec subtype + to_graphviz_rec subtype, f } end # Generate graphviz. -def to_graphviz type - puts "digraph G {" - to_graphviz_rec type - puts "}" +def to_graphviz type, f + f.puts "digraph G {" + to_graphviz_rec type, f + f.puts "}" end # ===== Start generating the type DAG ===== @@ -218,3 +218,7 @@ def add_union name, type_names } puts " ];" puts "}" + +File.open("zjit_types.dot", "w") do |f| + to_graphviz(any, f) +end From 03ed220cb0581d36606e13b1709d5ad133a45dc0 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 22 Nov 2025 17:48:41 +0900 Subject: [PATCH 1491/2435] Box: load_wrapping is not needed now Top level constants are defined on the Object class's constant table, and those constants can be referred as box::CONST_NAME from outside box. So load_wrapping() is not needed now. --- load.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/load.c b/load.c index c519efe2a13f8d..5a0697a2626707 100644 --- a/load.c +++ b/load.c @@ -1344,16 +1344,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa else { switch (found) { case 'r': - // iseq_eval_in_box will be called with the loading box eventually - if (BOX_OPTIONAL_P(box)) { - // check with BOX_OPTIONAL_P (not BOX_USER_P) for NS1::xxx naming - // it is not expected for the main box - // TODO: no need to use load_wrapping() here? - load_wrapping(saved.ec, path, box->box_object); - } - else { - load_iseq_eval(saved.ec, path); - } + load_iseq_eval(saved.ec, path); break; case 's': From 75f8a116c94f538ae18cf3c6921c3b0d2724dee7 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 30 Nov 2025 17:33:05 +0900 Subject: [PATCH 1492/2435] Box: Fix data type name --- box.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/box.c b/box.c index 81f285b3212ccd..282c5756384d5f 100644 --- a/box.c +++ b/box.c @@ -250,7 +250,7 @@ box_entry_memsize(const void *ptr) } const rb_data_type_t rb_box_data_type = { - "Namespace::Entry", + "Ruby::Box::Entry", { rb_box_entry_mark, box_entry_free, @@ -261,7 +261,7 @@ const rb_data_type_t rb_box_data_type = { }; const rb_data_type_t rb_root_box_data_type = { - "Namespace::Root", + "Ruby::Box::Root", { rb_box_entry_mark, box_root_free, From 84bc1f038a4df63ff09490c981753d7234a52ce4 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 30 Nov 2025 17:36:17 +0900 Subject: [PATCH 1493/2435] Box: Mark boxes when a class/module is originally defined in it. When a class/module defined by extension libraries in a box, checking types of instances of the class needs to access its data type (rb_data_type_t). So if a class still exists (not GCed), the box must exist too (to be marked). --- gc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gc.c b/gc.c index 4f6751316f07da..97b7362c9fbb85 100644 --- a/gc.c +++ b/gc.c @@ -3154,12 +3154,18 @@ rb_gc_mark_children(void *objspace, VALUE obj) foreach_args.objspace = objspace; foreach_args.obj = obj; rb_class_classext_foreach(obj, gc_mark_classext_module, (void *)&foreach_args); + if (BOX_USER_P(RCLASS_PRIME_BOX(obj))) { + gc_mark_internal(RCLASS_PRIME_BOX(obj)->box_object); + } break; case T_ICLASS: foreach_args.objspace = objspace; foreach_args.obj = obj; rb_class_classext_foreach(obj, gc_mark_classext_iclass, (void *)&foreach_args); + if (BOX_USER_P(RCLASS_PRIME_BOX(obj))) { + gc_mark_internal(RCLASS_PRIME_BOX(obj)->box_object); + } break; case T_ARRAY: From 9eafeaed67903bbf2f2dca5eb473c1bf774712f6 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sun, 30 Nov 2025 17:50:50 +0900 Subject: [PATCH 1494/2435] Box: Free rb_classext_t struct for a box when the box is GCed --- box.c | 32 ++++++++++++++++++++++++++++++++ class.c | 18 ++++++++++++++++-- internal/box.h | 1 + internal/class.h | 1 + 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/box.c b/box.c index 282c5756384d5f..3fbc4e9fe23a14 100644 --- a/box.c +++ b/box.c @@ -149,6 +149,7 @@ box_entry_initialize(rb_box_t *box) box->loading_table = st_init_strtable(); box->ruby_dln_libmap = rb_hash_new_with_size(0); box->gvar_tbl = rb_hash_new_with_size(0); + box->classext_cow_classes = st_init_numtable(); box->is_user = true; box->is_optional = true; @@ -199,6 +200,9 @@ rb_box_entry_mark(void *ptr) } rb_gc_mark(box->ruby_dln_libmap); rb_gc_mark(box->gvar_tbl); + if (box->classext_cow_classes) { + rb_mark_tbl(box->classext_cow_classes); + } } static int @@ -233,9 +237,36 @@ box_root_free(void *ptr) } } +static int +free_classext_for_box(st_data_t _key, st_data_t obj_value, st_data_t box_arg) +{ + rb_classext_t *ext; + VALUE obj = (VALUE)obj_value; + const rb_box_t *box = (const rb_box_t *)box_arg; + + if (RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)) { + ext = rb_class_unlink_classext(obj, box); + rb_class_classext_free(obj, ext, false); + } + else if (RB_TYPE_P(obj, T_ICLASS)) { + ext = rb_class_unlink_classext(obj, box); + rb_iclass_classext_free(obj, ext, false); + } + else { + rb_bug("Invalid type of object in classext_cow_classes: %s", rb_type_str(BUILTIN_TYPE(obj))); + } + return ST_CONTINUE; +} + static void box_entry_free(void *ptr) { + const rb_box_t *box = (const rb_box_t *)ptr; + + if (box->classext_cow_classes) { + st_foreach(box->classext_cow_classes, free_classext_for_box, (st_data_t)box); + } + box_root_free(ptr); xfree(ptr); } @@ -750,6 +781,7 @@ initialize_root_box(void) root->ruby_dln_libmap = rb_hash_new_with_size(0); root->gvar_tbl = rb_hash_new_with_size(0); + root->classext_cow_classes = NULL; // classext CoW never happen on the root box vm->root_box = root; diff --git a/class.c b/class.c index 2f94b801471104..0cec526b74fe98 100644 --- a/class.c +++ b/class.c @@ -86,6 +86,17 @@ cvar_table_free_i(VALUE value, void *ctx) return ID_TABLE_CONTINUE; } +rb_classext_t * +rb_class_unlink_classext(VALUE klass, const rb_box_t *box) +{ + st_data_t ext; + st_data_t key = (st_data_t)box->box_object; + VALUE obj_id = rb_obj_id(klass); + st_delete(box->classext_cow_classes, &obj_id, 0); + st_delete(RCLASS_CLASSEXT_TBL(klass), &key, &ext); + return (rb_classext_t *)ext; +} + void rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime) { @@ -156,7 +167,7 @@ struct rb_class_set_box_classext_args { }; static int -rb_class_set_box_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) +set_box_classext_update(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t a, int existing) { struct rb_class_set_box_classext_args *args = (struct rb_class_set_box_classext_args *)a; @@ -182,7 +193,10 @@ rb_class_set_box_classext(VALUE obj, const rb_box_t *box, rb_classext_t *ext) .ext = ext, }; - st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, rb_class_set_box_classext_update, (st_data_t)&args); + VM_ASSERT(BOX_USER_P(box)); + + st_update(RCLASS_CLASSEXT_TBL(obj), (st_data_t)box->box_object, set_box_classext_update, (st_data_t)&args); + st_insert(box->classext_cow_classes, (st_data_t)rb_obj_id(obj), obj); // FIXME: This is done here because this is the first time the objects in // the classext are exposed via this class. It's likely that if GC diff --git a/internal/box.h b/internal/box.h index 41cad634823f71..c341f046d3e147 100644 --- a/internal/box.h +++ b/internal/box.h @@ -34,6 +34,7 @@ struct rb_box_struct { VALUE ruby_dln_libmap; VALUE gvar_tbl; + struct st_table *classext_cow_classes; bool is_user; bool is_optional; diff --git a/internal/class.h b/internal/class.h index f122d2f189c580..296a07ae29e9f3 100644 --- a/internal/class.h +++ b/internal/class.h @@ -513,6 +513,7 @@ void rb_undef_methods_from(VALUE klass, VALUE super); VALUE rb_class_inherited(VALUE, VALUE); VALUE rb_keyword_error_new(const char *, VALUE); +rb_classext_t *rb_class_unlink_classext(VALUE klass, const rb_box_t *box); void rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); void rb_iclass_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime); From cc929bff7ddfeccf320c621a6ae0f106e76a52f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 2 Dec 2025 15:45:01 +0100 Subject: [PATCH 1495/2435] [ruby/json] Don't call to_json on the return value of as_json for Float::NAN https://github.com/ruby/json/commit/28c57df8f7 --- test/json/json_generator_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index c01ed678fcfda4..9600f4be8d15d0 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -883,6 +883,15 @@ def test_json_generate_as_json_convert_to_proc assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id }) end + def test_as_json_nan_does_not_call_to_json + def (obj = Object.new).to_json(*) + "null" + end + assert_raise(JSON::GeneratorError) do + JSON.generate(Float::NAN, strict: true, as_json: proc { obj }) + end + end + def assert_float_roundtrip(expected, actual) assert_equal(expected, JSON.generate(actual)) assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}") From c06c2203ed14e67dc12486c815b246395ef711e1 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:54:10 +0100 Subject: [PATCH 1496/2435] [ruby/prism] Fix the ripper translator to parse as the current ruby Otherwise, it uses the latest prism version https://github.com/ruby/prism/commit/86406f63aa --- lib/prism/translation/ripper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 6ea98fc1eaa598..e488b7c5cf0c72 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -71,7 +71,7 @@ def self.parse(src, filename = "(ripper)", lineno = 1) # [[1, 13], :on_kw, "end", END ]] # def self.lex(src, filename = "-", lineno = 1, raise_errors: false) - result = Prism.lex_compat(src, filepath: filename, line: lineno) + result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current") if result.failure? && raise_errors raise SyntaxError, result.errors.first.message @@ -3295,7 +3295,7 @@ def visit_yield_node(node) # Lazily initialize the parse result. def result - @result ||= Prism.parse(source, partial_script: true) + @result ||= Prism.parse(source, partial_script: true, version: "current") end ########################################################################## From 17bcd71e4218994bfb6c2d398fa784ccd74d2f2c Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:41:44 +0100 Subject: [PATCH 1497/2435] [ruby/prism] Clean up test excludes Mostly not having to list version-specific excludes when testing against ripper/parse.y Also don't test new syntax additions against the parser gems. The version support for them may (or may not) be expanded but we shouldn't bother while the ruby version hasn't even released yet. (ruby_parser translation is not versioned, so let as is for now) I also removed excludes that have since been implemented by parse.y https://github.com/ruby/prism/commit/e5a0221c37 --- test/prism/errors_test.rb | 2 +- test/prism/fixtures_test.rb | 16 +--------------- test/prism/lex_test.rb | 32 +++++++++----------------------- test/prism/locals_test.rb | 19 +++---------------- test/prism/ruby/parser_test.rb | 13 ++----------- test/prism/ruby/ripper_test.rb | 32 +++++++++++--------------------- test/prism/snippets_test.rb | 2 +- test/prism/test_helper.rb | 22 ++++++++++++++++------ 8 files changed, 44 insertions(+), 94 deletions(-) diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index bd7a8a638166c5..706b7395574e05 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -88,7 +88,7 @@ def assert_errors(filepath, version) expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8) source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "") - refute_valid_syntax(source) if current_major_minor == version + refute_valid_syntax(source) if CURRENT_MAJOR_MINOR == version result = Prism.parse(source, version: version) errors = result.errors diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 2aebb1847782dc..7df97029d3aa52 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -24,23 +24,9 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end - if RUBY_VERSION < "3.4.0" - except << "3.4/circular_parameters.txt" - end - - # Valid only on Ruby 3.3 - except << "3.3-3.3/block_args_in_array_assignment.txt" - except << "3.3-3.3/it_with_ordinary_parameter.txt" - except << "3.3-3.3/keyword_args_in_array_assignment.txt" - except << "3.3-3.3/return_in_sclass.txt" - - # Leaving these out until they are supported by parse.y. - except << "4.0/leading_logical.txt" - except << "4.0/endless_methods_command_call.txt" - # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" - Fixture.each(except: except) do |fixture| + Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } end end diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 19dd845d755621..68e47a096408cd 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -7,19 +7,13 @@ module Prism class LexTest < TestCase except = [ - # It seems like there are some oddities with nested heredocs and ripper. - # Waiting for feedback on https://bugs.ruby-lang.org/issues/19838. - "seattlerb/heredoc_nested.txt", - "whitequark/dedenting_heredoc.txt", - # Ripper seems to have a bug that the regex portions before and after - # the heredoc are combined into a single token. See - # https://bugs.ruby-lang.org/issues/19838. + # https://bugs.ruby-lang.org/issues/21756 "spanning_heredoc.txt", - "spanning_heredoc_newlines.txt", - # Prism emits a single :on_tstring_content in <<- style heredocs when there - # is a line continuation preceded by escaped backslashes. It should emit two, same - # as if the backslashes are not present. + # Prism emits a single string in some cases when ripper splits them up + "whitequark/dedenting_heredoc.txt", "heredocs_with_fake_newlines.txt", + # Prism emits BEG for `on_regexp_end` + "spanning_heredoc_newlines.txt", ] if RUBY_VERSION < "3.3.0" @@ -42,17 +36,11 @@ class LexTest < TestCase except << "whitequark/ruby_bug_19281.txt" end - # https://bugs.ruby-lang.org/issues/20925 - except << "4.0/leading_logical.txt" - - # https://bugs.ruby-lang.org/issues/17398#note-12 - except << "4.0/endless_methods_command_call.txt" - # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" - Fixture.each_with_version(except: except) do |fixture, version| - define_method(fixture.test_name(version)) { assert_lex(fixture, version) } + Fixture.each_for_current_ruby(except: except) do |fixture| + define_method(fixture.test_name) { assert_lex(fixture) } end def test_lex_file @@ -97,12 +85,10 @@ def test_parse_lex_file private - def assert_lex(fixture, version) - return unless current_major_minor == version - + def assert_lex(fixture) source = fixture.read - result = Prism.lex_compat(source, version: version) + result = Prism.lex_compat(source, version: "current") assert_equal [], result.errors Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)| diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 814c9a9978d84a..4f8d6080e8075d 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -13,11 +13,6 @@ # in comparing the locals because they will be the same. return if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism -# In Ruby 3.4.0, the local table for method forwarding changed. But 3.4.0 can -# refer to the dev version, so while 3.4.0 still isn't released, we need to -# check if we have a high enough revision. -return if RubyVM::InstructionSequence.compile("def foo(...); end").to_a[13][2][2][10].length != 1 - # Omit tests if running on a 32-bit machine because there is a bug with how # Ruby is handling large ISeqs on 32-bit machines return if RUBY_PLATFORM =~ /i686/ @@ -31,19 +26,11 @@ class LocalsTest < TestCase # CRuby is eliminating dead code. "whitequark/ruby_bug_10653.txt", - # Valid only on Ruby 3.3 - "3.3-3.3/block_args_in_array_assignment.txt", - "3.3-3.3/it_with_ordinary_parameter.txt", - "3.3-3.3/keyword_args_in_array_assignment.txt", - "3.3-3.3/return_in_sclass.txt", - - # Leaving these out until they are supported by parse.y. - "4.0/leading_logical.txt", - "4.0/endless_methods_command_call.txt", - "command_method_call_2.txt" + # https://bugs.ruby-lang.org/issues/21168#note-5 + "command_method_call_2.txt", ] - Fixture.each(except: except) do |fixture| + Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_locals(fixture) } end diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 648c44e77a6573..c972f0962bb6f9 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -65,15 +65,6 @@ class ParserTest < TestCase # 1.. && 2 "ranges.txt", - # https://bugs.ruby-lang.org/issues/20478 - "3.4/circular_parameters.txt", - - # Cannot yet handling leading logical operators. - "4.0/leading_logical.txt", - - # Ruby >= 4.0 specific syntax - "4.0/endless_methods_command_call.txt", - # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", ] @@ -148,7 +139,7 @@ class ParserTest < TestCase "whitequark/space_args_block.txt" ] - Fixture.each(except: skip_syntax_error) do |fixture| + Fixture.each_for_version(except: skip_syntax_error, version: "3.3") do |fixture| define_method(fixture.test_name) do assert_equal_parses( fixture, @@ -171,7 +162,7 @@ def test_non_prism_builder_class_deprecated if RUBY_VERSION >= "3.3" def test_current_parser_for_current_ruby - major, minor = current_major_minor.split(".") + major, minor = CURRENT_MAJOR_MINOR.split(".") # Let's just hope there never is a Ruby 3.10 or similar expected = major.to_i * 10 + minor.to_i assert_equal(expected, Translation::ParserCurrent.new.version) diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 400139acc03d01..bd63302efcf908 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -8,44 +8,34 @@ module Prism class RipperTest < TestCase # Skip these tests that Ripper is reporting the wrong results for. incorrect = [ - # Not yet supported. - "4.0/leading_logical.txt", - # Ripper incorrectly attributes the block to the keyword. - "seattlerb/block_break.txt", - "seattlerb/block_next.txt", "seattlerb/block_return.txt", - "whitequark/break_block.txt", - "whitequark/next_block.txt", "whitequark/return_block.txt", - # Ripper is not accounting for locals created by patterns using the ** - # operator within an `in` clause. - "seattlerb/parse_pattern_058.txt", - # Ripper cannot handle named capture groups in regular expressions. "regex.txt", - "regex_char_width.txt", - "whitequark/lvar_injecting_match.txt", # Ripper fails to understand some structures that span across heredocs. "spanning_heredoc.txt", - "3.3-3.3/block_args_in_array_assignment.txt", - "3.3-3.3/it_with_ordinary_parameter.txt", - "3.3-3.3/keyword_args_in_array_assignment.txt", - "3.3-3.3/return_in_sclass.txt", - - # https://bugs.ruby-lang.org/issues/20478 + # Ripper interprets circular keyword arguments as method calls. "3.4/circular_parameters.txt", - # https://bugs.ruby-lang.org/issues/17398#note-12 + # Ripper doesn't emit `args_add_block` when endless method is prefixed by modifier. "4.0/endless_methods_command_call.txt", # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", ] + if RUBY_VERSION.start_with?("3.3.") + incorrect += [ + "whitequark/lvar_injecting_match.txt", + "seattlerb/parse_pattern_058.txt", + "regex_char_width.txt", + ] + end + # Skip these tests that we haven't implemented yet. omitted = [ "dos_endings.txt", @@ -68,7 +58,7 @@ class RipperTest < TestCase "whitequark/slash_newline_in_heredocs.txt" ] - Fixture.each(except: incorrect | omitted) do |fixture| + Fixture.each_for_current_ruby(except: incorrect | omitted) do |fixture| define_method(fixture.test_name) { assert_ripper(fixture.read) } end diff --git a/test/prism/snippets_test.rb b/test/prism/snippets_test.rb index 3160442cc07653..3c28d27a250e22 100644 --- a/test/prism/snippets_test.rb +++ b/test/prism/snippets_test.rb @@ -18,7 +18,7 @@ class SnippetsTest < TestCase "whitequark/multiple_pattern_matches.txt" ] - Fixture.each_with_version(except: except) do |fixture, version| + Fixture.each_with_all_versions(except: except) do |fixture, version| define_method(fixture.test_name(version)) { assert_snippets(fixture, version) } end diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index 42555738cf318f..f78e68e87c107a 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -72,7 +72,18 @@ def self.each(except: [], &block) paths.each { |path| yield Fixture.new(path) } end - def self.each_with_version(except: [], &block) + def self.each_for_version(except: [], version:, &block) + each(except: except) do |fixture| + next unless TestCase.ruby_versions_for(fixture.path).include?(version) + yield fixture + end + end + + def self.each_for_current_ruby(except: [], &block) + each_for_version(except: except, version: CURRENT_MAJOR_MINOR, &block) + end + + def self.each_with_all_versions(except: [], &block) each(except: except) do |fixture| TestCase.ruby_versions_for(fixture.path).each do |version| yield fixture, version @@ -232,6 +243,9 @@ def self.windows? # All versions that prism can parse SYNTAX_VERSIONS = %w[3.3 3.4 4.0] + # `RUBY_VERSION` with the patch version excluded + CURRENT_MAJOR_MINOR = RUBY_VERSION.split(".")[0, 2].join(".") + # Returns an array of ruby versions that a given filepath should test against: # test.txt # => all available versions # 3.4/test.txt # => versions since 3.4 (inclusive) @@ -250,13 +264,9 @@ def self.ruby_versions_for(filepath) end end - def current_major_minor - RUBY_VERSION.split(".")[0, 2].join(".") - end - if RUBY_VERSION >= "3.3.0" def test_all_syntax_versions_present - assert_include(SYNTAX_VERSIONS, current_major_minor) + assert_include(SYNTAX_VERSIONS, CURRENT_MAJOR_MINOR) end end From d3907245d7928279fcffd1af2903d16b2dbb2d29 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 5 Nov 2025 11:13:09 +0100 Subject: [PATCH 1498/2435] [ruby/psych] Fix usage of rb_struct_initialize() to pass an Array of members values and not a Hash * rb_struct_initialize() does not accept a Hash, and it's very brittle to pass `[{...}]` and to rely on that C function using rb_keyword_given_p(). It basically worked accidentally, by having **members in the caller of the caller. Such logic when Struct#initialize is defined in Ruby (as in TruffleRuby) is basically impossible to implement, because it's incorrectly treating positional arguments as keyword arguments. * rb_struct_initialize() is used in CRuby to set members of Data instances in marshal.c (there is no rb_data_initialize() yet). There, the code passes an Array of members values for Data (and for Struct which are not `keyword_init: true`): https://github.com/ruby/ruby/blob/48c7f349f68846e10d60ae77ad299a38ee014479/marshal.c#L2150-L2176 So we should do the same in psych. * Rename to init_data since it's only used for Data. * See https://github.com/ruby/psych/pull/692#discussion_r2483947279. https://github.com/ruby/psych/commit/3550148378 --- ext/psych/lib/psych/visitors/to_ruby.rb | 3 ++- ext/psych/psych_to_ruby.c | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index 580a74e9fb034d..2814ce1a695df0 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -219,7 +219,8 @@ def visit_Psych_Nodes_Mapping o revive_data_members(members, o) end data ||= allocate_anon_data(o, members) - init_struct(data, **members) + values = data.members.map { |m| members[m] } + init_data(data, values) data.freeze data diff --git a/ext/psych/psych_to_ruby.c b/ext/psych/psych_to_ruby.c index d473a5f840a1d7..53a038270c4d70 100644 --- a/ext/psych/psych_to_ruby.c +++ b/ext/psych/psych_to_ruby.c @@ -24,12 +24,9 @@ static VALUE path2class(VALUE self, VALUE path) return rb_path_to_class(path); } -static VALUE init_struct(VALUE self, VALUE data, VALUE attrs) +static VALUE init_data(VALUE self, VALUE data, VALUE values) { - VALUE args = rb_ary_new2(1); - rb_ary_push(args, attrs); - rb_struct_initialize(data, args); - + rb_struct_initialize(data, values); return data; } @@ -42,7 +39,7 @@ void Init_psych_to_ruby(void) VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); - rb_define_private_method(cPsychVisitorsToRuby, "init_struct", init_struct, 2); + rb_define_private_method(cPsychVisitorsToRuby, "init_data", init_data, 2); rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); rb_define_private_method(class_loader, "path2class", path2class, 1); } From 3aa674ad9923a4a362a29840fad8421a6be7131f Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 5 Nov 2025 12:01:21 +0100 Subject: [PATCH 1499/2435] [ruby/psych] Properly set the message of Exceptions on TruffleRuby * From https://github.com/truffleruby/truffleruby/commit/1f81db82d2969ff7c5de0dacdecb38252664f42c https://github.com/ruby/psych/commit/dbabe7aac6 --- ext/psych/psych_to_ruby.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/psych/psych_to_ruby.c b/ext/psych/psych_to_ruby.c index 53a038270c4d70..0132b2c94e28c7 100644 --- a/ext/psych/psych_to_ruby.c +++ b/ext/psych/psych_to_ruby.c @@ -10,7 +10,11 @@ static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) { VALUE e = rb_obj_alloc(klass); +#ifdef TRUFFLERUBY + rb_exc_set_message(e, mesg); +#else rb_iv_set(e, "mesg", mesg); +#endif return e; } From 8d2f73d70a6f99335c2fc7ebb49fb9d95e431047 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 14:28:36 -0500 Subject: [PATCH 1500/2435] [ruby/prism] Introduce PM_NODE_UPCAST macro for readability https://github.com/ruby/prism/commit/7eb169513a --- prism/prism.c | 1024 ++++++++++++----------- prism/templates/include/prism/ast.h.erb | 37 +- 2 files changed, 536 insertions(+), 525 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index ac6fbd9f6b461a..044e61a1b1d5c5 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18,6 +18,12 @@ pm_version(void) { #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) +/******************************************************************************/ +/* Helpful node-related macros */ +/******************************************************************************/ + +#define UP PM_NODE_UPCAST + /******************************************************************************/ /* Lex mode manipulations */ /******************************************************************************/ @@ -1049,25 +1055,25 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { if (cast->ensure_clause != NULL) { if (cast->rescue_clause != NULL) { - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) cast->rescue_clause); + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->rescue_clause)); if (vn != NULL) return vn; } if (cast->statements != NULL) { - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) cast->statements); + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); if (vn != NULL) return vn; } - node = (pm_node_t *) cast->ensure_clause; + node = UP(cast->ensure_clause); } else if (cast->rescue_clause != NULL) { if (cast->statements == NULL) return NULL; - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) cast->statements); + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); if (vn == NULL) return NULL; if (void_node == NULL) void_node = vn; for (pm_rescue_node_t *rescue_clause = cast->rescue_clause; rescue_clause != NULL; rescue_clause = rescue_clause->subsequent) { - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) rescue_clause->statements); + pm_node_t *vn = pm_check_value_expression(parser, UP(rescue_clause->statements)); if (vn == NULL) { void_node = NULL; break; @@ -1078,24 +1084,24 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { } if (cast->else_clause != NULL) { - node = (pm_node_t *) cast->else_clause; + node = UP(cast->else_clause); } else { return void_node; } } else { - node = (pm_node_t *) cast->statements; + node = UP(cast->statements); } break; } case PM_ENSURE_NODE: { pm_ensure_node_t *cast = (pm_ensure_node_t *) node; - node = (pm_node_t *) cast->statements; + node = UP(cast->statements); break; } case PM_PARENTHESES_NODE: { pm_parentheses_node_t *cast = (pm_parentheses_node_t *) node; - node = (pm_node_t *) cast->body; + node = UP(cast->body); break; } case PM_STATEMENTS_NODE: { @@ -1108,7 +1114,7 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { if (cast->statements == NULL || cast->subsequent == NULL) { return NULL; } - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) cast->statements); + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); if (vn == NULL) { return NULL; } @@ -1123,19 +1129,19 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { if (cast->statements == NULL || cast->else_clause == NULL) { return NULL; } - pm_node_t *vn = pm_check_value_expression(parser, (pm_node_t *) cast->statements); + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); if (vn == NULL) { return NULL; } if (void_node == NULL) { void_node = vn; } - node = (pm_node_t *) cast->else_clause; + node = UP(cast->else_clause); break; } case PM_ELSE_NODE: { pm_else_node_t *cast = (pm_else_node_t *) node; - node = (pm_node_t *) cast->statements; + node = UP(cast->statements); break; } case PM_AND_NODE: { @@ -1635,7 +1641,7 @@ pm_arguments_validate_block(pm_parser_t *parser, pm_arguments_t *arguments, pm_b // If we didn't hit a case before this check, then at this point we need to // add a syntax error. - pm_parser_err_node(parser, (pm_node_t *) block, PM_ERR_ARGUMENT_UNEXPECTED_BLOCK); + pm_parser_err_node(parser, UP(block), PM_ERR_ARGUMENT_UNEXPECTED_BLOCK); } /******************************************************************************/ @@ -2059,9 +2065,9 @@ pm_arguments_node_arguments_append(pm_arguments_node_t *node, pm_node_t *argumen if (PM_NODE_TYPE_P(argument, PM_SPLAT_NODE)) { if (PM_NODE_FLAG_P(node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) { - pm_node_flag_set((pm_node_t *) node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_MULTIPLE_SPLATS); + pm_node_flag_set(UP(node), PM_ARGUMENTS_NODE_FLAGS_CONTAINS_MULTIPLE_SPLATS); } else { - pm_node_flag_set((pm_node_t *) node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT); + pm_node_flag_set(UP(node), PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT); } } } @@ -2098,11 +2104,11 @@ pm_array_node_elements_append(pm_array_node_t *node, pm_node_t *element) { // If the element is not a static literal, then the array is not a static // literal. Turn that flag off. if (PM_NODE_TYPE_P(element, PM_ARRAY_NODE) || PM_NODE_TYPE_P(element, PM_HASH_NODE) || PM_NODE_TYPE_P(element, PM_RANGE_NODE) || !PM_NODE_FLAG_P(element, PM_NODE_FLAG_STATIC_LITERAL)) { - pm_node_flag_unset((pm_node_t *)node, PM_NODE_FLAG_STATIC_LITERAL); + pm_node_flag_unset(UP(node), PM_NODE_FLAG_STATIC_LITERAL); } if (PM_NODE_TYPE_P(element, PM_SPLAT_NODE)) { - pm_node_flag_set((pm_node_t *)node, PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT); + pm_node_flag_set(UP(node), PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT); } } @@ -2475,7 +2481,7 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) */ static void pm_block_parameters_node_append_local(pm_block_parameters_node_t *node, const pm_block_local_variable_node_t *local) { - pm_node_list_append(&node->locals, (pm_node_t *) local); + pm_node_list_append(&node->locals, UP(local)); if (node->base.location.start == NULL) node->base.location.start = local->base.location.start; node->base.location.end = local->base.location.end; @@ -2623,7 +2629,7 @@ pm_call_node_call_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *o node->block = arguments->block; if (operator->type == PM_TOKEN_AMPERSAND_DOT) { - pm_node_flag_set((pm_node_t *)node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); + pm_node_flag_set(UP(node), PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); } /** @@ -2736,7 +2742,7 @@ pm_call_node_shorthand_create(pm_parser_t *parser, pm_node_t *receiver, pm_token node->block = arguments->block; if (operator->type == PM_TOKEN_AMPERSAND_DOT) { - pm_node_flag_set((pm_node_t *)node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); + pm_node_flag_set(UP(node), PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); } node->name = pm_parser_constant_id_constant(parser, "call", 4); @@ -3306,7 +3312,7 @@ pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_ *node = (pm_class_variable_write_node_t) { .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_WRITE_NODE, flags, read_node->base.location.start, value->location.end), .name = read_node->name, - .name_loc = PM_LOCATION_NODE_VALUE((pm_node_t *) read_node), + .name_loc = PM_LOCATION_NODE_VALUE(UP(read_node)), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value }; @@ -3510,7 +3516,7 @@ pm_def_node_receiver_check(pm_parser_t *parser, const pm_node_t *node) { switch (PM_NODE_TYPE(node)) { case PM_BEGIN_NODE: { const pm_begin_node_t *cast = (pm_begin_node_t *) node; - if (cast->statements != NULL) pm_def_node_receiver_check(parser, (pm_node_t *) cast->statements); + if (cast->statements != NULL) pm_def_node_receiver_check(parser, UP(cast->statements)); break; } case PM_PARENTHESES_NODE: { @@ -3719,7 +3725,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { pm_node_t *right; if (nodes->size == 1) { - right = (pm_node_t *) pm_missing_node_create(parser, left->location.end, left->location.end); + right = UP(pm_missing_node_create(parser, left->location.end, left->location.end)); } else { right = nodes->nodes[nodes->size - 1]; assert(PM_NODE_TYPE_P(right, PM_SPLAT_NODE)); @@ -3851,11 +3857,11 @@ pm_float_node_imaginary_create(pm_parser_t *parser, const pm_token_t *token) { pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), - .numeric = (pm_node_t *) pm_float_node_create(parser, &((pm_token_t) { + .numeric = UP(pm_float_node_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT, .start = token->start, .end = token->end - 1 - })) + }))) }; return node; @@ -3920,11 +3926,11 @@ pm_float_node_rational_imaginary_create(pm_parser_t *parser, const pm_token_t *t pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), - .numeric = (pm_node_t *) pm_float_node_rational_create(parser, &((pm_token_t) { + .numeric = UP(pm_float_node_rational_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT_RATIONAL, .start = token->start, .end = token->end - 1 - })) + }))) }; return node; @@ -4252,7 +4258,7 @@ pm_hash_node_elements_append(pm_hash_node_t *hash, pm_node_t *element) { } if (!static_literal) { - pm_node_flag_unset((pm_node_t *)hash, PM_NODE_FLAG_STATIC_LITERAL); + pm_node_flag_unset(UP(hash), PM_NODE_FLAG_STATIC_LITERAL); } } @@ -4350,7 +4356,7 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to .predicate = predicate, .then_keyword_loc = PM_LOCATION_TOKEN_VALUE(qmark), .statements = if_statements, - .subsequent = (pm_node_t *) else_node, + .subsequent = UP(else_node), .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE }; @@ -4438,11 +4444,11 @@ pm_integer_node_imaginary_create(pm_parser_t *parser, pm_node_flags_t base, cons pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), - .numeric = (pm_node_t *) pm_integer_node_create(parser, base, &((pm_token_t) { + .numeric = UP(pm_integer_node_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER, .start = token->start, .end = token->end - 1 - })) + }))) }; return node; @@ -4488,11 +4494,11 @@ pm_integer_node_rational_imaginary_create(pm_parser_t *parser, pm_node_flags_t b pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), - .numeric = (pm_node_t *) pm_integer_node_rational_create(parser, base, &((pm_token_t) { + .numeric = UP(pm_integer_node_rational_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER_RATIONAL, .start = token->start, .end = token->end - 1 - })) + }))) }; return node; @@ -4652,7 +4658,7 @@ pm_interpolated_node_append(pm_node_t *node, pm_node_list_t *parts, pm_node_t *p break; } case PM_EMBEDDED_VARIABLE_NODE: - pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + pm_node_flag_unset(UP(node), PM_NODE_FLAG_STATIC_LITERAL); break; default: assert(false && "unexpected node type"); @@ -4688,14 +4694,14 @@ pm_interpolated_regular_expression_node_append(pm_interpolated_regular_expressio node->base.location.end = part->location.end; } - pm_interpolated_node_append((pm_node_t *) node, &node->parts, part); + pm_interpolated_node_append(UP(node), &node->parts, part); } static inline void pm_interpolated_regular_expression_node_closing_set(pm_parser_t *parser, pm_interpolated_regular_expression_node_t *node, const pm_token_t *closing) { node->closing_loc = PM_LOCATION_TOKEN_VALUE(closing); node->base.location.end = closing->end; - pm_node_flag_set((pm_node_t *) node, pm_regular_expression_flags_create(parser, closing)); + pm_node_flag_set(UP(node), pm_regular_expression_flags_create(parser, closing)); } /** @@ -4741,7 +4747,7 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ // because concatenating two frozen strings (`'foo' 'bar'`) is still frozen. This holds true for // as long as this interpolation only consists of other string literals. if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { - pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + pm_node_flag_unset(UP(node), PM_NODE_FLAG_STATIC_LITERAL); } part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE); break; @@ -4860,7 +4866,7 @@ pm_interpolated_symbol_node_append(pm_interpolated_symbol_node_t *node, pm_node_ node->base.location.start = part->location.start; } - pm_interpolated_node_append((pm_node_t *) node, &node->parts, part); + pm_interpolated_node_append(UP(node), &node->parts, part); node->base.location.end = MAX(node->base.location.end, part->location.end); } @@ -4913,7 +4919,7 @@ pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *openi static inline void pm_interpolated_xstring_node_append(pm_interpolated_x_string_node_t *node, pm_node_t *part) { - pm_interpolated_node_append((pm_node_t *) node, &node->parts, part); + pm_interpolated_node_append(UP(node), &node->parts, part); node->base.location.end = part->location.end; } @@ -4974,7 +4980,7 @@ pm_keyword_hash_node_elements_append(pm_keyword_hash_node_t *hash, pm_node_t *el // If the element being added is not an AssocNode or does not have a symbol // key, then we want to turn the SYMBOL_KEYS flag off. if (!PM_NODE_TYPE_P(element, PM_ASSOC_NODE) || !PM_NODE_TYPE_P(((pm_assoc_node_t *) element)->key, PM_SYMBOL_NODE)) { - pm_node_flag_unset((pm_node_t *)hash, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS); + pm_node_flag_unset(UP(hash), PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS); } pm_node_list_append(&hash->elements, element); @@ -5618,8 +5624,8 @@ pm_parameters_node_requireds_append(pm_parameters_node_t *params, pm_node_t *par */ static void pm_parameters_node_optionals_append(pm_parameters_node_t *params, pm_optional_parameter_node_t *param) { - pm_parameters_node_location_set(params, (pm_node_t *) param); - pm_node_list_append(¶ms->optionals, (pm_node_t *) param); + pm_parameters_node_location_set(params, UP(param)); + pm_node_list_append(¶ms->optionals, UP(param)); } /** @@ -5665,7 +5671,7 @@ pm_parameters_node_keyword_rest_set(pm_parameters_node_t *params, pm_node_t *par static void pm_parameters_node_block_set(pm_parameters_node_t *params, pm_block_parameter_node_t *param) { assert(params->block == NULL); - pm_parameters_node_location_set(params, (pm_node_t *) param); + pm_parameters_node_location_set(params, UP(param)); params->block = param; } @@ -6546,7 +6552,7 @@ pm_symbol_node_label_create(pm_parser_t *parser, const pm_token_t *token) { assert((label.end - label.start) >= 0); pm_string_shared_init(&node->unescaped, label.start, label.end); - pm_node_flag_set((pm_node_t *) node, parse_symbol_encoding(parser, &label, &node->unescaped, false)); + pm_node_flag_set(UP(node), parse_symbol_encoding(parser, &label, &node->unescaped, false)); break; } @@ -6621,7 +6627,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const }; pm_token_t content = { .type = PM_TOKEN_IDENTIFIER, .start = node->content_loc.start, .end = node->content_loc.end }; - pm_node_flag_set((pm_node_t *) new_node, parse_symbol_encoding(parser, &content, &node->unescaped, true)); + pm_node_flag_set(UP(new_node), parse_symbol_encoding(parser, &content, &node->unescaped, true)); // We are explicitly _not_ using pm_node_destroy here because we don't want // to trash the unescaped string. We could instead copy the string if we @@ -12434,7 +12440,7 @@ parse_starred_expression(pm_parser_t *parser, pm_binding_power_t binding_power, if (accept1(parser, PM_TOKEN_USTAR)) { pm_token_t operator = parser->previous; pm_node_t *expression = parse_value_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_splat_node_create(parser, &operator, expression); + return UP(pm_splat_node_create(parser, &operator, expression)); } return parse_value_expression(parser, binding_power, accepts_command_call, false, diag_id, depth); @@ -12559,7 +12565,7 @@ parse_unwriteable_target(pm_parser_t *parser, pm_node_t *target) { pm_local_variable_target_node_t *result = pm_local_variable_target_node_create(parser, &target->location, name, 0); pm_node_destroy(parser, target); - return (pm_node_t *) result; + return UP(result); } /** @@ -12634,7 +12640,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p } case PM_IT_LOCAL_VARIABLE_READ_NODE: { pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); - pm_node_t *node = (pm_node_t *) pm_local_variable_target_node_create(parser, &target->location, name, 0); + pm_node_t *node = UP(pm_local_variable_target_node_create(parser, &target->location, name, 0)); pm_node_unreference(parser, target); pm_node_destroy(parser, target); @@ -12660,7 +12666,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p splat->expression = parse_target(parser, splat->expression, multiple, true); } - return (pm_node_t *) splat; + return UP(splat); } case PM_CALL_NODE: { pm_call_node_t *call = (pm_call_node_t *) target; @@ -12691,7 +12697,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p pm_constant_id_t name = pm_parser_local_add_location(parser, message_loc.start, message_loc.end, 0); pm_node_destroy(parser, target); - return (pm_node_t *) pm_local_variable_target_node_create(parser, &message_loc, name, 0); + return UP(pm_local_variable_target_node_create(parser, &message_loc, name, 0)); } if (*call->message_loc.start == '_' || parser->encoding->alnum_char(call->message_loc.start, call->message_loc.end - call->message_loc.start)) { @@ -12700,7 +12706,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p } parse_write_name(parser, &call->name); - return (pm_node_t *) pm_call_target_node_create(parser, call); + return UP(pm_call_target_node_create(parser, call)); } } @@ -12708,7 +12714,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p // an aref expression, and we can transform it into an aset // expression. if (PM_NODE_FLAG_P(call, PM_CALL_NODE_FLAGS_INDEX)) { - return (pm_node_t *) pm_index_target_node_create(parser, call); + return UP(pm_index_target_node_create(parser, call)); } } PRISM_FALLTHROUGH @@ -12751,7 +12757,7 @@ parse_shareable_constant_write(pm_parser_t *parser, pm_node_t *write) { pm_shareable_constant_value_t shareable_constant = pm_parser_scope_shareable_constant_get(parser); if (shareable_constant != PM_SCOPE_SHAREABLE_CONSTANT_NONE) { - return (pm_node_t *) pm_shareable_constant_node_create(parser, write, shareable_constant); + return UP(pm_shareable_constant_node_create(parser, write, shareable_constant)); } return write; @@ -12769,10 +12775,10 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod case PM_CLASS_VARIABLE_READ_NODE: { pm_class_variable_write_node_t *node = pm_class_variable_write_node_create(parser, (pm_class_variable_read_node_t *) target, operator, value); pm_node_destroy(parser, target); - return (pm_node_t *) node; + return UP(node); } case PM_CONSTANT_PATH_NODE: { - pm_node_t *node = (pm_node_t *) pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value); + pm_node_t *node = UP(pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value)); if (context_def_p(parser)) { pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_IN_METHOD); @@ -12781,7 +12787,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod return parse_shareable_constant_write(parser, node); } case PM_CONSTANT_READ_NODE: { - pm_node_t *node = (pm_node_t *) pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value); + pm_node_t *node = UP(pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value)); if (context_def_p(parser)) { pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_IN_METHOD); @@ -12797,7 +12803,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod case PM_GLOBAL_VARIABLE_READ_NODE: { pm_global_variable_write_node_t *node = pm_global_variable_write_node_create(parser, target, operator, value); pm_node_destroy(parser, target); - return (pm_node_t *) node; + return UP(node); } case PM_LOCAL_VARIABLE_READ_NODE: { pm_local_variable_read_node_t *local_read = (pm_local_variable_read_node_t *) target; @@ -12817,11 +12823,11 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_locals_unread(&scope->locals, name); pm_node_destroy(parser, target); - return (pm_node_t *) pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator); + return UP(pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator)); } case PM_IT_LOCAL_VARIABLE_READ_NODE: { pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2); - pm_node_t *node = (pm_node_t *) pm_local_variable_write_node_create(parser, name, 0, value, &target->location, operator); + pm_node_t *node = UP(pm_local_variable_write_node_create(parser, name, 0, value, &target->location, operator)); pm_node_unreference(parser, target); pm_node_destroy(parser, target); @@ -12829,12 +12835,12 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod return node; } case PM_INSTANCE_VARIABLE_READ_NODE: { - pm_node_t *write_node = (pm_node_t *) pm_instance_variable_write_node_create(parser, (pm_instance_variable_read_node_t *) target, operator, value); + pm_node_t *write_node = UP(pm_instance_variable_write_node_create(parser, (pm_instance_variable_read_node_t *) target, operator, value)); pm_node_destroy(parser, target); return write_node; } case PM_MULTI_TARGET_NODE: - return (pm_node_t *) pm_multi_write_node_create(parser, (pm_multi_target_node_t *) target, operator, value); + return UP(pm_multi_write_node_create(parser, (pm_multi_target_node_t *) target, operator, value)); case PM_SPLAT_NODE: { pm_splat_node_t *splat = (pm_splat_node_t *) target; @@ -12843,9 +12849,9 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod } pm_multi_target_node_t *multi_target = pm_multi_target_node_create(parser); - pm_multi_target_node_targets_append(parser, multi_target, (pm_node_t *) splat); + pm_multi_target_node_targets_append(parser, multi_target, UP(splat)); - return (pm_node_t *) pm_multi_write_node_create(parser, multi_target, operator, value); + return UP(pm_multi_write_node_create(parser, multi_target, operator, value)); } case PM_CALL_NODE: { pm_call_node_t *call = (pm_call_node_t *) target; @@ -12877,7 +12883,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_node_destroy(parser, target); pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, message.start, message.end); - target = (pm_node_t *) pm_local_variable_write_node_create(parser, constant_id, 0, value, &message, operator); + target = UP(pm_local_variable_write_node_create(parser, constant_id, 0, value, &message, operator)); pm_refute_numbered_parameter(parser, message.start, message.end); return target; @@ -12902,9 +12908,9 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod call->equal_loc = PM_LOCATION_TOKEN_VALUE(operator); parse_write_name(parser, &call->name); - pm_node_flag_set((pm_node_t *) call, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); + pm_node_flag_set(UP(call), PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); - return (pm_node_t *) call; + return UP(call); } } @@ -12925,7 +12931,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // Ensure that the arguments for []= don't contain keywords pm_index_arguments_check(parser, call->arguments, call->block); - pm_node_flag_set((pm_node_t *) call, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); + pm_node_flag_set(UP(call), PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); return target; } @@ -12976,7 +12982,7 @@ parse_unwriteable_write(pm_parser_t *parser, pm_node_t *target, const pm_token_t pm_local_variable_write_node_t *result = pm_local_variable_write_node_create(parser, name, 0, value, &target->location, equals); pm_node_destroy(parser, target); - return (pm_node_t *) result; + return UP(result); } /** @@ -13013,7 +13019,7 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b name = parse_target(parser, name, true, true); } - pm_node_t *splat = (pm_node_t *) pm_splat_node_create(parser, &star_operator, name); + pm_node_t *splat = UP(pm_splat_node_create(parser, &star_operator, name)); pm_multi_target_node_targets_append(parser, result, splat); has_rest = true; } else if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { @@ -13031,13 +13037,13 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b } else if (!match1(parser, PM_TOKEN_EOF)) { // If we get here, then we have a trailing , in a multi target node. // We'll add an implicit rest node to represent this. - pm_node_t *rest = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); + pm_node_t *rest = UP(pm_implicit_rest_node_create(parser, &parser->previous)); pm_multi_target_node_targets_append(parser, result, rest); break; } } - return (pm_node_t *) result; + return UP(result); } /** @@ -13231,7 +13237,7 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod pm_parser_scope_forwarding_keywords_check(parser, &operator); } - element = (pm_node_t *) pm_assoc_splat_node_create(parser, value, &operator); + element = UP(pm_assoc_splat_node_create(parser, value, &operator)); contains_keyword_splat = true; break; } @@ -13239,7 +13245,7 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod pm_token_t label = parser->current; parser_lex(parser); - pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &label); + pm_node_t *key = UP(pm_symbol_node_label_create(parser, &label)); pm_hash_key_static_literals_add(parser, literals, key); pm_token_t operator = not_provided(parser); @@ -13250,7 +13256,7 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod } else { if (parser->encoding->isupper_char(label.start, (label.end - 1) - label.start)) { pm_token_t constant = { .type = PM_TOKEN_CONSTANT, .start = label.start, .end = label.end - 1 }; - value = (pm_node_t *) pm_constant_read_node_create(parser, &constant); + value = UP(pm_constant_read_node_create(parser, &constant)); } else { int depth = -1; pm_token_t identifier = { .type = PM_TOKEN_IDENTIFIER, .start = label.start, .end = label.end - 1 }; @@ -13262,17 +13268,17 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod } if (depth == -1) { - value = (pm_node_t *) pm_call_node_variable_call_create(parser, &identifier); + value = UP(pm_call_node_variable_call_create(parser, &identifier)); } else { - value = (pm_node_t *) pm_local_variable_read_node_create(parser, &identifier, (uint32_t) depth); + value = UP(pm_local_variable_read_node_create(parser, &identifier, (uint32_t) depth)); } } value->location.end++; - value = (pm_node_t *) pm_implicit_node_create(parser, value); + value = UP(pm_implicit_node_create(parser, value)); } - element = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, value); + element = UP(pm_assoc_node_create(parser, key, &operator, value)); break; } default: { @@ -13295,7 +13301,7 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod } pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_HASH_VALUE, (uint16_t) (depth + 1)); - element = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, value); + element = UP(pm_assoc_node_create(parser, key, &operator, value)); break; } } @@ -13373,16 +13379,16 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for } pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser); - argument = (pm_node_t *) hash; + argument = UP(hash); pm_static_literals_t hash_keys = { 0 }; - bool contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) hash, (uint16_t) (depth + 1)); + bool contains_keyword_splat = parse_assocs(parser, &hash_keys, UP(hash), (uint16_t) (depth + 1)); parse_arguments_append(parser, arguments, argument); pm_node_flags_t flags = PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS; if (contains_keyword_splat) flags |= PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT; - pm_node_flag_set((pm_node_t *) arguments->arguments, flags); + pm_node_flag_set(UP(arguments->arguments), flags); pm_static_literals_free(&hash_keys); parsed_bare_hash = true; @@ -13400,7 +13406,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_parser_scope_forwarding_block_check(parser, &operator); } - argument = (pm_node_t *) pm_block_argument_node_create(parser, &operator, expression); + argument = UP(pm_block_argument_node_create(parser, &operator, expression)); if (parsed_block_argument) { parse_arguments_append(parser, arguments, argument); } else { @@ -13420,7 +13426,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for if (match4(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_COMMA, PM_TOKEN_SEMICOLON, PM_TOKEN_BRACKET_RIGHT)) { pm_parser_scope_forwarding_positionals_check(parser, &operator); - argument = (pm_node_t *) pm_splat_node_create(parser, &operator, NULL); + argument = UP(pm_splat_node_create(parser, &operator, NULL)); if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT); } @@ -13431,7 +13437,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_parser_err(parser, operator.start, expression->location.end, PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT); } - argument = (pm_node_t *) pm_splat_node_create(parser, &operator, expression); + argument = UP(pm_splat_node_create(parser, &operator, expression)); } parse_arguments_append(parser, arguments, argument); @@ -13456,16 +13462,16 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_parser_err(parser, range->operator_loc.start, range->operator_loc.end, PM_ERR_UNEXPECTED_RANGE_OPERATOR); } - argument = (pm_node_t *) pm_range_node_create(parser, NULL, &operator, right); + argument = UP(pm_range_node_create(parser, NULL, &operator, right)); } else { pm_parser_scope_forwarding_all_check(parser, &parser->previous); if (parsed_first_argument && terminator == PM_TOKEN_EOF) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_FORWARDING_UNBOUND); } - argument = (pm_node_t *) pm_forwarding_arguments_node_create(parser, &parser->previous); + argument = UP(pm_forwarding_arguments_node_create(parser, &parser->previous)); parse_arguments_append(parser, arguments, argument); - pm_node_flag_set((pm_node_t *) arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_FORWARDING); + pm_node_flag_set(UP(arguments->arguments), PM_ARGUMENTS_NODE_FLAGS_CONTAINS_FORWARDING); arguments->has_forwarding = true; parsed_forwarding_arguments = true; break; @@ -13502,17 +13508,17 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for // Finish parsing the one we are part way through. pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_HASH_VALUE, (uint16_t) (depth + 1)); - argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value); + argument = UP(pm_assoc_node_create(parser, argument, &operator, value)); pm_keyword_hash_node_elements_append(bare_hash, argument); - argument = (pm_node_t *) bare_hash; + argument = UP(bare_hash); // Then parse more if we have a comma if (accept1(parser, PM_TOKEN_COMMA) && ( token_begins_expression_p(parser->current.type) || match2(parser, PM_TOKEN_USTAR_STAR, PM_TOKEN_LABEL) )) { - contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) bare_hash, (uint16_t) (depth + 1)); + contains_keyword_splat = parse_assocs(parser, &hash_keys, UP(bare_hash), (uint16_t) (depth + 1)); } pm_static_literals_free(&hash_keys); @@ -13524,7 +13530,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_node_flags_t flags = 0; if (contains_keywords) flags |= PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS; if (contains_keyword_splat) flags |= PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT; - pm_node_flag_set((pm_node_t *) arguments->arguments, flags); + pm_node_flag_set(UP(arguments->arguments), flags); break; } @@ -13601,33 +13607,33 @@ parse_required_destructured_parameter(pm_parser_t *parser) { // commas, so here we'll assume this is a mistake of the user not // knowing it's not allowed here. if (node->lefts.size > 0 && match1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { - param = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); + param = UP(pm_implicit_rest_node_create(parser, &parser->previous)); pm_multi_target_node_targets_append(parser, node, param); pm_parser_err_current(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); break; } if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { - param = (pm_node_t *) parse_required_destructured_parameter(parser); + param = UP(parse_required_destructured_parameter(parser)); } else if (accept1(parser, PM_TOKEN_USTAR)) { pm_token_t star = parser->previous; pm_node_t *value = NULL; if (accept1(parser, PM_TOKEN_IDENTIFIER)) { pm_token_t name = parser->previous; - value = (pm_node_t *) pm_required_parameter_node_create(parser, &name); + value = UP(pm_required_parameter_node_create(parser, &name)); if (pm_parser_parameter_name_check(parser, &name)) { pm_node_flag_set_repeated_parameter(value); } pm_parser_local_add_token(parser, &name, 1); } - param = (pm_node_t *) pm_splat_node_create(parser, &star, value); + param = UP(pm_splat_node_create(parser, &star, value)); } else { expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_EXPECT_IDENT_REQ_PARAMETER); pm_token_t name = parser->previous; - param = (pm_node_t *) pm_required_parameter_node_create(parser, &name); + param = UP(pm_required_parameter_node_create(parser, &name)); if (pm_parser_parameter_name_check(parser, &name)) { pm_node_flag_set_repeated_parameter(param); } @@ -13740,7 +13746,7 @@ parse_parameters( switch (parser->current.type) { case PM_TOKEN_PARENTHESIS_LEFT: { update_parameter_state(parser, &parser->current, &order); - pm_node_t *param = (pm_node_t *) parse_required_destructured_parameter(parser); + pm_node_t *param = UP(parse_required_destructured_parameter(parser)); if (order > PM_PARAMETERS_ORDER_AFTER_OPTIONAL) { pm_parameters_node_requireds_append(params, param); @@ -13769,13 +13775,13 @@ parse_parameters( pm_block_parameter_node_t *param = pm_block_parameter_node_create(parser, &name, &operator); if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)param); + pm_node_flag_set_repeated_parameter(UP(param)); } if (params->block == NULL) { pm_parameters_node_block_set(params, param); } else { - pm_parser_err_node(parser, (pm_node_t *) param, PM_ERR_PARAMETER_BLOCK_MULTI); - pm_parameters_node_posts_append(params, (pm_node_t *) param); + pm_parser_err_node(parser, UP(param), PM_ERR_PARAMETER_BLOCK_MULTI); + pm_parameters_node_posts_append(params, UP(param)); } break; @@ -13800,7 +13806,7 @@ parse_parameters( params->keyword_rest = NULL; } - pm_parameters_node_keyword_rest_set(params, (pm_node_t *) param); + pm_parameters_node_keyword_rest_set(params, UP(param)); break; } case PM_TOKEN_CLASS_VARIABLE: @@ -13854,7 +13860,7 @@ parse_parameters( pm_optional_parameter_node_t *param = pm_optional_parameter_node_create(parser, &name, &operator, value); if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *) param); + pm_node_flag_set_repeated_parameter(UP(param)); } pm_parameters_node_optionals_append(params, param); @@ -13877,15 +13883,15 @@ parse_parameters( } else if (order > PM_PARAMETERS_ORDER_AFTER_OPTIONAL) { pm_required_parameter_node_t *param = pm_required_parameter_node_create(parser, &name); if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)param); + pm_node_flag_set_repeated_parameter(UP(param)); } - pm_parameters_node_requireds_append(params, (pm_node_t *) param); + pm_parameters_node_requireds_append(params, UP(param)); } else { pm_required_parameter_node_t *param = pm_required_parameter_node_create(parser, &name); if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)param); + pm_node_flag_set_repeated_parameter(UP(param)); } - pm_parameters_node_posts_append(params, (pm_node_t *) param); + pm_parameters_node_posts_append(params, UP(param)); } break; @@ -13916,7 +13922,7 @@ parse_parameters( case PM_TOKEN_PIPE: { context_pop(parser); - pm_node_t *param = (pm_node_t *) pm_required_keyword_parameter_node_create(parser, &name); + pm_node_t *param = UP(pm_required_keyword_parameter_node_create(parser, &name)); if (repeated) { pm_node_flag_set_repeated_parameter(param); } @@ -13933,7 +13939,7 @@ parse_parameters( break; } - pm_node_t *param = (pm_node_t *) pm_required_keyword_parameter_node_create(parser, &name); + pm_node_t *param = UP(pm_required_keyword_parameter_node_create(parser, &name)); if (repeated) { pm_node_flag_set_repeated_parameter(param); } @@ -13956,10 +13962,10 @@ parse_parameters( PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, local, PM_ERR_PARAMETER_CIRCULAR); } - param = (pm_node_t *) pm_optional_keyword_parameter_node_create(parser, &name, value); + param = UP(pm_optional_keyword_parameter_node_create(parser, &name, value)); } else { - param = (pm_node_t *) pm_required_keyword_parameter_node_create(parser, &name); + param = UP(pm_required_keyword_parameter_node_create(parser, &name)); } if (repeated) { @@ -14000,7 +14006,7 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS; } - pm_node_t *param = (pm_node_t *) pm_rest_parameter_node_create(parser, &operator, &name); + pm_node_t *param = UP(pm_rest_parameter_node_create(parser, &operator, &name)); if (repeated) { pm_node_flag_set_repeated_parameter(param); } @@ -14028,7 +14034,7 @@ parse_parameters( pm_parser_err_previous(parser, PM_ERR_PARAMETER_UNEXPECTED_NO_KW); } - param = (pm_node_t *) pm_no_keywords_parameter_node_create(parser, &operator, &parser->previous); + param = UP(pm_no_keywords_parameter_node_create(parser, &operator, &parser->previous)); } else { pm_token_t name; @@ -14042,7 +14048,7 @@ parse_parameters( parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS; } - param = (pm_node_t *) pm_keyword_rest_parameter_node_create(parser, &operator, &name); + param = UP(pm_keyword_rest_parameter_node_create(parser, &operator, &name)); if (repeated) { pm_node_flag_set_repeated_parameter(param); } @@ -14062,13 +14068,13 @@ parse_parameters( if (allows_trailing_comma && order >= PM_PARAMETERS_ORDER_NAMED) { // If we get here, then we have a trailing comma in a // block parameter list. - pm_node_t *param = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); + pm_node_t *param = UP(pm_implicit_rest_node_create(parser, &parser->previous)); if (params->rest == NULL) { pm_parameters_node_rest_set(params, param); } else { - pm_parser_err_node(parser, (pm_node_t *) param, PM_ERR_PARAMETER_SPLAT_MULTI); - pm_parameters_node_posts_append(params, (pm_node_t *) param); + pm_parser_err_node(parser, UP(param), PM_ERR_PARAMETER_SPLAT_MULTI); + pm_parameters_node_posts_append(params, UP(param)); } } else { pm_parser_err_previous(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); @@ -14105,7 +14111,7 @@ parse_parameters( // If we don't have any parameters, return `NULL` instead of an empty `ParametersNode`. if (params->base.location.start == params->base.location.end) { - pm_node_destroy(parser, (pm_node_t *) params); + pm_node_destroy(parser, UP(params)); return NULL; } @@ -14377,7 +14383,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ // If we don't have a `current` rescue node, then this is a dangling // else, and it's an error. - if (current == NULL) pm_parser_err_node(parser, (pm_node_t *) else_clause, PM_ERR_BEGIN_LONELY_ELSE); + if (current == NULL) pm_parser_err_node(parser, UP(else_clause), PM_ERR_BEGIN_LONELY_ELSE); } if (match1(parser, PM_TOKEN_KEYWORD_ENSURE)) { @@ -14501,7 +14507,7 @@ parse_block_parameters( pm_parser_local_add_token(parser, &parser->previous, 1); pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); - if (repeated) pm_node_flag_set_repeated_parameter((pm_node_t *) local); + if (repeated) pm_node_flag_set_repeated_parameter(UP(local)); pm_block_parameters_node_append_local(block_parameters, local); } while (accept1(parser, PM_TOKEN_COMMA)); @@ -14605,11 +14611,11 @@ parse_blocklike_parameters(pm_parser_t *parser, pm_node_t *parameters, const pm_ } const pm_location_t location = { .start = opening->start, .end = closing->end }; - return (pm_node_t *) pm_numbered_parameters_node_create(parser, &location, numbered_parameter); + return UP(pm_numbered_parameters_node_create(parser, &location, numbered_parameter)); } if (it_parameter) { - return (pm_node_t *) pm_it_parameters_node_create(parser, opening, closing); + return UP(pm_it_parameters_node_create(parser, opening, closing)); } return NULL; @@ -14649,7 +14655,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) { if (opening.type == PM_TOKEN_BRACE_LEFT) { if (!match1(parser, PM_TOKEN_BRACE_RIGHT)) { - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_BLOCK_BRACES, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_BLOCK_BRACES, (uint16_t) (depth + 1))); } expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_BLOCK_TERM_BRACE); @@ -14657,13 +14663,13 @@ parse_block(pm_parser_t *parser, uint16_t depth) { if (!match1(parser, PM_TOKEN_KEYWORD_END)) { if (!match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_ENSURE)) { pm_accepts_block_stack_push(parser, true); - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_BLOCK_KEYWORDS, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_BLOCK_KEYWORDS, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_implicit_begin(parser, 0, NULL, opening.start, (pm_statements_node_t *) statements, PM_RESCUES_BLOCK, (uint16_t) (depth + 1)); + statements = UP(parse_rescues_implicit_begin(parser, 0, NULL, opening.start, (pm_statements_node_t *) statements, PM_RESCUES_BLOCK, (uint16_t) (depth + 1))); } } @@ -14672,7 +14678,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) { pm_constant_id_list_t locals; pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); - pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous); + pm_node_t *parameters = parse_blocklike_parameters(parser, UP(block_parameters), &opening, &parser->previous); pm_parser_scope_pop(parser); pm_accepts_block_stack_pop(parser); @@ -14744,9 +14750,9 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept if (block != NULL) { if (arguments->block == NULL && !arguments->has_forwarding) { - arguments->block = (pm_node_t *) block; + arguments->block = UP(block); } else { - pm_parser_err_node(parser, (pm_node_t *) block, PM_ERR_ARGUMENT_BLOCK_MULTI); + pm_parser_err_node(parser, UP(block), PM_ERR_ARGUMENT_BLOCK_MULTI); if (arguments->block != NULL) { if (arguments->arguments == NULL) { @@ -14754,7 +14760,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } pm_arguments_node_arguments_append(arguments->arguments, arguments->block); } - arguments->block = (pm_node_t *) block; + arguments->block = UP(block); } } } @@ -15042,10 +15048,10 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl switch (context) { case PM_CONTEXT_IF: - parent = (pm_node_t *) pm_if_node_create(parser, &keyword, predicate, &then_keyword, statements, NULL, &end_keyword); + parent = UP(pm_if_node_create(parser, &keyword, predicate, &then_keyword, statements, NULL, &end_keyword)); break; case PM_CONTEXT_UNLESS: - parent = (pm_node_t *) pm_unless_node_create(parser, &keyword, predicate, &then_keyword, statements); + parent = UP(pm_unless_node_create(parser, &keyword, predicate, &then_keyword, statements)); break; default: assert(false && "unreachable"); @@ -15073,7 +15079,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl pm_accepts_block_stack_pop(parser); accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); - pm_node_t *elsif = (pm_node_t *) pm_if_node_create(parser, &elsif_keyword, predicate, &then_keyword, statements, NULL, &end_keyword); + pm_node_t *elsif = UP(pm_if_node_create(parser, &elsif_keyword, predicate, &then_keyword, statements, NULL, &end_keyword)); ((pm_if_node_t *) current)->subsequent = elsif; current = elsif; } @@ -15098,7 +15104,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl switch (context) { case PM_CONTEXT_IF: - ((pm_if_node_t *) current)->subsequent = (pm_node_t *) else_node; + ((pm_if_node_t *) current)->subsequent = UP(else_node); break; case PM_CONTEXT_UNLESS: ((pm_unless_node_t *) parent)->else_clause = else_node; @@ -15256,7 +15262,7 @@ parse_string_part(pm_parser_t *parser, uint16_t depth) { pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &parser->current, &closing); + pm_node_t *node = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); pm_node_flag_set(node, parse_unescaped_encoding(parser)); parser_lex(parser); @@ -15302,7 +15308,7 @@ parse_string_part(pm_parser_t *parser, uint16_t depth) { pm_node_flag_unset(statements->body.nodes[0], PM_NODE_FLAG_NEWLINE); } - return (pm_node_t *) pm_embedded_statements_node_create(parser, &opening, statements, &closing); + return UP(pm_embedded_statements_node_create(parser, &opening, statements, &closing)); } // Here the lexer has returned the beginning of an embedded variable. @@ -15327,42 +15333,42 @@ parse_string_part(pm_parser_t *parser, uint16_t depth) { // create a global variable read node. case PM_TOKEN_BACK_REFERENCE: parser_lex(parser); - variable = (pm_node_t *) pm_back_reference_read_node_create(parser, &parser->previous); + variable = UP(pm_back_reference_read_node_create(parser, &parser->previous)); break; // In this case an nth reference is being interpolated. We'll // create a global variable read node. case PM_TOKEN_NUMBERED_REFERENCE: parser_lex(parser); - variable = (pm_node_t *) pm_numbered_reference_read_node_create(parser, &parser->previous); + variable = UP(pm_numbered_reference_read_node_create(parser, &parser->previous)); break; // In this case a global variable is being interpolated. We'll // create a global variable read node. case PM_TOKEN_GLOBAL_VARIABLE: parser_lex(parser); - variable = (pm_node_t *) pm_global_variable_read_node_create(parser, &parser->previous); + variable = UP(pm_global_variable_read_node_create(parser, &parser->previous)); break; // In this case an instance variable is being interpolated. // We'll create an instance variable read node. case PM_TOKEN_INSTANCE_VARIABLE: parser_lex(parser); - variable = (pm_node_t *) pm_instance_variable_read_node_create(parser, &parser->previous); + variable = UP(pm_instance_variable_read_node_create(parser, &parser->previous)); break; // In this case a class variable is being interpolated. We'll // create a class variable read node. case PM_TOKEN_CLASS_VARIABLE: parser_lex(parser); - variable = (pm_node_t *) pm_class_variable_read_node_create(parser, &parser->previous); + variable = UP(pm_class_variable_read_node_create(parser, &parser->previous)); break; // We can hit here if we got an invalid token. In that case // we'll not attempt to lex this token and instead just return a // missing node. default: expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_EMBVAR_INVALID); - variable = (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + variable = UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); break; } - return (pm_node_t *) pm_embedded_variable_node_create(parser, &operator, variable); + return UP(pm_embedded_variable_node_create(parser, &operator, variable)); } default: parser_lex(parser); @@ -15399,9 +15405,9 @@ parse_operator_symbol(pm_parser_t *parser, const pm_token_t *opening, pm_lex_sta parser_lex(parser); pm_string_shared_init(&symbol->unescaped, parser->previous.start, end); - pm_node_flag_set((pm_node_t *) symbol, PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING); + pm_node_flag_set(UP(symbol), PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING); - return (pm_node_t *) symbol; + return UP(symbol); } /** @@ -15439,9 +15445,9 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s pm_symbol_node_t *symbol = pm_symbol_node_create(parser, &opening, &parser->previous, &closing); pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set((pm_node_t *) symbol, parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); + pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); - return (pm_node_t *) symbol; + return UP(symbol); } if (lex_mode->as.string.interpolation) { @@ -15452,7 +15458,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s pm_token_t content = not_provided(parser); pm_token_t closing = parser->previous; - return (pm_node_t *) pm_symbol_node_create(parser, &opening, &content, &closing); + return UP(pm_symbol_node_create(parser, &opening, &content, &closing)); } // Now we can parse the first part of the symbol. @@ -15464,7 +15470,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s if (next_state != PM_LEX_STATE_NONE) lex_state_set(parser, next_state); expect1(parser, PM_TOKEN_STRING_END, PM_ERR_SYMBOL_TERM_INTERPOLATED); - return (pm_node_t *) pm_string_node_to_symbol_node(parser, (pm_string_node_t *) part, &opening, &parser->previous); + return UP(pm_string_node_to_symbol_node(parser, (pm_string_node_t *) part, &opening, &parser->previous)); } pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); @@ -15484,7 +15490,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s } pm_interpolated_symbol_node_closing_loc_set(symbol, &parser->previous); - return (pm_node_t *) symbol; + return UP(symbol); } pm_token_t content; @@ -15508,10 +15514,10 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); pm_token_t bounds = not_provided(parser); - pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &unescaped); + pm_node_t *part = UP(pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &unescaped)); pm_interpolated_symbol_node_append(symbol, part); - part = (pm_node_t *) pm_string_node_create_unescaped(parser, &bounds, &parser->current, &bounds, &parser->current_string); + part = UP(pm_string_node_create_unescaped(parser, &bounds, &parser->current, &bounds, &parser->current_string)); pm_interpolated_symbol_node_append(symbol, part); if (next_state != PM_LEX_STATE_NONE) { @@ -15522,7 +15528,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s expect1(parser, PM_TOKEN_STRING_END, PM_ERR_SYMBOL_TERM_DYNAMIC); pm_interpolated_symbol_node_closing_loc_set(symbol, &parser->previous); - return (pm_node_t *) symbol; + return UP(symbol); } } else { content = (pm_token_t) { .type = PM_TOKEN_STRING_CONTENT, .start = parser->previous.end, .end = parser->previous.end }; @@ -15539,7 +15545,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s expect1(parser, PM_TOKEN_STRING_END, PM_ERR_SYMBOL_TERM_DYNAMIC); } - return (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, false)); + return UP(pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, false))); } /** @@ -15564,9 +15570,9 @@ parse_undef_argument(pm_parser_t *parser, uint16_t depth) { pm_symbol_node_t *symbol = pm_symbol_node_create(parser, &opening, &parser->previous, &closing); pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set((pm_node_t *) symbol, parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); + pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); - return (pm_node_t *) symbol; + return UP(symbol); } case PM_TOKEN_SYMBOL_BEGIN: { pm_lex_mode_t lex_mode = *parser->lex_modes.current; @@ -15576,7 +15582,7 @@ parse_undef_argument(pm_parser_t *parser, uint16_t depth) { } default: pm_parser_err_current(parser, PM_ERR_UNDEF_ARGUMENT); - return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + return UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); } } @@ -15605,9 +15611,9 @@ parse_alias_argument(pm_parser_t *parser, bool first, uint16_t depth) { pm_symbol_node_t *symbol = pm_symbol_node_create(parser, &opening, &parser->previous, &closing); pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set((pm_node_t *) symbol, parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); + pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, &parser->previous, &symbol->unescaped, false)); - return (pm_node_t *) symbol; + return UP(symbol); } case PM_TOKEN_SYMBOL_BEGIN: { pm_lex_mode_t lex_mode = *parser->lex_modes.current; @@ -15617,16 +15623,16 @@ parse_alias_argument(pm_parser_t *parser, bool first, uint16_t depth) { } case PM_TOKEN_BACK_REFERENCE: parser_lex(parser); - return (pm_node_t *) pm_back_reference_read_node_create(parser, &parser->previous); + return UP(pm_back_reference_read_node_create(parser, &parser->previous)); case PM_TOKEN_NUMBERED_REFERENCE: parser_lex(parser); - return (pm_node_t *) pm_numbered_reference_read_node_create(parser, &parser->previous); + return UP(pm_numbered_reference_read_node_create(parser, &parser->previous)); case PM_TOKEN_GLOBAL_VARIABLE: parser_lex(parser); - return (pm_node_t *) pm_global_variable_read_node_create(parser, &parser->previous); + return UP(pm_global_variable_read_node_create(parser, &parser->previous)); default: pm_parser_err_current(parser, PM_ERR_ALIAS_ARGUMENT); - return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + return UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); } } @@ -15641,7 +15647,7 @@ parse_variable(pm_parser_t *parser) { bool is_numbered_param = pm_token_is_numbered_parameter(parser->previous.start, parser->previous.end); if (!is_numbered_param && ((depth = pm_parser_local_depth_constant_id(parser, name_id)) != -1)) { - return (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, (uint32_t) depth, false); + return UP(pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, (uint32_t) depth, false)); } pm_scope_t *current_scope = parser->current_scope; @@ -15660,12 +15666,12 @@ parse_variable(pm_parser_t *parser) { parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED_FOUND; } - pm_node_t *node = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false); + pm_node_t *node = UP(pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false)); pm_node_list_append(¤t_scope->implicit_parameters, node); return node; } else if ((parser->version >= PM_OPTIONS_VERSION_CRUBY_3_4) && pm_token_is_it(parser->previous.start, parser->previous.end)) { - pm_node_t *node = (pm_node_t *) pm_it_local_variable_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_it_local_variable_read_node_create(parser, &parser->previous)); pm_node_list_append(¤t_scope->implicit_parameters, node); return node; @@ -15689,9 +15695,9 @@ parse_variable_call(pm_parser_t *parser) { } pm_call_node_t *node = pm_call_node_variable_call_create(parser, &parser->previous); - pm_node_flag_set((pm_node_t *)node, flags); + pm_node_flag_set(UP(node), flags); - return (pm_node_t *) node; + return UP(node); } /** @@ -15840,7 +15846,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_string_node_t *string = pm_string_node_create(parser, &opening, &content, &parser->previous); pm_string_shared_init(&string->unescaped, content.start, content.end); - node = (pm_node_t *) string; + node = UP(string); } else if (accept1(parser, PM_TOKEN_LABEL_END)) { // If we get here, then we have an end of a label immediately // after a start. In that case we'll create an empty symbol @@ -15849,7 +15855,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_symbol_node_t *symbol = pm_symbol_node_create(parser, &opening, &content, &parser->previous); pm_string_shared_init(&symbol->unescaped, content.start, content.end); - node = (pm_node_t *) symbol; + node = UP(symbol); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); } else if (!lex_interpolation) { @@ -15882,32 +15888,32 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_node_list_t parts = { 0 }; pm_token_t delimiters = not_provided(parser); - pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &delimiters, &content, &delimiters, &unescaped); + pm_node_t *part = UP(pm_string_node_create_unescaped(parser, &delimiters, &content, &delimiters, &unescaped)); pm_node_list_append(&parts, part); do { - part = (pm_node_t *) pm_string_node_create_current_string(parser, &delimiters, &parser->current, &delimiters); + part = UP(pm_string_node_create_current_string(parser, &delimiters, &parser->current, &delimiters)); pm_node_list_append(&parts, part); parser_lex(parser); } while (match1(parser, PM_TOKEN_STRING_CONTENT)); expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); - node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); + node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); pm_node_list_free(&parts); } else if (accept1(parser, PM_TOKEN_LABEL_END)) { - node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, true)); + node = UP(pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, true))); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); } else if (match1(parser, PM_TOKEN_EOF)) { pm_parser_err_token(parser, &opening, PM_ERR_STRING_LITERAL_EOF); - node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped); + node = UP(pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped)); } else if (accept1(parser, PM_TOKEN_STRING_END)) { - node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); + node = UP(pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped)); } else { PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_STRING_LITERAL_TERM, pm_token_type_human(parser->previous.type)); parser->previous.start = parser->previous.end; parser->previous.type = PM_TOKEN_MISSING; - node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); + node = UP(pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped)); } } else if (match1(parser, PM_TOKEN_STRING_CONTENT)) { // In this case we've hit string content so we know the string @@ -15919,7 +15925,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 parser_lex(parser); if (match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { - node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped); + node = UP(pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped)); pm_node_flag_set(node, parse_unescaped_encoding(parser)); // Kind of odd behavior, but basically if we have an @@ -15935,7 +15941,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 parser->previous.type = PM_TOKEN_MISSING; } } else if (accept1(parser, PM_TOKEN_LABEL_END)) { - node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, true)); + node = UP(pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, true))); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); } else { // If we get here, then we have interpolation so we'll need @@ -15944,7 +15950,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_token_t string_opening = not_provided(parser); pm_token_t string_closing = not_provided(parser); - pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &string_opening, &parser->previous, &string_closing, &unescaped); + pm_node_t *part = UP(pm_string_node_create_unescaped(parser, &string_opening, &parser->previous, &string_closing, &unescaped)); pm_node_flag_set(part, parse_unescaped_encoding(parser)); pm_node_list_append(&parts, part); @@ -15955,14 +15961,14 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 } if (accept1(parser, PM_TOKEN_LABEL_END)) { - node = (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous); + node = UP(pm_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous)); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); } else if (match1(parser, PM_TOKEN_EOF)) { pm_parser_err_token(parser, &opening, PM_ERR_STRING_INTERPOLATED_TERM); - node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->current); + node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->current)); } else { expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); - node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); + node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); } pm_node_list_free(&parts); @@ -15981,14 +15987,14 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 } if (accept1(parser, PM_TOKEN_LABEL_END)) { - node = (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous); + node = UP(pm_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous)); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); } else if (match1(parser, PM_TOKEN_EOF)) { pm_parser_err_token(parser, &opening, PM_ERR_STRING_INTERPOLATED_TERM); - node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->current); + node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->current)); } else { expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); - node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); + node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); } pm_node_list_free(&parts); @@ -16025,7 +16031,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_interpolated_string_node_t *container = pm_interpolated_string_node_create(parser, &bounds, NULL, &bounds); pm_interpolated_string_node_append(container, current); - current = (pm_node_t *) container; + current = UP(container); } pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, node); @@ -16069,7 +16075,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures while (accept1(parser, PM_TOKEN_COLON_COLON)) { pm_token_t delimiter = parser->previous; expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); - node = (pm_node_t *) pm_constant_path_node_create(parser, node, &delimiter, &parser->previous); + node = UP(pm_constant_path_node_create(parser, node, &delimiter, &parser->previous)); } // If there is a [ or ( that follows, then this is part of a larger pattern @@ -16111,7 +16117,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures if (!inner) { // If there was no inner pattern, then we have something like Foo() or // Foo[]. In that case we'll create an array pattern with no requireds. - return (pm_node_t *) pm_array_pattern_node_constant_create(parser, node, &opening, &closing); + return UP(pm_array_pattern_node_constant_create(parser, node, &opening, &closing)); } // Now that we have the inner pattern, check to see if it's an array, find, @@ -16130,7 +16136,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures pattern_node->opening_loc = PM_LOCATION_TOKEN_VALUE(&opening); pattern_node->closing_loc = PM_LOCATION_TOKEN_VALUE(&closing); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } break; @@ -16146,7 +16152,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures pattern_node->opening_loc = PM_LOCATION_TOKEN_VALUE(&opening); pattern_node->closing_loc = PM_LOCATION_TOKEN_VALUE(&closing); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } break; @@ -16162,7 +16168,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures pattern_node->opening_loc = PM_LOCATION_TOKEN_VALUE(&opening); pattern_node->closing_loc = PM_LOCATION_TOKEN_VALUE(&closing); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } break; @@ -16176,7 +16182,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures // attach our constant to it. pm_array_pattern_node_t *pattern_node = pm_array_pattern_node_constant_create(parser, node, &opening, &closing); pm_array_pattern_node_requireds_append(pattern_node, inner); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } /** @@ -16201,12 +16207,12 @@ parse_pattern_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { } parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&identifier)); - name = (pm_node_t *) pm_local_variable_target_node_create( + name = UP(pm_local_variable_target_node_create( parser, &PM_LOCATION_TOKEN_VALUE(&identifier), constant_id, (uint32_t) (depth == -1 ? 0 : depth) - ); + )); } // Finally we can return the created node. @@ -16225,7 +16231,7 @@ parse_pattern_keyword_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) pm_node_t *value = NULL; if (accept1(parser, PM_TOKEN_KEYWORD_NIL)) { - return (pm_node_t *) pm_no_keywords_parameter_node_create(parser, &operator, &parser->previous); + return UP(pm_no_keywords_parameter_node_create(parser, &operator, &parser->previous)); } if (accept1(parser, PM_TOKEN_IDENTIFIER)) { @@ -16237,15 +16243,15 @@ parse_pattern_keyword_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) } parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&parser->previous)); - value = (pm_node_t *) pm_local_variable_target_node_create( + value = UP(pm_local_variable_target_node_create( parser, &PM_LOCATION_TOKEN_VALUE(&parser->previous), constant_id, (uint32_t) (depth == -1 ? 0 : depth) - ); + )); } - return (pm_node_t *) pm_assoc_splat_node_create(parser, value, &operator); + return UP(pm_assoc_splat_node_create(parser, value, &operator)); } /** @@ -16308,7 +16314,7 @@ parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_list_t *ca (uint32_t) (depth == -1 ? 0 : depth) ); - return (pm_node_t *) pm_implicit_node_create(parser, (pm_node_t *) target); + return UP(pm_implicit_node_create(parser, UP(target))); } /** @@ -16352,7 +16358,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node } pm_token_t operator = not_provided(parser); - pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, first_node, &operator, value); + pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, &operator, value)); pm_node_list_append(&assocs, assoc); break; @@ -16367,8 +16373,8 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node pm_parser_err_node(parser, first_node, diag_id); pm_token_t operator = not_provided(parser); - pm_node_t *value = (pm_node_t *) pm_missing_node_create(parser, first_node->location.start, first_node->location.end); - pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, first_node, &operator, value); + pm_node_t *value = UP(pm_missing_node_create(parser, first_node->location.start, first_node->location.end)); + pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, &operator, value)); pm_node_list_append(&assocs, assoc); break; @@ -16409,7 +16415,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node } } else { expect1(parser, PM_TOKEN_LABEL, PM_ERR_PATTERN_LABEL_AFTER_COMMA); - key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); + key = UP(pm_symbol_node_label_create(parser, &parser->previous)); } parse_pattern_hash_key(parser, &keys, key); @@ -16419,14 +16425,14 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) { value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key); } else { - value = (pm_node_t *) pm_missing_node_create(parser, key->location.end, key->location.end); + value = UP(pm_missing_node_create(parser, key->location.end, key->location.end)); } } else { value = parse_pattern(parser, captures, PM_PARSE_PATTERN_SINGLE, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY, (uint16_t) (depth + 1)); } pm_token_t operator = not_provided(parser); - pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, value); + pm_node_t *assoc = UP(pm_assoc_node_create(parser, key, &operator, value)); if (rest != NULL) { pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST); @@ -16460,12 +16466,12 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm } parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&parser->previous)); - return (pm_node_t *) pm_local_variable_target_node_create( + return UP(pm_local_variable_target_node_create( parser, &PM_LOCATION_TOKEN_VALUE(&parser->previous), constant_id, (uint32_t) (depth == -1 ? 0 : depth) - ); + )); } case PM_TOKEN_BRACKET_LEFT_ARRAY: { pm_token_t opening = parser->current; @@ -16474,7 +16480,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm if (accept1(parser, PM_TOKEN_BRACKET_RIGHT)) { // If we have an empty array pattern, then we'll just return a new // array pattern node. - return (pm_node_t *) pm_array_pattern_node_empty_create(parser, &opening, &parser->previous); + return UP(pm_array_pattern_node_empty_create(parser, &opening, &parser->previous)); } // Otherwise, we'll parse the inner pattern, then deal with it depending @@ -16495,7 +16501,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pattern_node->opening_loc = PM_LOCATION_TOKEN_VALUE(&opening); pattern_node->closing_loc = PM_LOCATION_TOKEN_VALUE(&closing); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } break; @@ -16509,7 +16515,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pattern_node->opening_loc = PM_LOCATION_TOKEN_VALUE(&opening); pattern_node->closing_loc = PM_LOCATION_TOKEN_VALUE(&closing); - return (pm_node_t *) pattern_node; + return UP(pattern_node); } break; @@ -16520,7 +16526,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pm_array_pattern_node_t *node = pm_array_pattern_node_empty_create(parser, &opening, &closing); pm_array_pattern_node_requireds_append(node, inner); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_BRACE_LEFT: { bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; @@ -16540,7 +16546,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm switch (parser->current.type) { case PM_TOKEN_LABEL: parser_lex(parser); - first_node = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); + first_node = UP(pm_symbol_node_label_create(parser, &parser->previous)); break; case PM_TOKEN_USTAR_STAR: first_node = parse_pattern_keyword_rest(parser, captures); @@ -16552,7 +16558,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_PATTERN_HASH_KEY, pm_token_type_human(parser->current.type)); parser_lex(parser); - first_node = (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end); + first_node = UP(pm_missing_node_create(parser, parser->previous.start, parser->previous.end)); break; } } @@ -16571,7 +16577,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm } parser->pattern_matching_newlines = previous_pattern_matching_newlines; - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_UDOT_DOT: case PM_TOKEN_UDOT_DOT_DOT: { @@ -16583,12 +16589,12 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm switch (parser->current.type) { case PM_CASE_PRIMITIVE: { pm_node_t *right = parse_expression(parser, PM_BINDING_POWER_MAX, false, false, PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_range_node_create(parser, NULL, &operator, right); + return UP(pm_range_node_create(parser, NULL, &operator, right)); } default: { pm_parser_err_token(parser, &operator, PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE); - pm_node_t *right = (pm_node_t *) pm_missing_node_create(parser, operator.start, operator.end); - return (pm_node_t *) pm_range_node_create(parser, NULL, &operator, right); + pm_node_t *right = UP(pm_missing_node_create(parser, operator.start, operator.end)); + return UP(pm_range_node_create(parser, NULL, &operator, right)); } } } @@ -16603,7 +16609,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pm_parser_err_node(parser, node, diag_id); pm_missing_node_t *missing_node = pm_missing_node_create(parser, node->location.start, node->location.end); pm_node_destroy(parser, node); - return (pm_node_t *) missing_node; + return UP(missing_node); } // Now that we have a primitive, we need to check if it's part of a range. @@ -16616,10 +16622,10 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm switch (parser->current.type) { case PM_CASE_PRIMITIVE: { pm_node_t *right = parse_expression(parser, PM_BINDING_POWER_MAX, false, false, PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_range_node_create(parser, node, &operator, right); + return UP(pm_range_node_create(parser, node, &operator, right)); } default: - return (pm_node_t *) pm_range_node_create(parser, node, &operator, NULL); + return UP(pm_range_node_create(parser, node, &operator, NULL)); } } @@ -16634,44 +16640,44 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm switch (parser->current.type) { case PM_TOKEN_IDENTIFIER: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) parse_variable(parser); + pm_node_t *variable = UP(parse_variable(parser)); if (variable == NULL) { PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE); - variable = (pm_node_t *) pm_local_variable_read_node_missing_create(parser, &parser->previous, 0); + variable = UP(pm_local_variable_read_node_missing_create(parser, &parser->previous, 0)); } - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_INSTANCE_VARIABLE: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) pm_instance_variable_read_node_create(parser, &parser->previous); + pm_node_t *variable = UP(pm_instance_variable_read_node_create(parser, &parser->previous)); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_CLASS_VARIABLE: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) pm_class_variable_read_node_create(parser, &parser->previous); + pm_node_t *variable = UP(pm_class_variable_read_node_create(parser, &parser->previous)); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_GLOBAL_VARIABLE: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) pm_global_variable_read_node_create(parser, &parser->previous); + pm_node_t *variable = UP(pm_global_variable_read_node_create(parser, &parser->previous)); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_NUMBERED_REFERENCE: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) pm_numbered_reference_read_node_create(parser, &parser->previous); + pm_node_t *variable = UP(pm_numbered_reference_read_node_create(parser, &parser->previous)); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_BACK_REFERENCE: { parser_lex(parser); - pm_node_t *variable = (pm_node_t *) pm_back_reference_read_node_create(parser, &parser->previous); + pm_node_t *variable = UP(pm_back_reference_read_node_create(parser, &parser->previous)); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } case PM_TOKEN_PARENTHESIS_LEFT: { bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; @@ -16685,14 +16691,14 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); - return (pm_node_t *) pm_pinned_expression_node_create(parser, expression, &operator, &lparen, &parser->previous); + return UP(pm_pinned_expression_node_create(parser, expression, &operator, &lparen, &parser->previous)); } default: { // If we get here, then we have a pin operator followed by something // not understood. We'll create a missing node and return that. pm_parser_err_token(parser, &operator, PM_ERR_PATTERN_EXPRESSION_AFTER_PIN); - pm_node_t *variable = (pm_node_t *) pm_missing_node_create(parser, operator.start, operator.end); - return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable); + pm_node_t *variable = UP(pm_missing_node_create(parser, operator.start, operator.end)); + return UP(pm_pinned_variable_node_create(parser, &operator, variable)); } } } @@ -16703,18 +16709,18 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); pm_constant_path_node_t *node = pm_constant_path_node_create(parser, NULL, &delimiter, &parser->previous); - return parse_pattern_constant_path(parser, captures, (pm_node_t *) node, (uint16_t) (depth + 1)); + return parse_pattern_constant_path(parser, captures, UP(node), (uint16_t) (depth + 1)); } case PM_TOKEN_CONSTANT: { pm_token_t constant = parser->current; parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_constant_read_node_create(parser, &constant); + pm_node_t *node = UP(pm_constant_read_node_create(parser, &constant)); return parse_pattern_constant_path(parser, captures, node, (uint16_t) (depth + 1)); } default: pm_parser_err_current(parser, diag_id); - return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + return UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); } } @@ -16769,7 +16775,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, p pm_node_t *right = parse_pattern_primitive(parser, captures, PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE, (uint16_t) (depth + 1)); if (captures->size) parse_pattern_alternation_error(parser, right); - node = (pm_node_t *) pm_alternation_pattern_node_create(parser, node, right, &operator); + node = UP(pm_alternation_pattern_node_create(parser, node, right, &operator)); } break; @@ -16783,26 +16789,26 @@ parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, p pm_node_t *body = parse_pattern(parser, captures, PM_PARSE_PATTERN_SINGLE, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); - pm_node_t *right = (pm_node_t *) pm_parentheses_node_create(parser, &opening, body, &parser->previous, 0); + pm_node_t *right = UP(pm_parentheses_node_create(parser, &opening, body, &parser->previous, 0)); if (!alternation) { node = right; } else { if (captures->size) parse_pattern_alternation_error(parser, right); - node = (pm_node_t *) pm_alternation_pattern_node_create(parser, node, right, &operator); + node = UP(pm_alternation_pattern_node_create(parser, node, right, &operator)); } break; } default: { pm_parser_err_current(parser, diag_id); - pm_node_t *right = (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + pm_node_t *right = UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); if (!alternation) { node = right; } else { if (captures->size) parse_pattern_alternation_error(parser, right); - node = (pm_node_t *) pm_alternation_pattern_node_create(parser, node, right, &parser->previous); + node = UP(pm_alternation_pattern_node_create(parser, node, right, &parser->previous)); } break; @@ -16831,7 +16837,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, p (uint32_t) (depth == -1 ? 0 : depth) ); - node = (pm_node_t *) pm_capture_pattern_node_create(parser, node, target, &operator); + node = UP(pm_capture_pattern_node_create(parser, node, target, &operator)); } return node; @@ -16850,8 +16856,8 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag switch (parser->current.type) { case PM_TOKEN_LABEL: { parser_lex(parser); - pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); - node = (pm_node_t *) parse_pattern_hash(parser, captures, key, (uint16_t) (depth + 1)); + pm_node_t *key = UP(pm_symbol_node_label_create(parser, &parser->previous)); + node = UP(parse_pattern_hash(parser, captures, key, (uint16_t) (depth + 1))); if (!(flags & PM_PARSE_PATTERN_TOP)) { pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_IMPLICIT); @@ -16861,7 +16867,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag } case PM_TOKEN_USTAR_STAR: { node = parse_pattern_keyword_rest(parser, captures); - node = (pm_node_t *) parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1)); + node = UP(parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1))); if (!(flags & PM_PARSE_PATTERN_TOP)) { pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_IMPLICIT); @@ -16875,7 +16881,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag node = parse_pattern_primitive(parser, captures, diag_id, (uint16_t) (depth + 1)); if (pm_symbol_node_label_p(node)) { - node = (pm_node_t *) parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1)); + node = UP(parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1))); if (!(flags & PM_PARSE_PATTERN_TOP)) { pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_IMPLICIT); @@ -16890,7 +16896,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag case PM_TOKEN_USTAR: { if (flags & (PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI)) { parser_lex(parser); - node = (pm_node_t *) parse_pattern_rest(parser, captures); + node = UP(parse_pattern_rest(parser, captures)); leading_rest = true; break; } @@ -16904,7 +16910,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag // If we got a dynamic label symbol, then we need to treat it like the // beginning of a hash pattern. if (pm_symbol_node_label_p(node)) { - return (pm_node_t *) parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1)); + return UP(parse_pattern_hash(parser, captures, node, (uint16_t) (depth + 1))); } if ((flags & PM_PARSE_PATTERN_MULTI) && match1(parser, PM_TOKEN_COMMA)) { @@ -16918,14 +16924,14 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag while (accept1(parser, PM_TOKEN_COMMA)) { // Break early here in case we have a trailing comma. if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR)) { - node = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); + node = UP(pm_implicit_rest_node_create(parser, &parser->previous)); pm_node_list_append(&nodes, node); trailing_rest = true; break; } if (accept1(parser, PM_TOKEN_USTAR)) { - node = (pm_node_t *) parse_pattern_rest(parser, captures); + node = UP(parse_pattern_rest(parser, captures)); // If we have already parsed a splat pattern, then this is an // error. We will continue to parse the rest of the patterns, @@ -16947,13 +16953,13 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag // are in between because we know we already added the appropriate // errors. Otherwise we will create an array pattern. if (leading_rest && PM_NODE_TYPE_P(nodes.nodes[nodes.size - 1], PM_SPLAT_NODE)) { - node = (pm_node_t *) pm_find_pattern_node_create(parser, &nodes); + node = UP(pm_find_pattern_node_create(parser, &nodes)); if (nodes.size == 2) { pm_parser_err_node(parser, node, PM_ERR_PATTERN_FIND_MISSING_INNER); } } else { - node = (pm_node_t *) pm_array_pattern_node_node_list_create(parser, &nodes); + node = UP(pm_array_pattern_node_node_list_create(parser, &nodes)); if (leading_rest && trailing_rest) { pm_parser_err_node(parser, node, PM_ERR_PATTERN_ARRAY_MULTIPLE_RESTS); @@ -16964,7 +16970,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag } else if (leading_rest) { // Otherwise, if we parsed a single splat pattern, then we know we have // an array pattern, so we can go ahead and create that node. - node = (pm_node_t *) pm_array_pattern_node_rest_create(parser, node); + node = UP(pm_array_pattern_node_rest_create(parser, node)); } return node; @@ -17342,13 +17348,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_ARRAY_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); } - element = (pm_node_t *) pm_splat_node_create(parser, &operator, expression); + element = UP(pm_splat_node_create(parser, &operator, expression)); } else if (match2(parser, PM_TOKEN_LABEL, PM_TOKEN_USTAR_STAR)) { if (parsed_bare_hash) { pm_parser_err_current(parser, PM_ERR_EXPRESSION_BARE_HASH); } - element = (pm_node_t *) pm_keyword_hash_node_create(parser); + element = UP(pm_keyword_hash_node_create(parser)); pm_static_literals_t hash_keys = { 0 }; if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) { @@ -17377,10 +17383,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_HASH_VALUE, (uint16_t) (depth + 1)); - pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, element, &operator, value); + pm_node_t *assoc = UP(pm_assoc_node_create(parser, element, &operator, value)); pm_keyword_hash_node_elements_append(hash, assoc); - element = (pm_node_t *) hash; + element = UP(hash); if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) { parse_assocs(parser, &hash_keys, element, (uint16_t) (depth + 1)); } @@ -17405,7 +17411,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_array_node_close_set(array, &parser->previous); pm_accepts_block_stack_pop(parser); - return (pm_node_t *) array; + return UP(array); } case PM_TOKEN_PARENTHESIS_LEFT: case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES: { @@ -17432,7 +17438,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_parentheses_node_create(parser, &opening, NULL, &parser->previous, flags); + return UP(pm_parentheses_node_create(parser, &opening, NULL, &parser->previous, flags)); } // Otherwise, we're going to parse the first statement in the list @@ -17501,10 +17507,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *result; if (match1(parser, PM_TOKEN_COMMA) && (binding_power == PM_BINDING_POWER_STATEMENT)) { - result = parse_targets(parser, (pm_node_t *) multi_target, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); + result = parse_targets(parser, UP(multi_target), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); } else { - result = (pm_node_t *) multi_target; + result = UP(multi_target); } if (context_p(parser, PM_CONTEXT_MULTI_TARGET)) { @@ -17533,7 +17539,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_statements_node_t *statements = pm_statements_node_create(parser); pm_statements_node_body_append(parser, statements, statement, true); - return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous, flags); + return UP(pm_parentheses_node_create(parser, &opening, UP(statements), &parser->previous, flags)); } // If we have more than one statement in the set of parentheses, @@ -17598,16 +17604,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_multi_target_node_t *multi_target = pm_multi_target_node_create(parser); pm_multi_target_node_targets_append(parser, multi_target, statement); - statement = (pm_node_t *) multi_target; + statement = UP(multi_target); statements->body.nodes[statements->body.size - 1] = statement; } if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE)) { const uint8_t *offset = statement->location.end; pm_token_t operator = { .type = PM_TOKEN_EQUAL, .start = offset, .end = offset }; - pm_node_t *value = (pm_node_t *) pm_missing_node_create(parser, offset, offset); + pm_node_t *value = UP(pm_missing_node_create(parser, offset, offset)); - statement = (pm_node_t *) pm_multi_write_node_create(parser, (pm_multi_target_node_t *) statement, &operator, value); + statement = UP(pm_multi_write_node_create(parser, (pm_multi_target_node_t *) statement, &operator, value)); statements->body.nodes[statements->body.size - 1] = statement; pm_parser_err_node(parser, statement, PM_ERR_WRITE_TARGET_UNEXPECTED); @@ -17618,7 +17624,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_list_free(¤t_block_exits); pm_void_statements_check(parser, statements, true); - return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous, flags); + return UP(pm_parentheses_node_create(parser, &opening, UP(statements), &parser->previous, flags)); } case PM_TOKEN_BRACE_LEFT: { // If we were passed a current_hash_keys via the parser, then that @@ -17638,10 +17644,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (!match2(parser, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_EOF)) { if (current_hash_keys != NULL) { - parse_assocs(parser, current_hash_keys, (pm_node_t *) node, (uint16_t) (depth + 1)); + parse_assocs(parser, current_hash_keys, UP(node), (uint16_t) (depth + 1)); } else { pm_static_literals_t hash_keys = { 0 }; - parse_assocs(parser, &hash_keys, (pm_node_t *) node, (uint16_t) (depth + 1)); + parse_assocs(parser, &hash_keys, UP(node), (uint16_t) (depth + 1)); pm_static_literals_free(&hash_keys); } @@ -17652,11 +17658,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM); pm_hash_node_closing_loc_set(node, &parser->previous); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_CHARACTER_LITERAL: { pm_token_t closing = not_provided(parser); - pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string( + pm_node_t *node = UP(pm_string_node_create_current_string( parser, &(pm_token_t) { .type = PM_TOKEN_STRING_BEGIN, @@ -17669,7 +17675,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b .end = parser->current.end }, &closing - ); + )); pm_node_flag_set(node, parse_unescaped_encoding(parser)); @@ -17687,7 +17693,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } case PM_TOKEN_CLASS_VARIABLE: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_class_variable_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_class_variable_read_node_create(parser, &parser->previous)); if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17709,10 +17715,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b ) { pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, true, accepts_command_call, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_node_fcall_create(parser, &constant, &arguments); + return UP(pm_call_node_fcall_create(parser, &constant, &arguments)); } - pm_node_t *node = (pm_node_t *) pm_constant_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_constant_read_node_create(parser, &parser->previous)); if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { // If we get here, then we have a comma immediately following a @@ -17727,7 +17733,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t delimiter = parser->previous; expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); - pm_node_t *node = (pm_node_t *) pm_constant_path_node_create(parser, NULL, &delimiter, &parser->previous); + pm_node_t *node = UP(pm_constant_path_node_create(parser, NULL, &delimiter, &parser->previous)); if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17750,23 +17756,23 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_current(parser, PM_ERR_UNEXPECTED_RANGE_OPERATOR); } - return (pm_node_t *) pm_range_node_create(parser, NULL, &operator, right); + return UP(pm_range_node_create(parser, NULL, &operator, right)); } case PM_TOKEN_FLOAT: parser_lex(parser); - return (pm_node_t *) pm_float_node_create(parser, &parser->previous); + return UP(pm_float_node_create(parser, &parser->previous)); case PM_TOKEN_FLOAT_IMAGINARY: parser_lex(parser); - return (pm_node_t *) pm_float_node_imaginary_create(parser, &parser->previous); + return UP(pm_float_node_imaginary_create(parser, &parser->previous)); case PM_TOKEN_FLOAT_RATIONAL: parser_lex(parser); - return (pm_node_t *) pm_float_node_rational_create(parser, &parser->previous); + return UP(pm_float_node_rational_create(parser, &parser->previous)); case PM_TOKEN_FLOAT_RATIONAL_IMAGINARY: parser_lex(parser); - return (pm_node_t *) pm_float_node_rational_imaginary_create(parser, &parser->previous); + return UP(pm_float_node_rational_imaginary_create(parser, &parser->previous)); case PM_TOKEN_NUMBERED_REFERENCE: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_numbered_reference_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_numbered_reference_read_node_create(parser, &parser->previous)); if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17776,7 +17782,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } case PM_TOKEN_GLOBAL_VARIABLE: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_global_variable_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_global_variable_read_node_create(parser, &parser->previous)); if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17786,7 +17792,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } case PM_TOKEN_BACK_REFERENCE: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_back_reference_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_back_reference_read_node_create(parser, &parser->previous)); if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17811,7 +17817,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (parse_arguments_list(parser, &arguments, true, accepts_command_call, (uint16_t) (depth + 1))) { // Since we found arguments, we need to turn off the // variable call bit in the flags. - pm_node_flag_unset((pm_node_t *)call, PM_CALL_NODE_FLAGS_VARIABLE_CALL); + pm_node_flag_unset(UP(call), PM_CALL_NODE_FLAGS_VARIABLE_CALL); call->opening_loc = arguments.opening_loc; call->arguments = arguments.arguments; @@ -17858,7 +17864,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_destroy(parser, node); - return (pm_node_t *) fcall; + return UP(fcall); } } @@ -17890,9 +17896,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t content = parse_strings_empty_content(parser->previous.start); if (lex_mode.quote == PM_HEREDOC_QUOTE_BACKTICK) { - node = (pm_node_t *) pm_xstring_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY); + node = UP(pm_xstring_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY)); } else { - node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY); + node = UP(pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY)); } node->location.end = opening.end; @@ -17903,7 +17909,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // // parse_string_part handles its own errors, so there is no need // for us to add one here. - node = (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end); + node = UP(pm_missing_node_create(parser, parser->previous.start, parser->previous.end)); } else if (PM_NODE_TYPE_P(part, PM_STRING_NODE) && match2(parser, PM_TOKEN_HEREDOC_END, PM_TOKEN_EOF)) { // If we get here, then the part that we parsed was plain string // content and we're at the end of the heredoc, so we can return @@ -17925,7 +17931,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parse_heredoc_dedent_string(&cast->unescaped, common_whitespace); } - node = (pm_node_t *) cast; + node = UP(cast); expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length); } else { // If we get here, then we have multiple parts in the heredoc, @@ -17950,7 +17956,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_xstring_node_closing_set(cast, &parser->previous); cast->base.location = cast->opening_loc; - node = (pm_node_t *) cast; + node = UP(cast); } else { pm_interpolated_string_node_t *cast = pm_interpolated_string_node_create(parser, &opening, &parts, &opening); pm_node_list_free(&parts); @@ -17959,7 +17965,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_string_node_closing_set(cast, &parser->previous); cast->base.location = cast->opening_loc; - node = (pm_node_t *) cast; + node = UP(cast); } // If this is a heredoc that is indented with a ~, then we need @@ -17984,7 +17990,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } case PM_TOKEN_INSTANCE_VARIABLE: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_instance_variable_read_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_instance_variable_read_node_create(parser, &parser->previous)); if (binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -17995,32 +18001,32 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_TOKEN_INTEGER: { pm_node_flags_t base = parser->integer_base; parser_lex(parser); - return (pm_node_t *) pm_integer_node_create(parser, base, &parser->previous); + return UP(pm_integer_node_create(parser, base, &parser->previous)); } case PM_TOKEN_INTEGER_IMAGINARY: { pm_node_flags_t base = parser->integer_base; parser_lex(parser); - return (pm_node_t *) pm_integer_node_imaginary_create(parser, base, &parser->previous); + return UP(pm_integer_node_imaginary_create(parser, base, &parser->previous)); } case PM_TOKEN_INTEGER_RATIONAL: { pm_node_flags_t base = parser->integer_base; parser_lex(parser); - return (pm_node_t *) pm_integer_node_rational_create(parser, base, &parser->previous); + return UP(pm_integer_node_rational_create(parser, base, &parser->previous)); } case PM_TOKEN_INTEGER_RATIONAL_IMAGINARY: { pm_node_flags_t base = parser->integer_base; parser_lex(parser); - return (pm_node_t *) pm_integer_node_rational_imaginary_create(parser, base, &parser->previous); + return UP(pm_integer_node_rational_imaginary_create(parser, base, &parser->previous)); } case PM_TOKEN_KEYWORD___ENCODING__: parser_lex(parser); - return (pm_node_t *) pm_source_encoding_node_create(parser, &parser->previous); + return UP(pm_source_encoding_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD___FILE__: parser_lex(parser); - return (pm_node_t *) pm_source_file_node_create(parser, &parser->previous); + return UP(pm_source_file_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD___LINE__: parser_lex(parser); - return (pm_node_t *) pm_source_line_node_create(parser, &parser->previous); + return UP(pm_source_line_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_ALIAS: { if (binding_power != PM_BINDING_POWER_STATEMENT) { pm_parser_err_current(parser, PM_ERR_STATEMENT_ALIAS); @@ -18044,7 +18050,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_node(parser, old_name, PM_ERR_ALIAS_ARGUMENT); } - return (pm_node_t *) pm_alias_global_variable_node_create(parser, &keyword, new_name, old_name); + return UP(pm_alias_global_variable_node_create(parser, &keyword, new_name, old_name)); } case PM_SYMBOL_NODE: case PM_INTERPOLATED_SYMBOL_NODE: { @@ -18054,7 +18060,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } PRISM_FALLTHROUGH default: - return (pm_node_t *) pm_alias_method_node_create(parser, &keyword, new_name, old_name); + return UP(pm_alias_method_node_create(parser, &keyword, new_name, old_name)); } } case PM_TOKEN_KEYWORD_CASE: { @@ -18087,7 +18093,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_list_free(¤t_block_exits); pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS); - return (pm_node_t *) pm_case_node_create(parser, &case_keyword, predicate, &parser->previous); + return UP(pm_case_node_create(parser, &case_keyword, predicate, &parser->previous)); } // At this point we can create a case node, though we don't yet know @@ -18115,7 +18121,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); pm_splat_node_t *splat_node = pm_splat_node_create(parser, &operator, expression); - pm_when_node_conditions_append(when_node, (pm_node_t *) splat_node); + pm_when_node_conditions_append(when_node, UP(splat_node)); if (PM_NODE_TYPE_P(expression, PM_MISSING_NODE)) break; } else { @@ -18154,7 +18160,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - pm_case_node_condition_append(case_node, (pm_node_t *) when_node); + pm_case_node_condition_append(case_node, UP(when_node)); } // If we didn't parse any conditions (in or when) then we need @@ -18164,7 +18170,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_static_literals_free(&literals); - node = (pm_node_t *) case_node; + node = UP(case_node); } else { pm_case_match_node_t *case_node = pm_case_match_node_create(parser, &case_keyword, predicate, &end_keyword); @@ -18201,11 +18207,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (accept1(parser, PM_TOKEN_KEYWORD_IF_MODIFIER)) { pm_token_t keyword = parser->previous; pm_node_t *predicate = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_CONDITIONAL_IF_PREDICATE, (uint16_t) (depth + 1)); - pattern = (pm_node_t *) pm_if_node_modifier_create(parser, pattern, &keyword, predicate); + pattern = UP(pm_if_node_modifier_create(parser, pattern, &keyword, predicate)); } else if (accept1(parser, PM_TOKEN_KEYWORD_UNLESS_MODIFIER)) { pm_token_t keyword = parser->previous; pm_node_t *predicate = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_CONDITIONAL_UNLESS_PREDICATE, (uint16_t) (depth + 1)); - pattern = (pm_node_t *) pm_unless_node_modifier_create(parser, pattern, &keyword, predicate); + pattern = UP(pm_unless_node_modifier_create(parser, pattern, &keyword, predicate)); } // Now we need to check for the terminator of the in node's @@ -18234,7 +18240,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // Now that we have the full pattern and statements, we can // create the node and attach it to the case node. - pm_node_t *condition = (pm_node_t *) pm_in_node_create(parser, pattern, statements, &in_keyword, &then_keyword); + pm_node_t *condition = UP(pm_in_node_create(parser, pattern, statements, &in_keyword, &then_keyword)); pm_case_match_node_condition_append(case_node, condition); } @@ -18244,7 +18250,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS); } - node = (pm_node_t *) case_node; + node = UP(case_node); } accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); @@ -18307,7 +18313,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) begin_node; + return UP(begin_node); } case PM_TOKEN_KEYWORD_BEGIN_UPCASE: { pm_node_list_t current_block_exits = { 0 }; @@ -18333,7 +18339,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b flush_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_pre_execution_node_create(parser, &keyword, &opening, statements, &parser->previous); + return UP(pm_pre_execution_node_create(parser, &keyword, &opening, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_BREAK: case PM_TOKEN_KEYWORD_NEXT: @@ -18362,23 +18368,23 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b switch (keyword.type) { case PM_TOKEN_KEYWORD_BREAK: { - pm_node_t *node = (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments); + pm_node_t *node = UP(pm_break_node_create(parser, &keyword, arguments.arguments)); if (!parser->partial_script) parse_block_exit(parser, node); return node; } case PM_TOKEN_KEYWORD_NEXT: { - pm_node_t *node = (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments); + pm_node_t *node = UP(pm_next_node_create(parser, &keyword, arguments.arguments)); if (!parser->partial_script) parse_block_exit(parser, node); return node; } case PM_TOKEN_KEYWORD_RETURN: { - pm_node_t *node = (pm_node_t *) pm_return_node_create(parser, &keyword, arguments.arguments); + pm_node_t *node = UP(pm_return_node_create(parser, &keyword, arguments.arguments)); parse_return(parser, node); return node; } default: assert(false && "unreachable"); - return (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end); + return UP(pm_missing_node_create(parser, parser->previous.start, parser->previous.end)); } } case PM_TOKEN_KEYWORD_SUPER: { @@ -18393,10 +18399,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b arguments.arguments == NULL && ((arguments.block == NULL) || PM_NODE_TYPE_P(arguments.block, PM_BLOCK_NODE)) ) { - return (pm_node_t *) pm_forwarding_super_node_create(parser, &keyword, &arguments); + return UP(pm_forwarding_super_node_create(parser, &keyword, &arguments)); } - return (pm_node_t *) pm_super_node_create(parser, &keyword, &arguments); + return UP(pm_super_node_create(parser, &keyword, &arguments)); } case PM_TOKEN_KEYWORD_YIELD: { parser_lex(parser); @@ -18415,7 +18421,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b arguments.block = NULL; } - pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc); + pm_node_t *node = UP(pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc)); if (!parser->parsing_eval && !parser->partial_script) parse_yield(parser, node); return node; @@ -18442,13 +18448,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *statements = NULL; if (!match4(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_SCLASS, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_SCLASS, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_SCLASS, (uint16_t) (depth + 1)); + statements = UP(parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_SCLASS, (uint16_t) (depth + 1))); } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } @@ -18464,7 +18470,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b flush_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous); + return UP(pm_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous)); } pm_node_t *constant_path = parse_expression(parser, PM_BINDING_POWER_INDEX, false, false, PM_ERR_CLASS_NAME, (uint16_t) (depth + 1)); @@ -18500,13 +18506,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (!match4(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_CLASS, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_CLASS, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_CLASS, (uint16_t) (depth + 1)); + statements = UP(parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_CLASS, (uint16_t) (depth + 1))); } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } @@ -18530,7 +18536,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_class_node_create(parser, &locals, &class_keyword, constant_path, &name, &inheritance_operator, superclass, statements, &parser->previous); + return UP(pm_class_node_create(parser, &locals, &class_keyword, constant_path, &name, &inheritance_operator, superclass, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_DEF: { pm_node_list_t current_block_exits = { 0 }; @@ -18607,37 +18613,37 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b switch (identifier.type) { case PM_TOKEN_CONSTANT: - receiver = (pm_node_t *) pm_constant_read_node_create(parser, &identifier); + receiver = UP(pm_constant_read_node_create(parser, &identifier)); break; case PM_TOKEN_INSTANCE_VARIABLE: - receiver = (pm_node_t *) pm_instance_variable_read_node_create(parser, &identifier); + receiver = UP(pm_instance_variable_read_node_create(parser, &identifier)); break; case PM_TOKEN_CLASS_VARIABLE: - receiver = (pm_node_t *) pm_class_variable_read_node_create(parser, &identifier); + receiver = UP(pm_class_variable_read_node_create(parser, &identifier)); break; case PM_TOKEN_GLOBAL_VARIABLE: - receiver = (pm_node_t *) pm_global_variable_read_node_create(parser, &identifier); + receiver = UP(pm_global_variable_read_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD_NIL: - receiver = (pm_node_t *) pm_nil_node_create(parser, &identifier); + receiver = UP(pm_nil_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD_SELF: - receiver = (pm_node_t *) pm_self_node_create(parser, &identifier); + receiver = UP(pm_self_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD_TRUE: - receiver = (pm_node_t *) pm_true_node_create(parser, &identifier); + receiver = UP(pm_true_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD_FALSE: - receiver = (pm_node_t *) pm_false_node_create(parser, &identifier); + receiver = UP(pm_false_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD___FILE__: - receiver = (pm_node_t *) pm_source_file_node_create(parser, &identifier); + receiver = UP(pm_source_file_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD___LINE__: - receiver = (pm_node_t *) pm_source_line_node_create(parser, &identifier); + receiver = UP(pm_source_line_node_create(parser, &identifier)); break; case PM_TOKEN_KEYWORD___ENCODING__: - receiver = (pm_node_t *) pm_source_encoding_node_create(parser, &identifier); + receiver = UP(pm_source_encoding_node_create(parser, &identifier)); break; default: break; @@ -18672,7 +18678,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect2(parser, PM_TOKEN_DOT, PM_TOKEN_COLON_COLON, PM_ERR_DEF_RECEIVER_TERM); operator = parser->previous; - receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, expression, &rparen, 0); + receiver = UP(pm_parentheses_node_create(parser, &lparen, expression, &rparen, 0)); // To push `PM_CONTEXT_DEF_PARAMS` again is for the same // reason as described the above. @@ -18765,7 +18771,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b context_push(parser, PM_CONTEXT_DEF); pm_do_loop_stack_push(parser, false); - statements = (pm_node_t *) pm_statements_node_create(parser); + statements = UP(pm_statements_node_create(parser)); bool allow_command_call; if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { @@ -18784,7 +18790,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *value = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); - statement = (pm_node_t *) pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); + statement = UP(pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value)); } pm_statements_node_body_append(parser, (pm_statements_node_t *) statements, statement, false); @@ -18807,13 +18813,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (!match4(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_DEF, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_DEF, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &def_keyword, def_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_DEF, (uint16_t) (depth + 1)); + statements = UP(parse_rescues_implicit_begin(parser, opening_newline_index, &def_keyword, def_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_DEF, (uint16_t) (depth + 1))); } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &def_keyword, false, false); } @@ -18839,7 +18845,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b flush_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_def_node_create( + return UP(pm_def_node_create( parser, name_id, &name, @@ -18853,7 +18859,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b &rparen, &equal, &end_keyword - ); + )); } case PM_TOKEN_KEYWORD_DEFINED: { parser_lex(parser); @@ -18870,7 +18876,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b lparen = parser->previous; if (newline && accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { - expression = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous, 0); + expression = UP(pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous, 0)); lparen = not_provided(parser); rparen = not_provided(parser); } else { @@ -18891,13 +18897,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } context_pop(parser); - return (pm_node_t *) pm_defined_node_create( + return UP(pm_defined_node_create( parser, &lparen, expression, &rparen, &PM_LOCATION_TOKEN_VALUE(&keyword) - ); + )); } case PM_TOKEN_KEYWORD_END_UPCASE: { if (binding_power != PM_BINDING_POWER_STATEMENT) { @@ -18916,11 +18922,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_POSTEXE, (uint16_t) (depth + 1)); expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_END_UPCASE_TERM); - return (pm_node_t *) pm_post_execution_node_create(parser, &keyword, &opening, statements, &parser->previous); + return UP(pm_post_execution_node_create(parser, &keyword, &opening, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_FALSE: parser_lex(parser); - return (pm_node_t *) pm_false_node_create(parser, &parser->previous); + return UP(pm_false_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_FOR: { size_t opening_newline_index = token_newline_index(parser); parser_lex(parser); @@ -18939,12 +18945,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b name = parse_expression(parser, PM_BINDING_POWER_INDEX, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); } - index = (pm_node_t *) pm_splat_node_create(parser, &star_operator, name); + index = UP(pm_splat_node_create(parser, &star_operator, name)); } else if (token_begins_expression_p(parser->current.type)) { index = parse_expression(parser, PM_BINDING_POWER_INDEX, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1)); } else { pm_parser_err_token(parser, &for_keyword, PM_ERR_FOR_INDEX); - index = (pm_node_t *) pm_missing_node_create(parser, for_keyword.start, for_keyword.end); + index = UP(pm_missing_node_create(parser, for_keyword.start, for_keyword.end)); } // Now, if there are multiple index expressions, parse them out. @@ -18981,7 +18987,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &for_keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_FOR_TERM); - return (pm_node_t *) pm_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous); + return UP(pm_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous)); } case PM_TOKEN_KEYWORD_IF: if (parser_end_of_line_p(parser)) { @@ -19021,7 +19027,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - return (pm_node_t *) undef; + return UP(undef); } case PM_TOKEN_KEYWORD_NOT: { parser_lex(parser); @@ -19041,7 +19047,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_current(parser, PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER); } - return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + return UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); } accept1(parser, PM_TOKEN_NEWLINE); @@ -19050,7 +19056,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t lparen = parser->previous; if (accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { - receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous, 0); + receiver = UP(pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous, 0)); } else { arguments.opening_loc = PM_LOCATION_TOKEN_VALUE(&lparen); receiver = parse_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_NOT_EXPRESSION, (uint16_t) (depth + 1)); @@ -19065,7 +19071,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b receiver = parse_expression(parser, PM_BINDING_POWER_NOT, true, false, PM_ERR_NOT_EXPRESSION, (uint16_t) (depth + 1)); } - return (pm_node_t *) pm_call_node_not_create(parser, receiver, &message, &arguments); + return UP(pm_call_node_not_create(parser, receiver, &message, &arguments)); } case PM_TOKEN_KEYWORD_UNLESS: { size_t opening_newline_index = token_newline_index(parser); @@ -19091,14 +19097,14 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_list_free(¤t_block_exits); pm_token_t missing = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; - return (pm_node_t *) pm_module_node_create(parser, NULL, &module_keyword, constant_path, &missing, NULL, &missing); + return UP(pm_module_node_create(parser, NULL, &module_keyword, constant_path, &missing, NULL, &missing)); } while (accept1(parser, PM_TOKEN_COLON_COLON)) { pm_token_t double_colon = parser->previous; expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); - constant_path = (pm_node_t *) pm_constant_path_node_create(parser, constant_path, &double_colon, &parser->previous); + constant_path = UP(pm_constant_path_node_create(parser, constant_path, &double_colon, &parser->previous)); } // Here we retrieve the name of the module. If it wasn't a constant, @@ -19115,13 +19121,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (!match4(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - statements = (pm_node_t *) parse_statements(parser, PM_CONTEXT_MODULE, (uint16_t) (depth + 1)); + statements = UP(parse_statements(parser, PM_CONTEXT_MODULE, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_ELSE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &module_keyword, module_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_MODULE, (uint16_t) (depth + 1)); + statements = UP(parse_rescues_implicit_begin(parser, opening_newline_index, &module_keyword, module_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_MODULE, (uint16_t) (depth + 1))); } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &module_keyword, false, false); } @@ -19139,15 +19145,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_module_node_create(parser, &locals, &module_keyword, constant_path, &name, statements, &parser->previous); + return UP(pm_module_node_create(parser, &locals, &module_keyword, constant_path, &name, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_NIL: parser_lex(parser); - return (pm_node_t *) pm_nil_node_create(parser, &parser->previous); + return UP(pm_nil_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_REDO: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_redo_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_redo_node_create(parser, &parser->previous)); if (!parser->partial_script) parse_block_exit(parser, node); return node; @@ -19155,17 +19161,17 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_TOKEN_KEYWORD_RETRY: { parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_retry_node_create(parser, &parser->previous); + pm_node_t *node = UP(pm_retry_node_create(parser, &parser->previous)); parse_retry(parser, node); return node; } case PM_TOKEN_KEYWORD_SELF: parser_lex(parser); - return (pm_node_t *) pm_self_node_create(parser, &parser->previous); + return UP(pm_self_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_TRUE: parser_lex(parser); - return (pm_node_t *) pm_true_node_create(parser, &parser->previous); + return UP(pm_true_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_UNTIL: { size_t opening_newline_index = token_newline_index(parser); @@ -19198,7 +19204,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_UNTIL_TERM); - return (pm_node_t *) pm_until_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0); + return UP(pm_until_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0)); } case PM_TOKEN_KEYWORD_WHILE: { size_t opening_newline_index = token_newline_index(parser); @@ -19232,7 +19238,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_WHILE_TERM); - return (pm_node_t *) pm_while_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0); + return UP(pm_while_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0)); } case PM_TOKEN_PERCENT_LOWER_I: { parser_lex(parser); @@ -19246,7 +19252,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match1(parser, PM_TOKEN_STRING_CONTENT)) { pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_array_node_elements_append(array, (pm_node_t *) pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing)); + pm_array_node_elements_append(array, UP(pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing))); } expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_I_LOWER_ELEMENT); @@ -19261,7 +19267,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_array_node_close_set(array, &closing); - return (pm_node_t *) array; + return UP(array); } case PM_TOKEN_PERCENT_UPPER_I: { parser_lex(parser); @@ -19296,13 +19302,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we hit content and the current node is NULL, then this is // the first string content we've seen. In that case we're going // to create a new string node and set that to the current. - current = (pm_node_t *) pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing); + current = UP(pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing)); parser_lex(parser); } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_SYMBOL_NODE)) { // If we hit string content and the current node is an // interpolated string, then we need to append the string content // to the list of child nodes. - pm_node_t *string = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &parser->current, &closing); + pm_node_t *string = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); parser_lex(parser); pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, string); @@ -19314,8 +19320,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t bounds = not_provided(parser); pm_token_t content = { .type = PM_TOKEN_STRING_CONTENT, .start = cast->value_loc.start, .end = cast->value_loc.end }; - pm_node_t *first_string = (pm_node_t *) pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &cast->unescaped); - pm_node_t *second_string = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &parser->previous, &closing); + pm_node_t *first_string = UP(pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &cast->unescaped)); + pm_node_t *second_string = UP(pm_string_node_create_current_string(parser, &opening, &parser->previous, &closing)); parser_lex(parser); pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); @@ -19323,7 +19329,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_append(interpolated, second_string); xfree(current); - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else { assert(false && "unreachable"); } @@ -19338,7 +19344,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // node to a new interpolated string. pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - current = (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); + current = UP(pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing)); } else if (PM_NODE_TYPE_P(current, PM_SYMBOL_NODE)) { // If we hit an embedded variable and the current node is a string // node, then we'll convert the current into an interpolated @@ -19347,11 +19353,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t closing = not_provided(parser); pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); - current = (pm_node_t *) pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current); + current = UP(pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current)); pm_interpolated_symbol_node_append(interpolated, current); interpolated->base.location.start = current->location.start; start_location_set = true; - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else { // If we hit an embedded variable and the current node is an // interpolated string, then we'll just add the embedded variable. @@ -19372,7 +19378,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // node to a new interpolated string. pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - current = (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); + current = UP(pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing)); } else if (PM_NODE_TYPE_P(current, PM_SYMBOL_NODE)) { // If we hit an embedded expression and the current node is a // string node, then we'll convert the current into an @@ -19382,11 +19388,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t closing = not_provided(parser); pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); - current = (pm_node_t *) pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current); + current = UP(pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current)); pm_interpolated_symbol_node_append(interpolated, current); interpolated->base.location.start = current->location.start; start_location_set = true; - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_SYMBOL_NODE)) { // If we hit an embedded expression and the current node is an // interpolated string, then we'll just continue on. @@ -19422,7 +19428,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_array_node_close_set(array, &closing); - return (pm_node_t *) array; + return UP(array); } case PM_TOKEN_PERCENT_LOWER_W: { parser_lex(parser); @@ -19440,7 +19446,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_node_t *string = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &parser->current, &closing); + pm_node_t *string = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); pm_array_node_elements_append(array, string); } @@ -19456,7 +19462,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_array_node_close_set(array, &closing); - return (pm_node_t *) array; + return UP(array); } case PM_TOKEN_PERCENT_UPPER_W: { parser_lex(parser); @@ -19492,7 +19498,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_node_t *string = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &parser->current, &closing); + pm_node_t *string = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); pm_node_flag_set(string, parse_unescaped_encoding(parser)); parser_lex(parser); @@ -19515,7 +19521,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); pm_interpolated_string_node_append(interpolated, current); pm_interpolated_string_node_append(interpolated, string); - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else { assert(false && "unreachable"); } @@ -19530,7 +19536,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // interpolated string. pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - current = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, NULL, &closing); + current = UP(pm_interpolated_string_node_create(parser, &opening, NULL, &closing)); } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { // If we hit an embedded variable and the current // node is a string node, then we'll convert the @@ -19540,7 +19546,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t closing = not_provided(parser); pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); pm_interpolated_string_node_append(interpolated, current); - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else { // If we hit an embedded variable and the current // node is an interpolated string, then we'll just @@ -19559,7 +19565,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // interpolated string. pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - current = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, NULL, &closing); + current = UP(pm_interpolated_string_node_create(parser, &opening, NULL, &closing)); } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { // If we hit an embedded expression and the current // node is a string node, then we'll convert the @@ -19569,7 +19575,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t closing = not_provided(parser); pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); pm_interpolated_string_node_append(interpolated, current); - current = (pm_node_t *) interpolated; + current = UP(interpolated); } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_STRING_NODE)) { // If we hit an embedded expression and the current // node is an interpolated string, then we'll just @@ -19603,7 +19609,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_array_node_close_set(array, &closing); - return (pm_node_t *) array; + return UP(array); } case PM_TOKEN_REGEXP_BEGIN: { pm_token_t opening = parser->current; @@ -19621,7 +19627,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); - pm_node_t *node = (pm_node_t *) pm_regular_expression_node_create(parser, &opening, &content, &parser->previous); + pm_node_t *node = UP(pm_regular_expression_node_create(parser, &opening, &content, &parser->previous)); pm_node_flag_set(node, PM_REGULAR_EXPRESSION_FLAGS_FORCED_US_ASCII_ENCODING); return node; @@ -19653,8 +19659,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parse_regular_expression_errors(parser, node); } - pm_node_flag_set((pm_node_t *) node, parse_and_validate_regular_expression_encoding(parser, &unescaped, ascii_only, node->base.flags)); - return (pm_node_t *) node; + pm_node_flag_set(UP(node), parse_and_validate_regular_expression_encoding(parser, &unescaped, ascii_only, node->base.flags)); + return UP(node); } // If we get here, then we have interpolation so we'll need to create @@ -19663,7 +19669,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &parser->previous, &closing, &unescaped); + pm_node_t *part = UP(pm_string_node_create_unescaped(parser, &opening, &parser->previous, &closing, &unescaped)); if (parser->encoding == PM_ENCODING_US_ASCII_ENTRY) { // This is extremely strange, but the first string part of a @@ -19698,7 +19704,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_interpolated_regular_expression_node_closing_set(parser, interpolated, &closing); - return (pm_node_t *) interpolated; + return UP(interpolated); } case PM_TOKEN_BACKTICK: case PM_TOKEN_PERCENT_LOWER_X: { @@ -19720,7 +19726,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b }; parser_lex(parser); - return (pm_node_t *) pm_xstring_node_create(parser, &opening, &content, &parser->previous); + return UP(pm_xstring_node_create(parser, &opening, &content, &parser->previous)); } pm_interpolated_x_string_node_t *node; @@ -19735,7 +19741,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); if (match1(parser, PM_TOKEN_STRING_END)) { - pm_node_t *node = (pm_node_t *) pm_xstring_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped); + pm_node_t *node = UP(pm_xstring_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped)); pm_node_flag_set(node, parse_unescaped_encoding(parser)); parser_lex(parser); return node; @@ -19748,7 +19754,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &parser->previous, &closing, &unescaped); + pm_node_t *part = UP(pm_string_node_create_unescaped(parser, &opening, &parser->previous, &closing, &unescaped)); pm_node_flag_set(part, parse_unescaped_encoding(parser)); pm_interpolated_xstring_node_append(node, part); @@ -19775,7 +19781,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_interpolated_xstring_node_closing_set(node, &closing); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_USTAR: { parser_lex(parser); @@ -19785,7 +19791,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // still lex past it though and create a missing node place. if (binding_power != PM_BINDING_POWER_STATEMENT) { pm_parser_err_prefix(parser, diag_id); - return (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end); + return UP(pm_missing_node_create(parser, parser->previous.start, parser->previous.end)); } pm_token_t operator = parser->previous; @@ -19795,7 +19801,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b name = parse_expression(parser, PM_BINDING_POWER_INDEX, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); } - pm_node_t *splat = (pm_node_t *) pm_splat_node_create(parser, &operator, name); + pm_node_t *splat = UP(pm_splat_node_create(parser, &operator, name)); if (match1(parser, PM_TOKEN_COMMA)) { return parse_targets_validate(parser, splat, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -19815,7 +19821,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_call_node_t *node = pm_call_node_unary_create(parser, &operator, receiver, "!"); pm_conditional_predicate(parser, receiver, PM_CONDITIONAL_PREDICATE_TYPE_NOT); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_TILDE: { if (binding_power > PM_BINDING_POWER_UNARY) { @@ -19827,7 +19833,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = parse_expression(parser, pm_binding_powers[parser->previous.type].right, false, false, PM_ERR_UNARY_RECEIVER, (uint16_t) (depth + 1)); pm_call_node_t *node = pm_call_node_unary_create(parser, &operator, receiver, "~"); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_UMINUS: { if (binding_power > PM_BINDING_POWER_UNARY) { @@ -19839,7 +19845,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = parse_expression(parser, pm_binding_powers[parser->previous.type].right, false, false, PM_ERR_UNARY_RECEIVER, (uint16_t) (depth + 1)); pm_call_node_t *node = pm_call_node_unary_create(parser, &operator, receiver, "-@"); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_UMINUS_NUM: { parser_lex(parser); @@ -19850,8 +19856,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (accept1(parser, PM_TOKEN_STAR_STAR)) { pm_token_t exponent_operator = parser->previous; pm_node_t *exponent = parse_expression(parser, pm_binding_powers[exponent_operator.type].right, false, false, PM_ERR_EXPECT_ARGUMENT, (uint16_t) (depth + 1)); - node = (pm_node_t *) pm_call_node_binary_create(parser, node, &exponent_operator, exponent, 0); - node = (pm_node_t *) pm_call_node_unary_create(parser, &operator, node, "-@"); + node = UP(pm_call_node_binary_create(parser, node, &exponent_operator, exponent, 0)); + node = UP(pm_call_node_unary_create(parser, &operator, node, "-@")); } else { switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: @@ -19861,7 +19867,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parse_negative_numeric(node); break; default: - node = (pm_node_t *) pm_call_node_unary_create(parser, &operator, node, "-@"); + node = UP(pm_call_node_unary_create(parser, &operator, node, "-@")); break; } } @@ -19919,7 +19925,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b opening = parser->previous; if (!match1(parser, PM_TOKEN_BRACE_RIGHT)) { - body = (pm_node_t *) parse_statements(parser, PM_CONTEXT_LAMBDA_BRACES, (uint16_t) (depth + 1)); + body = UP(parse_statements(parser, PM_CONTEXT_LAMBDA_BRACES, (uint16_t) (depth + 1))); } parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); @@ -19930,13 +19936,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (!match3(parser, PM_TOKEN_KEYWORD_END, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { pm_accepts_block_stack_push(parser, true); - body = (pm_node_t *) parse_statements(parser, PM_CONTEXT_LAMBDA_DO_END, (uint16_t) (depth + 1)); + body = UP(parse_statements(parser, PM_CONTEXT_LAMBDA_DO_END, (uint16_t) (depth + 1))); pm_accepts_block_stack_pop(parser); } if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(body == NULL || PM_NODE_TYPE_P(body, PM_STATEMENTS_NODE)); - body = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &operator, opening.start, (pm_statements_node_t *) body, PM_RESCUES_LAMBDA, (uint16_t) (depth + 1)); + body = UP(parse_rescues_implicit_begin(parser, opening_newline_index, &operator, opening.start, (pm_statements_node_t *) body, PM_RESCUES_LAMBDA, (uint16_t) (depth + 1))); } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); } @@ -19946,12 +19952,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_constant_id_list_t locals; pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); - pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous); + pm_node_t *parameters = parse_blocklike_parameters(parser, UP(block_parameters), &operator, &parser->previous); pm_parser_scope_pop(parser); pm_accepts_block_stack_pop(parser); - return (pm_node_t *) pm_lambda_node_create(parser, &locals, &operator, &opening, &parser->previous, parameters, body); + return UP(pm_lambda_node_create(parser, &locals, &operator, &opening, &parser->previous, parameters, body)); } case PM_TOKEN_UPLUS: { if (binding_power > PM_BINDING_POWER_UNARY) { @@ -19963,7 +19969,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = parse_expression(parser, pm_binding_powers[parser->previous.type].right, false, false, PM_ERR_UNARY_RECEIVER, (uint16_t) (depth + 1)); pm_call_node_t *node = pm_call_node_unary_create(parser, &operator, receiver, "+@"); - return (pm_node_t *) node; + return UP(node); } case PM_TOKEN_STRING_BEGIN: return parse_strings(parser, NULL, accepts_label, (uint16_t) (depth + 1)); @@ -19999,7 +20005,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_prefix(parser, diag_id); } - return (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end); + return UP(pm_missing_node_create(parser, parser->previous.start, parser->previous.end)); } } } @@ -20028,7 +20034,7 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, false, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); - return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); + return UP(pm_rescue_modifier_node_create(parser, value, &rescue, right)); } return value; @@ -20100,7 +20106,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding pm_array_node_t *array = pm_array_node_create(parser, &opening); pm_array_node_elements_append(array, value); - value = (pm_node_t *) array; + value = UP(array); while (accept1(parser, PM_TOKEN_COMMA)) { pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT, (uint16_t) (depth + 1)); @@ -20134,7 +20140,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, accepts_command_call_inner, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); - return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); + return UP(pm_rescue_modifier_node_create(parser, value, &rescue, right)); } return value; @@ -20151,15 +20157,15 @@ static void parse_call_operator_write(pm_parser_t *parser, pm_call_node_t *call_node, const pm_token_t *operator) { if (call_node->arguments != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_ARGUMENTS); - pm_node_unreference(parser, (pm_node_t *) call_node->arguments); - pm_node_destroy(parser, (pm_node_t *) call_node->arguments); + pm_node_unreference(parser, UP(call_node->arguments)); + pm_node_destroy(parser, UP(call_node->arguments)); call_node->arguments = NULL; } if (call_node->block != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_BLOCK); - pm_node_unreference(parser, (pm_node_t *) call_node->block); - pm_node_destroy(parser, (pm_node_t *) call_node->block); + pm_node_unreference(parser, UP(call_node->block)); + pm_node_destroy(parser, UP(call_node->block)); call_node->block = NULL; } } @@ -20391,7 +20397,7 @@ parse_regular_expression_named_capture(const pm_string_t *capture, void *data) { // Next, create the local variable target and add it to the list of // targets for the match. - pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create(parser, &location, name, depth == -1 ? 0 : (uint32_t) depth); + pm_node_t *target = UP(pm_local_variable_target_node_create(parser, &location, name, depth == -1 ? 0 : (uint32_t) depth)); pm_node_list_append(&callback_data->match->targets, target); } @@ -20422,9 +20428,9 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * pm_constant_id_list_free(&callback_data.names); if (callback_data.match != NULL) { - return (pm_node_t *) callback_data.match; + return UP(callback_data.match); } else { - return (pm_node_t *) call; + return UP(call); } } @@ -20469,7 +20475,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_values(parser, previous_binding_power, PM_BINDING_POWER_MULTI_ASSIGNMENT + 1, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL, (uint16_t) (depth + 1)); - return parse_write(parser, (pm_node_t *) multi_target, &token, value); + return parse_write(parser, UP(multi_target), &token, value); } case PM_SOURCE_ENCODING_NODE: case PM_FALSE_NODE: @@ -20503,7 +20509,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_global_variable_and_write_node_create(parser, node, &token, value); + pm_node_t *result = UP(pm_global_variable_and_write_node_create(parser, node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20512,7 +20518,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_class_variable_and_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_class_variable_and_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20521,7 +20527,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value)); return parse_shareable_constant_write(parser, write); } @@ -20529,7 +20535,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); @@ -20538,7 +20544,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_instance_variable_and_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_instance_variable_and_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20548,7 +20554,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, node, &token, value, name, 0); + pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); pm_node_destroy(parser, node); @@ -20564,7 +20570,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, node, &token, value, cast->name, cast->depth); + pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, node, &token, value, cast->name, cast->depth)); pm_node_destroy(parser, node); return result; @@ -20583,9 +20589,9 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); + pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, (pm_node_t *) cast); + pm_node_destroy(parser, UP(cast)); return result; } @@ -20598,7 +20604,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // an aset expression. if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_index_and_write_node_create(parser, cast, &token, value); + return UP(pm_index_and_write_node_create(parser, cast, &token, value)); } // If this node cannot be writable, then we have an error. @@ -20610,7 +20616,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parse_call_operator_write(parser, cast, &token); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_and_write_node_create(parser, cast, &token, value); + return UP(pm_call_and_write_node_create(parser, cast, &token, value)); } case PM_MULTI_WRITE_NODE: { parser_lex(parser); @@ -20637,7 +20643,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_global_variable_or_write_node_create(parser, node, &token, value); + pm_node_t *result = UP(pm_global_variable_or_write_node_create(parser, node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20646,7 +20652,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_class_variable_or_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_class_variable_or_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20655,7 +20661,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value)); return parse_shareable_constant_write(parser, write); } @@ -20663,7 +20669,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); @@ -20672,7 +20678,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_instance_variable_or_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_instance_variable_or_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20682,7 +20688,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, node, &token, value, name, 0); + pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); pm_node_destroy(parser, node); @@ -20698,7 +20704,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, node, &token, value, cast->name, cast->depth); + pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, node, &token, value, cast->name, cast->depth)); pm_node_destroy(parser, node); return result; @@ -20717,9 +20723,9 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); + pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, (pm_node_t *) cast); + pm_node_destroy(parser, UP(cast)); return result; } @@ -20732,7 +20738,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // an aset expression. if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_index_or_write_node_create(parser, cast, &token, value); + return UP(pm_index_or_write_node_create(parser, cast, &token, value)); } // If this node cannot be writable, then we have an error. @@ -20744,7 +20750,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parse_call_operator_write(parser, cast, &token); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_or_write_node_create(parser, cast, &token, value); + return UP(pm_call_or_write_node_create(parser, cast, &token, value)); } case PM_MULTI_WRITE_NODE: { parser_lex(parser); @@ -20781,7 +20787,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_global_variable_operator_write_node_create(parser, node, &token, value); + pm_node_t *result = UP(pm_global_variable_operator_write_node_create(parser, node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20790,7 +20796,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_class_variable_operator_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_class_variable_operator_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20799,7 +20805,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value)); return parse_shareable_constant_write(parser, write); } @@ -20807,7 +20813,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *write = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = UP(pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); @@ -20816,7 +20822,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_instance_variable_operator_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value); + pm_node_t *result = UP(pm_instance_variable_operator_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); pm_node_destroy(parser, node); return result; @@ -20826,7 +20832,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, node, &token, value, name, 0); + pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); pm_node_destroy(parser, node); @@ -20842,7 +20848,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, node, &token, value, cast->name, cast->depth); + pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, node, &token, value, cast->name, cast->depth)); pm_node_destroy(parser, node); return result; @@ -20860,9 +20866,9 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); + pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, (pm_node_t *) cast); + pm_node_destroy(parser, UP(cast)); return result; } @@ -20871,7 +20877,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // an aset expression. if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_index_operator_write_node_create(parser, cast, &token, value); + return UP(pm_index_operator_write_node_create(parser, cast, &token, value)); } // If this node cannot be writable, then we have an error. @@ -20883,7 +20889,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parse_call_operator_write(parser, cast, &token); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_operator_write_node_create(parser, cast, &token, value); + return UP(pm_call_operator_write_node_create(parser, cast, &token, value)); } case PM_MULTI_WRITE_NODE: { parser_lex(parser); @@ -20905,14 +20911,14 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *right = parse_expression(parser, binding_power, parser->previous.type == PM_TOKEN_KEYWORD_AND, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_and_node_create(parser, node, &token, right); + return UP(pm_and_node_create(parser, node, &token, right)); } case PM_TOKEN_KEYWORD_OR: case PM_TOKEN_PIPE_PIPE: { parser_lex(parser); pm_node_t *right = parse_expression(parser, binding_power, parser->previous.type == PM_TOKEN_KEYWORD_OR, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_or_node_create(parser, node, &token, right); + return UP(pm_or_node_create(parser, node, &token, right)); } case PM_TOKEN_EQUAL_TILDE: { // Note that we _must_ parse the value before adding the local @@ -20927,7 +20933,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // By default, we're going to create a call node and then return it. pm_call_node_t *call = pm_call_node_binary_create(parser, node, &token, argument, 0); - pm_node_t *result = (pm_node_t *) call; + pm_node_t *result = UP(call); // If the receiver of this =~ is a regular expression node, then we // need to introduce local variables for it based on its named @@ -21030,7 +21036,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t } pm_node_t *argument = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_node_binary_create(parser, node, &token, argument, 0); + return UP(pm_call_node_binary_create(parser, node, &token, argument, 0)); } case PM_TOKEN_GREATER: case PM_TOKEN_GREATER_EQUAL: @@ -21042,7 +21048,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *argument = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_node_binary_create(parser, node, &token, argument, PM_CALL_NODE_FLAGS_COMPARISON); + return UP(pm_call_node_binary_create(parser, node, &token, argument, PM_CALL_NODE_FLAGS_COMPARISON)); } case PM_TOKEN_AMPERSAND_DOT: case PM_TOKEN_DOT: { @@ -21053,7 +21059,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // This if statement handles the foo.() syntax. if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { parse_arguments_list(parser, &arguments, true, false, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_node_shorthand_create(parser, node, &operator, &arguments); + return UP(pm_call_node_shorthand_create(parser, node, &operator, &arguments)); } switch (PM_NODE_TYPE(node)) { @@ -21109,9 +21115,9 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t arguments.opening_loc.start == NULL && match1(parser, PM_TOKEN_COMMA) ) { - return parse_targets_validate(parser, (pm_node_t *) call, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); + return parse_targets_validate(parser, UP(call), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } else { - return (pm_node_t *) call; + return UP(call); } } case PM_TOKEN_DOT_DOT: @@ -21123,21 +21129,21 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t right = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); } - return (pm_node_t *) pm_range_node_create(parser, node, &token, right); + return UP(pm_range_node_create(parser, node, &token, right)); } case PM_TOKEN_KEYWORD_IF_MODIFIER: { pm_token_t keyword = parser->current; parser_lex(parser); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, false, PM_ERR_CONDITIONAL_IF_PREDICATE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_if_node_modifier_create(parser, node, &keyword, predicate); + return UP(pm_if_node_modifier_create(parser, node, &keyword, predicate)); } case PM_TOKEN_KEYWORD_UNLESS_MODIFIER: { pm_token_t keyword = parser->current; parser_lex(parser); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, false, PM_ERR_CONDITIONAL_UNLESS_PREDICATE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_unless_node_modifier_create(parser, node, &keyword, predicate); + return UP(pm_unless_node_modifier_create(parser, node, &keyword, predicate)); } case PM_TOKEN_KEYWORD_UNTIL_MODIFIER: { parser_lex(parser); @@ -21145,7 +21151,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_statements_node_body_append(parser, statements, node, true); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, false, PM_ERR_CONDITIONAL_UNTIL_PREDICATE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_until_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); + return UP(pm_until_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0)); } case PM_TOKEN_KEYWORD_WHILE_MODIFIER: { parser_lex(parser); @@ -21153,7 +21159,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_statements_node_body_append(parser, statements, node, true); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, false, PM_ERR_CONDITIONAL_WHILE_PREDICATE, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); + return UP(pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0)); } case PM_TOKEN_QUESTION_MARK: { context_push(parser, PM_CONTEXT_TERNARY); @@ -21173,13 +21179,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // accidentally move past a ':' token that occurs after the syntax // error. pm_token_t colon = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; - pm_node_t *false_expression = (pm_node_t *) pm_missing_node_create(parser, colon.start, colon.end); + pm_node_t *false_expression = UP(pm_missing_node_create(parser, colon.start, colon.end)); context_pop(parser); pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); + return UP(pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression)); } accept1(parser, PM_TOKEN_NEWLINE); @@ -21192,7 +21198,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); - return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); + return UP(pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression)); } case PM_TOKEN_COLON_COLON: { parser_lex(parser); @@ -21217,10 +21223,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, true, accepts_command_call, (uint16_t) (depth + 1)); - path = (pm_node_t *) pm_call_node_call_create(parser, node, &delimiter, &message, &arguments); + path = UP(pm_call_node_call_create(parser, node, &delimiter, &message, &arguments)); } else { // Otherwise, this is a constant path. That would look like Foo::Bar. - path = (pm_node_t *) pm_constant_path_node_create(parser, node, &delimiter, &parser->previous); + path = UP(pm_constant_path_node_create(parser, node, &delimiter, &parser->previous)); } // If this is followed by a comma then it is a multiple assignment. @@ -21245,10 +21251,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If this is followed by a comma then it is a multiple assignment. if (previous_binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { - return parse_targets_validate(parser, (pm_node_t *) call, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); + return parse_targets_validate(parser, UP(call), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } - return (pm_node_t *) call; + return UP(call); } case PM_TOKEN_PARENTHESIS_LEFT: { // If we have a parenthesis following a '::' operator, then it is the @@ -21256,11 +21262,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, true, false, (uint16_t) (depth + 1)); - return (pm_node_t *) pm_call_node_shorthand_create(parser, node, &delimiter, &arguments); + return UP(pm_call_node_shorthand_create(parser, node, &delimiter, &arguments)); } default: { expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); - return (pm_node_t *) pm_constant_path_node_create(parser, node, &delimiter, &parser->previous); + return UP(pm_constant_path_node_create(parser, node, &delimiter, &parser->previous)); } } } @@ -21272,7 +21278,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_expression(parser, binding_power, true, false, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); context_pop(parser); - return (pm_node_t *) pm_rescue_modifier_node_create(parser, node, &token, value); + return UP(pm_rescue_modifier_node_create(parser, node, &token, value)); } case PM_TOKEN_BRACKET_LEFT: { parser_lex(parser); @@ -21293,7 +21299,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // assignment and we should parse the targets. if (previous_binding_power == PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_COMMA)) { pm_call_node_t *aref = pm_call_node_aref_create(parser, node, &arguments); - return parse_targets_validate(parser, (pm_node_t *) aref, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); + return parse_targets_validate(parser, UP(aref), PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); } // If we're at the end of the arguments, we can now check if there is a @@ -21309,17 +21315,17 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t if (block != NULL) { if (arguments.block != NULL) { - pm_parser_err_node(parser, (pm_node_t *) block, PM_ERR_ARGUMENT_AFTER_BLOCK); + pm_parser_err_node(parser, UP(block), PM_ERR_ARGUMENT_AFTER_BLOCK); if (arguments.arguments == NULL) { arguments.arguments = pm_arguments_node_create(parser); } pm_arguments_node_arguments_append(arguments.arguments, arguments.block); } - arguments.block = (pm_node_t *) block; + arguments.block = UP(block); } - return (pm_node_t *) pm_call_node_aref_create(parser, node, &arguments); + return UP(pm_call_node_aref_create(parser, node, &arguments)); } case PM_TOKEN_KEYWORD_IN: { bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; @@ -21336,7 +21342,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser->pattern_matching_newlines = previous_pattern_matching_newlines; pm_constant_id_list_free(&captures); - return (pm_node_t *) pm_match_predicate_node_create(parser, node, pattern, &operator); + return UP(pm_match_predicate_node_create(parser, node, pattern, &operator)); } case PM_TOKEN_EQUAL_GREATER: { bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; @@ -21353,7 +21359,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser->pattern_matching_newlines = previous_pattern_matching_newlines; pm_constant_id_list_free(&captures); - return (pm_node_t *) pm_match_required_node_create(parser, node, pattern, &operator); + return UP(pm_match_required_node_create(parser, node, pattern, &operator)); } default: assert(false && "unreachable"); @@ -21390,7 +21396,7 @@ static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, bool accepts_label, pm_diagnostic_id_t diag_id, uint16_t depth) { if (PRISM_UNLIKELY(depth >= PRISM_DEPTH_MAXIMUM)) { pm_parser_err_current(parser, PM_ERR_NESTING_TOO_DEEP); - return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + return UP(pm_missing_node_create(parser, parser->current.start, parser->current.end)); } pm_node_t *node = parse_expression_prefix(parser, binding_power, accepts_command_call, accepts_label, diag_id, depth); @@ -21584,14 +21590,14 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, - (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2)) + UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2))) ); - pm_statements_node_body_append(parser, statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( + pm_statements_node_body_append(parser, statements, UP(pm_call_node_fcall_synthesized_create( parser, arguments, pm_parser_constant_id_constant(parser, "print", 5) - ), true); + )), true); } if (PM_PARSER_COMMAND_LINE_OPTION_N(parser)) { @@ -21603,46 +21609,46 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, - (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$;", 2)) + UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$;", 2))) ); pm_global_variable_read_node_t *receiver = pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2)); - pm_call_node_t *call = pm_call_node_call_synthesized_create(parser, (pm_node_t *) receiver, "split", arguments); + pm_call_node_t *call = pm_call_node_call_synthesized_create(parser, UP(receiver), "split", arguments); pm_global_variable_write_node_t *write = pm_global_variable_write_node_synthesized_create( parser, pm_parser_constant_id_constant(parser, "$F", 2), - (pm_node_t *) call + UP(call) ); - pm_statements_node_body_prepend(statements, (pm_node_t *) write); + pm_statements_node_body_prepend(statements, UP(write)); } pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, - (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$/", 2)) + UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$/", 2))) ); if (PM_PARSER_COMMAND_LINE_OPTION_L(parser)) { pm_keyword_hash_node_t *keywords = pm_keyword_hash_node_create(parser); - pm_keyword_hash_node_elements_append(keywords, (pm_node_t *) pm_assoc_node_create( + pm_keyword_hash_node_elements_append(keywords, UP(pm_assoc_node_create( parser, - (pm_node_t *) pm_symbol_node_synthesized_create(parser, "chomp"), + UP(pm_symbol_node_synthesized_create(parser, "chomp")), &(pm_token_t) { .type = PM_TOKEN_NOT_PROVIDED, .start = parser->start, .end = parser->start }, - (pm_node_t *) pm_true_node_synthesized_create(parser) - )); + UP(pm_true_node_synthesized_create(parser)) + ))); - pm_arguments_node_arguments_append(arguments, (pm_node_t *) keywords); - pm_node_flag_set((pm_node_t *) arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS); + pm_arguments_node_arguments_append(arguments, UP(keywords)); + pm_node_flag_set(UP(arguments), PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS); } pm_statements_node_t *wrapped_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(parser, wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( + pm_statements_node_body_append(parser, wrapped_statements, UP(pm_while_node_synthesized_create( parser, - (pm_node_t *) pm_call_node_fcall_synthesized_create(parser, arguments, pm_parser_constant_id_constant(parser, "gets", 4)), + UP(pm_call_node_fcall_synthesized_create(parser, arguments, pm_parser_constant_id_constant(parser, "gets", 4))), statements - ), true); + )), true); statements = wrapped_statements; } @@ -21698,7 +21704,7 @@ parse_program(pm_parser_t *parser) { pm_statements_node_location_set(statements, parser->start, parser->start); } - return (pm_node_t *) pm_program_node_create(parser, &locals, statements); + return UP(pm_program_node_create(parser, &locals, statements)); } /******************************************************************************/ diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index e82cb78fe3106f..d69a0d74569a2f 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -105,22 +105,6 @@ typedef uint16_t pm_node_flags_t; static const pm_node_flags_t PM_NODE_FLAG_NEWLINE = 0x1; static const pm_node_flags_t PM_NODE_FLAG_STATIC_LITERAL = 0x2; -/** - * Cast the type to an enum to allow the compiler to provide exhaustiveness - * checking. - */ -#define PM_NODE_TYPE(node) ((enum pm_node_type) (node)->type) - -/** - * Return true if the type of the given node matches the given type. - */ -#define PM_NODE_TYPE_P(node, type) (PM_NODE_TYPE(node) == (type)) - -/** - * Return true if the given flag is set on the given node. - */ -#define PM_NODE_FLAG_P(node, flag) ((((pm_node_t *)(node))->flags & (flag)) != 0) - /** * This is the base structure that represents a node in the syntax tree. It is * embedded into every node type. @@ -150,6 +134,27 @@ typedef struct pm_node { */ pm_location_t location; } pm_node_t; + +/** + * Cast the given node to the base pm_node_t type. + */ +#define PM_NODE_UPCAST(node_) ((pm_node_t *) (node_)) + +/** + * Cast the type to an enum to allow the compiler to provide exhaustiveness + * checking. + */ +#define PM_NODE_TYPE(node_) ((enum pm_node_type) (node_)->type) + +/** + * Return true if the type of the given node matches the given type. + */ +#define PM_NODE_TYPE_P(node_, type_) (PM_NODE_TYPE(node_) == (type_)) + +/** + * Return true if the given flag is set on the given node. + */ +#define PM_NODE_FLAG_P(node_, flag_) ((PM_NODE_UPCAST(node_)->flags & (flag_)) != 0) <%- nodes.each do |node| -%> /** From 68617fb62b943b9d2cb67f177a55b63b7508f504 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 15:05:04 -0500 Subject: [PATCH 1501/2435] [ruby/prism] Specialize PM_NODE_INIT to reduce calls to location https://github.com/ruby/prism/commit/3e0b5c9eb7 --- prism/prism.c | 269 ++++++++++++++++++++++++++------------------------ 1 file changed, 142 insertions(+), 127 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 044e61a1b1d5c5..2c930d8deb4122 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -24,6 +24,12 @@ pm_version(void) { #define UP PM_NODE_UPCAST +#define PM_TOKEN_START(token_) ((token_)->start) +#define PM_TOKEN_END(token_) ((token_)->end) + +#define PM_NODE_START(node_) (UP(node_)->location.start) +#define PM_NODE_END(node_) (UP(node_)->location.end) + /******************************************************************************/ /* Lex mode manipulations */ /******************************************************************************/ @@ -1942,6 +1948,15 @@ pm_node_alloc(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, size_t size) { .location = { .start = (start_), .end = (end_) } \ } +#define PM_NODE_INIT_UNSET(parser_, type_, flags_) PM_NODE_INIT(parser_, type_, flags_, (parser_)->start, (parser_)->start) +#define PM_NODE_INIT_TOKEN(parser_, type_, flags_, token_) PM_NODE_INIT(parser_, type_, flags_, PM_TOKEN_START(token_), PM_TOKEN_END(token_)) +#define PM_NODE_INIT_NODE(parser_, type_, flags_, node_) PM_NODE_INIT(parser_, type_, flags_, PM_NODE_START(node_), PM_NODE_END(node_)) + +#define PM_NODE_INIT_TOKENS(parser_, type_, flags_, left_, right_) PM_NODE_INIT(parser_, type_, flags_, PM_TOKEN_START(left_), PM_TOKEN_END(right_)) +#define PM_NODE_INIT_NODES(parser_, type_, flags_, left_, right_) PM_NODE_INIT(parser_, type_, flags_, PM_NODE_START(left_), PM_NODE_END(right_)) +#define PM_NODE_INIT_TOKEN_NODE(parser_, type_, flags_, token_, node_) PM_NODE_INIT(parser_, type_, flags_, PM_TOKEN_START(token_), PM_NODE_END(node_)) +#define PM_NODE_INIT_NODE_TOKEN(parser_, type_, flags_, node_, token_) PM_NODE_INIT(parser_, type_, flags_, PM_NODE_START(node_), PM_TOKEN_END(token_)) + /** * Allocate a new MissingNode node. */ @@ -1965,7 +1980,7 @@ pm_alias_global_variable_node_create(pm_parser_t *parser, const pm_token_t *keyw pm_alias_global_variable_node_t *node = PM_NODE_ALLOC(parser, pm_alias_global_variable_node_t); *node = (pm_alias_global_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_ALIAS_GLOBAL_VARIABLE_NODE, 0, keyword->start, old_name->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_ALIAS_GLOBAL_VARIABLE_NODE, 0, keyword, old_name), .new_name = new_name, .old_name = old_name, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) @@ -1983,7 +1998,7 @@ pm_alias_method_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_n pm_alias_method_node_t *node = PM_NODE_ALLOC(parser, pm_alias_method_node_t); *node = (pm_alias_method_node_t) { - .base = PM_NODE_INIT(parser, PM_ALIAS_METHOD_NODE, 0, keyword->start, old_name->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_ALIAS_METHOD_NODE, 0, keyword, old_name), .new_name = new_name, .old_name = old_name, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) @@ -2000,7 +2015,7 @@ pm_alternation_pattern_node_create(pm_parser_t *parser, pm_node_t *left, pm_node pm_alternation_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_alternation_pattern_node_t); *node = (pm_alternation_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ALTERNATION_PATTERN_NODE, 0, left->location.start, right->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_ALTERNATION_PATTERN_NODE, 0, left, right), .left = left, .right = right, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -2019,7 +2034,7 @@ pm_and_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *opera pm_and_node_t *node = PM_NODE_ALLOC(parser, pm_and_node_t); *node = (pm_and_node_t) { - .base = PM_NODE_INIT(parser, PM_AND_NODE, 0, left->location.start, right->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_AND_NODE, 0, left, right), .left = left, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .right = right @@ -2036,7 +2051,7 @@ pm_arguments_node_create(pm_parser_t *parser) { pm_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_arguments_node_t); *node = (pm_arguments_node_t) { - .base = PM_NODE_INIT(parser, PM_ARGUMENTS_NODE, 0, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_ARGUMENTS_NODE, 0), .arguments = { 0 } }; @@ -2080,7 +2095,7 @@ pm_array_node_create(pm_parser_t *parser, const pm_token_t *opening) { pm_array_node_t *node = PM_NODE_ALLOC(parser, pm_array_node_t); *node = (pm_array_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, opening->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_ARRAY_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .elements = { 0 } @@ -2131,7 +2146,7 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, nodes->nodes[0]->location.start, nodes->nodes[nodes->size - 1]->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_ARRAY_PATTERN_NODE, 0, nodes->nodes[0], nodes->nodes[nodes->size - 1]), .constant = NULL, .rest = NULL, .requireds = { 0 }, @@ -2167,7 +2182,7 @@ pm_array_pattern_node_rest_create(pm_parser_t *parser, pm_node_t *rest) { pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, rest->location.start, rest->location.end), + .base = PM_NODE_INIT_NODE(parser, PM_ARRAY_PATTERN_NODE, 0, rest), .constant = NULL, .rest = rest, .requireds = { 0 }, @@ -2188,7 +2203,7 @@ pm_array_pattern_node_constant_create(pm_parser_t *parser, pm_node_t *constant, pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, constant->location.start, closing->end), + .base = PM_NODE_INIT_NODE_TOKEN(parser, PM_ARRAY_PATTERN_NODE, 0, constant, closing), .constant = constant, .rest = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -2209,7 +2224,7 @@ pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *openin pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_ARRAY_PATTERN_NODE, 0, opening, closing), .constant = NULL, .rest = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -2295,7 +2310,7 @@ pm_back_reference_read_node_create(pm_parser_t *parser, const pm_token_t *name) pm_back_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_back_reference_read_node_t); *node = (pm_back_reference_read_node_t) { - .base = PM_NODE_INIT(parser, PM_BACK_REFERENCE_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_BACK_REFERENCE_READ_NODE, 0, name), .name = pm_parser_constant_id_token(parser, name) }; @@ -2385,7 +2400,7 @@ pm_block_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p pm_block_node_t *node = PM_NODE_ALLOC(parser, pm_block_node_t); *node = (pm_block_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_BLOCK_NODE, 0, opening, closing), .locals = *locals, .parameters = parameters, .body = body, @@ -2469,7 +2484,7 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) pm_block_local_variable_node_t *node = PM_NODE_ALLOC(parser, pm_block_local_variable_node_t); *node = (pm_block_local_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_LOCAL_VARIABLE_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_BLOCK_LOCAL_VARIABLE_NODE, 0, name), .name = pm_parser_constant_id_token(parser, name) }; @@ -2523,7 +2538,7 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { pm_call_node_t *node = PM_NODE_ALLOC(parser, pm_call_node_t); *node = (pm_call_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_NODE, flags, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_CALL_NODE, flags), .receiver = NULL, .call_operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .message_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -2830,7 +2845,7 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_and_write_node_t); *node = (pm_call_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_AND_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_AND_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -2885,7 +2900,7 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_AND_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_AND_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -2913,7 +2928,7 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, pm_call_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_operator_write_node_t); *node = (pm_call_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_OPERATOR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_OPERATOR_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -2945,7 +2960,7 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_OPERATOR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OPERATOR_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -2975,7 +2990,7 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_or_write_node_t); *node = (pm_call_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_OR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_OR_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -3007,7 +3022,7 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_OR_WRITE_NODE, target->base.flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OR_WRITE_NODE, target->base.flags, target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -3035,7 +3050,7 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { pm_call_target_node_t *node = PM_NODE_ALLOC(parser, pm_call_target_node_t); *node = (pm_call_target_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_TARGET_NODE, target->base.flags, target->base.location.start, target->base.location.end), + .base = PM_NODE_INIT_NODE(parser, PM_CALL_TARGET_NODE, target->base.flags, target), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .name = target->name, @@ -3063,7 +3078,7 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_target_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_TARGET_NODE, flags | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, target->base.location.start, target->base.location.end), + .base = PM_NODE_INIT_NODE(parser, PM_INDEX_TARGET_NODE, flags | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, target), .receiver = target->receiver, .opening_loc = target->opening_loc, .arguments = target->arguments, @@ -3087,7 +3102,7 @@ pm_capture_pattern_node_create(pm_parser_t *parser, pm_node_t *value, pm_local_v pm_capture_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_capture_pattern_node_t); *node = (pm_capture_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_CAPTURE_PATTERN_NODE, 0, value->location.start, target->base.location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CAPTURE_PATTERN_NODE, 0, value, target), .value = value, .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -3104,7 +3119,7 @@ pm_case_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, pm_node pm_case_node_t *node = PM_NODE_ALLOC(parser, pm_case_node_t); *node = (pm_case_node_t) { - .base = PM_NODE_INIT(parser, PM_CASE_NODE, 0, case_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_CASE_NODE, 0, case_keyword, end_keyword), .predicate = predicate, .else_clause = NULL, .case_keyword_loc = PM_LOCATION_TOKEN_VALUE(case_keyword), @@ -3152,7 +3167,7 @@ pm_case_match_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, p pm_case_match_node_t *node = PM_NODE_ALLOC(parser, pm_case_match_node_t); *node = (pm_case_match_node_t) { - .base = PM_NODE_INIT(parser, PM_CASE_MATCH_NODE, 0, case_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_CASE_MATCH_NODE, 0, case_keyword, end_keyword), .predicate = predicate, .else_clause = NULL, .case_keyword_loc = PM_LOCATION_TOKEN_VALUE(case_keyword), @@ -3200,7 +3215,7 @@ pm_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p pm_class_node_t *node = PM_NODE_ALLOC(parser, pm_class_node_t); *node = (pm_class_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_NODE, 0, class_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_CLASS_NODE, 0, class_keyword, end_keyword), .locals = *locals, .class_keyword_loc = PM_LOCATION_TOKEN_VALUE(class_keyword), .constant_path = constant_path, @@ -3223,7 +3238,7 @@ pm_class_variable_and_write_node_create(pm_parser_t *parser, pm_class_variable_r pm_class_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_and_write_node_t); *node = (pm_class_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CLASS_VARIABLE_AND_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3241,7 +3256,7 @@ pm_class_variable_operator_write_node_create(pm_parser_t *parser, pm_class_varia pm_class_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_operator_write_node_t); *node = (pm_class_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3261,7 +3276,7 @@ pm_class_variable_or_write_node_create(pm_parser_t *parser, pm_class_variable_re pm_class_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_or_write_node_t); *node = (pm_class_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CLASS_VARIABLE_OR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3280,7 +3295,7 @@ pm_class_variable_read_node_create(pm_parser_t *parser, const pm_token_t *token) pm_class_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_read_node_t); *node = (pm_class_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_READ_NODE, 0, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_CLASS_VARIABLE_READ_NODE, 0, token), .name = pm_parser_constant_id_token(parser, token) }; @@ -3310,7 +3325,7 @@ pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_ pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_class_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_WRITE_NODE, flags, read_node->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CLASS_VARIABLE_WRITE_NODE, flags, read_node, value), .name = read_node->name, .name_loc = PM_LOCATION_NODE_VALUE(UP(read_node)), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3329,7 +3344,7 @@ pm_constant_path_and_write_node_create(pm_parser_t *parser, pm_constant_path_nod pm_constant_path_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_and_write_node_t); *node = (pm_constant_path_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_PATH_AND_WRITE_NODE, 0, target, value), .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3346,7 +3361,7 @@ pm_constant_path_operator_write_node_create(pm_parser_t *parser, pm_constant_pat pm_constant_path_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_operator_write_node_t); *node = (pm_constant_path_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, 0, target, value), .target = target, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -3365,7 +3380,7 @@ pm_constant_path_or_write_node_create(pm_parser_t *parser, pm_constant_path_node pm_constant_path_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_or_write_node_t); *node = (pm_constant_path_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_PATH_OR_WRITE_NODE, 0, target, value), .target = target, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3407,7 +3422,7 @@ pm_constant_path_write_node_create(pm_parser_t *parser, pm_constant_path_node_t pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_constant_path_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_WRITE_NODE, flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_PATH_WRITE_NODE, flags, target, value), .target = target, .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), .value = value @@ -3425,7 +3440,7 @@ pm_constant_and_write_node_create(pm_parser_t *parser, pm_constant_read_node_t * pm_constant_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_and_write_node_t); *node = (pm_constant_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_AND_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3443,7 +3458,7 @@ pm_constant_operator_write_node_create(pm_parser_t *parser, pm_constant_read_nod pm_constant_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_operator_write_node_t); *node = (pm_constant_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_OPERATOR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3463,7 +3478,7 @@ pm_constant_or_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *t pm_constant_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_or_write_node_t); *node = (pm_constant_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_OR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -3482,7 +3497,7 @@ pm_constant_read_node_create(pm_parser_t *parser, const pm_token_t *name) { pm_constant_read_node_t *node = PM_NODE_ALLOC(parser, pm_constant_read_node_t); *node = (pm_constant_read_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_CONSTANT_READ_NODE, 0, name), .name = pm_parser_constant_id_token(parser, name) }; @@ -3498,7 +3513,7 @@ pm_constant_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *targ pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_constant_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_WRITE_NODE, flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_CONSTANT_WRITE_NODE, flags, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -3653,7 +3668,7 @@ pm_embedded_statements_node_create(pm_parser_t *parser, const pm_token_t *openin pm_embedded_statements_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_statements_node_t); *node = (pm_embedded_statements_node_t) { - .base = PM_NODE_INIT(parser, PM_EMBEDDED_STATEMENTS_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_EMBEDDED_STATEMENTS_NODE, 0, opening, closing), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .statements = statements, .closing_loc = PM_LOCATION_TOKEN_VALUE(closing) @@ -3670,7 +3685,7 @@ pm_embedded_variable_node_create(pm_parser_t *parser, const pm_token_t *operator pm_embedded_variable_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_variable_node_t); *node = (pm_embedded_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_EMBEDDED_VARIABLE_NODE, 0, operator->start, variable->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_EMBEDDED_VARIABLE_NODE, 0, operator, variable), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .variable = variable }; @@ -3686,7 +3701,7 @@ pm_ensure_node_create(pm_parser_t *parser, const pm_token_t *ensure_keyword, pm_ pm_ensure_node_t *node = PM_NODE_ALLOC(parser, pm_ensure_node_t); *node = (pm_ensure_node_t) { - .base = PM_NODE_INIT(parser, PM_ENSURE_NODE, 0, ensure_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_ENSURE_NODE, 0, ensure_keyword, end_keyword), .ensure_keyword_loc = PM_LOCATION_TOKEN_VALUE(ensure_keyword), .statements = statements, .end_keyword_loc = PM_LOCATION_TOKEN_VALUE(end_keyword) @@ -3704,7 +3719,7 @@ pm_false_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_false_node_t *node = PM_NODE_ALLOC(parser, pm_false_node_t); *node = (pm_false_node_t) { - .base = PM_NODE_INIT(parser, PM_FALSE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_FALSE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token) }; return node; @@ -3739,7 +3754,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { pm_node_t *right_splat_node = right; #endif *node = (pm_find_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_FIND_PATTERN_NODE, 0, left->location.start, right->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_FIND_PATTERN_NODE, 0, left, right), .constant = NULL, .left = left_splat_node, .right = right_splat_node, @@ -3840,7 +3855,7 @@ pm_float_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_float_node_t *node = PM_NODE_ALLOC(parser, pm_float_node_t); *node = (pm_float_node_t) { - .base = PM_NODE_INIT(parser, PM_FLOAT_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_FLOAT_NODE, PM_NODE_FLAG_STATIC_LITERAL, token), .value = pm_double_parse(parser, token) }; @@ -3856,7 +3871,7 @@ pm_float_node_imaginary_create(pm_parser_t *parser, const pm_token_t *token) { pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token), .numeric = UP(pm_float_node_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT, .start = token->start, @@ -3876,7 +3891,7 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); *node = (pm_rational_node_t) { - .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_RATIONAL_NODE, PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, token), .numerator = { 0 }, .denominator = { 0 } }; @@ -3925,7 +3940,7 @@ pm_float_node_rational_imaginary_create(pm_parser_t *parser, const pm_token_t *t pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token), .numeric = UP(pm_float_node_rational_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT_RATIONAL, .start = token->start, @@ -3953,7 +3968,7 @@ pm_for_node_create( pm_for_node_t *node = PM_NODE_ALLOC(parser, pm_for_node_t); *node = (pm_for_node_t) { - .base = PM_NODE_INIT(parser, PM_FOR_NODE, 0, for_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_FOR_NODE, 0, for_keyword, end_keyword), .index = index, .collection = collection, .statements = statements, @@ -3975,7 +3990,7 @@ pm_forwarding_arguments_node_create(pm_parser_t *parser, const pm_token_t *token pm_forwarding_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_arguments_node_t); *node = (pm_forwarding_arguments_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_ARGUMENTS_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_FORWARDING_ARGUMENTS_NODE, 0, token) }; return node; @@ -3990,7 +4005,7 @@ pm_forwarding_parameter_node_create(pm_parser_t *parser, const pm_token_t *token pm_forwarding_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_parameter_node_t); *node = (pm_forwarding_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_PARAMETER_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_FORWARDING_PARAMETER_NODE, 0, token) }; return node; @@ -4028,7 +4043,7 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening pm_hash_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_hash_pattern_node_t); *node = (pm_hash_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_HASH_PATTERN_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_HASH_PATTERN_NODE, 0, opening, closing), .constant = NULL, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), @@ -4109,7 +4124,7 @@ pm_global_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_global_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_and_write_node_t); *node = (pm_global_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_AND_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_GLOBAL_VARIABLE_AND_WRITE_NODE, 0, target, value), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4127,7 +4142,7 @@ pm_global_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *ta pm_global_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_operator_write_node_t); *node = (pm_global_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target, value), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4147,7 +4162,7 @@ pm_global_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_global_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_or_write_node_t); *node = (pm_global_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OR_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_GLOBAL_VARIABLE_OR_WRITE_NODE, 0, target, value), .name = pm_global_variable_write_name(parser, target), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4165,7 +4180,7 @@ pm_global_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); *node = (pm_global_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, name), .name = pm_parser_constant_id_token(parser, name) }; @@ -4180,7 +4195,7 @@ pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); *node = (pm_global_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0), .name = name }; @@ -4196,7 +4211,7 @@ pm_global_variable_write_node_create(pm_parser_t *parser, pm_node_t *target, con pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_global_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, flags, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, flags, target, value), .name = pm_global_variable_write_name(parser, target), .name_loc = PM_LOCATION_NODE_VALUE(target), .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -4214,7 +4229,7 @@ pm_global_variable_write_node_synthesized_create(pm_parser_t *parser, pm_constan pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); *node = (pm_global_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0), .name = name, .name_loc = PM_LOCATION_NULL_VALUE(parser), .operator_loc = PM_LOCATION_NULL_VALUE(parser), @@ -4233,7 +4248,7 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) { pm_hash_node_t *node = PM_NODE_ALLOC(parser, pm_hash_node_t); *node = (pm_hash_node_t) { - .base = PM_NODE_INIT(parser, PM_HASH_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, opening->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_HASH_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_NULL_VALUE(parser), .elements = { 0 } @@ -4319,7 +4334,7 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t pm_statements_node_body_append(parser, statements, statement, true); *node = (pm_if_node_t) { - .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, statement->location.start, predicate->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, statement, predicate), .if_keyword_loc = PM_LOCATION_TOKEN_VALUE(if_keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -4351,7 +4366,7 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to pm_if_node_t *node = PM_NODE_ALLOC(parser, pm_if_node_t); *node = (pm_if_node_t) { - .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, predicate->location.start, false_expression->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, predicate, false_expression), .if_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .predicate = predicate, .then_keyword_loc = PM_LOCATION_TOKEN_VALUE(qmark), @@ -4384,7 +4399,7 @@ pm_implicit_node_create(pm_parser_t *parser, pm_node_t *value) { pm_implicit_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_node_t); *node = (pm_implicit_node_t) { - .base = PM_NODE_INIT(parser, PM_IMPLICIT_NODE, 0, value->location.start, value->location.end), + .base = PM_NODE_INIT_NODE(parser, PM_IMPLICIT_NODE, 0, value), .value = value }; @@ -4401,7 +4416,7 @@ pm_implicit_rest_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_implicit_rest_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_rest_node_t); *node = (pm_implicit_rest_node_t) { - .base = PM_NODE_INIT(parser, PM_IMPLICIT_REST_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_IMPLICIT_REST_NODE, 0, token) }; return node; @@ -4416,7 +4431,7 @@ pm_integer_node_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token pm_integer_node_t *node = PM_NODE_ALLOC(parser, pm_integer_node_t); *node = (pm_integer_node_t) { - .base = PM_NODE_INIT(parser, PM_INTEGER_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_INTEGER_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token), .value = { 0 } }; @@ -4443,7 +4458,7 @@ pm_integer_node_imaginary_create(pm_parser_t *parser, pm_node_flags_t base, cons pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token), .numeric = UP(pm_integer_node_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER, .start = token->start, @@ -4464,7 +4479,7 @@ pm_integer_node_rational_create(pm_parser_t *parser, pm_node_flags_t base, const pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); *node = (pm_rational_node_t) { - .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_RATIONAL_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, token), .numerator = { 0 }, .denominator = { .value = 1, 0 } }; @@ -4493,7 +4508,7 @@ pm_integer_node_rational_imaginary_create(pm_parser_t *parser, pm_node_flags_t b pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, token), .numeric = UP(pm_integer_node_rational_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER_RATIONAL, .start = token->start, @@ -4540,7 +4555,7 @@ pm_instance_variable_and_write_node_create(pm_parser_t *parser, pm_instance_vari pm_instance_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_and_write_node_t); *node = (pm_instance_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_AND_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INSTANCE_VARIABLE_AND_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4558,7 +4573,7 @@ pm_instance_variable_operator_write_node_create(pm_parser_t *parser, pm_instance pm_instance_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_operator_write_node_t); *node = (pm_instance_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4578,7 +4593,7 @@ pm_instance_variable_or_write_node_create(pm_parser_t *parser, pm_instance_varia pm_instance_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_or_write_node_t); *node = (pm_instance_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OR_WRITE_NODE, 0, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INSTANCE_VARIABLE_OR_WRITE_NODE, 0, target, value), .name = target->name, .name_loc = target->base.location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -4597,7 +4612,7 @@ pm_instance_variable_read_node_create(pm_parser_t *parser, const pm_token_t *tok pm_instance_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_read_node_t); *node = (pm_instance_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_READ_NODE, 0, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_INSTANCE_VARIABLE_READ_NODE, 0, token), .name = pm_parser_constant_id_token(parser, token) }; @@ -4614,7 +4629,7 @@ pm_instance_variable_write_node_create(pm_parser_t *parser, pm_instance_variable pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_instance_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_WRITE_NODE, flags, read_node->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_INSTANCE_VARIABLE_WRITE_NODE, flags, read_node, value), .name = read_node->name, .name_loc = PM_LOCATION_NODE_BASE_VALUE(read_node), .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), @@ -4835,7 +4850,7 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin } *node = (pm_interpolated_string_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_STRING_NODE, flags, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_INTERPOLATED_STRING_NODE, flags, opening, closing), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -4884,7 +4899,7 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin pm_interpolated_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_symbol_node_t); *node = (pm_interpolated_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_INTERPOLATED_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening, closing), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -4908,7 +4923,7 @@ pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *openi pm_interpolated_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_x_string_node_t); *node = (pm_interpolated_x_string_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_X_STRING_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_INTERPOLATED_X_STRING_NODE, 0, opening, closing), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), .parts = { 0 } @@ -4937,7 +4952,7 @@ pm_it_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *nam pm_it_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_it_local_variable_read_node_t); *node = (pm_it_local_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_IT_LOCAL_VARIABLE_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_IT_LOCAL_VARIABLE_READ_NODE, 0, name), }; return node; @@ -4951,7 +4966,7 @@ pm_it_parameters_node_create(pm_parser_t *parser, const pm_token_t *opening, con pm_it_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_it_parameters_node_t); *node = (pm_it_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_IT_PARAMETERS_NODE, 0, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_IT_PARAMETERS_NODE, 0, opening, closing), }; return node; @@ -4998,7 +5013,7 @@ pm_required_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t pm_required_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_keyword_parameter_node_t); *node = (pm_required_keyword_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REQUIRED_KEYWORD_PARAMETER_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_REQUIRED_KEYWORD_PARAMETER_NODE, 0, name), .name = pm_parser_constant_id_location(parser, name->start, name->end - 1), .name_loc = PM_LOCATION_TOKEN_VALUE(name), }; @@ -5014,7 +5029,7 @@ pm_optional_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t pm_optional_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_keyword_parameter_node_t); *node = (pm_optional_keyword_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_OPTIONAL_KEYWORD_PARAMETER_NODE, 0, name->start, value->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_OPTIONAL_KEYWORD_PARAMETER_NODE, 0, name, value), .name = pm_parser_constant_id_location(parser, name->start, name->end - 1), .name_loc = PM_LOCATION_TOKEN_VALUE(name), .value = value @@ -5056,7 +5071,7 @@ pm_lambda_node_create( pm_lambda_node_t *node = PM_NODE_ALLOC(parser, pm_lambda_node_t); *node = (pm_lambda_node_t) { - .base = PM_NODE_INIT(parser, PM_LAMBDA_NODE, 0, operator->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_LAMBDA_NODE, 0, operator, closing), .locals = *locals, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -5078,7 +5093,7 @@ pm_local_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, pm_local_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_and_write_node_t); *node = (pm_local_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_AND_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_LOCAL_VARIABLE_AND_WRITE_NODE, 0, target, value), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5097,7 +5112,7 @@ pm_local_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *tar pm_local_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_operator_write_node_t); *node = (pm_local_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, 0, target, value), .name_loc = target->location, .binary_operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5119,7 +5134,7 @@ pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, c pm_local_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_or_write_node_t); *node = (pm_local_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OR_WRITE_NODE, 0, target->location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_LOCAL_VARIABLE_OR_WRITE_NODE, 0, target, value), .name_loc = target->location, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value, @@ -5140,7 +5155,7 @@ pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_tok pm_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_read_node_t); *node = (pm_local_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_LOCAL_VARIABLE_READ_NODE, 0, name), .name = name_id, .depth = depth }; @@ -5243,7 +5258,7 @@ pm_match_predicate_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t pm_match_predicate_node_t *node = PM_NODE_ALLOC(parser, pm_match_predicate_node_t); *node = (pm_match_predicate_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_PREDICATE_NODE, 0, value->location.start, pattern->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_MATCH_PREDICATE_NODE, 0, value, pattern), .value = value, .pattern = pattern, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5262,7 +5277,7 @@ pm_match_required_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t * pm_match_required_node_t *node = PM_NODE_ALLOC(parser, pm_match_required_node_t); *node = (pm_match_required_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_REQUIRED_NODE, 0, value->location.start, pattern->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_MATCH_REQUIRED_NODE, 0, value, pattern), .value = value, .pattern = pattern, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5279,7 +5294,7 @@ pm_match_write_node_create(pm_parser_t *parser, pm_call_node_t *call) { pm_match_write_node_t *node = PM_NODE_ALLOC(parser, pm_match_write_node_t); *node = (pm_match_write_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_WRITE_NODE, 0, call->base.location.start, call->base.location.end), + .base = PM_NODE_INIT_NODE(parser, PM_MATCH_WRITE_NODE, 0, call), .call = call, .targets = { 0 } }; @@ -5295,7 +5310,7 @@ pm_module_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_module_node_t *node = PM_NODE_ALLOC(parser, pm_module_node_t); *node = (pm_module_node_t) { - .base = PM_NODE_INIT(parser, PM_MODULE_NODE, 0, module_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_MODULE_NODE, 0, module_keyword, end_keyword), .locals = (locals == NULL ? ((pm_constant_id_list_t) { .ids = NULL, .size = 0, .capacity = 0 }) : *locals), .module_keyword_loc = PM_LOCATION_TOKEN_VALUE(module_keyword), .constant_path = constant_path, @@ -5387,7 +5402,7 @@ pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_multi_write_node_t) { - .base = PM_NODE_INIT(parser, PM_MULTI_WRITE_NODE, flags, target->base.location.start, value->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_MULTI_WRITE_NODE, flags, target, value), .lefts = target->lefts, .rest = target->rest, .rights = target->rights, @@ -5430,7 +5445,7 @@ pm_nil_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_nil_node_t *node = PM_NODE_ALLOC(parser, pm_nil_node_t); *node = (pm_nil_node_t) { - .base = PM_NODE_INIT(parser, PM_NIL_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_NIL_NODE, PM_NODE_FLAG_STATIC_LITERAL, token) }; return node; @@ -5446,7 +5461,7 @@ pm_no_keywords_parameter_node_create(pm_parser_t *parser, const pm_token_t *oper pm_no_keywords_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_no_keywords_parameter_node_t); *node = (pm_no_keywords_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_NO_KEYWORDS_PARAMETER_NODE, 0, operator->start, keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_NO_KEYWORDS_PARAMETER_NODE, 0, operator, keyword), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) }; @@ -5527,7 +5542,7 @@ pm_numbered_reference_read_node_create(pm_parser_t *parser, const pm_token_t *na pm_numbered_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_reference_read_node_t); *node = (pm_numbered_reference_read_node_t) { - .base = PM_NODE_INIT(parser, PM_NUMBERED_REFERENCE_READ_NODE, 0, name->start, name->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_NUMBERED_REFERENCE_READ_NODE, 0, name), .number = pm_numbered_reference_read_node_number(parser, name) }; @@ -5542,7 +5557,7 @@ pm_optional_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, c pm_optional_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_parameter_node_t); *node = (pm_optional_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_OPTIONAL_PARAMETER_NODE, 0, name->start, value->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_OPTIONAL_PARAMETER_NODE, 0, name, value), .name = pm_parser_constant_id_token(parser, name), .name_loc = PM_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -5562,7 +5577,7 @@ pm_or_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operat pm_or_node_t *node = PM_NODE_ALLOC(parser, pm_or_node_t); *node = (pm_or_node_t) { - .base = PM_NODE_INIT(parser, PM_OR_NODE, 0, left->location.start, right->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_OR_NODE, 0, left, right), .left = left, .right = right, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5702,7 +5717,7 @@ pm_parentheses_node_create(pm_parser_t *parser, const pm_token_t *opening, pm_no pm_parentheses_node_t *node = PM_NODE_ALLOC(parser, pm_parentheses_node_t); *node = (pm_parentheses_node_t) { - .base = PM_NODE_INIT(parser, PM_PARENTHESES_NODE, flags, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_PARENTHESES_NODE, flags, opening, closing), .body = body, .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing) @@ -5719,7 +5734,7 @@ pm_pinned_expression_node_create(pm_parser_t *parser, pm_node_t *expression, con pm_pinned_expression_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_expression_node_t); *node = (pm_pinned_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_PINNED_EXPRESSION_NODE, 0, operator->start, rparen->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_PINNED_EXPRESSION_NODE, 0, operator, rparen), .expression = expression, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .lparen_loc = PM_LOCATION_TOKEN_VALUE(lparen), @@ -5737,7 +5752,7 @@ pm_pinned_variable_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_pinned_variable_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_variable_node_t); *node = (pm_pinned_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_PINNED_VARIABLE_NODE, 0, operator->start, variable->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_PINNED_VARIABLE_NODE, 0, operator, variable), .variable = variable, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -5753,7 +5768,7 @@ pm_post_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, co pm_post_execution_node_t *node = PM_NODE_ALLOC(parser, pm_post_execution_node_t); *node = (pm_post_execution_node_t) { - .base = PM_NODE_INIT(parser, PM_POST_EXECUTION_NODE, 0, keyword->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_POST_EXECUTION_NODE, 0, keyword, closing), .statements = statements, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -5771,7 +5786,7 @@ pm_pre_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, con pm_pre_execution_node_t *node = PM_NODE_ALLOC(parser, pm_pre_execution_node_t); *node = (pm_pre_execution_node_t) { - .base = PM_NODE_INIT(parser, PM_PRE_EXECUTION_NODE, 0, keyword->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_PRE_EXECUTION_NODE, 0, keyword, closing), .statements = statements, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), @@ -5826,7 +5841,7 @@ pm_redo_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_redo_node_t *node = PM_NODE_ALLOC(parser, pm_redo_node_t); *node = (pm_redo_node_t) { - .base = PM_NODE_INIT(parser, PM_REDO_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_REDO_NODE, 0, token) }; return node; @@ -5868,7 +5883,7 @@ pm_required_parameter_node_create(pm_parser_t *parser, const pm_token_t *token) pm_required_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_parameter_node_t); *node = (pm_required_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REQUIRED_PARAMETER_NODE, 0, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_REQUIRED_PARAMETER_NODE, 0, token), .name = pm_parser_constant_id_token(parser, token) }; @@ -5883,7 +5898,7 @@ pm_rescue_modifier_node_create(pm_parser_t *parser, pm_node_t *expression, const pm_rescue_modifier_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_modifier_node_t); *node = (pm_rescue_modifier_node_t) { - .base = PM_NODE_INIT(parser, PM_RESCUE_MODIFIER_NODE, 0, expression->location.start, rescue_expression->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_RESCUE_MODIFIER_NODE, 0, expression, rescue_expression), .expression = expression, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .rescue_expression = rescue_expression @@ -5900,7 +5915,7 @@ pm_rescue_node_create(pm_parser_t *parser, const pm_token_t *keyword) { pm_rescue_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_node_t); *node = (pm_rescue_node_t) { - .base = PM_NODE_INIT(parser, PM_RESCUE_NODE, 0, keyword->start, keyword->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_RESCUE_NODE, 0, keyword), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -5982,7 +5997,7 @@ pm_retry_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_retry_node_t *node = PM_NODE_ALLOC(parser, pm_retry_node_t); *node = (pm_retry_node_t) { - .base = PM_NODE_INIT(parser, PM_RETRY_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_RETRY_NODE, 0, token) }; return node; @@ -6013,7 +6028,7 @@ pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_self_node_t *node = PM_NODE_ALLOC(parser, pm_self_node_t); *node = (pm_self_node_t) { - .base = PM_NODE_INIT(parser, PM_SELF_NODE, 0, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_SELF_NODE, 0, token) }; return node; @@ -6027,7 +6042,7 @@ pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shar pm_shareable_constant_node_t *node = PM_NODE_ALLOC(parser, pm_shareable_constant_node_t); *node = (pm_shareable_constant_node_t) { - .base = PM_NODE_INIT(parser, PM_SHAREABLE_CONSTANT_NODE, (pm_node_flags_t) value, write->location.start, write->location.end), + .base = PM_NODE_INIT_NODE(parser, PM_SHAREABLE_CONSTANT_NODE, (pm_node_flags_t) value, write), .write = write }; @@ -6042,7 +6057,7 @@ pm_singleton_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *local pm_singleton_class_node_t *node = PM_NODE_ALLOC(parser, pm_singleton_class_node_t); *node = (pm_singleton_class_node_t) { - .base = PM_NODE_INIT(parser, PM_SINGLETON_CLASS_NODE, 0, class_keyword->start, end_keyword->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_SINGLETON_CLASS_NODE, 0, class_keyword, end_keyword), .locals = *locals, .class_keyword_loc = PM_LOCATION_TOKEN_VALUE(class_keyword), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), @@ -6063,7 +6078,7 @@ pm_source_encoding_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_source_encoding_node_t *node = PM_NODE_ALLOC(parser, pm_source_encoding_node_t); *node = (pm_source_encoding_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_ENCODING_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_SOURCE_ENCODING_NODE, PM_NODE_FLAG_STATIC_LITERAL, token) }; return node; @@ -6089,7 +6104,7 @@ pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) } *node = (pm_source_file_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_FILE_NODE, flags, file_keyword->start, file_keyword->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_SOURCE_FILE_NODE, flags, file_keyword), .filepath = parser->filepath }; @@ -6105,7 +6120,7 @@ pm_source_line_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_source_line_node_t *node = PM_NODE_ALLOC(parser, pm_source_line_node_t); *node = (pm_source_line_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_LINE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_SOURCE_LINE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token) }; return node; @@ -6135,7 +6150,7 @@ pm_statements_node_create(pm_parser_t *parser) { pm_statements_node_t *node = PM_NODE_ALLOC(parser, pm_statements_node_t); *node = (pm_statements_node_t) { - .base = PM_NODE_INIT(parser, PM_STATEMENTS_NODE, 0, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_STATEMENTS_NODE, 0), .body = { 0 } }; @@ -6581,7 +6596,7 @@ pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) { pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); *node = (pm_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING), .value_loc = PM_LOCATION_NULL_VALUE(parser), .unescaped = { 0 } }; @@ -6619,7 +6634,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const pm_symbol_node_t *new_node = PM_NODE_ALLOC(parser, pm_symbol_node_t); *new_node = (pm_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening, closing), .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), .value_loc = node->content_loc, .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -6655,7 +6670,7 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { } *new_node = (pm_string_node_t) { - .base = PM_NODE_INIT(parser, PM_STRING_NODE, flags, node->base.location.start, node->base.location.end), + .base = PM_NODE_INIT_NODE(parser, PM_STRING_NODE, flags, node), .opening_loc = node->opening_loc, .content_loc = node->value_loc, .closing_loc = node->closing_loc, @@ -6679,7 +6694,7 @@ pm_true_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); *node = (pm_true_node_t) { - .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token->start, token->end) + .base = PM_NODE_INIT_TOKEN(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, token) }; return node; @@ -6693,7 +6708,7 @@ pm_true_node_synthesized_create(pm_parser_t *parser) { pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); *node = (pm_true_node_t) { - .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, parser->start, parser->end) + .base = PM_NODE_INIT_UNSET(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL) }; return node; @@ -6708,7 +6723,7 @@ pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) { pm_undef_node_t *node = PM_NODE_ALLOC(parser, pm_undef_node_t); *node = (pm_undef_node_t) { - .base = PM_NODE_INIT(parser, PM_UNDEF_NODE, 0, token->start, token->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_UNDEF_NODE, 0, token), .keyword_loc = PM_LOCATION_TOKEN_VALUE(token), .names = { 0 } }; @@ -6765,7 +6780,7 @@ pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_statements_node_body_append(parser, statements, statement, true); *node = (pm_unless_node_t) { - .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, statement->location.start, predicate->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, statement, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(unless_keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -6815,7 +6830,7 @@ pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_until_node_t) { - .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, keyword->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_UNTIL_NODE, flags, keyword, closing), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -6836,7 +6851,7 @@ pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm pm_loop_modifier_block_exits(parser, statements); *node = (pm_until_node_t) { - .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, statements->base.location.start, predicate->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_UNTIL_NODE, flags, statements, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -6855,7 +6870,7 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) { pm_when_node_t *node = PM_NODE_ALLOC(parser, pm_when_node_t); *node = (pm_when_node_t) { - .base = PM_NODE_INIT(parser, PM_WHEN_NODE, 0, keyword->start, NULL), + .base = PM_NODE_INIT_TOKEN(parser, PM_WHEN_NODE, 0, keyword), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .statements = NULL, .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -6904,7 +6919,7 @@ pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, keyword->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_WHILE_NODE, flags, keyword, closing), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword), .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing), @@ -6925,7 +6940,7 @@ pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm pm_loop_modifier_block_exits(parser, statements); *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, statements->base.location.start, predicate->location.end), + .base = PM_NODE_INIT_NODES(parser, PM_WHILE_NODE, flags, statements, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -6944,7 +6959,7 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, 0, parser->start, parser->start), + .base = PM_NODE_INIT_UNSET(parser, PM_WHILE_NODE, 0), .keyword_loc = PM_LOCATION_NULL_VALUE(parser), .do_keyword_loc = PM_LOCATION_NULL_VALUE(parser), .closing_loc = PM_LOCATION_NULL_VALUE(parser), @@ -6964,7 +6979,7 @@ pm_xstring_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, pm_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_x_string_node_t); *node = (pm_x_string_node_t) { - .base = PM_NODE_INIT(parser, PM_X_STRING_NODE, PM_STRING_FLAGS_FROZEN, opening->start, closing->end), + .base = PM_NODE_INIT_TOKENS(parser, PM_X_STRING_NODE, PM_STRING_FLAGS_FROZEN, opening, closing), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .content_loc = PM_LOCATION_TOKEN_VALUE(content), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), From 56ee55b162088a3b6d2e3a5fc9326b17fc65eac6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 15:09:59 -0500 Subject: [PATCH 1502/2435] [ruby/prism] Introduce PM_NODE_FLAGS macro https://github.com/ruby/prism/commit/a20afe1674 --- prism/prism.c | 26 ++++++++++++------------- prism/templates/include/prism/ast.h.erb | 7 ++++++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 2c930d8deb4122..65949190f18036 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22,6 +22,7 @@ pm_version(void) { /* Helpful node-related macros */ /******************************************************************************/ +#define FL PM_NODE_FLAGS #define UP PM_NODE_UPCAST #define PM_TOKEN_START(token_) ((token_)->start) @@ -2845,7 +2846,7 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_and_write_node_t); *node = (pm_call_and_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_CALL_AND_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_AND_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -2900,7 +2901,7 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_and_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_INDEX_AND_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_AND_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -2928,7 +2929,7 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, pm_call_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_operator_write_node_t); *node = (pm_call_operator_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_CALL_OPERATOR_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_OPERATOR_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -2960,7 +2961,7 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_operator_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OPERATOR_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OPERATOR_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -2990,7 +2991,7 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_or_write_node_t); *node = (pm_call_or_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_CALL_OR_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_CALL_OR_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .message_loc = target->message_loc, @@ -3022,7 +3023,7 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); *node = (pm_index_or_write_node_t) { - .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OR_WRITE_NODE, target->base.flags, target, value), + .base = PM_NODE_INIT_NODES(parser, PM_INDEX_OR_WRITE_NODE, FL(target), target, value), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .opening_loc = target->opening_loc, @@ -3050,7 +3051,7 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { pm_call_target_node_t *node = PM_NODE_ALLOC(parser, pm_call_target_node_t); *node = (pm_call_target_node_t) { - .base = PM_NODE_INIT_NODE(parser, PM_CALL_TARGET_NODE, target->base.flags, target), + .base = PM_NODE_INIT_NODE(parser, PM_CALL_TARGET_NODE, FL(target), target), .receiver = target->receiver, .call_operator_loc = target->call_operator_loc, .name = target->name, @@ -3072,13 +3073,12 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { static pm_index_target_node_t * pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { pm_index_target_node_t *node = PM_NODE_ALLOC(parser, pm_index_target_node_t); - pm_node_flags_t flags = target->base.flags; pm_index_arguments_check(parser, target->arguments, target->block); - assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); + *node = (pm_index_target_node_t) { - .base = PM_NODE_INIT_NODE(parser, PM_INDEX_TARGET_NODE, flags | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, target), + .base = PM_NODE_INIT_NODE(parser, PM_INDEX_TARGET_NODE, FL(target) | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, target), .receiver = target->receiver, .opening_loc = target->opening_loc, .arguments = target->arguments, @@ -4745,10 +4745,10 @@ pm_interpolated_regular_expression_node_closing_set(pm_parser_t *parser, pm_inte static inline void pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_t *part) { #define CLEAR_FLAGS(node) \ - node->base.flags = (pm_node_flags_t) (node->base.flags & ~(PM_NODE_FLAG_STATIC_LITERAL | PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE)) + node->base.flags = (pm_node_flags_t) (FL(node) & ~(PM_NODE_FLAG_STATIC_LITERAL | PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE)) #define MUTABLE_FLAGS(node) \ - node->base.flags = (pm_node_flags_t) ((node->base.flags | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE) & ~PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN); + node->base.flags = (pm_node_flags_t) ((FL(node) | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE) & ~PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN); if (node->parts.size == 0 && node->opening_loc.start == NULL) { node->base.location.start = part->location.start; @@ -19674,7 +19674,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parse_regular_expression_errors(parser, node); } - pm_node_flag_set(UP(node), parse_and_validate_regular_expression_encoding(parser, &unescaped, ascii_only, node->base.flags)); + pm_node_flag_set(UP(node), parse_and_validate_regular_expression_encoding(parser, &unescaped, ascii_only, FL(node))); return UP(node); } diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index d69a0d74569a2f..790cf9ebb8ade1 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -151,10 +151,15 @@ typedef struct pm_node { */ #define PM_NODE_TYPE_P(node_, type_) (PM_NODE_TYPE(node_) == (type_)) +/** + * Return the flags associated with the given node. + */ +#define PM_NODE_FLAGS(node_) (PM_NODE_UPCAST(node_)->flags) + /** * Return true if the given flag is set on the given node. */ -#define PM_NODE_FLAG_P(node_, flag_) ((PM_NODE_UPCAST(node_)->flags & (flag_)) != 0) +#define PM_NODE_FLAG_P(node_, flag_) ((PM_NODE_FLAGS(node_) & (flag_)) != 0) <%- nodes.each do |node| -%> /** From a55040cac5b91b2d197fd604251f60f4bff289e8 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 15:21:40 -0500 Subject: [PATCH 1503/2435] [ruby/prism] Further specialize PM_NODE_INIT https://github.com/ruby/prism/commit/7ab6d9df47 --- prism/prism.c | 180 +++++++++++++++++++++++++++++++------------------- 1 file changed, 112 insertions(+), 68 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 65949190f18036..e17163091af291 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1949,7 +1949,8 @@ pm_node_alloc(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, size_t size) { .location = { .start = (start_), .end = (end_) } \ } -#define PM_NODE_INIT_UNSET(parser_, type_, flags_) PM_NODE_INIT(parser_, type_, flags_, (parser_)->start, (parser_)->start) +#define PM_NODE_INIT_UNSET(parser_, type_, flags_) PM_NODE_INIT(parser_, type_, flags_, NULL, NULL) +#define PM_NODE_INIT_BASE(parser_, type_, flags_) PM_NODE_INIT(parser_, type_, flags_, (parser_)->start, (parser_)->start) #define PM_NODE_INIT_TOKEN(parser_, type_, flags_, token_) PM_NODE_INIT(parser_, type_, flags_, PM_TOKEN_START(token_), PM_TOKEN_END(token_)) #define PM_NODE_INIT_NODE(parser_, type_, flags_, node_) PM_NODE_INIT(parser_, type_, flags_, PM_NODE_START(node_), PM_NODE_END(node_)) @@ -2052,7 +2053,7 @@ pm_arguments_node_create(pm_parser_t *parser) { pm_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_arguments_node_t); *node = (pm_arguments_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_ARGUMENTS_NODE, 0), + .base = PM_NODE_INIT_BASE(parser, PM_ARGUMENTS_NODE, 0), .arguments = { 0 } }; @@ -2294,7 +2295,11 @@ pm_assoc_splat_node_create(pm_parser_t *parser, pm_node_t *value, const pm_token pm_assoc_splat_node_t *node = PM_NODE_ALLOC(parser, pm_assoc_splat_node_t); *node = (pm_assoc_splat_node_t) { - .base = PM_NODE_INIT(parser, PM_ASSOC_SPLAT_NODE, 0, operator->start, value == NULL ? operator->end : value->location.end), + .base = ( + (value == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_ASSOC_SPLAT_NODE, 0, operator) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_ASSOC_SPLAT_NODE, 0, operator, value) + ), .value = value, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -2326,7 +2331,11 @@ pm_begin_node_create(pm_parser_t *parser, const pm_token_t *begin_keyword, pm_st pm_begin_node_t *node = PM_NODE_ALLOC(parser, pm_begin_node_t); *node = (pm_begin_node_t) { - .base = PM_NODE_INIT(parser, PM_BEGIN_NODE, 0, begin_keyword->start, statements == NULL ? begin_keyword->end : statements->base.location.end), + .base = ( + (statements == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_BEGIN_NODE, 0, begin_keyword) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_BEGIN_NODE, 0, begin_keyword, statements) + ), .begin_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(begin_keyword), .statements = statements, .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE @@ -2385,7 +2394,11 @@ pm_block_argument_node_create(pm_parser_t *parser, const pm_token_t *operator, p pm_block_argument_node_t *node = PM_NODE_ALLOC(parser, pm_block_argument_node_t); *node = (pm_block_argument_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_ARGUMENT_NODE, 0, operator->start, expression == NULL ? operator->end : expression->location.end), + .base = ( + (expression == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_BLOCK_ARGUMENT_NODE, 0, operator) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_BLOCK_ARGUMENT_NODE, 0, operator, expression) + ), .expression = expression, .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) }; @@ -2421,7 +2434,11 @@ pm_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, cons pm_block_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_block_parameter_node_t); *node = (pm_block_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETER_NODE, 0, operator->start, name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end), + .base = ( + (name->type == PM_TOKEN_NOT_PROVIDED) + ? PM_NODE_INIT_TOKEN(parser, PM_BLOCK_PARAMETER_NODE, 0, operator) + : PM_NODE_INIT_TOKENS(parser, PM_BLOCK_PARAMETER_NODE, 0, operator, name) + ), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -2512,7 +2529,11 @@ pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument pm_break_node_t *node = PM_NODE_ALLOC(parser, pm_break_node_t); *node = (pm_break_node_t) { - .base = PM_NODE_INIT(parser, PM_BREAK_NODE, 0, keyword->start, arguments == NULL ? keyword->end : arguments->base.location.end), + .base = ( + (arguments == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_BREAK_NODE, 0, keyword) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_BREAK_NODE, 0, keyword, arguments) + ), .arguments = arguments, .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) }; @@ -2539,7 +2560,7 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { pm_call_node_t *node = PM_NODE_ALLOC(parser, pm_call_node_t); *node = (pm_call_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_CALL_NODE, flags), + .base = PM_NODE_INIT_BASE(parser, PM_CALL_NODE, flags), .receiver = NULL, .call_operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .message_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, @@ -3402,13 +3423,23 @@ pm_constant_path_node_create(pm_parser_t *parser, pm_node_t *parent, const pm_to name = pm_parser_constant_id_token(parser, name_token); } - *node = (pm_constant_path_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_NODE, 0, parent == NULL ? delimiter->start : parent->location.start, name_token->end), - .parent = parent, - .name = name, - .delimiter_loc = PM_LOCATION_TOKEN_VALUE(delimiter), - .name_loc = PM_LOCATION_TOKEN_VALUE(name_token) - }; + if (parent == NULL) { + *node = (pm_constant_path_node_t) { + .base = PM_NODE_INIT_TOKENS(parser, PM_CONSTANT_PATH_NODE, 0, delimiter, name_token), + .parent = parent, + .name = name, + .delimiter_loc = PM_LOCATION_TOKEN_VALUE(delimiter), + .name_loc = PM_LOCATION_TOKEN_VALUE(name_token) + }; + } else { + *node = (pm_constant_path_node_t) { + .base = PM_NODE_INIT_NODE_TOKEN(parser, PM_CONSTANT_PATH_NODE, 0, parent, name_token), + .parent = parent, + .name = name, + .delimiter_loc = PM_LOCATION_TOKEN_VALUE(delimiter), + .name_loc = PM_LOCATION_TOKEN_VALUE(name_token) + }; + } return node; } @@ -3587,20 +3618,17 @@ pm_def_node_create( const pm_token_t *end_keyword ) { pm_def_node_t *node = PM_NODE_ALLOC(parser, pm_def_node_t); - const uint8_t *end; - - if (end_keyword->type == PM_TOKEN_NOT_PROVIDED) { - end = body->location.end; - } else { - end = end_keyword->end; - } if (receiver != NULL) { pm_def_node_receiver_check(parser, receiver); } *node = (pm_def_node_t) { - .base = PM_NODE_INIT(parser, PM_DEF_NODE, 0, def_keyword->start, end), + .base = ( + (end_keyword->type == PM_TOKEN_NOT_PROVIDED) + ? PM_NODE_INIT_TOKEN_NODE(parser, PM_DEF_NODE, 0, def_keyword, body) + : PM_NODE_INIT_TOKENS(parser, PM_DEF_NODE, 0, def_keyword, end_keyword) + ), .name = name, .name_loc = PM_LOCATION_TOKEN_VALUE(name_loc), .receiver = receiver, @@ -3622,16 +3650,19 @@ pm_def_node_create( * Allocate a new DefinedNode node. */ static pm_defined_node_t * -pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t *value, const pm_token_t *rparen, const pm_location_t *keyword_loc) { +pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t *value, const pm_token_t *rparen, const pm_token_t *keyword) { pm_defined_node_t *node = PM_NODE_ALLOC(parser, pm_defined_node_t); - const uint8_t *end = rparen->type == PM_TOKEN_NOT_PROVIDED ? value->location.end : rparen->end; *node = (pm_defined_node_t) { - .base = PM_NODE_INIT(parser, PM_DEFINED_NODE, 0, keyword_loc->start, end), + .base = ( + (rparen->type == PM_TOKEN_NOT_PROVIDED) + ? PM_NODE_INIT_TOKEN_NODE(parser, PM_DEFINED_NODE, 0, keyword, value) + : PM_NODE_INIT_TOKENS(parser, PM_DEFINED_NODE, 0, keyword, rparen) + ), .lparen_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(lparen), .value = value, .rparen_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(rparen), - .keyword_loc = *keyword_loc + .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) }; return node; @@ -3643,15 +3674,13 @@ pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t static pm_else_node_t * pm_else_node_create(pm_parser_t *parser, const pm_token_t *else_keyword, pm_statements_node_t *statements, const pm_token_t *end_keyword) { pm_else_node_t *node = PM_NODE_ALLOC(parser, pm_else_node_t); - const uint8_t *end = NULL; - if ((end_keyword->type == PM_TOKEN_NOT_PROVIDED) && (statements != NULL)) { - end = statements->base.location.end; - } else { - end = end_keyword->end; - } *node = (pm_else_node_t) { - .base = PM_NODE_INIT(parser, PM_ELSE_NODE, 0, else_keyword->start, end), + .base = ( + ((end_keyword->type == PM_TOKEN_NOT_PROVIDED) && (statements != NULL)) + ? PM_NODE_INIT_TOKEN_NODE(parser, PM_ELSE_NODE, 0, else_keyword, statements) + : PM_NODE_INIT_TOKENS(parser, PM_ELSE_NODE, 0, else_keyword, end_keyword) + ), .else_keyword_loc = PM_LOCATION_TOKEN_VALUE(else_keyword), .statements = statements, .end_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(end_keyword) @@ -4025,9 +4054,12 @@ pm_forwarding_super_node_create(pm_parser_t *parser, const pm_token_t *token, pm block = (pm_block_node_t *) arguments->block; } - const uint8_t *end = block != NULL ? block->base.location.end : token->end; *node = (pm_forwarding_super_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_SUPER_NODE, 0, token->start, end), + .base = ( + (block == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_FORWARDING_SUPER_NODE, 0, token) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_FORWARDING_SUPER_NODE, 0, token, block) + ), .block = block }; @@ -4195,7 +4227,7 @@ pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); *node = (pm_global_variable_read_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0), + .base = PM_NODE_INIT_BASE(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0), .name = name }; @@ -4229,7 +4261,7 @@ pm_global_variable_write_node_synthesized_create(pm_parser_t *parser, pm_constan pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); *node = (pm_global_variable_write_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0), + .base = PM_NODE_INIT_BASE(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0), .name = name, .name_loc = PM_LOCATION_NULL_VALUE(parser), .operator_loc = PM_LOCATION_NULL_VALUE(parser), @@ -4691,7 +4723,7 @@ pm_interpolated_regular_expression_node_create(pm_parser_t *parser, const pm_tok pm_interpolated_regular_expression_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_regular_expression_node_t); *node = (pm_interpolated_regular_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening->start, NULL), + .base = PM_NODE_INIT_TOKEN(parser, PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, PM_NODE_FLAG_STATIC_LITERAL, opening), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .closing_loc = PM_LOCATION_TOKEN_VALUE(opening), .parts = { 0 } @@ -4980,7 +5012,7 @@ pm_keyword_hash_node_create(pm_parser_t *parser) { pm_keyword_hash_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_hash_node_t); *node = (pm_keyword_hash_node_t) { - .base = PM_NODE_INIT(parser, PM_KEYWORD_HASH_NODE, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS, NULL, NULL), + .base = PM_NODE_INIT_UNSET(parser, PM_KEYWORD_HASH_NODE, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS), .elements = { 0 } }; @@ -5046,7 +5078,11 @@ pm_keyword_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *ope pm_keyword_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_rest_parameter_node_t); *node = (pm_keyword_rest_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_KEYWORD_REST_PARAMETER_NODE, 0, operator->start, (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end)), + .base = ( + (name->type == PM_TOKEN_NOT_PROVIDED) + ? PM_NODE_INIT_TOKEN(parser, PM_KEYWORD_REST_PARAMETER_NODE, 0, operator) + : PM_NODE_INIT_TOKENS(parser, PM_KEYWORD_REST_PARAMETER_NODE, 0, operator, name) + ), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -5191,7 +5227,7 @@ pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name, pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); *node = (pm_local_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_WRITE_NODE, flags, name_loc->start, value->location.end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_LOCAL_VARIABLE_WRITE_NODE, flags, name_loc, value), .name = name, .depth = depth, .value = value, @@ -5240,7 +5276,7 @@ pm_local_variable_target_node_create(pm_parser_t *parser, const pm_location_t *l pm_local_variable_target_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_target_node_t); *node = (pm_local_variable_target_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_TARGET_NODE, 0, location->start, location->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_LOCAL_VARIABLE_TARGET_NODE, 0, location), .name = name, .depth = depth }; @@ -5330,7 +5366,7 @@ pm_multi_target_node_create(pm_parser_t *parser) { pm_multi_target_node_t *node = PM_NODE_ALLOC(parser, pm_multi_target_node_t); *node = (pm_multi_target_node_t) { - .base = PM_NODE_INIT(parser, PM_MULTI_TARGET_NODE, 0, NULL, NULL), + .base = PM_NODE_INIT_UNSET(parser, PM_MULTI_TARGET_NODE, 0), .lefts = { 0 }, .rest = NULL, .rights = { 0 }, @@ -5428,7 +5464,11 @@ pm_next_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments pm_next_node_t *node = PM_NODE_ALLOC(parser, pm_next_node_t); *node = (pm_next_node_t) { - .base = PM_NODE_INIT(parser, PM_NEXT_NODE, 0, keyword->start, (arguments == NULL ? keyword->end : arguments->base.location.end)), + .base = ( + (arguments == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_NEXT_NODE, 0, keyword) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_NEXT_NODE, 0, keyword, arguments) + ), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .arguments = arguments }; @@ -5477,7 +5517,7 @@ pm_numbered_parameters_node_create(pm_parser_t *parser, const pm_location_t *loc pm_numbered_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_parameters_node_t); *node = (pm_numbered_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_NUMBERED_PARAMETERS_NODE, 0, location->start, location->end), + .base = PM_NODE_INIT_TOKEN(parser, PM_NUMBERED_PARAMETERS_NODE, 0, location), .maximum = maximum }; @@ -5594,7 +5634,7 @@ pm_parameters_node_create(pm_parser_t *parser) { pm_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_parameters_node_t); *node = (pm_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_PARAMETERS_NODE, 0, NULL, NULL), + .base = PM_NODE_INIT_UNSET(parser, PM_PARAMETERS_NODE, 0), .rest = NULL, .keyword_rest = NULL, .block = NULL, @@ -5697,11 +5737,8 @@ static pm_program_node_t * pm_program_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, pm_statements_node_t *statements) { pm_program_node_t *node = PM_NODE_ALLOC(parser, pm_program_node_t); - const uint8_t *start = statements == NULL ? parser->start : statements->base.location.start; - const uint8_t *end = statements == NULL ? parser->end : statements->base.location.end; - *node = (pm_program_node_t) { - .base = PM_NODE_INIT(parser, PM_PROGRAM_NODE, 0, start, end), + .base = PM_NODE_INIT_NODE(parser, PM_PROGRAM_NODE, 0, statements), .locals = *locals, .statements = statements }; @@ -5857,7 +5894,7 @@ pm_regular_expression_node_create_unescaped(pm_parser_t *parser, const pm_token_ pm_node_flags_t flags = pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL; *node = (pm_regular_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_REGULAR_EXPRESSION_NODE, flags, MIN(opening->start, closing->start), MAX(opening->end, closing->end)), + .base = PM_NODE_INIT_TOKENS(parser, PM_REGULAR_EXPRESSION_NODE, flags, opening, closing), .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), .content_loc = PM_LOCATION_TOKEN_VALUE(content), .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), @@ -5979,7 +6016,11 @@ pm_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, c pm_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_rest_parameter_node_t); *node = (pm_rest_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REST_PARAMETER_NODE, 0, operator->start, (name->type == PM_TOKEN_NOT_PROVIDED ? operator->end : name->end)), + .base = ( + (name->type == PM_TOKEN_NOT_PROVIDED) + ? PM_NODE_INIT_TOKEN(parser, PM_REST_PARAMETER_NODE, 0, operator) + : PM_NODE_INIT_TOKENS(parser, PM_REST_PARAMETER_NODE, 0, operator, name) + ), .name = pm_parser_optional_constant_id_token(parser, name), .name_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(name), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator) @@ -6011,7 +6052,11 @@ pm_return_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argumen pm_return_node_t *node = PM_NODE_ALLOC(parser, pm_return_node_t); *node = (pm_return_node_t) { - .base = PM_NODE_INIT(parser, PM_RETURN_NODE, 0, keyword->start, (arguments == NULL ? keyword->end : arguments->base.location.end)), + .base = ( + (arguments == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_RETURN_NODE, 0, keyword) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_RETURN_NODE, 0, keyword, arguments) + ), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .arguments = arguments }; @@ -6134,7 +6179,11 @@ pm_splat_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t pm_splat_node_t *node = PM_NODE_ALLOC(parser, pm_splat_node_t); *node = (pm_splat_node_t) { - .base = PM_NODE_INIT(parser, PM_SPLAT_NODE, 0, operator->start, (expression == NULL ? operator->end : expression->location.end)), + .base = ( + (expression == NULL) + ? PM_NODE_INIT_TOKEN(parser, PM_SPLAT_NODE, 0, operator) + : PM_NODE_INIT_TOKEN_NODE(parser, PM_SPLAT_NODE, 0, operator, expression) + ), .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .expression = expression }; @@ -6150,7 +6199,7 @@ pm_statements_node_create(pm_parser_t *parser) { pm_statements_node_t *node = PM_NODE_ALLOC(parser, pm_statements_node_t); *node = (pm_statements_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_STATEMENTS_NODE, 0), + .base = PM_NODE_INIT_BASE(parser, PM_STATEMENTS_NODE, 0), .body = { 0 } }; @@ -6596,7 +6645,7 @@ pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) { pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); *node = (pm_symbol_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING), + .base = PM_NODE_INIT_BASE(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING), .value_loc = PM_LOCATION_NULL_VALUE(parser), .unescaped = { 0 } }; @@ -6708,7 +6757,7 @@ pm_true_node_synthesized_create(pm_parser_t *parser) { pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); *node = (pm_true_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL) + .base = PM_NODE_INIT_BASE(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL) }; return node; @@ -6746,17 +6795,12 @@ pm_undef_node_append(pm_undef_node_t *node, pm_node_t *name) { static pm_unless_node_t * pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, const pm_token_t *then_keyword, pm_statements_node_t *statements) { pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - pm_unless_node_t *node = PM_NODE_ALLOC(parser, pm_unless_node_t); - const uint8_t *end; - if (statements != NULL) { - end = statements->base.location.end; - } else { - end = predicate->location.end; - } + pm_unless_node_t *node = PM_NODE_ALLOC(parser, pm_unless_node_t); + pm_node_t *end = statements == NULL ? predicate : UP(statements); *node = (pm_unless_node_t) { - .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, keyword->start, end), + .base = PM_NODE_INIT_TOKEN_NODE(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, keyword, end), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .predicate = predicate, .then_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(then_keyword), @@ -6959,7 +7003,7 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); *node = (pm_while_node_t) { - .base = PM_NODE_INIT_UNSET(parser, PM_WHILE_NODE, 0), + .base = PM_NODE_INIT_BASE(parser, PM_WHILE_NODE, 0), .keyword_loc = PM_LOCATION_NULL_VALUE(parser), .do_keyword_loc = PM_LOCATION_NULL_VALUE(parser), .closing_loc = PM_LOCATION_NULL_VALUE(parser), @@ -18917,7 +18961,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b &lparen, expression, &rparen, - &PM_LOCATION_TOKEN_VALUE(&keyword) + &keyword )); } case PM_TOKEN_KEYWORD_END_UPCASE: { From a1ea824b8550f214ecc501536eed3468cbfd11ae Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 15:44:33 -0500 Subject: [PATCH 1504/2435] [ruby/prism] Remove PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE macro https://github.com/ruby/prism/commit/1988615ce1 --- prism/prism.c | 65 +++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index e17163091af291..f6edce5d6f0e4a 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1576,8 +1576,7 @@ not_provided(pm_parser_t *parser) { #define PM_LOCATION_TOKEN_VALUE(token) ((pm_location_t) { .start = (token)->start, .end = (token)->end }) #define PM_LOCATION_NODE_VALUE(node) ((pm_location_t) { .start = (node)->location.start, .end = (node)->location.end }) #define PM_LOCATION_NODE_BASE_VALUE(node) ((pm_location_t) { .start = (node)->base.location.start, .end = (node)->base.location.end }) -#define PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE ((pm_location_t) { .start = NULL, .end = NULL }) -#define PM_OPTIONAL_LOCATION_TOKEN_VALUE(token) ((token)->type == PM_TOKEN_NOT_PROVIDED ? PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE : PM_LOCATION_TOKEN_VALUE(token)) +#define PM_OPTIONAL_LOCATION_TOKEN_VALUE(token) ((token)->type == PM_TOKEN_NOT_PROVIDED ? ((pm_location_t) { 0 }) : PM_LOCATION_TOKEN_VALUE(token)) /** * This is a special out parameter to the parse_arguments_list function that @@ -2153,8 +2152,8 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node .rest = NULL, .requireds = { 0 }, .posts = { 0 }, - .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .opening_loc = { 0 }, + .closing_loc = { 0 } }; // For now we're going to just copy over each pointer manually. This could be @@ -2189,8 +2188,8 @@ pm_array_pattern_node_rest_create(pm_parser_t *parser, pm_node_t *rest) { .rest = rest, .requireds = { 0 }, .posts = { 0 }, - .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .opening_loc = { 0 }, + .closing_loc = { 0 } }; return node; @@ -2338,7 +2337,7 @@ pm_begin_node_create(pm_parser_t *parser, const pm_token_t *begin_keyword, pm_st ), .begin_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(begin_keyword), .statements = statements, - .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .end_keyword_loc = { 0 } }; return node; @@ -2476,7 +2475,7 @@ pm_block_parameters_node_create(pm_parser_t *parser, pm_parameters_node_t *param .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETERS_NODE, 0, start, end), .parameters = parameters, .opening_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(opening), - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .closing_loc = { 0 }, .locals = { 0 } }; @@ -2562,12 +2561,12 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { *node = (pm_call_node_t) { .base = PM_NODE_INIT_BASE(parser, PM_CALL_NODE, flags), .receiver = NULL, - .call_operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .message_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .call_operator_loc = { 0 }, + .message_loc = { 0 }, + .opening_loc = { 0 }, .arguments = NULL, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .equal_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .closing_loc = { 0 }, + .equal_loc = { 0 }, .block = NULL, .name = 0 }; @@ -3788,8 +3787,8 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { .left = left_splat_node, .right = right_splat_node, .requireds = { 0 }, - .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .opening_loc = { 0 }, + .closing_loc = { 0 } }; // For now we're going to just copy over each pointer manually. This could be @@ -4115,8 +4114,8 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme .constant = NULL, .elements = { 0 }, .rest = rest, - .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .opening_loc = { 0 }, + .closing_loc = { 0 } }; pm_node_t *element; @@ -4369,10 +4368,10 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t .base = PM_NODE_INIT_NODES(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, statement, predicate), .if_keyword_loc = PM_LOCATION_TOKEN_VALUE(if_keyword), .predicate = predicate, - .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .then_keyword_loc = { 0 }, .statements = statements, .subsequent = NULL, - .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .end_keyword_loc = { 0 } }; return node; @@ -4399,12 +4398,12 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to *node = (pm_if_node_t) { .base = PM_NODE_INIT_NODES(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, predicate, false_expression), - .if_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .if_keyword_loc = { 0 }, .predicate = predicate, .then_keyword_loc = PM_LOCATION_TOKEN_VALUE(qmark), .statements = if_statements, .subsequent = UP(else_node), - .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .end_keyword_loc = { 0 } }; return node; @@ -5370,8 +5369,8 @@ pm_multi_target_node_create(pm_parser_t *parser) { .lefts = { 0 }, .rest = NULL, .rights = { 0 }, - .lparen_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .rparen_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .lparen_loc = { 0 }, + .rparen_loc = { 0 } }; return node; @@ -5954,8 +5953,8 @@ pm_rescue_node_create(pm_parser_t *parser, const pm_token_t *keyword) { *node = (pm_rescue_node_t) { .base = PM_NODE_INIT_TOKEN(parser, PM_RESCUE_NODE, 0, keyword), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), - .operator_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .operator_loc = { 0 }, + .then_keyword_loc = { 0 }, .reference = NULL, .statements = NULL, .subsequent = NULL, @@ -6806,7 +6805,7 @@ pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t .then_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(then_keyword), .statements = statements, .else_clause = NULL, - .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .end_keyword_loc = { 0 } }; return node; @@ -6827,10 +6826,10 @@ pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const .base = PM_NODE_INIT_NODES(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, statement, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(unless_keyword), .predicate = predicate, - .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .then_keyword_loc = { 0 }, .statements = statements, .else_clause = NULL, - .end_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE + .end_keyword_loc = { 0 } }; return node; @@ -6897,8 +6896,8 @@ pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm *node = (pm_until_node_t) { .base = PM_NODE_INIT_NODES(parser, PM_UNTIL_NODE, flags, statements, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), - .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .do_keyword_loc = { 0 }, + .closing_loc = { 0 }, .predicate = predicate, .statements = statements }; @@ -6917,7 +6916,7 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) { .base = PM_NODE_INIT_TOKEN(parser, PM_WHEN_NODE, 0, keyword), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), .statements = NULL, - .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .then_keyword_loc = { 0 }, .conditions = { 0 } }; @@ -6986,8 +6985,8 @@ pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm *node = (pm_while_node_t) { .base = PM_NODE_INIT_NODES(parser, PM_WHILE_NODE, flags, statements, predicate), .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), - .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, - .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .do_keyword_loc = { 0 }, + .closing_loc = { 0 }, .predicate = predicate, .statements = statements }; From e0746cc443dfe948fa8d0eac5ecaa36e7eb6c972 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 2 Dec 2025 15:51:34 -0500 Subject: [PATCH 1505/2435] [ruby/prism] Consolidate macro definitions https://github.com/ruby/prism/commit/cc0ca08757 --- prism/prism.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index f6edce5d6f0e4a..cd4d166a124ef1 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19,17 +19,22 @@ pm_version(void) { #define MAX(a,b) (((a)>(b))?(a):(b)) /******************************************************************************/ -/* Helpful node-related macros */ +/* Helpful AST-related macros */ /******************************************************************************/ #define FL PM_NODE_FLAGS #define UP PM_NODE_UPCAST #define PM_TOKEN_START(token_) ((token_)->start) -#define PM_TOKEN_END(token_) ((token_)->end) +#define PM_TOKEN_END(token_) ((token_)->end) #define PM_NODE_START(node_) (UP(node_)->location.start) -#define PM_NODE_END(node_) (UP(node_)->location.end) +#define PM_NODE_END(node_) (UP(node_)->location.end) + +#define PM_LOCATION_NULL_VALUE(parser_) ((pm_location_t) { .start = (parser_)->start, .end = (parser_)->start }) +#define PM_LOCATION_TOKEN_VALUE(token_) ((pm_location_t) { .start = PM_TOKEN_START(token_), .end = PM_TOKEN_END(token_) }) +#define PM_LOCATION_NODE_VALUE(node_) ((pm_location_t) { .start = PM_NODE_START(node_), .end = PM_NODE_END(node_) }) +#define PM_OPTIONAL_LOCATION_TOKEN_VALUE(token) ((token)->type == PM_TOKEN_NOT_PROVIDED ? ((pm_location_t) { 0 }) : PM_LOCATION_TOKEN_VALUE(token)) /******************************************************************************/ /* Lex mode manipulations */ @@ -1572,12 +1577,6 @@ not_provided(pm_parser_t *parser) { return (pm_token_t) { .type = PM_TOKEN_NOT_PROVIDED, .start = parser->start, .end = parser->start }; } -#define PM_LOCATION_NULL_VALUE(parser) ((pm_location_t) { .start = (parser)->start, .end = (parser)->start }) -#define PM_LOCATION_TOKEN_VALUE(token) ((pm_location_t) { .start = (token)->start, .end = (token)->end }) -#define PM_LOCATION_NODE_VALUE(node) ((pm_location_t) { .start = (node)->location.start, .end = (node)->location.end }) -#define PM_LOCATION_NODE_BASE_VALUE(node) ((pm_location_t) { .start = (node)->base.location.start, .end = (node)->base.location.end }) -#define PM_OPTIONAL_LOCATION_TOKEN_VALUE(token) ((token)->type == PM_TOKEN_NOT_PROVIDED ? ((pm_location_t) { 0 }) : PM_LOCATION_TOKEN_VALUE(token)) - /** * This is a special out parameter to the parse_arguments_list function that * includes opening and closing parentheses in addition to the arguments since @@ -4662,7 +4661,7 @@ pm_instance_variable_write_node_create(pm_parser_t *parser, pm_instance_variable *node = (pm_instance_variable_write_node_t) { .base = PM_NODE_INIT_NODES(parser, PM_INSTANCE_VARIABLE_WRITE_NODE, flags, read_node, value), .name = read_node->name, - .name_loc = PM_LOCATION_NODE_BASE_VALUE(read_node), + .name_loc = PM_LOCATION_NODE_VALUE(read_node), .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), .value = value }; @@ -7069,8 +7068,6 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo return node; } -#undef PM_NODE_ALLOC - /** * Check if any of the currently visible scopes contain a local variable * described by the given constant id. @@ -22293,10 +22290,6 @@ pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { #undef PM_CASE_OPERATOR #undef PM_CASE_WRITABLE #undef PM_STRING_EMPTY -#undef PM_LOCATION_NODE_BASE_VALUE -#undef PM_LOCATION_NODE_VALUE -#undef PM_LOCATION_NULL_VALUE -#undef PM_LOCATION_TOKEN_VALUE // We optionally support serializing to a binary string. For systems that don't // want or need this functionality, it can be turned off with the From a63147eed1184cb812664d5917d6687635a23ab6 Mon Sep 17 00:00:00 2001 From: Yuki Kurihara Date: Wed, 3 Dec 2025 07:24:01 +0900 Subject: [PATCH 1506/2435] [ruby/strscan] [DOC] Avoid being interpreted as a link (https://github.com/ruby/strscan/pull/180) Since `[](n)` is being interpreted as a Markdown link, it cannot be displayed as a method call. I have corrected this by escaping the brackets so that they are interpreted as strings rather than links. ### Before ri ``` #{}[n] | nth captured substring. | +nil+. ``` html image ### After ri ``` #[](n) | nth captured substring. | +nil+. ``` html image https://github.com/ruby/strscan/commit/b3d56867fd --- doc/strscan/strscan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/strscan/strscan.md b/doc/strscan/strscan.md index c4ab2034624947..385e92f84e3542 100644 --- a/doc/strscan/strscan.md +++ b/doc/strscan/strscan.md @@ -417,7 +417,7 @@ Each of these methods returns a captured match value: | Method | Return After Match | Return After No Match | |-----------------|-----------------------------------------|-----------------------| | #size | Count of captured substrings. | +nil+. | -| #[](n) | nth captured substring. | +nil+. | +| #\[\](n) | nth captured substring. | +nil+. | | #captures | Array of all captured substrings. | +nil+. | | #values_at(*n) | Array of specified captured substrings. | +nil+. | | #named_captures | Hash of named captures. | {}. | From a211abbcbd87f6d59a99cfcf2cb63a39d61b16ea Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 2 Dec 2025 17:35:53 -0500 Subject: [PATCH 1507/2435] Cache array length in `rb_ary_join` (#15362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When all elements are strings, we never have to recalculate the length of the array because there are no conversion methods that are called, so the length will never change. This speeds up the fast path by ~10%. ```ruby a = ["1"*10, "2"*10, "3"*10, "4"*10, "5"*10] * 10 10_000_000.times do a.join end ``` ``` hyperfine --warmup 1 'ruby ../ruby2/test.rb' './exe/ruby ../ruby2/test.rb' Benchmark 1: ruby ../ruby2/test.rb Time (mean ± σ): 3.779 s ± 0.053 s [User: 3.754 s, System: 0.017 s] Range (min … max): 3.715 s … 3.874 s 10 runs Benchmark 2: ./exe/ruby ../ruby2/test.rb Time (mean ± σ): 3.411 s ± 0.038 s [User: 3.387 s, System: 0.017 s] Range (min … max): 3.360 s … 3.472 s 10 runs Summary ./exe/ruby ../ruby2/test.rb ran 1.11 ± 0.02 times faster than ruby ../ruby2/test.rb ``` --- array.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/array.c b/array.c index 2acaafee03d833..a6aeeeeca156b4 100644 --- a/array.c +++ b/array.c @@ -2917,23 +2917,28 @@ rb_ary_join(VALUE ary, VALUE sep) StringValue(sep); len += RSTRING_LEN(sep) * (RARRAY_LEN(ary) - 1); } - for (i=0; i n) i = n; - result = rb_str_buf_new(len + (n-i)*10); - rb_enc_associate(result, rb_usascii_encoding()); - i = ary_join_0(ary, sep, i, result); - first = i == 0; - ary_join_1(ary, ary, sep, i, result, &first); - return result; + if (RB_UNLIKELY(!RB_TYPE_P(val, T_STRING))) { + tmp = rb_check_string_type(val); + if (NIL_P(tmp) || tmp != val) { + int first; + long n = RARRAY_LEN(ary); + if (i > n) i = n; + result = rb_str_buf_new(len + (n-i)*10); + rb_enc_associate(result, rb_usascii_encoding()); + i = ary_join_0(ary, sep, i, result); + first = i == 0; + ary_join_1(ary, ary, sep, i, result, &first); + return result; + } + len += RSTRING_LEN(tmp); + len_memo = RARRAY_LEN(ary); + } + else { + len += RSTRING_LEN(val); } - - len += RSTRING_LEN(tmp); } result = rb_str_new(0, len); From dfdc5d40ec25e42ff63982958c43d425bee909fb Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 26 Nov 2025 14:11:23 +0900 Subject: [PATCH 1508/2435] Check and raise semantics errors on nested variables captures in patterns This commit makes these codes to be invalid. ```ruby case 0 in [a] | 1 end case 0 in { a: b } | 1 end case 0 in [{ a: [{ b: [{ c: }] }] }] | 1 end ``` --- parse.y | 6 +----- test/.excludes-parsey/TestPatternMatching.rb | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 test/.excludes-parsey/TestPatternMatching.rb diff --git a/parse.y b/parse.y index f6222ea52ea1b7..a9fccdd5f721e0 100644 --- a/parse.y +++ b/parse.y @@ -5468,11 +5468,6 @@ p_top_expr_body : p_expr ; p_expr : p_as - { - p->ctxt.in_alt_pattern = 0; - p->ctxt.capture_in_pattern = 0; - $$ = $1; - } ; p_as : p_expr tASSOC p_variable @@ -5494,6 +5489,7 @@ p_alt : p_alt[left] '|'[alt] if (p->ctxt.capture_in_pattern) { yyerror1(&@alt, "alternative pattern after variable capture"); } + p->ctxt.in_alt_pattern = 0; $$ = NEW_OR($left, $right, &@$, &@alt); /*% ripper: binary!($:left, ID2VAL(idOr), $:right) %*/ } diff --git a/test/.excludes-parsey/TestPatternMatching.rb b/test/.excludes-parsey/TestPatternMatching.rb deleted file mode 100644 index 20a1868d0c5c9e..00000000000000 --- a/test/.excludes-parsey/TestPatternMatching.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_alternative_pattern_nested, "Deeply nested captures variables are missing a syntax error") From 28d9493b98c7fef39845393ea00ef5eb6947091b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 3 Dec 2025 10:56:53 +0900 Subject: [PATCH 1509/2435] [ruby/json] Fix macro arguments `ALWAYS_INLINE()` and `NOINLINE()` are defined with one argument. https://github.com/ruby/json/commit/8fb727901e --- ext/json/generator/generator.c | 15 +++++++-------- ext/json/parser/parser.c | 14 +++++++------- ext/json/simd/simd.h | 10 +++++----- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 6ece9f05385491..35d543a7a768fa 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -128,7 +128,7 @@ typedef struct _search_state { #endif /* HAVE_SIMD */ } search_state; -static ALWAYS_INLINE() void search_flush(search_state *search) +ALWAYS_INLINE(static) void search_flush(search_state *search) { // Do not remove this conditional without profiling, specifically escape-heavy text. // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush). @@ -171,7 +171,7 @@ static inline unsigned char search_escape_basic(search_state *search) return 0; } -static ALWAYS_INLINE() void escape_UTF8_char_basic(search_state *search) +ALWAYS_INLINE(static) void escape_UTF8_char_basic(search_state *search) { const unsigned char ch = (unsigned char)*search->ptr; switch (ch) { @@ -258,7 +258,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) #ifdef HAVE_SIMD -static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) +ALWAYS_INLINE(static) char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) { // Flush the buffer so everything up until the last 'len' characters are unflushed. search_flush(search); @@ -281,7 +281,7 @@ static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned #ifdef HAVE_SIMD_NEON -static ALWAYS_INLINE() unsigned char neon_next_match(search_state *search) +ALWAYS_INLINE(static) unsigned char neon_next_match(search_state *search) { uint64_t mask = search->matches_mask; uint32_t index = trailing_zeros64(mask) >> 2; @@ -395,7 +395,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search) #ifdef HAVE_SIMD_SSE2 -static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search) +ALWAYS_INLINE(static) unsigned char sse2_next_match(search_state *search) { int mask = search->matches_mask; int index = trailing_zeros(mask); @@ -419,7 +419,7 @@ static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search) #define TARGET_SSE2 #endif -static TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search) +ALWAYS_INLINE(static) TARGET_SSE2 unsigned char search_escape_basic_sse2(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { // There are more matches if search->matches_mask > 0. @@ -1123,8 +1123,7 @@ struct hash_foreach_arg { bool mixed_keys_encountered; }; -NOINLINE() -static void +NOINLINE(static) void json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) { if (arg->mixed_keys_encountered) { diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 0216eef987e3f3..5b7cd835cd7d1e 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -88,7 +88,7 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring #if JSON_CPU_LITTLE_ENDIAN_64BITS #if __has_builtin(__builtin_bswap64) #undef rstring_cache_memcmp -static ALWAYS_INLINE() int rstring_cache_memcmp(const char *str, const char *rptr, const long length) +ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr, const long length) { // The libc memcmp has numerous complex optimizations, but in this particular case, // we know the string is small (JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH), so being able to @@ -117,7 +117,7 @@ static ALWAYS_INLINE() int rstring_cache_memcmp(const char *str, const char *rpt #endif #endif -static ALWAYS_INLINE() int rstring_cache_cmp(const char *str, const long length, VALUE rstring) +ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring) { const char *rstring_ptr; long rstring_length; @@ -131,7 +131,7 @@ static ALWAYS_INLINE() int rstring_cache_cmp(const char *str, const long length, } } -static ALWAYS_INLINE() VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +ALWAYS_INLINE(static) VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) { int low = 0; int high = cache->length - 1; @@ -540,7 +540,7 @@ json_eat_comments(JSON_ParserState *state) } } -static ALWAYS_INLINE() void +ALWAYS_INLINE(static) void json_eat_whitespace(JSON_ParserState *state) { while (true) { @@ -661,7 +661,7 @@ static inline const char *json_next_backslash(const char *pe, const char *string return NULL; } -static NOINLINE() VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions) +NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions) { bool intern = is_name || config->freeze; bool symbolize = is_name && config->symbolize_names; @@ -946,7 +946,7 @@ static const bool string_scan_table[256] = { static SIMD_Implementation simd_impl = SIMD_NONE; #endif /* HAVE_SIMD */ -static ALWAYS_INLINE() bool string_scan(JSON_ParserState *state) +ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state) { #ifdef HAVE_SIMD #if defined(HAVE_SIMD_NEON) @@ -1015,7 +1015,7 @@ static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfi return Qfalse; } -static ALWAYS_INLINE() VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +ALWAYS_INLINE(static) VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) { state->cursor++; const char *start = state->cursor; diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index c9e3b3ec7c9f28..f8e5ee1880e5b1 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -73,14 +73,14 @@ static inline SIMD_Implementation find_simd_implementation(void) #define HAVE_SIMD_NEON 1 // See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon -static ALWAYS_INLINE() uint64_t neon_match_mask(uint8x16_t matches) +ALWAYS_INLINE(static) uint64_t neon_match_mask(uint8x16_t matches) { const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); return mask & 0x8888888888888888ull; } -static ALWAYS_INLINE() uint64_t compute_chunk_mask_neon(const char *ptr) +ALWAYS_INLINE(static) uint64_t compute_chunk_mask_neon(const char *ptr) { uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); @@ -93,7 +93,7 @@ static ALWAYS_INLINE() uint64_t compute_chunk_mask_neon(const char *ptr) return neon_match_mask(needs_escape); } -static ALWAYS_INLINE() int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) +ALWAYS_INLINE(static) int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) { while (*ptr + sizeof(uint8x16_t) <= end) { uint64_t chunk_mask = compute_chunk_mask_neon(*ptr); @@ -140,7 +140,7 @@ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) #define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) #define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) -static TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char *ptr) +ALWAYS_INLINE(static) TARGET_SSE2 int compute_chunk_mask_sse2(const char *ptr) { __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 @@ -151,7 +151,7 @@ static TARGET_SSE2 ALWAYS_INLINE() int compute_chunk_mask_sse2(const char *ptr) return _mm_movemask_epi8(needs_escape); } -static TARGET_SSE2 ALWAYS_INLINE() int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +ALWAYS_INLINE(static) TARGET_SSE2 int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) { while (*ptr + sizeof(__m128i) <= end) { int chunk_mask = compute_chunk_mask_sse2(*ptr); From bf144d8c1040d997ccbd79640f81ff6bb31ed64b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 02:03:21 +0000 Subject: [PATCH 1510/2435] Bump actions/checkout from 6.0.0 to 6.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6...v6.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/compilers.yml | 26 ++++++++++++------------ .github/workflows/cygwin.yml | 2 +- .github/workflows/default_gems_list.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/mingw.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/post_push.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/rust-warnings.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-macos.yml | 4 ++-- .github/workflows/yjit-ubuntu.yml | 6 +++--- .github/workflows/zjit-macos.yml | 6 +++--- .github/workflows/zjit-ubuntu.yml | 8 ++++---- 27 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index c1d227181a7c6b..a2958fc00971dc 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -61,7 +61,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 34081cfdf7bc7f..1351e78b98abef 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 68d65dd71cbb3a..9ad8978c3e6e51 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -53,7 +53,7 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler: none - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: ./.github/actions/setup/ubuntu diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 58742c1ce18134..ad2e3915199102 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index ee17217f274bcc..218bef28186e2d 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -30,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index d181759fc1320a..d7b096a96d58ec 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} @@ -65,7 +65,7 @@ jobs: echo RDOC='ruby -W0 --disable-gems tool/rdoc-srcdir -q' >> $GITHUB_ENV - name: Checkout rdoc - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: ruby/rdoc ref: ${{ steps.rdoc.outputs.ref }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 62fc319b19d1dd..a92c93b476d3af 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install libraries if: ${{ contains(matrix.os, 'macos') }} diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 3ecc749c0456a0..97a698613a2f10 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -51,7 +51,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } # Set fetch-depth: 10 so that Launchable can receive commits information. - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } @@ -74,7 +74,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - name: 'GCC 15 LTO' @@ -104,7 +104,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 22', with: { tag: 'clang-22' }, timeout-minutes: 5 } @@ -125,7 +125,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' }, timeout-minutes: 5 } @@ -146,7 +146,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } # -Wno-strict-prototypes is necessary with current clang-15 since @@ -172,7 +172,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 5 } @@ -192,7 +192,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } @@ -214,7 +214,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' }, timeout-minutes: 5 } @@ -234,7 +234,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' }, timeout-minutes: 5 } @@ -254,7 +254,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' }, timeout-minutes: 5 } @@ -274,7 +274,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' }, timeout-minutes: 5 } @@ -293,7 +293,7 @@ jobs: timeout-minutes: 60 services: { docuum: { image: 'stephanmisc/docuum', options: '--init', volumes: [ '/root', '/var/run/docker.sock:/var/run/docker.sock' ] } } steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' }, timeout-minutes: 5 } @@ -319,7 +319,7 @@ jobs: - 'compileB' - 'compileC' steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - uses: ./.github/actions/slack with: diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 8b97cc195ff8c8..ac73991fe80b55 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -40,7 +40,7 @@ jobs: steps: - run: git config --global core.autocrlf input - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup Cygwin uses: cygwin/cygwin-install-action@master diff --git a/.github/workflows/default_gems_list.yml b/.github/workflows/default_gems_list.yml index 63671382f94941..420228f3997d45 100644 --- a/.github/workflows/default_gems_list.yml +++ b/.github/workflows/default_gems_list.yml @@ -20,7 +20,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9a1feb06358463..29adcab39af018 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -61,7 +61,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 192f71649a9a35..1a709d8344a290 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -166,7 +166,7 @@ jobs: [ ${#failed[@]} -eq 0 ] shell: sh - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 8b62ccd111b72a..7851005e5133e1 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -48,7 +48,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 337973a54d07a6..a1035ef451dd82 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -51,7 +51,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 188b4d94d0056e..318444c0a291bf 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -28,7 +28,7 @@ jobs: REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 500 # for notify-slack-commits token: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24bdec5eb9d257..03227bcd7234fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.1 - uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml index f05b35346e8703..a2e3208e5294f4 100644 --- a/.github/workflows/rust-warnings.yml +++ b/.github/workflows/rust-warnings.yml @@ -36,7 +36,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Rust run: rustup default beta diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 6233f06661d557..a321d14010cd69 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -34,7 +34,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 20b3367c832a75..8ccb954d84548d 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -45,7 +45,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 with: diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 26144cc49689eb..907dc0b4f025fe 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 name: Check out ruby/ruby with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 9ec59324900094..9f3ad412f7ba6c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -60,7 +60,7 @@ jobs: )}} steps: &make-steps - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 082d1984e50620..b59e1d4c931ef7 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -59,7 +59,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index dd4bf0de968108..67ea1cacae2cf1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -66,7 +66,7 @@ jobs: bundler: none windows-toolchain: none - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 148dd7a4a3268d..a59b4d650855ef 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - run: RUST_BACKTRACE=1 cargo test working-directory: yjit @@ -83,7 +83,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 5303ab1c794f09..00214709b93e65 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -36,7 +36,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # For now we can't run cargo test --offline because it complains about the # capstone dependency, even though the dependency is optional @@ -68,7 +68,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Check that we don't have linting errors in release mode, too - run: cargo clippy --all-targets --all-features @@ -120,7 +120,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index fc7bf459d89de5..ed559d64e10d05 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -68,7 +68,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -172,7 +172,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: ./.github/actions/setup/macos @@ -192,7 +192,7 @@ jobs: run: echo "MAKEFLAGS=" >> "$GITHUB_ENV" - name: Checkout ruby-bench - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: ruby/ruby-bench path: ruby-bench diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index ac400d410d6cf6..5d281eab0b9947 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - run: cargo clippy --all-targets --all-features working-directory: zjit @@ -103,7 +103,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -228,7 +228,7 @@ jobs: )}} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: ./.github/actions/setup/ubuntu @@ -244,7 +244,7 @@ jobs: - run: make install - name: Checkout ruby-bench - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: ruby/ruby-bench path: ruby-bench From f4466ec8cb1e11192fd9e967bf564c2b4c37d55d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 02:06:58 +0000 Subject: [PATCH 1511/2435] Bump actions/checkout in /.github/actions/setup/directories Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...8e8c483db84b4bee98b60c0593521ed34d9990e8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 8389ef54b78e5e..916e8b5acd0c4c 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -95,7 +95,7 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} From b2f110651c0ab1c3991dd89dc2529a6f21e17170 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 2 Dec 2025 20:39:25 -0600 Subject: [PATCH 1512/2435] [DOC] Harmonize #+ methods --- complex.c | 25 +++++++++++++++++-------- numeric.c | 39 ++++++++++++++++++++++++--------------- rational.c | 27 +++++++++++++++++++-------- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/complex.c b/complex.c index d69b0a65817497..12dec4d23b04c8 100644 --- a/complex.c +++ b/complex.c @@ -816,17 +816,26 @@ rb_complex_uminus(VALUE self) } /* - * call-seq: - * complex + numeric -> new_complex + * call-seq: + * self + other -> numeric + * + * Returns the sum of +self+ and +other+: + * + * Complex(1, 2) + 0 # => (1+2i) + * Complex(1, 2) + 1 # => (2+2i) + * Complex(1, 2) + -1 # => (0+2i) + * + * Complex(1, 2) + 1.0 # => (2.0+2i) + * + * Complex(1, 2) + Complex(2, 1) # => (3+3i) + * Complex(1, 2) + Complex(2.0, 1.0) # => (3.0+3.0i) * - * Returns the sum of +self+ and +numeric+: + * Complex(1, 2) + Rational(1, 1) # => ((2/1)+2i) + * Complex(1, 2) + Rational(1, 2) # => ((3/2)+2i) * - * Complex.rect(2, 3) + Complex.rect(2, 3) # => (4+6i) - * Complex.rect(900) + Complex.rect(1) # => (901+0i) - * Complex.rect(-2, 9) + Complex.rect(-9, 2) # => (-11+11i) - * Complex.rect(9, 8) + 4 # => (13+8i) - * Complex.rect(20, 9) + 9.8 # => (29.8+9i) + * For a computation involving Floats, the result may be inexact (see Float#+): * + * Complex(1, 2) + 3.14 # => (4.140000000000001+2i) */ VALUE rb_complex_plus(VALUE self, VALUE other) diff --git a/numeric.c b/numeric.c index 54c9649a87fa55..dd0797b9c58d8f 100644 --- a/numeric.c +++ b/numeric.c @@ -1132,15 +1132,21 @@ rb_float_uminus(VALUE flt) /* * call-seq: - * self + other -> numeric + * self + other -> float or complex * - * Returns a new \Float which is the sum of +self+ and +other+: + * Returns the sum of +self+ and +other+; + * the result may be inexact (see Float): * - * f = 3.14 - * f + 1 # => 4.140000000000001 - * f + 1.0 # => 4.140000000000001 - * f + Rational(1, 1) # => 4.140000000000001 - * f + Complex(1, 0) # => (4.140000000000001+0i) + * 3.14 + 0 # => 3.14 + * 3.14 + 1 # => 4.140000000000001 + * -3.14 + 0 # => -3.14 + * -3.14 + 1 # => -2.14 + + * 3.14 + -3.14 # => 0.0 + * -3.14 + -3.14 # => -6.28 + * + * 3.14 + Complex(1, 0) # => (4.140000000000001+0i) + * 3.14 + Rational(1, 1) # => 4.140000000000001 * */ @@ -3999,17 +4005,20 @@ rb_fix_plus(VALUE x, VALUE y) /* * call-seq: - * self + numeric -> numeric_result + * self + other -> numeric + * + * Returns the sum of +self+ and +other+: * - * Performs addition: + * 1 + 1 # => 2 + * 1 + -1 # => 0 + * 1 + 0 # => 1 + * 1 + -2 # => -1 + * 1 + Complex(1, 0) # => (2+0i) + * 1 + Rational(1, 1) # => (2/1) * - * 2 + 2 # => 4 - * -2 + 2 # => 0 - * -2 + -2 # => -4 - * 2 + 2.0 # => 4.0 - * 2 + Rational(2, 1) # => (4/1) - * 2 + Complex(2, 0) # => (4+0i) + * For a computation involving Floats, the result may be inexact (see Float#+): * + * 1 + 3.14 # => 4.140000000000001 */ VALUE diff --git a/rational.c b/rational.c index 77b1b175d08185..7c2bd0b1eb7f00 100644 --- a/rational.c +++ b/rational.c @@ -715,16 +715,27 @@ f_addsub(VALUE self, VALUE anum, VALUE aden, VALUE bnum, VALUE bden, int k) static double nurat_to_double(VALUE self); /* - * call-seq: - * rat + numeric -> numeric + * call-seq: + * self + other -> numeric + * + * Returns the sum of +self+ and +other+: + * + * Rational(2, 3) + 0 # => (2/3) + * Rational(2, 3) + 1 # => (5/3) + * Rational(2, 3) + -1 # => (-1/3) + * + * Rational(2, 3) + Complex(1, 0) # => ((5/3)+0i) + * + * Rational(2, 3) + Rational(1, 1) # => (5/3) + * Rational(2, 3) + Rational(3, 2) # => (13/6) + * Rational(2, 3) + Rational(3.0, 2.0) # => (13/6) + * Rational(2, 3) + Rational(3.1, 2.1) # => (30399297484750849/14186338826217063) + * + * For a computation involving Floats, the result may be inexact (see Float#+): * - * Performs addition. + * Rational(2, 3) + 1.0 # => 1.6666666666666665 + * Rational(2, 3) + Complex(1.0, 0.0) # => (1.6666666666666665+0.0i) * - * Rational(2, 3) + Rational(2, 3) #=> (4/3) - * Rational(900) + Rational(1) #=> (901/1) - * Rational(-2, 9) + Rational(-9, 2) #=> (-85/18) - * Rational(9, 8) + 4 #=> (41/8) - * Rational(20, 9) + 9.8 #=> 12.022222222222222 */ VALUE rb_rational_plus(VALUE self, VALUE other) From 6e723bee45fedaffa5ea382c9ace628b58c0c70d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 2 Dec 2025 20:41:21 -0600 Subject: [PATCH 1513/2435] [DOC] About Float Imprecision (#15293) --- doc/float.rb | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ numeric.c | 82 --------------------------------- 2 files changed, 128 insertions(+), 82 deletions(-) create mode 100644 doc/float.rb diff --git a/doc/float.rb b/doc/float.rb new file mode 100644 index 00000000000000..01668bfc6dacf1 --- /dev/null +++ b/doc/float.rb @@ -0,0 +1,128 @@ +# A \Float object stores a real number +# using the native architecture's double-precision floating-point representation. +# +# == \Float Imprecisions +# +# Some real numbers can be represented precisely as \Float objects: +# +# 37.5 # => 37.5 +# 98.75 # => 98.75 +# 12.3125 # => 12.3125 +# +# Others cannot; among these are the transcendental numbers, including: +# +# - Pi, π: in mathematics, a number of infinite precision: +# 3.1415926535897932384626433... (to 25 places); +# in Ruby, it is of limited precision (in this case, to 16 decimal places): +# +# Math::PI # => 3.141592653589793 +# +# - Euler's number, e: in mathematics, a number of infinite precision: +# 2.7182818284590452353602874... (to 25 places); +# in Ruby, it is of limited precision (in this case, to 15 decimal places): +# +# Math::E # => 2.718281828459045 +# +# Some floating-point computations in Ruby give precise results: +# +# 1.0/2 # => 0.5 +# 100.0/8 # => 12.5 +# +# Others do not: +# +# - In mathematics, 2/3 as a decimal number is an infinitely-repeating decimal: +# 0.666... (forever); +# in Ruby, +2.0/3+ is of limited precision (in this case, to 16 decimal places): +# +# 2.0/3 # => 0.6666666666666666 +# +# - In mathematics, the square root of 2 is an irrational number of infinite precision: +# 1.4142135623730950488016887... (to 25 decimal places); +# in Ruby, it is of limited precision (in this case, to 16 decimal places): +# +# Math.sqrt(2.0) # => 1.4142135623730951 +# +# - Even a simple computation can introduce imprecision: +# +# x = 0.1 + 0.2 # => 0.30000000000000004 +# y = 0.3 # => 0.3 +# x == y # => false +# +# See: +# +# - https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html +# - https://github.com/rdp/ruby_tutorials_core/wiki/Ruby-Talk-FAQ#-why-are-rubys-floats-imprecise +# - https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems +# +# Note that precise storage and computation of rational numbers +# is possible using Rational objects. +# +# == Creating a \Float +# +# You can create a \Float object explicitly with: +# +# - A {floating-point literal}[rdoc-ref:syntax/literals.rdoc@Float+Literals]. +# +# You can convert certain objects to Floats with: +# +# - Method #Float. +# +# == What's Here +# +# First, what's elsewhere. Class \Float: +# +# - Inherits from +# {class Numeric}[rdoc-ref:Numeric@What-27s+Here] +# and {class Object}[rdoc-ref:Object@What-27s+Here]. +# - Includes {module Comparable}[rdoc-ref:Comparable@What-27s+Here]. +# +# Here, class \Float provides methods for: +# +# - {Querying}[rdoc-ref:Float@Querying] +# - {Comparing}[rdoc-ref:Float@Comparing] +# - {Converting}[rdoc-ref:Float@Converting] +# +# === Querying +# +# - #finite?: Returns whether +self+ is finite. +# - #hash: Returns the integer hash code for +self+. +# - #infinite?: Returns whether +self+ is infinite. +# - #nan?: Returns whether +self+ is a NaN (not-a-number). +# +# === Comparing +# +# - #<: Returns whether +self+ is less than the given value. +# - #<=: Returns whether +self+ is less than or equal to the given value. +# - #<=>: Returns a number indicating whether +self+ is less than, equal +# to, or greater than the given value. +# - #== (aliased as #=== and #eql?): Returns whether +self+ is equal to +# the given value. +# - #>: Returns whether +self+ is greater than the given value. +# - #>=: Returns whether +self+ is greater than or equal to the given value. +# +# === Converting +# +# - #% (aliased as #modulo): Returns +self+ modulo the given value. +# - #*: Returns the product of +self+ and the given value. +# - #**: Returns the value of +self+ raised to the power of the given value. +# - #+: Returns the sum of +self+ and the given value. +# - #-: Returns the difference of +self+ and the given value. +# - #/: Returns the quotient of +self+ and the given value. +# - #ceil: Returns the smallest number greater than or equal to +self+. +# - #coerce: Returns a 2-element array containing the given value converted to a \Float +# and +self+ +# - #divmod: Returns a 2-element array containing the quotient and remainder +# results of dividing +self+ by the given value. +# - #fdiv: Returns the \Float result of dividing +self+ by the given value. +# - #floor: Returns the greatest number smaller than or equal to +self+. +# - #next_float: Returns the next-larger representable \Float. +# - #prev_float: Returns the next-smaller representable \Float. +# - #quo: Returns the quotient from dividing +self+ by the given value. +# - #round: Returns +self+ rounded to the nearest value, to a given precision. +# - #to_i (aliased as #to_int): Returns +self+ truncated to an Integer. +# - #to_s (aliased as #inspect): Returns a string containing the place-value +# representation of +self+ in the given radix. +# - #truncate: Returns +self+ truncated to a given precision. +# + + class Float; end diff --git a/numeric.c b/numeric.c index dd0797b9c58d8f..bc0edd6abe91f9 100644 --- a/numeric.c +++ b/numeric.c @@ -906,88 +906,6 @@ num_negative_p(VALUE num) return RBOOL(rb_num_negative_int_p(num)); } - -/******************************************************************** - * - * Document-class: Float - * - * A \Float object represents a sometimes-inexact real number using the native - * architecture's double-precision floating point representation. - * - * Floating point has a different arithmetic and is an inexact number. - * So you should know its esoteric system. See following: - * - * - https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html - * - https://github.com/rdp/ruby_tutorials_core/wiki/Ruby-Talk-FAQ#-why-are-rubys-floats-imprecise - * - https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems - * - * You can create a \Float object explicitly with: - * - * - A {floating-point literal}[rdoc-ref:syntax/literals.rdoc@Float+Literals]. - * - * You can convert certain objects to Floats with: - * - * - Method #Float. - * - * == What's Here - * - * First, what's elsewhere. Class \Float: - * - * - Inherits from - * {class Numeric}[rdoc-ref:Numeric@What-27s+Here] - * and {class Object}[rdoc-ref:Object@What-27s+Here]. - * - Includes {module Comparable}[rdoc-ref:Comparable@What-27s+Here]. - * - * Here, class \Float provides methods for: - * - * - {Querying}[rdoc-ref:Float@Querying] - * - {Comparing}[rdoc-ref:Float@Comparing] - * - {Converting}[rdoc-ref:Float@Converting] - * - * === Querying - * - * - #finite?: Returns whether +self+ is finite. - * - #hash: Returns the integer hash code for +self+. - * - #infinite?: Returns whether +self+ is infinite. - * - #nan?: Returns whether +self+ is a NaN (not-a-number). - * - * === Comparing - * - * - #<: Returns whether +self+ is less than the given value. - * - #<=: Returns whether +self+ is less than or equal to the given value. - * - #<=>: Returns a number indicating whether +self+ is less than, equal - * to, or greater than the given value. - * - #== (aliased as #=== and #eql?): Returns whether +self+ is equal to - * the given value. - * - #>: Returns whether +self+ is greater than the given value. - * - #>=: Returns whether +self+ is greater than or equal to the given value. - * - * === Converting - * - * - #% (aliased as #modulo): Returns +self+ modulo the given value. - * - #*: Returns the product of +self+ and the given value. - * - #**: Returns the value of +self+ raised to the power of the given value. - * - #+: Returns the sum of +self+ and the given value. - * - #-: Returns the difference of +self+ and the given value. - * - #/: Returns the quotient of +self+ and the given value. - * - #ceil: Returns the smallest number greater than or equal to +self+. - * - #coerce: Returns a 2-element array containing the given value converted to a \Float - * and +self+ - * - #divmod: Returns a 2-element array containing the quotient and remainder - * results of dividing +self+ by the given value. - * - #fdiv: Returns the \Float result of dividing +self+ by the given value. - * - #floor: Returns the greatest number smaller than or equal to +self+. - * - #next_float: Returns the next-larger representable \Float. - * - #prev_float: Returns the next-smaller representable \Float. - * - #quo: Returns the quotient from dividing +self+ by the given value. - * - #round: Returns +self+ rounded to the nearest value, to a given precision. - * - #to_i (aliased as #to_int): Returns +self+ truncated to an Integer. - * - #to_s (aliased as #inspect): Returns a string containing the place-value - * representation of +self+ in the given radix. - * - #truncate: Returns +self+ truncated to a given precision. - * - */ - VALUE rb_float_new_in_heap(double d) { From d6107f4ae96ef526918847c566eef8f59cfc0bdd Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 3 Dec 2025 08:11:21 +0900 Subject: [PATCH 1514/2435] [ruby/rubygems] Bump Bundler version to 4.0.0 https://github.com/ruby/rubygems/commit/a55c485226 --- lib/bundler/version.rb | 2 +- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 3a5b5fd86bdabc..8cdc3067366a8e 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0.beta2".freeze + VERSION = "4.0.0".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 6f86f3192ec3a6..92b16c76064450 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0.beta2 + 4.0.0 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 42036fc23d17b4..90090e5fbfdc7e 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0.beta2 + 4.0.0 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index cb2d6b4106f453..4cc6655e43d631 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0.beta2 + 4.0.0 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 5cd23d7ef8bb62..77bdc4b6f90a2a 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.beta2 + 4.0.0 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 20b7d153929917..354a57d660e99b 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0.beta2 + 4.0.0 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 44a2cdcd969d3c..bc103af8605282 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -100,4 +100,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0.beta2 + 4.0.0 From 1af7550114a0401229eda42de24a829611ffacec Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 3 Dec 2025 08:11:22 +0900 Subject: [PATCH 1515/2435] [ruby/rubygems] Bump Rubygems version to 4.0.0 https://github.com/ruby/rubygems/commit/9d744beb56 --- lib/rubygems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 52ccf10159506b..eaeffc7a54d00b 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0.beta2" + VERSION = "4.0.0" end require_relative "rubygems/defaults" From 65cfd5e13beca200e834d92132a3c8aa86578c98 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 3 Dec 2025 02:43:39 +0000 Subject: [PATCH 1516/2435] Update default gems list at 1af7550114a0401229eda42de24a82 [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 18265f7c7cada5..bec7ee9db45036 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,8 +186,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.0.beta2 -* bundler 4.0.0.beta2 +* RubyGems 4.0.0 +* bundler 4.0.0 * date 3.5.0 * digest 3.2.1 * english 0.8.1 From b8a7988478bfa8faff7a6f36e5dd22f6f98872b7 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 2 Dec 2025 16:37:43 -0800 Subject: [PATCH 1517/2435] Avoid leaking fd in uminus_no_embed test --- test/ruby/test_string.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index e2384e15007d2b..50dc5ec2f95875 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3443,9 +3443,11 @@ def test_uminus_no_freeze_not_bare def test_uminus_no_embed_gc pad = "a"*2048 - ("aa".."zz").each do |c| - fstr = -(c + pad).freeze - File.open(IO::NULL, "w").write(fstr) + File.open(IO::NULL, "w") do |dev_null| + ("aa".."zz").each do |c| + fstr = -(c + pad).freeze + dev_null.write(fstr) + end end GC.start end From 4762f429f4c26ce838bd3810dc709d08a207716a Mon Sep 17 00:00:00 2001 From: "B. Burt" <162539390+beeburrt@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:02:06 -0700 Subject: [PATCH 1518/2435] [DOC] typo fix in ruby/file.c --- file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.c b/file.c index 2c8e87b768e4db..0fdccb7d149be4 100644 --- a/file.c +++ b/file.c @@ -2172,7 +2172,7 @@ rb_file_size_p(VALUE obj, VALUE fname) * File.owned?(file_name) -> true or false * * Returns true if the named file exists and the - * effective used id of the calling process is the owner of + * effective user id of the calling process is the owner of * the file. * * _file_name_ can be an IO object. From 8c3909935e2ba9f79bf3492772c77c305a0d370b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 25 Nov 2025 15:06:33 +0100 Subject: [PATCH 1519/2435] Handle NEWOBJ tracepoints settings fields [Bug #21710] - struct.c: `struct_alloc` It is possible for a `NEWOBJ` tracepoint call back to write fields into a newly allocated object before `struct_alloc` had the time to set the `RSTRUCT_GEN_FIELDS` flags and such. Hence we can't blindly initialize the `fields_obj` reference to `0` we first need to check no fields were added yet. - object.c: `rb_class_allocate_instance` Similarly, if a `NEWOBJ` tracepoint tries to set fields on the object, the `shape_id` must already be set, as it's required on T_OBJECT to know where to write fields. `NEWOBJ_OF` had to be refactored to accept a `shape_id`. --- ext/-test-/tracepoint/tracepoint.c | 24 ++++++++++++++++++++++++ gc.c | 15 ++++++++------- internal/gc.h | 12 +++++++----- object.c | 13 ++++++------- shape.c | 4 ++++ shape.h | 14 +++++++++++--- struct.c | 13 +++++++++---- test/-ext-/tracepoint/test_tracepoint.rb | 18 ++++++++++++++++++ 8 files changed, 87 insertions(+), 26 deletions(-) diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c index 001d9513b29fb2..e0bd182d18278b 100644 --- a/ext/-test-/tracepoint/tracepoint.c +++ b/ext/-test-/tracepoint/tracepoint.c @@ -86,6 +86,29 @@ tracepoint_specify_normal_and_internal_events(VALUE self) return Qnil; /* should not be reached */ } +int rb_objspace_internal_object_p(VALUE obj); + +static void +on_newobj_event(VALUE tpval, void *data) +{ + VALUE obj = rb_tracearg_object(rb_tracearg_from_tracepoint(tpval)); + if (RB_TYPE_P(obj, T_STRING)) { + // Would fail !rb_obj_exivar_p(str) assertion in fstring_concurrent_set_create + return; + } + if (!rb_objspace_internal_object_p(obj)) rb_obj_id(obj); +} + +static VALUE +add_object_id(RB_UNUSED_VAR(VALUE _)) +{ + VALUE tp = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, NULL); + rb_tracepoint_enable(tp); + rb_yield(Qnil); + rb_tracepoint_disable(tp); + return Qnil; +} + void Init_gc_hook(VALUE); void @@ -95,4 +118,5 @@ Init_tracepoint(void) Init_gc_hook(tp_mBug); rb_define_module_function(tp_mBug, "tracepoint_track_objspace_events", tracepoint_track_objspace_events, 0); rb_define_module_function(tp_mBug, "tracepoint_specify_normal_and_internal_events", tracepoint_specify_normal_and_internal_events, 0); + rb_define_singleton_method(tp_mBug, "tracepoint_add_object_id", add_object_id, 0); } diff --git a/gc.c b/gc.c index 97b7362c9fbb85..557a3cbff4017d 100644 --- a/gc.c +++ b/gc.c @@ -991,9 +991,10 @@ gc_validate_pc(VALUE obj) } static inline VALUE -newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t size) +newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t size) { VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size); + RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); gc_validate_pc(obj); @@ -1032,17 +1033,17 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, bool wb_protected, size_t s } VALUE -rb_wb_unprotected_newobj_of(VALUE klass, VALUE flags, size_t size) +rb_wb_unprotected_newobj_of(VALUE klass, VALUE flags, shape_id_t shape_id, size_t size) { GC_ASSERT((flags & FL_WB_PROTECTED) == 0); - return newobj_of(GET_RACTOR(), klass, flags, FALSE, size); + return newobj_of(GET_RACTOR(), klass, flags, shape_id, FALSE, size); } VALUE -rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size) +rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape_id, size_t size) { GC_ASSERT((flags & FL_WB_PROTECTED) == 0); - return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, TRUE, size); + return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, shape_id, TRUE, size); } #define UNEXPECTED_NODE(func) \ @@ -1063,7 +1064,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU { RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); if (klass) rb_data_object_check(klass); - VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA, !dmark, sizeof(struct RTypedData)); + VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA, ROOT_SHAPE_ID, !dmark, sizeof(struct RTypedData)); struct RData *data = (struct RData *)obj; data->dmark = dmark; @@ -1087,7 +1088,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; - VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, wb_protected, size); + VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, ROOT_SHAPE_ID, wb_protected, size); struct RTypedData *data = (struct RTypedData *)obj; data->fields_obj = 0; diff --git a/internal/gc.h b/internal/gc.h index ec408d7fac53b9..ea001449a0030c 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -122,10 +122,12 @@ const char *rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) struct rb_execution_context_struct; /* in vm_core.h */ struct rb_objspace; /* in vm_core.h */ -#define NEWOBJ_OF(var, T, c, f, s, ec) \ +#define NEWOBJ_OF_WITH_SHAPE(var, T, c, f, shape_id, s, ec) \ T *(var) = (T *)(((f) & FL_WB_PROTECTED) ? \ - rb_wb_protected_newobj_of((ec ? ec : GET_EC()), (c), (f) & ~FL_WB_PROTECTED, s) : \ - rb_wb_unprotected_newobj_of((c), (f), s)) + rb_wb_protected_newobj_of((ec ? ec : GET_EC()), (c), (f) & ~FL_WB_PROTECTED, shape_id, s) : \ + rb_wb_unprotected_newobj_of((c), (f), shape_id, s)) + +#define NEWOBJ_OF(var, T, c, f, s, ec) NEWOBJ_OF_WITH_SHAPE(var, T, c, f, 0 /* ROOT_SHAPE_ID */, s, ec) #ifndef RB_GC_OBJECT_METADATA_ENTRY_DEFINED # define RB_GC_OBJECT_METADATA_ENTRY_DEFINED @@ -248,8 +250,8 @@ VALUE rb_gc_disable_no_rest(void); /* gc.c (export) */ const char *rb_objspace_data_type_name(VALUE obj); -VALUE rb_wb_protected_newobj_of(struct rb_execution_context_struct *, VALUE, VALUE, size_t); -VALUE rb_wb_unprotected_newobj_of(VALUE, VALUE, size_t); +VALUE rb_wb_protected_newobj_of(struct rb_execution_context_struct *, VALUE, VALUE, uint32_t /* shape_id_t */, size_t); +VALUE rb_wb_unprotected_newobj_of(VALUE, VALUE, uint32_t /* shape_id_t */, size_t); size_t rb_obj_memsize_of(VALUE); struct rb_gc_object_metadata_entry *rb_gc_object_metadata(VALUE obj); void rb_gc_mark_values(long n, const VALUE *values); diff --git a/object.c b/object.c index e960f1855b0c80..bcafab3c3d4bf0 100644 --- a/object.c +++ b/object.c @@ -124,18 +124,17 @@ rb_class_allocate_instance(VALUE klass) size = sizeof(struct RObject); } - NEWOBJ_OF(o, struct RObject, klass, - T_OBJECT | (RGENGC_WB_PROTECTED_OBJECT ? FL_WB_PROTECTED : 0), size, 0); + // There might be a NEWOBJ tracepoint callback, and it may set fields. + // So the shape must be passed to `NEWOBJ_OF`. + VALUE flags = T_OBJECT | (RGENGC_WB_PROTECTED_OBJECT ? FL_WB_PROTECTED : 0); + NEWOBJ_OF_WITH_SHAPE(o, struct RObject, klass, flags, rb_shape_root(rb_gc_heap_id_for_size(size)), size, 0); VALUE obj = (VALUE)o; - RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(obj), SHAPE_ROOT)); - - RBASIC_SET_SHAPE_ID(obj, rb_shape_root(rb_gc_heap_id_for_size(size))); - #if RUBY_DEBUG RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); VALUE *ptr = ROBJECT_FIELDS(obj); - for (size_t i = 0; i < ROBJECT_FIELDS_CAPACITY(obj); i++) { + size_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + for (size_t i = fields_count; i < ROBJECT_FIELDS_CAPACITY(obj); i++) { ptr[i] = Qundef; } if (rb_obj_class(obj) != rb_class_real(klass)) { diff --git a/shape.c b/shape.c index 7acfe72930a09f..754be1cfd64e65 100644 --- a/shape.c +++ b/shape.c @@ -1240,6 +1240,10 @@ rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_ bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) { + if (shape_id == ROOT_SHAPE_ID) { + return true; + } + if (shape_id == INVALID_SHAPE_ID) { rb_bug("Can't set INVALID_SHAPE_ID on an object"); } diff --git a/shape.h b/shape.h index d9cfe48759c333..b0bb4db0bfce1b 100644 --- a/shape.h +++ b/shape.h @@ -163,10 +163,8 @@ bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id); #endif static inline void -RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) +RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) { - RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); - RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); #if RBASIC_SHAPE_ID_FIELD RBASIC(obj)->shape_id = (VALUE)shape_id; #else @@ -174,6 +172,16 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) RBASIC(obj)->flags &= SHAPE_FLAG_MASK; RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); #endif +} + +static inline void +RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) +{ + RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); + + RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); + RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id)); } diff --git a/struct.c b/struct.c index 9da9bbdfe369c8..a6155d4684249f 100644 --- a/struct.c +++ b/struct.c @@ -819,12 +819,17 @@ struct_alloc(VALUE klass) if (n > 0 && rb_gc_size_allocatable_p(embedded_size)) { flags |= n << RSTRUCT_EMBED_LEN_SHIFT; + if (RCLASS_MAX_IV_COUNT(klass) == 0) { + // We set the flag before calling `NEWOBJ_OF` in case a NEWOBJ tracepoint does + // attempt to write fields. We'll remove it later if no fields was written to. + flags |= RSTRUCT_GEN_FIELDS; + } NEWOBJ_OF(st, struct RStruct, klass, flags, embedded_size, 0); - if (RCLASS_MAX_IV_COUNT(klass) == 0 && embedded_size == rb_gc_obj_slot_size((VALUE)st)) { - FL_SET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); - } - else { + if (RCLASS_MAX_IV_COUNT(klass) == 0 + && !rb_shape_obj_has_fields((VALUE)st) + && embedded_size < rb_gc_obj_slot_size((VALUE)st)) { + FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); } rb_mem_clear((VALUE *)st->as.ary, n); diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb index debddd83d043fe..2256f58bc710b0 100644 --- a/test/-ext-/tracepoint/test_tracepoint.rb +++ b/test/-ext-/tracepoint/test_tracepoint.rb @@ -82,6 +82,24 @@ def run(hook) end end + def test_tracepoint_add_object_id + Bug.tracepoint_add_object_id do + klass = Struct.new + 2.times { klass.new } + + klass = Struct.new(:a) + 2.times { klass.new } + + klass = Struct.new(:a, :b, :c) + 2.times { klass.new } + + 2.times { Set.new } # To test T_DATA / TypedData RUBY_TYPED_EMBEDDABLE + 2.times { Proc.new { } } # To test T_DATA / TypedData non embeddable + + 2.times { Object.new } + end + end + def test_teardown_with_active_GC_end_hook assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start') end From 706d80830b9f0a5a2eac66251d1417abb2ff143c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 3 Dec 2025 17:38:23 +0900 Subject: [PATCH 1520/2435] [ruby/rubygems] Silence Bundler UI in plugin installer specs https://github.com/ruby/rubygems/commit/90a0af8204 --- spec/bundler/bundler/plugin/installer_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb index 8e1879395a6320..c200a98afaf264 100644 --- a/spec/bundler/bundler/plugin/installer_spec.rb +++ b/spec/bundler/bundler/plugin/installer_spec.rb @@ -47,6 +47,13 @@ build_plugin "re-plugin" build_plugin "ma-plugin" end + + @previous_ui = Bundler.ui + Bundler.ui = Bundler::UI::Silent.new + end + + after do + Bundler.ui = @previous_ui end context "git plugins" do From 4d377c8c2eedc42f274b3b2a841fa24d5b4c5541 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 14:41:35 +0100 Subject: [PATCH 1521/2435] [ruby/json] Improve `JSON.load` and `JSON.unsafe_load` to allow passing options as second argument Otherwise it's very error prone. https://github.com/ruby/json/commit/c54de70f90 --- ext/json/lib/json/common.rb | 21 +++++++++++++++++++-- test/json/json_common_interface_test.rb | 7 +++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 233b8c7e62d628..fcdc0d9f0be3e1 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -550,6 +550,7 @@ def pretty_generate(obj, opts = nil) :create_additions => nil, } # :call-seq: + # JSON.unsafe_load(source, options = {}) -> object # JSON.unsafe_load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. @@ -681,7 +682,12 @@ def pretty_generate(obj, opts = nil) # def unsafe_load(source, proc = nil, options = nil) opts = if options.nil? - _unsafe_load_default_options + if proc && proc.is_a?(Hash) + options, proc = proc, nil + options + else + _unsafe_load_default_options + end else _unsafe_load_default_options.merge(options) end @@ -709,6 +715,7 @@ def unsafe_load(source, proc = nil, options = nil) end # :call-seq: + # JSON.load(source, options = {}) -> object # JSON.load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. @@ -845,8 +852,18 @@ def unsafe_load(source, proc = nil, options = nil) # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>} # def load(source, proc = nil, options = nil) + if proc && options.nil? && proc.is_a?(Hash) + options = proc + proc = nil + end + opts = if options.nil? - _load_default_options + if proc && proc.is_a?(Hash) + options, proc = proc, nil + options + else + _load_default_options + end else _load_default_options.merge(options) end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 37fa439575cd87..13e2ca062a92bf 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -149,6 +149,7 @@ def test_load_with_proc def test_load_with_options json = '{ "foo": NaN }' assert JSON.load(json, nil, :allow_nan => true)['foo'].nan? + assert JSON.load(json, :allow_nan => true)['foo'].nan? end def test_load_null @@ -215,6 +216,12 @@ def test_unsafe_load_with_proc assert_equal expected, visited end + def test_unsafe_load_with_options + json = '{ "foo": NaN }' + assert JSON.unsafe_load(json, nil, :allow_nan => true)['foo'].nan? + assert JSON.unsafe_load(json, :allow_nan => true)['foo'].nan? + end + def test_unsafe_load_default_options too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' assert JSON.unsafe_load(too_deep, nil).is_a?(Array) From 54a73a57a292d8d7e88cadd3fd8454a3084a60e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 27 Nov 2025 16:12:17 +0100 Subject: [PATCH 1522/2435] [ruby/json] Test and restore behavior around to_json changing depth When serializing an Array, and one of the elements of the Array requires calling `to_json`, if the depth is changed, it will be used for the next entries, which wasn't the case before https://github.com/ruby/json/commit/5abd43490714, and is not the case with TruffleRuby and JRuby. Additionally, with TruffleRuby and JRuby the state's depth after the `to_json` call is used to close the Array, which isn't the case with CRuby. https://github.com/ruby/json/commit/386b36fde5 --- ext/json/generator/generator.c | 2 ++ test/json/json_generator_test.rb | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 35d543a7a768fa..9e6e617a59a7a8 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1294,6 +1294,8 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d VALUE tmp; if (rb_respond_to(obj, i_to_json)) { tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data)); + GET_STATE(data->vstate); + data->depth = state->depth; Check_Type(tmp, T_STRING); fbuffer_append_str(buffer, tmp); } else { diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 9600f4be8d15d0..ab3a6807d65fec 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -321,6 +321,30 @@ def test_allow_nan end end + def test_depth_bad_to_json + obj = Object.new + def obj.to_json(state) + state.depth += 1 + "{#{state.object_nl}"\ + "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\ + "#{state.indent * (state.depth - 1)}}" + end + indent = " " * 2 if RUBY_ENGINE != "ruby" + assert_equal <<~JSON.chomp, JSON.pretty_generate([obj] * 2) + [ + { + "foo": 1 + }, + { + "foo": 1 + } + #{indent}] + JSON + state = JSON::State.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ") + state.generate(obj) + assert_equal 1, state.depth + end + def test_depth pretty = { object_nl: "\n", array_nl: "\n", space: " ", indent: " " } state = JSON.state.new(**pretty) From 32c7c3c19aa7c9c3fda10a9520d29e244baeaa6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 28 Nov 2025 15:53:58 +0100 Subject: [PATCH 1523/2435] [ruby/json] Reproduce C ext behavior of ignoring mutated depth in arrays https://github.com/ruby/json/commit/e0257b9f82 --- test/json/json_generator_test.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index ab3a6807d65fec..e623e05409ee6c 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -329,7 +329,6 @@ def obj.to_json(state) "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\ "#{state.indent * (state.depth - 1)}}" end - indent = " " * 2 if RUBY_ENGINE != "ruby" assert_equal <<~JSON.chomp, JSON.pretty_generate([obj] * 2) [ { @@ -338,11 +337,14 @@ def obj.to_json(state) { "foo": 1 } - #{indent}] + ] JSON state = JSON::State.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ") state.generate(obj) - assert_equal 1, state.depth + assert_equal 1, state.depth # FIXME + state.depth = 0 + state.generate([obj]) + assert_equal 0, state.depth end def test_depth From 05383a1de2f2afe263ab894e851eca51e40bb543 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 15:13:00 +0100 Subject: [PATCH 1524/2435] [ruby/json] Fix duplicated test_unsafe_load_with_options test case https://github.com/ruby/json/commit/7b62fac525 --- test/json/json_common_interface_test.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 13e2ca062a92bf..3dfd0623cd98bc 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -216,12 +216,6 @@ def test_unsafe_load_with_proc assert_equal expected, visited end - def test_unsafe_load_with_options - json = '{ "foo": NaN }' - assert JSON.unsafe_load(json, nil, :allow_nan => true)['foo'].nan? - assert JSON.unsafe_load(json, :allow_nan => true)['foo'].nan? - end - def test_unsafe_load_default_options too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' assert JSON.unsafe_load(too_deep, nil).is_a?(Array) @@ -237,6 +231,7 @@ def test_unsafe_load_with_options assert_raise(JSON::ParserError) { JSON.unsafe_load(nan_json, nil, :allow_nan => false)['foo'].nan? } # make sure it still uses the defaults when something is provided assert JSON.unsafe_load(nan_json, nil, :allow_blank => true)['foo'].nan? + assert JSON.unsafe_load(nan_json, :allow_nan => true)['foo'].nan? end def test_unsafe_load_null From 5770c186d1e9d8e7202c83763c9619faa1f4c97c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 10:42:50 +0100 Subject: [PATCH 1525/2435] Rename `rb_obj_exivar_p` -> `rb_obj_gen_fields_p` The "EXIVAR" terminology has been replaced by "gen fields" AKA "generic fields". Exivar implies variable, but generic fields include more than just variables, e.g. `object_id`. --- ext/-test-/tracepoint/tracepoint.c | 2 +- gc.c | 4 ++-- hash.c | 4 ++-- ractor.c | 6 +++--- shape.h | 2 +- string.c | 2 +- variable.c | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c index e0bd182d18278b..03887b1d5b6549 100644 --- a/ext/-test-/tracepoint/tracepoint.c +++ b/ext/-test-/tracepoint/tracepoint.c @@ -93,7 +93,7 @@ on_newobj_event(VALUE tpval, void *data) { VALUE obj = rb_tracearg_object(rb_tracearg_from_tracepoint(tpval)); if (RB_TYPE_P(obj, T_STRING)) { - // Would fail !rb_obj_exivar_p(str) assertion in fstring_concurrent_set_create + // Would fail !rb_obj_gen_fields_p(str) assertion in fstring_concurrent_set_create return; } if (!rb_objspace_internal_object_p(obj)) rb_obj_id(obj); diff --git a/gc.c b/gc.c index 557a3cbff4017d..9ccaffdc0b371c 100644 --- a/gc.c +++ b/gc.c @@ -2061,7 +2061,7 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) { obj_free_object_id(obj); - if (rb_obj_exivar_p(obj)) { + if (rb_obj_gen_fields_p(obj)) { rb_free_generic_ivar(obj); } @@ -3116,7 +3116,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) { struct gc_mark_classext_foreach_arg foreach_args; - if (rb_obj_exivar_p(obj)) { + if (rb_obj_gen_fields_p(obj)) { rb_mark_generic_ivar(obj); } diff --git a/hash.c b/hash.c index 0b98b68d169b44..ac9a71794c8430 100644 --- a/hash.c +++ b/hash.c @@ -1554,7 +1554,7 @@ rb_hash_dup(VALUE hash) const VALUE flags = RBASIC(hash)->flags; VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT); - if (rb_obj_exivar_p(hash)) { + if (rb_obj_gen_fields_p(hash)) { rb_copy_generic_ivar(ret, hash); } return ret; @@ -2876,7 +2876,7 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing) VALUE rb_hash_key_str(VALUE key) { - if (!rb_obj_exivar_p(key) && RBASIC_CLASS(key) == rb_cString) { + if (!rb_obj_gen_fields_p(key) && RBASIC_CLASS(key) == rb_cString) { return rb_fstring(key); } else { diff --git a/ractor.c b/ractor.c index 8238d9a456e233..ea09583c5f7f7c 100644 --- a/ractor.c +++ b/ractor.c @@ -1137,7 +1137,7 @@ rb_obj_set_shareable_no_assert(VALUE obj) { FL_SET_RAW(obj, FL_SHAREABLE); - if (rb_obj_exivar_p(obj)) { + if (rb_obj_gen_fields_p(obj)) { VALUE fields = rb_obj_fields_no_ractor_check(obj); if (imemo_type_p(fields, imemo_fields)) { // no recursive mark @@ -1750,7 +1750,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) else if (data->replacement != _val) { RB_OBJ_WRITE(parent_obj, &v, data->replacement); } \ } while (0) - if (UNLIKELY(rb_obj_exivar_p(obj))) { + if (UNLIKELY(rb_obj_gen_fields_p(obj))) { VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) { @@ -1984,7 +1984,7 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data) rb_gc_writebarrier_remember(data->replacement); void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c - if (UNLIKELY(rb_obj_exivar_p(obj))) { + if (UNLIKELY(rb_obj_gen_fields_p(obj))) { rb_replace_generic_ivar(data->replacement, obj); } diff --git a/shape.h b/shape.h index b0bb4db0bfce1b..9478d4b3a95dc6 100644 --- a/shape.h +++ b/shape.h @@ -437,7 +437,7 @@ rb_shape_obj_has_fields(VALUE obj) } static inline bool -rb_obj_exivar_p(VALUE obj) +rb_obj_gen_fields_p(VALUE obj) { switch (TYPE(obj)) { case T_NONE: diff --git a/string.c b/string.c index c794b36748e6e6..56c83ca2d53cd6 100644 --- a/string.c +++ b/string.c @@ -549,7 +549,7 @@ fstring_concurrent_set_create(VALUE str, void *data) RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(str)); RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); - RUBY_ASSERT(!rb_obj_exivar_p(str)); + RUBY_ASSERT(!rb_obj_gen_fields_p(str)); RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); diff --git a/variable.c b/variable.c index d4c5d91e25d6dc..9a7e11850dcef5 100644 --- a/variable.c +++ b/variable.c @@ -1291,7 +1291,7 @@ rb_obj_fields(VALUE obj, ID field_name) void rb_free_generic_ivar(VALUE obj) { - if (rb_obj_exivar_p(obj)) { + if (rb_obj_gen_fields_p(obj)) { st_data_t key = (st_data_t)obj, value; switch (BUILTIN_TYPE(obj)) { case T_DATA: @@ -2218,7 +2218,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) rb_check_frozen(dest); - if (!rb_obj_exivar_p(obj)) { + if (!rb_obj_gen_fields_p(obj)) { return; } From b78db63be4d078b7ac29c8e9fcb40cb20d232265 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 10:47:28 +0100 Subject: [PATCH 1526/2435] fstring_concurrent_set_create: only assert the string has no ivars The NEWOBJ tracepoint can generate an object_id, that's alright, what we don't want is actual instance variables. --- ext/-test-/tracepoint/tracepoint.c | 4 ---- string.c | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c index 03887b1d5b6549..7f7aa246628858 100644 --- a/ext/-test-/tracepoint/tracepoint.c +++ b/ext/-test-/tracepoint/tracepoint.c @@ -92,10 +92,6 @@ static void on_newobj_event(VALUE tpval, void *data) { VALUE obj = rb_tracearg_object(rb_tracearg_from_tracepoint(tpval)); - if (RB_TYPE_P(obj, T_STRING)) { - // Would fail !rb_obj_gen_fields_p(str) assertion in fstring_concurrent_set_create - return; - } if (!rb_objspace_internal_object_p(obj)) rb_obj_id(obj); } diff --git a/string.c b/string.c index 56c83ca2d53cd6..0370fc5d1e02e6 100644 --- a/string.c +++ b/string.c @@ -549,7 +549,7 @@ fstring_concurrent_set_create(VALUE str, void *data) RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(str)); RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); - RUBY_ASSERT(!rb_obj_gen_fields_p(str)); + RUBY_ASSERT(!rb_shape_obj_has_ivars(str)); RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); From 208271e3723653cd4cb9cd2eb4a6c631eee0b09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Wed, 3 Dec 2025 15:43:28 +0100 Subject: [PATCH 1527/2435] [ruby/json] Fix handling of depth https://github.com/ruby/json/commit/ccca602274 --- ext/json/generator/generator.c | 50 +++------------------ ext/json/lib/json/common.rb | 2 +- test/json/json_generator_test.rb | 77 ++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 57 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 9e6e617a59a7a8..d202e97ea18156 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -968,14 +968,16 @@ static void vstate_spill(struct generate_json_data *data) RB_OBJ_WRITTEN(vstate, Qundef, state->as_json); } -static inline VALUE vstate_get(struct generate_json_data *data) +static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj) { if (RB_UNLIKELY(!data->vstate)) { vstate_spill(data); } GET_STATE(data->vstate); state->depth = data->depth; - return data->vstate; + VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate); + // no need to restore state->depth, vstate is just a temporary State + return tmp; } static VALUE @@ -1293,9 +1295,7 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d { VALUE tmp; if (rb_respond_to(obj, i_to_json)) { - tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data)); - GET_STATE(data->vstate); - data->depth = state->depth; + tmp = json_call_to_json(data, obj); Check_Type(tmp, T_STRING); fbuffer_append_str(buffer, tmp); } else { @@ -1477,16 +1477,6 @@ static VALUE generate_json_try(VALUE d) return fbuffer_finalize(data->buffer); } -// Preserves the deprecated behavior of State#depth being set. -static VALUE generate_json_ensure_deprecated(VALUE d) -{ - struct generate_json_data *data = (struct generate_json_data *)d; - fbuffer_free(data->buffer); - data->state->depth = data->depth; - - return Qundef; -} - static VALUE generate_json_ensure(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; @@ -1507,13 +1497,13 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, struct generate_json_data data = { .buffer = &buffer, - .vstate = self, + .vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json .state = state, .depth = state->depth, .obj = obj, .func = func }; - return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure_deprecated, (VALUE)&data); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* call-seq: @@ -1532,31 +1522,6 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) return cState_partial_generate(self, obj, generate_json, io); } -static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 1, 2); - VALUE obj = argv[0]; - VALUE io = argc > 1 ? argv[1] : Qnil; - - GET_STATE(self); - - char stack_buffer[FBUFFER_STACK_SIZE]; - FBuffer buffer = { - .io = RTEST(io) ? io : Qfalse, - }; - fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); - - struct generate_json_data data = { - .buffer = &buffer, - .vstate = Qfalse, - .state = state, - .depth = state->depth, - .obj = obj, - .func = generate_json - }; - return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); -} - static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) { rb_warn("The json gem extension was loaded with the stdlib ruby code. You should upgrade rubygems with `gem update --system`"); @@ -2145,7 +2110,6 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); - rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc: rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index fcdc0d9f0be3e1..877b96814e8bca 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -1074,7 +1074,7 @@ def initialize(options = nil, &as_json) # # Serialize the given object into a \JSON document. def dump(object, io = nil) - @state.generate_new(object, io) + @state.generate(object, io) end alias_method :generate, :dump diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index e623e05409ee6c..9f8b35de093271 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -321,7 +321,8 @@ def test_allow_nan end end - def test_depth_bad_to_json + # An object that changes state.depth when it receives to_json(state) + def bad_to_json obj = Object.new def obj.to_json(state) state.depth += 1 @@ -329,21 +330,44 @@ def obj.to_json(state) "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\ "#{state.indent * (state.depth - 1)}}" end - assert_equal <<~JSON.chomp, JSON.pretty_generate([obj] * 2) + obj + end + + def test_depth_restored_bad_to_json + state = JSON::State.new + state.generate(bad_to_json) + assert_equal 0, state.depth + end + + def test_depth_restored_bad_to_json_in_Array + assert_equal <<~JSON.chomp, JSON.pretty_generate([bad_to_json] * 2) [ { "foo": 1 }, { - "foo": 1 - } + "foo": 1 + } ] JSON - state = JSON::State.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ") - state.generate(obj) - assert_equal 1, state.depth # FIXME - state.depth = 0 - state.generate([obj]) + state = JSON::State.new + state.generate([bad_to_json]) + assert_equal 0, state.depth + end + + def test_depth_restored_bad_to_json_in_Hash + assert_equal <<~JSON.chomp, JSON.pretty_generate(a: bad_to_json, b: bad_to_json) + { + "a": { + "foo": 1 + }, + "b": { + "foo": 1 + } + } + JSON + state = JSON::State.new + state.generate(a: bad_to_json) assert_equal 0, state.depth end @@ -361,10 +385,36 @@ def test_depth_nesting_error ary = []; ary << ary assert_raise(JSON::NestingError) { generate(ary) } assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) } - s = JSON.state.new - assert_equal 0, s.depth + end + + def test_depth_nesting_error_to_json + ary = []; ary << ary + s = JSON.state.new(depth: 1) assert_raise(JSON::NestingError) { ary.to_json(s) } - assert_equal 100, s.depth + assert_equal 1, s.depth + end + + def test_depth_nesting_error_Hash_to_json + hash = {}; hash[:a] = hash + s = JSON.state.new(depth: 1) + assert_raise(JSON::NestingError) { hash.to_json(s) } + assert_equal 1, s.depth + end + + def test_depth_nesting_error_generate + ary = []; ary << ary + s = JSON.state.new(depth: 1) + assert_raise(JSON::NestingError) { s.generate(ary) } + assert_equal 1, s.depth + end + + def test_depth_exception_calling_to_json + def (obj = Object.new).to_json(*) + raise + end + s = JSON.state.new(depth: 1).freeze + assert_raise(RuntimeError) { s.generate([{ hash: obj }]) } + assert_equal 1, s.depth end def test_buffer_initial_length @@ -1006,7 +1056,8 @@ def test_nesting_recovery state = JSON::State.new ary = [] ary << ary - assert_raise(JSON::NestingError) { state.generate_new(ary) } + assert_raise(JSON::NestingError) { state.generate(ary) } + assert_equal 0, state.depth assert_equal '{"a":1}', state.generate({ a: 1 }) end end From 94581b1ffde5e2afeba4631152955c18ec52ccf0 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 16:23:14 +0100 Subject: [PATCH 1528/2435] [ruby/json] Release 2.17.0 https://github.com/ruby/json/commit/4bdb2d14fe --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index cc25a0453e20c9..b7de7c27e21446 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.16.0' + VERSION = '2.17.0' end From 20fc8aff05ce857f3d3b759d92f1941132398b65 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 3 Dec 2025 15:27:19 +0000 Subject: [PATCH 1529/2435] Update default gems list at 94581b1ffde5e2afeba4631152955c [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index bec7ee9db45036..f3e7f1ae0e3a6e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -198,7 +198,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.4.0.dev -* json 2.16.0 +* json 2.17.0 * net-http 0.8.0 * openssl 4.0.0.pre * optparse 0.8.0 From d7dffcdbeeee81bb3bbe63b86620cb682eb3ab23 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 3 Dec 2025 15:41:23 +0100 Subject: [PATCH 1530/2435] [ruby/prism] Follow repo move from oracle/truffleruby to truffleruby/truffleruby https://github.com/ruby/prism/commit/c8e1b11120 --- prism/prism.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.h b/prism/prism.h index dc31f26e786a5c..c468db18bef3c2 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -314,7 +314,7 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * dependencies. It is currently being integrated into * [CRuby](https://github.com/ruby/ruby), * [JRuby](https://github.com/jruby/jruby), - * [TruffleRuby](https://github.com/oracle/truffleruby), + * [TruffleRuby](https://github.com/truffleruby/truffleruby), * [Sorbet](https://github.com/sorbet/sorbet), and * [Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree). * From fcf3939780972d587b18afc26c4abd2da2c0b7ec Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 14:50:35 +0100 Subject: [PATCH 1531/2435] Speedup TypedData_Get_Struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While profiling `Monitor#synchronize` and `Mutex#synchronize` I noticed a fairly significant amount of time spent in `rb_check_typeddata`. By implementing a fast path that assumes the object is valid and that can be inlined, it does make a significant difference: Before: ``` Mutex 13.548M (± 3.6%) i/s (73.81 ns/i) - 68.566M in 5.067444 Monitor 10.497M (± 6.5%) i/s (95.27 ns/i) - 52.529M in 5.032698s ``` After: ``` Mutex 20.887M (± 0.3%) i/s (47.88 ns/i) - 106.021M in 5.075989s Monitor 16.245M (±13.3%) i/s (61.56 ns/i) - 80.705M in 5.099680s ``` ```ruby require 'bundler/inline' gemfile do gem "benchmark-ips" end mutex = Mutex.new require "monitor" monitor = Monitor.new Benchmark.ips do |x| x.report("Mutex") { mutex.synchronize { } } x.report("Monitor") { monitor.synchronize { } } end ``` --- include/ruby/internal/core/rtypeddata.h | 45 ++++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 24e87e63f979f9..aaf8f7997c8459 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -507,19 +507,6 @@ RBIMPL_SYMBOL_EXPORT_END() sizeof(type)) #endif -/** - * Obtains a C struct from inside of a wrapper Ruby object. - * - * @param obj An instance of ::RTypedData. - * @param type Type name of the C struct. - * @param data_type The data type describing `type`. - * @param sval Variable name of obtained C struct. - * @exception rb_eTypeError `obj` is not a kind of `data_type`. - * @return Unwrapped C struct that `obj` holds. - */ -#define TypedData_Get_Struct(obj,type,data_type,sval) \ - ((sval) = RBIMPL_CAST((type *)rb_check_typeddata((obj), (data_type)))) - static inline bool RTYPEDDATA_EMBEDDED_P(VALUE obj) { @@ -614,6 +601,38 @@ RTYPEDDATA_TYPE(VALUE obj) return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK); } +RBIMPL_ATTR_PURE_UNLESS_DEBUG() +RBIMPL_ATTR_ARTIFICIAL() +/** + * @private + * + * This is an implementation detail of TypedData_Get_Struct(). Don't use it + * directly. + */ +static inline void * +rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *type) +{ + if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == type)) { + return RTYPEDDATA_GET_DATA(obj); + } + + return rb_check_typeddata(obj, type); +} + + +/** + * Obtains a C struct from inside of a wrapper Ruby object. + * + * @param obj An instance of ::RTypedData. + * @param type Type name of the C struct. + * @param data_type The data type describing `type`. + * @param sval Variable name of obtained C struct. + * @exception rb_eTypeError `obj` is not a kind of `data_type`. + * @return Unwrapped C struct that `obj` holds. + */ +#define TypedData_Get_Struct(obj,type,data_type,sval) \ + ((sval) = RBIMPL_CAST((type *)rbimpl_check_typeddata((obj), (data_type)))) + /** * While we don't stop you from using this function, it seems to be an * implementation detail of #TypedData_Make_Struct, which is preferred over From f9cd94f17d6fef49f1ee5cbb8f66839f0d7a5db9 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 1 Dec 2025 15:15:42 -0800 Subject: [PATCH 1532/2435] wb-protect autoload_const --- variable.c | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/variable.c b/variable.c index 9a7e11850dcef5..faeffec660a9f2 100644 --- a/variable.c +++ b/variable.c @@ -2734,7 +2734,7 @@ autoload_const_free(void *ptr) static const rb_data_type_t autoload_const_type = { "autoload_const", {autoload_const_mark_and_move, autoload_const_free, autoload_const_memsize, autoload_const_mark_and_move,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; static struct autoload_data * @@ -2778,12 +2778,12 @@ autoload_copy_table_for_box_i(st_data_t key, st_data_t value, st_data_t arg) struct autoload_data *autoload_data = rb_check_typeddata(autoload_data_value, &autoload_data_type); VALUE new_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->box_value = rb_get_box_object((rb_box_t *)box); - autoload_const->module = src_const->module; + RB_OBJ_WRITE(new_value, &autoload_const->box_value, rb_get_box_object((rb_box_t *)box)); + RB_OBJ_WRITE(new_value, &autoload_const->module, src_const->module); autoload_const->name = src_const->name; - autoload_const->value = src_const->value; + RB_OBJ_WRITE(new_value, &autoload_const->value, src_const->value); autoload_const->flag = src_const->flag; - autoload_const->autoload_data_value = autoload_data_value; + RB_OBJ_WRITE(new_value, &autoload_const->autoload_data_value, autoload_data_value); ccan_list_add_tail(&autoload_data->constants, &autoload_const->cnode); st_insert(tbl, (st_data_t)autoload_const->name, (st_data_t)new_value); @@ -2907,12 +2907,12 @@ autoload_synchronized(VALUE _arguments) { struct autoload_const *autoload_const; VALUE autoload_const_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const); - autoload_const->box_value = arguments->box_value; - autoload_const->module = arguments->module; + RB_OBJ_WRITE(autoload_const_value, &autoload_const->box_value, arguments->box_value); + RB_OBJ_WRITE(autoload_const_value, &autoload_const->module, arguments->module); autoload_const->name = arguments->name; autoload_const->value = Qundef; autoload_const->flag = CONST_PUBLIC; - autoload_const->autoload_data_value = autoload_data_value; + RB_OBJ_WRITE(autoload_const_value, &autoload_const->autoload_data_value, autoload_data_value); ccan_list_add_tail(&autoload_data->constants, &autoload_const->cnode); st_insert(autoload_table, (st_data_t)arguments->name, (st_data_t)autoload_const_value); RB_OBJ_WRITTEN(autoload_table_value, Qundef, autoload_const_value); @@ -3920,21 +3920,21 @@ rb_const_set(VALUE klass, ID id, VALUE val) const_added(klass, id); } -static struct autoload_data * -autoload_data_for_named_constant(VALUE module, ID name, struct autoload_const **autoload_const_pointer) +static VALUE +autoload_const_value_for_named_constant(VALUE module, ID name, struct autoload_const **autoload_const_pointer) { - VALUE autoload_data_value = autoload_data(module, name); - if (!autoload_data_value) return 0; + VALUE autoload_const_value = autoload_data(module, name); + if (!autoload_const_value) return Qfalse; - struct autoload_data *autoload_data = get_autoload_data(autoload_data_value, autoload_const_pointer); - if (!autoload_data) return 0; + struct autoload_data *autoload_data = get_autoload_data(autoload_const_value, autoload_const_pointer); + if (!autoload_data) return Qfalse; /* for autoloading thread, keep the defined value to autoloading storage */ if (autoload_by_current(autoload_data)) { - return autoload_data; + return autoload_const_value; } - return 0; + return Qfalse; } static void @@ -3954,13 +3954,13 @@ const_tbl_update(struct autoload_const *ac, int autoload_force) RUBY_ASSERT_CRITICAL_SECTION_ENTER(); VALUE file = ac->file; int line = ac->line; - struct autoload_data *ele = autoload_data_for_named_constant(klass, id, &ac); + VALUE autoload_const_value = autoload_const_value_for_named_constant(klass, id, &ac); - if (!autoload_force && ele) { + if (!autoload_force && autoload_const_value) { rb_clear_constant_cache_for_id(id); - ac->value = val; /* autoload_data is non-WB-protected */ - ac->file = rb_source_location(&ac->line); + RB_OBJ_WRITE(autoload_const_value, &ac->value, val); + RB_OBJ_WRITE(autoload_const_value, &ac->file, rb_source_location(&ac->line)); } else { /* otherwise autoloaded constant, allow to override */ @@ -4054,10 +4054,7 @@ set_const_visibility(VALUE mod, int argc, const VALUE *argv, ce->flag &= ~mask; ce->flag |= flag; if (UNDEF_P(ce->value)) { - struct autoload_data *ele; - - ele = autoload_data_for_named_constant(mod, id, &ac); - if (ele) { + if (autoload_const_value_for_named_constant(mod, id, &ac)) { ac->flag &= ~mask; ac->flag |= flag; } From ed31a0caa88006afa507fd387e3f84ad8b8ddb00 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:05:13 +0100 Subject: [PATCH 1533/2435] [ruby/prism] Correctly handle line continuations in %w/i% interrupted by heredocs See https://bugs.ruby-lang.org/issues/21756. Ripper fails to parse this, but prism actually also doesn't handle it correctly. When heredocs are used, even in lowercase percent arays there can be multiple `STRING_CONTENT` tokens. We need to concat them. Luckily we don't need to handle as many cases as in uppercase arrays where interpolation is allowed. https://github.com/ruby/prism/commit/211677000e --- prism/prism.c | 71 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index cd4d166a124ef1..291d1d85218c1e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19299,18 +19299,52 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_token_t opening = parser->previous; pm_array_node_t *array = pm_array_node_create(parser, &opening); + pm_node_t *current = NULL; while (!match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { accept1(parser, PM_TOKEN_WORDS_SEP); if (match1(parser, PM_TOKEN_STRING_END)) break; - if (match1(parser, PM_TOKEN_STRING_CONTENT)) { + // Interpolation is not possible but nested heredocs can still lead to + // consecutive (disjoint) string tokens when the final newline is escaped. + while (match1(parser, PM_TOKEN_STRING_CONTENT)) { pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); - pm_array_node_elements_append(array, UP(pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing))); + + // Record the string node, moving to interpolation if needed. + if (current == NULL) { + current = UP(pm_symbol_node_create_current_string(parser, &opening, &parser->current, &closing)); + parser_lex(parser); + } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_SYMBOL_NODE)) { + pm_node_t *string = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); + parser_lex(parser); + pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, string); + } else if (PM_NODE_TYPE_P(current, PM_SYMBOL_NODE)) { + pm_symbol_node_t *cast = (pm_symbol_node_t *) current; + pm_token_t bounds = not_provided(parser); + + pm_token_t content = { .type = PM_TOKEN_STRING_CONTENT, .start = cast->value_loc.start, .end = cast->value_loc.end }; + pm_node_t *first_string = UP(pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &cast->unescaped)); + pm_node_t *second_string = UP(pm_string_node_create_current_string(parser, &opening, &parser->previous, &closing)); + parser_lex(parser); + + pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, &opening, NULL, &closing); + pm_interpolated_symbol_node_append(interpolated, first_string); + pm_interpolated_symbol_node_append(interpolated, second_string); + + xfree(current); + current = UP(interpolated); + } else { + assert(false && "unreachable"); + } } - expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_I_LOWER_ELEMENT); + if (current) { + pm_array_node_elements_append(array, current); + current = NULL; + } else { + expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_W_LOWER_ELEMENT); + } } pm_token_t closing = parser->current; @@ -19489,23 +19523,42 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_token_t opening = parser->previous; pm_array_node_t *array = pm_array_node_create(parser, &opening); - - // skip all leading whitespaces - accept1(parser, PM_TOKEN_WORDS_SEP); + pm_node_t *current = NULL; while (!match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { accept1(parser, PM_TOKEN_WORDS_SEP); if (match1(parser, PM_TOKEN_STRING_END)) break; - if (match1(parser, PM_TOKEN_STRING_CONTENT)) { + // Interpolation is not possible but nested heredocs can still lead to + // consecutive (disjoint) string tokens when the final newline is escaped. + while (match1(parser, PM_TOKEN_STRING_CONTENT)) { pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); pm_node_t *string = UP(pm_string_node_create_current_string(parser, &opening, &parser->current, &closing)); - pm_array_node_elements_append(array, string); + + // Record the string node, moving to interpolation if needed. + if (current == NULL) { + current = string; + } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_STRING_NODE)) { + pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, string); + } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { + pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); + pm_interpolated_string_node_append(interpolated, current); + pm_interpolated_string_node_append(interpolated, string); + current = UP(interpolated); + } else { + assert(false && "unreachable"); + } + parser_lex(parser); } - expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_W_LOWER_ELEMENT); + if (current) { + pm_array_node_elements_append(array, current); + current = NULL; + } else { + expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_W_LOWER_ELEMENT); + } } pm_token_t closing = parser->current; From d5c7cf0a1a1d2a72421b9a166e19442f89b99868 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:06:22 +0100 Subject: [PATCH 1534/2435] [ruby/prism] Fix wrong error message for lower percent i arrays Not so sure how to trigger it but this is definitly more correct. https://github.com/ruby/prism/commit/1bc8ec5e5d --- prism/prism.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 291d1d85218c1e..02247734e2ace7 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19343,7 +19343,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_array_node_elements_append(array, current); current = NULL; } else { - expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_W_LOWER_ELEMENT); + expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_I_LOWER_ELEMENT); } } From fd02356e36198b5bb0bb64f303a716a4ada9ed15 Mon Sep 17 00:00:00 2001 From: Goshanraj Govindaraj Date: Wed, 3 Dec 2025 13:37:43 -0500 Subject: [PATCH 1535/2435] ZJIT: Optimize NewArray to use rb_ec_ary_new_from_values (#15391) --- zjit/src/codegen.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index bb19d7d8209ff8..57ee65e91b754b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1451,15 +1451,16 @@ fn gen_new_array( ) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); - let length: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); + let num: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); - let new_array = asm_ccall!(asm, rb_ary_new_capa, length.into()); - - for val in elements { - asm_ccall!(asm, rb_ary_push, new_array, val); + if elements.is_empty() { + asm_ccall!(asm, rb_ec_ary_new_from_values, EC, 0i64.into(), Opnd::UImm(0)) + } else { + let argv = gen_push_opnds(asm, &elements); + let new_array = asm_ccall!(asm, rb_ec_ary_new_from_values, EC, num.into(), argv); + gen_pop_opnds(asm, &elements); + new_array } - - new_array } /// Compile array access (`array[index]`) From 228d13f6ed914d1e7f6bd2416e3f5be8283be865 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 18:08:14 +0100 Subject: [PATCH 1536/2435] gc.c: Pass shape_id to `newobj_init` Attempt to fix the following SEGV: ``` ruby(gc_mark) ../src/gc/default/default.c:4429 ruby(gc_mark_children+0x45) [0x560b380bf8b5] ../src/gc/default/default.c:4625 ruby(gc_mark_stacked_objects) ../src/gc/default/default.c:4647 ruby(gc_mark_stacked_objects_all) ../src/gc/default/default.c:4685 ruby(gc_marks_rest) ../src/gc/default/default.c:5707 ruby(gc_marks+0x4e7) [0x560b380c41c1] ../src/gc/default/default.c:5821 ruby(gc_start) ../src/gc/default/default.c:6502 ruby(heap_prepare+0xa4) [0x560b380c4efc] ../src/gc/default/default.c:2074 ruby(heap_next_free_page) ../src/gc/default/default.c:2289 ruby(newobj_cache_miss) ../src/gc/default/default.c:2396 ruby(RB_SPECIAL_CONST_P+0x0) [0x560b380c5df4] ../src/gc/default/default.c:2420 ruby(RB_BUILTIN_TYPE) ../src/include/ruby/internal/value_type.h:184 ruby(newobj_init) ../src/gc/default/default.c:2136 ruby(rb_gc_impl_new_obj) ../src/gc/default/default.c:2500 ruby(newobj_of) ../src/gc.c:996 ruby(rb_imemo_new+0x37) [0x560b380d8bed] ../src/imemo.c:46 ruby(imemo_fields_new) ../src/imemo.c:105 ruby(rb_imemo_fields_new) ../src/imemo.c:120 ``` I have no reproduction, but my understanding based on the backtrace and error is that GC is triggered inside `newobj_init` causing the new object to be marked while in a incomplete state. I believe the fix is to pass the `shape_id` down to `newobj_init` so it can be set before the GC has a chance to trigger. --- gc.c | 5 ++--- gc/default/default.c | 33 ++++++++++++++++----------------- gc/gc_impl.h | 2 +- gc/mmtk/mmtk.c | 5 +++-- shape.h | 9 +++++++-- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/gc.c b/gc.c index 9ccaffdc0b371c..1020a461dce11f 100644 --- a/gc.c +++ b/gc.c @@ -622,7 +622,7 @@ typedef struct gc_function_map { void (*stress_set)(void *objspace_ptr, VALUE flag); VALUE (*stress_get)(void *objspace_ptr); // Object allocation - VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); + VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size); size_t (*obj_slot_size)(VALUE obj); size_t (*heap_id_for_size)(void *objspace_ptr, size_t size); bool (*size_allocatable_p)(size_t size); @@ -993,8 +993,7 @@ gc_validate_pc(VALUE obj) static inline VALUE newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t size) { - VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size); - RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); + VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, shape_id, wb_protected, size); gc_validate_pc(obj); diff --git a/gc/default/default.c b/gc/default/default.c index 54b93dc8d07cf6..2cacb2cfd9e26b 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -32,6 +32,7 @@ #include "darray.h" #include "gc/gc.h" #include "gc/gc_impl.h" +#include "shape.h" #ifndef BUILDING_MODULAR_GC # include "probes.h" @@ -2131,15 +2132,13 @@ rb_gc_impl_source_location_cstr(int *ptr) #endif static inline VALUE -newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj) +newobj_init(VALUE klass, VALUE flags, shape_id_t shape_id, int wb_protected, rb_objspace_t *objspace, VALUE obj) { GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE); GC_ASSERT((flags & FL_WB_PROTECTED) == 0); RBASIC(obj)->flags = flags; *((VALUE *)&RBASIC(obj)->klass) = klass; -#if RBASIC_SHAPE_ID_FIELD - RBASIC(obj)->shape_id = 0; -#endif + RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); int t = flags & RUBY_T_MASK; if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) { @@ -2423,10 +2422,10 @@ newobj_alloc(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t he return obj; } -ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx)); +ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx)); static inline VALUE -newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx) +newobj_slowpath(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx) { VALUE obj; unsigned int lev; @@ -2451,32 +2450,32 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_new } obj = newobj_alloc(objspace, cache, heap_idx, true); - newobj_init(klass, flags, wb_protected, objspace, obj); + newobj_init(klass, flags, shape_id, wb_protected, objspace, obj); } RB_GC_CR_UNLOCK(lev); return obj; } -NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags, +NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx)); -NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, +NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx)); static VALUE -newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) +newobj_slowpath_wb_protected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) { - return newobj_slowpath(klass, flags, objspace, cache, TRUE, heap_idx); + return newobj_slowpath(klass, flags, shape_id, objspace, cache, TRUE, heap_idx); } static VALUE -newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) +newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) { - return newobj_slowpath(klass, flags, objspace, cache, FALSE, heap_idx); + return newobj_slowpath(klass, flags, shape_id, objspace, cache, FALSE, heap_idx); } VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size) { VALUE obj; rb_objspace_t *objspace = objspace_ptr; @@ -2497,14 +2496,14 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags if (!RB_UNLIKELY(during_gc || ruby_gc_stressful) && wb_protected) { obj = newobj_alloc(objspace, cache, heap_idx, false); - newobj_init(klass, flags, wb_protected, objspace, obj); + newobj_init(klass, flags, shape_id, wb_protected, objspace, obj); } else { RB_DEBUG_COUNTER_INC(obj_newobj_slowpath); obj = wb_protected ? - newobj_slowpath_wb_protected(klass, flags, objspace, cache, heap_idx) : - newobj_slowpath_wb_unprotected(klass, flags, objspace, cache, heap_idx); + newobj_slowpath_wb_protected(klass, flags, shape_id, objspace, cache, heap_idx) : + newobj_slowpath_wb_unprotected(klass, flags, shape_id, objspace, cache, heap_idx); } return obj; diff --git a/gc/gc_impl.h b/gc/gc_impl.h index 3250483639e775..65c0586d46bfad 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -55,7 +55,7 @@ GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); // Object allocation -GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); +GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, uint32_t /* shape_id_t */ shape_id, bool wb_protected, size_t alloc_size); GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); GC_IMPL_FN size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size); GC_IMPL_FN bool rb_gc_impl_size_allocatable_p(size_t size); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e1678dcf6ab0b4..a3bdf1cd4a9a42 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -7,6 +7,7 @@ #include "gc/gc.h" #include "gc/gc_impl.h" +#include "shape.h" #include "gc/mmtk/mmtk.h" #include "ccan/list/list.h" @@ -603,7 +604,7 @@ rb_gc_impl_config_set(void *objspace_ptr, VALUE hash) // Object allocation VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size) { #define MMTK_ALLOCATION_SEMANTICS_DEFAULT 0 struct objspace *objspace = objspace_ptr; @@ -625,7 +626,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags VALUE *alloc_obj = mmtk_alloc(ractor_cache->mutator, alloc_size + 8, MMTk_MIN_OBJ_ALIGN, 0, MMTK_ALLOCATION_SEMANTICS_DEFAULT); alloc_obj++; alloc_obj[-1] = alloc_size; - alloc_obj[0] = flags; + alloc_obj[0] = RSHAPE_COMBINE_IN_FLAGS(flags, shape_id);; alloc_obj[1] = klass; mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size + 8, MMTK_ALLOCATION_SEMANTICS_DEFAULT); diff --git a/shape.h b/shape.h index 9478d4b3a95dc6..28c2a7eef9421a 100644 --- a/shape.h +++ b/shape.h @@ -162,6 +162,12 @@ RBASIC_SHAPE_ID_FOR_READ(VALUE obj) bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id); #endif +static inline VALUE +RSHAPE_COMBINE_IN_FLAGS(VALUE flags, shape_id_t shape_id) +{ + return (flags &SHAPE_FLAG_MASK) | (((VALUE)shape_id) << SHAPE_FLAG_SHIFT); +} + static inline void RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) { @@ -169,8 +175,7 @@ RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) RBASIC(obj)->shape_id = (VALUE)shape_id; #else // Object shapes are occupying top bits - RBASIC(obj)->flags &= SHAPE_FLAG_MASK; - RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); + RBASIC(obj)->flags = RSHAPE_COMBINE_IN_FLAGS(RBASIC(obj)->flags, shape_id); #endif } From 8d1a6bc48b1a45cacd8b1f1c42f71d5967a27bba Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 20:40:22 +0100 Subject: [PATCH 1537/2435] gc.c: check if the struct has fields before marking the fields_obj If GC trigger in the middle of `struct_alloc`, and the struct has more than 3 elements, then `fields_obj` reference is garbage. We must first check the shape to know if it was actually initialized. --- gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 1020a461dce11f..7b4547e2ea0bca 100644 --- a/gc.c +++ b/gc.c @@ -3303,7 +3303,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) gc_mark_internal(ptr[i]); } - if (!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) { + if (rb_shape_obj_has_fields(obj) && !FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) { gc_mark_internal(RSTRUCT_FIELDS_OBJ(obj)); } From 9913d8da1ffcd00e8c648ac52abefdc086662797 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 1 Dec 2025 23:19:14 -0800 Subject: [PATCH 1538/2435] Group malloc counters together --- gc/default/default.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 2cacb2cfd9e26b..1d50172739fdfd 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -468,8 +468,14 @@ enum gc_mode { typedef struct rb_objspace { struct { - size_t limit; size_t increase; +#if RGENGC_ESTIMATE_OLDMALLOC + size_t oldmalloc_increase; +#endif + } malloc_counters; + + struct { + size_t limit; #if MALLOC_ALLOCATED_SIZE size_t allocated_size; size_t allocations; @@ -590,7 +596,6 @@ typedef struct rb_objspace { size_t old_objects_limit; #if RGENGC_ESTIMATE_OLDMALLOC - size_t oldmalloc_increase; size_t oldmalloc_increase_limit; #endif @@ -864,7 +869,7 @@ RVALUE_AGE_SET(VALUE obj, int age) } #define malloc_limit objspace->malloc_params.limit -#define malloc_increase objspace->malloc_params.increase +#define malloc_increase objspace->malloc_counters.increase #define malloc_allocated_size objspace->malloc_params.allocated_size #define heap_pages_lomem objspace->heap_pages.range[0] #define heap_pages_himem objspace->heap_pages.range[1] @@ -4910,7 +4915,7 @@ gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, { size_t saved_malloc_increase = objspace->malloc_params.increase; #if RGENGC_ESTIMATE_OLDMALLOC - size_t saved_oldmalloc_increase = objspace->rgengc.oldmalloc_increase; + size_t saved_oldmalloc_increase = objspace->malloc_counters.oldmalloc_increase; #endif VALUE already_disabled = rb_objspace_gc_disable(objspace); @@ -4933,7 +4938,7 @@ gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace); objspace->malloc_params.increase = saved_malloc_increase; #if RGENGC_ESTIMATE_OLDMALLOC - objspace->rgengc.oldmalloc_increase = saved_oldmalloc_increase; + objspace->malloc_counters.oldmalloc_increase = saved_oldmalloc_increase; #endif } #endif /* RGENGC_CHECK_MODE >= 4 */ @@ -6331,7 +6336,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) /* reset oldmalloc info */ #if RGENGC_ESTIMATE_OLDMALLOC if (!full_mark) { - if (objspace->rgengc.oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) { + if (objspace->malloc_counters.oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) { gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC; objspace->rgengc.oldmalloc_increase_limit = (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor); @@ -6344,13 +6349,13 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRIuSIZE"\t%"PRIuSIZE"\t%"PRIdSIZE"\n", rb_gc_count(), gc_needs_major_flags, - objspace->rgengc.oldmalloc_increase, + objspace->malloc_counters.oldmalloc_increase, objspace->rgengc.oldmalloc_increase_limit, gc_params.oldmalloc_limit_max); } else { /* major GC */ - objspace->rgengc.oldmalloc_increase = 0; + objspace->malloc_counters.oldmalloc_increase = 0; if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) { objspace->rgengc.oldmalloc_increase_limit = @@ -7583,7 +7588,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) SET(old_objects, objspace->rgengc.old_objects); SET(old_objects_limit, objspace->rgengc.old_objects_limit); #if RGENGC_ESTIMATE_OLDMALLOC - SET(oldmalloc_increase_bytes, objspace->rgengc.oldmalloc_increase); + SET(oldmalloc_increase_bytes, objspace->malloc_counters.oldmalloc_increase); SET(oldmalloc_increase_bytes_limit, objspace->rgengc.oldmalloc_increase_limit); #endif @@ -8040,13 +8045,13 @@ objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_siz if (new_size > old_size) { RUBY_ATOMIC_SIZE_ADD(malloc_increase, new_size - old_size); #if RGENGC_ESTIMATE_OLDMALLOC - RUBY_ATOMIC_SIZE_ADD(objspace->rgengc.oldmalloc_increase, new_size - old_size); + RUBY_ATOMIC_SIZE_ADD(objspace->malloc_counters.oldmalloc_increase, new_size - old_size); #endif } else { atomic_sub_nounderflow(&malloc_increase, old_size - new_size); #if RGENGC_ESTIMATE_OLDMALLOC - atomic_sub_nounderflow(&objspace->rgengc.oldmalloc_increase, old_size - new_size); + atomic_sub_nounderflow(&objspace->malloc_counters.oldmalloc_increase, old_size - new_size); #endif } From a773bbf0cc35cd4b73509edd58a0757d06abaca6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 2 Dec 2025 09:46:10 -0800 Subject: [PATCH 1539/2435] Track small malloc/free changes in thread local --- gc/default/default.c | 76 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 1d50172739fdfd..f8fd40b44fd8b4 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -159,6 +159,17 @@ #define GC_OLDMALLOC_LIMIT_MAX (128 * 1024 * 1024 /* 128MB */) #endif +#ifndef GC_MALLOC_INCREASE_LOCAL_THRESHOLD +#define GC_MALLOC_INCREASE_LOCAL_THRESHOLD (8 * 1024 /* 8KB */) +#endif + +#ifdef RB_THREAD_LOCAL_SPECIFIER +#define USE_MALLOC_INCREASE_LOCAL 1 +static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local; +#else +#define USE_MALLOC_INCREASE_LOCAL 0 +#endif + #ifndef GC_CAN_COMPILE_COMPACTION #if defined(__wasi__) /* WebAssembly doesn't support signals */ # define GC_CAN_COMPILE_COMPACTION 0 @@ -7531,6 +7542,8 @@ ns_to_ms(uint64_t ns) return ns / (1000 * 1000); } +static void malloc_increase_local_flush(rb_objspace_t *objspace); + VALUE rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) { @@ -7540,6 +7553,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) setup_gc_stat_symbols(); ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + malloc_increase_local_flush(objspace); if (RB_TYPE_P(hash_or_sym, T_HASH)) { hash = hash_or_sym; @@ -8027,6 +8041,45 @@ objspace_malloc_gc_stress(rb_objspace_t *objspace) } } +static void +malloc_increase_commit(rb_objspace_t *objspace, size_t new_size, size_t old_size) +{ + if (new_size > old_size) { + RUBY_ATOMIC_SIZE_ADD(malloc_increase, new_size - old_size); +#if RGENGC_ESTIMATE_OLDMALLOC + RUBY_ATOMIC_SIZE_ADD(objspace->malloc_counters.oldmalloc_increase, new_size - old_size); +#endif + } + else { + atomic_sub_nounderflow(&malloc_increase, old_size - new_size); +#if RGENGC_ESTIMATE_OLDMALLOC + atomic_sub_nounderflow(&objspace->malloc_counters.oldmalloc_increase, old_size - new_size); +#endif + } +} + +#if USE_MALLOC_INCREASE_LOCAL +static void +malloc_increase_local_flush(rb_objspace_t *objspace) +{ + int delta = malloc_increase_local; + if (delta == 0) return; + + malloc_increase_local = 0; + if (delta > 0) { + malloc_increase_commit(objspace, (size_t)delta, 0); + } + else { + malloc_increase_commit(objspace, 0, (size_t)(-delta)); + } +} +#else +static void +malloc_increase_local_flush(rb_objspace_t *objspace) +{ +} +#endif + static inline bool objspace_malloc_increase_report(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed) { @@ -8042,18 +8095,23 @@ objspace_malloc_increase_report(rb_objspace_t *objspace, void *mem, size_t new_s static bool objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_size, size_t old_size, enum memop_type type, bool gc_allowed) { - if (new_size > old_size) { - RUBY_ATOMIC_SIZE_ADD(malloc_increase, new_size - old_size); -#if RGENGC_ESTIMATE_OLDMALLOC - RUBY_ATOMIC_SIZE_ADD(objspace->malloc_counters.oldmalloc_increase, new_size - old_size); -#endif +#if USE_MALLOC_INCREASE_LOCAL + if (new_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD && + old_size < GC_MALLOC_INCREASE_LOCAL_THRESHOLD) { + malloc_increase_local += (int)new_size - (int)old_size; + + if (malloc_increase_local >= GC_MALLOC_INCREASE_LOCAL_THRESHOLD || + malloc_increase_local <= -GC_MALLOC_INCREASE_LOCAL_THRESHOLD) { + malloc_increase_local_flush(objspace); + } } else { - atomic_sub_nounderflow(&malloc_increase, old_size - new_size); -#if RGENGC_ESTIMATE_OLDMALLOC - atomic_sub_nounderflow(&objspace->malloc_counters.oldmalloc_increase, old_size - new_size); -#endif + malloc_increase_local_flush(objspace); + malloc_increase_commit(objspace, new_size, old_size); } +#else + malloc_increase_commit(objspace, new_size, old_size); +#endif if (type == MEMOP_TYPE_MALLOC && gc_allowed) { retry: From 2b23b05bf2c0f30f2c4ee9bb3030fa58f2cba3a6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 24 Nov 2025 16:16:08 -0800 Subject: [PATCH 1540/2435] ZJIT: Add a specialized instruction iterator to the assembler This commit adds a specialized instruction iterator to the assembler with a custom "peek" method. The reason is that we want to add basic blocks to LIR. When we split instructions, we need to add any new instructions to the correct basic block. The custom iterator will maintain the correct basic block inside the assembler, that way when we push any new instructions they will be appended to the correct place. --- zjit/src/backend/lir.rs | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 3c9bf72023d90b..d7c6740d13a1e4 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1399,6 +1399,15 @@ impl Assembler } } + pub fn instruction_iterator(&mut self) -> InsnIter { + let insns = take(&mut self.insns); + InsnIter { + old_insns_iter: insns.into_iter(), + peeked: None, + index: 0, + } + } + pub fn expect_leaf_ccall(&mut self, stack_size: usize) { self.leaf_ccall_stack_size = Some(stack_size); } @@ -1998,6 +2007,44 @@ impl fmt::Debug for Assembler { } } +pub struct InsnIter { + old_insns_iter: std::vec::IntoIter, + peeked: Option<(usize, Insn)>, + index: usize, +} + +impl InsnIter { + // We're implementing our own peek() because we don't want peek to + // cross basic blocks as we're iterating. + pub fn peek(&mut self) -> Option<&(usize, Insn)> { + // If we don't have a peeked value, get one + if self.peeked.is_none() { + let insn = self.old_insns_iter.next()?; + let idx = self.index; + self.index += 1; + self.peeked = Some((idx, insn)); + } + // Return a reference to the peeked value + self.peeked.as_ref() + } + + // Get the next instruction. Right now we're passing the "new" assembler + // (the assembler we're copying in to) as a parameter. Once we've + // introduced basic blocks to LIR, we'll use the to set the correct BB + // on the new assembler, but for now it is unused. + pub fn next(&mut self, _new_asm: &mut Assembler) -> Option<(usize, Insn)> { + // If we have a peeked value, return it + if let Some(item) = self.peeked.take() { + return Some(item); + } + // Otherwise get the next from underlying iterator + let insn = self.old_insns_iter.next()?; + let idx = self.index; + self.index += 1; + Some((idx, insn)) + } +} + impl Assembler { #[must_use] pub fn add(&mut self, left: Opnd, right: Opnd) -> Opnd { From d7e55f84f2bd62d302b29513d4c4dc8ae9aef96f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 25 Nov 2025 16:51:54 -0800 Subject: [PATCH 1541/2435] ZJIT: Use the custom iterator This commit uses the custom instruction iterator in arm64 / x86_64 instruction splitting. Once we introduce basic blocks to LIR, the custom iterator will ensure that instructions are added to the correct place. --- zjit/src/backend/arm64/mod.rs | 25 +++++++++++++------------ zjit/src/backend/x86_64/mod.rs | 21 +++++++++++---------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 013fd315833409..78fb69b0b04d5b 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -391,10 +391,10 @@ impl Assembler { let mut asm_local = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); - let mut iterator = self.insns.into_iter().enumerate().peekable(); + let mut iterator = self.instruction_iterator(); let asm = &mut asm_local; - while let Some((index, mut insn)) = iterator.next() { + while let Some((index, mut insn)) = iterator.next(asm) { // Here we're going to map the operands of the instruction to load // any Opnd::Value operands into registers if they are heap objects // such that only the Op::Load instruction needs to handle that @@ -428,13 +428,13 @@ impl Assembler { *right = split_shifted_immediate(asm, other_opnd); // Now `right` is either a register or an immediate, both can try to // merge with a subsequent mov. - merge_three_reg_mov(&live_ranges, &mut iterator, left, left, out); + merge_three_reg_mov(&live_ranges, &mut iterator, asm, left, left, out); asm.push_insn(insn); } _ => { *left = split_load_operand(asm, *left); *right = split_shifted_immediate(asm, *right); - merge_three_reg_mov(&live_ranges, &mut iterator, left, right, out); + merge_three_reg_mov(&live_ranges, &mut iterator, asm, left, right, out); asm.push_insn(insn); } } @@ -444,7 +444,7 @@ impl Assembler { *right = split_shifted_immediate(asm, *right); // Now `right` is either a register or an immediate, // both can try to merge with a subsequent mov. - merge_three_reg_mov(&live_ranges, &mut iterator, left, left, out); + merge_three_reg_mov(&live_ranges, &mut iterator, asm, left, left, out); asm.push_insn(insn); } Insn::And { left, right, out } | @@ -454,7 +454,7 @@ impl Assembler { *left = opnd0; *right = opnd1; - merge_three_reg_mov(&live_ranges, &mut iterator, left, right, out); + merge_three_reg_mov(&live_ranges, &mut iterator, asm, left, right, out); asm.push_insn(insn); } @@ -567,7 +567,7 @@ impl Assembler { if matches!(out, Opnd::VReg { .. }) && *out == *src && live_ranges[out.vreg_idx()].end() == index + 1 => { *out = Opnd::Reg(*reg); asm.push_insn(insn); - iterator.next(); // Pop merged Insn::Mov + iterator.next(asm); // Pop merged Insn::Mov } _ => { asm.push_insn(insn); @@ -694,7 +694,7 @@ impl Assembler { /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so this /// splits them and uses scratch registers for it. - fn arm64_scratch_split(self) -> Assembler { + fn arm64_scratch_split(mut self) -> Assembler { /// If opnd is Opnd::Mem with a too large disp, make the disp smaller using lea. fn split_large_disp(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd { match opnd { @@ -753,9 +753,9 @@ impl Assembler { let mut asm_local = Assembler::new_with_asm(&self); let asm = &mut asm_local; asm.accept_scratch_reg = true; - let mut iterator = self.insns.into_iter().enumerate().peekable(); + let iterator = &mut self.instruction_iterator(); - while let Some((_, mut insn)) = iterator.next() { + while let Some((_, mut insn)) = iterator.next(asm) { match &mut insn { Insn::Add { left, right, out } | Insn::Sub { left, right, out } | @@ -1660,7 +1660,8 @@ impl Assembler { /// If a, b, and c are all registers. fn merge_three_reg_mov( live_ranges: &[LiveRange], - iterator: &mut std::iter::Peekable>, + iterator: &mut InsnIter, + asm: &mut Assembler, left: &Opnd, right: &Opnd, out: &mut Opnd, @@ -1671,7 +1672,7 @@ fn merge_three_reg_mov( = (left, right, iterator.peek()) { if out == src && live_ranges[out.vreg_idx()].end() == *mov_idx && matches!(*dest, Opnd::Reg(_) | Opnd::VReg{..}) { *out = *dest; - iterator.next(); // Pop merged Insn::Mov + iterator.next(asm); // Pop merged Insn::Mov } } } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index a9bd57f368c59b..5e975e1bd03ccf 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -138,11 +138,12 @@ impl Assembler { /// Split IR instructions for the x86 platform fn x86_split(mut self) -> Assembler { - let mut asm = Assembler::new_with_asm(&self); + let mut asm_local = Assembler::new_with_asm(&self); + let asm = &mut asm_local; let live_ranges: Vec = take(&mut self.live_ranges); - let mut iterator = self.insns.into_iter().enumerate().peekable(); + let mut iterator = self.instruction_iterator(); - while let Some((index, mut insn)) = iterator.next() { + while let Some((index, mut insn)) = iterator.next(asm) { let is_load = matches!(insn, Insn::Load { .. } | Insn::LoadInto { .. }); let mut opnd_iter = insn.opnd_iter_mut(); @@ -187,13 +188,13 @@ impl Assembler { if out == src && left == dest && live_ranges[out.vreg_idx()].end() == index + 1 && uimm_num_bits(*value) <= 32 => { *out = *dest; asm.push_insn(insn); - iterator.next(); // Pop merged Insn::Mov + iterator.next(asm); // Pop merged Insn::Mov } (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) if out == src && live_ranges[out.vreg_idx()].end() == index + 1 && *dest == *left => { *out = *dest; asm.push_insn(insn); - iterator.next(); // Pop merged Insn::Mov + iterator.next(asm); // Pop merged Insn::Mov } _ => { match (*left, *right) { @@ -373,7 +374,7 @@ impl Assembler { (Insn::Lea { opnd, out }, Some(Insn::Mov { dest: Opnd::Reg(reg), src })) if matches!(out, Opnd::VReg { .. }) && out == src && live_ranges[out.vreg_idx()].end() == index + 1 => { asm.push_insn(Insn::Lea { opnd: *opnd, out: Opnd::Reg(*reg) }); - iterator.next(); // Pop merged Insn::Mov + iterator.next(asm); // Pop merged Insn::Mov } _ => asm.push_insn(insn), } @@ -384,14 +385,14 @@ impl Assembler { } } - asm + asm_local } /// Split instructions using scratch registers. To maximize the use of the register pool /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so /// this splits them and uses scratch registers for it. - pub fn x86_scratch_split(self) -> Assembler { + pub fn x86_scratch_split(mut self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register /// allocator. So we just use the SCRATCH0_OPND register temporarily to hold @@ -470,9 +471,9 @@ impl Assembler { let mut asm_local = Assembler::new_with_asm(&self); let asm = &mut asm_local; asm.accept_scratch_reg = true; - let mut iterator = self.insns.into_iter().enumerate().peekable(); + let mut iterator = self.instruction_iterator(); - while let Some((_, mut insn)) = iterator.next() { + while let Some((_, mut insn)) = iterator.next(asm) { match &mut insn { Insn::Add { left, right, out } | Insn::Sub { left, right, out } | From 612a66805f35b2c732ec843d78f1dcad5c6456cd Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 16:02:31 -0500 Subject: [PATCH 1542/2435] Remove spurious obj != klass check in shape_get_next This should never be true. I added an `rb_bug` in case it was and it wasn't true in any of btest or test-all. Co-authored-by: Aaron Patterson --- shape.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shape.c b/shape.c index 754be1cfd64e65..c6f5af2519323e 100644 --- a/shape.c +++ b/shape.c @@ -839,7 +839,7 @@ shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE obj, ID id, } // Check if we should update max_iv_count on the object's class - if (obj != klass && new_shape->next_field_index > RCLASS_MAX_IV_COUNT(klass)) { + if (new_shape->next_field_index > RCLASS_MAX_IV_COUNT(klass)) { RCLASS_SET_MAX_IV_COUNT(klass, new_shape->next_field_index); } From f1670733249fb30d755bad1f88c0e54b26bdf49e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 16:06:41 -0500 Subject: [PATCH 1543/2435] Move imemo fields check out of shape_get_next Not every caller (for example, YJIT) actually needs to pass the object. YJIT (and, in the future, ZJIT) only need to pass the class. --- shape.c | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/shape.c b/shape.c index c6f5af2519323e..13d1edf36c928a 100644 --- a/shape.c +++ b/shape.c @@ -801,7 +801,7 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) } static inline rb_shape_t * -shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE obj, ID id, bool emit_warnings) +shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE klass, ID id, bool emit_warnings) { RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id)))); @@ -812,23 +812,6 @@ shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE obj, ID id, } #endif - VALUE klass; - if (IMEMO_TYPE_P(obj, imemo_fields)) { - VALUE owner = rb_imemo_fields_owner(obj); - switch (BUILTIN_TYPE(owner)) { - case T_CLASS: - case T_MODULE: - klass = rb_singleton_class(owner); - break; - default: - klass = rb_obj_class(owner); - break; - } - } - else { - klass = rb_obj_class(obj); - } - bool allow_new_shape = RCLASS_VARIATION_COUNT(klass) < SHAPE_MAX_VARIATIONS; bool variation_created = false; rb_shape_t *new_shape = get_next_shape_internal(shape, id, shape_type, &variation_created, allow_new_shape); @@ -862,6 +845,28 @@ shape_get_next(rb_shape_t *shape, enum shape_type shape_type, VALUE obj, ID id, return new_shape; } +static VALUE +obj_get_owner_class(VALUE obj) +{ + VALUE klass; + if (IMEMO_TYPE_P(obj, imemo_fields)) { + VALUE owner = rb_imemo_fields_owner(obj); + switch (BUILTIN_TYPE(owner)) { + case T_CLASS: + case T_MODULE: + klass = rb_singleton_class(owner); + break; + default: + klass = rb_obj_class(owner); + break; + } + } + else { + klass = rb_obj_class(obj); + } + return klass; +} + static rb_shape_t * remove_shape_recursive(VALUE obj, rb_shape_t *shape, ID id, rb_shape_t **removed_shape) { @@ -884,7 +889,8 @@ remove_shape_recursive(VALUE obj, rb_shape_t *shape, ID id, rb_shape_t **removed // We found a new parent. Create a child of the new parent that // has the same attributes as this shape. if (new_parent) { - rb_shape_t *new_child = shape_get_next(new_parent, shape->type, obj, shape->edge_name, true); + VALUE klass = obj_get_owner_class(obj); + rb_shape_t *new_child = shape_get_next(new_parent, shape->type, klass, shape->edge_name, true); RUBY_ASSERT(!new_child || new_child->capacity <= shape->capacity); return new_child; } @@ -933,7 +939,8 @@ rb_shape_transition_add_ivar(VALUE obj, ID id) shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); RUBY_ASSERT(!shape_frozen_p(original_shape_id)); - rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, obj, id, true); + VALUE klass = obj_get_owner_class(obj); + rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, klass, id, true); if (next_shape) { return shape_id(next_shape, original_shape_id); } @@ -948,7 +955,8 @@ rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id) shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); RUBY_ASSERT(!shape_frozen_p(original_shape_id)); - rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, obj, id, false); + VALUE klass = obj_get_owner_class(obj); + rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, klass, id, false); if (next_shape) { return shape_id(next_shape, original_shape_id); } From b43e66d3b37d4bd029a90dbee376e475aed79d2a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 16:11:30 -0500 Subject: [PATCH 1544/2435] YJIT: Pass class and shape ID directly instead of object --- shape.c | 4 +--- shape.h | 2 +- yjit/src/codegen.rs | 4 +++- yjit/src/cruby_bindings.inc.rs | 6 +++++- zjit/src/cruby_bindings.inc.rs | 6 +++++- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/shape.c b/shape.c index 13d1edf36c928a..90036722f10026 100644 --- a/shape.c +++ b/shape.c @@ -950,12 +950,10 @@ rb_shape_transition_add_ivar(VALUE obj, ID id) } shape_id_t -rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id) +rb_shape_transition_add_ivar_no_warnings(VALUE klass, shape_id_t original_shape_id, ID id) { - shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); RUBY_ASSERT(!shape_frozen_p(original_shape_id)); - VALUE klass = obj_get_owner_class(obj); rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), SHAPE_IVAR, klass, id, false); if (next_shape) { return shape_id(next_shape, original_shape_id); diff --git a/shape.h b/shape.h index 28c2a7eef9421a..bebfaba608c7fa 100644 --- a/shape.h +++ b/shape.h @@ -230,7 +230,7 @@ shape_id_t rb_shape_transition_frozen(VALUE obj); shape_id_t rb_shape_transition_complex(VALUE obj); shape_id_t rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id); shape_id_t rb_shape_transition_add_ivar(VALUE obj, ID id); -shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id); +shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE klass, shape_id_t original_shape_id, ID id); shape_id_t rb_shape_transition_object_id(VALUE obj); shape_id_t rb_shape_transition_heap(VALUE obj, size_t heap_index); shape_id_t rb_shape_object_id(shape_id_t original_shape_id); diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 50762c64d3eceb..865f80ca4f0cab 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -3101,7 +3101,9 @@ fn gen_set_ivar( let mut new_shape_too_complex = false; let new_shape = if !shape_too_complex && receiver_t_object && ivar_index.is_none() { let current_shape_id = comptime_receiver.shape_id_of(); - let next_shape_id = unsafe { rb_shape_transition_add_ivar_no_warnings(comptime_receiver, ivar_name) }; + // We don't need to check about imemo_fields here because we're definitely looking at a T_OBJECT. + let klass = unsafe { rb_obj_class(comptime_receiver) }; + let next_shape_id = unsafe { rb_shape_transition_add_ivar_no_warnings(klass, current_shape_id, ivar_name) }; // If the VM ran out of shapes, or this class generated too many leaf, // it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table). diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 6efef9c55c39c4..b9a8197184a21a 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1064,7 +1064,11 @@ extern "C" { pub fn rb_shape_id_offset() -> i32; pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; - pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t; + pub fn rb_shape_transition_add_ivar_no_warnings( + klass: VALUE, + original_shape_id: shape_id_t, + id: ID, + ) -> shape_id_t; pub fn rb_ivar_get_at(obj: VALUE, index: attr_index_t, id: ID) -> VALUE; pub fn rb_ivar_get_at_no_ractor_check(obj: VALUE, index: attr_index_t) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 7bd392563987a3..0048fd6daece27 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2033,7 +2033,11 @@ unsafe extern "C" { pub fn rb_shape_id_offset() -> i32; pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; - pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t; + pub fn rb_shape_transition_add_ivar_no_warnings( + klass: VALUE, + original_shape_id: shape_id_t, + id: ID, + ) -> shape_id_t; pub fn rb_ivar_get_at_no_ractor_check(obj: VALUE, index: attr_index_t) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; From b79ef73a3bb911c8d5ac9016092eab08b146fb3a Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 3 Dec 2025 11:27:06 +0900 Subject: [PATCH 1545/2435] Remove needless parse.y `value_expr` macro In the past parse.y and ripper had different `value_expr` definition so that `value_expr` does nothing for ripper. ```c // parse.y #define value_expr(node) value_expr_gen(p, (node)) // ripper #define value_expr(node) ((void)(node)) ``` However Rearchitect Ripper (89cfc1520717257073012ec07105c551e4b8af7c) removed `value_expr` definition for ripper then this commit removes needless parse.y macro and uses `value_expr_gen` directly. --- parse.y | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/parse.y b/parse.y index a9fccdd5f721e0..e65159257632c0 100644 --- a/parse.y +++ b/parse.y @@ -1402,10 +1402,9 @@ static NODE *logop(struct parser_params*,ID,NODE*,NODE*,const YYLTYPE*,const YYL static NODE *newline_node(NODE*); static void fixpos(NODE*,NODE*); -static int value_expr_gen(struct parser_params*,NODE*); +static int value_expr(struct parser_params*,NODE*); static void void_expr(struct parser_params*,NODE*); static NODE *remove_begin(NODE*); -#define value_expr(node) value_expr_gen(p, (node)) static NODE *void_stmts(struct parser_params*,NODE*); static void reduce_nodes(struct parser_params*,NODE**); static void block_dup_check(struct parser_params*,NODE*,NODE*); @@ -3103,39 +3102,39 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) %rule range_expr(range) : range tDOT2 range { - value_expr($1); - value_expr($3); + value_expr(p, $1); + value_expr(p, $3); $$ = NEW_DOT2($1, $3, &@$, &@2); /*% ripper: dot2!($:1, $:3) %*/ } | range tDOT3 range { - value_expr($1); - value_expr($3); + value_expr(p, $1); + value_expr(p, $3); $$ = NEW_DOT3($1, $3, &@$, &@2); /*% ripper: dot3!($:1, $:3) %*/ } | range tDOT2 { - value_expr($1); + value_expr(p, $1); $$ = NEW_DOT2($1, new_nil_at(p, &@2.end_pos), &@$, &@2); /*% ripper: dot2!($:1, Qnil) %*/ } | range tDOT3 { - value_expr($1); + value_expr(p, $1); $$ = NEW_DOT3($1, new_nil_at(p, &@2.end_pos), &@$, &@2); /*% ripper: dot3!($:1, Qnil) %*/ } | tBDOT2 range { - value_expr($2); + value_expr(p, $2); $$ = NEW_DOT2(new_nil_at(p, &@1.beg_pos), $2, &@$, &@1); /*% ripper: dot2!(Qnil, $:2) %*/ } | tBDOT3 range { - value_expr($2); + value_expr(p, $2); $$ = NEW_DOT3(new_nil_at(p, &@1.beg_pos), $2, &@$, &@1); /*% ripper: dot3!(Qnil, $:2) %*/ } @@ -3144,7 +3143,7 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) %rule value_expr(value) : value { - value_expr($1); + value_expr(p, $1); $$ = $1; } ; @@ -3467,7 +3466,7 @@ expr : command_call } | arg tASSOC { - value_expr($arg); + value_expr(p, $arg); } p_in_kwarg[ctxt] p_pvtbl p_pktbl p_top_expr_body[body] @@ -3482,7 +3481,7 @@ expr : command_call } | arg keyword_in { - value_expr($arg); + value_expr(p, $arg); } p_in_kwarg[ctxt] p_pvtbl p_pktbl p_top_expr_body[body] @@ -4059,7 +4058,7 @@ arg : asgn(arg_rhs) ternary : arg '?' arg '\n'? ':' arg { - value_expr($1); + value_expr(p, $1); $$ = new_if(p, $1, $3, $6, &@$, &NULL_LOC, &@5, &NULL_LOC); fixpos($$, $1); /*% ripper: ifop!($:1, $:3, $:6) %*/ @@ -4138,13 +4137,13 @@ aref_args : none arg_rhs : arg %prec tOP_ASGN { - value_expr($1); + value_expr(p, $1); $$ = $1; } | arg modifier_rescue after_rescue arg { p->ctxt.in_rescue = $3.in_rescue; - value_expr($1); + value_expr(p, $1); $$ = rescued_expr(p, $1, $4, &@1, &@2, &@4); /*% ripper: rescue_mod!($:1, $:4) %*/ } @@ -12820,8 +12819,8 @@ call_bin_op(struct parser_params *p, NODE *recv, ID id, NODE *arg1, const YYLTYPE *op_loc, const YYLTYPE *loc) { NODE *expr; - value_expr(recv); - value_expr(arg1); + value_expr(p, recv); + value_expr(p, arg1); expr = NEW_OPCALL(recv, id, NEW_LIST(arg1, &arg1->nd_loc), loc); nd_set_line(expr, op_loc->beg_pos.lineno); return expr; @@ -12831,7 +12830,7 @@ static NODE * call_uni_op(struct parser_params *p, NODE *recv, ID id, const YYLTYPE *op_loc, const YYLTYPE *loc) { NODE *opcall; - value_expr(recv); + value_expr(p, recv); opcall = NEW_OPCALL(recv, id, 0, loc); nd_set_line(opcall, op_loc->beg_pos.lineno); return opcall; @@ -12881,8 +12880,8 @@ match_op(struct parser_params *p, NODE *node1, NODE *node2, const YYLTYPE *op_lo NODE *n; int line = op_loc->beg_pos.lineno; - value_expr(node1); - value_expr(node2); + value_expr(p, node1); + value_expr(p, node2); if ((n = last_expr_once_body(node1)) != 0) { switch (nd_type(n)) { @@ -13922,7 +13921,7 @@ value_expr_check(struct parser_params *p, NODE *node) } static int -value_expr_gen(struct parser_params *p, NODE *node) +value_expr(struct parser_params *p, NODE *node) { NODE *void_node = value_expr_check(p, node); if (void_node) { @@ -14191,7 +14190,7 @@ range_op(struct parser_params *p, NODE *node, const YYLTYPE *loc) if (node == 0) return 0; type = nd_type(node); - value_expr(node); + value_expr(p, node); if (type == NODE_INTEGER) { if (!e_option_supplied(p)) rb_warn0L(nd_line(node), "integer literal in flip-flop"); ID lineno = rb_intern("$."); @@ -14328,7 +14327,7 @@ logop(struct parser_params *p, ID id, NODE *left, NODE *right, { enum node_type type = id == idAND || id == idANDOP ? NODE_AND : NODE_OR; NODE *op; - value_expr(left); + value_expr(p, left); if (left && nd_type_p(left, type)) { NODE *node = left, *second; while ((second = RNODE_AND(node)->nd_2nd) != 0 && nd_type_p(second, type)) { From e96bbd718e4aef81c749080016a44364a2b8e8c9 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 3 Dec 2025 15:52:30 +0900 Subject: [PATCH 1546/2435] Remove needless parse.y `new_nil` macro In the past parse.y and ripper had different `new_nil` definition so that `new_nil` returns `nil` for ripper. ```c // parse.y #define new_nil(loc) NEW_NIL(loc) // ripper #define new_nil(loc) Qnil ``` However Rearchitect Ripper (89cfc1520717257073012ec07105c551e4b8af7c) removed `new_nil` definition for ripper then this commit removes needless parse.y macro and uses `NEW_NIL` directly. --- parse.y | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parse.y b/parse.y index e65159257632c0..32431434de9734 100644 --- a/parse.y +++ b/parse.y @@ -1393,7 +1393,6 @@ last_expr_node(NODE *expr) static NODE* cond(struct parser_params *p, NODE *node, const YYLTYPE *loc); static NODE* method_cond(struct parser_params *p, NODE *node, const YYLTYPE *loc); -#define new_nil(loc) NEW_NIL(loc) static NODE *new_nil_at(struct parser_params *p, const rb_code_position_t *pos); static NODE *new_if(struct parser_params*,NODE*,NODE*,NODE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*); static NODE *new_unless(struct parser_params*,NODE*,NODE*,NODE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*,const YYLTYPE*); @@ -4451,7 +4450,7 @@ primary : inline_primary } | keyword_not '(' rparen { - $$ = call_uni_op(p, method_cond(p, new_nil(&@2), &@2), METHOD_NOT, &@1, &@$); + $$ = call_uni_op(p, method_cond(p, NEW_NIL(&@2), &@2), METHOD_NOT, &@1, &@$); /*% ripper: unary!(ID2VAL(idNOT), Qnil) %*/ } | fcall brace_block From 8f7d821dbae8bba00e1e905bbda1b639998288aa Mon Sep 17 00:00:00 2001 From: Rune Philosof Date: Tue, 28 Oct 2025 13:55:59 +0100 Subject: [PATCH 1547/2435] [ruby/psych] Remove y Object extension in IRB Fixes: ruby#685 This feature can easily break how you use other gems like factory_bot or prawn. https://github.com/ruby/psych/pull/747#issuecomment-3413139525 > But I kind of think we should leave `psych/y` around. If people really want to use it they could require the file. If you miss the function in Kernel, you can require it interactively or add it to `.irbrc`: ```ruby require 'psych/y' ``` https://github.com/ruby/psych/commit/f1610b3f05 --- ext/psych/lib/psych/core_ext.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb index 78fe262239a391..6dfd0f16962896 100644 --- a/ext/psych/lib/psych/core_ext.rb +++ b/ext/psych/lib/psych/core_ext.rb @@ -14,10 +14,6 @@ def to_yaml options = {} end end -if defined?(::IRB) - require_relative 'y' -end - # Up to Ruby 3.4, Set was a regular object and was dumped as such # by Pysch. # Starting from Ruby 4.0 it's a core class written in C, so we have to implement From 0e7e6858e3a3689708371028cb1553317b905bb0 Mon Sep 17 00:00:00 2001 From: Caleb Stewart Date: Mon, 4 Aug 2025 20:52:52 -0500 Subject: [PATCH 1548/2435] [ruby/psych] Add option to disable symbol parsing https://github.com/ruby/psych/commit/4e9d08c285 --- ext/psych/lib/psych.rb | 13 +++++++------ ext/psych/lib/psych/nodes/node.rb | 4 ++-- ext/psych/lib/psych/scalar_scanner.rb | 5 +++-- ext/psych/lib/psych/visitors/to_ruby.rb | 4 ++-- test/psych/test_scalar_scanner.rb | 5 +++++ 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb index 0c158c9ff329b1..850a6d19374d87 100644 --- a/ext/psych/lib/psych.rb +++ b/ext/psych/lib/psych.rb @@ -269,10 +269,10 @@ module Psych # YAML documents that are supplied via user input. Instead, please use the # load method or the safe_load method. # - def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false + def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true result = parse(yaml, filename: filename) return fallback unless result - result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer) + result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: parse_symbols) end ### @@ -320,13 +320,13 @@ def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: fals # Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"} # Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} # - def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false + def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true result = parse(yaml, filename: filename) return fallback unless result class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s)) - scanner = ScalarScanner.new class_loader, strict_integer: strict_integer + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer, parse_symbols: parse_symbols visitor = if aliases Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze else @@ -366,7 +366,7 @@ def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: # Raises a TypeError when `yaml` parameter is NilClass. This method is # similar to `safe_load` except that `Symbol` objects are allowed by default. # - def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false + def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true safe_load yaml, permitted_classes: permitted_classes, permitted_symbols: permitted_symbols, aliases: aliases, @@ -374,7 +374,8 @@ def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: fallback: fallback, symbolize_names: symbolize_names, freeze: freeze, - strict_integer: strict_integer + strict_integer: strict_integer, + parse_symbols: parse_symbols end ### diff --git a/ext/psych/lib/psych/nodes/node.rb b/ext/psych/lib/psych/nodes/node.rb index 6ae5c591484539..f89c82b7f76df8 100644 --- a/ext/psych/lib/psych/nodes/node.rb +++ b/ext/psych/lib/psych/nodes/node.rb @@ -45,8 +45,8 @@ def each &block # Convert this node to Ruby. # # See also Psych::Visitors::ToRuby - def to_ruby(symbolize_names: false, freeze: false, strict_integer: false) - Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer).accept(self) + def to_ruby(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) + Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: true).accept(self) end alias :transform :to_ruby diff --git a/ext/psych/lib/psych/scalar_scanner.rb b/ext/psych/lib/psych/scalar_scanner.rb index 8cf868f8638e9e..6a556fb3b89bc2 100644 --- a/ext/psych/lib/psych/scalar_scanner.rb +++ b/ext/psych/lib/psych/scalar_scanner.rb @@ -27,10 +27,11 @@ class ScalarScanner attr_reader :class_loader # Create a new scanner - def initialize class_loader, strict_integer: false + def initialize class_loader, strict_integer: false, parse_symbols: true @symbol_cache = {} @class_loader = class_loader @strict_integer = strict_integer + @parse_symbols = parse_symbols end # Tokenize +string+ returning the Ruby object @@ -72,7 +73,7 @@ def tokenize string -Float::INFINITY elsif string.match?(/^\.nan$/i) Float::NAN - elsif string.match?(/^:./) + elsif @parse_symbols && string.match?(/^:./) if string =~ /^:(["'])(.*)\1/ @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, '')) else diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index 2814ce1a695df0..e62311ae12d177 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -12,9 +12,9 @@ module Visitors ### # This class walks a YAML AST, converting each node to Ruby class ToRuby < Psych::Visitors::Visitor - def self.create(symbolize_names: false, freeze: false, strict_integer: false) + def self.create(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) class_loader = ClassLoader.new - scanner = ScalarScanner.new class_loader, strict_integer: strict_integer + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer, parse_symbols: parse_symbols new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze) end diff --git a/test/psych/test_scalar_scanner.rb b/test/psych/test_scalar_scanner.rb index 2637a74df82b31..bc6a74ad8b2a98 100644 --- a/test/psych/test_scalar_scanner.rb +++ b/test/psych/test_scalar_scanner.rb @@ -138,6 +138,11 @@ def test_scan_strings_with_strict_int_delimiters assert_equal '-0b___', scanner.tokenize('-0b___') end + def test_scan_without_parse_symbols + scanner = Psych::ScalarScanner.new ClassLoader.new, parse_symbols: false + assert_equal ':foo', scanner.tokenize(':foo') + end + def test_scan_int_commas_and_underscores # NB: This test is to ensure backward compatibility with prior Psych versions, # not to test against any actual YAML specification. From c764269fbc4e2e58cea7c3880fa1485c7d2db123 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 2 Dec 2025 10:11:09 -0800 Subject: [PATCH 1549/2435] ZJIT: Only use make_equal_to for instructions with output It's used as an alternative to find-and-replace, so we should have nothing to replace. --- zjit/src/hir.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5ede64d42c2d80..b51a55db93c463 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1995,6 +1995,10 @@ impl Function { /// Replace `insn` with the new instruction `replacement`, which will get appended to `insns`. fn make_equal_to(&mut self, insn: InsnId, replacement: InsnId) { + assert!(self.insns[insn.0].has_output(), + "Don't use make_equal_to for instruction with no output"); + assert!(self.insns[replacement.0].has_output(), + "Can't replace instruction that has output with instruction that has no output"); // Don't push it to the block self.union_find.borrow_mut().make_equal_to(insn, replacement); } @@ -2960,9 +2964,8 @@ impl Function { let offset = SIZEOF_VALUE_I32 * ivar_index as i32; (as_heap, offset) }; - let replacement = self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val }); + self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val }); self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); - self.make_equal_to(insn_id, replacement); } _ => { self.push_insn_id(block, insn_id); } } @@ -3520,11 +3523,9 @@ impl Function { }; // If we're adding a new instruction, mark the two equivalent in the union-find and // do an incremental flow typing of the new instruction. - if insn_id != replacement_id { + if insn_id != replacement_id && self.insns[replacement_id.0].has_output() { self.make_equal_to(insn_id, replacement_id); - if self.insns[replacement_id.0].has_output() { - self.insn_types[replacement_id.0] = self.infer_type(replacement_id); - } + self.insn_types[replacement_id.0] = self.infer_type(replacement_id); } new_insns.push(replacement_id); // If we've just folded an IfTrue into a Jump, for example, don't bother copying From 19f0df04ff733f687bf33ebd9c85285c5e733253 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 2 Dec 2025 10:28:41 -0800 Subject: [PATCH 1550/2435] ZJIT: Fix definite assignment to work with multiple entry blocks --- zjit/src/hir.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b51a55db93c463..9d38336f8918fd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4165,11 +4165,13 @@ impl Function { // missing location. let mut assigned_in = vec![None; self.num_blocks()]; let rpo = self.rpo(); - // Begin with every block having every variable defined, except for the entry block, which - // starts with nothing defined. - assigned_in[self.entry_block.0] = Some(InsnSet::with_capacity(self.insns.len())); + // Begin with every block having every variable defined, except for the entry blocks, which + // start with nothing defined. + let entry_blocks = self.entry_blocks(); for &block in &rpo { - if block != self.entry_block { + if entry_blocks.contains(&block) { + assigned_in[block.0] = Some(InsnSet::with_capacity(self.insns.len())); + } else { let mut all_ones = InsnSet::with_capacity(self.insns.len()); all_ones.insert_all(); assigned_in[block.0] = Some(all_ones); From 3efd8c6764a5b324b7d04a12443c2f18dd74181b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 20:25:52 -0500 Subject: [PATCH 1551/2435] ZJIT: Inline Kernel#class (#15397) We generally know the receiver's class from profile info. I see 600k of these when running lobsters. --- zjit/bindgen/src/main.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/cruby_methods.rs | 18 +++++- zjit/src/hir.rs | 33 +++++++++-- zjit/src/hir/opt_tests.rs | 101 ++++++++++++++++++++++++++++++--- 5 files changed, 138 insertions(+), 16 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 0860c073cd17ed..2bae33713b274d 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -107,6 +107,7 @@ fn main() { .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") .allowlist_function("rb_class_inherited_p") + .allowlist_function("rb_class_real") .allowlist_type("ruby_encoding_consts") .allowlist_function("rb_hash_new") .allowlist_function("rb_hash_new_with_size") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 0048fd6daece27..5b083e56a8d432 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1971,6 +1971,7 @@ unsafe extern "C" { pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE; pub fn rb_obj_alloc(klass: VALUE) -> VALUE; pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE; + pub fn rb_class_real(klass: VALUE) -> VALUE; pub fn rb_class_inherited_p(scion: VALUE, ascendant: VALUE) -> VALUE; pub fn rb_backref_get() -> VALUE; pub fn rb_range_new(beg: VALUE, end: VALUE, excl: ::std::os::raw::c_int) -> VALUE; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0f5c992b88f892..71bf6ab83df0ad 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -186,11 +186,18 @@ pub fn init() -> Annotations { ($module:ident, $method_name:literal, $return_type:expr) => { annotate_builtin!($module, $method_name, $return_type, no_gc, leaf, elidable) }; - ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { + ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { let mut props = FnProperties::default(); props.return_type = $return_type; $(props.$properties = true;)+ annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); + }; + ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { + let mut props = FnProperties::default(); + props.return_type = $return_type; + props.inline = $inline; + $(props.$properties = true;)+ + annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); } } @@ -256,7 +263,7 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Integer", types::Integer); // TODO(max): Annotate rb_mKernel#class as returning types::Class. Right now there is a subtle // type system bug that causes an issue if we make it return types::Class. - annotate_builtin!(rb_mKernel, "class", types::HeapObject, leaf); + annotate_builtin!(rb_mKernel, "class", inline_kernel_class, types::HeapObject, leaf); annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); @@ -785,3 +792,10 @@ fn inline_kernel_respond_to_p( } Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) })) } + +fn inline_kernel_class(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[recv] = args else { return None; }; + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + let real_class = unsafe { rb_class_real(recv_class) }; + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(real_class) })) +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9d38336f8918fd..797cc167fc4513 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -862,6 +862,7 @@ pub enum Insn { // Invoke a builtin function InvokeBuiltin { bf: rb_builtin_function, + recv: InsnId, args: Vec, state: InsnId, leaf: bool, @@ -1922,7 +1923,7 @@ impl Function { state, reason, }, - &InvokeBuiltin { bf, ref args, state, leaf, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, leaf, return_type }, + &InvokeBuiltin { bf, recv, ref args, state, leaf, return_type } => InvokeBuiltin { bf, recv: find!(recv), args: find_vec!(args), state, leaf, return_type }, &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, &HashDup { val, state } => HashDup { val: find!(val), state }, &HashAref { hash, key, state } => HashAref { hash: find!(hash), key: find!(key), state }, @@ -2811,6 +2812,7 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); let replacement = self.push_insn(block, Insn::InvokeBuiltin { bf, + recv, args: vec![recv], state, leaf: true, @@ -3388,6 +3390,25 @@ impl Function { continue; } } + Insn::InvokeBuiltin { bf, recv, args, state, .. } => { + let props = ZJITState::get_method_annotations().get_builtin_properties(&bf).unwrap_or_default(); + // Try inlining the cfunc into HIR + let tmp_block = self.new_block(u32::MAX); + if let Some(replacement) = (props.inline)(self, tmp_block, recv, &args, state) { + // Copy contents of tmp_block to block + assert_ne!(block, tmp_block); + let insns = std::mem::take(&mut self.blocks[tmp_block.0].insns); + self.blocks[block.0].insns.extend(insns); + self.push_insn(block, Insn::IncrCounter(Counter::inline_cfunc_optimized_send_count)); + self.make_equal_to(insn_id, replacement); + if self.type_of(replacement).bit_equal(types::Any) { + // Not set yet; infer type + self.insn_types[replacement.0] = self.infer_type(replacement); + } + self.remove_block(tmp_block); + continue; + } + } _ => {} } self.push_insn_id(block, insn_id); @@ -3704,13 +3725,13 @@ impl Function { | &Insn::CCallVariadic { recv, ref args, state, .. } | &Insn::CCallWithFrame { recv, ref args, state, .. } | &Insn::SendWithoutBlockDirect { recv, ref args, state, .. } + | &Insn::InvokeBuiltin { recv, ref args, state, .. } | &Insn::InvokeSuper { recv, ref args, state, .. } => { worklist.push_back(recv); worklist.extend(args); worklist.push_back(state); } - &Insn::InvokeBuiltin { ref args, state, .. } - | &Insn::InvokeBlock { ref args, state, .. } => { + &Insn::InvokeBlock { ref args, state, .. } => { worklist.extend(args); worklist.push_back(state) } @@ -4323,6 +4344,7 @@ impl Function { | Insn::InvokeSuper { recv, ref args, .. } | Insn::CCallWithFrame { recv, ref args, .. } | Insn::CCallVariadic { recv, ref args, .. } + | Insn::InvokeBuiltin { recv, ref args, .. } | Insn::ArrayInclude { target: recv, elements: ref args, .. } => { self.assert_subtype(insn_id, recv, types::BasicObject)?; for &arg in args { @@ -4331,8 +4353,7 @@ impl Function { Ok(()) } // Instructions with a Vec of Ruby objects - Insn::InvokeBuiltin { ref args, .. } - | Insn::InvokeBlock { ref args, .. } + Insn::InvokeBlock { ref args, .. } | Insn::NewArray { elements: ref args, .. } | Insn::ArrayHash { elements: ref args, .. } | Insn::ArrayMax { elements: ref args, .. } => { @@ -5785,6 +5806,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, + recv: self_param, args, state: exit_id, leaf, @@ -5813,6 +5835,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, + recv: self_param, args, state: exit_id, leaf, diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 9809c8bffbee01..ab9bc1151381d7 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2611,9 +2611,10 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_iseq_optimized_send_count - v25:HeapObject = InvokeBuiltin leaf _bi20, v20 + v26:Class[Module@0x1010] = Const Value(VALUE(0x1010)) + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v25 + Return v26 "); } @@ -8937,15 +8938,15 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v40:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count - v43:HeapObject = InvokeBuiltin leaf _bi20, v40 + v44:Class[C@0x1000] = Const Value(VALUE(0x1000)) + IncrCounter inline_cfunc_optimized_send_count v13:StaticSymbol[:_lex_actions] = Const Value(VALUE(0x1038)) v15:TrueClass = Const Value(true) PatchPoint MethodRedefined(Class@0x1040, respond_to?@0x1048, cme:0x1050) PatchPoint NoSingletonClass(Class@0x1040) - v47:ModuleSubclass[class_exact*:Class@VALUE(0x1040)] = GuardType v43, ModuleSubclass[class_exact*:Class@VALUE(0x1040)] PatchPoint MethodRedefined(Class@0x1040, _lex_actions@0x1078, cme:0x1080) PatchPoint NoSingletonClass(Class@0x1040) - v51:TrueClass = Const Value(true) + v52:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v24:StaticSymbol[:CORRECT] = Const Value(VALUE(0x10a8)) @@ -8976,14 +8977,96 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count - v26:HeapObject = InvokeBuiltin leaf _bi20, v23 + v27:Class[C@0x1000] = Const Value(VALUE(0x1000)) + IncrCounter inline_cfunc_optimized_send_count PatchPoint MethodRedefined(Class@0x1038, name@0x1040, cme:0x1048) PatchPoint NoSingletonClass(Class@0x1038) - v30:ModuleSubclass[class_exact*:Class@VALUE(0x1038)] = GuardType v26, ModuleSubclass[class_exact*:Class@VALUE(0x1038)] IncrCounter inline_cfunc_optimized_send_count - v32:StringExact|NilClass = CCall v30, :Module#name@0x1070 + v33:StringExact|NilClass = CCall v27, :Module#name@0x1070 CheckInterrupts - Return v32 + Return v33 + "); + } + + #[test] + fn test_fold_kernel_class() { + eval(r#" + class C; end + def test(o) = o.class + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v25:Class[C@0x1000] = Const Value(VALUE(0x1000)) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_fold_fixnum_class() { + eval(r#" + def test = 5.class + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(Integer@0x1000, class@0x1008, cme:0x1010) + IncrCounter inline_iseq_optimized_send_count + v21:Class[Integer@0x1000] = Const Value(VALUE(0x1000)) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_fold_singleton_class() { + eval(r#" + def test = self.class + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Class[Object@0x1038] = Const Value(VALUE(0x1038)) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 "); } From 0af85a1fe291856d6b3d2b3bb68e6edb8cb44b4f Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 21:27:56 -0500 Subject: [PATCH 1552/2435] ZJIT: Optimize setivar with shape transition (#15375) Since we do a decent job of pre-sizing objects, don't handle the case where we would need to re-size an object. Also don't handle too-complex shapes. lobsters stats before: ``` Top-20 calls to C functions from JIT code (79.4% of total 90,051,140): rb_vm_opt_send_without_block: 19,762,433 (21.9%) rb_vm_setinstancevariable: 7,698,314 ( 8.5%) rb_hash_aref: 6,767,461 ( 7.5%) rb_vm_env_write: 5,373,080 ( 6.0%) rb_vm_send: 5,049,229 ( 5.6%) rb_vm_getinstancevariable: 4,535,259 ( 5.0%) rb_obj_is_kind_of: 3,746,306 ( 4.2%) rb_ivar_get_at_no_ractor_check: 3,745,237 ( 4.2%) rb_vm_invokesuper: 3,037,467 ( 3.4%) rb_ary_entry: 2,351,983 ( 2.6%) rb_vm_opt_getconstant_path: 1,344,740 ( 1.5%) rb_vm_invokeblock: 1,184,474 ( 1.3%) Hash#[]=: 1,064,288 ( 1.2%) rb_gc_writebarrier: 1,006,972 ( 1.1%) rb_ec_ary_new_from_values: 902,687 ( 1.0%) fetch: 898,667 ( 1.0%) rb_str_buf_append: 833,787 ( 0.9%) rb_class_allocate_instance: 822,024 ( 0.9%) Hash#fetch: 699,580 ( 0.8%) _bi20: 682,068 ( 0.8%) Top-4 setivar fallback reasons (100.0% of total 7,732,326): shape_transition: 6,032,109 (78.0%) not_monomorphic: 1,469,300 (19.0%) not_t_object: 172,636 ( 2.2%) too_complex: 58,281 ( 0.8%) ``` lobsters stats after: ``` Top-20 calls to C functions from JIT code (79.0% of total 88,322,656): rb_vm_opt_send_without_block: 19,777,880 (22.4%) rb_hash_aref: 6,771,589 ( 7.7%) rb_vm_env_write: 5,372,789 ( 6.1%) rb_gc_writebarrier: 5,195,527 ( 5.9%) rb_vm_send: 5,049,145 ( 5.7%) rb_vm_getinstancevariable: 4,538,485 ( 5.1%) rb_obj_is_kind_of: 3,746,241 ( 4.2%) rb_ivar_get_at_no_ractor_check: 3,745,172 ( 4.2%) rb_vm_invokesuper: 3,037,157 ( 3.4%) rb_ary_entry: 2,351,968 ( 2.7%) rb_vm_setinstancevariable: 1,703,337 ( 1.9%) rb_vm_opt_getconstant_path: 1,344,730 ( 1.5%) rb_vm_invokeblock: 1,184,290 ( 1.3%) Hash#[]=: 1,061,868 ( 1.2%) rb_ec_ary_new_from_values: 902,666 ( 1.0%) fetch: 898,666 ( 1.0%) rb_str_buf_append: 833,784 ( 0.9%) rb_class_allocate_instance: 821,778 ( 0.9%) Hash#fetch: 755,913 ( 0.9%) Top-4 setivar fallback reasons (100.0% of total 1,703,337): not_monomorphic: 1,472,405 (86.4%) not_t_object: 172,629 (10.1%) too_complex: 58,281 ( 3.4%) new_shape_needs_extension: 22 ( 0.0%) ``` I also noticed that primitive printing in HIR was broken so I fixed that. Co-authored-by: Aaron Patterson --- jit.c | 6 ++ zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 11 ++- zjit/src/cruby.rs | 4 +- zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 37 +++++++- zjit/src/hir/opt_tests.rs | 157 +++++++++++++++++++++++++++++---- zjit/src/hir_type/mod.rs | 51 +++++++++-- zjit/src/stats.rs | 2 + 9 files changed, 242 insertions(+), 28 deletions(-) diff --git a/jit.c b/jit.c index dd28bd6ad02aef..a886b66278c2aa 100644 --- a/jit.c +++ b/jit.c @@ -767,3 +767,9 @@ rb_yarv_str_eql_internal(VALUE str1, VALUE str2) } void rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint); + +attr_index_t +rb_jit_shape_capacity(shape_id_t shape_id) +{ + return RSHAPE_CAPACITY(shape_id); +} diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 2bae33713b274d..e07953ffd5df08 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -100,6 +100,7 @@ fn main() { .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") + .allowlist_function("rb_jit_shape_capacity") .allowlist_var("rb_invalid_shape_id") .allowlist_type("shape_id_fl_type") .allowlist_var("VM_KW_SPECIFIED_BITS_MAX") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 57ee65e91b754b..8c5fdc816d6450 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -350,6 +350,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val), &Insn::Const { val: Const::CInt64(val) } => gen_const_long(val), &Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val), + &Insn::Const { val: Const::CUInt32(val) } => gen_const_uint32(val), Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), @@ -475,7 +476,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadEC => gen_load_ec(), Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), - &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val))), + &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))), &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), @@ -1099,10 +1100,10 @@ fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd asm.load(Opnd::mem(64, recv, offset)) } -fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd) { +fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) { asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset); let recv = asm.load(recv); - asm.store(Opnd::mem(64, recv, offset), val); + asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val); } fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { @@ -1159,6 +1160,10 @@ fn gen_const_uint16(val: u16) -> lir::Opnd { Opnd::UImm(val as u64) } +fn gen_const_uint32(val: u32) -> lir::Opnd { + Opnd::UImm(val as u64) +} + /// Compile a basic block argument fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd { // Allocate a register or a stack slot diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 9ed3a1abf79ca2..b255c28e52cd17 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -999,8 +999,9 @@ mod manual_defs { use super::*; pub const SIZEOF_VALUE: usize = 8; + pub const BITS_PER_BYTE: usize = 8; pub const SIZEOF_VALUE_I32: i32 = SIZEOF_VALUE as i32; - pub const VALUE_BITS: u8 = 8 * SIZEOF_VALUE as u8; + pub const VALUE_BITS: u8 = BITS_PER_BYTE as u8 * SIZEOF_VALUE as u8; pub const RUBY_LONG_MIN: isize = std::os::raw::c_long::MIN as isize; pub const RUBY_LONG_MAX: isize = std::os::raw::c_long::MAX as isize; @@ -1382,6 +1383,7 @@ pub(crate) mod ids { name: thread_ptr name: self_ content: b"self" name: rb_ivar_get_at_no_ractor_check + name: _shape_id } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5b083e56a8d432..a80f3d83c5f414 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2232,4 +2232,5 @@ unsafe extern "C" { ); pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); + pub fn rb_jit_shape_capacity(shape_id: shape_id_t) -> attr_index_t; } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 797cc167fc4513..32d6f63b9ed74c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2949,10 +2949,34 @@ impl Function { self.push_insn_id(block, insn_id); continue; } let mut ivar_index: u16 = 0; + let mut next_shape_id = recv_type.shape(); if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { - // TODO(max): Shape transition - self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition)); - self.push_insn_id(block, insn_id); continue; + // Current shape does not contain this ivar; do a shape transition. + let current_shape_id = recv_type.shape(); + let class = recv_type.class(); + // We're only looking at T_OBJECT so ignore all of the imemo stuff. + assert!(recv_type.flags().is_t_object()); + next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(class, current_shape_id.0, id) }); + let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) }; + assert!(ivar_result, "New shape must have the ivar index"); + // If the VM ran out of shapes, or this class generated too many leaf, + // it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table). + let new_shape_too_complex = unsafe { rb_jit_shape_too_complex_p(next_shape_id.0) }; + // TODO(max): Is it OK to bail out here after making a shape transition? + if new_shape_too_complex { + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_too_complex)); + self.push_insn_id(block, insn_id); continue; + } + let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) }; + let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) }; + // If the new shape has a different capacity, or is TOO_COMPLEX, we'll have to + // reallocate it. + let needs_extension = next_capacity != current_capacity; + if needs_extension { + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_needs_extension)); + self.push_insn_id(block, insn_id); continue; + } + // Fall through to emitting the ivar write } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); @@ -2968,6 +2992,13 @@ impl Function { }; self.push_insn(block, Insn::StoreField { recv: ivar_storage, id, offset, val }); self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); + if next_shape_id != recv_type.shape() { + // Write the new shape ID + assert_eq!(SHAPE_ID_NUM_BITS, 32); + let shape_id = self.push_insn(block, Insn::Const { val: Const::CUInt32(next_shape_id.0) }); + let shape_id_offset = unsafe { rb_shape_id_offset() }; + self.push_insn(block, Insn::StoreField { recv: self_val, id: ID!(_shape_id), offset: shape_id_offset, val: shape_id }); + } } _ => { self.push_insn_id(block, insn_id); } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index ab9bc1151381d7..a174ac1b69280e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3484,6 +3484,39 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_specialize_complex_shape_definedivar() { + eval(r#" + class C + def test = defined?(@a) + end + obj = C.new + (0..1000).each do |i| + obj.instance_variable_set(:"@v#{i}", i) + end + (0..1000).each do |i| + obj.remove_instance_variable(:"@v#{i}") + end + obj.test + TEST = C.instance_method(:test) + "#); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + IncrCounter definedivar_fallback_too_complex + v10:StringExact|NilClass = DefinedIvar v6, :@a + CheckInterrupts + Return v10 + "); + } + #[test] fn test_specialize_monomorphic_setivar_already_in_shape() { eval(" @@ -3513,7 +3546,7 @@ mod hir_opt_tests { } #[test] - fn test_dont_specialize_monomorphic_setivar_with_shape_transition() { + fn test_specialize_monomorphic_setivar_with_shape_transition() { eval(" def test = @foo = 5 test @@ -3530,13 +3563,57 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - IncrCounter setivar_fallback_shape_transition - SetIvar v6, :@foo, v10 + v19:HeapBasicObject = GuardType v6, HeapBasicObject + v20:HeapBasicObject = GuardShape v19, 0x1000 + StoreField v20, :@foo@0x1001, v10 + WriteBarrier v20, v10 + v23:CUInt32[4194311] = Const CUInt32(4194311) + StoreField v20, :_shape_id@0x1002, v23 CheckInterrupts Return v10 "); } + #[test] + fn test_specialize_multiple_monomorphic_setivar_with_shape_transition() { + eval(" + def test + @foo = 1 + @bar = 2 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + v25:HeapBasicObject = GuardType v6, HeapBasicObject + v26:HeapBasicObject = GuardShape v25, 0x1000 + StoreField v26, :@foo@0x1001, v10 + WriteBarrier v26, v10 + v29:CUInt32[4194311] = Const CUInt32(4194311) + StoreField v26, :_shape_id@0x1002, v29 + v16:Fixnum[2] = Const Value(2) + PatchPoint SingleRactorMode + v31:HeapBasicObject = GuardType v6, HeapBasicObject + v32:HeapBasicObject = GuardShape v31, 0x1003 + StoreField v32, :@bar@0x1004, v16 + WriteBarrier v32, v16 + v35:CUInt32[4194312] = Const CUInt32(4194312) + StoreField v32, :_shape_id@0x1002, v35 + CheckInterrupts + Return v16 + "); + } + #[test] fn test_dont_specialize_setivar_with_t_data() { eval(" @@ -3611,6 +3688,9 @@ mod hir_opt_tests { (0..1000).each do |i| obj.instance_variable_set(:"@v#{i}", i) end + (0..1000).each do |i| + obj.remove_instance_variable(:"@v#{i}") + end obj.test TEST = C.instance_method(:test) "#); @@ -3626,7 +3706,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - IncrCounter setivar_fallback_shape_transition + IncrCounter setivar_fallback_too_complex SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -5417,6 +5497,43 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_optimize_getivar_with_too_complex_shape() { + eval(r#" + class C + attr_accessor :foo + end + obj = C.new + (0..1000).each do |i| + obj.instance_variable_set(:"@v#{i}", i) + end + (0..1000).each do |i| + obj.remove_instance_variable(:"@v#{i}") + end + def test(o) = o.foo + test obj + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:12: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter getivar_fallback_too_complex + v22:BasicObject = GetIvar v21, :@foo + CheckInterrupts + Return v22 + "); + } + #[test] fn test_optimize_send_with_block() { eval(r#" @@ -5697,8 +5814,11 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - IncrCounter setivar_fallback_shape_transition - SetIvar v26, :@foo, v16 + v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 + StoreField v29, :@foo@0x1039, v16 + WriteBarrier v29, v16 + v32:CUInt32[4194311] = Const CUInt32(4194311) + StoreField v29, :_shape_id@0x103a, v32 CheckInterrupts Return v16 "); @@ -5729,8 +5849,11 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - IncrCounter setivar_fallback_shape_transition - SetIvar v26, :@foo, v16 + v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 + StoreField v29, :@foo@0x1039, v16 + WriteBarrier v29, v16 + v32:CUInt32[4194311] = Const CUInt32(4194311) + StoreField v29, :_shape_id@0x103a, v32 CheckInterrupts Return v16 "); @@ -9109,18 +9232,22 @@ mod hir_opt_tests { SetLocal l0, EP@3, v27 v39:BasicObject = GetLocal l0, EP@3 PatchPoint SingleRactorMode - IncrCounter setivar_fallback_shape_transition - SetIvar v14, :@formatted, v39 - v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1008) - v60:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1040, block=0x1048 + v56:HeapBasicObject = GuardType v14, HeapBasicObject + v57:HeapBasicObject = GuardShape v56, 0x1000 + StoreField v57, :@formatted@0x1001, v39 + WriteBarrier v57, v39 + v60:CUInt32[4194311] = Const CUInt32(4194311) + StoreField v57, :_shape_id@0x1002, v60 + v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + v65:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 v48:BasicObject = GetLocal l0, EP@6 v49:BasicObject = GetLocal l0, EP@5 v50:BasicObject = GetLocal l0, EP@4 v51:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v60 + Return v65 "); } } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 8e862d74e71aba..a7ffcd1b77e882 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -89,13 +89,13 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(val)), Specialization::Int(val) if ty.is_subtype(types::CBool) => write!(f, "[{}]", val != 0), - Specialization::Int(val) if ty.is_subtype(types::CInt8) => write!(f, "[{}]", (val as i64) >> 56), - Specialization::Int(val) if ty.is_subtype(types::CInt16) => write!(f, "[{}]", (val as i64) >> 48), - Specialization::Int(val) if ty.is_subtype(types::CInt32) => write!(f, "[{}]", (val as i64) >> 32), + Specialization::Int(val) if ty.is_subtype(types::CInt8) => write!(f, "[{}]", (val & u8::MAX as u64) as i8), + Specialization::Int(val) if ty.is_subtype(types::CInt16) => write!(f, "[{}]", (val & u16::MAX as u64) as i16), + Specialization::Int(val) if ty.is_subtype(types::CInt32) => write!(f, "[{}]", (val & u32::MAX as u64) as i32), Specialization::Int(val) if ty.is_subtype(types::CInt64) => write!(f, "[{}]", val as i64), - Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val >> 56), - Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val >> 48), - Specialization::Int(val) if ty.is_subtype(types::CUInt32) => write!(f, "[{}]", val >> 32), + Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val & u8::MAX as u64), + Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val & u16::MAX as u64), + Specialization::Int(val) if ty.is_subtype(types::CUInt32) => write!(f, "[{}]", val & u32::MAX as u64), Specialization::Int(val) if ty.is_subtype(types::CUInt64) => write!(f, "[{}]", val), Specialization::Int(val) if ty.is_subtype(types::CPtr) => write!(f, "[{}]", Const::CPtr(val as *const u8).print(printer.ptr_map)), Specialization::Int(val) => write!(f, "[{val}]"), @@ -517,6 +517,18 @@ impl Type { pub fn print(self, ptr_map: &PtrPrintMap) -> TypePrinter<'_> { TypePrinter { inner: self, ptr_map } } + + pub fn num_bits(&self) -> u8 { + self.num_bytes() * crate::cruby::BITS_PER_BYTE as u8 + } + + pub fn num_bytes(&self) -> u8 { + if self.is_subtype(types::CUInt8) || self.is_subtype(types::CInt8) { return 1; } + if self.is_subtype(types::CUInt16) || self.is_subtype(types::CInt16) { return 2; } + if self.is_subtype(types::CUInt32) || self.is_subtype(types::CInt32) { return 4; } + // CUInt64, CInt64, CPtr, CNull, CDouble, or anything else defaults to 8 bytes + crate::cruby::SIZEOF_VALUE as u8 + } } #[cfg(test)] @@ -574,6 +586,33 @@ mod tests { assert_subtype(types::Any, types::Any); } + #[test] + fn from_const() { + let cint32 = Type::from_const(Const::CInt32(12)); + assert_subtype(cint32, types::CInt32); + assert_eq!(cint32.spec, Specialization::Int(12)); + assert_eq!(format!("{}", cint32), "CInt32[12]"); + + let cint32 = Type::from_const(Const::CInt32(-12)); + assert_subtype(cint32, types::CInt32); + assert_eq!(cint32.spec, Specialization::Int((-12i64) as u64)); + assert_eq!(format!("{}", cint32), "CInt32[-12]"); + + let cuint32 = Type::from_const(Const::CInt32(12)); + assert_subtype(cuint32, types::CInt32); + assert_eq!(cuint32.spec, Specialization::Int(12)); + + let cuint32 = Type::from_const(Const::CUInt32(0xffffffff)); + assert_subtype(cuint32, types::CUInt32); + assert_eq!(cuint32.spec, Specialization::Int(0xffffffff)); + assert_eq!(format!("{}", cuint32), "CUInt32[4294967295]"); + + let cuint32 = Type::from_const(Const::CUInt32(0xc00087)); + assert_subtype(cuint32, types::CUInt32); + assert_eq!(cuint32.spec, Specialization::Int(0xc00087)); + assert_eq!(format!("{}", cuint32), "CUInt32[12583047]"); + } + #[test] fn integer() { assert_subtype(Type::fixnum(123), types::Fixnum); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 890d92bc569a28..7a076b7cda2550 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -255,6 +255,8 @@ make_counters! { setivar_fallback_too_complex, setivar_fallback_frozen, setivar_fallback_shape_transition, + setivar_fallback_new_shape_too_complex, + setivar_fallback_new_shape_needs_extension, } // Ivar fallback counters that are summed as dynamic_getivar_count From 932762f29457ad1def6fbab7eca7bcbeeb58ea5c Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 17 Nov 2025 00:18:33 +0100 Subject: [PATCH 1553/2435] [ruby/rubygems] Increase connection pool to allow for up to 70% speed increase: - ### TL;DR Bundler is heavily limited by the connection pool which manages a single connection. By increasing the number of connection, we can drastiscally speed up the installation process when many gems need to be downloaded and installed. ### Benchmark There are various factors that are hard to control such as compilation time and network speed but after dozens of tests I can consistently get aroud 70% speed increase when downloading and installing 472 gems, most having no native extensions (on purpose). ``` # Before bundle install 28.60s user 12.70s system 179% cpu 23.014 total # After bundle install 30.09s user 15.90s system 281% cpu 16.317 total ``` You can find on this gist how this was benchmarked and the Gemfile used https://gist.github.com/Edouard-chin/c8e39148c0cdf324dae827716fbe24a0 ### Context A while ago in #869, Aaron introduced a connection pool which greatly improved Bundler speed. It was noted in the PR description that managing one connection was already good enough and it wasn't clear whether we needed more connections. Aaron also had the intuition that we may need to increase the pool for downloading gems and he was right. > We need to study how RubyGems uses connections and make a decision > based on request usage (e.g. only use one connection for many small > requests like bundler API, and maybe many connections for > downloading gems) When bundler downloads and installs gem in parallel https://github.com/ruby/rubygems/blob/4f85e02fdd89ee28852722dfed42a13c9f5c9193/bundler/lib/bundler/installer/parallel_installer.rb#L128 most threads have to wait for the only connection in the pool to be available which is not efficient. ### Solution This commit modifies the pool size for the fetcher that Bundler uses. RubyGems fetcher will continue to use a single connection. The bundler fetcher is used in 2 places. 1. When downloading gems https://github.com/ruby/rubygems/blob/4f85e02fdd89ee28852722dfed42a13c9f5c9193/bundler/lib/bundler/source/rubygems.rb#L481-L484 2. When grabing the index (not the compact index) using the `bundle install --full-index` flag. https://github.com/ruby/rubygems/blob/4f85e02fdd89ee28852722dfed42a13c9f5c9193/bundler/lib/bundler/fetcher/index.rb#L9 Having more connections in 2) is not any useful but tweaking the size based on where the fetcher is used is a bit tricky so I opted to modify it at the class level. I fiddle with the pool size and found that 5 seems to be the sweet spot at least for my environment. https://github.com/ruby/rubygems/commit/6063fd9963 --- lib/bundler/fetcher/gem_remote_fetcher.rb | 6 ++ lib/rubygems/remote_fetcher.rb | 3 +- lib/rubygems/request/connection_pools.rb | 7 ++- lib/rubygems/request/http_pool.rb | 15 +++-- .../fetcher/gem_remote_fetcher_spec.rb | 60 +++++++++++++++++++ .../test_gem_request_connection_pools.rb | 12 ++++ tool/bundler/test_gems.rb | 1 + tool/bundler/test_gems.rb.lock | 3 + 8 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb diff --git a/lib/bundler/fetcher/gem_remote_fetcher.rb b/lib/bundler/fetcher/gem_remote_fetcher.rb index 3fc7b682632b85..3c3c1826a1b1e2 100644 --- a/lib/bundler/fetcher/gem_remote_fetcher.rb +++ b/lib/bundler/fetcher/gem_remote_fetcher.rb @@ -5,6 +5,12 @@ module Bundler class Fetcher class GemRemoteFetcher < Gem::RemoteFetcher + def initialize(*) + super + + @pool_size = 5 + end + def request(*args) super do |req| req.delete("User-Agent") if headers["User-Agent"] diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 01788a6a5f17d5..805f7aaf82ed1a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -82,6 +82,7 @@ def initialize(proxy = nil, dns = nil, headers = {}) @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new + @pool_size = 1 @cert_files = Gem::Request.get_cert_files @headers = headers @@ -338,7 +339,7 @@ def proxy_for(proxy, uri) def pools_for(proxy) @pool_lock.synchronize do - @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files + @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size end end end diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 6c1b04ab6567d2..01e7e0629a903f 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -7,11 +7,12 @@ class << self attr_accessor :client end - def initialize(proxy_uri, cert_files) + def initialize(proxy_uri, cert_files, pool_size = 1) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new + @pool_size = pool_size end def pool_for(uri) @@ -20,9 +21,9 @@ def pool_for(uri) @pool_mutex.synchronize do @pools[key] ||= if https? uri - Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size) else - Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size) end end end diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 52543de41fdeab..468502ca6b64d6 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -9,12 +9,14 @@ class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri - def initialize(http_args, cert_files, proxy_uri) + def initialize(http_args, cert_files, proxy_uri, pool_size) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = Thread::SizedQueue.new 1 - @queue << nil + @pool_size = pool_size + + @queue = Thread::SizedQueue.new @pool_size + setup_queue end def checkout @@ -31,7 +33,8 @@ def close_all connection.finish end end - @queue.push(nil) + + setup_queue end private @@ -44,4 +47,8 @@ def setup_connection(connection) connection.start connection end + + def setup_queue + @pool_size.times { @queue.push(nil) } + end end diff --git a/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb new file mode 100644 index 00000000000000..df1a58d843623e --- /dev/null +++ b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "rubygems/remote_fetcher" +require "bundler/fetcher/gem_remote_fetcher" +require_relative "../../support/artifice/helpers/artifice" +require "bundler/vendored_persistent.rb" + +RSpec.describe Bundler::Fetcher::GemRemoteFetcher do + describe "Parallel download" do + it "download using multiple connections from the pool" do + unless Bundler.rubygems.provides?(">= 4.0.0.dev") + skip "This example can only run when RubyGems supports multiple http connection pool" + end + + require_relative "../../support/artifice/helpers/endpoint" + concurrent_ruby_path = Dir[scoped_base_system_gem_path.join("gems/concurrent-ruby-*/lib/concurrent-ruby")].first + $LOAD_PATH.unshift(concurrent_ruby_path) + require "concurrent-ruby" + + require_rack_test + responses = [] + + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + previous_client = Gem::Request::ConnectionPools.client + dummy_endpoint = Class.new(Endpoint) do + get "/foo" do + latch2.count_down + latch1.wait + + responses << "foo" + end + + get "/bar" do + responses << "bar" + + latch1.count_down + end + end + + Artifice.activate_with(dummy_endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + first_request = Thread.new do + subject.fetch_path("https://example.org/foo") + end + second_request = Thread.new do + latch2.wait + subject.fetch_path("https://example.org/bar") + end + + [first_request, second_request].each(&:join) + + expect(responses).to eq(["bar", "foo"]) + ensure + Artifice.deactivate + Gem::Request::ConnectionPools.client = previous_client + end + end +end diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb index 966447bff64192..2860deabf7132d 100644 --- a/test/rubygems/test_gem_request_connection_pools.rb +++ b/test/rubygems/test_gem_request_connection_pools.rb @@ -148,4 +148,16 @@ def test_thread_waits_for_connection end end.join end + + def test_checkouts_multiple_connections_from_the_pool + uri = Gem::URI.parse("http://example/some_endpoint") + pools = Gem::Request::ConnectionPools.new nil, [], 2 + pool = pools.pool_for uri + + pool.checkout + + Thread.new do + assert_not_nil(pool.checkout) + end.join + end end diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 9776a96b90eded..ddc19e2939d447 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -11,6 +11,7 @@ gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" +gem "concurrent-ruby" gem "psych" gem "etc", platforms: [:ruby, :windows] gem "open3" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index bc103af8605282..b11a9607255aa1 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -4,6 +4,7 @@ GEM base64 (0.3.0) builder (3.3.0) compact_index (0.15.0) + concurrent-ruby (1.3.5) date (3.5.0) date (3.5.0-java) etc (1.4.6) @@ -59,6 +60,7 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) compact_index (~> 0.15.0) + concurrent-ruby etc fiddle open3 @@ -75,6 +77,7 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 date (3.5.0) sha256=5e74fd6c04b0e65d97ad4f3bb5cb2d8efb37f386cc848f46310b4593ffc46ee5 date (3.5.0-java) sha256=d6876651299185b935e1b834a353e3a1d1db054be478967e8104e30a9a8f1127 etc (1.4.6) sha256=0f7e9e7842ea5e3c3bd9bc81746ebb8c65ea29e4c42a93520a0d638129c7de01 From d58a45d32ffe8afed1685e54017fb81cea898867 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 4 Dec 2025 09:06:10 +0100 Subject: [PATCH 1554/2435] [ruby/json] Fix a regression in parsing of unicode surogate pairs Fix: https://github.com/ruby/json/issues/912 In the case of surogate pairs we consume two backslashes, so `json_next_backslash` need to ensure it's not sending us back in the stream. https://github.com/ruby/json/commit/0fce370c41 --- ext/json/parser/parser.c | 4 +++- test/json/json_parser_test.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 5b7cd835cd7d1e..c84c7ed660a06d 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -651,7 +651,9 @@ static inline const char *json_next_backslash(const char *pe, const char *string positions->size--; const char *next_position = positions->positions[0]; positions->positions++; - return next_position; + if (next_position >= pe) { + return next_position; + } } if (positions->has_more) { diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 3e662bda324ba5..257e4f1736a2d0 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -325,6 +325,13 @@ def test_invalid_unicode_escape assert_raise(JSON::ParserError) { parse('"\u111___"') } end + def test_unicode_followed_by_newline + # Ref: https://github.com/ruby/json/issues/912 + assert_equal "🌌\n".bytes, JSON.parse('"\ud83c\udf0c\n"').bytes + assert_equal "🌌\n", JSON.parse('"\ud83c\udf0c\n"') + assert_predicate JSON.parse('"\ud83c\udf0c\n"'), :valid_encoding? + end + def test_invalid_surogates assert_raise(JSON::ParserError) { parse('"\\uD800"') } assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') } From d343968ec3a899a29ff4e330dc914d67972ebe85 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 4 Dec 2025 09:12:10 +0100 Subject: [PATCH 1555/2435] [ruby/json] Release 2.17.1 https://github.com/ruby/json/commit/e5e4fd558e --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index b7de7c27e21446..4ed61c43c8b187 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.17.0' + VERSION = '2.17.1' end From 6917321686ad99551925d87ef353ddb21149d3db Mon Sep 17 00:00:00 2001 From: git Date: Thu, 4 Dec 2025 08:14:02 +0000 Subject: [PATCH 1556/2435] Update default gems list at d343968ec3a899a29ff4e330dc914d [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f3e7f1ae0e3a6e..89f45deb99cf7b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -198,7 +198,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.4.0.dev -* json 2.17.0 +* json 2.17.1 * net-http 0.8.0 * openssl 4.0.0.pre * optparse 0.8.0 From f8231dacd570e8c96c385fb9691719756b1dbc7d Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 4 Dec 2025 14:29:35 +0900 Subject: [PATCH 1557/2435] Ractor.store_if_absent should not warn ```ruby $VERBOSE = true Ractor.store_if_absent :key do end #=> warning: the block passed to 'Ractor.store_if_absent' defined at :474 may be ignored ``` --- ractor.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ractor.rb b/ractor.rb index 7c00e148a120aa..3c0c6744e6148e 100644 --- a/ractor.rb +++ b/ractor.rb @@ -472,6 +472,7 @@ def self.[]=(sym, val) # }.map(&:value).uniq.size #=> 1 and f() is called only once # def self.store_if_absent(sym) + Primitive.attr! :use_block Primitive.ractor_local_value_store_if_absent(sym) end From 7f41c3e7b1373f35cc073241b3773289be9be03e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 4 Dec 2025 17:35:38 +0900 Subject: [PATCH 1558/2435] Add `rb_eval_cmd_call_kw` to shortcut --- internal/vm.h | 1 + signal.c | 2 +- variable.c | 2 +- vm_eval.c | 14 ++++++++++++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/internal/vm.h b/internal/vm.h index e5ed47afae0e29..7fae590d198fdd 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -78,6 +78,7 @@ void rb_check_stack_overflow(void); VALUE rb_block_call2(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, VALUE data2, long flags); struct vm_ifunc *rb_current_ifunc(void); VALUE rb_gccct_clear_table(VALUE); +VALUE rb_eval_cmd_call_kw(VALUE cmd, int argc, const VALUE *argv, int kw_splat); #if USE_YJIT || USE_ZJIT /* vm_exec.c */ diff --git a/signal.c b/signal.c index 5971e12ee9b00f..12af9058a25439 100644 --- a/signal.c +++ b/signal.c @@ -1066,7 +1066,7 @@ signal_exec(VALUE cmd, int sig) EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { VALUE signum = INT2NUM(sig); - rb_eval_cmd_kw(cmd, rb_ary_new3(1, signum), RB_NO_KEYWORDS); + rb_eval_cmd_call_kw(cmd, 1, &signum, RB_NO_KEYWORDS); } EC_POP_TAG(); ec = GET_EC(); diff --git a/variable.c b/variable.c index faeffec660a9f2..47b521856676dd 100644 --- a/variable.c +++ b/variable.c @@ -862,7 +862,7 @@ rb_define_virtual_variable( static void rb_trace_eval(VALUE cmd, VALUE val) { - rb_eval_cmd_kw(cmd, rb_ary_new3(1, val), RB_NO_KEYWORDS); + rb_eval_cmd_call_kw(cmd, 1, &val, RB_NO_KEYWORDS); } VALUE diff --git a/vm_eval.c b/vm_eval.c index 281d63a1b8268a..12bdabc3302f68 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -2147,6 +2147,17 @@ rb_eval_string_wrap(const char *str, int *pstate) VALUE rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat) +{ + Check_Type(arg, T_ARRAY); + int argc = RARRAY_LENINT(arg); + const VALUE *argv = RARRAY_CONST_PTR(arg); + VALUE val = rb_eval_cmd_call_kw(cmd, argc, argv, kw_splat); + RB_GC_GUARD(arg); + return val; +} + +VALUE +rb_eval_cmd_call_kw(VALUE cmd, int argc, const VALUE *argv, int kw_splat) { enum ruby_tag_type state; volatile VALUE val = Qnil; /* OK */ @@ -2155,8 +2166,7 @@ rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat) EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { if (!RB_TYPE_P(cmd, T_STRING)) { - val = rb_funcallv_kw(cmd, idCall, RARRAY_LENINT(arg), - RARRAY_CONST_PTR(arg), kw_splat); + val = rb_funcallv_kw(cmd, idCall, argc, argv, kw_splat); } else { val = eval_string_with_cref(rb_vm_top_self(), cmd, NULL, 0, 0); From b872fbe3dd3fe2601e4cddfd3179b5166dc121d4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 4 Dec 2025 18:07:32 +0900 Subject: [PATCH 1559/2435] Deprecate `rb_eval_cmd_kw` --- include/ruby/internal/intern/vm.h | 3 +++ spec/ruby/optional/capi/ext/kernel_spec.c | 4 +++ spec/ruby/optional/capi/kernel_spec.rb | 30 ++++++++++++----------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/ruby/internal/intern/vm.h b/include/ruby/internal/intern/vm.h index 29e0c7f534acb1..779f77fe91fcb5 100644 --- a/include/ruby/internal/intern/vm.h +++ b/include/ruby/internal/intern/vm.h @@ -95,6 +95,7 @@ VALUE rb_check_funcall(VALUE recv, ID mid, int argc, const VALUE *argv); */ VALUE rb_check_funcall_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat); +#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL /** * This API is practically a variant of rb_proc_call_kw() now. Historically * when there still was a concept called `$SAFE`, this was an API for that. @@ -109,7 +110,9 @@ VALUE rb_check_funcall_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int k * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. * @return What the command evaluates to. */ +RBIMPL_ATTR_DEPRECATED_INTERNAL(4.0) VALUE rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat); +#endif /** * Identical to rb_funcallv(), except it takes Ruby's array instead of C's. diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index ff71a7e5892219..a8fed21b5900b6 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -117,9 +117,11 @@ VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) { return rb_eval_string(RSTRING_PTR(str)); } +#ifndef RUBY_VERSION_IS_4_0 VALUE kernel_spec_rb_eval_cmd_kw(VALUE self, VALUE cmd, VALUE args, VALUE kw_splat) { return rb_eval_cmd_kw(cmd, args, NUM2INT(kw_splat)); } +#endif VALUE kernel_spec_rb_raise(VALUE self, VALUE hash) { rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("before"))); @@ -403,7 +405,9 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_category_warn_deprecated_with_integer_extra_value", kernel_spec_rb_category_warn_deprecated_with_integer_extra_value, 1); rb_define_method(cls, "rb_ensure", kernel_spec_rb_ensure, 4); rb_define_method(cls, "rb_eval_string", kernel_spec_rb_eval_string, 1); +#ifndef RUBY_VERSION_IS_4_0 rb_define_method(cls, "rb_eval_cmd_kw", kernel_spec_rb_eval_cmd_kw, 3); +#endif rb_define_method(cls, "rb_raise", kernel_spec_rb_raise, 1); rb_define_method(cls, "rb_throw", kernel_spec_rb_throw, 1); rb_define_method(cls, "rb_throw_obj", kernel_spec_rb_throw_obj, 2); diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index d915a72c22af75..6633ee50c1f8ac 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -635,22 +635,24 @@ def proc_caller end end - describe "rb_eval_cmd_kw" do - it "evaluates a string of ruby code" do - @s.rb_eval_cmd_kw("1+1", [], 0).should == 2 - end + ruby_version_is ""..."4.0" do + describe "rb_eval_cmd_kw" do + it "evaluates a string of ruby code" do + @s.rb_eval_cmd_kw("1+1", [], 0).should == 2 + end - it "calls a proc with the supplied arguments" do - @s.rb_eval_cmd_kw(-> *x { x.map { |i| i + 1 } }, [1, 3, 7], 0).should == [2, 4, 8] - end + it "calls a proc with the supplied arguments" do + @s.rb_eval_cmd_kw(-> *x { x.map { |i| i + 1 } }, [1, 3, 7], 0).should == [2, 4, 8] + end - it "calls a proc with keyword arguments if kw_splat is non zero" do - a_proc = -> *x, **y { - res = x.map { |i| i + 1 } - y.each { |k, v| res << k; res << v } - res - } - @s.rb_eval_cmd_kw(a_proc, [1, 3, 7, {a: 1, b: 2, c: 3}], 1).should == [2, 4, 8, :a, 1, :b, 2, :c, 3] + it "calls a proc with keyword arguments if kw_splat is non zero" do + a_proc = -> *x, **y { + res = x.map { |i| i + 1 } + y.each { |k, v| res << k; res << v } + res + } + @s.rb_eval_cmd_kw(a_proc, [1, 3, 7, {a: 1, b: 2, c: 3}], 1).should == [2, 4, 8, :a, 1, :b, 2, :c, 3] + end end end From 8099e9d2d1b47ef4ce2fa184bea079a270ceb466 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 4 Dec 2025 18:08:23 +0900 Subject: [PATCH 1560/2435] [DOC] Fix a macro name --- doc/extension.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/extension.rdoc b/doc/extension.rdoc index ba59d107ab4c84..6cf4c4926c93fc 100644 --- a/doc/extension.rdoc +++ b/doc/extension.rdoc @@ -2047,7 +2047,7 @@ the *_kw functions introduced in Ruby 2.7. #define rb_proc_call_with_block_kw(p, c, v, b, kw) rb_proc_call_with_block(p, c, v, b) #define rb_method_call_kw(c, v, m, kw) rb_method_call(c, v, m) #define rb_method_call_with_block_kw(c, v, m, b, kw) rb_method_call_with_block(c, v, m, b) - #define rb_eval_cmd_kwd(c, a, kw) rb_eval_cmd(c, a, 0) + #define rb_eval_cmd_kw(c, a, kw) rb_eval_cmd(c, a, 0) #endif == Appendix C. Functions available for use in extconf.rb From 465a86c3417d2936c311d9571aa9b6494a83eed8 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:58:51 +0100 Subject: [PATCH 1561/2435] [ruby/prism] Fix `%Q` with newline delimiter and heredoc interpolation The lexer did not jump to the `heredoc_end`, causing the heredoc end delimiter to be parsed twice. Normally the heredocs get flushed when a newline is encountered. But because the newline is part of the string delimiter, that codepath is not taken. Fixes [Bug #21758] https://github.com/ruby/prism/commit/7440eb4b11 --- prism/prism.c | 7 ++++++ .../heredoc_percent_q_newline_delimiter.txt | 11 ++++++++++ .../heredoc_percent_q_newline_delimiter.txt | 22 +++++++++++++++++++ test/prism/ruby/parser_test.rb | 3 +++ test/prism/ruby/ruby_parser_test.rb | 1 + 5 files changed, 44 insertions(+) create mode 100644 test/prism/errors/heredoc_percent_q_newline_delimiter.txt create mode 100644 test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt diff --git a/prism/prism.c b/prism/prism.c index 02247734e2ace7..77ac74192e6f66 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -11612,6 +11612,13 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_LABEL_END); } + // When the delimiter itself is a newline, we won't + // get a chance to flush heredocs in the usual places since + // the newline is already consumed. + if (term == '\n' && parser->heredoc_end) { + parser_flush_heredoc_end(parser); + } + lex_state_set(parser, PM_LEX_STATE_END); lex_mode_pop(parser); LEX(PM_TOKEN_STRING_END); diff --git a/test/prism/errors/heredoc_percent_q_newline_delimiter.txt b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt new file mode 100644 index 00000000000000..73664c071f6208 --- /dev/null +++ b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt @@ -0,0 +1,11 @@ +%q +#{< Date: Fri, 5 Dec 2025 00:27:45 +0900 Subject: [PATCH 1562/2435] [Bug #21764] Propagate the encoding of ID to warning --- io.c | 20 ++++---------------- spec/ruby/language/predefined_spec.rb | 16 ++++++++++++++-- string.c | 17 ++++++++++++++++- test/ruby/test_string.rb | 14 +++++++++++++- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/io.c b/io.c index a5a8139f2914c9..549e7e1317ef30 100644 --- a/io.c +++ b/io.c @@ -8641,31 +8641,19 @@ rb_f_printf(int argc, VALUE *argv, VALUE _) return Qnil; } -static void -deprecated_str_setter(VALUE val, ID id, VALUE *var) -{ - rb_str_setter(val, id, &val); - if (!NIL_P(val)) { - rb_warn_deprecated("'%s'", NULL, rb_id2name(id)); - } - *var = val; -} +extern void rb_deprecated_str_setter(VALUE val, ID id, VALUE *var); static void deprecated_rs_setter(VALUE val, ID id, VALUE *var) { + rb_deprecated_str_setter(val, id, &val); if (!NIL_P(val)) { - if (!RB_TYPE_P(val, T_STRING)) { - rb_raise(rb_eTypeError, "value of %"PRIsVALUE" must be String", rb_id2str(id)); - } if (rb_str_equal(val, rb_default_rs)) { val = rb_default_rs; } else { val = rb_str_frozen_bare_string(val); } - rb_enc_str_coderange(val); - rb_warn_deprecated("'%s'", NULL, rb_id2name(id)); } *var = val; } @@ -15730,7 +15718,7 @@ Init_IO(void) rb_define_method(rb_cIO, "initialize", rb_io_initialize, -1); rb_output_fs = Qnil; - rb_define_hooked_variable("$,", &rb_output_fs, 0, deprecated_str_setter); + rb_define_hooked_variable("$,", &rb_output_fs, 0, rb_deprecated_str_setter); rb_default_rs = rb_fstring_lit("\n"); /* avoid modifying RS_default */ rb_vm_register_global_object(rb_default_rs); @@ -15740,7 +15728,7 @@ Init_IO(void) rb_gvar_ractor_local("$/"); // not local but ractor safe rb_define_hooked_variable("$-0", &rb_rs, 0, deprecated_rs_setter); rb_gvar_ractor_local("$-0"); // not local but ractor safe - rb_define_hooked_variable("$\\", &rb_output_rs, 0, deprecated_str_setter); + rb_define_hooked_variable("$\\", &rb_output_rs, 0, rb_deprecated_str_setter); rb_define_virtual_variable("$_", get_LAST_READ_LINE, set_LAST_READ_LINE); rb_gvar_ractor_local("$_"); diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index f2488615aaec37..fc1667a38fe8d0 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -748,6 +748,10 @@ def foo it "raises a TypeError if assigned a boolean" do -> { $/ = true }.should raise_error(TypeError, 'value of $/ must be String') end + + it "warns if assigned non-nil" do + -> { $/ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\/' is deprecated/) + end end describe "Predefined global $-0" do @@ -825,6 +829,10 @@ def foo it "raises a TypeError if assigned a boolean" do -> { $-0 = true }.should raise_error(TypeError, 'value of $-0 must be String') end + + it "warns if assigned non-nil" do + -> { $-0 = "_" }.should complain(/warning: (?:non-nil )?[`']\$-0' is deprecated/) + end end describe "Predefined global $\\" do @@ -864,6 +872,10 @@ def foo -> { $\ = 1 }.should raise_error(TypeError, 'value of $\ must be String') -> { $\ = true }.should raise_error(TypeError, 'value of $\ must be String') end + + it "warns if assigned non-nil" do + -> { $\ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\\' is deprecated/) + end end describe "Predefined global $," do @@ -880,7 +892,7 @@ def foo end it "warns if assigned non-nil" do - -> { $, = "_" }.should complain(/warning: [`']\$,' is deprecated/) + -> { $, = "_" }.should complain(/warning: (?:non-nil )?[`']\$,' is deprecated/) end end @@ -917,7 +929,7 @@ def foo end it "warns if assigned non-nil" do - -> { $; = "_" }.should complain(/warning: [`']\$;' is deprecated/) + -> { $; = "_" }.should complain(/warning: (?:non-nil )?[`']\$;' is deprecated/) end end diff --git a/string.c b/string.c index 0370fc5d1e02e6..065e8c4d8c94dc 100644 --- a/string.c +++ b/string.c @@ -11375,6 +11375,21 @@ rb_str_setter(VALUE val, ID id, VALUE *var) *var = val; } +static void +nil_setter_warning(ID id) +{ + rb_warn_deprecated("'%"PRIsVALUE"'", NULL, rb_id2str(id)); +} + +void +rb_deprecated_str_setter(VALUE val, ID id, VALUE *var) +{ + rb_str_setter(val, id, var); + if (!NIL_P(*var)) { + nil_setter_warning(id); + } +} + static void rb_fs_setter(VALUE val, ID id, VALUE *var) { @@ -11385,7 +11400,7 @@ rb_fs_setter(VALUE val, ID id, VALUE *var) rb_id2str(id)); } if (!NIL_P(val)) { - rb_warn_deprecated("'$;'", NULL); + nil_setter_warning(id); } *var = val; } diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 50dc5ec2f95875..6fdc2bdaa8cdfb 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1886,6 +1886,17 @@ def test_fs assert_raise_with_message(TypeError, /\$;/) { $; = [] } + name = "\u{5206 5217}" + assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") + do; + alias $#{name} $; + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } + assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } + end; + end + + def test_fs_gc + return unless @cls == String assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}") bug = '[ruby-core:79582] $; must not be GCed' @@ -2761,7 +2772,7 @@ def (hyphen = Object.new).to_str; "-"; end assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/)) end - def test_fs_setter + def test_rs return unless @cls == String assert_raise(TypeError) { $/ = 1 } @@ -2769,6 +2780,7 @@ def test_fs_setter assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") do; alias $#{name} $/ + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } end; end From 29a12297c3594ed24102d62e16dfd6d4e5e328f3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 01:04:08 +0900 Subject: [PATCH 1563/2435] Refine non-nil warnings for the deprecated variables --- string.c | 2 +- test/ruby/test_io.rb | 8 ++++---- test/ruby/test_string.rb | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/string.c b/string.c index 065e8c4d8c94dc..34c79eca8e5cff 100644 --- a/string.c +++ b/string.c @@ -11378,7 +11378,7 @@ rb_str_setter(VALUE val, ID id, VALUE *var) static void nil_setter_warning(ID id) { - rb_warn_deprecated("'%"PRIsVALUE"'", NULL, rb_id2str(id)); + rb_warn_deprecated("non-nil '%"PRIsVALUE"'", NULL, rb_id2str(id)); } void diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index dd8ccbc96a63a5..1adf47ac51a2d6 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -2907,10 +2907,10 @@ def test_print end def test_print_separators - EnvUtil.suppress_warning { - $, = ':' - $\ = "\n" - } + assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"} + assert_raise(TypeError) {$, = 1} + assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"} + assert_raise(TypeError) {$/ = 1} pipe(proc do |w| w.print('a') EnvUtil.suppress_warning {w.print('a','b','c')} diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 6fdc2bdaa8cdfb..1fe0629331ec30 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1883,9 +1883,13 @@ def test_split_with_block def test_fs return unless @cls == String - assert_raise_with_message(TypeError, /\$;/) { - $; = [] - } + begin + fs = $; + assert_deprecated_warning(/non-nil '\$;'/) {$; = "x"} + assert_raise_with_message(TypeError, /\$;/) {$; = []} + ensure + EnvUtil.suppress_warning {$; = fs} + end name = "\u{5206 5217}" assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") do; @@ -2775,7 +2779,13 @@ def (hyphen = Object.new).to_str; "-"; end def test_rs return unless @cls == String - assert_raise(TypeError) { $/ = 1 } + begin + rs = $/ + assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" } + assert_raise(TypeError) { $/ = 1 } + ensure + EnvUtil.suppress_warning { $/ = rs } + end name = "\u{5206 884c}" assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") do; From 3730022787086852fa2fbc94ffda6ec8c8fbc0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 4 Dec 2025 17:25:45 +0100 Subject: [PATCH 1564/2435] Remove mismatched indentations warning (#15410) --- test/ruby/test_zjit.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 6609bb2461c31d..00550bbe2058e2 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1081,20 +1081,20 @@ def test(x) end def test_opt_duparray_send_include_p_redefined - assert_compiles '[:true, :false]', %q{ - class Array - alias_method :old_include?, :include? - def include?(x) - old_include?(x) ? :true : :false - end + assert_compiles '[:true, :false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false end + end - def test(x) - [:y, 1].include?(x) - end - [test(1), test("n")] - }, insns: [:opt_duparray_send], call_threshold: 1 - end + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end def test_opt_newarray_send_hash assert_compiles 'Integer', %q{ From f2cd772329b8d07e29ed114480ff99ad36acbd75 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 01:13:55 +0900 Subject: [PATCH 1565/2435] (experimental) RUBY_TYPED_FROZEN_SHAREABLE_NO_REC `T_DATA` has a flag `RUBY_TYPED_FROZEN_SHAREABLE` which means if the `T_DATA` object is frozen, it can be sharable. On the `Ractor.make_sharable(obj)`, rechable objects from the `T_DATA` object will be apply `Ractor.make_shareable` recursively. `RUBY_TYPED_FROZEN_SHAREABLE_NO_REC` is similar to the `RUBY_TYPED_FROZEN_SHAREABLE`, but doesn't apply `Ractor.make_sharable` recursively for children. If it refers to unshareable objects, it will simply raise an error. I'm not sure this pattern is common or not, so it is not in public. If we find more cases, we can discuss publication. --- include/ruby/internal/core/rtypeddata.h | 6 +++ ractor.c | 50 ++++++++++++++++++------- ractor_core.h | 3 ++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index aaf8f7997c8459..ee79c2e2a90a18 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -157,6 +157,12 @@ rbimpl_typeddata_flags { */ RUBY_TYPED_FROZEN_SHAREABLE = RUBY_FL_SHAREABLE, + // experimental flag + // Similar to RUBY_TYPED_FROZEN_SHAREABLE, but doesn't make shareable + // reachable objects from this T_DATA object on the Ractor.make_shareable. + // If it refers to unsharable objects, simply raise an error. + // RUBY_TYPED_FROZEN_SHAREABLE_NO_REC = RUBY_FL_FINALIZE, + /** * This flag has something to do with our garbage collector. These days * ruby objects are "generational". There are those who are young and diff --git a/ractor.c b/ractor.c index ea09583c5f7f7c..6bd38b6ab875f0 100644 --- a/ractor.c +++ b/ractor.c @@ -1427,6 +1427,26 @@ allow_frozen_shareable_p(VALUE obj) return false; } +static enum obj_traverse_iterator_result +make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result) +{ + if (!RB_OBJ_FROZEN_RAW(obj)) { + rb_funcall(obj, idFreeze, 0); + + if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) { + rb_raise(rb_eRactorError, "#freeze does not freeze object correctly"); + } + + if (RB_OBJ_SHAREABLE_P(obj)) { + return traverse_skip; + } + } + + return result; +} + +static int obj_refer_only_shareables_p(VALUE obj); + static enum obj_traverse_iterator_result make_shareable_check_shareable(VALUE obj) { @@ -1436,7 +1456,21 @@ make_shareable_check_shareable(VALUE obj) return traverse_skip; } else if (!allow_frozen_shareable_p(obj)) { - if (rb_obj_is_proc(obj)) { + VM_ASSERT(RB_TYPE_P(obj, T_DATA)); + const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); + + if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) { + if (obj_refer_only_shareables_p(obj)) { + make_shareable_check_shareable_freeze(obj, traverse_skip); + RB_OBJ_SET_SHAREABLE(obj); + return traverse_skip; + } + else { + rb_raise(rb_eRactorError, + "can not make shareable object for %+"PRIsVALUE" because it refers unshareable objects", obj); + } + } + else if (rb_obj_is_proc(obj)) { rb_proc_ractor_make_shareable(obj, Qundef); return traverse_cont; } @@ -1466,19 +1500,7 @@ make_shareable_check_shareable(VALUE obj) break; } - if (!RB_OBJ_FROZEN_RAW(obj)) { - rb_funcall(obj, idFreeze, 0); - - if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) { - rb_raise(rb_eRactorError, "#freeze does not freeze object correctly"); - } - - if (RB_OBJ_SHAREABLE_P(obj)) { - return traverse_skip; - } - } - - return traverse_cont; + return make_shareable_check_shareable_freeze(obj, traverse_cont); } static enum obj_traverse_iterator_result diff --git a/ractor_core.h b/ractor_core.h index 81374c0769f6ca..d6f9d4eda0fd4b 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -9,6 +9,9 @@ #define RACTOR_CHECK_MODE (VM_CHECK_MODE || RUBY_DEBUG) && (SIZEOF_UINT64_T == SIZEOF_VALUE) #endif +// experimental flag because it is not sure it is the common pattern +#define RUBY_TYPED_FROZEN_SHAREABLE_NO_REC RUBY_FL_FINALIZE + struct rb_ractor_sync { // ractor lock rb_nativethread_lock_t lock; From 4c893e2ff1077b977483da14c0540e9247b87c7e Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 01:19:55 +0900 Subject: [PATCH 1566/2435] Method and UnboundMethod can be sharable with `RUBY_TYPED_FROZEN_SHAREABLE_NO_REC`, if the receiver object is shareable on Method objects. --- bootstraptest/test_ractor.rb | 22 ++++++++++++++++++++++ proc.c | 2 +- test/ruby/test_ractor.rb | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 6c3a45148c23e1..cc00fa3586af93 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1203,6 +1203,28 @@ def /(other) end } +# Ractor.make_sharable(Method/UnboundMethod) +assert_equal 'true', %q{ + # raise because receiver is unsharable + begin + _m0 = Ractor.make_shareable(self.method(:__id__)) + rescue => e + raise e unless e.message =~ /can not make shareable object/ + else + raise "no error" + end + + # Method with sharable receiver + M1 = Ractor.make_shareable(Object.method(:__id__)) + + # UnboundMethod + M2 = Ractor.make_shareable(Object.instance_method(:__id__)) + + Ractor.new do + Object.__id__ == M1.call && M1.call == M2.bind_call(Object) + end.value +} + # Ractor.shareable?(recursive_objects) assert_equal '[false, false]', %q{ y = [] diff --git a/proc.c b/proc.c index 2b14c38938b8f0..2df8fbee5e23a9 100644 --- a/proc.c +++ b/proc.c @@ -1668,7 +1668,7 @@ static const rb_data_type_t method_data_type = { NULL, // No external memory to report, bm_mark_and_move, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_FROZEN_SHAREABLE_NO_REC }; VALUE diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index efc67338e4c0dd..09c470911a5aaa 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -47,7 +47,7 @@ def test_shareability_error_uses_inspect def x.to_s raise "this should not be called" end - assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) + assert_unshareable(x, "can not make shareable object for # because it refers unshareable objects", exception: Ractor::Error) end def test_default_thread_group From 6fe1c1591106ef428f42cd5601be1cab994dae9a Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 18 Nov 2025 22:40:42 +0900 Subject: [PATCH 1567/2435] [ruby/openssl] Revert "rewriting most of the asn1 init code in ruby" This reverts commit https://github.com/ruby/openssl/commit/830505172882. The commit is part of the bigger effort to rewrite OpenSSL::ASN1 in Ruby. OpenSSL::ASN1 is relatively isolated from the rest of ruby/openssl and is not tightly bound to the OpenSSL API. The current implementation also needs a major refactor for several reasons, so this remains a long-term goal. However, the work is not yet complete. We are close to releasing v4.0.0, and we want to avoid shipping fragmented code in a stable branch. The changes can be reapplied when the rest is ready. https://github.com/ruby/openssl/commit/362942dcbf --- ext/openssl/lib/openssl.rb | 1 - ext/openssl/lib/openssl/asn1.rb | 188 ------------------------- ext/openssl/ossl_asn1.c | 238 +++++++++++++++++++++++++++++--- 3 files changed, 216 insertions(+), 211 deletions(-) delete mode 100644 ext/openssl/lib/openssl/asn1.rb diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb index 48f88dcbba9a6c..41927f32ae8238 100644 --- a/ext/openssl/lib/openssl.rb +++ b/ext/openssl/lib/openssl.rb @@ -12,7 +12,6 @@ require 'openssl.so' -require_relative 'openssl/asn1' require_relative 'openssl/bn' require_relative 'openssl/cipher' require_relative 'openssl/digest' diff --git a/ext/openssl/lib/openssl/asn1.rb b/ext/openssl/lib/openssl/asn1.rb deleted file mode 100644 index 89fa28e1fb0b4a..00000000000000 --- a/ext/openssl/lib/openssl/asn1.rb +++ /dev/null @@ -1,188 +0,0 @@ -# frozen_string_literal: true -#-- -# -# = Ruby-space definitions that completes C-space funcs for ASN.1 -# -# = Licence -# This program is licensed under the same licence as Ruby. -# (See the file 'COPYING'.) -#++ - -module OpenSSL - module ASN1 - class ASN1Data - # - # Carries the value of a ASN.1 type. - # Please confer Constructive and Primitive for the mappings between - # ASN.1 data types and Ruby classes. - # - attr_accessor :value - - # An Integer representing the tag number of this ASN1Data. Never +nil+. - attr_accessor :tag - - # A Symbol representing the tag class of this ASN1Data. Never +nil+. - # See ASN1Data for possible values. - attr_accessor :tag_class - - # - # Never +nil+. A boolean value indicating whether the encoding uses - # indefinite length (in the case of parsing) or whether an indefinite - # length form shall be used (in the encoding case). - # In DER, every value uses definite length form. But in scenarios where - # large amounts of data need to be transferred it might be desirable to - # have some kind of streaming support available. - # For example, huge OCTET STRINGs are preferably sent in smaller-sized - # chunks, each at a time. - # This is possible in BER by setting the length bytes of an encoding - # to zero and by this indicating that the following value will be - # sent in chunks. Indefinite length encodings are always constructed. - # The end of such a stream of chunks is indicated by sending a EOC - # (End of Content) tag. SETs and SEQUENCEs may use an indefinite length - # encoding, but also primitive types such as e.g. OCTET STRINGS or - # BIT STRINGS may leverage this functionality (cf. ITU-T X.690). - # - attr_accessor :indefinite_length - - alias infinite_length indefinite_length - alias infinite_length= indefinite_length= - - # - # :call-seq: - # OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data - # - # _value_: Please have a look at Constructive and Primitive to see how Ruby - # types are mapped to ASN.1 types and vice versa. - # - # _tag_: An Integer indicating the tag number. - # - # _tag_class_: A Symbol indicating the tag class. Please cf. ASN1 for - # possible values. - # - # == Example - # asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42) - # tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER - # - def initialize(value, tag, tag_class) - raise ASN1Error, "invalid tag class" unless tag_class.is_a?(Symbol) - - @tag = tag - @value = value - @tag_class = tag_class - @indefinite_length = false - end - end - - module TaggedASN1Data - # - # May be used as a hint for encoding a value either implicitly or - # explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. - # _tagging_ is not set when a ASN.1 structure is parsed using - # OpenSSL::ASN1.decode. - # - attr_accessor :tagging - - # :call-seq: - # OpenSSL::ASN1::Primitive.new(value [, tag, tagging, tag_class ]) => Primitive - # - # _value_: is mandatory. - # - # _tag_: optional, may be specified for tagged values. If no _tag_ is - # specified, the UNIVERSAL tag corresponding to the Primitive sub-class - # is used by default. - # - # _tagging_: may be used as an encoding hint to encode a value either - # explicitly or implicitly, see ASN1 for possible values. - # - # _tag_class_: if _tag_ and _tagging_ are +nil+ then this is set to - # +:UNIVERSAL+ by default. If either _tag_ or _tagging_ are set then - # +:CONTEXT_SPECIFIC+ is used as the default. For possible values please - # cf. ASN1. - # - # == Example - # int = OpenSSL::ASN1::Integer.new(42) - # zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT) - # private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE) - # - def initialize(value, tag = nil, tagging = nil, tag_class = nil) - tag ||= ASN1.take_default_tag(self.class) - - raise ASN1Error, "must specify tag number" unless tag - - if tagging - raise ASN1Error, "invalid tagging method" unless tagging.is_a?(Symbol) - end - - tag_class ||= tagging ? :CONTEXT_SPECIFIC : :UNIVERSAL - - raise ASN1Error, "invalid tag class" unless tag_class.is_a?(Symbol) - - @tagging = tagging - super(value ,tag, tag_class) - end - end - - class Primitive < ASN1Data - include TaggedASN1Data - - undef_method :indefinite_length= - undef_method :infinite_length= - end - - class Constructive < ASN1Data - include TaggedASN1Data - include Enumerable - - # :call-seq: - # asn1_ary.each { |asn1| block } => asn1_ary - # - # Calls the given block once for each element in self, passing that element - # as parameter _asn1_. If no block is given, an enumerator is returned - # instead. - # - # == Example - # asn1_ary.each do |asn1| - # puts asn1 - # end - # - def each(&blk) - @value.each(&blk) - - self - end - end - - class Boolean < Primitive ; end - class Integer < Primitive ; end - class Enumerated < Primitive ; end - - class BitString < Primitive - attr_accessor :unused_bits - - def initialize(*) - super - - @unused_bits = 0 - end - end - - class EndOfContent < ASN1Data - def initialize - super("", 0, :UNIVERSAL) - end - end - - # :nodoc: - def self.take_default_tag(klass) - tag = CLASS_TAG_MAP[klass] - - return tag if tag - - sklass = klass.superclass - - return unless sklass - - take_default_tag(sklass) - end - end -end diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 38397ebda87c86..17cecd0e96cb56 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -11,6 +11,7 @@ static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, int yield, long *num_read); +static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); /* * DATE conversion @@ -199,6 +200,10 @@ ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *obj) #define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) #define ossl_asn1_get_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_LENGTH) +#define ossl_asn1_set_value(o,v) rb_ivar_set((o),sivVALUE,(v)) +#define ossl_asn1_set_tag(o,v) rb_ivar_set((o),sivTAG,(v)) +#define ossl_asn1_set_tagging(o,v) rb_ivar_set((o),sivTAGGING,(v)) +#define ossl_asn1_set_tag_class(o,v) rb_ivar_set((o),sivTAG_CLASS,(v)) #define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) VALUE mASN1; @@ -226,6 +231,7 @@ static VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ static VALUE sym_IMPLICIT, sym_EXPLICIT; static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; +static ID id_each; /* * Ruby to ASN1 converters @@ -669,6 +675,35 @@ ossl_asn1_class2sym(int tc) return sym_UNIVERSAL; } +/* + * call-seq: + * OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data + * + * _value_: Please have a look at Constructive and Primitive to see how Ruby + * types are mapped to ASN.1 types and vice versa. + * + * _tag_: An Integer indicating the tag number. + * + * _tag_class_: A Symbol indicating the tag class. Please cf. ASN1 for + * possible values. + * + * == Example + * asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42) + * tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER + */ +static VALUE +ossl_asn1data_initialize(VALUE self, VALUE value, VALUE tag, VALUE tag_class) +{ + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + + return self; +} + static VALUE to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) { @@ -797,19 +832,20 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, if (tc == sym_UNIVERSAL && tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { VALUE klass = *ossl_asn1_info[tag].klass; - if (tag == V_ASN1_EOC) - asn1data = rb_funcall(cASN1EndOfContent, rb_intern("new"), 0); - else { - VALUE args[4] = { value, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(klass, rb_intern("new"), 4, args); - } + VALUE args[4]; + args[0] = value; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + asn1data = rb_obj_alloc(klass); + ossl_asn1_initialize(4, args, asn1data); if(tag == V_ASN1_BIT_STRING){ rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); } } else { - VALUE args[3] = { value, INT2NUM(tag), tc }; - asn1data = rb_funcallv_public(cASN1Data, rb_intern("new"), 3, args); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), tc); } return asn1data; @@ -845,20 +881,20 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, } if (tc == sym_UNIVERSAL) { - if (tag == V_ASN1_SEQUENCE) { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Sequence, rb_intern("new"), 4, args); - } else if (tag == V_ASN1_SET) { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Set, rb_intern("new"), 4, args); - } else { - VALUE args[4] = { ary, INT2NUM(tag), Qnil, tc }; - asn1data = rb_funcallv_public(cASN1Constructive, rb_intern("new"), 4, args); - } + VALUE args[4]; + if (tag == V_ASN1_SEQUENCE || tag == V_ASN1_SET) + asn1data = rb_obj_alloc(*ossl_asn1_info[tag].klass); + else + asn1data = rb_obj_alloc(cASN1Constructive); + args[0] = ary; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + ossl_asn1_initialize(4, args, asn1data); } else { - VALUE args[3] = {ary, INT2NUM(tag), tc}; - asn1data = rb_funcallv_public(cASN1Data, rb_intern("new"), 3, args); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), tc); } if (indefinite) @@ -1051,6 +1087,83 @@ ossl_asn1_decode_all(VALUE self, VALUE obj) return ary; } +/* + * call-seq: + * OpenSSL::ASN1::Primitive.new(value [, tag, tagging, tag_class ]) => Primitive + * + * _value_: is mandatory. + * + * _tag_: optional, may be specified for tagged values. If no _tag_ is + * specified, the UNIVERSAL tag corresponding to the Primitive sub-class + * is used by default. + * + * _tagging_: may be used as an encoding hint to encode a value either + * explicitly or implicitly, see ASN1 for possible values. + * + * _tag_class_: if _tag_ and _tagging_ are +nil+ then this is set to + * +:UNIVERSAL+ by default. If either _tag_ or _tagging_ are set then + * +:CONTEXT_SPECIFIC+ is used as the default. For possible values please + * cf. ASN1. + * + * == Example + * int = OpenSSL::ASN1::Integer.new(42) + * zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT) + * private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE) + */ +static VALUE +ossl_asn1_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE value, tag, tagging, tag_class; + int default_tag; + + rb_scan_args(argc, argv, "13", &value, &tag, &tagging, &tag_class); + default_tag = ossl_asn1_default_tag(self); + + if (default_tag == -1 || argc > 1) { + if(NIL_P(tag)) + ossl_raise(eASN1Error, "must specify tag number"); + if(!NIL_P(tagging) && !SYMBOL_P(tagging)) + ossl_raise(eASN1Error, "invalid tagging method"); + if(NIL_P(tag_class)) { + if (NIL_P(tagging)) + tag_class = sym_UNIVERSAL; + else + tag_class = sym_CONTEXT_SPECIFIC; + } + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); + } + else{ + tag = INT2NUM(default_tag); + tagging = Qnil; + tag_class = sym_UNIVERSAL; + } + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + if (default_tag == V_ASN1_BIT_STRING) + rb_ivar_set(self, sivUNUSED_BITS, INT2FIX(0)); + + return self; +} + +static VALUE +ossl_asn1eoc_initialize(VALUE self) { + VALUE tag, tagging, tag_class, value; + tag = INT2FIX(0); + tagging = Qnil; + tag_class = sym_UNIVERSAL; + value = rb_str_new("", 0); + ossl_asn1_set_tag(self, tag); + ossl_asn1_set_value(self, value); + ossl_asn1_set_tagging(self, tagging); + ossl_asn1_set_tag_class(self, tag_class); + ossl_asn1_set_indefinite_length(self, Qfalse); + return self; +} + static VALUE ossl_asn1eoc_to_der(VALUE self) { @@ -1142,6 +1255,27 @@ ossl_asn1cons_to_der(VALUE self) return to_der_internal(self, 1, indef_len, str); } +/* + * call-seq: + * asn1_ary.each { |asn1| block } => asn1_ary + * + * Calls the given block once for each element in self, passing that element + * as parameter _asn1_. If no block is given, an enumerator is returned + * instead. + * + * == Example + * asn1_ary.each do |asn1| + * puts asn1 + * end + */ +static VALUE +ossl_asn1cons_each(VALUE self) +{ + rb_block_call(ossl_asn1_get_value(self), id_each, 0, 0, 0, 0); + + return self; +} + /* * call-seq: * OpenSSL::ASN1::ObjectId.register(object_id, short_name, long_name) @@ -1255,7 +1389,7 @@ ossl_asn1obj_eq(VALUE self, VALUE other) #define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \ static VALUE ossl_asn1_##klass(int argc, VALUE *argv, VALUE self)\ -{ return rb_funcallv_public(cASN1##klass, rb_intern("new"), argc, argv); } +{ return rb_funcall3(cASN1##klass, rb_intern("new"), argc, argv); } OSSL_ASN1_IMPL_FACTORY_METHOD(Boolean) OSSL_ASN1_IMPL_FACTORY_METHOD(Integer) @@ -1536,6 +1670,42 @@ Init_ossl_asn1(void) * puts int2.value # => 1 */ cASN1Data = rb_define_class_under(mASN1, "ASN1Data", rb_cObject); + /* + * Carries the value of a ASN.1 type. + * Please confer Constructive and Primitive for the mappings between + * ASN.1 data types and Ruby classes. + */ + rb_attr(cASN1Data, rb_intern("value"), 1, 1, 0); + /* + * An Integer representing the tag number of this ASN1Data. Never +nil+. + */ + rb_attr(cASN1Data, rb_intern("tag"), 1, 1, 0); + /* + * A Symbol representing the tag class of this ASN1Data. Never +nil+. + * See ASN1Data for possible values. + */ + rb_attr(cASN1Data, rb_intern("tag_class"), 1, 1, 0); + /* + * Never +nil+. A boolean value indicating whether the encoding uses + * indefinite length (in the case of parsing) or whether an indefinite + * length form shall be used (in the encoding case). + * In DER, every value uses definite length form. But in scenarios where + * large amounts of data need to be transferred it might be desirable to + * have some kind of streaming support available. + * For example, huge OCTET STRINGs are preferably sent in smaller-sized + * chunks, each at a time. + * This is possible in BER by setting the length bytes of an encoding + * to zero and by this indicating that the following value will be + * sent in chunks. Indefinite length encodings are always constructed. + * The end of such a stream of chunks is indicated by sending a EOC + * (End of Content) tag. SETs and SEQUENCEs may use an indefinite length + * encoding, but also primitive types such as e.g. OCTET STRINGS or + * BIT STRINGS may leverage this functionality (cf. ITU-T X.690). + */ + rb_attr(cASN1Data, rb_intern("indefinite_length"), 1, 1, 0); + rb_define_alias(cASN1Data, "infinite_length", "indefinite_length"); + rb_define_alias(cASN1Data, "infinite_length=", "indefinite_length="); + rb_define_method(cASN1Data, "initialize", ossl_asn1data_initialize, 3); rb_define_method(cASN1Data, "to_der", ossl_asn1data_to_der, 0); /* Document-class: OpenSSL::ASN1::Primitive @@ -1603,6 +1773,16 @@ Init_ossl_asn1(void) * prim_zero_tagged_explicit = .new(value, 0, :EXPLICIT) */ cASN1Primitive = rb_define_class_under(mASN1, "Primitive", cASN1Data); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * _tagging_ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Primitive, rb_intern("tagging"), 1, 1, Qtrue); + rb_undef_method(cASN1Primitive, "indefinite_length="); + rb_undef_method(cASN1Primitive, "infinite_length="); + rb_define_method(cASN1Primitive, "initialize", ossl_asn1_initialize, -1); rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); /* Document-class: OpenSSL::ASN1::Constructive @@ -1633,7 +1813,17 @@ Init_ossl_asn1(void) * set = OpenSSL::ASN1::Set.new( [ int, str ] ) */ cASN1Constructive = rb_define_class_under(mASN1,"Constructive", cASN1Data); + rb_include_module(cASN1Constructive, rb_mEnumerable); + /* + * May be used as a hint for encoding a value either implicitly or + * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+. + * _tagging_ is not set when a ASN.1 structure is parsed using + * OpenSSL::ASN1.decode. + */ + rb_attr(cASN1Constructive, rb_intern("tagging"), 1, 1, Qtrue); + rb_define_method(cASN1Constructive, "initialize", ossl_asn1_initialize, -1); rb_define_method(cASN1Constructive, "to_der", ossl_asn1cons_to_der, 0); + rb_define_method(cASN1Constructive, "each", ossl_asn1cons_each, 0); #define OSSL_ASN1_DEFINE_CLASS(name, super) \ do{\ @@ -1682,10 +1872,13 @@ do{\ rb_define_alias(cASN1ObjectId, "short_name", "sn"); rb_define_alias(cASN1ObjectId, "long_name", "ln"); rb_define_method(cASN1ObjectId, "==", ossl_asn1obj_eq, 1); + rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0); + rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0); rb_define_method(cASN1EndOfContent, "to_der", ossl_asn1eoc_to_der, 0); class_tag_map = rb_hash_new(); + rb_gc_register_mark_object(class_tag_map); rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC)); rb_hash_aset(class_tag_map, cASN1Boolean, INT2NUM(V_ASN1_BOOLEAN)); rb_hash_aset(class_tag_map, cASN1Integer, INT2NUM(V_ASN1_INTEGER)); @@ -1709,5 +1902,6 @@ do{\ rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING)); rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING)); rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING)); - rb_define_const(mASN1, "CLASS_TAG_MAP", class_tag_map); + + id_each = rb_intern_const("each"); } From 5062c0c621d887367af8a054e5e5d83d7ec57dd3 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 30 Jul 2025 03:40:32 +0900 Subject: [PATCH 1568/2435] [ruby/openssl] Expand tabs in C source files Since around 2018, we have been using spaces for indentation for newly added code[1]. The mixed use of tabs and spaces has repeatedly confused new contributors who configured their editors to use a different tab size than 8. Since git blame can now skip specific commits, ruby/ruby did a mass reformatting of tabs in 2022[2]. Do the same in ruby/openssl. While at it, fix a few indentation issues, mainly in switch-case labels and in ossl_ssl_session.c, which used doubled indentation size. This patch contains white-space changes only. git diff -w output should be empty. [1] https://bugs.ruby-lang.org/issues/14246 [2] https://bugs.ruby-lang.org/issues/18891 https://github.com/ruby/openssl/commit/4d6214f507 --- ext/openssl/ossl.c | 250 ++++++------- ext/openssl/ossl.h | 18 +- ext/openssl/ossl_asn1.c | 650 ++++++++++++++++----------------- ext/openssl/ossl_bio.c | 6 +- ext/openssl/ossl_bn.c | 564 ++++++++++++++-------------- ext/openssl/ossl_cipher.c | 98 ++--- ext/openssl/ossl_digest.c | 34 +- ext/openssl/ossl_engine.c | 74 ++-- ext/openssl/ossl_hmac.c | 42 +-- ext/openssl/ossl_kdf.c | 74 ++-- ext/openssl/ossl_ns_spki.c | 50 +-- ext/openssl/ossl_ocsp.c | 272 +++++++------- ext/openssl/ossl_pkcs12.c | 26 +- ext/openssl/ossl_pkcs7.c | 144 ++++---- ext/openssl/ossl_pkey.c | 132 +++---- ext/openssl/ossl_pkey.h | 190 +++++----- ext/openssl/ossl_pkey_dh.c | 28 +- ext/openssl/ossl_pkey_dsa.c | 4 +- ext/openssl/ossl_pkey_ec.c | 196 +++++----- ext/openssl/ossl_pkey_rsa.c | 62 ++-- ext/openssl/ossl_rand.c | 14 +- ext/openssl/ossl_ssl.c | 300 +++++++-------- ext/openssl/ossl_ssl.h | 16 +- ext/openssl/ossl_ssl_session.c | 184 +++++----- ext/openssl/ossl_ts.c | 6 +- ext/openssl/ossl_x509.c | 2 +- ext/openssl/ossl_x509attr.c | 48 +-- ext/openssl/ossl_x509cert.c | 100 ++--- ext/openssl/ossl_x509crl.c | 96 ++--- ext/openssl/ossl_x509ext.c | 64 ++-- ext/openssl/ossl_x509name.c | 90 ++--- ext/openssl/ossl_x509req.c | 72 ++-- ext/openssl/ossl_x509revoked.c | 46 +-- ext/openssl/ossl_x509store.c | 62 ++-- 34 files changed, 2007 insertions(+), 2007 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 60780790b02d1d..a19ff23b107c88 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -13,71 +13,71 @@ /* * Data Conversion */ -#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ -VALUE \ -ossl_##name##_ary2sk0(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - VALUE val; \ - type *x; \ - int i; \ - \ - Check_Type(ary, T_ARRAY); \ - sk = sk_##type##_new_null(); \ - if (!sk) ossl_raise(eOSSLError, NULL); \ - \ - for (i = 0; i < RARRAY_LEN(ary); i++) { \ - val = rb_ary_entry(ary, i); \ - if (!rb_obj_is_kind_of(val, expected_class)) { \ - sk_##type##_pop_free(sk, type##_free); \ - ossl_raise(eOSSLError, "object in array not" \ - " of class ##type##"); \ - } \ - x = dup(val); /* NEED TO DUP */ \ - sk_##type##_push(sk, x); \ - } \ - return (VALUE)sk; \ -} \ - \ -STACK_OF(type) * \ -ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ -{ \ - return (STACK_OF(type)*)rb_protect( \ - (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ - ary, \ - status); \ -} \ - \ -STACK_OF(type) * \ -ossl_##name##_ary2sk(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - int status = 0; \ - \ - sk = ossl_protect_##name##_ary2sk(ary, &status); \ - if (status) rb_jump_tag(status); \ - \ - return sk; \ +#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ +VALUE \ +ossl_##name##_ary2sk0(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + VALUE val; \ + type *x; \ + int i; \ + \ + Check_Type(ary, T_ARRAY); \ + sk = sk_##type##_new_null(); \ + if (!sk) ossl_raise(eOSSLError, NULL); \ + \ + for (i = 0; i < RARRAY_LEN(ary); i++) { \ + val = rb_ary_entry(ary, i); \ + if (!rb_obj_is_kind_of(val, expected_class)) { \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, "object in array not" \ + " of class ##type##"); \ + } \ + x = dup(val); /* NEED TO DUP */ \ + sk_##type##_push(sk, x); \ + } \ + return (VALUE)sk; \ +} \ + \ +STACK_OF(type) * \ +ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ +{ \ + return (STACK_OF(type)*)rb_protect( \ + (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ + ary, \ + status); \ +} \ + \ +STACK_OF(type) * \ +ossl_##name##_ary2sk(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + int status = 0; \ + \ + sk = ossl_protect_##name##_ary2sk(ary, &status); \ + if (status) rb_jump_tag(status); \ + \ + return sk; \ } OSSL_IMPL_ARY2SK(x509, X509, cX509Cert, DupX509CertPtr) -#define OSSL_IMPL_SK2ARY(name, type) \ -VALUE \ -ossl_##name##_sk2ary(const STACK_OF(type) *sk) \ -{ \ - type *t; \ - int i, num; \ - VALUE ary; \ - \ - RUBY_ASSERT(sk != NULL); \ - num = sk_##type##_num(sk); \ - ary = rb_ary_new_capa(num); \ - \ - for (i=0; i> 4]; - out[i * 2 + 1] = hex[p & 0x0f]; + out[i * 2 + 0] = hex[p >> 4]; + out[i * 2 + 1] = hex[p & 0x0f]; } } @@ -143,14 +143,14 @@ VALUE ossl_pem_passwd_value(VALUE pass) { if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); /* PEM_BUFSIZE is currently used as the second argument of pem_password_cb, * that is +max_len+ of ossl_pem_passwd_cb() */ if (RSTRING_LEN(pass) > PEM_BUFSIZE) - ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); + ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); return pass; } @@ -160,7 +160,7 @@ ossl_pem_passwd_cb0(VALUE flag) { VALUE pass = rb_yield(flag); if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); return pass; } @@ -173,46 +173,46 @@ ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_) VALUE rflag, pass = (VALUE)pwd_; if (RTEST(pass)) { - /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not - * work because it does not allow NUL characters and truncates to 1024 - * bytes silently if the input is over 1024 bytes */ - if (RB_TYPE_P(pass, T_STRING)) { - len = RSTRING_LEN(pass); - if (len <= max_len) { - memcpy(buf, RSTRING_PTR(pass), len); - return (int)len; - } - } - OSSL_Debug("passed data is not valid String???"); - return -1; + /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not + * work because it does not allow NUL characters and truncates to 1024 + * bytes silently if the input is over 1024 bytes */ + if (RB_TYPE_P(pass, T_STRING)) { + len = RSTRING_LEN(pass); + if (len <= max_len) { + memcpy(buf, RSTRING_PTR(pass), len); + return (int)len; + } + } + OSSL_Debug("passed data is not valid String???"); + return -1; } if (!rb_block_given_p()) { - return PEM_def_callback(buf, max_len, flag, NULL); + return PEM_def_callback(buf, max_len, flag, NULL); } while (1) { - /* - * when the flag is nonzero, this password - * will be used to perform encryption; otherwise it will - * be used to perform decryption. - */ - rflag = flag ? Qtrue : Qfalse; - pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); - if (status) { - /* ignore an exception raised. */ - rb_set_errinfo(Qnil); - return -1; - } - if (NIL_P(pass)) - return -1; - len = RSTRING_LEN(pass); - if (len > max_len) { - rb_warning("password must not be longer than %d bytes", max_len); - continue; - } - memcpy(buf, RSTRING_PTR(pass), len); - break; + /* + * when the flag is nonzero, this password + * will be used to perform encryption; otherwise it will + * be used to perform decryption. + */ + rflag = flag ? Qtrue : Qfalse; + pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); + if (status) { + /* ignore an exception raised. */ + rb_set_errinfo(Qnil); + return -1; + } + if (NIL_P(pass)) + return -1; + len = RSTRING_LEN(pass); + if (len > max_len) { + rb_warning("password must not be longer than %d bytes", max_len); + continue; + } + memcpy(buf, RSTRING_PTR(pass), len); + break; } return (int)len; } @@ -247,7 +247,7 @@ VALUE ossl_to_der_if_possible(VALUE obj) { if(rb_respond_to(obj, ossl_s_to_der)) - return ossl_to_der(obj); + return ossl_to_der(obj); return obj; } @@ -289,12 +289,12 @@ ossl_raise(VALUE exc, const char *fmt, ...) VALUE err; if (fmt) { - va_start(args, fmt); - err = rb_vsprintf(fmt, args); - va_end(args); + va_start(args, fmt); + err = rb_vsprintf(fmt, args); + va_end(args); } else { - err = Qnil; + err = Qnil; } rb_exc_raise(ossl_make_error(exc, err)); @@ -434,17 +434,17 @@ ossl_fips_mode_set(VALUE self, VALUE enabled) return enabled; #elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) if (RTEST(enabled)) { - int mode = FIPS_mode(); - if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ - ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + int mode = FIPS_mode(); + if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); } else { - if(!FIPS_mode_set(0)) /* turning off twice is OK */ - ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + if(!FIPS_mode_set(0)) /* turning off twice is OK */ + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); } return enabled; #else if (RTEST(enabled)) - ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); + ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); return enabled; #endif } @@ -473,8 +473,8 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) } switch (CRYPTO_memcmp(p1, p2, len1)) { - case 0: return Qtrue; - default: return Qfalse; + case 0: return Qtrue; + default: return Qfalse; } } @@ -996,13 +996,13 @@ Init_openssl(void) #if OSSL_OPENSSL_PREREQ(3, 0, 0) Qtrue #elif defined(OPENSSL_FIPS) - Qtrue + Qtrue #elif defined(OPENSSL_IS_AWSLC) // AWS-LC FIPS can only be enabled during compile time. - FIPS_mode() ? Qtrue : Qfalse + FIPS_mode() ? Qtrue : Qfalse #else - Qfalse + Qfalse #endif - ); + ); rb_define_module_function(mOSSL, "fips_mode", ossl_fips_mode_get, 0); rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index d519c96cd6d313..93deafb4b68363 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -92,10 +92,10 @@ extern VALUE eOSSLError; * CheckTypes */ #define OSSL_Check_Kind(obj, klass) do {\ - if (!rb_obj_is_kind_of((obj), (klass))) {\ - ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\ - rb_obj_class(obj), (klass));\ - }\ + if (!rb_obj_is_kind_of((obj), (klass))) {\ + ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\ + rb_obj_class(obj), (klass));\ + }\ } while (0) /* @@ -174,11 +174,11 @@ VALUE ossl_to_der_if_possible(VALUE); extern VALUE dOSSL; #define OSSL_Debug(...) do { \ - if (dOSSL == Qtrue) { \ - fprintf(stderr, "OSSL_DEBUG: "); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ - } \ + if (dOSSL == Qtrue) { \ + fprintf(stderr, "OSSL_DEBUG: "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ + } \ } while (0) /* diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 17cecd0e96cb56..cb13ac6ecf876d 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -10,7 +10,7 @@ #include "ossl.h" static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, - int depth, int yield, long *num_read); + int depth, int yield, long *num_read); static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); /* @@ -26,37 +26,37 @@ asn1time_to_time(const ASN1_TIME *time) memset(&tm, 0, sizeof(struct tm)); switch (time->type) { - case V_ASN1_UTCTIME: - count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - - if (count == 5) { - tm.tm_sec = 0; - } else if (count != 6) { - ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", - time->data); - } - if (tm.tm_year < 50) { - tm.tm_year += 2000; - } else { - tm.tm_year += 1900; - } - break; - case V_ASN1_GENERALIZEDTIME: - count = sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - if (count == 5) { - tm.tm_sec = 0; - } - else if (count != 6) { - ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format: \"%s\"", - time->data); - } - break; - default: - rb_warning("unknown time format"); + case V_ASN1_UTCTIME: + count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, + &tm.tm_sec); + + if (count == 5) { + tm.tm_sec = 0; + } else if (count != 6) { + ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", + time->data); + } + if (tm.tm_year < 50) { + tm.tm_year += 2000; + } else { + tm.tm_year += 1900; + } + break; + case V_ASN1_GENERALIZEDTIME: + count = sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, + &tm.tm_sec); + if (count == 5) { + tm.tm_sec = 0; + } + else if (count != 6) { + ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format: \"%s\"", + time->data); + } + break; + default: + rb_warning("unknown time format"); return Qnil; } argv[0] = INT2NUM(tm.tm_year); @@ -81,13 +81,13 @@ ossl_time_split(VALUE time, time_t *sec, int *days) VALUE num = rb_Integer(time); if (FIXNUM_P(num)) { - time_t t = FIX2LONG(num); - *sec = t % 86400; - *days = rb_long2int(t / 86400); + time_t t = FIX2LONG(num); + *sec = t % 86400; + *days = rb_long2int(t / 86400); } else { - *days = NUM2INT(rb_funcall(num, rb_intern("/"), 1, INT2FIX(86400))); - *sec = NUM2TIMET(rb_funcall(num, rb_intern("%"), 1, INT2FIX(86400))); + *days = NUM2INT(rb_funcall(num, rb_intern("/"), 1, INT2FIX(86400))); + *sec = NUM2TIMET(rb_funcall(num, rb_intern("%"), 1, INT2FIX(86400))); } } @@ -110,16 +110,16 @@ asn1integer_to_num(const ASN1_INTEGER *ai) VALUE num; if (!ai) { - ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); + ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); } if (ai->type == V_ASN1_ENUMERATED) - /* const_cast: workaround for old OpenSSL */ - bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL); + /* const_cast: workaround for old OpenSSL */ + bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL); else - bn = ASN1_INTEGER_to_BN(ai, NULL); + bn = ASN1_INTEGER_to_BN(ai, NULL); if (!bn) - ossl_raise(eOSSLError, NULL); + ossl_raise(eOSSLError, NULL); num = ossl_bn_new(bn); BN_free(bn); @@ -132,12 +132,12 @@ num_to_asn1integer(VALUE obj, ASN1_INTEGER *ai) BIGNUM *bn; if (NIL_P(obj)) - ossl_raise(rb_eTypeError, "Can't convert nil into Integer"); + ossl_raise(rb_eTypeError, "Can't convert nil into Integer"); bn = GetBNPtr(obj); if (!(ai = BN_to_ASN1_INTEGER(bn, ai))) - ossl_raise(eOSSLError, NULL); + ossl_raise(eOSSLError, NULL); return ai; } @@ -160,13 +160,13 @@ ossl_asn1obj_to_string_oid(const ASN1_OBJECT *a1obj) str = rb_usascii_str_new(NULL, 127); len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); if (len <= 0 || len == INT_MAX) - ossl_raise(eOSSLError, "OBJ_obj2txt"); + ossl_raise(eOSSLError, "OBJ_obj2txt"); if (len > RSTRING_LEN(str)) { - /* +1 is for the \0 terminator added by OBJ_obj2txt() */ - rb_str_resize(str, len + 1); - len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); - if (len <= 0) - ossl_raise(eOSSLError, "OBJ_obj2txt"); + /* +1 is for the \0 terminator added by OBJ_obj2txt() */ + rb_str_resize(str, len + 1); + len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); + if (len <= 0) + ossl_raise(eOSSLError, "OBJ_obj2txt"); } rb_str_set_len(str, len); return str; @@ -240,9 +240,9 @@ static ASN1_BOOLEAN obj_to_asn1bool(VALUE obj) { if (NIL_P(obj)) - ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); + ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); - return RTEST(obj) ? 0xff : 0x0; + return RTEST(obj) ? 0xff : 0x0; } static ASN1_INTEGER* @@ -257,11 +257,11 @@ obj_to_asn1bstr(VALUE obj, long unused_bits) ASN1_BIT_STRING *bstr; if (unused_bits < 0 || unused_bits > 7) - ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ - "the range 0 to 7"); + ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ + "the range 0 to 7"); StringValue(obj); if(!(bstr = ASN1_BIT_STRING_new())) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj)); bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */ bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; @@ -276,7 +276,7 @@ obj_to_asn1str(VALUE obj) StringValue(obj); if(!(str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj)); return str; @@ -288,9 +288,9 @@ obj_to_asn1null(VALUE obj) ASN1_NULL *null; if(!NIL_P(obj)) - ossl_raise(eASN1Error, "nil expected"); + ossl_raise(eASN1Error, "nil expected"); if(!(null = ASN1_NULL_new())) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return null; } @@ -318,7 +318,7 @@ obj_to_asn1utime(VALUE time) ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_UTCTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return t; } @@ -333,7 +333,7 @@ obj_to_asn1gtime(VALUE time) ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_GENERALIZEDTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return t; } @@ -346,7 +346,7 @@ obj_to_asn1derstr(VALUE obj) str = ossl_to_der(obj); if(!(a1str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str)); return a1str; @@ -361,9 +361,9 @@ decode_bool(unsigned char* der, long length) const unsigned char *p = der; if (length != 3) - ossl_raise(eASN1Error, "invalid length for BOOLEAN"); + ossl_raise(eASN1Error, "invalid length for BOOLEAN"); if (p[0] != 1 || p[1] != 1) - ossl_raise(eASN1Error, "invalid BOOLEAN"); + ossl_raise(eASN1Error, "invalid BOOLEAN"); return p[2] ? Qtrue : Qfalse; } @@ -378,9 +378,9 @@ decode_int(unsigned char* der, long length) p = der; if(!(ai = d2i_ASN1_INTEGER(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1integer_to_num_i, - (VALUE)ai, &status); + (VALUE)ai, &status); ASN1_INTEGER_free(ai); if(status) rb_jump_tag(status); @@ -397,11 +397,11 @@ decode_bstr(unsigned char* der, long length, long *unused_bits) p = der; if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); len = bstr->length; *unused_bits = 0; if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT) - *unused_bits = bstr->flags & 0x07; + *unused_bits = bstr->flags & 0x07; ret = rb_str_new((const char *)bstr->data, len); ASN1_BIT_STRING_free(bstr); @@ -418,9 +418,9 @@ decode_enum(unsigned char* der, long length) p = der; if(!(ai = d2i_ASN1_ENUMERATED(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1integer_to_num_i, - (VALUE)ai, &status); + (VALUE)ai, &status); ASN1_ENUMERATED_free(ai); if(status) rb_jump_tag(status); @@ -435,7 +435,7 @@ decode_null(unsigned char* der, long length) p = der; if(!(null = d2i_ASN1_NULL(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ASN1_NULL_free(null); return Qnil; @@ -475,9 +475,9 @@ decode_time(unsigned char* der, long length) p = der; if(!(time = d2i_ASN1_TIME(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); ret = rb_protect(asn1time_to_time_i, - (VALUE)time, &status); + (VALUE)time, &status); ASN1_TIME_free(time); if(status) rb_jump_tag(status); @@ -488,7 +488,7 @@ static VALUE decode_eoc(unsigned char *der, long length) { if (length != 2 || !(der[0] == 0x00 && der[1] == 0x00)) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return rb_str_new("", 0); } @@ -553,62 +553,62 @@ ossl_asn1_get_asn1type(VALUE obj) tag = ossl_asn1_default_tag(obj); value = ossl_asn1_get_value(obj); switch(tag){ - case V_ASN1_BOOLEAN: - ptr = (void*)(VALUE)obj_to_asn1bool(value); - free_func = NULL; - break; - case V_ASN1_INTEGER: /* FALLTHROUGH */ - case V_ASN1_ENUMERATED: - ptr = obj_to_asn1int(value); - free_func = (free_func_type *)ASN1_INTEGER_free; - break; - case V_ASN1_BIT_STRING: + case V_ASN1_BOOLEAN: + ptr = (void*)(VALUE)obj_to_asn1bool(value); + free_func = NULL; + break; + case V_ASN1_INTEGER: /* FALLTHROUGH */ + case V_ASN1_ENUMERATED: + ptr = obj_to_asn1int(value); + free_func = (free_func_type *)ASN1_INTEGER_free; + break; + case V_ASN1_BIT_STRING: rflag = rb_attr_get(obj, sivUNUSED_BITS); - ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); - free_func = (free_func_type *)ASN1_BIT_STRING_free; - break; - case V_ASN1_NULL: - ptr = obj_to_asn1null(value); - free_func = (free_func_type *)ASN1_NULL_free; - break; - case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ - case V_ASN1_UTF8STRING: /* FALLTHROUGH */ - case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ - case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ - case V_ASN1_T61STRING: /* FALLTHROUGH */ - case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ - case V_ASN1_IA5STRING: /* FALLTHROUGH */ - case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ - case V_ASN1_ISO64STRING: /* FALLTHROUGH */ - case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ - case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ - case V_ASN1_BMPSTRING: - ptr = obj_to_asn1str(value); - free_func = (free_func_type *)ASN1_STRING_free; - break; - case V_ASN1_OBJECT: - ptr = ossl_to_asn1obj(value); - free_func = (free_func_type *)ASN1_OBJECT_free; - break; - case V_ASN1_UTCTIME: - ptr = obj_to_asn1utime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_GENERALIZEDTIME: - ptr = obj_to_asn1gtime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_SET: /* FALLTHROUGH */ - case V_ASN1_SEQUENCE: - ptr = obj_to_asn1derstr(obj); - free_func = (free_func_type *)ASN1_STRING_free; - break; - default: - ossl_raise(eASN1Error, "unsupported ASN.1 type"); + ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); + free_func = (free_func_type *)ASN1_BIT_STRING_free; + break; + case V_ASN1_NULL: + ptr = obj_to_asn1null(value); + free_func = (free_func_type *)ASN1_NULL_free; + break; + case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ + case V_ASN1_UTF8STRING: /* FALLTHROUGH */ + case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ + case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ + case V_ASN1_T61STRING: /* FALLTHROUGH */ + case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ + case V_ASN1_IA5STRING: /* FALLTHROUGH */ + case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ + case V_ASN1_ISO64STRING: /* FALLTHROUGH */ + case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ + case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ + case V_ASN1_BMPSTRING: + ptr = obj_to_asn1str(value); + free_func = (free_func_type *)ASN1_STRING_free; + break; + case V_ASN1_OBJECT: + ptr = ossl_to_asn1obj(value); + free_func = (free_func_type *)ASN1_OBJECT_free; + break; + case V_ASN1_UTCTIME: + ptr = obj_to_asn1utime(value); + free_func = (free_func_type *)ASN1_TIME_free; + break; + case V_ASN1_GENERALIZEDTIME: + ptr = obj_to_asn1gtime(value); + free_func = (free_func_type *)ASN1_TIME_free; + break; + case V_ASN1_SET: /* FALLTHROUGH */ + case V_ASN1_SEQUENCE: + ptr = obj_to_asn1derstr(obj); + free_func = (free_func_type *)ASN1_STRING_free; + break; + default: + ossl_raise(eASN1Error, "unsupported ASN.1 type"); } if(!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))){ - if(free_func) free_func(ptr); - ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); + if(free_func) free_func(ptr); + ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); } memset(ret, 0, sizeof(ASN1_TYPE)); ASN1_TYPE_set(ret, tag, ptr); @@ -623,10 +623,10 @@ ossl_asn1_default_tag(VALUE obj) tmp_class = CLASS_OF(obj); while (!NIL_P(tmp_class)) { - tag = rb_hash_lookup(class_tag_map, tmp_class); - if (tag != Qnil) - return NUM2INT(tag); - tmp_class = rb_class_superclass(tmp_class); + tag = rb_hash_lookup(class_tag_map, tmp_class); + if (tag != Qnil) + return NUM2INT(tag); + tmp_class = rb_class_superclass(tmp_class); } return -1; @@ -639,7 +639,7 @@ ossl_asn1_tag(VALUE obj) tag = ossl_asn1_get_tag(obj); if(NIL_P(tag)) - ossl_raise(eASN1Error, "tag number not specified"); + ossl_raise(eASN1Error, "tag number not specified"); return NUM2INT(tag); } @@ -651,28 +651,28 @@ ossl_asn1_tag_class(VALUE obj) s = ossl_asn1_get_tag_class(obj); if (NIL_P(s) || s == sym_UNIVERSAL) - return V_ASN1_UNIVERSAL; + return V_ASN1_UNIVERSAL; else if (s == sym_APPLICATION) - return V_ASN1_APPLICATION; + return V_ASN1_APPLICATION; else if (s == sym_CONTEXT_SPECIFIC) - return V_ASN1_CONTEXT_SPECIFIC; + return V_ASN1_CONTEXT_SPECIFIC; else if (s == sym_PRIVATE) - return V_ASN1_PRIVATE; + return V_ASN1_PRIVATE; else - ossl_raise(eASN1Error, "invalid tag class"); + ossl_raise(eASN1Error, "invalid tag class"); } static VALUE ossl_asn1_class2sym(int tc) { if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) - return sym_PRIVATE; + return sym_PRIVATE; else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) - return sym_CONTEXT_SPECIFIC; + return sym_CONTEXT_SPECIFIC; else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) - return sym_APPLICATION; + return sym_APPLICATION; else - return sym_UNIVERSAL; + return sym_UNIVERSAL; } /* @@ -695,7 +695,7 @@ static VALUE ossl_asn1data_initialize(VALUE self, VALUE value, VALUE tag, VALUE tag_class) { if(!SYMBOL_P(tag_class)) - ossl_raise(eASN1Error, "invalid tag class"); + ossl_raise(eASN1Error, "invalid tag class"); ossl_asn1_set_tag(self, tag); ossl_asn1_set_value(self, value); ossl_asn1_set_tag_class(self, tag_class); @@ -717,35 +717,35 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) body_length = RSTRING_LENINT(body); if (ossl_asn1_get_tagging(self) == sym_EXPLICIT) { - int inner_length, e_encoding = indef_len ? 2 : 1; - - if (default_tag_number == -1) - ossl_raise(eASN1Error, "explicit tagging of unknown tag"); - - inner_length = ASN1_object_size(encoding, body_length, default_tag_number); - total_length = ASN1_object_size(e_encoding, inner_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - /* Put explicit tag */ - ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); - /* Append inner object */ - ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) { - ASN1_put_eoc(&p); /* For inner object */ - ASN1_put_eoc(&p); /* For wrapper object */ - } + int inner_length, e_encoding = indef_len ? 2 : 1; + + if (default_tag_number == -1) + ossl_raise(eASN1Error, "explicit tagging of unknown tag"); + + inner_length = ASN1_object_size(encoding, body_length, default_tag_number); + total_length = ASN1_object_size(e_encoding, inner_length, tag_number); + str = rb_str_new(NULL, total_length); + p = (unsigned char *)RSTRING_PTR(str); + /* Put explicit tag */ + ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); + /* Append inner object */ + ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); + memcpy(p, RSTRING_PTR(body), body_length); + p += body_length; + if (indef_len) { + ASN1_put_eoc(&p); /* For inner object */ + ASN1_put_eoc(&p); /* For wrapper object */ + } } else { - total_length = ASN1_object_size(encoding, body_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) - ASN1_put_eoc(&p); + total_length = ASN1_object_size(encoding, body_length, tag_number); + str = rb_str_new(NULL, total_length); + p = (unsigned char *)RSTRING_PTR(str); + ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); + memcpy(p, RSTRING_PTR(body), body_length); + p += body_length; + if (indef_len) + ASN1_put_eoc(&p); } assert(p - (unsigned char *)RSTRING_PTR(str) == total_length); return str; @@ -768,18 +768,18 @@ ossl_asn1data_to_der(VALUE self) VALUE value = ossl_asn1_get_value(self); if (rb_obj_is_kind_of(value, rb_cArray)) - return ossl_asn1cons_to_der(self); + return ossl_asn1cons_to_der(self); else { - if (RTEST(ossl_asn1_get_indefinite_length(self))) - ossl_raise(eASN1Error, "indefinite length form cannot be used " \ - "with primitive encoding"); - return ossl_asn1prim_to_der(self); + if (RTEST(ossl_asn1_get_indefinite_length(self))) + ossl_raise(eASN1Error, "indefinite length form cannot be used " \ + "with primitive encoding"); + return ossl_asn1prim_to_der(self); } } static VALUE int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, - VALUE tc, long *num_read) + VALUE tc, long *num_read) { VALUE value, asn1data; unsigned char *p; @@ -788,64 +788,64 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, p = *pp; if(tc == sym_UNIVERSAL && tag < ossl_asn1_info_size) { - switch(tag){ - case V_ASN1_EOC: - value = decode_eoc(p, hlen+length); - break; - case V_ASN1_BOOLEAN: - value = decode_bool(p, hlen+length); - break; - case V_ASN1_INTEGER: - value = decode_int(p, hlen+length); - break; - case V_ASN1_BIT_STRING: - value = decode_bstr(p, hlen+length, &flag); - break; - case V_ASN1_NULL: - value = decode_null(p, hlen+length); - break; - case V_ASN1_ENUMERATED: - value = decode_enum(p, hlen+length); - break; - case V_ASN1_OBJECT: - value = decode_obj(p, hlen+length); - break; - case V_ASN1_UTCTIME: /* FALLTHROUGH */ - case V_ASN1_GENERALIZEDTIME: - value = decode_time(p, hlen+length); - break; - default: - /* use original value */ - p += hlen; - value = rb_str_new((const char *)p, length); - break; - } + switch(tag){ + case V_ASN1_EOC: + value = decode_eoc(p, hlen+length); + break; + case V_ASN1_BOOLEAN: + value = decode_bool(p, hlen+length); + break; + case V_ASN1_INTEGER: + value = decode_int(p, hlen+length); + break; + case V_ASN1_BIT_STRING: + value = decode_bstr(p, hlen+length, &flag); + break; + case V_ASN1_NULL: + value = decode_null(p, hlen+length); + break; + case V_ASN1_ENUMERATED: + value = decode_enum(p, hlen+length); + break; + case V_ASN1_OBJECT: + value = decode_obj(p, hlen+length); + break; + case V_ASN1_UTCTIME: /* FALLTHROUGH */ + case V_ASN1_GENERALIZEDTIME: + value = decode_time(p, hlen+length); + break; + default: + /* use original value */ + p += hlen; + value = rb_str_new((const char *)p, length); + break; + } } else { - p += hlen; - value = rb_str_new((const char *)p, length); + p += hlen; + value = rb_str_new((const char *)p, length); } *pp += hlen + length; *num_read = hlen + length; if (tc == sym_UNIVERSAL && - tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { - VALUE klass = *ossl_asn1_info[tag].klass; - VALUE args[4]; - args[0] = value; - args[1] = INT2NUM(tag); - args[2] = Qnil; - args[3] = tc; - asn1data = rb_obj_alloc(klass); - ossl_asn1_initialize(4, args, asn1data); - if(tag == V_ASN1_BIT_STRING){ - rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); - } + tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) { + VALUE klass = *ossl_asn1_info[tag].klass; + VALUE args[4]; + args[0] = value; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + asn1data = rb_obj_alloc(klass); + ossl_asn1_initialize(4, args, asn1data); + if(tag == V_ASN1_BIT_STRING){ + rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); + } } else { - asn1data = rb_obj_alloc(cASN1Data); - ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), tc); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), tc); } return asn1data; @@ -853,8 +853,8 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, static VALUE int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, - long *offset, int depth, int yield, int j, - int tag, VALUE tc, long *num_read) + long *offset, int depth, int yield, int j, + int tag, VALUE tc, long *num_read) { VALUE value, asn1data, ary; int indefinite; @@ -865,42 +865,42 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, available_len = indefinite ? max_len : length; while (available_len > 0) { - long inner_read = 0; - value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read); - *num_read += inner_read; - available_len -= inner_read; + long inner_read = 0; + value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read); + *num_read += inner_read; + available_len -= inner_read; - if (indefinite) { + if (indefinite) { if (ossl_asn1_tag(value) == V_ASN1_EOC && ossl_asn1_get_tag_class(value) == sym_UNIVERSAL) break; if (available_len == 0) ossl_raise(eASN1Error, "EOC missing in indefinite length encoding"); - } - rb_ary_push(ary, value); + } + rb_ary_push(ary, value); } if (tc == sym_UNIVERSAL) { - VALUE args[4]; - if (tag == V_ASN1_SEQUENCE || tag == V_ASN1_SET) - asn1data = rb_obj_alloc(*ossl_asn1_info[tag].klass); - else - asn1data = rb_obj_alloc(cASN1Constructive); - args[0] = ary; - args[1] = INT2NUM(tag); - args[2] = Qnil; - args[3] = tc; - ossl_asn1_initialize(4, args, asn1data); + VALUE args[4]; + if (tag == V_ASN1_SEQUENCE || tag == V_ASN1_SET) + asn1data = rb_obj_alloc(*ossl_asn1_info[tag].klass); + else + asn1data = rb_obj_alloc(cASN1Constructive); + args[0] = ary; + args[1] = INT2NUM(tag); + args[2] = Qnil; + args[3] = tc; + ossl_asn1_initialize(4, args, asn1data); } else { - asn1data = rb_obj_alloc(cASN1Data); - ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), tc); + asn1data = rb_obj_alloc(cASN1Data); + ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), tc); } if (indefinite) - ossl_asn1_set_indefinite_length(asn1data, Qtrue); + ossl_asn1_set_indefinite_length(asn1data, Qtrue); else - ossl_asn1_set_indefinite_length(asn1data, Qfalse); + ossl_asn1_set_indefinite_length(asn1data, Qfalse); *offset = off; return asn1data; @@ -908,7 +908,7 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length, static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, - int yield, long *num_read) + int yield, long *num_read) { unsigned char *start, *p; const unsigned char *p0; @@ -924,46 +924,46 @@ ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, if(j & 0x80) ossl_raise(eASN1Error, NULL); if(len > length) ossl_raise(eASN1Error, "value is too short"); if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE) - tag_class = sym_PRIVATE; + tag_class = sym_PRIVATE; else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC) - tag_class = sym_CONTEXT_SPECIFIC; + tag_class = sym_CONTEXT_SPECIFIC; else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION) - tag_class = sym_APPLICATION; + tag_class = sym_APPLICATION; else - tag_class = sym_UNIVERSAL; + tag_class = sym_UNIVERSAL; hlen = p - start; if(yield) { - VALUE arg = rb_ary_new(); - rb_ary_push(arg, LONG2NUM(depth)); - rb_ary_push(arg, LONG2NUM(*offset)); - rb_ary_push(arg, LONG2NUM(hlen)); - rb_ary_push(arg, LONG2NUM(len)); - rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse); - rb_ary_push(arg, ossl_asn1_class2sym(tc)); - rb_ary_push(arg, INT2NUM(tag)); - rb_yield(arg); + VALUE arg = rb_ary_new(); + rb_ary_push(arg, LONG2NUM(depth)); + rb_ary_push(arg, LONG2NUM(*offset)); + rb_ary_push(arg, LONG2NUM(hlen)); + rb_ary_push(arg, LONG2NUM(len)); + rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse); + rb_ary_push(arg, ossl_asn1_class2sym(tc)); + rb_ary_push(arg, INT2NUM(tag)); + rb_yield(arg); } if(j & V_ASN1_CONSTRUCTED) { - *pp += hlen; - off += hlen; - asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read); - inner_read += hlen; + *pp += hlen; + off += hlen; + asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read); + inner_read += hlen; } else { - if ((j & 0x01) && (len == 0)) - ossl_raise(eASN1Error, "indefinite length for primitive value"); - asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read); - off += hlen + len; + if ((j & 0x01) && (len == 0)) + ossl_raise(eASN1Error, "indefinite length for primitive value"); + asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read); + off += hlen + len; } if (num_read) - *num_read = inner_read; + *num_read = inner_read; if (len != 0 && inner_read != hlen + len) { - ossl_raise(eASN1Error, - "Type mismatch. Bytes read: %ld Bytes available: %ld", - inner_read, hlen + len); + ossl_raise(eASN1Error, + "Type mismatch. Bytes read: %ld Bytes available: %ld", + inner_read, hlen + len); } *offset = off; @@ -974,9 +974,9 @@ static void int_ossl_decode_sanity_check(long len, long read, long offset) { if (len != 0 && (read != len || offset != len)) { - ossl_raise(eASN1Error, - "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld", - read, len, offset); + ossl_raise(eASN1Error, + "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld", + read, len, offset); } } @@ -1076,11 +1076,11 @@ ossl_asn1_decode_all(VALUE self, VALUE obj) tmp_len = len; ary = rb_ary_new(); while (tmp_len > 0) { - long tmp_read = 0; - val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read); - rb_ary_push(ary, val); - read += tmp_read; - tmp_len -= tmp_read; + long tmp_read = 0; + val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read); + rb_ary_push(ary, val); + read += tmp_read; + tmp_len -= tmp_read; } RB_GC_GUARD(tmp); int_ossl_decode_sanity_check(len, read, offset); @@ -1120,23 +1120,23 @@ ossl_asn1_initialize(int argc, VALUE *argv, VALUE self) default_tag = ossl_asn1_default_tag(self); if (default_tag == -1 || argc > 1) { - if(NIL_P(tag)) - ossl_raise(eASN1Error, "must specify tag number"); - if(!NIL_P(tagging) && !SYMBOL_P(tagging)) - ossl_raise(eASN1Error, "invalid tagging method"); - if(NIL_P(tag_class)) { - if (NIL_P(tagging)) - tag_class = sym_UNIVERSAL; - else - tag_class = sym_CONTEXT_SPECIFIC; - } - if(!SYMBOL_P(tag_class)) - ossl_raise(eASN1Error, "invalid tag class"); + if(NIL_P(tag)) + ossl_raise(eASN1Error, "must specify tag number"); + if(!NIL_P(tagging) && !SYMBOL_P(tagging)) + ossl_raise(eASN1Error, "invalid tagging method"); + if(NIL_P(tag_class)) { + if (NIL_P(tagging)) + tag_class = sym_UNIVERSAL; + else + tag_class = sym_CONTEXT_SPECIFIC; + } + if(!SYMBOL_P(tag_class)) + ossl_raise(eASN1Error, "invalid tag class"); } else{ - tag = INT2NUM(default_tag); - tagging = Qnil; - tag_class = sym_UNIVERSAL; + tag = INT2NUM(default_tag); + tagging = Qnil; + tag_class = sym_UNIVERSAL; } ossl_asn1_set_tag(self, tag); ossl_asn1_set_value(self, value); @@ -1144,7 +1144,7 @@ ossl_asn1_initialize(int argc, VALUE *argv, VALUE self) ossl_asn1_set_tag_class(self, tag_class); ossl_asn1_set_indefinite_length(self, Qfalse); if (default_tag == V_ASN1_BIT_STRING) - rb_ivar_set(self, sivUNUSED_BITS, INT2FIX(0)); + rb_ivar_set(self, sivUNUSED_BITS, INT2FIX(0)); return self; } @@ -1186,20 +1186,20 @@ ossl_asn1prim_to_der(VALUE self) VALUE str; if (ossl_asn1_default_tag(self) == -1) { - str = ossl_asn1_get_value(self); - return to_der_internal(self, 0, 0, StringValue(str)); + str = ossl_asn1_get_value(self); + return to_der_internal(self, 0, 0, StringValue(str)); } asn1 = ossl_asn1_get_asn1type(self); alllen = i2d_ASN1_TYPE(asn1, NULL); if (alllen < 0) { - ASN1_TYPE_free(asn1); - ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); + ASN1_TYPE_free(asn1); + ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); } str = ossl_str_new(NULL, alllen, &state); if (state) { - ASN1_TYPE_free(asn1); - rb_jump_tag(state); + ASN1_TYPE_free(asn1); + rb_jump_tag(state); } p0 = p1 = (unsigned char *)RSTRING_PTR(str); if (i2d_ASN1_TYPE(asn1, &p0) < 0) { @@ -1212,7 +1212,7 @@ ossl_asn1prim_to_der(VALUE self) /* Strip header since to_der_internal() wants only the payload */ j = ASN1_get_object((const unsigned char **)&p1, &bodylen, &tag, &tc, alllen); if (j & 0x80) - ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ + ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ return to_der_internal(self, 0, 0, rb_str_drop_bytes(str, alllen - bodylen)); } @@ -1234,22 +1234,22 @@ ossl_asn1cons_to_der(VALUE self) ary = rb_convert_type(ossl_asn1_get_value(self), T_ARRAY, "Array", "to_a"); str = rb_str_new(NULL, 0); for (i = 0; i < RARRAY_LEN(ary); i++) { - VALUE item = RARRAY_AREF(ary, i); - - if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { - if (i != RARRAY_LEN(ary) - 1) - ossl_raise(eASN1Error, "illegal EOC octets in value"); - - /* - * EOC is not really part of the content, but we required to add one - * at the end in the past. - */ - break; - } - - item = ossl_to_der_if_possible(item); - StringValue(item); - rb_str_append(str, item); + VALUE item = RARRAY_AREF(ary, i); + + if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { + if (i != RARRAY_LEN(ary) - 1) + ossl_raise(eASN1Error, "illegal EOC octets in value"); + + /* + * EOC is not really part of the content, but we required to add one + * at the end in the past. + */ + break; + } + + item = ossl_to_der_if_possible(item); + StringValue(item); + rb_str_append(str, item); } return to_der_internal(self, 1, indef_len, str); @@ -1295,7 +1295,7 @@ ossl_asn1obj_s_register(VALUE self, VALUE oid, VALUE sn, VALUE ln) StringValueCStr(ln); if(!OBJ_create(RSTRING_PTR(oid), RSTRING_PTR(sn), RSTRING_PTR(ln))) - ossl_raise(eASN1Error, NULL); + ossl_raise(eASN1Error, NULL); return Qtrue; } @@ -1315,7 +1315,7 @@ ossl_asn1obj_get_sn(VALUE self) val = ossl_asn1_get_value(self); if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef) - ret = rb_str_new2(OBJ_nid2sn(nid)); + ret = rb_str_new2(OBJ_nid2sn(nid)); return ret; } @@ -1335,7 +1335,7 @@ ossl_asn1obj_get_ln(VALUE self) val = ossl_asn1_get_value(self); if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef) - ret = rb_str_new2(OBJ_nid2ln(nid)); + ret = rb_str_new2(OBJ_nid2ln(nid)); return ret; } @@ -1364,7 +1364,7 @@ ossl_asn1obj_get_oid(VALUE self) str = rb_protect(asn1obj_get_oid_i, (VALUE)a1obj, &state); ASN1_OBJECT_free(a1obj); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return str; } @@ -1577,9 +1577,9 @@ Init_ossl_asn1(void) */ rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary); for(i = 0; i < ossl_asn1_info_size; i++){ - if(ossl_asn1_info[i].name[0] == '[') continue; - rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i)); - rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name)); + if(ossl_asn1_info[i].name[0] == '[') continue; + rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i)); + rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name)); } /* Document-class: OpenSSL::ASN1::ASN1Data diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c index 2ef2080507b709..4edde5091d0933 100644 --- a/ext/openssl/ossl_bio.c +++ b/ext/openssl/ossl_bio.c @@ -16,11 +16,11 @@ ossl_obj2bio(volatile VALUE *pobj) BIO *bio; if (RB_TYPE_P(obj, T_FILE)) - obj = rb_funcallv(obj, rb_intern("read"), 0, NULL); + obj = rb_funcallv(obj, rb_intern("read"), 0, NULL); StringValue(obj); bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LENINT(obj)); if (!bio) - ossl_raise(eOSSLError, "BIO_new_mem_buf"); + ossl_raise(eOSSLError, "BIO_new_mem_buf"); *pobj = obj; return bio; } @@ -36,7 +36,7 @@ ossl_membio2str(BIO *bio) ret = ossl_str_new(buf->data, buf->length, &state); BIO_free(bio); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return ret; } diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index 0ac8ae248ef9e4..9014f2df2b9df9 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -11,19 +11,19 @@ #include "ossl.h" #define NewBN(klass) \ - TypedData_Wrap_Struct((klass), &ossl_bn_type, 0) + TypedData_Wrap_Struct((klass), &ossl_bn_type, 0) #define SetBN(obj, bn) do { \ - if (!(bn)) { \ - ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ - } \ - RTYPEDDATA_DATA(obj) = (bn); \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ + RTYPEDDATA_DATA(obj) = (bn); \ } while (0) #define GetBN(obj, bn) do { \ - TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \ - if (!(bn)) { \ - ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ - } \ + TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \ + if (!(bn)) { \ + ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \ + } \ } while (0) static void @@ -35,7 +35,7 @@ ossl_bn_free(void *ptr) static const rb_data_type_t ossl_bn_type = { "OpenSSL/BN", { - 0, ossl_bn_free, + 0, ossl_bn_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; @@ -75,40 +75,40 @@ integer_to_bnptr(VALUE obj, BIGNUM *orig) BIGNUM *bn; if (FIXNUM_P(obj)) { - long i; - unsigned char bin[sizeof(long)]; - long n = FIX2LONG(obj); - unsigned long un = labs(n); - - for (i = sizeof(long) - 1; 0 <= i; i--) { - bin[i] = un & 0xff; - un >>= 8; - } - - bn = BN_bin2bn(bin, sizeof(bin), orig); - if (!bn) - ossl_raise(eBNError, "BN_bin2bn"); - if (n < 0) - BN_set_negative(bn, 1); + long i; + unsigned char bin[sizeof(long)]; + long n = FIX2LONG(obj); + unsigned long un = labs(n); + + for (i = sizeof(long) - 1; 0 <= i; i--) { + bin[i] = un & 0xff; + un >>= 8; + } + + bn = BN_bin2bn(bin, sizeof(bin), orig); + if (!bn) + ossl_raise(eBNError, "BN_bin2bn"); + if (n < 0) + BN_set_negative(bn, 1); } else { /* assuming Bignum */ - size_t len = rb_absint_size(obj, NULL); - unsigned char *bin; - VALUE buf; - int sign; - - if (INT_MAX < len) { - rb_raise(eBNError, "bignum too long"); - } - bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len); - sign = rb_integer_pack(obj, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN); - - bn = BN_bin2bn(bin, (int)len, orig); - ALLOCV_END(buf); - if (!bn) - ossl_raise(eBNError, "BN_bin2bn"); - if (sign < 0) - BN_set_negative(bn, 1); + size_t len = rb_absint_size(obj, NULL); + unsigned char *bin; + VALUE buf; + int sign; + + if (INT_MAX < len) { + rb_raise(eBNError, "bignum too long"); + } + bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len); + sign = rb_integer_pack(obj, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN); + + bn = BN_bin2bn(bin, (int)len, orig); + ALLOCV_END(buf); + if (!bn) + ossl_raise(eBNError, "BN_bin2bn"); + if (sign < 0) + BN_set_negative(bn, 1); } return bn; @@ -121,11 +121,11 @@ try_convert_to_bn(VALUE obj) VALUE newobj = Qnil; if (rb_obj_is_kind_of(obj, cBN)) - return obj; + return obj; if (RB_INTEGER_TYPE_P(obj)) { - newobj = NewBN(cBN); /* Handle potential mem leaks */ - bn = integer_to_bnptr(obj, NULL); - SetBN(newobj, bn); + newobj = NewBN(cBN); /* Handle potential mem leaks */ + bn = integer_to_bnptr(obj, NULL); + SetBN(newobj, bn); } return newobj; @@ -139,7 +139,7 @@ ossl_bn_value_ptr(volatile VALUE *ptr) tmp = try_convert_to_bn(*ptr); if (NIL_P(tmp)) - ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN"); + ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN"); GetBN(tmp, bn); *ptr = tmp; @@ -209,7 +209,7 @@ ossl_bn_alloc(VALUE klass) VALUE obj = NewBN(klass); if (!(bn = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } SetBN(obj, bn); @@ -251,7 +251,7 @@ ossl_bn_initialize(int argc, VALUE *argv, VALUE self) char *ptr; if (rb_scan_args(argc, argv, "11", &str, &bs) == 2) { - base = NUM2INT(bs); + base = NUM2INT(bs); } if (NIL_P(str)) { @@ -260,49 +260,49 @@ ossl_bn_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (RB_INTEGER_TYPE_P(str)) { - GetBN(self, bn); - integer_to_bnptr(str, bn); + GetBN(self, bn); + integer_to_bnptr(str, bn); - return self; + return self; } if (RTEST(rb_obj_is_kind_of(str, cBN))) { - BIGNUM *other; - - GetBN(self, bn); - GetBN(str, other); /* Safe - we checked kind_of? above */ - if (!BN_copy(bn, other)) { - ossl_raise(eBNError, NULL); - } - return self; + BIGNUM *other; + + GetBN(self, bn); + GetBN(str, other); /* Safe - we checked kind_of? above */ + if (!BN_copy(bn, other)) { + ossl_raise(eBNError, NULL); + } + return self; } GetBN(self, bn); switch (base) { - case 0: + case 0: ptr = StringValuePtr(str); if (!BN_mpi2bn((unsigned char *)ptr, RSTRING_LENINT(str), bn)) { - ossl_raise(eBNError, NULL); - } - break; - case 2: + ossl_raise(eBNError, NULL); + } + break; + case 2: ptr = StringValuePtr(str); if (!BN_bin2bn((unsigned char *)ptr, RSTRING_LENINT(str), bn)) { - ossl_raise(eBNError, NULL); - } - break; - case 10: - if (!BN_dec2bn(&bn, StringValueCStr(str))) { - ossl_raise(eBNError, NULL); - } - break; - case 16: - if (!BN_hex2bn(&bn, StringValueCStr(str))) { - ossl_raise(eBNError, NULL); - } - break; - default: - ossl_raise(rb_eArgError, "invalid radix %d", base); + ossl_raise(eBNError, NULL); + } + break; + case 10: + if (!BN_dec2bn(&bn, StringValueCStr(str))) { + ossl_raise(eBNError, NULL); + } + break; + case 16: + if (!BN_hex2bn(&bn, StringValueCStr(str))) { + ossl_raise(eBNError, NULL); + } + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); } return self; } @@ -334,32 +334,32 @@ ossl_bn_to_s(int argc, VALUE *argv, VALUE self) char *buf; if (rb_scan_args(argc, argv, "01", &bs) == 1) { - base = NUM2INT(bs); + base = NUM2INT(bs); } GetBN(self, bn); switch (base) { - case 0: - len = BN_bn2mpi(bn, NULL); + case 0: + len = BN_bn2mpi(bn, NULL); str = rb_str_new(0, len); - if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len) - ossl_raise(eBNError, NULL); - break; - case 2: - len = BN_num_bytes(bn); + if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 2: + len = BN_num_bytes(bn); str = rb_str_new(0, len); - if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) - ossl_raise(eBNError, NULL); - break; - case 10: - if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL); - str = ossl_buf2str(buf, rb_long2int(strlen(buf))); - break; - case 16: - if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL); - str = ossl_buf2str(buf, rb_long2int(strlen(buf))); - break; - default: - ossl_raise(rb_eArgError, "invalid radix %d", base); + if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len) + ossl_raise(eBNError, NULL); + break; + case 10: + if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + case 16: + if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL); + str = ossl_buf2str(buf, rb_long2int(strlen(buf))); + break; + default: + ossl_raise(rb_eArgError, "invalid radix %d", base); } return str; @@ -379,7 +379,7 @@ ossl_bn_to_i(VALUE self) GetBN(self, bn); if (!(txt = BN_bn2hex(bn))) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } num = rb_cstr_to_inum(txt, 16, Qtrue); OPENSSL_free(txt); @@ -397,31 +397,31 @@ static VALUE ossl_bn_coerce(VALUE self, VALUE other) { switch(TYPE(other)) { - case T_STRING: - self = ossl_bn_to_s(0, NULL, self); - break; - case T_FIXNUM: - case T_BIGNUM: - self = ossl_bn_to_i(self); - break; - default: - if (!RTEST(rb_obj_is_kind_of(other, cBN))) { - ossl_raise(rb_eTypeError, "Don't know how to coerce"); - } + case T_STRING: + self = ossl_bn_to_s(0, NULL, self); + break; + case T_FIXNUM: + case T_BIGNUM: + self = ossl_bn_to_i(self); + break; + default: + if (!RTEST(rb_obj_is_kind_of(other, cBN))) { + ossl_raise(rb_eTypeError, "Don't know how to coerce"); + } } return rb_assoc_new(other, self); } -#define BIGNUM_BOOL1(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn; \ - GetBN(self, bn); \ - if (BN_##func(bn)) { \ - return Qtrue; \ - } \ - return Qfalse; \ +#define BIGNUM_BOOL1(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + if (BN_##func(bn)) { \ + return Qtrue; \ + } \ + return Qfalse; \ } /* @@ -456,27 +456,27 @@ ossl_bn_is_negative(VALUE self) GetBN(self, bn); if (BN_is_zero(bn)) - return Qfalse; + return Qfalse; return BN_is_negative(bn) ? Qtrue : Qfalse; } -#define BIGNUM_1c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn, *result; \ - VALUE obj; \ - GetBN(self, bn); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_1c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn, *result; \ + VALUE obj; \ + GetBN(self, bn); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -486,23 +486,23 @@ ossl_bn_is_negative(VALUE self) */ BIGNUM_1c(sqr) -#define BIGNUM_2(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -519,23 +519,23 @@ BIGNUM_2(add) */ BIGNUM_2(sub) -#define BIGNUM_2c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -573,18 +573,18 @@ BIGNUM_2c(gcd) */ BIGNUM_2c(mod_sqr) -#define BIGNUM_2cr(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ - ossl_raise(eBNError, NULL); \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_2cr(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ + ossl_raise(eBNError, NULL); \ + SetBN(obj, result); \ + return obj; \ } /* @@ -619,16 +619,16 @@ ossl_bn_div(VALUE self, VALUE other) obj1 = NewBN(klass); obj2 = NewBN(klass); if (!(r1 = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } if (!(r2 = BN_new())) { - BN_free(r1); - ossl_raise(eBNError, NULL); + BN_free(r1); + ossl_raise(eBNError, NULL); } if (!BN_div(r1, r2, bn1, bn2, ossl_bn_ctx)) { - BN_free(r1); - BN_free(r2); - ossl_raise(eBNError, NULL); + BN_free(r1); + BN_free(r2); + ossl_raise(eBNError, NULL); } SetBN(obj1, r1); SetBN(obj2, r2); @@ -636,24 +636,24 @@ ossl_bn_div(VALUE self, VALUE other) return rb_ary_new3(2, obj1, obj2); } -#define BIGNUM_3c(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other1, VALUE other2) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other1); \ - BIGNUM *bn3 = GetBNPtr(other2), *result; \ - VALUE obj; \ - GetBN(self, bn1); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_3c(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other1, VALUE other2) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other1); \ + BIGNUM *bn3 = GetBNPtr(other2), *result; \ + VALUE obj; \ + GetBN(self, bn1); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -684,17 +684,17 @@ BIGNUM_3c(mod_mul) */ BIGNUM_3c(mod_exp) -#define BIGNUM_BIT(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE bit) \ - { \ - BIGNUM *bn; \ - rb_check_frozen(self); \ - GetBN(self, bn); \ - if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ - ossl_raise(eBNError, NULL); \ - } \ - return self; \ +#define BIGNUM_BIT(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bit) \ + { \ + BIGNUM *bn; \ + rb_check_frozen(self); \ + GetBN(self, bn); \ + if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ + ossl_raise(eBNError, NULL); \ + } \ + return self; \ } /* @@ -733,30 +733,30 @@ ossl_bn_is_bit_set(VALUE self, VALUE bit) b = NUM2INT(bit); GetBN(self, bn); if (BN_is_bit_set(bn, b)) { - return Qtrue; + return Qtrue; } return Qfalse; } -#define BIGNUM_SHIFT(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE bits) \ - { \ - BIGNUM *bn, *result; \ - int b; \ - VALUE obj; \ - b = NUM2INT(bits); \ - GetBN(self, bn); \ - obj = NewBN(rb_obj_class(self)); \ - if (!(result = BN_new())) { \ - ossl_raise(eBNError, NULL); \ - } \ - if (BN_##func(result, bn, b) <= 0) { \ - BN_free(result); \ - ossl_raise(eBNError, NULL); \ - } \ - SetBN(obj, result); \ - return obj; \ +#define BIGNUM_SHIFT(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn, *result; \ + int b; \ + VALUE obj; \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + obj = NewBN(rb_obj_class(self)); \ + if (!(result = BN_new())) { \ + ossl_raise(eBNError, NULL); \ + } \ + if (BN_##func(result, bn, b) <= 0) { \ + BN_free(result); \ + ossl_raise(eBNError, NULL); \ + } \ + SetBN(obj, result); \ + return obj; \ } /* @@ -773,18 +773,18 @@ BIGNUM_SHIFT(lshift) */ BIGNUM_SHIFT(rshift) -#define BIGNUM_SELF_SHIFT(func) \ - static VALUE \ - ossl_bn_self_##func(VALUE self, VALUE bits) \ - { \ - BIGNUM *bn; \ - int b; \ - rb_check_frozen(self); \ - b = NUM2INT(bits); \ - GetBN(self, bn); \ - if (BN_##func(bn, bn, b) <= 0) \ - ossl_raise(eBNError, NULL); \ - return self; \ +#define BIGNUM_SELF_SHIFT(func) \ + static VALUE \ + ossl_bn_self_##func(VALUE self, VALUE bits) \ + { \ + BIGNUM *bn; \ + int b; \ + rb_check_frozen(self); \ + b = NUM2INT(bits); \ + GetBN(self, bn); \ + if (BN_##func(bn, bn, b) <= 0) \ + ossl_raise(eBNError, NULL); \ + return self; \ } /* @@ -886,32 +886,32 @@ ossl_bn_s_generate_prime(int argc, VALUE *argv, VALUE klass) num = NUM2INT(vnum); if (vsafe == Qfalse) { - safe = 0; + safe = 0; } if (!NIL_P(vadd)) { - add = GetBNPtr(vadd); - rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem); + add = GetBNPtr(vadd); + rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem); } obj = NewBN(klass); if (!(result = BN_new())) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } if (!BN_generate_prime_ex(result, num, safe, add, rem, NULL)) { - BN_free(result); - ossl_raise(eBNError, NULL); + BN_free(result); + ossl_raise(eBNError, NULL); } SetBN(obj, result); return obj; } -#define BIGNUM_NUM(func) \ - static VALUE \ - ossl_bn_##func(VALUE self) \ - { \ - BIGNUM *bn; \ - GetBN(self, bn); \ - return INT2NUM(BN_##func(bn)); \ +#define BIGNUM_NUM(func) \ + static VALUE \ + ossl_bn_##func(VALUE self) \ + { \ + BIGNUM *bn; \ + GetBN(self, bn); \ + return INT2NUM(BN_##func(bn)); \ } /* @@ -942,7 +942,7 @@ ossl_bn_copy(VALUE self, VALUE other) bn2 = GetBNPtr(other); if (!BN_copy(bn1, bn2)) { - ossl_raise(eBNError, NULL); + ossl_raise(eBNError, NULL); } return self; } @@ -961,7 +961,7 @@ ossl_bn_uplus(VALUE self) obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) - ossl_raise(eBNError, "BN_dup"); + ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); return obj; @@ -981,7 +981,7 @@ ossl_bn_uminus(VALUE self) obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) - ossl_raise(eBNError, "BN_dup"); + ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); BN_set_negative(bn2, !BN_is_negative(bn2)); @@ -1006,13 +1006,13 @@ ossl_bn_abs(VALUE self) } } -#define BIGNUM_CMP(func) \ - static VALUE \ - ossl_bn_##func(VALUE self, VALUE other) \ - { \ - BIGNUM *bn1, *bn2 = GetBNPtr(other); \ - GetBN(self, bn1); \ - return INT2NUM(BN_##func(bn1, bn2)); \ +#define BIGNUM_CMP(func) \ + static VALUE \ + ossl_bn_##func(VALUE self, VALUE other) \ + { \ + BIGNUM *bn1, *bn2 = GetBNPtr(other); \ + GetBN(self, bn1); \ + return INT2NUM(BN_##func(bn1, bn2)); \ } /* @@ -1049,11 +1049,11 @@ ossl_bn_eq(VALUE self, VALUE other) GetBN(self, bn1); other = try_convert_to_bn(other); if (NIL_P(other)) - return Qfalse; + return Qfalse; GetBN(other, bn2); if (!BN_cmp(bn1, bn2)) { - return Qtrue; + return Qtrue; } return Qfalse; } @@ -1072,7 +1072,7 @@ ossl_bn_eql(VALUE self, VALUE other) BIGNUM *bn1, *bn2; if (!rb_obj_is_kind_of(other, cBN)) - return Qfalse; + return Qfalse; GetBN(self, bn1); GetBN(other, bn2); @@ -1099,8 +1099,8 @@ ossl_bn_hash(VALUE self) len = BN_num_bytes(bn); buf = ALLOCV(tmp, len); if (BN_bn2bin(bn, buf) != len) { - ALLOCV_END(tmp); - ossl_raise(eBNError, "BN_bn2bin"); + ALLOCV_END(tmp); + ossl_raise(eBNError, "BN_bn2bin"); } hash = ST2FIX(rb_memhash(buf, len)); diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index f7b2c01a36d933..db65e99888d3bf 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -14,7 +14,7 @@ #define AllocCipher(obj, ctx) do { \ (ctx) = EVP_CIPHER_CTX_new(); \ if (!(ctx)) \ - ossl_raise(rb_eRuntimeError, NULL); \ + ossl_raise(rb_eRuntimeError, NULL); \ RTYPEDDATA_DATA(obj) = (ctx); \ } while (0) #define GetCipherInit(obj, ctx) do { \ @@ -23,7 +23,7 @@ #define GetCipher(obj, ctx) do { \ GetCipherInit((obj), (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "Cipher not initialized!"); \ + ossl_raise(rb_eRuntimeError, "Cipher not initialized!"); \ } \ } while (0) @@ -41,7 +41,7 @@ static void ossl_cipher_free(void *ptr); static const rb_data_type_t ossl_cipher_type = { "OpenSSL/Cipher", { - 0, ossl_cipher_free, + 0, ossl_cipher_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -112,7 +112,7 @@ ossl_cipher_new(const EVP_CIPHER *cipher) ret = ossl_cipher_alloc(cCipher); AllocCipher(ret, ctx); if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return ret; } @@ -149,7 +149,7 @@ ossl_cipher_initialize(VALUE self, VALUE str) GetCipherInit(self, ctx); if (ctx) { - ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); + ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } cipher = ossl_evp_cipher_fetch(str, &cipher_holder); AllocCipher(self, ctx); @@ -171,11 +171,11 @@ ossl_cipher_copy(VALUE self, VALUE other) GetCipherInit(self, ctx1); if (!ctx1) { - AllocCipher(self, ctx1); + AllocCipher(self, ctx1); } GetCipher(other, ctx2); if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return self; } @@ -200,8 +200,8 @@ ossl_s_ciphers(VALUE self) ary = rb_ary_new(); OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH, - add_cipher_name_to_ary, - (void*)ary); + add_cipher_name_to_ary, + (void*)ary); return ary; } @@ -222,7 +222,7 @@ ossl_cipher_reset(VALUE self) GetCipher(self, ctx); if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return self; } @@ -304,20 +304,20 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest); StringValue(vpass); if(!NIL_P(vsalt)){ - StringValue(vsalt); - if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN) - ossl_raise(eCipherError, "salt must be an 8-octet string"); - salt = (unsigned char *)RSTRING_PTR(vsalt); + StringValue(vsalt); + if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN) + ossl_raise(eCipherError, "salt must be an 8-octet string"); + salt = (unsigned char *)RSTRING_PTR(vsalt); } iter = NIL_P(viter) ? 2048 : NUM2INT(viter); if (iter <= 0) - rb_raise(rb_eArgError, "iterations must be a positive integer"); + rb_raise(rb_eArgError, "iterations must be a positive integer"); digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_md_fetch(vdigest, &md_holder); GetCipher(self, ctx); EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt, - (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); + (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv); if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); OPENSSL_cleanse(key, sizeof key); OPENSSL_cleanse(iv, sizeof iv); @@ -328,25 +328,25 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self) static int ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_ptr, - const unsigned char *in, long in_len) + const unsigned char *in, long in_len) { int out_part_len; int limit = INT_MAX / 2 + 1; long out_len = 0; do { - int in_part_len = in_len > limit ? limit : (int)in_len; + int in_part_len = in_len > limit ? limit : (int)in_len; - if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0, - &out_part_len, in, in_part_len)) - return 0; + if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0, + &out_part_len, in, in_part_len)) + return 0; - out_len += out_part_len; - in += in_part_len; + out_len += out_part_len; + in += in_part_len; } while ((in_len -= limit) > 0); if (out_len_ptr) - *out_len_ptr = out_len; + *out_len_ptr = out_len; return 1; } @@ -377,7 +377,7 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "11", &data, &str); if (!RTEST(rb_attr_get(self, id_key_set))) - ossl_raise(eCipherError, "key not set"); + ossl_raise(eCipherError, "key not set"); StringValue(data); in = (unsigned char *)RSTRING_PTR(data); @@ -396,8 +396,8 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) * currently implemented in OpenSSL, but this can change in the future. */ if (in_len > LONG_MAX - EVP_MAX_BLOCK_LENGTH) { - ossl_raise(rb_eRangeError, - "data too big to make output buffer: %ld bytes", in_len); + ossl_raise(rb_eRangeError, + "data too big to make output buffer: %ld bytes", in_len); } out_len = in_len + EVP_MAX_BLOCK_LENGTH; @@ -412,7 +412,7 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) } if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len)) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); assert(out_len <= RSTRING_LEN(str)); rb_str_set_len(str, out_len); @@ -503,10 +503,10 @@ ossl_cipher_set_key(VALUE self, VALUE key) key_len = EVP_CIPHER_CTX_key_length(ctx); if (RSTRING_LEN(key) != key_len) - ossl_raise(rb_eArgError, "key must be %d bytes", key_len); + ossl_raise(rb_eArgError, "key must be %d bytes", key_len); if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); rb_ivar_set(self, id_key_set, Qtrue); @@ -541,14 +541,14 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) GetCipher(self, ctx); if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) - iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); + iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!iv_len) - iv_len = EVP_CIPHER_CTX_iv_length(ctx); + iv_len = EVP_CIPHER_CTX_iv_length(ctx); if (RSTRING_LEN(iv) != iv_len) - ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len); + ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len); if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return iv; } @@ -603,7 +603,7 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "AEAD not supported by this cipher"); + ossl_raise(eCipherError, "AEAD not supported by this cipher"); if (!ossl_cipher_update_long(ctx, NULL, &out_len, in, in_len)) ossl_raise(eCipherError, "couldn't set additional authenticated data"); @@ -636,18 +636,18 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &vtag_len); if (NIL_P(vtag_len)) - vtag_len = rb_attr_get(self, id_auth_tag_len); + vtag_len = rb_attr_get(self, id_auth_tag_len); if (!NIL_P(vtag_len)) - tag_len = NUM2INT(vtag_len); + tag_len = NUM2INT(vtag_len); GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); ret = rb_str_new(NULL, tag_len); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, RSTRING_PTR(ret))) - ossl_raise(eCipherError, "retrieving the authentication tag failed"); + ossl_raise(eCipherError, "retrieving the authentication tag failed"); return ret; } @@ -686,10 +686,10 @@ ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "authentication tag not supported by this cipher"); + ossl_raise(eCipherError, "authentication tag not supported by this cipher"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag)) - ossl_raise(eCipherError, "unable to set AEAD tag"); + ossl_raise(eCipherError, "unable to set AEAD tag"); return vtag; } @@ -716,10 +716,10 @@ ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "AEAD not supported by this cipher"); + ossl_raise(eCipherError, "AEAD not supported by this cipher"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL)) - ossl_raise(eCipherError, "unable to set authentication tag length"); + ossl_raise(eCipherError, "unable to set authentication tag length"); /* for #auth_tag */ rb_ivar_set(self, id_auth_tag_len, INT2NUM(tag_len)); @@ -748,10 +748,10 @@ ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) GetCipher(self, ctx); if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) - ossl_raise(eCipherError, "cipher does not support AEAD"); + ossl_raise(eCipherError, "cipher does not support AEAD"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL)) - ossl_raise(eCipherError, "unable to set IV length"); + ossl_raise(eCipherError, "unable to set IV length"); /* * EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save @@ -809,7 +809,7 @@ ossl_cipher_set_padding(VALUE self, VALUE padding) GetCipher(self, ctx); if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return padding; } @@ -843,9 +843,9 @@ ossl_cipher_iv_length(VALUE self) GetCipher(self, ctx); if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) - len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); + len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!len) - len = EVP_CIPHER_CTX_iv_length(ctx); + len = EVP_CIPHER_CTX_iv_length(ctx); return INT2NUM(len); } diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index 8c1b825cf3909c..e23968b1e325ef 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -12,7 +12,7 @@ #define GetDigest(obj, ctx) do { \ TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_digest_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ } \ } while (0) @@ -34,7 +34,7 @@ ossl_digest_free(void *ctx) static const rb_data_type_t ossl_digest_type = { "OpenSSL/Digest", { - 0, ossl_digest_free, + 0, ossl_digest_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -110,11 +110,11 @@ ossl_digest_new(const EVP_MD *md) ret = ossl_digest_alloc(cDigest); ctx = EVP_MD_CTX_new(); if (!ctx) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + ossl_raise(eDigestError, "EVP_MD_CTX_new"); RTYPEDDATA_DATA(ret) = ctx; if (!EVP_DigestInit_ex(ctx, md, NULL)) - ossl_raise(eDigestError, "Digest initialization failed"); + ossl_raise(eDigestError, "Digest initialization failed"); return ret; } @@ -161,13 +161,13 @@ ossl_digest_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx); if (!ctx) { - RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); - if (!ctx) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); + if (!ctx) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); } if (!EVP_DigestInit_ex(ctx, md, NULL)) - ossl_raise(eDigestError, "Digest initialization failed"); + ossl_raise(eDigestError, "Digest initialization failed"); rb_ivar_set(self, id_md_holder, md_holder); if (!NIL_P(data)) return ossl_digest_update(self, data); @@ -185,14 +185,14 @@ ossl_digest_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx1); if (!ctx1) { - RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); - if (!ctx1) - ossl_raise(eDigestError, "EVP_MD_CTX_new"); + RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); + if (!ctx1) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); } GetDigest(other, ctx2); if (!EVP_MD_CTX_copy(ctx1, ctx2)) { - ossl_raise(eDigestError, NULL); + ossl_raise(eDigestError, NULL); } return self; } @@ -217,8 +217,8 @@ ossl_s_digests(VALUE self) ary = rb_ary_new(); OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, - add_digest_name_to_ary, - (void*)ary); + add_digest_name_to_ary, + (void*)ary); return ary; } @@ -238,7 +238,7 @@ ossl_digest_reset(VALUE self) GetDigest(self, ctx); if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_get0_md(ctx), NULL) != 1) { - ossl_raise(eDigestError, "Digest initialization failed."); + ossl_raise(eDigestError, "Digest initialization failed."); } return self; @@ -268,7 +268,7 @@ ossl_digest_update(VALUE self, VALUE data) GetDigest(self, ctx); if (!EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) - ossl_raise(eDigestError, "EVP_DigestUpdate"); + ossl_raise(eDigestError, "EVP_DigestUpdate"); return self; } @@ -287,7 +287,7 @@ ossl_digest_finish(VALUE self) GetDigest(self, ctx); str = rb_str_new(NULL, EVP_MD_CTX_size(ctx)); if (!EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL)) - ossl_raise(eDigestError, "EVP_DigestFinal_ex"); + ossl_raise(eDigestError, "EVP_DigestFinal_ex"); return str; } diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index e40f0ff68de37f..a2bcb07ea47d73 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -16,7 +16,7 @@ TypedData_Wrap_Struct((klass), &ossl_engine_type, 0) #define SetEngine(obj, engine) do { \ if (!(engine)) { \ - ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (engine); \ } while(0) @@ -49,12 +49,12 @@ static VALUE eEngineError; */ #define OSSL_ENGINE_LOAD_IF_MATCH(engine_name, x) \ do{\ - if(!strcmp(#engine_name, RSTRING_PTR(name))){\ - if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ - return Qtrue;\ - else\ - ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ - }\ + if(!strcmp(#engine_name, RSTRING_PTR(name))){\ + if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ + return Qtrue;\ + else\ + ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ + }\ }while(0) static void @@ -66,7 +66,7 @@ ossl_engine_free(void *engine) static const rb_data_type_t ossl_engine_type = { "OpenSSL/Engine", { - 0, ossl_engine_free, + 0, ossl_engine_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -130,12 +130,12 @@ ossl_engine_s_engines(VALUE klass) ary = rb_ary_new(); for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)){ - obj = NewEngine(klass); - /* Need a ref count of two here because of ENGINE_free being - * called internally by OpenSSL when moving to the next ENGINE - * and by us when releasing the ENGINE reference */ - ENGINE_up_ref(e); - SetEngine(obj, e); + obj = NewEngine(klass); + /* Need a ref count of two here because of ENGINE_free being + * called internally by OpenSSL when moving to the next ENGINE + * and by us when releasing the ENGINE reference */ + ENGINE_up_ref(e); + SetEngine(obj, e); rb_ary_push(ary, obj); } @@ -163,13 +163,13 @@ ossl_engine_s_by_id(VALUE klass, VALUE id) ossl_engine_s_load(1, &id, klass); obj = NewEngine(klass); if(!(e = ENGINE_by_id(RSTRING_PTR(id)))) - ossl_raise(eEngineError, NULL); + ossl_raise(eEngineError, NULL); SetEngine(obj, e); if(rb_block_given_p()) rb_yield(obj); if(!ENGINE_init(e)) - ossl_raise(eEngineError, NULL); + ossl_raise(eEngineError, NULL); ENGINE_ctrl(e, ENGINE_CTRL_SET_PASSWORD_CALLBACK, - 0, NULL, (void(*)(void))ossl_pem_passwd_cb); + 0, NULL, (void(*)(void))ossl_pem_passwd_cb); ossl_clear_error(); return obj; @@ -184,7 +184,7 @@ ossl_engine_s_by_id(VALUE klass, VALUE id) * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#, ...] * OpenSSL::Engine.engines.first.id - * #=> "rsax" + * #=> "rsax" */ static VALUE ossl_engine_get_id(VALUE self) @@ -203,7 +203,7 @@ ossl_engine_get_id(VALUE self) * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#, ...] * OpenSSL::Engine.engines.first.name - * #=> "RSAX engine support" + * #=> "RSAX engine support" * */ static VALUE @@ -274,11 +274,11 @@ ossl_engine_get_cipher(VALUE self, VALUE name) * Will raise an EngineError if the digest is unavailable. * * e = OpenSSL::Engine.by_id("openssl") - * #=> # + * #=> # * e.digest("SHA1") - * #=> # + * #=> # * e.digest("zomg") - * #=> OpenSSL::Engine::EngineError: no such digest `zomg' + * #=> OpenSSL::Engine::EngineError: no such digest `zomg' */ static VALUE ossl_engine_get_digest(VALUE self, VALUE name) @@ -365,7 +365,7 @@ ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self) * your OS. * * [All flags] 0xFFFF - * [No flags] 0x0000 + * [No flags] 0x0000 * * See also */ @@ -399,7 +399,7 @@ ossl_engine_ctrl_cmd(int argc, VALUE *argv, VALUE self) GetEngine(self, e); rb_scan_args(argc, argv, "11", &cmd, &val); ret = ENGINE_ctrl_cmd_string(e, StringValueCStr(cmd), - NIL_P(val) ? NULL : StringValueCStr(val), 0); + NIL_P(val) ? NULL : StringValueCStr(val), 0); if (!ret) ossl_raise(eEngineError, NULL); return self; @@ -409,11 +409,11 @@ static VALUE ossl_engine_cmd_flag_to_name(int flag) { switch(flag){ - case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC"); - case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING"); - case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT"); - case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL"); - default: return rb_str_new2("UNKNOWN"); + case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC"); + case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING"); + case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT"); + case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL"); + default: return rb_str_new2("UNKNOWN"); } } @@ -433,13 +433,13 @@ ossl_engine_get_cmds(VALUE self) GetEngine(self, e); ary = rb_ary_new(); if ((defn = ENGINE_get_cmd_defns(e)) != NULL){ - for (p = defn; p->cmd_num > 0; p++){ - tmp = rb_ary_new(); - rb_ary_push(tmp, rb_str_new2(p->cmd_name)); - rb_ary_push(tmp, rb_str_new2(p->cmd_desc)); - rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags)); - rb_ary_push(ary, tmp); - } + for (p = defn; p->cmd_num > 0; p++){ + tmp = rb_ary_new(); + rb_ary_push(tmp, rb_str_new2(p->cmd_name)); + rb_ary_push(tmp, rb_str_new2(p->cmd_desc)); + rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags)); + rb_ary_push(ary, tmp); + } } return ary; @@ -458,7 +458,7 @@ ossl_engine_inspect(VALUE self) GetEngine(self, e); return rb_sprintf("#<%"PRIsVALUE" id=\"%s\" name=\"%s\">", - rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e)); + rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e)); } #define DefEngineConst(x) rb_define_const(cEngine, #x, INT2NUM(ENGINE_##x)) diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index 8153f64fc57d65..ddcc6a5f8d0d3a 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -14,7 +14,7 @@ #define GetHMAC(obj, ctx) do { \ TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_hmac_type, (ctx)); \ if (!(ctx)) { \ - ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ + ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ } \ } while (0) @@ -41,7 +41,7 @@ ossl_hmac_free(void *ctx) static const rb_data_type_t ossl_hmac_type = { "OpenSSL/HMAC", { - 0, ossl_hmac_free, + 0, ossl_hmac_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -74,17 +74,17 @@ ossl_hmac_alloc(VALUE klass) * * === Example * - * key = 'key' - * instance = OpenSSL::HMAC.new(key, 'SHA1') - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f - * instance.class - * #=> OpenSSL::HMAC + * key = 'key' + * instance = OpenSSL::HMAC.new(key, 'SHA1') + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.class + * #=> OpenSSL::HMAC * * === A note about comparisons * * Two instances can be securely compared with #== in constant time: * - * other_instance = OpenSSL::HMAC.new('key', 'SHA1') + * other_instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance == other_instance * #=> true @@ -142,13 +142,13 @@ ossl_hmac_copy(VALUE self, VALUE other) * * === Example * - * first_chunk = 'The quick brown fox jumps ' - * second_chunk = 'over the lazy dog' + * first_chunk = 'The quick brown fox jumps ' + * second_chunk = 'over the lazy dog' * - * instance.update(first_chunk) - * #=> 5b9a8038a65d571076d97fe783989e52278a492a - * instance.update(second_chunk) - * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * instance.update(first_chunk) + * #=> 5b9a8038a65d571076d97fe783989e52278a492a + * instance.update(second_chunk) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 * */ static VALUE @@ -226,14 +226,14 @@ ossl_hmac_hexdigest(VALUE self) * * === Example * - * data = "The quick brown fox jumps over the lazy dog" - * instance = OpenSSL::HMAC.new('key', 'SHA1') - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * data = "The quick brown fox jumps over the lazy dog" + * instance = OpenSSL::HMAC.new('key', 'SHA1') + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * - * instance.update(data) - * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 - * instance.reset - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance.update(data) + * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 + * instance.reset + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * */ static VALUE diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index 9450612ee2d375..1f280164405aed 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -41,10 +41,10 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) const EVP_MD *md; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("iterations"); - kwargs_ids[2] = rb_intern_const("length"); - kwargs_ids[3] = rb_intern_const("hash"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("iterations"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); @@ -57,10 +57,10 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) str = rb_str_new(0, len); if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), - (unsigned char *)RSTRING_PTR(salt), - RSTRING_LENINT(salt), iters, md, len, - (unsigned char *)RSTRING_PTR(str))) - ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); + (unsigned char *)RSTRING_PTR(salt), + RSTRING_LENINT(salt), iters, md, len, + (unsigned char *)RSTRING_PTR(str))) + ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); return str; } @@ -107,11 +107,11 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) uint64_t N, r, p, maxmem; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("N"); - kwargs_ids[2] = rb_intern_const("r"); - kwargs_ids[3] = rb_intern_const("p"); - kwargs_ids[4] = rb_intern_const("length"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("N"); + kwargs_ids[2] = rb_intern_const("r"); + kwargs_ids[3] = rb_intern_const("p"); + kwargs_ids[4] = rb_intern_const("length"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 5, 0, kwargs); @@ -131,9 +131,9 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) str = rb_str_new(0, len); if (!EVP_PBE_scrypt(RSTRING_PTR(pass), RSTRING_LEN(pass), - (unsigned char *)RSTRING_PTR(salt), RSTRING_LEN(salt), - N, r, p, maxmem, (unsigned char *)RSTRING_PTR(str), len)) - ossl_raise(eKDF, "EVP_PBE_scrypt"); + (unsigned char *)RSTRING_PTR(salt), RSTRING_LEN(salt), + N, r, p, maxmem, (unsigned char *)RSTRING_PTR(str), len)) + ossl_raise(eKDF, "EVP_PBE_scrypt"); return str; } @@ -180,10 +180,10 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) EVP_PKEY_CTX *pctx; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt"); - kwargs_ids[1] = rb_intern_const("info"); - kwargs_ids[2] = rb_intern_const("length"); - kwargs_ids[3] = rb_intern_const("hash"); + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("info"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &ikm, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); @@ -196,39 +196,39 @@ kdf_hkdf(int argc, VALUE *argv, VALUE self) infolen = RSTRING_LENINT(info); len = (size_t)NUM2LONG(kwargs[2]); if (len > LONG_MAX) - rb_raise(rb_eArgError, "length must be non-negative"); + rb_raise(rb_eArgError, "length must be non-negative"); md = ossl_evp_md_fetch(kwargs[3], &md_holder); str = rb_str_new(NULL, (long)len); pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); if (!pctx) - ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); + ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); if (EVP_PKEY_derive_init(pctx) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_derive_init"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive_init"); } if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); } if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt), - saltlen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); + saltlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm), - ikmlen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); + ikmlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); } if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info), - infolen) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); + infolen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); } if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) { - EVP_PKEY_CTX_free(pctx); - ossl_raise(eKDF, "EVP_PKEY_derive"); + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive"); } rb_str_set_len(str, (long)len); EVP_PKEY_CTX_free(pctx); diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index e1589fea0e0b24..1d1498824620a9 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_netscape_spki_type, 0) #define SetSPKI(obj, spki) do { \ if (!(spki)) { \ - ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (spki); \ } while (0) #define GetSPKI(obj, spki) do { \ TypedData_Get_Struct((obj), NETSCAPE_SPKI, &ossl_netscape_spki_type, (spki)); \ if (!(spki)) { \ - ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \ } \ } while (0) @@ -48,7 +48,7 @@ ossl_netscape_spki_free(void *spki) static const rb_data_type_t ossl_netscape_spki_type = { "OpenSSL/NETSCAPE_SPKI", { - 0, ossl_netscape_spki_free, + 0, ossl_netscape_spki_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -61,7 +61,7 @@ ossl_spki_alloc(VALUE klass) obj = NewSPKI(klass); if (!(spki = NETSCAPE_SPKI_new())) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } SetSPKI(obj, spki); @@ -83,15 +83,15 @@ ossl_spki_initialize(int argc, VALUE *argv, VALUE self) const unsigned char *p; if (rb_scan_args(argc, argv, "01", &buffer) == 0) { - return self; + return self; } StringValue(buffer); if (!(spki = NETSCAPE_SPKI_b64_decode(RSTRING_PTR(buffer), RSTRING_LENINT(buffer)))) { - ossl_clear_error(); - p = (unsigned char *)RSTRING_PTR(buffer); - if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) { - ossl_raise(eSPKIError, NULL); - } + ossl_clear_error(); + p = (unsigned char *)RSTRING_PTR(buffer); + if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) { + ossl_raise(eSPKIError, NULL); + } } NETSCAPE_SPKI_free(DATA_PTR(self)); SetSPKI(self, spki); @@ -140,7 +140,7 @@ ossl_spki_to_pem(VALUE self) GetSPKI(self, spki); if (!(data = NETSCAPE_SPKI_b64_encode(spki))) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } str = ossl_buf2str(data, rb_long2int(strlen(data))); @@ -162,11 +162,11 @@ ossl_spki_print(VALUE self) GetSPKI(self, spki); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } if (!NETSCAPE_SPKI_print(out, spki)) { - BIO_free(out); - ossl_raise(eSPKIError, NULL); + BIO_free(out); + ossl_raise(eSPKIError, NULL); } return ossl_membio2str(out); @@ -187,7 +187,7 @@ ossl_spki_get_public_key(VALUE self) GetSPKI(self, spki); if (!(pkey = NETSCAPE_SPKI_get_pubkey(spki))) { /* adds an reference */ - ossl_raise(eSPKIError, NULL); + ossl_raise(eSPKIError, NULL); } return ossl_pkey_wrap(pkey); @@ -214,7 +214,7 @@ ossl_spki_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) - ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); + ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); return key; } @@ -231,12 +231,12 @@ ossl_spki_get_challenge(VALUE self) GetSPKI(self, spki); if (spki->spkac->challenge->length <= 0) { - OSSL_Debug("Challenge.length <= 0?"); - return rb_str_new(0, 0); + OSSL_Debug("Challenge.length <= 0?"); + return rb_str_new(0, 0); } return rb_str_new((const char *)spki->spkac->challenge->data, - spki->spkac->challenge->length); + spki->spkac->challenge->length); } /* @@ -257,8 +257,8 @@ ossl_spki_set_challenge(VALUE self, VALUE str) StringValue(str); GetSPKI(self, spki); if (!ASN1_STRING_set(spki->spkac->challenge, RSTRING_PTR(str), - RSTRING_LENINT(str))) { - ossl_raise(eSPKIError, NULL); + RSTRING_LENINT(str))) { + ossl_raise(eSPKIError, NULL); } return str; @@ -315,12 +315,12 @@ ossl_spki_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (NETSCAPE_SPKI_verify(spki, pkey)) { case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; case 1: - return Qtrue; + return Qtrue; default: - ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); + ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); } } diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index b19b51a4ffbb85..97ab24c347b556 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -84,7 +84,7 @@ ossl_ocsp_request_free(void *ptr) static const rb_data_type_t ossl_ocsp_request_type = { "OpenSSL/OCSP/REQUEST", { - 0, ossl_ocsp_request_free, + 0, ossl_ocsp_request_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -98,7 +98,7 @@ ossl_ocsp_response_free(void *ptr) static const rb_data_type_t ossl_ocsp_response_type = { "OpenSSL/OCSP/RESPONSE", { - 0, ossl_ocsp_response_free, + 0, ossl_ocsp_response_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -112,7 +112,7 @@ ossl_ocsp_basicresp_free(void *ptr) static const rb_data_type_t ossl_ocsp_basicresp_type = { "OpenSSL/OCSP/BASICRESP", { - 0, ossl_ocsp_basicresp_free, + 0, ossl_ocsp_basicresp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -126,7 +126,7 @@ ossl_ocsp_singleresp_free(void *ptr) static const rb_data_type_t ossl_ocsp_singleresp_type = { "OpenSSL/OCSP/SINGLERESP", { - 0, ossl_ocsp_singleresp_free, + 0, ossl_ocsp_singleresp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -140,7 +140,7 @@ ossl_ocsp_certid_free(void *ptr) static const rb_data_type_t ossl_ocsp_certid_type = { "OpenSSL/OCSP/CERTID", { - 0, ossl_ocsp_certid_free, + 0, ossl_ocsp_certid_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -171,7 +171,7 @@ ossl_ocspreq_alloc(VALUE klass) obj = NewOCSPReq(klass); if (!(req = OCSP_REQUEST_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPReq(obj, req); return obj; @@ -189,7 +189,7 @@ ossl_ocspreq_initialize_copy(VALUE self, VALUE other) req_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_REQUEST), req); if (!req_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPReq(self, req_new); OCSP_REQUEST_free(req_old); @@ -215,15 +215,15 @@ ossl_ocspreq_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if(!NIL_P(arg)){ - GetOCSPReq(self, req); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg)); - if (!req_new) - ossl_raise(eOCSPError, "d2i_OCSP_REQUEST"); - SetOCSPReq(self, req_new); - OCSP_REQUEST_free(req); + GetOCSPReq(self, req); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg)); + if (!req_new) + ossl_raise(eOCSPError, "d2i_OCSP_REQUEST"); + SetOCSPReq(self, req_new); + OCSP_REQUEST_free(req); } return self; @@ -249,13 +249,13 @@ ossl_ocspreq_add_nonce(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &val); if(NIL_P(val)) { - GetOCSPReq(self, req); - ret = OCSP_request_add1_nonce(req, NULL, -1); + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, NULL, -1); } else{ - StringValue(val); - GetOCSPReq(self, req); - ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + StringValue(val); + GetOCSPReq(self, req); + ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); } if(!ret) ossl_raise(eOCSPError, NULL); @@ -312,10 +312,10 @@ ossl_ocspreq_add_certid(VALUE self, VALUE certid) GetOCSPCertId(certid, id); if (!(id_new = OCSP_CERTID_dup(id))) - ossl_raise(eOCSPError, "OCSP_CERTID_dup"); + ossl_raise(eOCSPError, "OCSP_CERTID_dup"); if (!OCSP_request_add0_id(req, id_new)) { - OCSP_CERTID_free(id_new); - ossl_raise(eOCSPError, "OCSP_request_add0_id"); + OCSP_CERTID_free(id_new); + ossl_raise(eOCSPError, "OCSP_request_add0_id"); } return self; @@ -383,12 +383,12 @@ ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self) signer = GetX509CertPtr(signer_cert); key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) - flg = NUM2INT(flags); + flg = NUM2INT(flags); md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) - flg |= OCSP_NOCERTS; + flg |= OCSP_NOCERTS; else - x509s = ossl_x509_ary2sk(certs); + x509s = ossl_x509_ary2sk(certs); ret = OCSP_request_sign(req, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); @@ -427,7 +427,7 @@ ossl_ocspreq_verify(int argc, VALUE *argv, VALUE self) result = OCSP_request_verify(req, x509s, x509st, flg); sk_X509_pop_free(x509s, X509_free); if (result <= 0) - ossl_clear_error(); + ossl_clear_error(); return result > 0 ? Qtrue : Qfalse; } @@ -446,11 +446,11 @@ ossl_ocspreq_to_der(VALUE self) GetOCSPReq(self, req); if((len = i2d_OCSP_REQUEST(req, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_OCSP_REQUEST(req, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -494,7 +494,7 @@ ossl_ocspres_s_create(VALUE klass, VALUE status, VALUE basic_resp) else GetOCSPBasicRes(basic_resp, bs); /* NO NEED TO DUP */ obj = NewOCSPRes(klass); if(!(res = OCSP_response_create(st, bs))) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPRes(obj, res); return obj; @@ -508,7 +508,7 @@ ossl_ocspres_alloc(VALUE klass) obj = NewOCSPRes(klass); if(!(res = OCSP_RESPONSE_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPRes(obj, res); return obj; @@ -526,7 +526,7 @@ ossl_ocspres_initialize_copy(VALUE self, VALUE other) res_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_RESPONSE), res); if (!res_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPRes(self, res_new); OCSP_RESPONSE_free(res_old); @@ -552,15 +552,15 @@ ossl_ocspres_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if(!NIL_P(arg)){ - GetOCSPRes(self, res); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg)); - if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE"); - SetOCSPRes(self, res_new); - OCSP_RESPONSE_free(res); + GetOCSPRes(self, res); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg)); + if (!res_new) + ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE"); + SetOCSPRes(self, res_new); + OCSP_RESPONSE_free(res); } return self; @@ -621,7 +621,7 @@ ossl_ocspres_get_basic(VALUE self) GetOCSPRes(self, res); ret = NewOCSPBasicRes(cOCSPBasicRes); if(!(bs = OCSP_response_get1_basic(res))) - return Qnil; + return Qnil; SetOCSPBasicRes(ret, bs); return ret; @@ -644,11 +644,11 @@ ossl_ocspres_to_der(VALUE self) GetOCSPRes(self, res); if((len = i2d_OCSP_RESPONSE(res, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_OCSP_RESPONSE(res, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -665,7 +665,7 @@ ossl_ocspbres_alloc(VALUE klass) obj = NewOCSPBasicRes(klass); if(!(bs = OCSP_BASICRESP_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPBasicRes(obj, bs); return obj; @@ -683,7 +683,7 @@ ossl_ocspbres_initialize_copy(VALUE self, VALUE other) bs_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs); if (!bs_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPBasicRes(self, bs_new); OCSP_BASICRESP_free(bs_old); @@ -708,15 +708,15 @@ ossl_ocspbres_initialize(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &arg); if (!NIL_P(arg)) { - GetOCSPBasicRes(self, res); - arg = ossl_to_der_if_possible(arg); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg)); - if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP"); - SetOCSPBasicRes(self, res_new); - OCSP_BASICRESP_free(res); + GetOCSPBasicRes(self, res); + arg = ossl_to_der_if_possible(arg); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg)); + if (!res_new) + ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP"); + SetOCSPBasicRes(self, res_new); + OCSP_BASICRESP_free(res); } return self; @@ -761,13 +761,13 @@ ossl_ocspbres_add_nonce(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "01", &val); if(NIL_P(val)) { - GetOCSPBasicRes(self, bs); - ret = OCSP_basic_add1_nonce(bs, NULL, -1); + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, NULL, -1); } else{ - StringValue(val); - GetOCSPBasicRes(self, bs); - ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); + StringValue(val); + GetOCSPBasicRes(self, bs); + ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val)); } if(!ret) ossl_raise(eOCSPError, NULL); @@ -780,12 +780,12 @@ add_status_convert_time(VALUE obj) ASN1_TIME *time; if (RB_INTEGER_TYPE_P(obj)) - time = X509_gmtime_adj(NULL, NUM2INT(obj)); + time = X509_gmtime_adj(NULL, NUM2INT(obj)); else - time = ossl_x509_time_adjust(NULL, obj); + time = ossl_x509_time_adjust(NULL, obj); if (!time) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); return (VALUE)time; } @@ -819,8 +819,8 @@ add_status_convert_time(VALUE obj) */ static VALUE ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, - VALUE reason, VALUE revtime, - VALUE thisupd, VALUE nextupd, VALUE ext) + VALUE reason, VALUE revtime, + VALUE thisupd, VALUE nextupd, VALUE ext) { OCSP_BASICRESP *bs; OCSP_SINGLERESP *single; @@ -834,16 +834,16 @@ ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, GetOCSPCertId(cid, id); st = NUM2INT(status); if (!NIL_P(ext)) { /* All ext's members must be X509::Extension */ - ext = rb_check_array_type(ext); - for (i = 0; i < RARRAY_LEN(ext); i++) - OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext); + ext = rb_check_array_type(ext); + for (i = 0; i < RARRAY_LEN(ext); i++) + OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext); } if (st == V_OCSP_CERTSTATUS_REVOKED) { - rsn = NUM2INT(reason); - tmp = rb_protect(add_status_convert_time, revtime, &rstatus); - if (rstatus) goto err; - rev = (ASN1_TIME *)tmp; + rsn = NUM2INT(reason); + tmp = rb_protect(add_status_convert_time, revtime, &rstatus); + if (rstatus) goto err; + rev = (ASN1_TIME *)tmp; } tmp = rb_protect(add_status_convert_time, thisupd, &rstatus); @@ -851,29 +851,29 @@ ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status, ths = (ASN1_TIME *)tmp; if (!NIL_P(nextupd)) { - tmp = rb_protect(add_status_convert_time, nextupd, &rstatus); - if (rstatus) goto err; - nxt = (ASN1_TIME *)tmp; + tmp = rb_protect(add_status_convert_time, nextupd, &rstatus); + if (rstatus) goto err; + nxt = (ASN1_TIME *)tmp; } if(!(single = OCSP_basic_add1_status(bs, id, st, rsn, rev, ths, nxt))){ - error = 1; - goto err; + error = 1; + goto err; } if(!NIL_P(ext)){ - X509_EXTENSION *x509ext; - - for(i = 0; i < RARRAY_LEN(ext); i++){ - x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i)); - if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){ - error = 1; - goto err; - } - } + X509_EXTENSION *x509ext; + + for(i = 0; i < RARRAY_LEN(ext); i++){ + x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i)); + if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){ + error = 1; + goto err; + } + } } - err: + err: ASN1_TIME_free(ths); ASN1_TIME_free(nxt); ASN1_TIME_free(rev); @@ -978,7 +978,7 @@ ossl_ocspbres_find_response(VALUE self, VALUE target) GetOCSPBasicRes(self, bs); if ((n = OCSP_resp_find(bs, id, -1)) == -1) - return Qnil; + return Qnil; return ossl_ocspsres_new(OCSP_resp_get0(bs, n)); } @@ -1012,12 +1012,12 @@ ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self) signer = GetX509CertPtr(signer_cert); key = GetPrivPKeyPtr(signer_key); if (!NIL_P(flags)) - flg = NUM2INT(flags); + flg = NUM2INT(flags); md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (NIL_P(certs)) - flg |= OCSP_NOCERTS; + flg |= OCSP_NOCERTS; else - x509s = ossl_x509_ary2sk(certs); + x509s = ossl_x509_ary2sk(certs); ret = OCSP_basic_sign(bs, signer, key, md, x509s, flg); sk_X509_pop_free(x509s, X509_free); @@ -1051,7 +1051,7 @@ ossl_ocspbres_verify(int argc, VALUE *argv, VALUE self) result = OCSP_basic_verify(bs, x509s, x509st, flg); sk_X509_pop_free(x509s, X509_free); if (result <= 0) - ossl_clear_error(); + ossl_clear_error(); return result > 0 ? Qtrue : Qfalse; } @@ -1072,11 +1072,11 @@ ossl_ocspbres_to_der(VALUE self) GetOCSPBasicRes(self, res); if ((len = i2d_OCSP_BASICRESP(res, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_BASICRESP(res, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -1110,7 +1110,7 @@ ossl_ocspsres_alloc(VALUE klass) obj = NewOCSPSingleRes(klass); if (!(sres = OCSP_SINGLERESP_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPSingleRes(obj, sres); return obj; @@ -1135,7 +1135,7 @@ ossl_ocspsres_initialize(VALUE self, VALUE arg) p = (unsigned char*)RSTRING_PTR(arg); res_new = d2i_OCSP_SINGLERESP(NULL, &p, RSTRING_LEN(arg)); if (!res_new) - ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP"); + ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP"); SetOCSPSingleRes(self, res_new); OCSP_SINGLERESP_free(res); @@ -1154,7 +1154,7 @@ ossl_ocspsres_initialize_copy(VALUE self, VALUE other) sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres); if (!sres_new) - ossl_raise(eOCSPError, "ASN1_item_dup"); + ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPSingleRes(self, sres_new); OCSP_SINGLERESP_free(sres_old); @@ -1193,15 +1193,15 @@ ossl_ocspsres_check_validity(int argc, VALUE *argv, VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, &this_update, &next_update); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); ret = OCSP_check_validity(this_update, next_update, nsec, maxsec); if (ret) - return Qtrue; + return Qtrue; else { - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; } } @@ -1243,7 +1243,7 @@ ossl_ocspsres_get_cert_status(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); return INT2NUM(status); } @@ -1262,9 +1262,9 @@ ossl_ocspsres_get_this_update(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, &time, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1283,9 +1283,9 @@ ossl_ocspsres_get_next_update(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, NULL, NULL, &time); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1304,11 +1304,11 @@ ossl_ocspsres_get_revocation_time(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, NULL, &time, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (status != V_OCSP_CERTSTATUS_REVOKED) - ossl_raise(eOCSPError, "certificate is not revoked"); + ossl_raise(eOCSPError, "certificate is not revoked"); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -1326,9 +1326,9 @@ ossl_ocspsres_get_revocation_reason(VALUE self) GetOCSPSingleRes(self, sres); status = OCSP_single_get0_status(sres, &reason, NULL, NULL, NULL); if (status < 0) - ossl_raise(eOCSPError, "OCSP_single_get0_status"); + ossl_raise(eOCSPError, "OCSP_single_get0_status"); if (status != V_OCSP_CERTSTATUS_REVOKED) - ossl_raise(eOCSPError, "certificate is not revoked"); + ossl_raise(eOCSPError, "certificate is not revoked"); return INT2NUM(reason); } @@ -1350,8 +1350,8 @@ ossl_ocspsres_get_extensions(VALUE self) count = OCSP_SINGLERESP_get_ext_count(sres); ary = rb_ary_new2(count); for (i = 0; i < count; i++) { - ext = OCSP_SINGLERESP_get_ext(sres, i); - rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */ + ext = OCSP_SINGLERESP_get_ext(sres, i); + rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */ } return ary; @@ -1373,11 +1373,11 @@ ossl_ocspsres_to_der(VALUE self) GetOCSPSingleRes(self, sres); if ((len = i2d_OCSP_SINGLERESP(sres, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_SINGLERESP(sres, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; @@ -1395,7 +1395,7 @@ ossl_ocspcid_alloc(VALUE klass) obj = NewOCSPCertId(klass); if(!(id = OCSP_CERTID_new())) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); SetOCSPCertId(obj, id); return obj; @@ -1413,7 +1413,7 @@ ossl_ocspcid_initialize_copy(VALUE self, VALUE other) cid_new = OCSP_CERTID_dup(cid); if (!cid_new) - ossl_raise(eOCSPError, "OCSP_CERTID_dup"); + ossl_raise(eOCSPError, "OCSP_CERTID_dup"); SetOCSPCertId(self, cid_new); OCSP_CERTID_free(cid_old); @@ -1443,28 +1443,28 @@ ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self) GetOCSPCertId(self, id); if (rb_scan_args(argc, argv, "12", &subject, &issuer, &digest) == 1) { - VALUE arg; - const unsigned char *p; - - arg = ossl_to_der_if_possible(subject); - StringValue(arg); - p = (unsigned char *)RSTRING_PTR(arg); - newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg)); - if (!newid) - ossl_raise(eOCSPError, "d2i_OCSP_CERTID"); + VALUE arg; + const unsigned char *p; + + arg = ossl_to_der_if_possible(subject); + StringValue(arg); + p = (unsigned char *)RSTRING_PTR(arg); + newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg)); + if (!newid) + ossl_raise(eOCSPError, "d2i_OCSP_CERTID"); } else { - X509 *x509s, *x509i; - const EVP_MD *md; + X509 *x509s, *x509i; + const EVP_MD *md; VALUE md_holder; - x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ - x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ + x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ + x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); - newid = OCSP_cert_to_id(md, x509s, x509i); - if (!newid) - ossl_raise(eOCSPError, "OCSP_cert_to_id"); + newid = OCSP_cert_to_id(md, x509s, x509i); + if (!newid) + ossl_raise(eOCSPError, "OCSP_cert_to_id"); } SetOCSPCertId(self, newid); @@ -1613,11 +1613,11 @@ ossl_ocspcid_to_der(VALUE self) GetOCSPCertId(self, id); if ((len = i2d_OCSP_CERTID(id, NULL)) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_OCSP_CERTID(id, &p) <= 0) - ossl_raise(eOCSPError, NULL); + ossl_raise(eOCSPError, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index 8aff77a6dad934..32b82a881c361f 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -42,7 +42,7 @@ ossl_pkcs12_free(void *ptr) static const rb_data_type_t ossl_pkcs12_type = { "OpenSSL/PKCS12", { - 0, ossl_pkcs12_free, + 0, ossl_pkcs12_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -72,7 +72,7 @@ ossl_pkcs12_initialize_copy(VALUE self, VALUE other) p12_new = ASN1_dup((i2d_of_void *)i2d_PKCS12, (d2i_of_void *)d2i_PKCS12, (char *)p12); if (!p12_new) - ossl_raise(ePKCS12Error, "ASN1_dup"); + ossl_raise(ePKCS12Error, "ASN1_dup"); SetPKCS12(self, p12_new); PKCS12_free(p12_old); @@ -122,11 +122,11 @@ ossl_pkcs12_s_create(int argc, VALUE *argv, VALUE self) /* TODO: make a VALUE to nid function */ if (!NIL_P(key_nid)) { if ((nkey = OBJ_txt2nid(StringValueCStr(key_nid))) == NID_undef) - ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, key_nid); + ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, key_nid); } if (!NIL_P(cert_nid)) { if ((ncert = OBJ_txt2nid(StringValueCStr(cert_nid))) == NID_undef) - ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, cert_nid); + ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, cert_nid); } if (!NIL_P(key_iter)) kiter = NUM2INT(key_iter); @@ -209,18 +209,18 @@ ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) pkey = cert = ca = Qnil; if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s)) - ossl_raise(ePKCS12Error, "PKCS12_parse"); + ossl_raise(ePKCS12Error, "PKCS12_parse"); if (key) { - pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); - if (st) goto err; + pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); + if (st) goto err; } if (x509) { - cert = rb_protect(ossl_x509_new_i, (VALUE)x509, &st); - if (st) goto err; + cert = rb_protect(ossl_x509_new_i, (VALUE)x509, &st); + if (st) goto err; } if (x509s) { - ca = rb_protect(ossl_x509_sk2ary_i, (VALUE)x509s, &st); - if (st) goto err; + ca = rb_protect(ossl_x509_sk2ary_i, (VALUE)x509s, &st); + if (st) goto err; } err: @@ -244,11 +244,11 @@ ossl_pkcs12_to_der(VALUE self) GetPKCS12(self, p12); if((len = i2d_PKCS12(p12, NULL)) <= 0) - ossl_raise(ePKCS12Error, NULL); + ossl_raise(ePKCS12Error, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_PKCS12(p12, &p) <= 0) - ossl_raise(ePKCS12Error, NULL); + ossl_raise(ePKCS12Error, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 7bd6c18f92acbb..88f06c8831c188 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -28,14 +28,14 @@ TypedData_Wrap_Struct((klass), &ossl_pkcs7_signer_info_type, 0) #define SetPKCS7si(obj, p7si) do { \ if (!(p7si)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (p7si); \ } while (0) #define GetPKCS7si(obj, p7si) do { \ TypedData_Get_Struct((obj), PKCS7_SIGNER_INFO, &ossl_pkcs7_signer_info_type, (p7si)); \ if (!(p7si)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \ } \ } while (0) @@ -43,14 +43,14 @@ TypedData_Wrap_Struct((klass), &ossl_pkcs7_recip_info_type, 0) #define SetPKCS7ri(obj, p7ri) do { \ if (!(p7ri)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (p7ri); \ } while (0) #define GetPKCS7ri(obj, p7ri) do { \ TypedData_Get_Struct((obj), PKCS7_RECIP_INFO, &ossl_pkcs7_recip_info_type, (p7ri)); \ if (!(p7ri)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \ } \ } while (0) @@ -79,7 +79,7 @@ ossl_pkcs7_free(void *ptr) static const rb_data_type_t ossl_pkcs7_type = { "OpenSSL/PKCS7", { - 0, ossl_pkcs7_free, + 0, ossl_pkcs7_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -107,7 +107,7 @@ ossl_pkcs7_signer_info_free(void *ptr) static const rb_data_type_t ossl_pkcs7_signer_info_type = { "OpenSSL/PKCS7/SIGNER_INFO", { - 0, ossl_pkcs7_signer_info_free, + 0, ossl_pkcs7_signer_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -121,7 +121,7 @@ ossl_pkcs7_recip_info_free(void *ptr) static const rb_data_type_t ossl_pkcs7_recip_info_type = { "OpenSSL/PKCS7/RECIP_INFO", { - 0, ossl_pkcs7_recip_info_free, + 0, ossl_pkcs7_recip_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -238,7 +238,7 @@ ossl_pkcs7_s_write_smime(int argc, VALUE *argv, VALUE klass) if(NIL_P(data)) data = ossl_pkcs7_get_data(pkcs7); GetPKCS7(pkcs7, p7); if(!NIL_P(data) && PKCS7_is_detached(p7)) - flg |= PKCS7_DETACHED; + flg |= PKCS7_DETACHED; in = NIL_P(data) ? NULL : ossl_obj2bio(&data); if(!(out = BIO_new(BIO_s_mem()))){ BIO_free(in); @@ -279,16 +279,16 @@ ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass) in = ossl_obj2bio(&data); if(NIL_P(certs)) x509s = NULL; else{ - x509s = ossl_protect_x509_ary2sk(certs, &status); - if(status){ - BIO_free(in); - rb_jump_tag(status); - } + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } } if(!(pkcs7 = PKCS7_sign(x509, pkey, x509s, in, flg))){ - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7(ret, pkcs7); ossl_pkcs7_set_data(ret, data); @@ -333,13 +333,13 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) in = ossl_obj2bio(&data); x509s = ossl_protect_x509_ary2sk(certs, &status); if(status){ - BIO_free(in); - rb_jump_tag(status); + BIO_free(in); + rb_jump_tag(status); } if (!(p7 = PKCS7_encrypt(x509s, in, ciph, flg))) { - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } BIO_free(in); SetPKCS7(ret, p7); @@ -358,7 +358,7 @@ ossl_pkcs7_alloc(VALUE klass) obj = NewPKCS7(klass); if (!(pkcs7 = PKCS7_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7(obj, pkcs7); @@ -380,7 +380,7 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) VALUE arg; if(rb_scan_args(argc, argv, "01", &arg) == 0) - return self; + return self; arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); p7 = d2i_PKCS7_bio(in, NULL); @@ -418,7 +418,7 @@ ossl_pkcs7_copy(VALUE self, VALUE other) pkcs7 = PKCS7_dup(b); if (!pkcs7) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } DATA_PTR(self) = pkcs7; PKCS7_free(a); @@ -450,13 +450,13 @@ ossl_pkcs7_sym2typeid(VALUE sym) RSTRING_GETMEM(sym, s, l); for(i = 0; ; i++){ - if(i == numberof(p7_type_tab)) - ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym); - if(strlen(p7_type_tab[i].name) != l) continue; - if(strcmp(p7_type_tab[i].name, s) == 0){ - ret = p7_type_tab[i].nid; - break; - } + if(i == numberof(p7_type_tab)) + ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym); + if(strlen(p7_type_tab[i].name) != l) continue; + if(strcmp(p7_type_tab[i].name, s) == 0){ + ret = p7_type_tab[i].nid; + break; + } } return ret; @@ -473,7 +473,7 @@ ossl_pkcs7_set_type(VALUE self, VALUE type) GetPKCS7(self, p7); if(!PKCS7_set_type(p7, ossl_pkcs7_sym2typeid(type))) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); return type; } @@ -489,15 +489,15 @@ ossl_pkcs7_get_type(VALUE self) GetPKCS7(self, p7); if(PKCS7_type_is_signed(p7)) - return ID2SYM(rb_intern("signed")); + return ID2SYM(rb_intern("signed")); if(PKCS7_type_is_encrypted(p7)) - return ID2SYM(rb_intern("encrypted")); + return ID2SYM(rb_intern("encrypted")); if(PKCS7_type_is_enveloped(p7)) - return ID2SYM(rb_intern("enveloped")); + return ID2SYM(rb_intern("enveloped")); if(PKCS7_type_is_signedAndEnveloped(p7)) - return ID2SYM(rb_intern("signedAndEnveloped")); + return ID2SYM(rb_intern("signedAndEnveloped")); if(PKCS7_type_is_data(p7)) - return ID2SYM(rb_intern("data")); + return ID2SYM(rb_intern("data")); return Qnil; } @@ -508,9 +508,9 @@ ossl_pkcs7_set_detached(VALUE self, VALUE flag) GetPKCS7(self, p7); if(flag != Qtrue && flag != Qfalse) - ossl_raise(ePKCS7Error, "must specify a boolean"); + ossl_raise(ePKCS7Error, "must specify a boolean"); if(!PKCS7_set_detached(p7, flag == Qtrue ? 1 : 0)) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); return flag; } @@ -584,8 +584,8 @@ ossl_pkcs7_get_signer(VALUE self) num = sk_PKCS7_SIGNER_INFO_num(sk); ary = rb_ary_new_capa(num); for (i=0; id.enveloped->recipientinfo; + sk = pkcs7->d.enveloped->recipientinfo; else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) - sk = pkcs7->d.signed_and_enveloped->recipientinfo; + sk = pkcs7->d.signed_and_enveloped->recipientinfo; else sk = NULL; if (!sk) return rb_ary_new(); num = sk_PKCS7_RECIP_INFO_num(sk); @@ -646,7 +646,7 @@ ossl_pkcs7_add_certificate(VALUE self, VALUE cert) GetPKCS7(self, pkcs7); x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ if (!PKCS7_add_certificate(pkcs7, x509)){ - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } return self; @@ -662,13 +662,13 @@ pkcs7_get_certs(VALUE self) GetPKCS7(self, pkcs7); i = OBJ_obj2nid(pkcs7->type); switch(i){ - case NID_pkcs7_signed: + case NID_pkcs7_signed: certs = pkcs7->d.sign->cert; break; - case NID_pkcs7_signedAndEnveloped: + case NID_pkcs7_signedAndEnveloped: certs = pkcs7->d.signed_and_enveloped->cert; break; - default: + default: certs = NULL; } @@ -685,13 +685,13 @@ pkcs7_get_crls(VALUE self) GetPKCS7(self, pkcs7); i = OBJ_obj2nid(pkcs7->type); switch(i){ - case NID_pkcs7_signed: + case NID_pkcs7_signed: crls = pkcs7->d.sign->crl; break; - case NID_pkcs7_signedAndEnveloped: + case NID_pkcs7_signedAndEnveloped: crls = pkcs7->d.signed_and_enveloped->crl; break; - default: + default: crls = NULL; } @@ -738,7 +738,7 @@ ossl_pkcs7_add_crl(VALUE self, VALUE crl) GetPKCS7(self, pkcs7); /* NO DUP needed! */ x509crl = GetX509CRLPtr(crl); if (!PKCS7_add_crl(pkcs7, x509crl)) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } return self; @@ -794,16 +794,16 @@ ossl_pkcs7_verify(int argc, VALUE *argv, VALUE self) in = NIL_P(indata) ? NULL : ossl_obj2bio(&indata); if(NIL_P(certs)) x509s = NULL; else{ - x509s = ossl_protect_x509_ary2sk(certs, &status); - if(status){ - BIO_free(in); - rb_jump_tag(status); - } + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } } if(!(out = BIO_new(BIO_s_mem()))){ - BIO_free(in); - sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); } ok = PKCS7_verify(p7, x509s, x509st, in, out, flg); BIO_free(in); @@ -837,10 +837,10 @@ ossl_pkcs7_decrypt(int argc, VALUE *argv, VALUE self) flg = NIL_P(flags) ? 0 : NUM2INT(flags); GetPKCS7(self, p7); if(!(out = BIO_new(BIO_s_mem()))) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); if(!PKCS7_decrypt(p7, key, x509, out, flg)){ - BIO_free(out); - ossl_raise(ePKCS7Error, NULL); + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); } str = ossl_membio2str(out); /* out will be free */ @@ -899,11 +899,11 @@ ossl_pkcs7_to_der(VALUE self) GetPKCS7(self, pkcs7); if((len = i2d_PKCS7(pkcs7, NULL)) <= 0) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_PKCS7(pkcs7, &p) <= 0) - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); ossl_str_adjust(str, p); return str; @@ -937,11 +937,11 @@ ossl_pkcs7_to_pem(VALUE self) GetPKCS7(self, pkcs7); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } if (!PEM_write_bio_PKCS7(out, pkcs7)) { - BIO_free(out); - ossl_raise(ePKCS7Error, NULL); + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); } str = ossl_membio2str(out); @@ -959,7 +959,7 @@ ossl_pkcs7si_alloc(VALUE klass) obj = NewPKCS7si(klass); if (!(p7si = PKCS7_SIGNER_INFO_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7si(obj, p7si); @@ -1015,10 +1015,10 @@ ossl_pkcs7si_get_signed_time(VALUE self) GetPKCS7si(self, p7si); if (!(asn1obj = PKCS7_get_signed_attribute(p7si, NID_pkcs9_signingTime))) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } if (asn1obj->type == V_ASN1_UTCTIME) { - return asn1time_to_time(asn1obj->value.utctime); + return asn1time_to_time(asn1obj->value.utctime); } /* * OR @@ -1040,7 +1040,7 @@ ossl_pkcs7ri_alloc(VALUE klass) obj = NewPKCS7ri(klass); if (!(p7ri = PKCS7_RECIP_INFO_new())) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } SetPKCS7ri(obj, p7ri); @@ -1056,7 +1056,7 @@ ossl_pkcs7ri_initialize(VALUE self, VALUE cert) x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ GetPKCS7ri(self, p7ri); if (!PKCS7_RECIP_INFO_set(p7ri, x509)) { - ossl_raise(ePKCS7Error, NULL); + ossl_raise(ePKCS7Error, NULL); } return self; diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 61ff472aadd1b5..d2fd5b29c32204 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -33,7 +33,7 @@ ossl_evp_pkey_free(void *ptr) const rb_data_type_t ossl_evp_pkey_type = { "OpenSSL/EVP_PKEY", { - 0, ossl_evp_pkey_free, + 0, ossl_evp_pkey_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -72,8 +72,8 @@ ossl_pkey_wrap(EVP_PKEY *pkey) obj = rb_protect(pkey_wrap0, (VALUE)pkey, &status); if (status) { - EVP_PKEY_free(pkey); - rb_jump_tag(status); + EVP_PKEY_free(pkey); + rb_jump_tag(status); } return obj; @@ -188,23 +188,23 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass) EVP_PKEY *pkey; if ((pkey = d2i_PrivateKey_bio(bio, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, ppass))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PUBKEY_bio(bio, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */ if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, ppass))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL))) - goto out; + goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_Parameters(bio, NULL))) - goto out; + goto out; out: return pkey; @@ -239,7 +239,7 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) pkey = ossl_pkey_read_generic(bio, ossl_pem_passwd_value(pass)); BIO_free(bio); if (!pkey) - ossl_raise(ePKeyError, "Could not parse PKey"); + ossl_raise(ePKeyError, "Could not parse PKey"); return ossl_pkey_wrap(pkey); } @@ -516,34 +516,34 @@ ossl_pkey_check_public_key(const EVP_PKEY *pkey) const BIGNUM *n, *e, *pubkey; if (EVP_PKEY_missing_parameters(pkey)) - ossl_raise(ePKeyError, "parameters missing"); + ossl_raise(ePKeyError, "parameters missing"); ptr = EVP_PKEY_get0(pkey); switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: - RSA_get0_key(ptr, &n, &e, NULL); - if (n && e) - return; - break; + RSA_get0_key(ptr, &n, &e, NULL); + if (n && e) + return; + break; case EVP_PKEY_DSA: - DSA_get0_key(ptr, &pubkey, NULL); - if (pubkey) - return; - break; + DSA_get0_key(ptr, &pubkey, NULL); + if (pubkey) + return; + break; case EVP_PKEY_DH: - DH_get0_key(ptr, &pubkey, NULL); - if (pubkey) - return; - break; + DH_get0_key(ptr, &pubkey, NULL); + if (pubkey) + return; + break; #if !defined(OPENSSL_NO_EC) case EVP_PKEY_EC: - if (EC_KEY_get0_public_key(ptr)) - return; - break; + if (EC_KEY_get0_public_key(ptr)) + return; + break; #endif default: - /* unsupported type; assuming ok */ - return; + /* unsupported type; assuming ok */ + return; } ossl_raise(ePKeyError, "public key missing"); #endif @@ -610,7 +610,7 @@ static VALUE ossl_pkey_initialize(VALUE self) { if (rb_obj_is_instance_of(self, cPKey)) { - ossl_raise(rb_eTypeError, "OpenSSL::PKey::PKey can't be instantiated directly"); + ossl_raise(rb_eTypeError, "OpenSSL::PKey::PKey can't be instantiated directly"); } return self; } @@ -822,25 +822,25 @@ ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) rb_scan_args(argc, argv, "02", &cipher, &pass); if (!NIL_P(cipher)) { enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); - pass = ossl_pem_passwd_value(pass); + pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PrivateKey_bio(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PrivateKey_bio"); - } + if (!i2d_PrivateKey_bio(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PrivateKey_bio"); + } } else { - if (!PEM_write_bio_PrivateKey_traditional(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, - (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PrivateKey_traditional"); - } + if (!PEM_write_bio_PrivateKey_traditional(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, + (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PrivateKey_traditional"); + } } return ossl_membio2str(bio); } @@ -856,30 +856,30 @@ do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (argc > 0) { - /* - * TODO: EncryptedPrivateKeyInfo actually has more options. - * Should they be exposed? - */ + /* + * TODO: EncryptedPrivateKeyInfo actually has more options. + * Should they be exposed? + */ enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); - pass = ossl_pem_passwd_value(pass); + pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); - } + if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); + } } else { - if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, - ossl_pem_passwd_cb, (void *)pass)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); - } + if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); + } } return ossl_membio2str(bio); } @@ -963,18 +963,18 @@ ossl_pkey_export_spki(VALUE self, int to_der) ossl_pkey_check_public_key(pkey); bio = BIO_new(BIO_s_mem()); if (!bio) - ossl_raise(ePKeyError, "BIO_new"); + ossl_raise(ePKeyError, "BIO_new"); if (to_der) { - if (!i2d_PUBKEY_bio(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); - } + if (!i2d_PUBKEY_bio(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); + } } else { - if (!PEM_write_bio_PUBKEY(bio, pkey)) { - BIO_free(bio); - ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); - } + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); + } } return ossl_membio2str(bio); } diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 24823e0f3e721a..023361b90f2306 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -22,7 +22,7 @@ extern const rb_data_type_t ossl_evp_pkey_type; #define GetPKey(obj, pkey) do {\ TypedData_Get_Struct((obj), EVP_PKEY, &ossl_evp_pkey_type, (pkey)); \ if (!(pkey)) { \ - rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\ + rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\ } \ } while (0) @@ -45,7 +45,7 @@ VALUE ossl_pkey_export_spki(VALUE self, int to_der); * #to_der. */ VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, - int to_der); + int to_der); void Init_ossl_pkey(void); @@ -74,120 +74,120 @@ extern VALUE cEC; VALUE ossl_ec_new(EVP_PKEY *); void Init_ossl_ec(void); -#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ -/* \ - * call-seq: \ - * _keytype##.##_name -> aBN \ - */ \ -static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ -{ \ - const _type *obj; \ - const BIGNUM *bn; \ - \ - Get##_type(self, obj); \ - _get; \ - if (bn == NULL) \ - return Qnil; \ - return ossl_bn_new(bn); \ +#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ +/* \ + * call-seq: \ + * _keytype##.##_name -> aBN \ + */ \ +static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ +{ \ + const _type *obj; \ + const BIGNUM *bn; \ + \ + Get##_type(self, obj); \ + _get; \ + if (bn == NULL) \ + return Qnil; \ + return ossl_bn_new(bn); \ } -#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, \ - _type##_get0_##_group(obj, NULL, NULL, &bn)) +#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ + _type##_get0_##_group(obj, &bn, NULL, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ + _type##_get0_##_group(obj, NULL, &bn, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3, \ + _type##_get0_##_group(obj, NULL, NULL, &bn)) -#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ - _type##_get0_##_group(obj, &bn, NULL)) \ - OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ - _type##_get0_##_group(obj, NULL, &bn)) +#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1, \ + _type##_get0_##_group(obj, &bn, NULL)) \ + OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2, \ + _type##_get0_##_group(obj, NULL, &bn)) #ifndef OSSL_HAVE_IMMUTABLE_PKEY -#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ -/* \ - * call-seq: \ - * _keytype##.set_##_group(a1, a2, a3) -> self \ - */ \ +#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ +/* \ + * call-seq: \ + * _keytype##.set_##_group(a1, a2, a3) -> self \ + */ \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \ -{ \ - _type *obj; \ - BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ - BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ - BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\ - \ - Get##_type(self, obj); \ - if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ - (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) || \ - (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - BN_clear_free(bn3); \ - ossl_raise(ePKeyError, "BN_dup"); \ - } \ - \ - if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - BN_clear_free(bn3); \ - ossl_raise(ePKeyError, #_type"_set0_"#_group); \ - } \ - return self; \ +{ \ + _type *obj; \ + BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ + BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ + BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\ + \ + Get##_type(self, obj); \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) || \ + (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + BN_clear_free(bn3); \ + ossl_raise(ePKeyError, "BN_dup"); \ + } \ + \ + if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + BN_clear_free(bn3); \ + ossl_raise(ePKeyError, #_type"_set0_"#_group); \ + } \ + return self; \ } -#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ -/* \ - * call-seq: \ - * _keytype##.set_##_group(a1, a2) -> self \ - */ \ +#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ +/* \ + * call-seq: \ + * _keytype##.set_##_group(a1, a2) -> self \ + */ \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ -{ \ - _type *obj; \ - BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ - BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ - \ - Get##_type(self, obj); \ - if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ - (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - ossl_raise(ePKeyError, "BN_dup"); \ - } \ - \ - if (!_type##_set0_##_group(obj, bn1, bn2)) { \ - BN_clear_free(bn1); \ - BN_clear_free(bn2); \ - ossl_raise(ePKeyError, #_type"_set0_"#_group); \ - } \ - return self; \ +{ \ + _type *obj; \ + BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\ + BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ + \ + Get##_type(self, obj); \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + ossl_raise(ePKeyError, "BN_dup"); \ + } \ + \ + if (!_type##_set0_##_group(obj, bn1, bn2)) { \ + BN_clear_free(bn1); \ + BN_clear_free(bn2); \ + ossl_raise(ePKeyError, #_type"_set0_"#_group); \ + } \ + return self; \ } #else -#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ +#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \ -{ \ - rb_raise(ePKeyError, \ +{ \ + rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } -#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ +#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ -{ \ - rb_raise(ePKeyError, \ +{ \ + rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } #endif -#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ - OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) +#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3) \ + OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) -#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ - OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) +#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2) \ + OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) -#define DEF_OSSL_PKEY_BN(class, keytype, name) \ - rb_define_method((class), #name, ossl_##keytype##_get_##name, 0) +#define DEF_OSSL_PKEY_BN(class, keytype, name) \ + rb_define_method((class), #name, ossl_##keytype##_get_##name, 0) #endif /* OSSL_PKEY_H */ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 60cd20a857fc73..3f2975c5a3fdc4 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -14,7 +14,7 @@ #define GetPKeyDH(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \ } \ } while (0) #define GetDH(obj, dh) do { \ @@ -151,19 +151,19 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) dh = DHparams_dup(dh_other); if (!dh) - ossl_raise(ePKeyError, "DHparams_dup"); + ossl_raise(ePKeyError, "DHparams_dup"); DH_get0_key(dh_other, &pub, &priv); if (pub) { - BIGNUM *pub2 = BN_dup(pub); - BIGNUM *priv2 = BN_dup(priv); + BIGNUM *pub2 = BN_dup(pub); + BIGNUM *priv2 = BN_dup(priv); if (!pub2 || (priv && !priv2)) { - BN_clear_free(pub2); - BN_clear_free(priv2); - ossl_raise(ePKeyError, "BN_dup"); - } - DH_set0_key(dh, pub2, priv2); + BN_clear_free(pub2); + BN_clear_free(priv2); + ossl_raise(ePKeyError, "BN_dup"); + } + DH_set0_key(dh, pub2, priv2); } pkey = EVP_PKEY_new(); @@ -249,11 +249,11 @@ ossl_dh_export(VALUE self) GetDH(self, dh); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(ePKeyError, NULL); + ossl_raise(ePKeyError, NULL); } if (!PEM_write_bio_DHparams(out, dh)) { - BIO_free(out); - ossl_raise(ePKeyError, NULL); + BIO_free(out); + ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); @@ -283,11 +283,11 @@ ossl_dh_to_der(VALUE self) GetDH(self, dh); if((len = i2d_DHparams(dh, NULL)) <= 0) - ossl_raise(ePKeyError, NULL); + ossl_raise(ePKeyError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_DHparams(dh, &p) < 0) - ossl_raise(ePKeyError, NULL); + ossl_raise(ePKeyError, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 8b141afab775b1..041646a0585d84 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -14,7 +14,7 @@ #define GetPKeyDSA(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DSA) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \ } \ } while (0) #define GetDSA(obj, dsa) do { \ @@ -163,7 +163,7 @@ ossl_dsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_DSAPrivateKey, (char *)dsa); if (!dsa_new) - ossl_raise(ePKeyError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa_new) != 1) { diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index bf77e0fa0556d3..bb19533edf4744 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -15,7 +15,7 @@ static const rb_data_type_t ossl_ec_point_type; #define GetPKeyEC(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \ } \ } while (0) #define GetEC(obj, key) do { \ @@ -29,13 +29,13 @@ static const rb_data_type_t ossl_ec_point_type; #define GetECGroup(obj, group) do { \ TypedData_Get_Struct(obj, EC_GROUP, &ossl_ec_group_type, group); \ if ((group) == NULL) \ - ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \ + ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \ } while (0) #define GetECPoint(obj, point) do { \ TypedData_Get_Struct(obj, EC_POINT, &ossl_ec_point_type, point); \ if ((point) == NULL) \ - ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \ + ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \ } while (0) #define GetECPointGroup(obj, group) do { \ VALUE _group = rb_attr_get(obj, id_i_group); \ @@ -66,27 +66,27 @@ ec_key_new_from_group(VALUE arg) EC_KEY *ec; if (rb_obj_is_kind_of(arg, cEC_GROUP)) { - EC_GROUP *group; + EC_GROUP *group; - GetECGroup(arg, group); - if (!(ec = EC_KEY_new())) - ossl_raise(ePKeyError, NULL); + GetECGroup(arg, group); + if (!(ec = EC_KEY_new())) + ossl_raise(ePKeyError, NULL); - if (!EC_KEY_set_group(ec, group)) { - EC_KEY_free(ec); - ossl_raise(ePKeyError, NULL); - } + if (!EC_KEY_set_group(ec, group)) { + EC_KEY_free(ec); + ossl_raise(ePKeyError, NULL); + } } else { - int nid = OBJ_sn2nid(StringValueCStr(arg)); + int nid = OBJ_sn2nid(StringValueCStr(arg)); - if (nid == NID_undef) - ossl_raise(ePKeyError, "invalid curve name"); + if (nid == NID_undef) + ossl_raise(ePKeyError, "invalid curve name"); - if (!(ec = EC_KEY_new_by_curve_name(nid))) - ossl_raise(ePKeyError, NULL); + if (!(ec = EC_KEY_new_by_curve_name(nid))) + ossl_raise(ePKeyError, NULL); - EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); + EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED); } return ec; @@ -118,7 +118,7 @@ ossl_ec_key_s_generate(VALUE klass, VALUE arg) RTYPEDDATA_DATA(obj) = pkey; if (!EC_KEY_generate_key(ec)) - ossl_raise(ePKeyError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return obj; } @@ -208,7 +208,7 @@ ossl_ec_key_initialize_copy(VALUE self, VALUE other) ec_new = EC_KEY_dup(ec); if (!ec_new) - ossl_raise(ePKeyError, "EC_KEY_dup"); + ossl_raise(ePKeyError, "EC_KEY_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec_new) != 1) { @@ -237,7 +237,7 @@ ossl_ec_key_get_group(VALUE self) GetEC(self, ec); group = EC_KEY_get0_group(ec); if (!group) - return Qnil; + return Qnil; return ec_group_new(group); } @@ -305,13 +305,13 @@ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) bn = GetBNPtr(private_key); switch (EC_KEY_set_private_key(ec, bn)) { - case 1: + case 1: break; - case 0: + case 0: if (bn == NULL) break; - /* fallthrough */ - default: + /* fallthrough */ + default: ossl_raise(ePKeyError, "EC_KEY_set_private_key"); } @@ -356,13 +356,13 @@ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) GetECPoint(public_key, point); switch (EC_KEY_set_public_key(ec, point)) { - case 1: + case 1: break; - case 0: + case 0: if (point == NULL) break; - /* fallthrough */ - default: + /* fallthrough */ + default: ossl_raise(ePKeyError, "EC_KEY_set_public_key"); } @@ -524,7 +524,7 @@ static VALUE ossl_ec_key_generate_key(VALUE self) GetEC(self, ec); if (EC_KEY_generate_key(ec) != 1) - ossl_raise(ePKeyError, "EC_KEY_generate_key"); + ossl_raise(ePKeyError, "EC_KEY_generate_key"); return self; #endif @@ -570,7 +570,7 @@ static VALUE ossl_ec_key_check_key(VALUE self) GetEC(self, ec); if (EC_KEY_check_key(ec) != 1) - ossl_raise(ePKeyError, "EC_KEY_check_key"); + ossl_raise(ePKeyError, "EC_KEY_check_key"); #endif return Qtrue; @@ -588,7 +588,7 @@ ossl_ec_group_free(void *ptr) static const rb_data_type_t ossl_ec_group_type = { "OpenSSL/ec_group", { - 0, ossl_ec_group_free, + 0, ossl_ec_group_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -608,7 +608,7 @@ ec_group_new(const EC_GROUP *group) obj = ossl_ec_group_alloc(cEC_GROUP); group_new = EC_GROUP_dup(group); if (!group_new) - ossl_raise(eEC_GROUP, "EC_GROUP_dup"); + ossl_raise(eEC_GROUP, "EC_GROUP_dup"); RTYPEDDATA_DATA(obj) = group_new; return obj; @@ -636,7 +636,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) ossl_raise(rb_eRuntimeError, "EC_GROUP is already initialized"); switch (rb_scan_args(argc, argv, "13", &arg1, &arg2, &arg3, &arg4)) { - case 1: + case 1: if (rb_obj_is_kind_of(arg1, cEC_GROUP)) { const EC_GROUP *arg1_group; @@ -648,7 +648,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) group = PEM_read_bio_ECPKParameters(in, NULL, NULL, NULL); if (!group) { - OSSL_BIO_reset(in); + OSSL_BIO_reset(in); group = d2i_ECPKParameters_bio(in, NULL); } @@ -658,7 +658,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) const char *name = StringValueCStr(arg1); int nid = OBJ_sn2nid(name); - ossl_clear_error(); /* ignore errors in d2i_ECPKParameters_bio() */ + ossl_clear_error(); /* ignore errors in d2i_ECPKParameters_bio() */ if (nid == NID_undef) ossl_raise(eEC_GROUP, "unknown curve name (%"PRIsVALUE")", arg1); #if !defined(OPENSSL_IS_AWSLC) @@ -675,7 +675,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) } break; - case 4: + case 4: if (SYMBOL_P(arg1)) { EC_GROUP *(*new_curve)(const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; const BIGNUM *p = GetBNPtr(arg2); @@ -697,11 +697,11 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) if ((group = new_curve(p, a, b, ossl_bn_ctx)) == NULL) ossl_raise(eEC_GROUP, "EC_GROUP_new_by_GF*"); } else { - ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m"); + ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m"); } break; - default: + default: ossl_raise(rb_eArgError, "wrong number of arguments"); } @@ -719,12 +719,12 @@ ossl_ec_group_initialize_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EC_GROUP, &ossl_ec_group_type, group_new); if (group_new) - ossl_raise(eEC_GROUP, "EC::Group already initialized"); + ossl_raise(eEC_GROUP, "EC::Group already initialized"); GetECGroup(other, group); group_new = EC_GROUP_dup(group); if (!group_new) - ossl_raise(eEC_GROUP, "EC_GROUP_dup"); + ossl_raise(eEC_GROUP, "EC_GROUP_dup"); RTYPEDDATA_DATA(self) = group_new; return self; @@ -746,9 +746,9 @@ static VALUE ossl_ec_group_eql(VALUE a, VALUE b) GetECGroup(b, group2); switch (EC_GROUP_cmp(group1, group2, ossl_bn_ctx)) { - case 0: return Qtrue; - case 1: return Qfalse; - default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); } } @@ -768,7 +768,7 @@ static VALUE ossl_ec_group_get_generator(VALUE self) GetECGroup(self, group); generator = EC_GROUP_get0_generator(group); if (!generator) - return Qnil; + return Qnil; return ec_point_new(generator, group); } @@ -981,11 +981,11 @@ static point_conversion_form_t parse_point_conversion_form_symbol(VALUE sym) { if (sym == sym_uncompressed) - return POINT_CONVERSION_UNCOMPRESSED; + return POINT_CONVERSION_UNCOMPRESSED; if (sym == sym_compressed) - return POINT_CONVERSION_COMPRESSED; + return POINT_CONVERSION_COMPRESSED; if (sym == sym_hybrid) - return POINT_CONVERSION_HYBRID; + return POINT_CONVERSION_HYBRID; ossl_raise(rb_eArgError, "unsupported point conversion form %+"PRIsVALUE " (expected :compressed, :uncompressed, or :hybrid)", sym); } @@ -1092,15 +1092,15 @@ static VALUE ossl_ec_group_to_string(VALUE self, int format) ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); switch(format) { - case EXPORT_PEM: + case EXPORT_PEM: i = PEM_write_bio_ECPKParameters(out, group); - break; - case EXPORT_DER: + break; + case EXPORT_DER: i = i2d_ECPKParameters_bio(out, group); - break; - default: + break; + default: BIO_free(out); - ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); + ossl_raise(rb_eRuntimeError, "unknown format (internal error)"); } if (i != 1) { @@ -1149,11 +1149,11 @@ static VALUE ossl_ec_group_to_text(VALUE self) GetECGroup(self, group); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); + ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())"); } if (!ECPKParameters_print(out, group, 0)) { - BIO_free(out); - ossl_raise(eEC_GROUP, NULL); + BIO_free(out); + ossl_raise(eEC_GROUP, NULL); } str = ossl_membio2str(out); @@ -1173,7 +1173,7 @@ ossl_ec_point_free(void *ptr) static const rb_data_type_t ossl_ec_point_type = { "OpenSSL/EC_POINT", { - 0, ossl_ec_point_free, + 0, ossl_ec_point_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -1193,7 +1193,7 @@ ec_point_new(const EC_POINT *point, const EC_GROUP *group) obj = ossl_ec_point_alloc(cEC_POINT); point_new = EC_POINT_dup(point, group); if (!point_new) - ossl_raise(eEC_POINT, "EC_POINT_dup"); + ossl_raise(eEC_POINT, "EC_POINT_dup"); RTYPEDDATA_DATA(obj) = point_new; rb_ivar_set(obj, id_i_group, ec_group_new(group)); @@ -1221,39 +1221,39 @@ static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point); if (point) - rb_raise(eEC_POINT, "EC_POINT already initialized"); + rb_raise(eEC_POINT, "EC_POINT already initialized"); rb_scan_args(argc, argv, "11", &group_v, &arg2); if (rb_obj_is_kind_of(group_v, cEC_POINT)) { - if (argc != 1) - rb_raise(rb_eArgError, "invalid second argument"); - return ossl_ec_point_initialize_copy(self, group_v); + if (argc != 1) + rb_raise(rb_eArgError, "invalid second argument"); + return ossl_ec_point_initialize_copy(self, group_v); } GetECGroup(group_v, group); if (argc == 1) { - point = EC_POINT_new(group); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_new"); + point = EC_POINT_new(group); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_new"); } else { - if (rb_obj_is_kind_of(arg2, cBN)) { - point = EC_POINT_bn2point(group, GetBNPtr(arg2), NULL, ossl_bn_ctx); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_bn2point"); - } - else { - StringValue(arg2); - point = EC_POINT_new(group); - if (!point) - ossl_raise(eEC_POINT, "EC_POINT_new"); - if (!EC_POINT_oct2point(group, point, - (unsigned char *)RSTRING_PTR(arg2), - RSTRING_LEN(arg2), ossl_bn_ctx)) { - EC_POINT_free(point); - ossl_raise(eEC_POINT, "EC_POINT_oct2point"); - } - } + if (rb_obj_is_kind_of(arg2, cBN)) { + point = EC_POINT_bn2point(group, GetBNPtr(arg2), NULL, ossl_bn_ctx); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_bn2point"); + } + else { + StringValue(arg2); + point = EC_POINT_new(group); + if (!point) + ossl_raise(eEC_POINT, "EC_POINT_new"); + if (!EC_POINT_oct2point(group, point, + (unsigned char *)RSTRING_PTR(arg2), + RSTRING_LEN(arg2), ossl_bn_ctx)) { + EC_POINT_free(point); + ossl_raise(eEC_POINT, "EC_POINT_oct2point"); + } + } } RTYPEDDATA_DATA(self) = point; @@ -1272,7 +1272,7 @@ ossl_ec_point_initialize_copy(VALUE self, VALUE other) TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point_new); if (point_new) - ossl_raise(eEC_POINT, "EC::Point already initialized"); + ossl_raise(eEC_POINT, "EC::Point already initialized"); GetECPoint(other, point); group_v = rb_obj_dup(rb_attr_get(other, id_i_group)); @@ -1280,7 +1280,7 @@ ossl_ec_point_initialize_copy(VALUE self, VALUE other) point_new = EC_POINT_dup(point, group); if (!point_new) - ossl_raise(eEC_POINT, "EC_POINT_dup"); + ossl_raise(eEC_POINT, "EC_POINT_dup"); RTYPEDDATA_DATA(self) = point_new; rb_ivar_set(self, id_i_group, group_v); @@ -1307,9 +1307,9 @@ static VALUE ossl_ec_point_eql(VALUE a, VALUE b) GetECGroup(group_v1, group); switch (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx)) { - case 0: return Qtrue; - case 1: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); } UNREACHABLE; @@ -1328,9 +1328,9 @@ static VALUE ossl_ec_point_is_at_infinity(VALUE self) GetECPointGroup(self, group); switch (EC_POINT_is_at_infinity(group, point)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_is_at_infinity"); + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_is_at_infinity"); } UNREACHABLE; @@ -1349,9 +1349,9 @@ static VALUE ossl_ec_point_is_on_curve(VALUE self) GetECPointGroup(self, group); switch (EC_POINT_is_on_curve(group, point, ossl_bn_ctx)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: ossl_raise(eEC_POINT, "EC_POINT_is_on_curve"); + case 1: return Qtrue; + case 0: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_is_on_curve"); } UNREACHABLE; @@ -1443,12 +1443,12 @@ ossl_ec_point_to_octet_string(VALUE self, VALUE conversion_form) len = EC_POINT_point2oct(group, point, form, NULL, 0, ossl_bn_ctx); if (!len) - ossl_raise(eEC_POINT, "EC_POINT_point2oct"); + ossl_raise(eEC_POINT, "EC_POINT_point2oct"); str = rb_str_new(NULL, (long)len); if (!EC_POINT_point2oct(group, point, form, - (unsigned char *)RSTRING_PTR(str), len, - ossl_bn_ctx)) - ossl_raise(eEC_POINT, "EC_POINT_point2oct"); + (unsigned char *)RSTRING_PTR(str), len, + ossl_bn_ctx)) + ossl_raise(eEC_POINT, "EC_POINT_point2oct"); return str; } diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 9cc0bfa37933f9..039b2c6a34668b 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -14,7 +14,7 @@ #define GetPKeyRSA(obj, pkey) do { \ GetPKey((obj), (pkey)); \ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { /* PARANOIA? */ \ - ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \ + ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \ } \ } while (0) #define GetRSA(obj, rsa) do { \ @@ -95,7 +95,7 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) rb_raise(rb_eArgError, "OpenSSL::PKey::RSA.new cannot be called " \ "without arguments; pkeys are immutable with OpenSSL 3.0"); #else - rsa = RSA_new(); + rsa = RSA_new(); if (!rsa) ossl_raise(ePKeyError, "RSA_new"); goto legacy; @@ -159,7 +159,7 @@ ossl_rsa_initialize_copy(VALUE self, VALUE other) (d2i_of_void *)d2i_RSAPrivateKey, (char *)rsa); if (!rsa_new) - ossl_raise(ePKeyError, "ASN1_dup"); + ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa_new) != 1) { @@ -358,17 +358,17 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) int salt_len; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt_length"); - kwargs_ids[1] = rb_intern_const("mgf1_hash"); + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "2:", &digest, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("max"))) - salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ + salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) - salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else - salt_len = NUM2INT(kwargs[0]); + salt_len = NUM2INT(kwargs[0]); mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); pkey = GetPrivPKeyPtr(self); @@ -379,25 +379,25 @@ ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) md_ctx = EVP_MD_CTX_new(); if (!md_ctx) - goto err; + goto err; if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) - goto err; + goto err; if (EVP_DigestSignUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) - goto err; + goto err; if (EVP_DigestSignFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), &buf_len) != 1) - goto err; + goto err; rb_str_set_len(signature, (long)buf_len); @@ -444,17 +444,17 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) int result, salt_len; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("salt_length"); - kwargs_ids[1] = rb_intern_const("mgf1_hash"); + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "3:", &digest, &signature, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("auto"))) - salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ + salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) - salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else - salt_len = NUM2INT(kwargs[0]); + salt_len = NUM2INT(kwargs[0]); mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); GetPKey(self, pkey); @@ -464,34 +464,34 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) md_ctx = EVP_MD_CTX_new(); if (!md_ctx) - goto err; + goto err; if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) - goto err; + goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) - goto err; + goto err; if (EVP_DigestVerifyUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) - goto err; + goto err; result = EVP_DigestVerifyFinal(md_ctx, - (unsigned char *)RSTRING_PTR(signature), - RSTRING_LEN(signature)); + (unsigned char *)RSTRING_PTR(signature), + RSTRING_LEN(signature)); EVP_MD_CTX_free(md_ctx); switch (result) { case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; case 1: - return Qtrue; + return Qtrue; default: ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); } diff --git a/ext/openssl/ossl_rand.c b/ext/openssl/ossl_rand.c index 97d01903e276ae..753f8b25f77fa5 100644 --- a/ext/openssl/ossl_rand.c +++ b/ext/openssl/ossl_rand.c @@ -68,7 +68,7 @@ static VALUE ossl_rand_load_file(VALUE self, VALUE filename) { if(!RAND_load_file(StringValueCStr(filename), -1)) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } @@ -85,14 +85,14 @@ static VALUE ossl_rand_write_file(VALUE self, VALUE filename) { if (RAND_write_file(StringValueCStr(filename)) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } /* * call-seq: - * random_bytes(length) -> string + * random_bytes(length) -> string * * Generates a String with _length_ number of cryptographically strong * pseudo-random bytes. @@ -112,9 +112,9 @@ ossl_rand_bytes(VALUE self, VALUE len) str = rb_str_new(0, n); ret = RAND_bytes((unsigned char *)RSTRING_PTR(str), n); if (ret == 0) { - ossl_raise(eRandomError, "RAND_bytes"); + ossl_raise(eRandomError, "RAND_bytes"); } else if (ret == -1) { - ossl_raise(eRandomError, "RAND_bytes is not supported"); + ossl_raise(eRandomError, "RAND_bytes is not supported"); } return str; @@ -131,7 +131,7 @@ static VALUE ossl_rand_egd(VALUE self, VALUE filename) { if (RAND_egd(StringValueCStr(filename)) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } @@ -151,7 +151,7 @@ ossl_rand_egd_bytes(VALUE self, VALUE filename, VALUE len) int n = NUM2INT(len); if (RAND_egd_bytes(StringValueCStr(filename), n) == -1) { - ossl_raise(eRandomError, NULL); + ossl_raise(eRandomError, NULL); } return Qtrue; } diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 454f3de4a4b9ed..630d46e43f256e 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -25,7 +25,7 @@ #endif #define GetSSLCTX(obj, ctx) do { \ - TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx)); \ + TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx)); \ } while (0) VALUE mSSL; @@ -40,13 +40,13 @@ static ID id_call, ID_callback_state, id_npn_protocols_encoded, id_each; static VALUE sym_exception, sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, - id_i_verify_depth, id_i_verify_callback, id_i_client_ca, - id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert, - id_i_client_cert_cb, id_i_timeout, - id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, - id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, - id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, - id_i_verify_hostname, id_i_keylog_cb, id_i_tmp_dh_callback; + id_i_verify_depth, id_i_verify_callback, id_i_client_ca, + id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert, + id_i_client_cert_cb, id_i_timeout, + id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, + id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, + id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, + id_i_verify_hostname, id_i_keylog_cb, id_i_tmp_dh_callback; static ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_ptr_idx; @@ -78,9 +78,9 @@ ossl_sslctx_s_alloc(VALUE klass) { SSL_CTX *ctx; long mode = 0 | - SSL_MODE_ENABLE_PARTIAL_WRITE | - SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | - SSL_MODE_RELEASE_BUFFERS; + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | + SSL_MODE_RELEASE_BUFFERS; VALUE obj; obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0); @@ -104,7 +104,7 @@ ossl_call_client_cert_cb(VALUE obj) ctx_obj = rb_attr_get(obj, id_i_context); cb = rb_attr_get(ctx_obj, id_i_client_cert_cb); if (NIL_P(cb)) - return Qnil; + return Qnil; ary = rb_funcallv(cb, id_call, 1, &obj); Check_Type(ary, T_ARRAY); @@ -122,7 +122,7 @@ ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); ret = rb_protect(ossl_call_client_cert_cb, obj, NULL); if (NIL_P(ret)) - return 0; + return 0; *x509 = DupX509CertPtr(RARRAY_AREF(ret, 0)); *pkey = DupPKeyPtr(RARRAY_AREF(ret, 1)); @@ -167,8 +167,8 @@ ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) struct tmp_dh_callback_args args = {rb_ssl, is_export, keylength}; VALUE ret = rb_protect(ossl_call_tmp_dh_callback, (VALUE)&args, &state); if (state) { - rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); - return NULL; + rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); + return NULL; } return (DH *)ret; } @@ -186,13 +186,13 @@ call_verify_certificate_identity(VALUE ctx_v) hostname = rb_attr_get(ssl_obj, id_i_hostname); if (!RTEST(hostname)) { - rb_warning("verify_hostname requires hostname to be set"); - return Qtrue; + rb_warning("verify_hostname requires hostname to be set"); + return Qtrue; } cert_obj = ossl_x509_new(X509_STORE_CTX_get_current_cert(ctx)); return rb_funcall(mSSL, rb_intern("verify_certificate_identity"), 2, - cert_obj, hostname); + cert_obj, hostname); } static int @@ -209,12 +209,12 @@ ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) verify_hostname = rb_attr_get(sslctx_obj, id_i_verify_hostname); if (preverify_ok && RTEST(verify_hostname) && !SSL_is_server(ssl) && - !X509_STORE_CTX_get_error_depth(ctx)) { - ret = rb_protect(call_verify_certificate_identity, (VALUE)ctx, &status); - if (status) { - rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); - return 0; - } + !X509_STORE_CTX_get_error_depth(ctx)) { + ret = rb_protect(call_verify_certificate_identity, (VALUE)ctx, &status); + if (status) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); + return 0; + } if (ret != Qtrue) { preverify_ok = 0; X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH); @@ -385,7 +385,7 @@ ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) * when SSL_CTX_free() is called. */ if (rb_during_gc()) - return; + return; OSSL_Debug("SSL SESSION remove callback entered"); @@ -448,8 +448,8 @@ ossl_call_servername_cb(VALUE arg) ossl_raise(eSSLError, "SSL_set_SSL_CTX"); rb_ivar_set(ssl_obj, id_i_context, ret_obj); } else if (!NIL_P(ret_obj)) { - ossl_raise(rb_eArgError, "servername_cb must return an " - "OpenSSL::SSL::SSLContext object or nil"); + ossl_raise(rb_eArgError, "servername_cb must return an " + "OpenSSL::SSL::SSLContext object or nil"); } return Qnil; @@ -489,7 +489,7 @@ ssl_npn_encode_protocol_i(RB_BLOCK_CALL_FUNC_ARGLIST(cur, encoded)) int len = RSTRING_LENINT(cur); char len_byte; if (len < 1 || len > 255) - ossl_raise(eSSLError, "Advertised protocol must have length 1..255"); + ossl_raise(eSSLError, "Advertised protocol must have length 1..255"); /* Encode the length byte */ len_byte = len; rb_str_buf_cat(encoded, &len_byte, 1); @@ -523,16 +523,16 @@ npn_select_cb_common_i(VALUE tmp) /* assume OpenSSL verifies this format */ /* The format is len_1|proto_1|...|len_n|proto_n */ while (in < in_end) { - l = *in++; - rb_ary_push(protocols, rb_str_new((const char *)in, l)); - in += l; + l = *in++; + rb_ary_push(protocols, rb_str_new((const char *)in, l)); + in += l; } selected = rb_funcallv(args->cb, id_call, 1, &protocols); StringValue(selected); len = RSTRING_LEN(selected); if (len < 1 || len >= 256) { - ossl_raise(eSSLError, "Selected protocol name must have length 1..255"); + ossl_raise(eSSLError, "Selected protocol name must have length 1..255"); } return selected; @@ -540,8 +540,8 @@ npn_select_cb_common_i(VALUE tmp) static int ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen) + unsigned char *outlen, const unsigned char *in, + unsigned int inlen) { VALUE selected; int status; @@ -553,10 +553,10 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, selected = rb_protect(npn_select_cb_common_i, (VALUE)&args, &status); if (status) { - VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); - rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); - return SSL_TLSEXT_ERR_ALERT_FATAL; + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status)); + return SSL_TLSEXT_ERR_ALERT_FATAL; } *out = (unsigned char *)RSTRING_PTR(selected); @@ -568,7 +568,7 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, #ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, - void *arg) + void *arg) { VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); @@ -580,7 +580,7 @@ ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, static int ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) + const unsigned char *in, unsigned int inlen, void *arg) { VALUE sslctx_obj, cb; @@ -588,13 +588,13 @@ ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, cb = rb_attr_get(sslctx_obj, id_i_npn_select_cb); return ssl_npn_select_cb_common(ssl, cb, (const unsigned char **)out, - outlen, in, inlen); + outlen, in, inlen); } #endif static int ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) + const unsigned char *in, unsigned int inlen, void *arg) { VALUE sslctx_obj, cb; @@ -611,7 +611,7 @@ ssl_info_cb(const SSL *ssl, int where, int val) int is_server = SSL_is_server((SSL *)ssl); if (is_server && where & SSL_CB_HANDSHAKE_START) { - ssl_renegotiation_cb(ssl); + ssl_renegotiation_cb(ssl); } } @@ -657,9 +657,9 @@ ossl_sslctx_set_options(VALUE self, VALUE options) SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx)); if (NIL_P(options)) { - SSL_CTX_set_options(ctx, SSL_OP_ALL); + SSL_CTX_set_options(ctx, SSL_OP_ALL); } else { - SSL_CTX_set_options(ctx, NUM2ULONG(options)); + SSL_CTX_set_options(ctx, NUM2ULONG(options)); } return self; @@ -701,14 +701,14 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_cert_store); if (!NIL_P(val)) { - X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */ - SSL_CTX_set_cert_store(ctx, store); - X509_STORE_up_ref(store); + X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */ + SSL_CTX_set_cert_store(ctx, store); + X509_STORE_up_ref(store); } val = rb_attr_get(self, id_i_extra_chain_cert); if(!NIL_P(val)){ - rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self); + rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self); } /* private key may be bundled in certificate file. */ @@ -732,22 +732,22 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_client_ca); if(!NIL_P(val)){ - if (RB_TYPE_P(val, T_ARRAY)) { - for(i = 0; i < RARRAY_LEN(val); i++){ - client_ca = GetX509CertPtr(RARRAY_AREF(val, i)); - if (!SSL_CTX_add_client_CA(ctx, client_ca)){ - /* Copies X509_NAME => FREE it. */ - ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); - } - } + if (RB_TYPE_P(val, T_ARRAY)) { + for(i = 0; i < RARRAY_LEN(val); i++){ + client_ca = GetX509CertPtr(RARRAY_AREF(val, i)); + if (!SSL_CTX_add_client_CA(ctx, client_ca)){ + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + } + } } - else{ - client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */ + else{ + client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */ if (!SSL_CTX_add_client_CA(ctx, client_ca)){ - /* Copies X509_NAME => FREE it. */ - ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); + /* Copies X509_NAME => FREE it. */ + ossl_raise(eSSLError, "SSL_CTX_add_client_CA"); } - } + } } val = rb_attr_get(self, id_i_ca_file); @@ -770,7 +770,7 @@ ossl_sslctx_setup(VALUE self) verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val); SSL_CTX_set_verify(ctx, verify_mode, ossl_ssl_verify_callback); if (RTEST(rb_attr_get(self, id_i_client_cert_cb))) - SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb); + SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb); val = rb_attr_get(self, id_i_timeout); if(!NIL_P(val)) SSL_CTX_set_timeout(ctx, NUM2LONG(val)); @@ -781,60 +781,60 @@ ossl_sslctx_setup(VALUE self) #ifdef OSSL_USE_NEXTPROTONEG val = rb_attr_get(self, id_i_npn_protocols); if (!NIL_P(val)) { - VALUE encoded = ssl_encode_npn_protocols(val); - rb_ivar_set(self, id_npn_protocols_encoded, encoded); - SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); - OSSL_Debug("SSL NPN advertise callback added"); + VALUE encoded = ssl_encode_npn_protocols(val); + rb_ivar_set(self, id_npn_protocols_encoded, encoded); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); + OSSL_Debug("SSL NPN advertise callback added"); } if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) { - SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); - OSSL_Debug("SSL NPN select callback added"); + SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); + OSSL_Debug("SSL NPN select callback added"); } #endif val = rb_attr_get(self, id_i_alpn_protocols); if (!NIL_P(val)) { - VALUE rprotos = ssl_encode_npn_protocols(val); + VALUE rprotos = ssl_encode_npn_protocols(val); - /* returns 0 on success */ - if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos), - RSTRING_LENINT(rprotos))) - ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos"); - OSSL_Debug("SSL ALPN values added"); + /* returns 0 on success */ + if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos), + RSTRING_LENINT(rprotos))) + ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos"); + OSSL_Debug("SSL ALPN values added"); } if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) { - SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); - OSSL_Debug("SSL ALPN select callback added"); + SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); + OSSL_Debug("SSL ALPN select callback added"); } rb_obj_freeze(self); val = rb_attr_get(self, id_i_session_id_context); if (!NIL_P(val)){ - StringValue(val); - if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val), - RSTRING_LENINT(val))){ - ossl_raise(eSSLError, "SSL_CTX_set_session_id_context"); - } + StringValue(val); + if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val), + RSTRING_LENINT(val))){ + ossl_raise(eSSLError, "SSL_CTX_set_session_id_context"); + } } if (RTEST(rb_attr_get(self, id_i_session_get_cb))) { - SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb); - OSSL_Debug("SSL SESSION get callback added"); + SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb); + OSSL_Debug("SSL SESSION get callback added"); } if (RTEST(rb_attr_get(self, id_i_session_new_cb))) { - SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb); - OSSL_Debug("SSL SESSION new callback added"); + SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb); + OSSL_Debug("SSL SESSION new callback added"); } if (RTEST(rb_attr_get(self, id_i_session_remove_cb))) { - SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); - OSSL_Debug("SSL SESSION remove callback added"); + SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); + OSSL_Debug("SSL SESSION remove callback added"); } val = rb_attr_get(self, id_i_servername_cb); if (!NIL_P(val)) { SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); - OSSL_Debug("SSL TLSEXT servername callback added"); + OSSL_Debug("SSL TLSEXT servername callback added"); } #if !OSSL_IS_LIBRESSL @@ -857,28 +857,28 @@ parse_proto_version(VALUE str) { int i; static const struct { - const char *name; - int version; + const char *name; + int version; } map[] = { - { "SSL2", SSL2_VERSION }, - { "SSL3", SSL3_VERSION }, - { "TLS1", TLS1_VERSION }, - { "TLS1_1", TLS1_1_VERSION }, - { "TLS1_2", TLS1_2_VERSION }, - { "TLS1_3", TLS1_3_VERSION }, + { "SSL2", SSL2_VERSION }, + { "SSL3", SSL3_VERSION }, + { "TLS1", TLS1_VERSION }, + { "TLS1_1", TLS1_1_VERSION }, + { "TLS1_2", TLS1_2_VERSION }, + { "TLS1_3", TLS1_3_VERSION }, }; if (NIL_P(str)) - return 0; + return 0; if (RB_INTEGER_TYPE_P(str)) - return NUM2INT(str); + return NUM2INT(str); if (SYMBOL_P(str)) - str = rb_sym2str(str); + str = rb_sym2str(str); StringValue(str); for (i = 0; i < numberof(map); i++) - if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) - return map[i].version; + if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) + return map[i].version; rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); } @@ -1344,20 +1344,20 @@ ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self) pub_pkey = X509_get_pubkey(x509); EVP_PKEY_free(pub_pkey); if (!pub_pkey) - rb_raise(rb_eArgError, "certificate does not contain public key"); + rb_raise(rb_eArgError, "certificate does not contain public key"); if (EVP_PKEY_eq(pub_pkey, pkey) != 1) - rb_raise(rb_eArgError, "public key mismatch"); + rb_raise(rb_eArgError, "public key mismatch"); if (argc >= 3) - extra_chain = ossl_x509_ary2sk(extra_chain_ary); + extra_chain = ossl_x509_ary2sk(extra_chain_ary); if (!SSL_CTX_use_certificate(ctx, x509)) { - sk_X509_pop_free(extra_chain, X509_free); - ossl_raise(eSSLError, "SSL_CTX_use_certificate"); + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_certificate"); } if (!SSL_CTX_use_PrivateKey(ctx, pkey)) { - sk_X509_pop_free(extra_chain, X509_free); - ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); } if (extra_chain && !SSL_CTX_set0_chain(ctx, extra_chain)) { sk_X509_pop_free(extra_chain, X509_free); @@ -1637,23 +1637,23 @@ ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, SSL, &ossl_ssl_type, ssl); if (ssl) - ossl_raise(eSSLError, "SSL already initialized"); + ossl_raise(eSSLError, "SSL already initialized"); if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1) - v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); + v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); GetSSLCTX(v_ctx, ctx); rb_ivar_set(self, id_i_context, v_ctx); ossl_sslctx_setup(v_ctx); if (rb_respond_to(io, rb_intern("nonblock="))) - rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); + rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); Check_Type(io, T_FILE); rb_ivar_set(self, id_i_io, io); ssl = SSL_new(ctx); if (!ssl) - ossl_raise(eSSLError, NULL); + ossl_raise(eSSLError, NULL); RTYPEDDATA_DATA(self) = ssl; SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self); @@ -1684,7 +1684,7 @@ ossl_ssl_setup(VALUE self) GetSSL(self, ssl); if (ssl_started(ssl)) - return Qtrue; + return Qtrue; io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); @@ -1706,14 +1706,14 @@ static void write_would_block(int nonblock) { if (nonblock) - ossl_raise(eSSLErrorWaitWritable, "write would block"); + ossl_raise(eSSLErrorWaitWritable, "write would block"); } static void read_would_block(int nonblock) { if (nonblock) - ossl_raise(eSSLErrorWaitReadable, "read would block"); + ossl_raise(eSSLErrorWaitReadable, "read would block"); } static int @@ -1721,7 +1721,7 @@ no_exception_p(VALUE opts) { if (RB_TYPE_P(opts, T_HASH) && rb_hash_lookup2(opts, sym_exception, Qundef) == Qfalse) - return 1; + return 1; return 0; } @@ -1945,9 +1945,9 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) VALUE opts = Qnil; if (nonblock) { - rb_scan_args(argc, argv, "11:", &len, &str, &opts); + rb_scan_args(argc, argv, "11:", &len, &str, &opts); } else { - rb_scan_args(argc, argv, "11", &len, &str); + rb_scan_args(argc, argv, "11", &len, &str); } GetSSL(self, ssl); if (!ssl_started(ssl)) @@ -1955,13 +1955,13 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) ilen = NUM2INT(len); if (NIL_P(str)) - str = rb_str_new(0, ilen); + str = rb_str_new(0, ilen); else { - StringValue(str); - if (RSTRING_LEN(str) >= ilen) - rb_str_modify(str); - else - rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); + StringValue(str); + if (RSTRING_LEN(str) >= ilen) + rb_str_modify(str); + else + rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); } if (ilen == 0) { @@ -2198,12 +2198,12 @@ ossl_ssl_stop(VALUE self) GetSSL(self, ssl); if (!ssl_started(ssl)) - return Qnil; + return Qnil; ret = SSL_shutdown(ssl); if (ret == 1) /* Have already received close_notify */ - return Qnil; + return Qnil; if (ret == 0) /* Sent close_notify, but we don't wait for reply */ - return Qnil; + return Qnil; /* * XXX: Something happened. Possibly it failed because the underlying socket @@ -2289,20 +2289,20 @@ ossl_ssl_get_peer_cert_chain(VALUE self) num = sk_X509_num(chain); ary = rb_ary_new2(num); for (i = 0; i < num; i++){ - cert = sk_X509_value(chain, i); - rb_ary_push(ary, ossl_x509_new(cert)); + cert = sk_X509_value(chain, i); + rb_ary_push(ary, ossl_x509_new(cert)); } return ary; } /* -* call-seq: -* ssl.ssl_version => String -* -* Returns a String representing the SSL/TLS version that was negotiated -* for the connection, for example "TLSv1.2". -*/ + * call-seq: + * ssl.ssl_version => String + * + * Returns a String representing the SSL/TLS version that was negotiated + * for the connection, for example "TLSv1.2". + */ static VALUE ossl_ssl_get_version(VALUE self) { @@ -2423,10 +2423,10 @@ ossl_ssl_set_hostname(VALUE self, VALUE arg) GetSSL(self, ssl); if (!NIL_P(arg)) - hostname = StringValueCStr(arg); + hostname = StringValueCStr(arg); if (!SSL_set_tlsext_host_name(ssl, hostname)) - ossl_raise(eSSLError, NULL); + ossl_raise(eSSLError, NULL); /* for SSLSocket#hostname */ rb_ivar_set(self, id_i_hostname, arg); @@ -2547,9 +2547,9 @@ ossl_ssl_npn_protocol(VALUE self) SSL_get0_next_proto_negotiated(ssl, &out, &outlen); if (!outlen) - return Qnil; + return Qnil; else - return rb_str_new((const char *) out, outlen); + return rb_str_new((const char *) out, outlen); } # endif @@ -2571,9 +2571,9 @@ ossl_ssl_alpn_protocol(VALUE self) SSL_get0_alpn_selected(ssl, &out, &outlen); if (!outlen) - return Qnil; + return Qnil; else - return rb_str_new((const char *) out, outlen); + return rb_str_new((const char *) out, outlen); } /* @@ -2606,15 +2606,15 @@ ossl_ssl_export_keying_material(int argc, VALUE *argv, VALUE self) str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (!NIL_P(context)) { - use_ctx = 1; - StringValue(context); - ctx = (unsigned char *)RSTRING_PTR(context); - ctx_len = RSTRING_LEN(context); + use_ctx = 1; + StringValue(context); + ctx = (unsigned char *)RSTRING_PTR(context); + ctx_len = RSTRING_LEN(context); } ret = SSL_export_keying_material(ssl, p, len, (char *)RSTRING_PTR(label), - RSTRING_LENINT(label), ctx, ctx_len, use_ctx); + RSTRING_LENINT(label), ctx, ctx_len, use_ctx); if (ret == 0 || ret == -1) { - ossl_raise(eSSLError, "SSL_export_keying_material"); + ossl_raise(eSSLError, "SSL_export_keying_material"); } return str; } @@ -2633,7 +2633,7 @@ ossl_ssl_tmp_key(VALUE self) GetSSL(self, ssl); if (!SSL_get_server_tmp_key(ssl, &key)) - return Qnil; + return Qnil; return ossl_pkey_wrap(key); } @@ -2714,10 +2714,10 @@ Init_ossl_ssl(void) ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0, (void *)"ossl_ssl_ex_ptr_idx", 0, 0, 0); if (ossl_ssl_ex_ptr_idx < 0) - ossl_raise(rb_eRuntimeError, "SSL_get_ex_new_index"); + ossl_raise(rb_eRuntimeError, "SSL_get_ex_new_index"); ossl_sslctx_ex_ptr_idx = SSL_CTX_get_ex_new_index(0, (void *)"ossl_sslctx_ex_ptr_idx", 0, 0, 0); if (ossl_sslctx_ex_ptr_idx < 0) - ossl_raise(rb_eRuntimeError, "SSL_CTX_get_ex_new_index"); + ossl_raise(rb_eRuntimeError, "SSL_CTX_get_ex_new_index"); /* Document-module: OpenSSL::SSL * diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h index a92985c601ebf3..a87e62d450d50d 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -11,17 +11,17 @@ #define _OSSL_SSL_H_ #define GetSSL(obj, ssl) do { \ - TypedData_Get_Struct((obj), SSL, &ossl_ssl_type, (ssl)); \ - if (!(ssl)) { \ - ossl_raise(rb_eRuntimeError, "SSL is not initialized"); \ - } \ + TypedData_Get_Struct((obj), SSL, &ossl_ssl_type, (ssl)); \ + if (!(ssl)) { \ + ossl_raise(rb_eRuntimeError, "SSL is not initialized"); \ + } \ } while (0) #define GetSSLSession(obj, sess) do { \ - TypedData_Get_Struct((obj), SSL_SESSION, &ossl_ssl_session_type, (sess)); \ - if (!(sess)) { \ - ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ - } \ + TypedData_Get_Struct((obj), SSL_SESSION, &ossl_ssl_session_type, (sess)); \ + if (!(sess)) { \ + ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \ + } \ } while (0) extern const rb_data_type_t ossl_ssl_type; diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c index 010adf20ac9618..8a2fbf4100ca8b 100644 --- a/ext/openssl/ossl_ssl_session.c +++ b/ext/openssl/ossl_ssl_session.c @@ -17,14 +17,14 @@ ossl_ssl_session_free(void *ptr) const rb_data_type_t ossl_ssl_session_type = { "OpenSSL/SSL/Session", { - 0, ossl_ssl_session_free, + 0, ossl_ssl_session_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_ssl_session_alloc(VALUE klass) { - return TypedData_Wrap_Struct(klass, &ossl_ssl_session_type, NULL); + return TypedData_Wrap_Struct(klass, &ossl_ssl_session_type, NULL); } /* @@ -80,9 +80,9 @@ ossl_ssl_session_initialize_copy(VALUE self, VALUE other) GetSSLSession(other, sess_other); sess_new = ASN1_dup((i2d_of_void *)i2d_SSL_SESSION, (d2i_of_void *)d2i_SSL_SESSION, - (char *)sess_other); + (char *)sess_other); if (!sess_new) - ossl_raise(eSSLSession, "ASN1_dup"); + ossl_raise(eSSLSession, "ASN1_dup"); RTYPEDDATA_DATA(self) = sess_new; SSL_SESSION_free(sess); @@ -99,9 +99,9 @@ ossl_SSL_SESSION_cmp(const SSL_SESSION *a, const SSL_SESSION *b) const unsigned char *b_sid = SSL_SESSION_get_id(b, &b_len); if (SSL_SESSION_get_protocol_version(a) != SSL_SESSION_get_protocol_version(b)) - return 1; + return 1; if (a_len != b_len) - return 1; + return 1; return CRYPTO_memcmp(a_sid, b_sid, a_len); } @@ -114,15 +114,15 @@ ossl_SSL_SESSION_cmp(const SSL_SESSION *a, const SSL_SESSION *b) */ static VALUE ossl_ssl_session_eq(VALUE val1, VALUE val2) { - SSL_SESSION *ctx1, *ctx2; + SSL_SESSION *ctx1, *ctx2; - GetSSLSession(val1, ctx1); - GetSSLSession(val2, ctx2); + GetSSLSession(val1, ctx1); + GetSSLSession(val2, ctx2); - switch (ossl_SSL_SESSION_cmp(ctx1, ctx2)) { - case 0: return Qtrue; - default: return Qfalse; - } + switch (ossl_SSL_SESSION_cmp(ctx1, ctx2)) { + case 0: return Qtrue; + default: return Qfalse; + } } /* @@ -140,7 +140,7 @@ ossl_ssl_session_get_time(VALUE self) GetSSLSession(self, ctx); t = SSL_SESSION_get_time(ctx); if (t == 0) - return Qnil; + return Qnil; return rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(t)); } @@ -175,16 +175,16 @@ ossl_ssl_session_get_timeout(VALUE self) */ static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v) { - SSL_SESSION *ctx; - long t; - - GetSSLSession(self, ctx); - if (rb_obj_is_instance_of(time_v, rb_cTime)) { - time_v = rb_funcall(time_v, rb_intern("to_i"), 0); - } - t = NUM2LONG(time_v); - SSL_SESSION_set_time(ctx, t); - return ossl_ssl_session_get_time(self); + SSL_SESSION *ctx; + long t; + + GetSSLSession(self, ctx); + if (rb_obj_is_instance_of(time_v, rb_cTime)) { + time_v = rb_funcall(time_v, rb_intern("to_i"), 0); + } + t = NUM2LONG(time_v); + SSL_SESSION_set_time(ctx, t); + return ossl_ssl_session_get_time(self); } /* @@ -195,13 +195,13 @@ static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v) */ static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v) { - SSL_SESSION *ctx; - long t; + SSL_SESSION *ctx; + long t; - GetSSLSession(self, ctx); - t = NUM2LONG(time_v); - SSL_SESSION_set_timeout(ctx, t); - return ossl_ssl_session_get_timeout(self); + GetSSLSession(self, ctx); + t = NUM2LONG(time_v); + SSL_SESSION_set_timeout(ctx, t); + return ossl_ssl_session_get_timeout(self); } /* @@ -209,18 +209,18 @@ static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v) * session.id -> String * * Returns the Session ID. -*/ + */ static VALUE ossl_ssl_session_get_id(VALUE self) { - SSL_SESSION *ctx; - const unsigned char *p = NULL; - unsigned int i = 0; + SSL_SESSION *ctx; + const unsigned char *p = NULL; + unsigned int i = 0; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - p = SSL_SESSION_get_id(ctx, &i); + p = SSL_SESSION_get_id(ctx, &i); - return rb_str_new((const char *) p, i); + return rb_str_new((const char *) p, i); } /* @@ -231,22 +231,22 @@ static VALUE ossl_ssl_session_get_id(VALUE self) */ static VALUE ossl_ssl_session_to_der(VALUE self) { - SSL_SESSION *ctx; - unsigned char *p; - int len; - VALUE str; - - GetSSLSession(self, ctx); - len = i2d_SSL_SESSION(ctx, NULL); - if (len <= 0) { - ossl_raise(eSSLSession, "i2d_SSL_SESSION"); - } - - str = rb_str_new(0, len); - p = (unsigned char *)RSTRING_PTR(str); - i2d_SSL_SESSION(ctx, &p); - ossl_str_adjust(str, p); - return str; + SSL_SESSION *ctx; + unsigned char *p; + int len; + VALUE str; + + GetSSLSession(self, ctx); + len = i2d_SSL_SESSION(ctx, NULL); + if (len <= 0) { + ossl_raise(eSSLSession, "i2d_SSL_SESSION"); + } + + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + i2d_SSL_SESSION(ctx, &p); + ossl_str_adjust(str, p); + return str; } /* @@ -257,22 +257,22 @@ static VALUE ossl_ssl_session_to_der(VALUE self) */ static VALUE ossl_ssl_session_to_pem(VALUE self) { - SSL_SESSION *ctx; - BIO *out; + SSL_SESSION *ctx; + BIO *out; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSSLSession, "BIO_s_mem()"); - } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } - if (!PEM_write_bio_SSL_SESSION(out, ctx)) { - BIO_free(out); - ossl_raise(eSSLSession, "SSL_SESSION_print()"); - } + if (!PEM_write_bio_SSL_SESSION(out, ctx)) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } - return ossl_membio2str(out); + return ossl_membio2str(out); } @@ -284,21 +284,21 @@ static VALUE ossl_ssl_session_to_pem(VALUE self) */ static VALUE ossl_ssl_session_to_text(VALUE self) { - SSL_SESSION *ctx; - BIO *out; + SSL_SESSION *ctx; + BIO *out; - GetSSLSession(self, ctx); + GetSSLSession(self, ctx); - if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eSSLSession, "BIO_s_mem()"); - } + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eSSLSession, "BIO_s_mem()"); + } - if (!SSL_SESSION_print(out, ctx)) { - BIO_free(out); - ossl_raise(eSSLSession, "SSL_SESSION_print()"); - } + if (!SSL_SESSION_print(out, ctx)) { + BIO_free(out); + ossl_raise(eSSLSession, "SSL_SESSION_print()"); + } - return ossl_membio2str(out); + return ossl_membio2str(out); } #endif /* !defined(OPENSSL_NO_SOCK) */ @@ -306,22 +306,22 @@ static VALUE ossl_ssl_session_to_text(VALUE self) void Init_ossl_ssl_session(void) { #ifndef OPENSSL_NO_SOCK - cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); - eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); - - rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc); - rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1); - rb_define_method(cSSLSession, "initialize_copy", ossl_ssl_session_initialize_copy, 1); - - rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1); - - rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0); - rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1); - rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0); - rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1); - rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0); - rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); - rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); - rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); + cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); + eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); + + rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc); + rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1); + rb_define_method(cSSLSession, "initialize_copy", ossl_ssl_session_initialize_copy, 1); + + rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1); + + rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0); + rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1); + rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0); + rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1); + rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0); + rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); + rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); + rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); #endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index e071ee84811c75..615eedc016a0f4 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -103,7 +103,7 @@ static const rb_data_type_t ossl_ts_resp_type = { static void ossl_ts_token_info_free(void *ptr) { - TS_TST_INFO_free(ptr); + TS_TST_INFO_free(ptr); } static const rb_data_type_t ossl_ts_token_info_type = { @@ -1226,7 +1226,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) if (rb_obj_is_kind_of(additional_certs, rb_cArray)) { inter_certs = ossl_protect_x509_ary2sk(additional_certs, &status); if (status) - goto end; + goto end; /* this dups the sk_X509 and ups each cert's ref count */ TS_RESP_CTX_set_certs(ctx, inter_certs); @@ -1281,7 +1281,7 @@ ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) SetTSResponse(tsresp, response); ret = tsresp; -end: + end: ASN1_INTEGER_free(asn1_serial); ASN1_OBJECT_free(def_policy_id_obj); TS_RESP_CTX_free(ctx); diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c index 04adfab960799a..e341ca1fbb4c1c 100644 --- a/ext/openssl/ossl_x509.c +++ b/ext/openssl/ossl_x509.c @@ -13,7 +13,7 @@ VALUE mX509; #define DefX509Const(x) rb_define_const(mX509, #x, INT2NUM(X509_##x)) #define DefX509Default(x,i) \ - rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i())) + rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i())) ASN1_TIME * ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index 998f87b0e8649b..4769e56e1e8501 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509attr_type, 0) #define SetX509Attr(obj, attr) do { \ if (!(attr)) { \ - ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (attr); \ } while (0) #define GetX509Attr(obj, attr) do { \ TypedData_Get_Struct((obj), X509_ATTRIBUTE, &ossl_x509attr_type, (attr)); \ if (!(attr)) { \ - ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509attr_free(void *ptr) static const rb_data_type_t ossl_x509attr_type = { "OpenSSL/X509/ATTRIBUTE", { - 0, ossl_x509attr_free, + 0, ossl_x509attr_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -83,7 +83,7 @@ ossl_x509attr_alloc(VALUE klass) obj = NewX509Attr(klass); if (!(attr = X509_ATTRIBUTE_new())) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); SetX509Attr(obj, attr); return obj; @@ -102,15 +102,15 @@ ossl_x509attr_initialize(int argc, VALUE *argv, VALUE self) GetX509Attr(self, attr); if(rb_scan_args(argc, argv, "11", &oid, &value) == 1){ - oid = ossl_to_der_if_possible(oid); - StringValue(oid); - p = (unsigned char *)RSTRING_PTR(oid); - x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); - DATA_PTR(self) = attr; - if(!x){ - ossl_raise(eX509AttrError, NULL); - } - return self; + oid = ossl_to_der_if_possible(oid); + StringValue(oid); + p = (unsigned char *)RSTRING_PTR(oid); + x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid)); + DATA_PTR(self) = attr; + if(!x){ + ossl_raise(eX509AttrError, NULL); + } + return self; } rb_funcall(self, rb_intern("oid="), 1, oid); rb_funcall(self, rb_intern("value="), 1, value); @@ -130,7 +130,7 @@ ossl_x509attr_initialize_copy(VALUE self, VALUE other) attr_new = X509_ATTRIBUTE_dup(attr_other); if (!attr_new) - ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); + ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); SetX509Attr(self, attr_new); X509_ATTRIBUTE_free(attr); @@ -154,8 +154,8 @@ ossl_x509attr_set_oid(VALUE self, VALUE oid) obj = OBJ_txt2obj(s, 0); if(!obj) ossl_raise(eX509AttrError, NULL); if (!X509_ATTRIBUTE_set1_object(attr, obj)) { - ASN1_OBJECT_free(obj); - ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_object"); + ASN1_OBJECT_free(obj); + ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_object"); } ASN1_OBJECT_free(obj); @@ -236,21 +236,21 @@ ossl_x509attr_get_value(VALUE self) GetX509Attr(self, attr); /* there is no X509_ATTRIBUTE_get0_set() :( */ if (!(sk = sk_ASN1_TYPE_new_null())) - ossl_raise(eX509AttrError, "sk_new"); + ossl_raise(eX509AttrError, "sk_new"); count = X509_ATTRIBUTE_count(attr); for (i = 0; i < count; i++) - sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i)); + sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i)); if ((len = i2d_ASN1_SET_ANY(sk, NULL)) <= 0) { - sk_ASN1_TYPE_free(sk); - ossl_raise(eX509AttrError, NULL); + sk_ASN1_TYPE_free(sk); + ossl_raise(eX509AttrError, NULL); } str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_ASN1_SET_ANY(sk, &p) <= 0) { - sk_ASN1_TYPE_free(sk); - ossl_raise(eX509AttrError, NULL); + sk_ASN1_TYPE_free(sk); + ossl_raise(eX509AttrError, NULL); } ossl_str_adjust(str, p); sk_ASN1_TYPE_free(sk); @@ -272,11 +272,11 @@ ossl_x509attr_to_der(VALUE self) GetX509Attr(self, attr); if((len = i2d_X509_ATTRIBUTE(attr, NULL)) <= 0) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_ATTRIBUTE(attr, &p) <= 0) - ossl_raise(eX509AttrError, NULL); + ossl_raise(eX509AttrError, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index dca28395935a12..b1e82a2790adf6 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509_type, 0) #define SetX509(obj, x509) do { \ if (!(x509)) { \ - ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (x509); \ } while (0) #define GetX509(obj, x509) do { \ TypedData_Get_Struct((obj), X509, &ossl_x509_type, (x509)); \ if (!(x509)) { \ - ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509_free(void *ptr) static const rb_data_type_t ossl_x509_type = { "OpenSSL/X509", { - 0, ossl_x509_free, + 0, ossl_x509_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -115,8 +115,8 @@ ossl_x509_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - /* create just empty X509Cert */ - return self; + /* create just empty X509Cert */ + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -170,11 +170,11 @@ ossl_x509_to_der(VALUE self) GetX509(self, x509); if ((len = i2d_X509(x509, NULL)) <= 0) - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_X509(x509, &p) <= 0) - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); ossl_str_adjust(str, p); return str; @@ -196,8 +196,8 @@ ossl_x509_to_pem(VALUE self) if (!out) ossl_raise(eX509CertError, NULL); if (!PEM_write_bio_X509(out, x509)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); + BIO_free(out); + ossl_raise(eX509CertError, NULL); } str = ossl_membio2str(out); @@ -221,8 +221,8 @@ ossl_x509_to_text(VALUE self) if (!out) ossl_raise(eX509CertError, NULL); if (!X509_print(out, x509)) { - BIO_free(out); - ossl_raise(eX509CertError, NULL); + BIO_free(out); + ossl_raise(eX509CertError, NULL); } str = ossl_membio2str(out); @@ -242,7 +242,7 @@ ossl_x509_to_req(VALUE self) GetX509(self, x509); if (!(req = X509_to_X509_REQ(x509, NULL, EVP_md5()))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } obj = ossl_x509req_new(req); X509_REQ_free(req); @@ -276,11 +276,11 @@ ossl_x509_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509CertError, "version must be >= 0!"); + ossl_raise(eX509CertError, "version must be >= 0!"); } GetX509(self, x509); if (!X509_set_version(x509, ver)) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return version; @@ -349,7 +349,7 @@ ossl_x509_get_subject(VALUE self) GetX509(self, x509); if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_x509name_new(name); @@ -366,7 +366,7 @@ ossl_x509_set_subject(VALUE self, VALUE subject) GetX509(self, x509); if (!X509_set_subject_name(x509, GetX509NamePtr(subject))) { /* DUPs name */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return subject; @@ -384,7 +384,7 @@ ossl_x509_get_issuer(VALUE self) GetX509(self, x509); if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_x509name_new(name); @@ -401,7 +401,7 @@ ossl_x509_set_issuer(VALUE self, VALUE issuer) GetX509(self, x509); if (!X509_set_issuer_name(x509, GetX509NamePtr(issuer))) { /* DUPs name */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return issuer; @@ -419,7 +419,7 @@ ossl_x509_get_not_before(VALUE self) GetX509(self, x509); if (!(asn1time = X509_get0_notBefore(x509))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return asn1time_to_time(asn1time); @@ -438,8 +438,8 @@ ossl_x509_set_not_before(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_set1_notBefore(x509, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CertError, "X509_set_notBefore"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CertError, "X509_set_notBefore"); } ASN1_TIME_free(asn1time); @@ -458,7 +458,7 @@ ossl_x509_get_not_after(VALUE self) GetX509(self, x509); if (!(asn1time = X509_get0_notAfter(x509))) { - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return asn1time_to_time(asn1time); @@ -477,8 +477,8 @@ ossl_x509_set_not_after(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_set1_notAfter(x509, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CertError, "X509_set_notAfter"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CertError, "X509_set_notAfter"); } ASN1_TIME_free(asn1time); @@ -497,7 +497,7 @@ ossl_x509_get_public_key(VALUE self) GetX509(self, x509); if (!(pkey = X509_get_pubkey(x509))) { /* adds an reference */ - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } return ossl_pkey_wrap(pkey); @@ -517,7 +517,7 @@ ossl_x509_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_set_pubkey(x509, pkey)) - ossl_raise(eX509CertError, "X509_set_pubkey"); + ossl_raise(eX509CertError, "X509_set_pubkey"); return key; } @@ -561,12 +561,12 @@ ossl_x509_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (X509_verify(x509, pkey)) { case 1: - return Qtrue; + return Qtrue; case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; default: - ossl_raise(eX509CertError, NULL); + ossl_raise(eX509CertError, NULL); } } @@ -587,8 +587,8 @@ ossl_x509_check_private_key(VALUE self, VALUE key) pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ GetX509(self, x509); if (!X509_check_private_key(x509, pkey)) { - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; } return Qtrue; @@ -610,8 +610,8 @@ ossl_x509_get_extensions(VALUE self) count = X509_get_ext_count(x509); ary = rb_ary_new_capa(count); for (i=0; i 0; i--) X509_EXTENSION_free(X509_delete_ext(x509, 0)); for (i=0; i", - rb_obj_class(self), - ossl_x509_get_subject(self), - ossl_x509_get_issuer(self), - ossl_x509_get_serial(self), - ossl_x509_get_not_before(self), - ossl_x509_get_not_after(self)); + "issuer=%+"PRIsVALUE", serial=%+"PRIsVALUE", " + "not_before=%+"PRIsVALUE", not_after=%+"PRIsVALUE">", + rb_obj_class(self), + ossl_x509_get_subject(self), + ossl_x509_get_issuer(self), + ossl_x509_get_serial(self), + ossl_x509_get_not_before(self), + ossl_x509_get_not_after(self)); } /* @@ -693,7 +693,7 @@ ossl_x509_eq(VALUE self, VALUE other) GetX509(self, a); if (!rb_obj_is_kind_of(other, cX509Cert)) - return Qfalse; + return Qfalse; GetX509(other, b); return !X509_cmp(a, b) ? Qtrue : Qfalse; @@ -788,7 +788,7 @@ load_chained_certificates_PEM(BIO *in) { certificates = load_chained_certificates_append(Qnil, certificate); while ((certificate = PEM_read_bio_X509(in, NULL, NULL, NULL))) { - load_chained_certificates_append(certificates, certificate); + load_chained_certificates_append(certificates, certificate); } /* We tried to read one more certificate but could not read start line: */ diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 19f08053e15c82..a221429c347d5e 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509crl_type, 0) #define SetX509CRL(obj, crl) do { \ if (!(crl)) { \ - ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (crl); \ } while (0) #define GetX509CRL(obj, crl) do { \ TypedData_Get_Struct((obj), X509_CRL, &ossl_x509crl_type, (crl)); \ if (!(crl)) { \ - ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509crl_free(void *ptr) static const rb_data_type_t ossl_x509crl_type = { "OpenSSL/X509/CRL", { - 0, ossl_x509crl_free, + 0, ossl_x509crl_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -83,7 +83,7 @@ ossl_x509crl_alloc(VALUE klass) obj = NewX509CRL(klass); if (!(crl = X509_CRL_new())) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } SetX509CRL(obj, crl); @@ -99,7 +99,7 @@ ossl_x509crl_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - return self; + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -129,7 +129,7 @@ ossl_x509crl_copy(VALUE self, VALUE other) GetX509CRL(self, a); GetX509CRL(other, b); if (!(crl = X509_CRL_dup(b))) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } X509_CRL_free(a); DATA_PTR(self) = crl; @@ -156,11 +156,11 @@ ossl_x509crl_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509CRLError, "version must be >= 0!"); + ossl_raise(eX509CRLError, "version must be >= 0!"); } GetX509CRL(self, crl); if (!X509_CRL_set_version(crl, ver)) { - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } return version; @@ -206,7 +206,7 @@ ossl_x509crl_set_issuer(VALUE self, VALUE issuer) GetX509CRL(self, crl); if (!X509_CRL_set_issuer_name(crl, GetX509NamePtr(issuer))) { /* DUPs name */ - ossl_raise(eX509CRLError, NULL); + ossl_raise(eX509CRLError, NULL); } return issuer; } @@ -220,7 +220,7 @@ ossl_x509crl_get_last_update(VALUE self) GetX509CRL(self, crl); time = X509_CRL_get0_lastUpdate(crl); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -234,8 +234,8 @@ ossl_x509crl_set_last_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_CRL_set1_lastUpdate(crl, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate"); } ASN1_TIME_free(asn1time); @@ -251,7 +251,7 @@ ossl_x509crl_get_next_update(VALUE self) GetX509CRL(self, crl); time = X509_CRL_get0_nextUpdate(crl); if (!time) - return Qnil; + return Qnil; return asn1time_to_time(time); } @@ -265,8 +265,8 @@ ossl_x509crl_set_next_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); if (!X509_CRL_set1_nextUpdate(crl, asn1time)) { - ASN1_TIME_free(asn1time); - ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate"); + ASN1_TIME_free(asn1time); + ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate"); } ASN1_TIME_free(asn1time); @@ -289,8 +289,8 @@ ossl_x509crl_get_revoked(VALUE self) num = sk_X509_REVOKED_num(sk); ary = rb_ary_new_capa(num); for(i=0; i 0; i--) X509_EXTENSION_free(X509_CRL_delete_ext(crl, 0)); for (i=0; idata, value->length); } @@ -424,11 +424,11 @@ ossl_x509ext_to_der(VALUE obj) GetX509Ext(obj, ext); if((len = i2d_X509_EXTENSION(ext, NULL)) <= 0) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_EXTENSION(ext, &p) < 0) - ossl_raise(eX509ExtError, NULL); + ossl_raise(eX509ExtError, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 3c0c6aca2905be..b91c92c1ff9670 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -13,21 +13,21 @@ TypedData_Wrap_Struct((klass), &ossl_x509name_type, 0) #define SetX509Name(obj, name) do { \ if (!(name)) { \ - ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (name); \ } while (0) #define GetX509Name(obj, name) do { \ TypedData_Get_Struct((obj), X509_NAME, &ossl_x509name_type, (name)); \ if (!(name)) { \ - ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ + ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \ } \ } while (0) #define OBJECT_TYPE_TEMPLATE \ - rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE")) + rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE")) #define DEFAULT_OBJECT_TYPE \ - rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE")) + rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE")) /* * Classes @@ -44,7 +44,7 @@ ossl_x509name_free(void *ptr) static const rb_data_type_t ossl_x509name_type = { "OpenSSL/X509/NAME", { - 0, ossl_x509name_free, + 0, ossl_x509name_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -88,7 +88,7 @@ ossl_x509name_alloc(VALUE klass) obj = NewX509Name(klass); if (!(name = X509_NAME_new())) { - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); } SetX509Name(obj, name); @@ -145,28 +145,28 @@ ossl_x509name_initialize(int argc, VALUE *argv, VALUE self) GetX509Name(self, name); if (rb_scan_args(argc, argv, "02", &arg, &template) == 0) { - return self; + return self; } else { - VALUE tmp = rb_check_array_type(arg); - if (!NIL_P(tmp)) { - VALUE args; - if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE; - args = rb_ary_new3(2, self, template); - rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args); - } - else{ - const unsigned char *p; - VALUE str = ossl_to_der_if_possible(arg); - X509_NAME *x; - StringValue(str); - p = (unsigned char *)RSTRING_PTR(str); - x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); - DATA_PTR(self) = name; - if(!x){ - ossl_raise(eX509NameError, NULL); - } - } + VALUE tmp = rb_check_array_type(arg); + if (!NIL_P(tmp)) { + VALUE args; + if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE; + args = rb_ary_new3(2, self, template); + rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args); + } + else{ + const unsigned char *p; + VALUE str = ossl_to_der_if_possible(arg); + X509_NAME *x; + StringValue(str); + p = (unsigned char *)RSTRING_PTR(str); + x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str)); + DATA_PTR(self) = name; + if(!x){ + ossl_raise(eX509NameError, NULL); + } + } } return self; @@ -184,7 +184,7 @@ ossl_x509name_initialize_copy(VALUE self, VALUE other) name_new = X509_NAME_dup(name_other); if (!name_new) - ossl_raise(eX509NameError, "X509_NAME_dup"); + ossl_raise(eX509NameError, "X509_NAME_dup"); SetX509Name(self, name_new); X509_NAME_free(name); @@ -221,8 +221,8 @@ VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) int loc = -1, set = 0; if (!kwargs_ids[0]) { - kwargs_ids[0] = rb_intern_const("loc"); - kwargs_ids[1] = rb_intern_const("set"); + kwargs_ids[0] = rb_intern_const("loc"); + kwargs_ids[1] = rb_intern_const("set"); } rb_scan_args(argc, argv, "21:", &oid, &value, &type, &opts); rb_get_kwargs(opts, kwargs_ids, 0, 2, kwargs); @@ -230,14 +230,14 @@ VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) StringValue(value); if(NIL_P(type)) type = rb_aref(OBJECT_TYPE_TEMPLATE, oid); if (kwargs[0] != Qundef) - loc = NUM2INT(kwargs[0]); + loc = NUM2INT(kwargs[0]); if (kwargs[1] != Qundef) - set = NUM2INT(kwargs[1]); + set = NUM2INT(kwargs[1]); GetX509Name(self, name); if (!X509_NAME_add_entry_by_txt(name, oid_name, NUM2INT(type), - (unsigned char *)RSTRING_PTR(value), - RSTRING_LENINT(value), loc, set)) - ossl_raise(eX509NameError, "X509_NAME_add_entry_by_txt"); + (unsigned char *)RSTRING_PTR(value), + RSTRING_LENINT(value), loc, set)) + ossl_raise(eX509NameError, "X509_NAME_add_entry_by_txt"); return self; } @@ -250,7 +250,7 @@ ossl_x509name_to_s_old(VALUE self) GetX509Name(self, name); buf = X509_NAME_oneline(name, NULL, 0); if (!buf) - ossl_raise(eX509NameError, "X509_NAME_oneline"); + ossl_raise(eX509NameError, "X509_NAME_oneline"); return ossl_buf2str(buf, rb_long2int(strlen(buf))); } @@ -264,11 +264,11 @@ x509name_print(VALUE self, unsigned long iflag) GetX509Name(self, name); out = BIO_new(BIO_s_mem()); if (!out) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); ret = X509_NAME_print_ex(out, name, 0, iflag); if (ret < 0 || (iflag == XN_FLAG_COMPAT && ret == 0)) { - BIO_free(out); - ossl_raise(eX509NameError, "X509_NAME_print_ex"); + BIO_free(out); + ossl_raise(eX509NameError, "X509_NAME_print_ex"); } return ossl_membio2str(out); } @@ -302,9 +302,9 @@ ossl_x509name_to_s(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 0, 1); /* name.to_s(nil) was allowed */ if (!argc || NIL_P(argv[0])) - return ossl_x509name_to_s_old(self); + return ossl_x509name_to_s_old(self); else - return x509name_print(self, NUM2ULONG(argv[0])); + return x509name_print(self, NUM2ULONG(argv[0])); } /* @@ -327,7 +327,7 @@ static VALUE ossl_x509name_inspect(VALUE self) { return rb_enc_sprintf(rb_utf8_encoding(), "#<%"PRIsVALUE" %"PRIsVALUE">", - rb_obj_class(self), ossl_x509name_to_utf8(self)); + rb_obj_class(self), ossl_x509name_to_utf8(self)); } /* @@ -387,7 +387,7 @@ ossl_x509name_cmp(VALUE self, VALUE other) int result; if (!rb_obj_is_kind_of(other, cX509Name)) - return Qnil; + return Qnil; result = ossl_x509name_cmp0(self, other); if (result < 0) return INT2FIX(-1); @@ -406,7 +406,7 @@ static VALUE ossl_x509name_eql(VALUE self, VALUE other) { if (!rb_obj_is_kind_of(other, cX509Name)) - return Qfalse; + return Qfalse; return ossl_x509name_cmp0(self, other) == 0 ? Qtrue : Qfalse; } @@ -466,11 +466,11 @@ ossl_x509name_to_der(VALUE self) GetX509Name(self, name); if((len = i2d_X509_NAME(name, NULL)) <= 0) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_X509_NAME(name, &p) <= 0) - ossl_raise(eX509NameError, NULL); + ossl_raise(eX509NameError, NULL); ossl_str_adjust(str, p); return str; diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index d7252379cdda4d..433cc461a9933b 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -13,14 +13,14 @@ TypedData_Wrap_Struct((klass), &ossl_x509req_type, 0) #define SetX509Req(obj, req) do { \ if (!(req)) { \ - ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ } \ RTYPEDDATA_DATA(obj) = (req); \ } while (0) #define GetX509Req(obj, req) do { \ TypedData_Get_Struct((obj), X509_REQ, &ossl_x509req_type, (req)); \ if (!(req)) { \ - ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ + ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \ } \ } while (0) @@ -39,7 +39,7 @@ ossl_x509req_free(void *ptr) static const rb_data_type_t ossl_x509req_type = { "OpenSSL/X509/REQ", { - 0, ossl_x509req_free, + 0, ossl_x509req_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; @@ -68,7 +68,7 @@ ossl_x509req_alloc(VALUE klass) obj = NewX509Req(klass); if (!(req = X509_REQ_new())) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } SetX509Req(obj, req); @@ -84,7 +84,7 @@ ossl_x509req_initialize(int argc, VALUE *argv, VALUE self) rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { - return self; + return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); @@ -114,7 +114,7 @@ ossl_x509req_copy(VALUE self, VALUE other) GetX509Req(self, a); GetX509Req(other, b); if (!(req = X509_REQ_dup(b))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } X509_REQ_free(a); DATA_PTR(self) = req; @@ -130,11 +130,11 @@ ossl_x509req_to_pem(VALUE self) GetX509Req(self, req); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } if (!PEM_write_bio_X509_REQ(out, req)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); + BIO_free(out); + ossl_raise(eX509ReqError, NULL); } return ossl_membio2str(out); @@ -150,11 +150,11 @@ ossl_x509req_to_der(VALUE self) GetX509Req(self, req); if ((len = i2d_X509_REQ(req, NULL)) <= 0) - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_X509_REQ(req, &p) <= 0) - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); ossl_str_adjust(str, p); return str; @@ -168,11 +168,11 @@ ossl_x509req_to_text(VALUE self) GetX509Req(self, req); if (!(out = BIO_new(BIO_s_mem()))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } if (!X509_REQ_print(out, req)) { - BIO_free(out); - ossl_raise(eX509ReqError, NULL); + BIO_free(out); + ossl_raise(eX509ReqError, NULL); } return ossl_membio2str(out); @@ -191,7 +191,7 @@ ossl_x509req_to_x509(VALUE self, VALUE days, VALUE key) GetX509Req(self, req); ... if (!(x509 = X509_REQ_to_X509(req, d, pkey))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_x509_new(x509); @@ -217,11 +217,11 @@ ossl_x509req_set_version(VALUE self, VALUE version) long ver; if ((ver = NUM2LONG(version)) < 0) { - ossl_raise(eX509ReqError, "version must be >= 0!"); + ossl_raise(eX509ReqError, "version must be >= 0!"); } GetX509Req(self, req); if (!X509_REQ_set_version(req, ver)) { - ossl_raise(eX509ReqError, "X509_REQ_set_version"); + ossl_raise(eX509ReqError, "X509_REQ_set_version"); } return version; @@ -235,7 +235,7 @@ ossl_x509req_get_subject(VALUE self) GetX509Req(self, req); if (!(name = X509_REQ_get_subject_name(req))) { /* NO DUP - don't free */ - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_x509name_new(name); @@ -249,7 +249,7 @@ ossl_x509req_set_subject(VALUE self, VALUE subject) GetX509Req(self, req); /* DUPs name */ if (!X509_REQ_set_subject_name(req, GetX509NamePtr(subject))) { - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return subject; @@ -285,7 +285,7 @@ ossl_x509req_get_public_key(VALUE self) GetX509Req(self, req); if (!(pkey = X509_REQ_get_pubkey(req))) { /* adds reference */ - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } return ossl_pkey_wrap(pkey); @@ -301,7 +301,7 @@ ossl_x509req_set_public_key(VALUE self, VALUE key) pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_REQ_set_pubkey(req, pkey)) - ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); + ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); return key; } @@ -337,12 +337,12 @@ ossl_x509req_verify(VALUE self, VALUE key) ossl_pkey_check_public_key(pkey); switch (X509_REQ_verify(req, pkey)) { case 1: - return Qtrue; + return Qtrue; case 0: - ossl_clear_error(); - return Qfalse; + ossl_clear_error(); + return Qfalse; default: - ossl_raise(eX509ReqError, NULL); + ossl_raise(eX509ReqError, NULL); } } @@ -358,13 +358,13 @@ ossl_x509req_get_attributes(VALUE self) count = X509_REQ_get_attr_count(req); if (count < 0) { - OSSL_Debug("count < 0???"); - return rb_ary_new(); + OSSL_Debug("count < 0???"); + return rb_ary_new(); } ary = rb_ary_new2(count); for (i=0; i 0; i--) X509_ATTRIBUTE_free(X509_REQ_delete_attr(req, 0)); for (i=0;i 0; i--) X509_EXTENSION_free(X509_REVOKED_delete_ext(rev, 0)); for (i=0; iproc, rb_intern("call"), 2, - args->preverify_ok, args->store_ctx); + args->preverify_ok, args->store_ctx); } int @@ -73,33 +73,33 @@ ossl_verify_cb_call(VALUE proc, int ok, X509_STORE_CTX *ctx) int state; if (NIL_P(proc)) - return ok; + return ok; ret = Qfalse; rctx = rb_protect(ossl_x509stctx_new_i, (VALUE)ctx, &state); if (state) { - rb_set_errinfo(Qnil); - rb_warn("StoreContext initialization failure"); + rb_set_errinfo(Qnil); + rb_warn("StoreContext initialization failure"); } else { - args.proc = proc; - args.preverify_ok = ok ? Qtrue : Qfalse; - args.store_ctx = rctx; - ret = rb_protect(call_verify_cb_proc, (VALUE)&args, &state); - if (state) { - rb_set_errinfo(Qnil); - rb_warn("exception in verify_callback is ignored"); - } - RTYPEDDATA_DATA(rctx) = NULL; + args.proc = proc; + args.preverify_ok = ok ? Qtrue : Qfalse; + args.store_ctx = rctx; + ret = rb_protect(call_verify_cb_proc, (VALUE)&args, &state); + if (state) { + rb_set_errinfo(Qnil); + rb_warn("exception in verify_callback is ignored"); + } + RTYPEDDATA_DATA(rctx) = NULL; } if (ret == Qtrue) { - X509_STORE_CTX_set_error(ctx, X509_V_OK); - ok = 1; + X509_STORE_CTX_set_error(ctx, X509_V_OK); + ok = 1; } else { - if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) - X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); - ok = 0; + if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); + ok = 0; } return ok; @@ -159,10 +159,10 @@ x509store_verify_cb(int ok, X509_STORE_CTX *ctx) proc = (VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx); if (!proc) - proc = (VALUE)X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx), - store_ex_verify_cb_idx); + proc = (VALUE)X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx), + store_ex_verify_cb_idx); if (!proc) - return ok; + return ok; return ossl_verify_cb_call(proc, ok, ctx); } @@ -484,7 +484,7 @@ ossl_x509store_verify(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "11", &cert, &chain); ctx = rb_funcall(cX509StoreContext, rb_intern("new"), 3, self, cert, chain); proc = rb_block_given_p() ? rb_block_proc() : - rb_iv_get(self, "@verify_callback"); + rb_iv_get(self, "@verify_callback"); rb_iv_set(ctx, "@verify_callback", proc); result = rb_funcall(ctx, rb_intern("verify"), 0); @@ -513,9 +513,9 @@ ossl_x509stctx_free(void *ptr) { X509_STORE_CTX *ctx = ptr; if (X509_STORE_CTX_get0_untrusted(ctx)) - sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free); + sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free); if (X509_STORE_CTX_get0_cert(ctx)) - X509_free(X509_STORE_CTX_get0_cert(ctx)); + X509_free(X509_STORE_CTX_get0_cert(ctx)); X509_STORE_CTX_free(ctx); } @@ -763,7 +763,7 @@ ossl_x509stctx_get_curr_crl(VALUE self) GetX509StCtx(self, ctx); crl = X509_STORE_CTX_get0_current_crl(ctx); if (!crl) - return Qnil; + return Qnil; return ossl_x509crl_new(crl); } @@ -862,10 +862,10 @@ Init_ossl_x509store(void) /* Register ext_data slot for verify callback Proc */ stctx_ex_verify_cb_idx = X509_STORE_CTX_get_ex_new_index(0, (void *)"stctx_ex_verify_cb_idx", 0, 0, 0); if (stctx_ex_verify_cb_idx < 0) - ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index"); + ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index"); store_ex_verify_cb_idx = X509_STORE_get_ex_new_index(0, (void *)"store_ex_verify_cb_idx", 0, 0, 0); if (store_ex_verify_cb_idx < 0) - ossl_raise(eOSSLError, "X509_STORE_get_ex_new_index"); + ossl_raise(eOSSLError, "X509_STORE_get_ex_new_index"); eX509StoreError = rb_define_class_under(mX509, "StoreError", eOSSLError); From 2aaea665bb19be8ed64605ec9aa5c990fddbd2ce Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 02:40:59 +0900 Subject: [PATCH 1569/2435] fix typo s/sharable/shareable/ --- bootstraptest/test_ractor.rb | 6 +++--- doc/syntax/comments.rdoc | 2 +- include/ruby/internal/core/rtypeddata.h | 2 +- include/ruby/ractor.h | 2 +- ractor.c | 2 +- ractor.rb | 8 ++++---- test/ruby/test_parse.rb | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index cc00fa3586af93..271abb3c84006a 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1203,9 +1203,9 @@ def /(other) end } -# Ractor.make_sharable(Method/UnboundMethod) +# Ractor.make_shareable(Method/UnboundMethod) assert_equal 'true', %q{ - # raise because receiver is unsharable + # raise because receiver is unshareable begin _m0 = Ractor.make_shareable(self.method(:__id__)) rescue => e @@ -1214,7 +1214,7 @@ def /(other) raise "no error" end - # Method with sharable receiver + # Method with shareable receiver M1 = Ractor.make_shareable(Object.method(:__id__)) # UnboundMethod diff --git a/doc/syntax/comments.rdoc b/doc/syntax/comments.rdoc index 00d19d588af508..cb6829a984a32e 100644 --- a/doc/syntax/comments.rdoc +++ b/doc/syntax/comments.rdoc @@ -170,7 +170,7 @@ In this mode, all values assigned to constants are made shareable. # shareable_constant_value: experimental_everything FOO = Set[1, 2, {foo: []}] - # same as FOO = Ractor.make_sharable(...) + # same as FOO = Ractor.make_shareable(...) # OR same as `FOO = Set[1, 2, {foo: [].freeze}.freeze].freeze` var = [{foo: []}] diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index ee79c2e2a90a18..ebcac2c8469db6 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -160,7 +160,7 @@ rbimpl_typeddata_flags { // experimental flag // Similar to RUBY_TYPED_FROZEN_SHAREABLE, but doesn't make shareable // reachable objects from this T_DATA object on the Ractor.make_shareable. - // If it refers to unsharable objects, simply raise an error. + // If it refers to unshareable objects, simply raise an error. // RUBY_TYPED_FROZEN_SHAREABLE_NO_REC = RUBY_FL_FINALIZE, /** diff --git a/include/ruby/ractor.h b/include/ruby/ractor.h index 85222bbe115860..8cfca2162107c8 100644 --- a/include/ruby/ractor.h +++ b/include/ruby/ractor.h @@ -217,7 +217,7 @@ VALUE rb_ractor_make_shareable(VALUE obj); * * @param[in] obj Arbitrary ruby object to duplicate. * @exception rb_eRactorError Ractors cannot share `obj` by nature. - * @return A deep copy of `obj` which is sharable among Ractors. + * @return A deep copy of `obj` which is shareable among Ractors. */ VALUE rb_ractor_make_shareable_copy(VALUE obj); diff --git a/ractor.c b/ractor.c index 6bd38b6ab875f0..b7c7d9467924b6 100644 --- a/ractor.c +++ b/ractor.c @@ -2366,7 +2366,7 @@ ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data); } -// sharable_proc +// shareable_proc static VALUE ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda) diff --git a/ractor.rb b/ractor.rb index 3c0c6744e6148e..d992f0a04702c9 100644 --- a/ractor.rb +++ b/ractor.rb @@ -607,7 +607,7 @@ def unmonitor port # # call-seq: - # Ractor.sharable_proc(self: nil){} -> sharable proc + # Ractor.shareable_proc(self: nil){} -> shareable proc # # It returns shareable Proc object. The Proc object is # shareable and the self in a block will be replaced with @@ -619,7 +619,7 @@ def unmonitor port # Ractor.shareable_proc{ p a } # #=> can not isolate a Proc because it accesses outer variables (a). (ArgumentError) # - # The `self` should be a sharable object + # The `self` should be a shareable object # # Ractor.shareable_proc(self: self){} # #=> self should be shareable: main (Ractor::IsolationError) @@ -634,9 +634,9 @@ def self.shareable_proc self: nil # # call-seq: - # Ractor.sharable_proc{} -> sharable proc + # Ractor.shareable_proc{} -> shareable proc # - # Same as Ractor.sharable_proc, but returns lambda proc. + # Same as Ractor.shareable_proc, but returns lambda proc. # def self.shareable_lambda self: nil Primitive.attr! :use_block diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 98e95b98afaf77..78a5638647af67 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1544,7 +1544,7 @@ def test_shareable_constant_value_ignored end def test_shareable_constant_value_simple - obj = [['unsharable_value']] + obj = [['unshareable_value']] a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") begin; # shareable_constant_value: experimental_everything From d9aced864bed7183059785c3aaf37ae835434f4a Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 5 Dec 2025 03:15:50 +0900 Subject: [PATCH 1570/2435] Add openssl reformatting to .git-blame-ignore-revs [ci skip] --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 3ca28ad72c8917..d98646febf69c1 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -10,6 +10,7 @@ e63a2115f64433b21cb5dd67c5bf8b30f87ef293 712ac99e4d0384a941c80a9f48f62943ba7d97c0 d1474affa8e105bece209cc9d594bb0a989859e1 2da92388b948821269b18d6b178a680f17e41750 +5062c0c621d887367af8a054e5e5d83d7ec57dd3 # Enable Style/StringLiterals cop for RubyGems/Bundler d7ffd3fea402239b16833cc434404a7af82d44f3 From de2c2bd60fdce52cc7ba38a25f3e9436442af604 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 4 Dec 2025 16:10:21 -0500 Subject: [PATCH 1571/2435] Take VM lock in `class_switch_superclass` (#15356) Safe multi-ractor subclass list mutation We need to lock around mutation and accesses of a class's subclasses list. Unfortunately we also need to do this when creating singleton classes, as the singleton class does need to go into `super`'s subclasses list for CC invalidation purposes. --- class.c | 6 ++++-- test/ruby/test_class.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/class.c b/class.c index 0cec526b74fe98..9716ba07dae2e2 100644 --- a/class.c +++ b/class.c @@ -734,8 +734,10 @@ class_detach_subclasses(VALUE klass, VALUE arg) static void class_switch_superclass(VALUE super, VALUE klass) { - class_detach_subclasses(klass, Qnil); - rb_class_subclass_add(super, klass); + RB_VM_LOCKING() { + class_detach_subclasses(klass, Qnil); + rb_class_subclass_add(super, klass); + } } /** diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 10b7655e9a5562..1faecd0cb25e13 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -917,4 +917,17 @@ class T end end; end + + def test_safe_multi_ractor_subclasses_list_mutation + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + 4.times.map do + Ractor.new do + 20_000.times do + Object.new.singleton_class + end + end + end.each(&:join) + end; + end end From 1d3fe2c382fd543bf12b2e12c2633162ea4b3a0a Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 30 Oct 2025 12:05:25 -0700 Subject: [PATCH 1572/2435] Change bmethod defined_ractor to use id instead When defining a bmethod, we recorded the current Ractor's object in the method. However that was never marked and so could be GC'd and reused by a future Ractor. Instead we can use the Ractor's id, which we expect to be unique forever. Co-authored-by: Luke Gruber --- bootstraptest/test_ractor.rb | 25 +++++++++++++++++++++++++ method.h | 2 +- vm_insnhelper.c | 4 ++-- vm_method.c | 3 +-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 271abb3c84006a..b19349391cce81 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -2413,3 +2413,28 @@ def call_test(obj) :ok end RUBY + +# When creating bmethods in Ractors, they should only be usable from their +# defining ractor, even if it is GC'd +assert_equal 'ok', <<~'RUBY' +CLASSES = 1000.times.map do + Ractor.new do + Class.new do + define_method(:foo) {} + end + end +end.map(&:value).freeze + +any = 1000.times.map do + Ractor.new do + CLASSES.any? do |klass| + begin + klass.new.foo + true + rescue RuntimeError + false + end + end + end +end.map(&:value).none? && :ok +RUBY diff --git a/method.h b/method.h index 87fb2498a054d7..b174c6fccb4d94 100644 --- a/method.h +++ b/method.h @@ -167,7 +167,7 @@ typedef struct rb_method_refined_struct { typedef struct rb_method_bmethod_struct { VALUE proc; /* should be marked */ struct rb_hook_list_struct *hooks; - VALUE defined_ractor; + rb_serial_t defined_ractor_id; } rb_method_bmethod_t; enum method_optimized_type { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 60e1b647ae5d8d..8c1992de43d495 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4121,7 +4121,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor != rb_ractor_self(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } @@ -4144,7 +4144,7 @@ vm_call_iseq_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor != rb_ractor_self(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } diff --git a/vm_method.c b/vm_method.c index 179deb749daeec..c4f391b5afe38a 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1020,7 +1020,7 @@ rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *de } case VM_METHOD_TYPE_BMETHOD: RB_OBJ_WRITE(me, &def->body.bmethod.proc, (VALUE)opts); - RB_OBJ_WRITE(me, &def->body.bmethod.defined_ractor, rb_ractor_self(GET_RACTOR())); + def->body.bmethod.defined_ractor_id = rb_ractor_id(rb_ec_ractor_ptr(GET_EC())); return; case VM_METHOD_TYPE_NOTIMPLEMENTED: setup_method_cfunc_struct(UNALIGNED_MEMBER_PTR(def, body.cfunc), (VALUE(*)(ANYARGS))rb_f_notimplement_internal, -1); @@ -1060,7 +1060,6 @@ method_definition_reset(const rb_method_entry_t *me) break; case VM_METHOD_TYPE_BMETHOD: RB_OBJ_WRITTEN(me, Qundef, def->body.bmethod.proc); - RB_OBJ_WRITTEN(me, Qundef, def->body.bmethod.defined_ractor); /* give up to check all in a list */ if (def->body.bmethod.hooks) rb_gc_writebarrier_remember((VALUE)me); break; From 7d9558f99b5bb8279c138860670b56735a28a3e4 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 1 Dec 2025 19:31:38 -0800 Subject: [PATCH 1573/2435] Adjust test to avoid bug --- bootstraptest/test_ractor.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index b19349391cce81..7d09ac52838f66 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -2417,13 +2417,24 @@ def call_test(obj) # When creating bmethods in Ractors, they should only be usable from their # defining ractor, even if it is GC'd assert_equal 'ok', <<~'RUBY' -CLASSES = 1000.times.map do - Ractor.new do - Class.new do - define_method(:foo) {} - end +CLASSES = 1000.times.map { Class.new }.freeze + +# This would be better to run in parallel, but there's a bug with lambda +# creation and YJIT causing crashes in dev mode +ractors = CLASSES.map do |klass| + Ractor.new(klass) do |klass| + Ractor.receive + klass.define_method(:foo) {} end -end.map(&:value).freeze +end + +ractors.each do |ractor| + ractor << nil + ractor.join +end + +ractors.clear +GC.start any = 1000.times.map do Ractor.new do From 8d8159e7d87e4fd1594ce2fad3d2653e47fb1026 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 4 Dec 2025 16:51:11 -0500 Subject: [PATCH 1574/2435] Fix thread scheduler issue with thread_sched_wait_events (#15392) Fix race between timer thread dequeuing waiting thread and thread skipping sleeping due to being dequeued. We now use `th->event_serial` which is protected by `thread_sched_lock`. When a thread is put on timer thread's waiting list, the event serial is saved on the item. The timer thread checks that the saved serial is the same as current thread's serial before calling `thread_sched_to_ready`. The following script (taken from a test in `test_thread.rb` used to crash on scheduler debug assertions. It would likely crash in non-debug mode as well. ```ruby def assert_nil(val) if val != nil raise "Expected #{val} to be nil" end end def assert_equal(expected, actual) if expected != actual raise "Expected #{expected} to be #{actual}" end end def test_join2 ok = false t1 = Thread.new { ok = true; sleep } Thread.pass until ok Thread.pass until t1.stop? t2 = Thread.new do Thread.pass while ok t1.join(0.01) end t3 = Thread.new do ok = false t1.join end assert_nil(t2.value) t1.wakeup assert_equal(t1, t3.value) ensure t1&.kill&.join t2&.kill&.join t3&.kill&.join end rs = 30.times.map do Ractor.new do test_join2 end end rs.each(&:join) ``` --- thread_pthread.c | 31 ++++++++++++++++++------------- thread_pthread.h | 1 + thread_pthread_mn.c | 34 ++++++++++++++++++++++------------ vm_core.h | 1 + 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 2eaa407f10ca94..93b32e55c0f72b 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1122,8 +1122,10 @@ thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t { if (!RUBY_VM_INTERRUPTED(th->ec)) { bool can_direct_transfer = !th_has_dedicated_nt(th); + th->status = THREAD_STOPPED_FOREVER; thread_sched_wakeup_next_thread(sched, th, can_direct_transfer); thread_sched_wait_running_turn(sched, th, can_direct_transfer); + th->status = THREAD_RUNNABLE; } else { RUBY_DEBUG_LOG("th:%u interrupted", rb_th_serial(th)); @@ -1149,6 +1151,7 @@ thread_sched_yield(struct rb_thread_sched *sched, rb_thread_t *th) bool can_direct_transfer = !th_has_dedicated_nt(th); thread_sched_to_ready_common(sched, th, false, can_direct_transfer); thread_sched_wait_running_turn(sched, th, can_direct_transfer); + th->status = THREAD_RUNNABLE; } else { VM_ASSERT(sched->readyq_cnt == 0); @@ -1338,7 +1341,7 @@ void rb_ractor_lock_self(rb_ractor_t *r); void rb_ractor_unlock_self(rb_ractor_t *r); // The current thread for a ractor is put to "sleep" (descheduled in the STOPPED_FOREVER state) waiting for -// a ractor action to wake it up. See docs for `ractor_sched_sleep_with_cleanup` for more info. +// a ractor action to wake it up. void rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ubf_arg) { @@ -2868,7 +2871,7 @@ static struct { static void timer_thread_check_timeslice(rb_vm_t *vm); static int timer_thread_set_timeout(rb_vm_t *vm); -static void timer_thread_wakeup_thread(rb_thread_t *th); +static void timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial); #include "thread_pthread_mn.c" @@ -2970,7 +2973,7 @@ timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now) } static rb_thread_t * -timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now) +timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now, uint32_t *event_serial) { struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); @@ -2987,32 +2990,31 @@ timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now) w->flags = thread_sched_waiting_none; w->data.result = 0; - return thread_sched_waiting_thread(w); + rb_thread_t *th = thread_sched_waiting_thread(w); + *event_serial = w->data.event_serial; + return th; } return NULL; } static void -timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th) +timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial) { - if (sched->running != th) { + if (sched->running != th && th->event_serial == event_serial) { thread_sched_to_ready_common(sched, th, true, false); } - else { - // will be release the execution right - } } static void -timer_thread_wakeup_thread(rb_thread_t *th) +timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial) { RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); struct rb_thread_sched *sched = TH_SCHED(th); thread_sched_lock(sched, th); { - timer_thread_wakeup_thread_locked(sched, th); + timer_thread_wakeup_thread_locked(sched, th, event_serial); } thread_sched_unlock(sched, th); } @@ -3022,11 +3024,14 @@ timer_thread_check_timeout(rb_vm_t *vm) { rb_hrtime_t now = rb_hrtime_now(); rb_thread_t *th; + uint32_t event_serial; rb_native_mutex_lock(&timer_th.waiting_lock); { - while ((th = timer_thread_deq_wakeup(vm, now)) != NULL) { - timer_thread_wakeup_thread(th); + while ((th = timer_thread_deq_wakeup(vm, now, &event_serial)) != NULL) { + rb_native_mutex_unlock(&timer_th.waiting_lock); + timer_thread_wakeup_thread(th, event_serial); + rb_native_mutex_lock(&timer_th.waiting_lock); } } rb_native_mutex_unlock(&timer_th.waiting_lock); diff --git a/thread_pthread.h b/thread_pthread.h index d635948c4bbb7c..f579e53781a74c 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -39,6 +39,7 @@ struct rb_thread_sched_waiting { #else uint64_t timeout; #endif + uint32_t event_serial; int fd; // -1 for timeout only int result; } data; diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 8100fd534e461e..2211d4d1067c39 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -3,7 +3,7 @@ #if USE_MN_THREADS static void timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags); -static void timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th); +static void timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial); static bool timer_thread_cancel_waiting(rb_thread_t *th) @@ -15,9 +15,7 @@ timer_thread_cancel_waiting(rb_thread_t *th) if (th->sched.waiting_reason.flags) { canceled = true; ccan_list_del_init(&th->sched.waiting_reason.node); - if (th->sched.waiting_reason.flags & (thread_sched_waiting_io_read | thread_sched_waiting_io_write)) { - timer_thread_unregister_waiting(th, th->sched.waiting_reason.data.fd, th->sched.waiting_reason.flags); - } + timer_thread_unregister_waiting(th, th->sched.waiting_reason.data.fd, th->sched.waiting_reason.flags); th->sched.waiting_reason.flags = thread_sched_waiting_none; } } @@ -57,7 +55,7 @@ ubf_event_waiting(void *ptr) thread_sched_unlock(sched, th); } -static bool timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel); +static bool timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial); // return true if timed out static bool @@ -67,13 +65,15 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, volatile bool timedout = false, need_cancel = false; + uint32_t event_serial = ++th->event_serial; // overflow is okay + if (ubf_set(th, ubf_event_waiting, (void *)th)) { return false; } thread_sched_lock(sched, th); { - if (timer_thread_register_waiting(th, fd, events, rel)) { + if (timer_thread_register_waiting(th, fd, events, rel, event_serial)) { RUBY_DEBUG_LOG("wait fd:%d", fd); RB_VM_SAVE_MACHINE_CONTEXT(th); @@ -81,9 +81,11 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); if (th->sched.waiting_reason.flags == thread_sched_waiting_none) { - // already awaken + th->event_serial++; + // timer thread has dequeued us already, but it won't try to wake us because we bumped our serial } else if (RUBY_VM_INTERRUPTED(th->ec)) { + th->event_serial++; // make sure timer thread doesn't try to wake us need_cancel = true; } else { @@ -111,7 +113,8 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, } thread_sched_unlock(sched, th); - ubf_clear(th); // TODO: maybe it is already NULL? + // if ubf triggered between sched unlock and ubf clear, sched->running == th here + ubf_clear(th); VM_ASSERT(sched->running == th); @@ -680,7 +683,7 @@ kqueue_already_registered(int fd) // return false if the fd is not waitable or not need to wait. static bool -timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel) +timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial) { RUBY_DEBUG_LOG("th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (unsigned long)*rel : 0); @@ -807,6 +810,7 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting th->sched.waiting_reason.data.timeout = abs; th->sched.waiting_reason.data.fd = fd; th->sched.waiting_reason.data.result = 0; + th->sched.waiting_reason.data.event_serial = event_serial; } if (abs == 0) { // no timeout @@ -855,6 +859,10 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting static void timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags) { + if (!(th->sched.waiting_reason.flags & (thread_sched_waiting_io_read | thread_sched_waiting_io_write))) { + return; + } + RUBY_DEBUG_LOG("th:%u fd:%d", rb_th_serial(th), fd); #if HAVE_SYS_EVENT_H kqueue_unregister_waiting(fd, flags); @@ -885,7 +893,7 @@ timer_thread_setup_mn(void) #endif RUBY_DEBUG_LOG("comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]); - timer_thread_register_waiting(NULL, timer_th.comm_fds[0], thread_sched_waiting_io_read | thread_sched_waiting_io_force, NULL); + timer_thread_register_waiting(NULL, timer_th.comm_fds[0], thread_sched_waiting_io_read | thread_sched_waiting_io_force, NULL, 0); } static int @@ -986,8 +994,9 @@ timer_thread_polling(rb_vm_t *vm) th->sched.waiting_reason.flags = thread_sched_waiting_none; th->sched.waiting_reason.data.fd = -1; th->sched.waiting_reason.data.result = filter; + uint32_t event_serial = th->sched.waiting_reason.data.event_serial; - timer_thread_wakeup_thread_locked(sched, th); + timer_thread_wakeup_thread_locked(sched, th, event_serial); } else { // already released @@ -1031,8 +1040,9 @@ timer_thread_polling(rb_vm_t *vm) th->sched.waiting_reason.flags = thread_sched_waiting_none; th->sched.waiting_reason.data.fd = -1; th->sched.waiting_reason.data.result = (int)events; + uint32_t event_serial = th->sched.waiting_reason.data.event_serial; - timer_thread_wakeup_thread_locked(sched, th); + timer_thread_wakeup_thread_locked(sched, th, event_serial); } else { // already released diff --git a/vm_core.h b/vm_core.h index 9d11457966ed71..09f27f6e859fd1 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1127,6 +1127,7 @@ typedef struct rb_thread_struct { struct rb_thread_sched_item sched; bool mn_schedulable; rb_atomic_t serial; // only for RUBY_DEBUG_LOG() + uint32_t event_serial; VALUE last_status; /* $? */ From 6d0c9598f5c62901e0bd9c39e34f0aa2fc7846ed Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 2 Dec 2025 19:59:15 +0000 Subject: [PATCH 1575/2435] [DOC] Cross-link between README pages --- README.ja.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.ja.md b/README.ja.md index 278285dc83db3c..9bbc3a83a5fdee 100644 --- a/README.ja.md +++ b/README.ja.md @@ -4,6 +4,8 @@ [![AppVeyor status](https://ci.appveyor.com/api/projects/status/0sy8rrxut4o0k960/branch/master?svg=true)](https://ci.appveyor.com/project/ruby/ruby/branch/master) [![Travis Status](https://app.travis-ci.com/ruby/ruby.svg?branch=master)](https://app.travis-ci.com/ruby/ruby) +[English](rdoc-ref:README.md) + # Rubyとは Rubyはシンプルかつ強力なオブジェクト指向スクリプト言語です. Rubyは純粋なオブジェクト指向言語として設計されているので, diff --git a/README.md b/README.md index 5ed312cca872a0..02435b419e026d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Actions Status: Windows](https://github.com/ruby/ruby/workflows/Windows/badge.svg)](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") [![Travis Status](https://app.travis-ci.com/ruby/ruby.svg?branch=master)](https://app.travis-ci.com/ruby/ruby) +[日本語](rdoc-ref:README.ja.md) + # What is Ruby? Ruby is an interpreted object-oriented programming language often From 412895ae84c204466f085d1aae6ca90db88e255a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Dec 2025 14:19:00 +0900 Subject: [PATCH 1576/2435] [ruby/rubygems] Try to split and run three runners for Windows I organized all examples the followings: ``` Total test time: 2468.41 seconds Total files: 168 Group A: 42 files, 617.08 seconds Group B: 42 files, 617.05 seconds Group C: 42 files, 617.14 seconds Group D: 42 files, 617.14 seconds ``` https://github.com/ruby/rubygems/commit/94d41e6c7c --- spec/bundler/spec_helper.rb | 10 ++ spec/bundler/support/windows_tag_group.rb | 185 ++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 spec/bundler/support/windows_tag_group.rb diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 96cd7be4720a66..f0d6bce006ef20 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -38,6 +38,7 @@ require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" +require_relative "support/windows_tag_group" $debug = false @@ -56,6 +57,7 @@ def self.ruby=(ruby) config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions + config.include Spec::WindowsTagGroup # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -129,4 +131,12 @@ def self.ruby=(ruby) ensure reset! end + + Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.each do |tag, file_paths| + file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") }) + + config.define_derived_metadata(file_path: file_pattern) do |metadata| + metadata[tag] = true + end + end end diff --git a/spec/bundler/support/windows_tag_group.rb b/spec/bundler/support/windows_tag_group.rb new file mode 100644 index 00000000000000..77112a2c6cd1ce --- /dev/null +++ b/spec/bundler/support/windows_tag_group.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +module Spec + module WindowsTagGroup + EXAMPLE_MAPPINGS = { + windows_a: [ + "spec/runtime/setup_spec.rb", + "spec/commands/install_spec.rb", + "spec/commands/add_spec.rb", + "spec/install/gems/compact_index_spec.rb", + "spec/commands/config_spec.rb", + "spec/commands/pristine_spec.rb", + "spec/install/gemfile/path_spec.rb", + "spec/update/git_spec.rb", + "spec/commands/open_spec.rb", + "spec/commands/remove_spec.rb", + "spec/commands/show_spec.rb", + "spec/plugins/source/example_spec.rb", + "spec/commands/console_spec.rb", + "spec/runtime/require_spec.rb", + "spec/runtime/env_helpers_spec.rb", + "spec/runtime/gem_tasks_spec.rb", + "spec/install/gemfile_spec.rb", + "spec/commands/fund_spec.rb", + "spec/commands/init_spec.rb", + "spec/bundler/ruby_dsl_spec.rb", + "spec/bundler/mirror_spec.rb", + "spec/bundler/source/git/git_proxy_spec.rb", + "spec/bundler/source_list_spec.rb", + "spec/bundler/plugin/installer_spec.rb", + "spec/bundler/friendly_errors_spec.rb", + "spec/resolver/platform_spec.rb", + "spec/bundler/fetcher/downloader_spec.rb", + "spec/update/force_spec.rb", + "spec/bundler/env_spec.rb", + "spec/install/gems/mirror_spec.rb", + "spec/install/failure_spec.rb", + "spec/bundler/yaml_serializer_spec.rb", + "spec/bundler/environment_preserver_spec.rb", + "spec/install/gemfile/install_if_spec.rb", + "spec/install/gems/gemfile_source_header_spec.rb", + "spec/bundler/fetcher/base_spec.rb", + "spec/bundler/rubygems_integration_spec.rb", + "spec/bundler/worker_spec.rb", + "spec/bundler/dependency_spec.rb", + "spec/bundler/ui_spec.rb", + "spec/bundler/plugin/source_list_spec.rb", + "spec/bundler/source/path_spec.rb", + ], + windows_b: [ + "spec/install/gemfile/git_spec.rb", + "spec/install/gems/standalone_spec.rb", + "spec/commands/lock_spec.rb", + "spec/cache/gems_spec.rb", + "spec/other/major_deprecation_spec.rb", + "spec/install/gems/dependency_api_spec.rb", + "spec/install/gemfile/gemspec_spec.rb", + "spec/plugins/install_spec.rb", + "spec/commands/binstubs_spec.rb", + "spec/install/gems/flex_spec.rb", + "spec/runtime/inline_spec.rb", + "spec/commands/post_bundle_message_spec.rb", + "spec/runtime/executable_spec.rb", + "spec/lock/git_spec.rb", + "spec/plugins/hook_spec.rb", + "spec/install/allow_offline_install_spec.rb", + "spec/install/gems/post_install_spec.rb", + "spec/install/gemfile/ruby_spec.rb", + "spec/install/security_policy_spec.rb", + "spec/install/yanked_spec.rb", + "spec/update/gemfile_spec.rb", + "spec/runtime/load_spec.rb", + "spec/plugins/command_spec.rb", + "spec/commands/version_spec.rb", + "spec/install/prereleases_spec.rb", + "spec/bundler/uri_credentials_filter_spec.rb", + "spec/bundler/plugin_spec.rb", + "spec/install/gems/mirror_probe_spec.rb", + "spec/plugins/list_spec.rb", + "spec/bundler/compact_index_client/parser_spec.rb", + "spec/bundler/gem_version_promoter_spec.rb", + "spec/other/cli_dispatch_spec.rb", + "spec/bundler/source/rubygems_spec.rb", + "spec/cache/platform_spec.rb", + "spec/update/gems/fund_spec.rb", + "spec/bundler/stub_specification_spec.rb", + "spec/bundler/retry_spec.rb", + "spec/bundler/installer/spec_installation_spec.rb", + "spec/bundler/spec_set_spec.rb", + "spec/quality_es_spec.rb", + "spec/bundler/index_spec.rb", + "spec/other/cli_man_pages_spec.rb", + ], + windows_c: [ + "spec/commands/newgem_spec.rb", + "spec/commands/exec_spec.rb", + "spec/commands/clean_spec.rb", + "spec/commands/platform_spec.rb", + "spec/cache/git_spec.rb", + "spec/install/gemfile/groups_spec.rb", + "spec/commands/cache_spec.rb", + "spec/commands/check_spec.rb", + "spec/commands/list_spec.rb", + "spec/install/path_spec.rb", + "spec/bundler/cli_spec.rb", + "spec/install/bundler_spec.rb", + "spec/install/git_spec.rb", + "spec/commands/doctor_spec.rb", + "spec/bundler/dsl_spec.rb", + "spec/install/gems/fund_spec.rb", + "spec/install/gems/env_spec.rb", + "spec/bundler/ruby_version_spec.rb", + "spec/bundler/definition_spec.rb", + "spec/install/gemfile/eval_gemfile_spec.rb", + "spec/plugins/source_spec.rb", + "spec/install/gems/dependency_api_fallback_spec.rb", + "spec/plugins/uninstall_spec.rb", + "spec/bundler/plugin/index_spec.rb", + "spec/bundler/bundler_spec.rb", + "spec/bundler/fetcher_spec.rb", + "spec/bundler/source/rubygems/remote_spec.rb", + "spec/bundler/lockfile_parser_spec.rb", + "spec/cache/cache_path_spec.rb", + "spec/bundler/source/git_spec.rb", + "spec/bundler/source_spec.rb", + "spec/commands/ssl_spec.rb", + "spec/bundler/fetcher/compact_index_spec.rb", + "spec/bundler/plugin/api_spec.rb", + "spec/bundler/endpoint_specification_spec.rb", + "spec/bundler/fetcher/index_spec.rb", + "spec/bundler/settings/validator_spec.rb", + "spec/bundler/build_metadata_spec.rb", + "spec/bundler/current_ruby_spec.rb", + "spec/bundler/installer/gem_installer_spec.rb", + "spec/bundler/cli_common_spec.rb", + "spec/bundler/ci_detector_spec.rb", + ], + windows_d: [ + "spec/commands/outdated_spec.rb", + "spec/commands/update_spec.rb", + "spec/lock/lockfile_spec.rb", + "spec/install/deploy_spec.rb", + "spec/install/gemfile/sources_spec.rb", + "spec/runtime/self_management_spec.rb", + "spec/install/gemfile/specific_platform_spec.rb", + "spec/commands/info_spec.rb", + "spec/install/gems/resolving_spec.rb", + "spec/install/gemfile/platform_spec.rb", + "spec/bundler/gem_helper_spec.rb", + "spec/install/global_cache_spec.rb", + "spec/runtime/platform_spec.rb", + "spec/update/gems/post_install_spec.rb", + "spec/install/gems/native_extensions_spec.rb", + "spec/install/force_spec.rb", + "spec/cache/path_spec.rb", + "spec/install/gemspecs_spec.rb", + "spec/commands/help_spec.rb", + "spec/bundler/shared_helpers_spec.rb", + "spec/bundler/settings_spec.rb", + "spec/resolver/basic_spec.rb", + "spec/install/gemfile/force_ruby_platform_spec.rb", + "spec/commands/licenses_spec.rb", + "spec/install/gemfile/lockfile_spec.rb", + "spec/bundler/fetcher/dependency_spec.rb", + "spec/quality_spec.rb", + "spec/bundler/remote_specification_spec.rb", + "spec/install/process_lock_spec.rb", + "spec/install/binstubs_spec.rb", + "spec/bundler/compact_index_client/updater_spec.rb", + "spec/bundler/ui/shell_spec.rb", + "spec/other/ext_spec.rb", + "spec/commands/issue_spec.rb", + "spec/update/path_spec.rb", + "spec/bundler/plugin/api/source_spec.rb", + "spec/install/gems/win32_spec.rb", + "spec/bundler/plugin/dsl_spec.rb", + "spec/runtime/requiring_spec.rb", + "spec/bundler/plugin/events_spec.rb", + "spec/bundler/resolver/candidate_spec.rb", + "spec/bundler/digest_spec.rb", + "spec/bundler/fetcher/gem_remote_fetcher_spec.rb", + ], + }.freeze + end +end From d105709f2b989c03261371306b08f8fc5ccaa680 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Dec 2025 06:33:34 +0900 Subject: [PATCH 1577/2435] [ruby/rubygems] Add before(:context) hook to warn when spec files are not assigned to any Windows runner group https://github.com/ruby/rubygems/commit/3ddb740969 --- spec/bundler/spec_helper.rb | 7 +++++++ spec/bundler/support/windows_tag_group.rb | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index f0d6bce006ef20..fad1d4ce3266d4 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -139,4 +139,11 @@ def self.ruby=(ruby) metadata[tag] = true end end + + config.before(:context) do |example| + metadata = example.class.metadata + if metadata[:type] != :aruba && metadata.keys.none? {|k| Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.keys.include?(k) } + warn "#{metadata[:file_path]} is not assigned to any Windows runner group. see spec/support/windows_tag_group.rb for details." + end + end end diff --git a/spec/bundler/support/windows_tag_group.rb b/spec/bundler/support/windows_tag_group.rb index 77112a2c6cd1ce..8eb0a749dafbaf 100644 --- a/spec/bundler/support/windows_tag_group.rb +++ b/spec/bundler/support/windows_tag_group.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +# This group classifies test files into 4 groups by running `bin/rspec --profile 10000` +# to ensure balanced execution times. When adding new test files, it is recommended to +# re-aggregate and adjust the groups to keep them balanced. +# For now, please add new files to group 'windows_d'. + module Spec module WindowsTagGroup EXAMPLE_MAPPINGS = { From 02cddcc2be3150fad1da37ebdeb6e2d9ec29306c Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 02:48:05 +0900 Subject: [PATCH 1578/2435] Ractor.shareable_proc(&pr) should copy pr `pr` should not change on this method. --- bootstraptest/test_ractor.rb | 10 ++++++++++ ractor.c | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 7d09ac52838f66..bec742da4be0d5 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -157,6 +157,16 @@ Ractor.shareable_proc(&closure).call } +# Ractor.make_shareable makes a copy of given Proc +assert_equal '[true, true]', %q{ + pr1 = Proc.new do + self + end + pr2 = Ractor.shareable_proc(&pr1) + + [pr1.call == self, pr2.call == nil] +} + # Ractor::IsolationError cases assert_equal '3', %q{ ok = 0 diff --git a/ractor.c b/ractor.c index b7c7d9467924b6..9fec70a1dfab71 100644 --- a/ractor.c +++ b/ractor.c @@ -2376,7 +2376,7 @@ ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_la } else { VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc(); - return rb_proc_ractor_make_shareable(proc, replace_self); + return rb_proc_ractor_make_shareable(rb_proc_dup(proc), replace_self); } } From d3a9a17b6f908a64d8cf7b35c5e7337be3fc7cc5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Dec 2025 11:00:17 +0900 Subject: [PATCH 1579/2435] Skip Windows runner group warning under ruby/ruby repo --- spec/bundler/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index fad1d4ce3266d4..a79e33fbb01684 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -145,5 +145,5 @@ def self.ruby=(ruby) if metadata[:type] != :aruba && metadata.keys.none? {|k| Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.keys.include?(k) } warn "#{metadata[:file_path]} is not assigned to any Windows runner group. see spec/support/windows_tag_group.rb for details." end - end + end unless Spec::Path.ruby_core? end From e822209d3e3dfde39a7d6818951869606ae8cbf0 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 5 Dec 2025 14:16:25 +0900 Subject: [PATCH 1580/2435] Update NEWS.md for Ruby 4.0.0 (#15369) I extracted the relevant descriptions from the draft NEWS.md that hsbt-san had Gemini generate by analyzing `git log`. Co-authored-by: Hiroshi SHIBATA Co-authored-by: Alan Wu Co-authored-by: Jeremy Evans Co-Authored-By: Earlopain <14981592+Earlopain@users.noreply.github.com> --- NEWS.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 89f45deb99cf7b..84f8eaaaac2116 100644 --- a/NEWS.md +++ b/NEWS.md @@ -64,6 +64,12 @@ Note: We're only listing outstanding class updates. Also, `Binding#local_variable_get` and `Binding#local_variable_set` reject to handle numbered parameters. [[Bug #21049]] +* File + + * `File::Stat#birthtime` is now available on Linux via the statx + system call when supported by the kernel and filesystem. + [[Feature #21205]] + * IO * `IO.select` accepts `Float::INFINITY` as a timeout argument. @@ -76,10 +82,20 @@ Note: We're only listing outstanding class updates. * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] -* Socket +* Method - * `Socket.tcp` & `TCPSocket.new` accepts `open_timeout` as a keyword argument to specify - the timeout for the initial connection. [[Feature #21347]] + * `Method#source_location`, `Proc#source_location`, and + `UnboundMethod#source_location` now return extended location + information with 5 elements: `[path, start_line, start_column, + end_line, end_column]`. The previous 2-element format `[path, + line]` can still be obtained by calling `.take(2)` on the result. + [[Feature #6012]] + +* Proc + + * `Proc#parameters` now shows anonymous optional parameters as `[:opt]` + instead of `[:opt, nil]`, making the output consistent with when the + anonymous parameter is required. [[Bug #20974]] * Ractor @@ -127,11 +143,41 @@ Note: We're only listing outstanding class updates. to make shareable Proc or lambda. [[Feature #21550]], [[Feature #21557]] +* Range + + * `Range#to_set` and `Enumerator#to_set` now perform size checks to prevent + issues with endless ranges. [[Bug #21654]] + + * `Range#overlap?` now correctly handles infinite (unbounded) ranges. + [[Bug #21185]] + + * `Range#max` behavior on beginless integer ranges has been fixed. + [[Bug #21174]] [[Bug #21175]] + +* Ruby + + * A new toplevel module `Ruby` has been defined, which contains + Ruby-related constants. This module was reserved in Ruby 3.4 + and is now officially defined. [[Feature #20884]] + * `Set` * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] + * `Set#inspect` now returns a string suitable for `eval`, using the + `Set[]` syntax (e.g., `Set[1, 2, 3]` instead of + `#`). This makes it consistent with other core + collection classes like Array and Hash. [[Feature #21389]] + + * Passing arguments to `Set#to_set` and `Enumerable#to_set` is now deprecated. + [[Feature #21390]] + +* Socket + + * `Socket.tcp` & `TCPSocket.new` accepts an `open_timeout` keyword argument to specify + the timeout for the initial connection. [[Feature #21347]] + * String * Update Unicode to Version 17.0.0 and Emoji Version 17.0. @@ -240,6 +286,11 @@ The following bundled gems are updated. ## Supported platforms +* Windows + + * Dropped support for MSVC versions older than 14.0 (_MSC_VER 1900). + This means Visual Studio 2015 or later is now required. + ## Compatibility issues * The following methods were removed from Ractor due to the addition of `Ractor::Port`: @@ -253,6 +304,14 @@ The following bundled gems are updated. * `ObjectSpace._id2ref` is deprecated. [[Feature #15408]] +* `Process::Status#&` and `Process::Status#>>` have been removed. + They were deprecated in Ruby 3.3. [[Bug #19868]] + +* `rb_path_check` has been removed. This function was used for + `$SAFE` path checking which was removed in Ruby 2.7, + and was already deprecated,. + [[Feature #20971]] + ## Stdlib compatibility issues * CGI library is removed from the default gems. Now we only provide `cgi/escape` for @@ -320,17 +379,26 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * `--rjit` is removed. We will move the implementation of the third-party JIT API to the [ruby/rjit](https://github.com/ruby/rjit) repository. +[Feature #6012]: https://bugs.ruby-lang.org/issues/6012 [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 [Feature #19630]: https://bugs.ruby-lang.org/issues/19630 +[Bug #19868]: https://bugs.ruby-lang.org/issues/19868 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 +[Feature #20884]: https://bugs.ruby-lang.org/issues/20884 [Feature #20925]: https://bugs.ruby-lang.org/issues/20925 +[Feature #20971]: https://bugs.ruby-lang.org/issues/20971 +[Bug #20974]: https://bugs.ruby-lang.org/issues/20974 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 [Bug #21049]: https://bugs.ruby-lang.org/issues/21049 [Feature #21166]: https://bugs.ruby-lang.org/issues/21166 +[Bug #21174]: https://bugs.ruby-lang.org/issues/21174 +[Bug #21175]: https://bugs.ruby-lang.org/issues/21175 +[Bug #21185]: https://bugs.ruby-lang.org/issues/21185 +[Feature #21205]: https://bugs.ruby-lang.org/issues/21205 [Feature #21216]: https://bugs.ruby-lang.org/issues/21216 [Feature #21219]: https://bugs.ruby-lang.org/issues/21219 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 @@ -339,6 +407,9 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 +[Feature #21389]: https://bugs.ruby-lang.org/issues/21389 +[Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 +[Bug #21654]: https://bugs.ruby-lang.org/issues/21654 From 5f6b31c23ff1eefdf3cd5807ce5f80abf24eb1b2 Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Fri, 5 Dec 2025 06:20:39 +0100 Subject: [PATCH 1581/2435] Correctly handle `Process.fork` with an active `Fiber.scheduler`. (#15385) In the child process, nullify the current fiber scheduler and set the current fiber to blocking. --- cont.c | 2 ++ test/fiber/test_scheduler.rb | 57 ++++++++++++++++++++++++++++++++++++ thread.c | 1 + 3 files changed, 60 insertions(+) diff --git a/cont.c b/cont.c index 50e8ffb3498ec4..acfcefc81e89f2 100644 --- a/cont.c +++ b/cont.c @@ -3371,6 +3371,8 @@ rb_fiber_atfork(rb_thread_t *th) th->root_fiber = th->ec->fiber_ptr; } th->root_fiber->prev = 0; + th->root_fiber->blocking = 1; + th->blocking = 1; } } #endif diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 7c77bd8cf0dba4..97ce94bd270b69 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -226,4 +226,61 @@ def test_condition_variable thread.join assert_kind_of RuntimeError, error end + + def test_post_fork_scheduler_reset + omit 'fork not supported' unless Process.respond_to?(:fork) + + forked_scheduler_state = nil + thread = Thread.new do + r, w = IO.pipe + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + forked_pid = fork do + r.close + w << (Fiber.scheduler ? 'set' : 'reset') + w.close + end + w.close + forked_scheduler_state = r.read + Process.wait(forked_pid) + ensure + r.close rescue nil + w.close rescue nil + end + thread.join + assert_equal 'reset', forked_scheduler_state + ensure + thread.kill rescue nil + end + + def test_post_fork_fiber_blocking + omit 'fork not supported' unless Process.respond_to?(:fork) + + fiber_blocking_state = nil + thread = Thread.new do + r, w = IO.pipe + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + forked_pid = nil + Fiber.schedule do + forked_pid = fork do + r.close + w << (Fiber.current.blocking? ? 'blocking' : 'nonblocking') + w.close + end + end + w.close + fiber_blocking_state = r.read + Process.wait(forked_pid) + ensure + r.close rescue nil + w.close rescue nil + end + thread.join + assert_equal 'blocking', fiber_blocking_state + ensure + thread.kill rescue nil + end end diff --git a/thread.c b/thread.c index 6e5c8e89ff075a..5f592a42562adf 100644 --- a/thread.c +++ b/thread.c @@ -4997,6 +4997,7 @@ rb_thread_atfork(void) rb_threadptr_pending_interrupt_clear(th); rb_thread_atfork_internal(th, terminate_atfork_i); th->join_list = NULL; + th->scheduler = Qnil; rb_fiber_atfork(th); /* We don't want reproduce CVE-2003-0900. */ From 479185daa140f9d5894e9c254f2f881d36df1ddc Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 4 Dec 2025 21:52:55 -0800 Subject: [PATCH 1582/2435] Add Set C API to news Also, don't use backticks around Set in the top level of the Core classes updates section, as other classes/modules do not use that format. --- NEWS.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 84f8eaaaac2116..53ed47cb8c3265 100644 --- a/NEWS.md +++ b/NEWS.md @@ -160,7 +160,7 @@ Note: We're only listing outstanding class updates. Ruby-related constants. This module was reserved in Ruby 3.4 and is now officially defined. [[Feature #20884]] -* `Set` +* Set * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] @@ -342,6 +342,20 @@ The following bundled gems are updated. `IO` objects share the same file descriptor, closing one does not affect the other. [[Feature #18455]] +* Set + + * A C API for `Set` has been added. The following methods are supported: + [[Feature #21459]] + + * `rb_set_foreach` + * `rb_set_new` + * `rb_set_new_capa` + * `rb_set_lookup` + * `rb_set_add` + * `rb_set_clear` + * `rb_set_delete` + * `rb_set_size` + ## Implementation improvements ### Ractor @@ -409,6 +423,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 [Feature #21389]: https://bugs.ruby-lang.org/issues/21389 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 +[Feature #21459]: https://bugs.ruby-lang.org/issues/21459 [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 From b35aff5813193a1f676bb1ff7b390797a892ad4e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 12:33:03 +0900 Subject: [PATCH 1583/2435] [DOC] Centerize Variable, English, and Constant columns --- doc/language/globals.md | 106 ++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index b9315f5ff975e4..221ad17e44e0c3 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -14,70 +14,70 @@ require 'English' ### Exceptions -| Variable | English | Contains | -|-------------|-------------------|----------------------------------------------------| -| `$!` | `$ERROR_INFO` | Exception object; set by Kernel#raise. | -| `$@` | `$ERROR_POSITION` | Array of backtrace positions; set by Kernel#raise. | +| Variable | English | Contains | +|:--------:|:-----------------:|----------------------------------------------------| +| `$!` | `$ERROR_INFO` | Exception object; set by Kernel#raise. | +| `$@` | `$ERROR_POSITION` | Array of backtrace positions; set by Kernel#raise. | ### Pattern Matching -| Variable | English | Contains | -|---------------|---------------------|--------------------------------------------------| -| `$~` | `$LAST_MATCH_INFO` | MatchData object; set by matcher method. | -| `$&` | `$MATCH` | Matched substring; set by matcher method. | -| `` $` `` | `$PRE_MATCH` | Substring left of match; set by matcher method. | -| `$'` | `$POST_MATCH` | Substring right of match; set by matcher method. | -| `$+` | `$LAST_PAREN_MATCH` | Last group matched; set by matcher method. | -| `$1` | | First group matched; set by matcher method. | -| `$2` | | Second group matched; set by matcher method. | +| Variable | English | Contains | +|:-------------:|:-------------------:|--------------------------------------------------| +| `$~` | `$LAST_MATCH_INFO` | MatchData object; set by matcher method. | +| `$&` | `$MATCH` | Matched substring; set by matcher method. | +| `` $` `` | `$PRE_MATCH` | Substring left of match; set by matcher method. | +| `$'` | `$POST_MATCH` | Substring right of match; set by matcher method. | +| `$+` | `$LAST_PAREN_MATCH` | Last group matched; set by matcher method. | +| `$1` | | First group matched; set by matcher method. | +| `$2` | | Second group matched; set by matcher method. | | $_n_ | | nth group matched; set by matcher method. | ### Separators -| Variable | English | Contains | -|----------|----------------------------|--------------------------------------------| -| `$/` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | -| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | +| Variable | English | Contains | +|:--------:|:--------------------------:|--------------------------------------------| +| `$/` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | +| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | ### Streams -| Variable | English | Contains | -|-----------|-----------------------------|-----------------------------------------------| +| Variable | English | Contains | +|:---------:|:---------------------------:|-----------------------------------------------| | `$stdin` | | Standard input stream; initially `STDIN`. | | `$stdout` | | Standard input stream; initially `STDIOUT`. | | `$stderr` | | Standard input stream; initially `STDERR`. | -| `$<` | `$DEFAULT_INPUT` | Default standard input; `ARGF` or `$stdin`. | -| `$>` | `$DEFAULT_OUTPUT` | Default standard output; initially `$stdout`. | -| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream. | -| `$_` | `$LAST_READ_LINE` | String from most recently read stream. | +| `$<` | `$DEFAULT_INPUT` | Default standard input; `ARGF` or `$stdin`. | +| `$>` | `$DEFAULT_OUTPUT` | Default standard output; initially `$stdout`. | +| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream. | +| `$_` | `$LAST_READ_LINE` | String from most recently read stream. | ### Processes -| Variable | English | Contains | -|---------------------------|-----------------------|--------------------------------------------------------| -| `$0` | | Initially, the name of the executing program. | -| `$*` | `$ARGV` | Points to the `ARGV` array. | -| `$$` | `$PROCESS_ID`, `$PID` | Process ID of the current process. | -| `$?` | `$CHILD_STATUS` | Process::Status of most recently exited child process. | +| Variable | English | Contains | +|:-------------------------:|:---------------------:|--------------------------------------------------------| +| `$0` | | Initially, the name of the executing program. | +| `$*` | `$ARGV` | Points to the `ARGV` array. | +| `$$` | `$PROCESS_ID`, `$PID` | Process ID of the current process. | +| `$?` | `$CHILD_STATUS` | Process::Status of most recently exited child process. | | `$LOAD_PATH`, `$:`, `$-I` | | Array of paths to be searched. | | `$LOADED_FEATURES`, `$"` | | Array of paths to loaded files. | ### Debugging -| Variable | English | Contains | -|-------------|---------|--------------------------------------------------------| +| Variable | English | Contains | +|:-----------:|:-------:|--------------------------------------------------------| | `$FILENAME` | | The value returned by method ARGF.filename. | -| `$DEBUG` | | Initially, whether option `-d` or `--debug` was given. | +| `$DEBUG` | | Initially, whether option `-d` or `--debug` was given. | | `$VERBOSE` | | Initially, whether option `-V` or `-W` was given. | ### Other Variables | Variable | English | Contains | -|----------|---------|------------------------------------------------| -| `$-a` | | Whether option `-a` was given. | -| `$-i` | | Extension given with command-line option `-i`. | -| `$-l` | | Whether option `-l` was given. | -| `$-p` | | Whether option `-p` was given. | +|:--------:|:-------:|------------------------------------------------| +| `$-a` | | Whether option `-a` was given. | +| `$-i` | | Extension given with command-line option `-i`. | +| `$-l` | | Whether option `-l` was given. | +| `$-p` | | Whether option `-p` was given. | ## Exceptions @@ -374,33 +374,33 @@ Whether command-line option `-p` was given; read-only. ### Streams | Constant | Contains | -|----------|-------------------------| +|:--------:|-------------------------| | `STDIN` | Standard input stream. | | `STDOUT` | Standard output stream. | | `STDERR` | Standard error stream. | ### Environment -| Constant | Contains | -|-----------------------|-------------------------------------------------------------------------------| -| `ENV` | Hash of current environment variable names and values. | -| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | -| `ARGV` | Array of the given command-line arguments. | -| `TOPLEVEL_BINDING` | Binding of the top level scope. | -| `RUBY_VERSION` | String Ruby version. | -| `RUBY_RELEASE_DATE` | String Ruby release date. | -| `RUBY_PLATFORM` | String Ruby platform. | -| `RUBY_PATCH_LEVEL` | String Ruby patch level. | -| `RUBY_REVISION` | String Ruby revision. | -| `RUBY_COPYRIGHT` | String Ruby copyright. | -| `RUBY_ENGINE` | String Ruby engine. | +| Constant | Contains | +|:---------------------:|-------------------------------------------------------------------------------| +| `ENV` | Hash of current environment variable names and values. | +| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | +| `ARGV` | Array of the given command-line arguments. | +| `TOPLEVEL_BINDING` | Binding of the top level scope. | +| `RUBY_VERSION` | String Ruby version. | +| `RUBY_RELEASE_DATE` | String Ruby release date. | +| `RUBY_PLATFORM` | String Ruby platform. | +| `RUBY_PATCH_LEVEL` | String Ruby patch level. | +| `RUBY_REVISION` | String Ruby revision. | +| `RUBY_COPYRIGHT` | String Ruby copyright. | +| `RUBY_ENGINE` | String Ruby engine. | | `RUBY_ENGINE_VERSION` | String Ruby engine version. | -| `RUBY_DESCRIPTION` | String Ruby description. | +| `RUBY_DESCRIPTION` | String Ruby description. | ### Embedded Data | Constant | Contains | -|----------|--------------------------------------------------------------------| +|:--------:|--------------------------------------------------------------------| | `DATA` | File containing embedded data (lines following `__END__`, if any). | ## Streams From 1e7373ef3061a3e0f3010ee580e5d4f80baf2e1a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 13:02:22 +0900 Subject: [PATCH 1584/2435] [DOC] Describe the global variables set by command line options These variables are set by command line options, but it is deprecated to assign them any value other than nil in ruby code. --- doc/language/globals.md | 43 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index 221ad17e44e0c3..7030cc1bfd7b0c 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -34,10 +34,10 @@ require 'English' ### Separators -| Variable | English | Contains | -|:--------:|:--------------------------:|--------------------------------------------| -| `$/` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | -| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | +| Variable | English | Contains | +|:-----------:|:--------------------------:|--------------------------------------------| +| `$/`, `$-0` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | +| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | ### Streams @@ -72,12 +72,13 @@ require 'English' ### Other Variables -| Variable | English | Contains | -|:--------:|:-------:|------------------------------------------------| -| `$-a` | | Whether option `-a` was given. | -| `$-i` | | Extension given with command-line option `-i`. | -| `$-l` | | Whether option `-l` was given. | -| `$-p` | | Whether option `-p` was given. | +| Variable | English | Contains | +|:-----------:|:-------:|------------------------------------------------| +| `$-F`, `$;` | | Separator given with command-line option `-F`. | +| `$-a` | | Whether option `-a` was given. | +| `$-i` | | Extension given with command-line option `-i`. | +| `$-l` | | Whether option `-l` was given. | +| `$-p` | | Whether option `-p` was given. | ## Exceptions @@ -174,6 +175,10 @@ No \English. ### `$/` (Input Record Separator) An input record separator, initially newline. +Set by the command-line option `-0`. + +Setting to non-nil value by other than the command-line option is +deprecated. English - `$INPUT_RECORD_SEPARATOR`, `$RS`. @@ -182,6 +187,10 @@ Aliased as `$-0`. ### `$\` (Output Record Separator) An output record separator, initially `nil`. +Copied from `$/` when the command-line option `-l` is given. + +Setting to non-nil value by other than the command-line option is +deprecated. English - `$OUTPUT_RECORD_SEPARATOR`, `$ORS`. @@ -340,6 +349,16 @@ Aliased as `$-v` and `$-w`. ## Other Variables +### `$-F` + +The default field separator in String#split; must be a String or a +Regexp, and can be set with command-line option `-F`. + +Setting to non-nil value by other than the command-line option is +deprecated. + +Aliased as `$;`. + ### `$-a` Whether command-line option `-a` was given; read-only. @@ -365,8 +384,6 @@ Whether command-line option `-p` was given; read-only. ### `$,` -### `$;` - # Pre-Defined Global Constants ## Summary @@ -401,7 +418,7 @@ Whether command-line option `-p` was given; read-only. | Constant | Contains | |:--------:|--------------------------------------------------------------------| -| `DATA` | File containing embedded data (lines following `__END__`, if any). | +| `DATA` | File containing embedded data (lines following `__END__`, if any). | ## Streams From 1cad20e2d5179b283cbb1aabe2496449f8edcbcb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 16:12:49 +0900 Subject: [PATCH 1585/2435] [DOC] Describe `$F` This variation is used when `-a` option is given. --- doc/language/globals.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/language/globals.md b/doc/language/globals.md index 7030cc1bfd7b0c..b22363f1da1b71 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -79,6 +79,7 @@ require 'English' | `$-i` | | Extension given with command-line option `-i`. | | `$-l` | | Whether option `-l` was given. | | `$-p` | | Whether option `-p` was given. | +| `$F` | | Array of `$_` split by `$-F`. | ## Exceptions @@ -378,6 +379,12 @@ Whether command-line option `-l` was set; read-only. Whether command-line option `-p` was given; read-only. +### `$F` + +If the command line option `-a` is given, the array obtained by +splitting `$_` by `$-F` is assigned at the start of each `-l`/`-p` +loop. + ## Deprecated ### `$=` From 95ea3bd5ee9abc9e32c68e6a4fe2d795d853d907 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 12:32:35 +0100 Subject: [PATCH 1586/2435] [ruby/timeout] Only the timeout method should be public on the Timeout module https://github.com/ruby/timeout/commit/cd51eac3ca --- lib/timeout.rb | 10 +++++++--- test/test_timeout.rb | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index e1f0a4a78c6f59..f173d028a39d0a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -133,6 +133,7 @@ def self.ensure_timeout_thread_created end end end + private_class_method :ensure_timeout_thread_created # We keep a private reference so that time mocking libraries won't break # Timeout. @@ -167,7 +168,7 @@ def self.ensure_timeout_thread_created # Note that this is both a method of module Timeout, so you can include # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). - def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ + def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec @@ -177,7 +178,7 @@ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return scheduler.timeout_after(sec, klass || Error, message, &block) end - Timeout.ensure_timeout_thread_created + ensure_timeout_thread_created perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) QUEUE_MUTEX.synchronize do @@ -197,5 +198,8 @@ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ Error.handle_timeout(message, &perform) end end - module_function :timeout + + private def timeout(*args, &block) + Timeout.timeout(*args, &block) + end end diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 01156867b05609..e367df757cc9b2 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -4,6 +4,12 @@ class TestTimeout < Test::Unit::TestCase + def test_public_methods + assert_equal [:timeout], Timeout.private_instance_methods(false) + assert_equal [], Timeout.public_instance_methods(false) + assert_equal [:timeout], Timeout.singleton_class.public_instance_methods(false) + end + def test_work_is_done_in_same_thread_as_caller assert_equal Thread.current, Timeout.timeout(10){ Thread.current } end From bf2b9c09473ee1a49766c6d7c6e1d683236a13ee Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 5 Dec 2025 02:52:38 +0900 Subject: [PATCH 1587/2435] [ruby/openssl] asn1: reorder declarations Move variable declarations for OpenSSL::ASN1 classes to the top of the file. asn1time_to_time() will need eASN1Error in the next patch. https://github.com/ruby/openssl/commit/6c0ef87897 --- ext/openssl/ossl_asn1.c | 92 ++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index cb13ac6ecf876d..91acf5c6522740 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -9,9 +9,48 @@ */ #include "ossl.h" -static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, - int depth, int yield, long *num_read); -static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); +/********/ +/* + * ASN1 module + */ +#define ossl_asn1_get_value(o) rb_attr_get((o),sivVALUE) +#define ossl_asn1_get_tag(o) rb_attr_get((o),sivTAG) +#define ossl_asn1_get_tagging(o) rb_attr_get((o),sivTAGGING) +#define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) +#define ossl_asn1_get_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_LENGTH) + +#define ossl_asn1_set_value(o,v) rb_ivar_set((o),sivVALUE,(v)) +#define ossl_asn1_set_tag(o,v) rb_ivar_set((o),sivTAG,(v)) +#define ossl_asn1_set_tagging(o,v) rb_ivar_set((o),sivTAGGING,(v)) +#define ossl_asn1_set_tag_class(o,v) rb_ivar_set((o),sivTAG_CLASS,(v)) +#define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) + +VALUE mASN1; +static VALUE eASN1Error; + +VALUE cASN1Data; +static VALUE cASN1Primitive; +static VALUE cASN1Constructive; + +static VALUE cASN1EndOfContent; +static VALUE cASN1Boolean; /* BOOLEAN */ +static VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ +static VALUE cASN1BitString; /* BIT STRING */ +static VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ +static VALUE cASN1NumericString, cASN1PrintableString; +static VALUE cASN1T61String, cASN1VideotexString; +static VALUE cASN1IA5String, cASN1GraphicString; +static VALUE cASN1ISO64String, cASN1GeneralString; +static VALUE cASN1UniversalString, cASN1BMPString; +static VALUE cASN1Null; /* NULL */ +static VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ +static VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ +static VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ + +static VALUE sym_IMPLICIT, sym_EXPLICIT; +static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; +static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; +static ID id_each; /* * DATE conversion @@ -190,49 +229,6 @@ ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *obj) return ossl_asn1obj_to_string_oid(obj); } -/********/ -/* - * ASN1 module - */ -#define ossl_asn1_get_value(o) rb_attr_get((o),sivVALUE) -#define ossl_asn1_get_tag(o) rb_attr_get((o),sivTAG) -#define ossl_asn1_get_tagging(o) rb_attr_get((o),sivTAGGING) -#define ossl_asn1_get_tag_class(o) rb_attr_get((o),sivTAG_CLASS) -#define ossl_asn1_get_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_LENGTH) - -#define ossl_asn1_set_value(o,v) rb_ivar_set((o),sivVALUE,(v)) -#define ossl_asn1_set_tag(o,v) rb_ivar_set((o),sivTAG,(v)) -#define ossl_asn1_set_tagging(o,v) rb_ivar_set((o),sivTAGGING,(v)) -#define ossl_asn1_set_tag_class(o,v) rb_ivar_set((o),sivTAG_CLASS,(v)) -#define ossl_asn1_set_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) - -VALUE mASN1; -static VALUE eASN1Error; - -VALUE cASN1Data; -static VALUE cASN1Primitive; -static VALUE cASN1Constructive; - -static VALUE cASN1EndOfContent; -static VALUE cASN1Boolean; /* BOOLEAN */ -static VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ -static VALUE cASN1BitString; /* BIT STRING */ -static VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ -static VALUE cASN1NumericString, cASN1PrintableString; -static VALUE cASN1T61String, cASN1VideotexString; -static VALUE cASN1IA5String, cASN1GraphicString; -static VALUE cASN1ISO64String, cASN1GeneralString; -static VALUE cASN1UniversalString, cASN1BMPString; -static VALUE cASN1Null; /* NULL */ -static VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ -static VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ -static VALUE cASN1Sequence, cASN1Set; /* CONSTRUCTIVE */ - -static VALUE sym_IMPLICIT, sym_EXPLICIT; -static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; -static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; -static ID id_each; - /* * Ruby to ASN1 converters */ @@ -777,6 +773,10 @@ ossl_asn1data_to_der(VALUE self) } } +static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); +static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, + int depth, int yield, long *num_read); + static VALUE int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, VALUE tc, long *num_read) From f179885d3c454c6a98c23b2a977480657bb0f676 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 28 Feb 2025 03:10:35 +0900 Subject: [PATCH 1588/2435] [ruby/openssl] asn1: use ASN1_TIME_to_tm() to decode UTCTime and GeneralizedTime The current logic relies on sscanf() and error checks are almost entirely missing. It also assumes that ASN1_STRING contents are NUL terminated, which is undocumented and not guaranteed for all valid ASN1_TIME objects. Switch to using ASN1_TIME_to_tm() added in OpenSSL 1.1.1. It is also supported by LibreSSL and AWS-LC. In the long term, we may want to replace ASN1_TIME_to_tm() with a hand-rolled decoder, since the function is intended for a specific use-case. It is too permissive for strict DER, yet still does not support all valid DER inputs and silently drops information such as fractional seconds. However, it handles everything that the current sscanf() code could handle. https://github.com/ruby/openssl/commit/73484f6794 --- ext/openssl/ossl_asn1.c | 72 ++++++++++++++------------------------- test/openssl/test_asn1.rb | 59 +++++++++++++++++++++++++------- 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 91acf5c6522740..b21a79484ccc4a 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -55,57 +55,35 @@ static ID id_each; /* * DATE conversion */ +static VALUE +time_utc_new(VALUE args) +{ + return rb_funcallv(rb_cTime, rb_intern("utc"), 6, (VALUE *)args); +} + +static VALUE +time_utc_new_rescue(VALUE args, VALUE exc) +{ + rb_raise(eASN1Error, "invalid time"); +} + VALUE asn1time_to_time(const ASN1_TIME *time) { struct tm tm; - VALUE argv[6]; - int count; - - memset(&tm, 0, sizeof(struct tm)); - - switch (time->type) { - case V_ASN1_UTCTIME: - count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - - if (count == 5) { - tm.tm_sec = 0; - } else if (count != 6) { - ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", - time->data); - } - if (tm.tm_year < 50) { - tm.tm_year += 2000; - } else { - tm.tm_year += 1900; - } - break; - case V_ASN1_GENERALIZEDTIME: - count = sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, - &tm.tm_sec); - if (count == 5) { - tm.tm_sec = 0; - } - else if (count != 6) { - ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format: \"%s\"", - time->data); - } - break; - default: - rb_warning("unknown time format"); - return Qnil; - } - argv[0] = INT2NUM(tm.tm_year); - argv[1] = INT2NUM(tm.tm_mon); - argv[2] = INT2NUM(tm.tm_mday); - argv[3] = INT2NUM(tm.tm_hour); - argv[4] = INT2NUM(tm.tm_min); - argv[5] = INT2NUM(tm.tm_sec); - - return rb_funcall2(rb_cTime, rb_intern("utc"), 6, argv); + if (!ASN1_TIME_to_tm(time, &tm)) + ossl_raise(eASN1Error, "ASN1_TIME_to_tm"); + + VALUE args[] = { + INT2NUM(tm.tm_year + 1900), + INT2NUM(tm.tm_mon + 1), + INT2NUM(tm.tm_mday), + INT2NUM(tm.tm_hour), + INT2NUM(tm.tm_min), + INT2NUM(tm.tm_sec), + }; + return rb_rescue2(time_utc_new, (VALUE)args, time_utc_new_rescue, Qnil, + rb_eArgError, 0); } static VALUE diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index df8b0accb36c84..5978ecf6736e10 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -426,17 +426,28 @@ def test_utctime OpenSSL::ASN1::UTCTime.new(Time.new(2049, 12, 31, 23, 0, 0, "-04:00")).to_der } - # not implemented + # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it # decode_test B(%w{ 17 11 }) + "500908234339+0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30")) # decode_test B(%w{ 17 0F }) + "5009082343-0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 0, "-09:30")) - # assert_raise(OpenSSL::ASN1::ASN1Error) { - # OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b) - # } - # assert_raise(OpenSSL::ASN1::ASN1Error) { - # OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b) - # } + + # Seconds is omitted (BER) + # decode_test B(%w{ 18 0D }) + "201612081934Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0)) + + # Fractional seconds is not allowed in UTCTime + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 17 0F }) + "160908234339.5Z".b) + } + + # Missing "Z" + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b) + } + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b) + } end def test_generalizedtime @@ -444,24 +455,46 @@ def test_generalizedtime OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 29)) encode_decode_test B(%w{ 18 0F }) + "99990908234339Z".b, OpenSSL::ASN1::GeneralizedTime.new(Time.utc(9999, 9, 8, 23, 43, 39)) - # not implemented + + # Fractional seconds (DER). Not supported by ASN1_TIME_to_tm() + # because struct tm cannot store it. + # encode_decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5)) + + # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it # decode_test B(%w{ 18 13 }) + "20161208193439+0930".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 39, "+09:30")) # decode_test B(%w{ 18 11 }) + "201612081934-0930".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:30")) # decode_test B(%w{ 18 11 }) + "201612081934-09".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:00")) + + # Minutes and seconds are omitted (BER) + # decode_test B(%w{ 18 0B }) + "2016120819Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 0, 0)) + # Fractional hours (BER) # decode_test B(%w{ 18 0D }) + "2016120819.5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) + # Fractional hours with "," as the decimal separator (BER) # decode_test B(%w{ 18 0D }) + "2016120819,5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) + + # Seconds is omitted (BER) + # decode_test B(%w{ 18 0D }) + "201612081934Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0)) + # Fractional minutes (BER) # decode_test B(%w{ 18 0F }) + "201612081934.5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 30)) - # decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b, - # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5)) - # assert_raise(OpenSSL::ASN1::ASN1Error) { - # OpenSSL::ASN1.decode(B(%w{ 18 0D }) + "201612081934Y".b) - # } + + # Missing "Z" + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 18 0F }) + "20161208193429Y".b) + } + + # Encoding year out of range + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1::GeneralizedTime.new(Time.utc(10000, 9, 8, 23, 43, 39)).to_der + } end def test_basic_asn1data From ea415e9636d3be8890d5dc97cf0b67fc95403a46 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Thu, 4 Dec 2025 21:52:53 +0900 Subject: [PATCH 1589/2435] [ruby/net-http] open: Never call Timeout.timeout in rescue clause The try-open_timeout-then-fallback-to-timeout introduced in https://github.com/ruby/net-http/commit/1903cedd8cd0 works well, but when it errors due to any reason in Rubies which do not support `open_timeout`, it spits the rescued ArgumentError that is unrelated to user code and not actionable. Net::HTTP.start('foo.bar', 80) /.../net-http-0.8.0/lib/net/http.rb:1691:in 'TCPSocket#initialize': Failed to open TCP connection to foo.bar:80 (getaddrinfo(3): nodename nor servname provided, or not known) (Socket::ResolutionError) from /.../net-http-0.8.0/lib/net/http.rb:1691:in 'IO.open' from /.../net-http-0.8.0/lib/net/http.rb:1691:in 'block in Net::HTTP#connect' from /.../timeout-0.4.4/lib/timeout.rb:188:in 'block in Timeout.timeout' from /.../timeout-0.4.4/lib/timeout.rb:195:in 'Timeout.timeout' from /.../net-http-0.8.0/lib/net/http.rb:1690:in 'Net::HTTP#connect' from /.../net-http-0.8.0/lib/net/http.rb:1655:in 'Net::HTTP#do_start' from /.../net-http-0.8.0/lib/net/http.rb:1635:in 'Net::HTTP#start' from /.../net-http-0.8.0/lib/net/http.rb:1064:in 'Net::HTTP.start' (snip) /.../net-http-0.8.0/lib/net/http.rb:1682:in 'TCPSocket#initialize': unknown keyword: :open_timeout (ArgumentError) sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ from /.../net-http-0.8.0/lib/net/http.rb:1682:in 'IO.open' from /.../net-http-0.8.0/lib/net/http.rb:1682:in 'Net::HTTP#connect' from /.../net-http-0.8.0/lib/net/http.rb:1655:in 'Net::HTTP#do_start' from /.../net-http-0.8.0/lib/net/http.rb:1635:in 'Net::HTTP#start' from /.../net-http-0.8.0/lib/net/http.rb:1064:in 'Net::HTTP.start' (snip) ... 8 levels... This patch suppresses the ArgumentError by moving the retry out of the rescue clause. https://github.com/ruby/net-http/commit/86232d62f5 --- lib/net/http.rb | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 4205d414609bf0..8a4ff481872abc 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1674,30 +1674,7 @@ def connect debug "opening connection to #{conn_addr}:#{conn_port}..." begin - s = - case @tcpsocket_supports_open_timeout - when nil, true - begin - # Use built-in timeout in TCPSocket.open if available - sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) - @tcpsocket_supports_open_timeout = true - sock - rescue ArgumentError => e - raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) - @tcpsocket_supports_open_timeout = false - - # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout - Timeout.timeout(@open_timeout, Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } - end - when false - # The current Ruby is known to not support TCPSocket(open_timeout:). - # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue. - Timeout.timeout(@open_timeout, Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } - end + s = timeouted_connect(conn_addr, conn_port) rescue => e e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions raise e, "Failed to open TCP connection to " + @@ -1795,6 +1772,29 @@ def connect end private :connect + def timeouted_connect(conn_addr, conn_port) + if @tcpsocket_supports_open_timeout == nil || @tcpsocket_supports_open_timeout == true + # Try to use built-in open_timeout in TCPSocket.open if: + # - The current Ruby runtime is known to support it, or + # - It is unknown whether the current Ruby runtime supports it (so we'll try). + begin + sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + @tcpsocket_supports_open_timeout = true + return sock + rescue ArgumentError => e + raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) + @tcpsocket_supports_open_timeout = false + end + end + + # This Ruby runtime is known not to support `TCPSocket(open_timeout:)`. + # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue. + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + private :timeouted_connect + def on_connect end private :on_connect From b0c9286d98929db56514d8009040fe206b3d310f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Sun, 2 Nov 2025 21:13:37 -0800 Subject: [PATCH 1590/2435] Use VWA for bignum Previously we only allocated bignums from the 40 byte sizepool, and embedded bignum used a fixed size. --- bignum.c | 62 ++++++++++++++++++++++++++++++++-------- ext/-test-/bignum/depend | 12 ++++++++ internal/bignum.h | 25 +++++++++------- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/bignum.c b/bignum.c index 054b6c1cc9afe3..9e8d6a91f1004d 100644 --- a/bignum.c +++ b/bignum.c @@ -79,7 +79,6 @@ STATIC_ASSERT(sizeof_bdigit_and_dbl, SIZEOF_BDIGIT*2 <= SIZEOF_BDIGIT_DBL); STATIC_ASSERT(bdigit_signedness, 0 < (BDIGIT)-1); STATIC_ASSERT(bdigit_dbl_signedness, 0 < (BDIGIT_DBL)-1); STATIC_ASSERT(bdigit_dbl_signed_signedness, 0 > (BDIGIT_DBL_SIGNED)-1); -STATIC_ASSERT(rbignum_embed_len_max, BIGNUM_EMBED_LEN_MAX <= (BIGNUM_EMBED_LEN_MASK >> BIGNUM_EMBED_LEN_SHIFT)); #if SIZEOF_BDIGIT < SIZEOF_LONG STATIC_ASSERT(sizeof_long_and_sizeof_bdigit, SIZEOF_LONG % SIZEOF_BDIGIT == 0); @@ -2990,25 +2989,56 @@ rb_cmpint(VALUE val, VALUE a, VALUE b) ((l) << BIGNUM_EMBED_LEN_SHIFT)) : \ (void)(RBIGNUM(b)->as.heap.len = (l))) +static size_t +big_embed_capa(VALUE big) +{ + size_t size = rb_gc_obj_slot_size(big) - offsetof(struct RBignum, as.ary); + RUBY_ASSERT(size % sizeof(BDIGIT) == 0); + size_t capa = size / sizeof(BDIGIT); + RUBY_ASSERT(capa <= BIGNUM_EMBED_LEN_MAX); + return capa; +} + +static size_t +big_embed_size(size_t capa) +{ + size_t size = offsetof(struct RBignum, as.ary) + (sizeof(BDIGIT) * capa); + if (size < sizeof(struct RBignum)) { + size = sizeof(struct RBignum); + } + return size; +} + +static bool +big_embeddable_p(size_t capa) +{ + if (capa > BIGNUM_EMBED_LEN_MAX) { + return false; + } + return rb_gc_size_allocatable_p(big_embed_size(capa)); +} + static void rb_big_realloc(VALUE big, size_t len) { BDIGIT *ds; + size_t embed_capa = big_embed_capa(big); + if (BIGNUM_EMBED_P(big)) { - if (BIGNUM_EMBED_LEN_MAX < len) { + if (embed_capa < len) { ds = ALLOC_N(BDIGIT, len); - MEMCPY(ds, RBIGNUM(big)->as.ary, BDIGIT, BIGNUM_EMBED_LEN_MAX); + MEMCPY(ds, RBIGNUM(big)->as.ary, BDIGIT, embed_capa); RBIGNUM(big)->as.heap.len = BIGNUM_LEN(big); RBIGNUM(big)->as.heap.digits = ds; FL_UNSET_RAW(big, BIGNUM_EMBED_FLAG); } } else { - if (len <= BIGNUM_EMBED_LEN_MAX) { + if (len <= embed_capa) { ds = RBIGNUM(big)->as.heap.digits; FL_SET_RAW(big, BIGNUM_EMBED_FLAG); BIGNUM_SET_LEN(big, len); - (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)RBIGNUM(big)->as.ary, sizeof(RBIGNUM(big)->as.ary)); + (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)RBIGNUM(big)->as.ary, embed_capa * sizeof(BDIGIT)); if (ds) { MEMCPY(RBIGNUM(big)->as.ary, ds, BDIGIT, len); xfree(ds); @@ -3035,16 +3065,24 @@ rb_big_resize(VALUE big, size_t len) static VALUE bignew_1(VALUE klass, size_t len, int sign) { - NEWOBJ_OF(big, struct RBignum, klass, - T_BIGNUM | (RGENGC_WB_PROTECTED_BIGNUM ? FL_WB_PROTECTED : 0), sizeof(struct RBignum), 0); - VALUE bigv = (VALUE)big; - BIGNUM_SET_SIGN(bigv, sign); - if (len <= BIGNUM_EMBED_LEN_MAX) { - FL_SET_RAW(bigv, BIGNUM_EMBED_FLAG); + VALUE bigv; + + if (big_embeddable_p(len)) { + size_t size = big_embed_size(len); + RUBY_ASSERT(rb_gc_size_allocatable_p(size)); + NEWOBJ_OF(big, struct RBignum, klass, + T_BIGNUM | BIGNUM_EMBED_FLAG | (RGENGC_WB_PROTECTED_BIGNUM ? FL_WB_PROTECTED : 0), + size, 0); + bigv = (VALUE)big; + BIGNUM_SET_SIGN(bigv, sign); BIGNUM_SET_LEN(bigv, len); - (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)big->as.ary, sizeof(big->as.ary)); + (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)big->as.ary, len * sizeof(BDIGIT)); } else { + NEWOBJ_OF(big, struct RBignum, klass, + T_BIGNUM | (RGENGC_WB_PROTECTED_BIGNUM ? FL_WB_PROTECTED : 0), sizeof(struct RBignum), 0); + bigv = (VALUE)big; + BIGNUM_SET_SIGN(bigv, sign); big->as.heap.digits = ALLOC_N(BDIGIT, len); big->as.heap.len = len; } diff --git a/ext/-test-/bignum/depend b/ext/-test-/bignum/depend index 049f0c7b520970..82972f10327311 100644 --- a/ext/-test-/bignum/depend +++ b/ext/-test-/bignum/depend @@ -6,6 +6,7 @@ big2str.o: $(hdrdir)/ruby/backward.h big2str.o: $(hdrdir)/ruby/backward/2/assume.h big2str.o: $(hdrdir)/ruby/backward/2/attributes.h big2str.o: $(hdrdir)/ruby/backward/2/bool.h +big2str.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h big2str.o: $(hdrdir)/ruby/backward/2/inttypes.h big2str.o: $(hdrdir)/ruby/backward/2/limits.h big2str.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -159,6 +160,7 @@ big2str.o: $(hdrdir)/ruby/ruby.h big2str.o: $(hdrdir)/ruby/st.h big2str.o: $(hdrdir)/ruby/subst.h big2str.o: $(top_srcdir)/internal/bignum.h +big2str.o: $(top_srcdir)/internal/compilers.h big2str.o: big2str.c bigzero.o: $(RUBY_EXTCONF_H) bigzero.o: $(arch_hdrdir)/ruby/config.h @@ -167,6 +169,7 @@ bigzero.o: $(hdrdir)/ruby/backward.h bigzero.o: $(hdrdir)/ruby/backward/2/assume.h bigzero.o: $(hdrdir)/ruby/backward/2/attributes.h bigzero.o: $(hdrdir)/ruby/backward/2/bool.h +bigzero.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h bigzero.o: $(hdrdir)/ruby/backward/2/inttypes.h bigzero.o: $(hdrdir)/ruby/backward/2/limits.h bigzero.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -320,6 +323,7 @@ bigzero.o: $(hdrdir)/ruby/ruby.h bigzero.o: $(hdrdir)/ruby/st.h bigzero.o: $(hdrdir)/ruby/subst.h bigzero.o: $(top_srcdir)/internal/bignum.h +bigzero.o: $(top_srcdir)/internal/compilers.h bigzero.o: bigzero.c div.o: $(RUBY_EXTCONF_H) div.o: $(arch_hdrdir)/ruby/config.h @@ -328,6 +332,7 @@ div.o: $(hdrdir)/ruby/backward.h div.o: $(hdrdir)/ruby/backward/2/assume.h div.o: $(hdrdir)/ruby/backward/2/attributes.h div.o: $(hdrdir)/ruby/backward/2/bool.h +div.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h div.o: $(hdrdir)/ruby/backward/2/inttypes.h div.o: $(hdrdir)/ruby/backward/2/limits.h div.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -481,6 +486,7 @@ div.o: $(hdrdir)/ruby/ruby.h div.o: $(hdrdir)/ruby/st.h div.o: $(hdrdir)/ruby/subst.h div.o: $(top_srcdir)/internal/bignum.h +div.o: $(top_srcdir)/internal/compilers.h div.o: div.c init.o: $(RUBY_EXTCONF_H) init.o: $(arch_hdrdir)/ruby/config.h @@ -650,6 +656,7 @@ intpack.o: $(hdrdir)/ruby/backward.h intpack.o: $(hdrdir)/ruby/backward/2/assume.h intpack.o: $(hdrdir)/ruby/backward/2/attributes.h intpack.o: $(hdrdir)/ruby/backward/2/bool.h +intpack.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h intpack.o: $(hdrdir)/ruby/backward/2/inttypes.h intpack.o: $(hdrdir)/ruby/backward/2/limits.h intpack.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -803,6 +810,7 @@ intpack.o: $(hdrdir)/ruby/ruby.h intpack.o: $(hdrdir)/ruby/st.h intpack.o: $(hdrdir)/ruby/subst.h intpack.o: $(top_srcdir)/internal/bignum.h +intpack.o: $(top_srcdir)/internal/compilers.h intpack.o: intpack.c mul.o: $(RUBY_EXTCONF_H) mul.o: $(arch_hdrdir)/ruby/config.h @@ -811,6 +819,7 @@ mul.o: $(hdrdir)/ruby/backward.h mul.o: $(hdrdir)/ruby/backward/2/assume.h mul.o: $(hdrdir)/ruby/backward/2/attributes.h mul.o: $(hdrdir)/ruby/backward/2/bool.h +mul.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h mul.o: $(hdrdir)/ruby/backward/2/inttypes.h mul.o: $(hdrdir)/ruby/backward/2/limits.h mul.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -964,6 +973,7 @@ mul.o: $(hdrdir)/ruby/ruby.h mul.o: $(hdrdir)/ruby/st.h mul.o: $(hdrdir)/ruby/subst.h mul.o: $(top_srcdir)/internal/bignum.h +mul.o: $(top_srcdir)/internal/compilers.h mul.o: mul.c str2big.o: $(RUBY_EXTCONF_H) str2big.o: $(arch_hdrdir)/ruby/config.h @@ -972,6 +982,7 @@ str2big.o: $(hdrdir)/ruby/backward.h str2big.o: $(hdrdir)/ruby/backward/2/assume.h str2big.o: $(hdrdir)/ruby/backward/2/attributes.h str2big.o: $(hdrdir)/ruby/backward/2/bool.h +str2big.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h str2big.o: $(hdrdir)/ruby/backward/2/inttypes.h str2big.o: $(hdrdir)/ruby/backward/2/limits.h str2big.o: $(hdrdir)/ruby/backward/2/long_long.h @@ -1125,5 +1136,6 @@ str2big.o: $(hdrdir)/ruby/ruby.h str2big.o: $(hdrdir)/ruby/st.h str2big.o: $(hdrdir)/ruby/subst.h str2big.o: $(top_srcdir)/internal/bignum.h +str2big.o: $(top_srcdir)/internal/compilers.h str2big.o: str2big.c # AUTOGENERATED DEPENDENCIES END diff --git a/internal/bignum.h b/internal/bignum.h index 0ba21a492334f1..e5b6b425631f80 100644 --- a/internal/bignum.h +++ b/internal/bignum.h @@ -9,6 +9,7 @@ * @brief Internal header for Bignums. */ #include "ruby/internal/config.h" /* for HAVE_LIBGMP */ +#include "internal/compilers.h" /* for FLEX_ARY_LEN */ #include /* for size_t */ #ifdef HAVE_SYS_TYPES_H @@ -76,18 +77,17 @@ #define RBIGNUM(obj) ((struct RBignum *)(obj)) #define BIGNUM_SIGN_BIT FL_USER1 #define BIGNUM_EMBED_FLAG ((VALUE)FL_USER2) -#define BIGNUM_EMBED_LEN_NUMBITS 3 + +/* This is likely more bits than we need today and will also need adjustment if + * we change GC slot sizes. + */ +#define BIGNUM_EMBED_LEN_NUMBITS 9 #define BIGNUM_EMBED_LEN_MASK \ - (~(~(VALUE)0U << BIGNUM_EMBED_LEN_NUMBITS) << BIGNUM_EMBED_LEN_SHIFT) + (RUBY_FL_USER11 | RUBY_FL_USER10 | RUBY_FL_USER9 | RUBY_FL_USER8 | RUBY_FL_USER7 | \ + RUBY_FL_USER6 | RUBY_FL_USER5 | RUBY_FL_USER4 | RUBY_FL_USER3) #define BIGNUM_EMBED_LEN_SHIFT \ (FL_USHIFT+3) /* bit offset of BIGNUM_EMBED_LEN_MASK */ -#ifndef BIGNUM_EMBED_LEN_MAX -# if (SIZEOF_VALUE*RBIMPL_RVALUE_EMBED_LEN_MAX/SIZEOF_ACTUAL_BDIGIT) < (1 << BIGNUM_EMBED_LEN_NUMBITS)-1 -# define BIGNUM_EMBED_LEN_MAX (SIZEOF_VALUE*RBIMPL_RVALUE_EMBED_LEN_MAX/SIZEOF_ACTUAL_BDIGIT) -# else -# define BIGNUM_EMBED_LEN_MAX ((1 << BIGNUM_EMBED_LEN_NUMBITS)-1) -# endif -#endif +#define BIGNUM_EMBED_LEN_MAX (BIGNUM_EMBED_LEN_MASK >> BIGNUM_EMBED_LEN_SHIFT) enum rb_int_parse_flags { RB_INT_PARSE_SIGN = 0x01, @@ -104,7 +104,12 @@ struct RBignum { size_t len; BDIGIT *digits; } heap; - BDIGIT ary[BIGNUM_EMBED_LEN_MAX]; + /* This is a length 1 array because: + * 1. GCC has a bug that does not optimize C flexible array members + * (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102452) + * 2. Zero length arrays are not supported by all compilers + */ + BDIGIT ary[1]; } as; }; From 930afa1c7ba747658cc4253c2cb6a367d7a6a36b Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 3 Nov 2025 15:48:28 -0800 Subject: [PATCH 1591/2435] Never shrink bignum on realloc As far as I can tell, this only ever shrinks by one, and it's really not worth the expensive realloc for that. --- bignum.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bignum.c b/bignum.c index 9e8d6a91f1004d..8d3eac8e0a9173 100644 --- a/bignum.c +++ b/bignum.c @@ -3048,7 +3048,7 @@ rb_big_realloc(VALUE big, size_t len) if (BIGNUM_LEN(big) == 0) { RBIGNUM(big)->as.heap.digits = ALLOC_N(BDIGIT, len); } - else { + else if (BIGNUM_LEN(big) < len) { REALLOC_N(RBIGNUM(big)->as.heap.digits, BDIGIT, len); } } From 65dbd571c1e0aae3b0919ae6e64704726f18b265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 5 Dec 2025 15:33:44 +0100 Subject: [PATCH 1592/2435] [ruby/psych] Use Node#to_ruby parse_symbols option https://github.com/ruby/psych/commit/907fd4fa97 --- ext/psych/lib/psych/nodes/node.rb | 2 +- test/psych/visitors/test_to_ruby.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/psych/lib/psych/nodes/node.rb b/ext/psych/lib/psych/nodes/node.rb index f89c82b7f76df8..fc27448f2e5ec8 100644 --- a/ext/psych/lib/psych/nodes/node.rb +++ b/ext/psych/lib/psych/nodes/node.rb @@ -46,7 +46,7 @@ def each &block # # See also Psych::Visitors::ToRuby def to_ruby(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) - Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: true).accept(self) + Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, parse_symbols: parse_symbols).accept(self) end alias :transform :to_ruby diff --git a/test/psych/visitors/test_to_ruby.rb b/test/psych/visitors/test_to_ruby.rb index 89c367665191f9..c9b501dfa274db 100644 --- a/test/psych/visitors/test_to_ruby.rb +++ b/test/psych/visitors/test_to_ruby.rb @@ -328,6 +328,12 @@ def test_mapping_with_str_tag mapping.children << Nodes::Scalar.new('bar') assert_equal({'foo' => 'bar'}, mapping.to_ruby) end + + def test_parse_symbols + node = Nodes::Scalar.new(':foo') + assert_equal :foo, node.to_ruby + assert_equal ':foo', node.to_ruby(parse_symbols: false) + end end end end From ec28bd75a869c2e525388582370239a9ff8c19e7 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 01:49:31 +0900 Subject: [PATCH 1593/2435] [ruby/timeout] support Ractor 1. Introduce State to store all status. 2. Store State instance to the Ractor local storage if possible 3. Make `GET_TIME` (Method object) shareable if possible 3 is supporeted Ruby 4.0 and later, so the Rator support is works only on Ruby 4.0 and later. https://github.com/ruby/timeout/commit/54ff671c6c --- lib/timeout.rb | 166 ++++++++++++++++++++++++++++--------------- test/test_timeout.rb | 20 ++++++ 2 files changed, 127 insertions(+), 59 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index f173d028a39d0a..2bf3e7514653f6 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -44,12 +44,107 @@ def self.handle_timeout(message) # :nodoc: end # :stopdoc: - CONDVAR = ConditionVariable.new - QUEUE = Queue.new - QUEUE_MUTEX = Mutex.new - TIMEOUT_THREAD_MUTEX = Mutex.new - @timeout_thread = nil - private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX + + # We keep a private reference so that time mocking libraries won't break + # Timeout. + GET_TIME = + if defined?(Ractor.make_shareable) + begin + Ractor.make_shareable(Process.method(:clock_gettime)) + rescue # failed on Ruby 3.4 + Process.method(:clock_gettime) + end + else + Process.method(:clock_gettime) + end + + private_constant :GET_TIME + + class State + attr_reader :condvar, :queue, :queue_mutex # shared with Timeout.timeout() + + def initialize + @condvar = ConditionVariable.new + @queue = Queue.new + @queue_mutex = Mutex.new + + @timeout_thread = nil + @timeout_thread_mutex = Mutex.new + end + + if defined?(Ractor.store_if_absent) && + defined?(Ractor.shareble?) && Ractor.shareable?(GET_TIME) + + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + + Ractor.store_if_absent :timeout_gem_state do + State.new + end + + def self.instance + Ractor[:timeout_gem_state] + end + + ::Timeout::RACTOR_SUPPORT = true # for test + else + @GLOBAL_STATE = State.new + + def self.instance + @GLOBAL_STATE + end + end + + def create_timeout_thread + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline + + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end + end + + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) + end + end + + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end + + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end + + def ensure_timeout_thread_created + unless @timeout_thread&.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the Timeout thread. + return if @timeout_thread_mutex.owned? + + @timeout_thread_mutex.synchronize do + unless @timeout_thread&.alive? + @timeout_thread = create_timeout_thread + end + end + end + end + end + + private_constant :State class Request attr_reader :deadline @@ -91,55 +186,6 @@ def finished end private_constant :Request - def self.create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until QUEUE.empty? and !requests.empty? # wait to have at least one request - req = QUEUE.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline - - now = 0.0 - QUEUE_MUTEX.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? - CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) - end - end - - requests.each do |req| - req.interrupt if req.expired?(now) - end - requests.reject!(&:done?) - end - end - ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher - end - private_class_method :create_timeout_thread - - def self.ensure_timeout_thread_created - unless @timeout_thread and @timeout_thread.alive? - # If the Mutex is already owned we are in a signal handler. - # In that case, just return and let the main thread create the @timeout_thread. - return if TIMEOUT_THREAD_MUTEX.owned? - TIMEOUT_THREAD_MUTEX.synchronize do - unless @timeout_thread and @timeout_thread.alive? - @timeout_thread = create_timeout_thread - end - end - end - end - private_class_method :ensure_timeout_thread_created - - # We keep a private reference so that time mocking libraries won't break - # Timeout. - GET_TIME = Process.method(:clock_gettime) - private_constant :GET_TIME - # :startdoc: # Perform an operation in a block, raising an error if it takes longer than @@ -178,12 +224,14 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return scheduler.timeout_after(sec, klass || Error, message, &block) end - ensure_timeout_thread_created + state = State.instance + state.ensure_timeout_thread_created + perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - QUEUE_MUTEX.synchronize do - QUEUE << request - CONDVAR.signal + state.queue_mutex.synchronize do + state.queue << request + state.condvar.signal end begin return yield(sec) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index e367df757cc9b2..233f54eb82bf2d 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -280,4 +280,24 @@ def test_handling_enclosed_threadgroup }.join end; end + + def test_ractor + assert_separately(%w[-rtimeout -W0], <<-'end;') + r = Ractor.new do + Timeout.timeout(1) { 42 } + end.value + + assert_equal 42, r + + r = Ractor.new do + begin + Timeout.timeout(0.1) { sleep } + rescue Timeout::Error + :ok + end + end.value + + assert_equal :ok, r + end; + end if Timeout.const_defined?(:RACTOR_SUPPORT) end From a523e9d872d51e64eecbf3feeaa8d4d8769f72bd Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:14:28 +0100 Subject: [PATCH 1594/2435] [ruby/timeout] Minor tweaks https://github.com/ruby/timeout/commit/daab9a2193 --- lib/timeout.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 2bf3e7514653f6..3ad0193ac5f785 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -57,7 +57,6 @@ def self.handle_timeout(message) # :nodoc: else Process.method(:clock_gettime) end - private_constant :GET_TIME class State @@ -89,10 +88,10 @@ def self.instance ::Timeout::RACTOR_SUPPORT = true # for test else - @GLOBAL_STATE = State.new + GLOBAL_STATE = State.new def self.instance - @GLOBAL_STATE + GLOBAL_STATE end end @@ -143,7 +142,6 @@ def ensure_timeout_thread_created end end end - private_constant :State class Request From dc406af9cb9a269ae550483fa1278eedf297fb92 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:19:13 +0100 Subject: [PATCH 1595/2435] [ruby/timeout] Fix condition and fix test to catch that broken condition https://github.com/ruby/timeout/commit/82fb6f6925 --- lib/timeout.rb | 4 +--- test/test_timeout.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 3ad0193ac5f785..47410af386295c 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -72,7 +72,7 @@ def initialize end if defined?(Ractor.store_if_absent) && - defined?(Ractor.shareble?) && Ractor.shareable?(GET_TIME) + defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) # Ractor support if # 1. Ractor.store_if_absent is available @@ -85,8 +85,6 @@ def initialize def self.instance Ractor[:timeout_gem_state] end - - ::Timeout::RACTOR_SUPPORT = true # for test else GLOBAL_STATE = State.new diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 233f54eb82bf2d..3f94134fb04d9c 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -299,5 +299,5 @@ def test_ractor assert_equal :ok, r end; - end if Timeout.const_defined?(:RACTOR_SUPPORT) + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' end From 3e189ddb9db486d1bc7d5c15f395017b4fb0c136 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:21:37 +0100 Subject: [PATCH 1596/2435] [ruby/timeout] Fix logic for Ractor support * Fix indentation to stay a multiple of 2 spaces. https://github.com/ruby/timeout/commit/a1d784cb66 --- lib/timeout.rb | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 47410af386295c..eab1a1be9e19a4 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -45,8 +45,7 @@ def self.handle_timeout(message) # :nodoc: # :stopdoc: - # We keep a private reference so that time mocking libraries won't break - # Timeout. + # We keep a private reference so that time mocking libraries won't break Timeout. GET_TIME = if defined?(Ractor.make_shareable) begin @@ -71,20 +70,15 @@ def initialize @timeout_thread_mutex = Mutex.new end - if defined?(Ractor.store_if_absent) && - defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) - - # Ractor support if - # 1. Ractor.store_if_absent is available - # 2. Method object can be shareable (4.0~) - - Ractor.store_if_absent :timeout_gem_state do - State.new - end - - def self.instance - Ractor[:timeout_gem_state] - end + if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + def self.instance + Ractor.store_if_absent :timeout_gem_state do + State.new + end + end else GLOBAL_STATE = State.new From 00b91c727fdd0dd3bcd970dd4bc6c2b598cf4e1b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:25:22 +0100 Subject: [PATCH 1597/2435] [ruby/timeout] Simplify logic to make GET_TIME shareable https://github.com/ruby/timeout/commit/281b2507e7 --- lib/timeout.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index eab1a1be9e19a4..36cd0f915bc7ff 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -46,16 +46,11 @@ def self.handle_timeout(message) # :nodoc: # :stopdoc: # We keep a private reference so that time mocking libraries won't break Timeout. - GET_TIME = - if defined?(Ractor.make_shareable) - begin - Ractor.make_shareable(Process.method(:clock_gettime)) - rescue # failed on Ruby 3.4 - Process.method(:clock_gettime) - end - else - Process.method(:clock_gettime) - end + GET_TIME = Process.method(:clock_gettime) + if defined?(Ractor.make_shareable) + # Ractor.make_shareable(Method) only works on Ruby 4+ + Ractor.make_shareable(GET_TIME) rescue nil + end private_constant :GET_TIME class State From 8c4f79d5f30fb2fe647c4f3fd262a5fdeacaeca2 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 6 Dec 2025 03:33:12 +0900 Subject: [PATCH 1598/2435] [ruby/openssl] x509cert: handle invalid validity periods in Certificate#inspect In a newly allocated OpenSSL X509 object, the notBefore and notAfter fields contain an ASN1_STRING object with type V_ASN1_UNDEF rather than an ASN1_TIME. Commit https://github.com/ruby/openssl/commit/73484f67949a made asn1time_to_time() stricter and it now raises an exception if the argument is not an ASN1_TIME. Previously, it would print a verbose-mode warning and return nil. OpenSSL::X509::Certificate#inspect should work even when the certificate is invalid. Let's handle this. https://github.com/ruby/openssl/commit/18c283f2b6 --- ext/openssl/lib/openssl/x509.rb | 9 +++++++++ ext/openssl/ossl_x509cert.c | 15 --------------- test/openssl/test_x509cert.rb | 8 ++++++++ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/ext/openssl/lib/openssl/x509.rb b/ext/openssl/lib/openssl/x509.rb index 6459d37b12766e..66765ffeabf0dc 100644 --- a/ext/openssl/lib/openssl/x509.rb +++ b/ext/openssl/lib/openssl/x509.rb @@ -346,6 +346,15 @@ class Certificate include Extension::CRLDistributionPoints include Extension::AuthorityInfoAccess + def inspect + "#<#{self.class}: " \ + "subject=#{subject.inspect}, " \ + "issuer=#{issuer.inspect}, " \ + "serial=#{serial.inspect}, " \ + "not_before=#{not_before.inspect rescue "(error)"}, " \ + "not_after=#{not_after.inspect rescue "(error)"}>" + end + def pretty_print(q) q.object_group(self) { q.breakable diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index b1e82a2790adf6..4d69008fdd9a81 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -665,20 +665,6 @@ ossl_x509_add_extension(VALUE self, VALUE extension) return extension; } -static VALUE -ossl_x509_inspect(VALUE self) -{ - return rb_sprintf("#<%"PRIsVALUE": subject=%+"PRIsVALUE", " - "issuer=%+"PRIsVALUE", serial=%+"PRIsVALUE", " - "not_before=%+"PRIsVALUE", not_after=%+"PRIsVALUE">", - rb_obj_class(self), - ossl_x509_get_subject(self), - ossl_x509_get_issuer(self), - ossl_x509_get_serial(self), - ossl_x509_get_not_before(self), - ossl_x509_get_not_after(self)); -} - /* * call-seq: * cert1 == cert2 -> true | false @@ -1013,7 +999,6 @@ Init_ossl_x509cert(void) rb_define_method(cX509Cert, "extensions", ossl_x509_get_extensions, 0); rb_define_method(cX509Cert, "extensions=", ossl_x509_set_extensions, 1); rb_define_method(cX509Cert, "add_extension", ossl_x509_add_extension, 1); - rb_define_method(cX509Cert, "inspect", ossl_x509_inspect, 0); rb_define_method(cX509Cert, "==", ossl_x509_eq, 1); rb_define_method(cX509Cert, "tbs_bytes", ossl_x509_tbs_bytes, 0); } diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb index 877eac69ce5e34..9e0aa4edf6b372 100644 --- a/test/openssl/test_x509cert.rb +++ b/test/openssl/test_x509cert.rb @@ -298,6 +298,14 @@ def test_eq assert_equal false, cert3 == cert4 end + def test_inspect + cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + assert_include(cacert.inspect, "subject=#{@ca.inspect}") + + # Do not raise an exception for an invalid certificate + assert_instance_of(String, OpenSSL::X509::Certificate.new.inspect) + end + def test_marshal now = Time.now cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil, From 2b057859414d1ccfa4d88a898d27defc8c74dc1c Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Thu, 26 Sep 2024 16:34:39 -0400 Subject: [PATCH 1599/2435] Allow rb_thread_call_with_gvl() to work when thread already has GVL [Feature #20750] Co-authored-by: Benoit Daloze --- NEWS.md | 7 +++++++ gc.c | 6 +----- thread.c | 6 +++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 53ed47cb8c3265..b44df19ca2b9b2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -342,6 +342,12 @@ The following bundled gems are updated. `IO` objects share the same file descriptor, closing one does not affect the other. [[Feature #18455]] +* GVL + + * `rb_thread_call_with_gvl` now works with or without the GVL. + This allows gems to avoid checking `ruby_thread_has_gvl_p`. + Please still be diligent about the GVL. [[Feature #20750]] + * Set * A C API for `Set` has been added. The following methods are supported: @@ -402,6 +408,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 +[Feature #20750]: https://bugs.ruby-lang.org/issues/20750 [Feature #20884]: https://bugs.ruby-lang.org/issues/20884 [Feature #20925]: https://bugs.ruby-lang.org/issues/20925 [Feature #20971]: https://bugs.ruby-lang.org/issues/20971 diff --git a/gc.c b/gc.c index 7b4547e2ea0bca..18badba00581d8 100644 --- a/gc.c +++ b/gc.c @@ -5063,11 +5063,7 @@ gc_raise(VALUE exc, const char *fmt, ...) exc, fmt, &ap, }; - if (ruby_thread_has_gvl_p()) { - gc_vraise(&argv); - UNREACHABLE; - } - else if (ruby_native_thread_p()) { + if (ruby_native_thread_p()) { rb_thread_call_with_gvl(gc_vraise, &argv); UNREACHABLE; } diff --git a/thread.c b/thread.c index 5f592a42562adf..7c88c979029a10 100644 --- a/thread.c +++ b/thread.c @@ -2042,6 +2042,9 @@ rb_thread_io_blocking_region(struct rb_io *io, rb_blocking_function_t *func, voi * created as Ruby thread (created by Thread.new or so). In other * words, this function *DOES NOT* associate or convert a NON-Ruby * thread to a Ruby thread. + * + * NOTE: If this thread has already acquired the GVL, then the method call + * is performed without acquiring or releasing the GVL (from Ruby 4.0). */ void * rb_thread_call_with_gvl(void *(*func)(void *), void *data1) @@ -2065,7 +2068,8 @@ rb_thread_call_with_gvl(void *(*func)(void *), void *data1) prev_unblock = th->unblock; if (brb == 0) { - rb_bug("rb_thread_call_with_gvl: called by a thread which has GVL."); + /* the GVL is already acquired, call method directly */ + return (*func)(data1); } blocking_region_end(th, brb); From 834adc358ae967dfa52890a9abba551a070ab75e Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Fri, 5 Dec 2025 16:07:33 +0000 Subject: [PATCH 1600/2435] [ruby/prism] Avoid out-of-bounds reads Fixes https://github.com/ruby/prism/pull/3784. https://github.com/ruby/prism/commit/3fe862534b --- prism/encoding.c | 84 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/prism/encoding.c b/prism/encoding.c index 424f149b8f833f..d7e5616840483f 100644 --- a/prism/encoding.c +++ b/prism/encoding.c @@ -2377,6 +2377,10 @@ pm_encoding_utf_8_char_width(const uint8_t *b, ptrdiff_t n) { */ size_t pm_encoding_utf_8_alpha_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & PRISM_ENCODING_ALPHABETIC_BIT) ? 1 : 0; } @@ -2397,6 +2401,10 @@ pm_encoding_utf_8_alpha_char(const uint8_t *b, ptrdiff_t n) { */ size_t pm_encoding_utf_8_alnum_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & (PRISM_ENCODING_ALPHANUMERIC_BIT)) ? 1 : 0; } @@ -2417,6 +2425,10 @@ pm_encoding_utf_8_alnum_char(const uint8_t *b, ptrdiff_t n) { */ bool pm_encoding_utf_8_isupper_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & PRISM_ENCODING_UPPERCASE_BIT) ? true : false; } @@ -2435,7 +2447,8 @@ pm_encoding_utf_8_isupper_char(const uint8_t *b, ptrdiff_t n) { static pm_unicode_codepoint_t pm_cesu_8_codepoint(const uint8_t *b, ptrdiff_t n, size_t *width) { - if (b[0] < 0x80) { + + if ((n > 0) && (b[0] < 0x80)) { *width = 1; return (pm_unicode_codepoint_t) b[0]; } @@ -2474,6 +2487,10 @@ pm_cesu_8_codepoint(const uint8_t *b, ptrdiff_t n, size_t *width) { static size_t pm_encoding_cesu_8_char_width(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + size_t width; pm_cesu_8_codepoint(b, n, &width); return width; @@ -2481,6 +2498,10 @@ pm_encoding_cesu_8_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_cesu_8_alpha_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & PRISM_ENCODING_ALPHABETIC_BIT) ? 1 : 0; } @@ -2497,6 +2518,10 @@ pm_encoding_cesu_8_alpha_char(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_cesu_8_alnum_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & (PRISM_ENCODING_ALPHANUMERIC_BIT)) ? 1 : 0; } @@ -2513,6 +2538,10 @@ pm_encoding_cesu_8_alnum_char(const uint8_t *b, ptrdiff_t n) { static bool pm_encoding_cesu_8_isupper_char(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } + if (*b < 0x80) { return (pm_encoding_unicode_table[*b] & PRISM_ENCODING_UPPERCASE_BIT) ? true : false; } @@ -3928,14 +3957,14 @@ static const uint8_t pm_encoding_windows_874_table[256] = { }; #define PRISM_ENCODING_TABLE(name) \ - static size_t pm_encoding_ ##name ## _alpha_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { \ - return (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_ALPHABETIC_BIT); \ + static size_t pm_encoding_ ##name ## _alpha_char(const uint8_t *b, ptrdiff_t n) { \ + return ((n > 0) && (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_ALPHABETIC_BIT)); \ } \ - static size_t pm_encoding_ ##name ## _alnum_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { \ - return (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_ALPHANUMERIC_BIT) ? 1 : 0; \ + static size_t pm_encoding_ ##name ## _alnum_char(const uint8_t *b, ptrdiff_t n) { \ + return ((n > 0) && (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_ALPHANUMERIC_BIT)) ? 1 : 0; \ } \ - static bool pm_encoding_ ##name ## _isupper_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { \ - return (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_UPPERCASE_BIT); \ + static bool pm_encoding_ ##name ## _isupper_char(const uint8_t *b, ptrdiff_t n) { \ + return ((n > 0) && (pm_encoding_ ##name ## _table[*b] & PRISM_ENCODING_UPPERCASE_BIT)); \ } PRISM_ENCODING_TABLE(cp850) @@ -4004,8 +4033,8 @@ PRISM_ENCODING_TABLE(windows_874) * means that if the top bit is not set, the character is 1 byte long. */ static size_t -pm_encoding_ascii_char_width(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { - return *b < 0x80 ? 1 : 0; +pm_encoding_ascii_char_width(const uint8_t *b, ptrdiff_t n) { + return ((n > 0) && (*b < 0x80)) ? 1 : 0; } /** @@ -4013,8 +4042,8 @@ pm_encoding_ascii_char_width(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t * alphabetical character. */ static size_t -pm_encoding_ascii_alpha_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { - return (pm_encoding_ascii_table[*b] & PRISM_ENCODING_ALPHABETIC_BIT); +pm_encoding_ascii_alpha_char(const uint8_t *b, ptrdiff_t n) { + return (n > 0) ? (pm_encoding_ascii_table[*b] & PRISM_ENCODING_ALPHABETIC_BIT) : 0; } /** @@ -4024,7 +4053,7 @@ pm_encoding_ascii_alpha_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t */ static size_t pm_encoding_ascii_alpha_char_7bit(const uint8_t *b, ptrdiff_t n) { - return (*b < 0x80) ? pm_encoding_ascii_alpha_char(b, n) : 0; + return ((n > 0) && (*b < 0x80)) ? pm_encoding_ascii_alpha_char(b, n) : 0; } /** @@ -4032,8 +4061,8 @@ pm_encoding_ascii_alpha_char_7bit(const uint8_t *b, ptrdiff_t n) { * alphanumeric character. */ static size_t -pm_encoding_ascii_alnum_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { - return (pm_encoding_ascii_table[*b] & PRISM_ENCODING_ALPHANUMERIC_BIT) ? 1 : 0; +pm_encoding_ascii_alnum_char(const uint8_t *b, ptrdiff_t n) { + return ((n > 0) && (pm_encoding_ascii_table[*b] & PRISM_ENCODING_ALPHANUMERIC_BIT)) ? 1 : 0; } /** @@ -4043,7 +4072,7 @@ pm_encoding_ascii_alnum_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t */ static size_t pm_encoding_ascii_alnum_char_7bit(const uint8_t *b, ptrdiff_t n) { - return (*b < 0x80) ? pm_encoding_ascii_alnum_char(b, n) : 0; + return ((n > 0) && (*b < 0x80)) ? pm_encoding_ascii_alnum_char(b, n) : 0; } /** @@ -4051,8 +4080,8 @@ pm_encoding_ascii_alnum_char_7bit(const uint8_t *b, ptrdiff_t n) { * character. */ static bool -pm_encoding_ascii_isupper_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { - return (pm_encoding_ascii_table[*b] & PRISM_ENCODING_UPPERCASE_BIT); +pm_encoding_ascii_isupper_char(const uint8_t *b, ptrdiff_t n) { + return (n > 0) && (pm_encoding_ascii_table[*b] & PRISM_ENCODING_UPPERCASE_BIT); } /** @@ -4071,7 +4100,7 @@ pm_encoding_single_char_width(PRISM_ATTRIBUTE_UNUSED const uint8_t *b, PRISM_ATT static size_t pm_encoding_euc_jp_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4115,6 +4144,9 @@ pm_encoding_euc_jp_isupper_char(const uint8_t *b, ptrdiff_t n) { */ static size_t pm_encoding_shift_jis_char_width(const uint8_t *b, ptrdiff_t n) { + if (n == 0) { + return 0; + } // These are the single byte characters. if (b[0] < 0x80 || (b[0] >= 0xA1 && b[0] <= 0xDF)) { return 1; @@ -4178,7 +4210,7 @@ pm_encoding_shift_jis_isupper_char(const uint8_t *b, ptrdiff_t n) { */ static bool pm_encoding_ascii_isupper_char_7bit(const uint8_t *b, ptrdiff_t n) { - return (*b < 0x80) && pm_encoding_ascii_isupper_char(b, n); + return (n > 0) && (*b < 0x80) && pm_encoding_ascii_isupper_char(b, n); } /** @@ -4188,7 +4220,7 @@ pm_encoding_ascii_isupper_char_7bit(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_big5_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4207,7 +4239,7 @@ pm_encoding_big5_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_cp949_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters - if (*b <= 0x80) { + if ((n > 0) && (*b <= 0x80)) { return 1; } @@ -4226,7 +4258,7 @@ pm_encoding_cp949_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_emacs_mule_char_width(const uint8_t *b, ptrdiff_t n) { // These are the 1 byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4269,7 +4301,7 @@ pm_encoding_emacs_mule_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_euc_kr_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4288,7 +4320,7 @@ pm_encoding_euc_kr_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_euc_tw_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4312,7 +4344,7 @@ pm_encoding_euc_tw_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_gb18030_char_width(const uint8_t *b, ptrdiff_t n) { // These are the 1 byte characters. - if (*b < 0x80) { + if ((n > 0) && (*b < 0x80)) { return 1; } @@ -4336,7 +4368,7 @@ pm_encoding_gb18030_char_width(const uint8_t *b, ptrdiff_t n) { static size_t pm_encoding_gbk_char_width(const uint8_t *b, ptrdiff_t n) { // These are the single byte characters. - if (*b <= 0x80) { + if ((n > 0) && (*b <= 0x80)) { return 1; } From be12e19856c95ac722efa944af74122a9b2a6bef Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Fri, 5 Dec 2025 17:44:00 +0000 Subject: [PATCH 1601/2435] [ruby/prism] Avoid undefined int overflow behaviour Fixes https://github.com/ruby/prism/pull/3786. https://github.com/ruby/prism/commit/b72b664675 --- prism/templates/src/serialize.c.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 3e15a11039ef94..0f0aace445a680 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -315,7 +315,7 @@ pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) // buffer offset. We will add a leading 1 to indicate that this // is a buffer offset. uint32_t content_offset = pm_sizet_to_u32(buffer->length); - uint32_t owned_mask = (uint32_t) (1 << 31); + uint32_t owned_mask = 1U << 31; assert(content_offset < owned_mask); content_offset |= owned_mask; From 786f67393875b0a5089da73504ac57179e8ef829 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 5 Dec 2025 15:17:09 -0500 Subject: [PATCH 1602/2435] [ruby/prism] Correct constant pool bucket type logic When replacing an owned constant by a different type (constant or shared) replace with the correct type instead of defaulting to shared. https://github.com/ruby/prism/commit/fbe9b131a1 --- prism/util/pm_constant_pool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/util/pm_constant_pool.c b/prism/util/pm_constant_pool.c index 38ea01a2289d65..922ce6a18cfaff 100644 --- a/prism/util/pm_constant_pool.c +++ b/prism/util/pm_constant_pool.c @@ -264,7 +264,7 @@ pm_constant_pool_insert(pm_constant_pool_t *pool, const uint8_t *start, size_t l // constant and replace it with the shared constant. xfree((void *) constant->start); constant->start = start; - bucket->type = (unsigned int) (PM_CONSTANT_POOL_BUCKET_DEFAULT & 0x3); + bucket->type = (unsigned int) (type & 0x3); } return bucket->id; From ee7923288fd4915199954f615980e516750cb8bb Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 2 Dec 2025 21:50:06 -0500 Subject: [PATCH 1603/2435] ZJIT: Skip GC.auto_compact test when unsupported --- test/ruby/test_zjit.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 00550bbe2058e2..45ee42e6a29162 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2421,6 +2421,7 @@ def test_require_rubygems end def test_require_rubygems_with_auto_compact + omit("GC.auto_compact= support is required for this test") unless GC.respond_to?(:auto_compact=) assert_runs 'true', %q{ GC.auto_compact = true require 'rubygems' From 3269ae1b0d1aaa78d12b1527a2f6b095e148c5d3 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 2 Dec 2025 22:11:20 -0500 Subject: [PATCH 1604/2435] ZJIT: Fix -Wpedantic warning in C99 mode when built with YJIT > insns.def:857:5: error: assigning to 'rb_zjit_func_t' (aka 'unsigned > long (*)(struct rb_execution_context_struct *, struct > rb_control_frame_struct *, unsigned long (*)(struct > rb_execution_context_struct *, struct rb_control_frame_struct *))') from > 'void *' converts between void pointer and function pointer > [-Werror,-Wpedantic] --- vm_exec.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm_exec.h b/vm_exec.h index 033a48f1e7683c..641ace4eaf29b9 100644 --- a/vm_exec.h +++ b/vm_exec.h @@ -185,7 +185,7 @@ default: \ if (ec->tag->state) THROW_EXCEPTION(val); \ } \ } \ - else if ((zjit_entry = rb_zjit_entry)) { \ + else if ((zjit_entry = (rb_zjit_func_t)rb_zjit_entry)) { \ rb_jit_func_t func = zjit_compile(ec); \ if (func) { \ val = zjit_entry(ec, ec->cfp, func); \ From 7ecd369a87d65879f98d95d726772238c1dabc16 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 14:31:48 -0500 Subject: [PATCH 1605/2435] ZJIT: Account for when YJIT is on by default in test_zjit_enable --- test/ruby/test_zjit.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 45ee42e6a29162..03d2461fa0a0af 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -60,7 +60,9 @@ def test_enable_through_env end def test_zjit_enable - assert_separately([], <<~'RUBY') + # --disable-all is important in case the build/environment has YJIT enabled by + # default through e.g. -DYJIT_FORCE_ENABLE. Can't enable ZJIT when YJIT is on. + assert_separately(["--disable-all"], <<~'RUBY') refute_predicate RubyVM::ZJIT, :enabled? refute_predicate RubyVM::ZJIT, :stats_enabled? refute_includes RUBY_DESCRIPTION, "+ZJIT" From 02ca507aa3fe45640ace494e5440d9e8bfd5a517 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 15:05:01 -0500 Subject: [PATCH 1606/2435] JITs: rb_iseq_opcode_at_pc(): Accommodate switch-case interpreter --- jit.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jit.c b/jit.c index a886b66278c2aa..ae592b2205c0be 100644 --- a/jit.c +++ b/jit.c @@ -52,8 +52,11 @@ rb_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx) int rb_iseq_opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc) { - // YJIT should only use iseqs after AST to bytecode compilation - RUBY_ASSERT_ALWAYS(FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)); + // YJIT should only use iseqs after AST to bytecode compilation. + // (Certain non-default interpreter configurations never set ISEQ_TRANSLATED) + if (OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE) { + RUBY_ASSERT_ALWAYS(FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)); + } const VALUE at_pc = *pc; return rb_vm_insn_addr2opcode((const void *)at_pc); From f01fd2bde2c79dd4f3e23154c953baca623b6460 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 15:41:21 -0500 Subject: [PATCH 1607/2435] JITs: Update bindings to include interpreter zjit_ opcodes Mostly YJIT. ZJIT already has the right bindings and this just tweaks the CI configuration. --- .github/workflows/yjit-ubuntu.yml | 3 ++- .github/workflows/zjit-ubuntu.yml | 3 ++- yjit/src/cruby_bindings.inc.rs | 32 ++++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 00214709b93e65..7dce69cda4ea78 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -81,7 +81,8 @@ jobs: include: - test_task: 'yjit-bindgen' hint: 'To fix: use patch in logs' - configure: '--with-gcc=clang-14 --enable-yjit=dev' + # Build with YJIT+ZJIT for output that works in the most number of configurations + configure: '--with-gcc=clang-14 --enable-yjit=dev --enable-zjit' libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' - test_task: 'check' diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 5d281eab0b9947..37a9000d704c5a 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -73,7 +73,8 @@ jobs: - test_task: 'zjit-bindgen' hint: 'To fix: use patch in logs' - configure: '--enable-zjit=dev --with-gcc=clang-16' + # Build with YJIT+ZJIT for output that works in the most number of configurations + configure: '--enable-zjit=dev --enable-yjit --with-gcc=clang-16' clang_path: '/usr/bin/clang-16' runs-on: 'ubuntu-24.04' # for clang-16 diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index b9a8197184a21a..0bd2cefc08acf2 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -919,7 +919,37 @@ pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 214; pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 215; pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 216; pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 217; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 218; +pub const YARVINSN_zjit_getinstancevariable: ruby_vminsn_type = 218; +pub const YARVINSN_zjit_setinstancevariable: ruby_vminsn_type = 219; +pub const YARVINSN_zjit_definedivar: ruby_vminsn_type = 220; +pub const YARVINSN_zjit_send: ruby_vminsn_type = 221; +pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 222; +pub const YARVINSN_zjit_objtostring: ruby_vminsn_type = 223; +pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 224; +pub const YARVINSN_zjit_invokeblock: ruby_vminsn_type = 225; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 226; +pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 227; +pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 228; +pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 229; +pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 230; +pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 231; +pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 232; +pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 234; +pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_ltlt: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 238; +pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 239; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 240; +pub const YARVINSN_zjit_opt_aset: ruby_vminsn_type = 241; +pub const YARVINSN_zjit_opt_length: ruby_vminsn_type = 242; +pub const YARVINSN_zjit_opt_size: ruby_vminsn_type = 243; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 244; +pub const YARVINSN_zjit_opt_succ: ruby_vminsn_type = 245; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 246; +pub const YARVINSN_zjit_opt_regexpmatch2: ruby_vminsn_type = 247; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 248; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), From 8132b3d1d8abbe38810a1e268c59b6d49e46d9a1 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 21:15:04 -0500 Subject: [PATCH 1608/2435] YJIT: Fix including stats for ZJIT instructions when ZJIT not in build --- yjit.c | 9 +++++++++ yjit/bindgen/src/main.rs | 1 + yjit/src/cruby_bindings.inc.rs | 1 + yjit/src/stats.rs | 7 +++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/yjit.c b/yjit.c index 4b78cfbae25a25..16debf6eca5555 100644 --- a/yjit.c +++ b/yjit.c @@ -521,6 +521,15 @@ rb_yjit_set_exception_return(rb_control_frame_t *cfp, void *leave_exit, void *le } } +// VM_INSTRUCTION_SIZE changes depending on if ZJIT is in the build. Since +// bindgen can only grab one version of the constant and copy that to rust, +// we make that the upper bound and this the accurate value. +uint32_t +rb_vm_instruction_size(void) +{ + return VM_INSTRUCTION_SIZE; +} + // Primitives used by yjit.rb VALUE rb_yjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_print_stats_p(rb_execution_context_t *ec, VALUE self); diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 9c28177a6054b2..8d11b4216ef0b0 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -304,6 +304,7 @@ fn main() { .allowlist_function("rb_mod_name") .allowlist_function("rb_const_get") .allowlist_var("rb_vm_insn_count") + .allowlist_function("rb_vm_instruction_size") .allowlist_function("rb_get_alloc_func") .allowlist_function("rb_class_allocate_instance") .allowlist_function("rb_obj_equal") diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0bd2cefc08acf2..b9e5fb3ab12843 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1204,6 +1204,7 @@ extern "C" { leave_exit: *mut ::std::os::raw::c_void, leave_exception: *mut ::std::os::raw::c_void, ); + pub fn rb_vm_instruction_size() -> u32; pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index e8ce7b8b353d63..4f23d97bce7360 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -90,7 +90,9 @@ pub extern "C" fn incr_iseq_counter(idx: usize) { iseq_call_count[idx] += 1; } -// YJIT exit counts for each instruction type +/// YJIT exit counts for each instruction type. +/// Note that `VM_INSTRUCTION_SIZE` is an upper bound and the actual number +/// of VM opcodes may be different in the build. See [`rb_vm_instruction_size()`] const VM_INSTRUCTION_SIZE_USIZE: usize = VM_INSTRUCTION_SIZE as usize; static mut EXIT_OP_COUNT: [u64; VM_INSTRUCTION_SIZE_USIZE] = [0; VM_INSTRUCTION_SIZE_USIZE]; @@ -804,7 +806,8 @@ fn rb_yjit_gen_stats_dict(key: VALUE) -> VALUE { // For each entry in exit_op_count, add a stats entry with key "exit_INSTRUCTION_NAME" // and the value is the count of side exits for that instruction. - for op_idx in 0..VM_INSTRUCTION_SIZE_USIZE { + use crate::utils::IntoUsize; + for op_idx in 0..rb_vm_instruction_size().as_usize() { let op_name = insn_name(op_idx); let key_string = "exit_".to_owned() + &op_name; let count = EXIT_OP_COUNT[op_idx]; From 109ddd291ebc4f07f786e7b4ed277b20e3faaa8a Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 16:03:23 -0500 Subject: [PATCH 1609/2435] ZJIT: Avoid binding to `rb_iseq_constant_body` Its definition changes depending on e.g. whether there is YJIT in the build. --- zjit.c | 4 ++++ zjit/bindgen/src/main.rs | 2 ++ zjit/src/cruby.rs | 13 +++++++++++- zjit/src/cruby_bindings.inc.rs | 36 ++-------------------------------- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/zjit.c b/zjit.c index b8a89aad1a8498..d62c5c58c6334c 100644 --- a/zjit.c +++ b/zjit.c @@ -31,6 +31,10 @@ #include +enum zjit_struct_offsets { + ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param) +}; + #define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) // For a given raw_sample (frame), set the hash with the caller's diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index e07953ffd5df08..30e85149744e22 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -301,6 +301,7 @@ fn main() { .allowlist_function("rb_zjit_defined_ivar") .allowlist_function("rb_zjit_insn_leaf") .allowlist_type("jit_bindgen_constants") + .allowlist_type("zjit_struct_offsets") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") @@ -428,6 +429,7 @@ fn main() { // We define these manually, don't import them .blocklist_type("VALUE") .blocklist_type("ID") + .blocklist_type("rb_iseq_constant_body") // Avoid binding to stuff we don't use .blocklist_item("rb_thread_struct.*") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index b255c28e52cd17..e653602874df77 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -231,6 +231,16 @@ pub fn insn_len(opcode: usize) -> u32 { } } +/// We avoid using bindgen for `rb_iseq_constant_body` since its definition changes depending +/// on build configuration while we need one bindgen file that works for all configurations. +/// Use an opaque type for it instead. +/// See: +#[repr(C)] +pub struct rb_iseq_constant_body { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + /// An object handle similar to VALUE in the C code. Our methods assume /// that this is a handle. Sometimes the C code briefly uses VALUE as /// an unsigned integer type and don't necessarily store valid handles but @@ -683,7 +693,8 @@ pub trait IseqAccess { impl IseqAccess for IseqPtr { /// Get a description of the ISEQ's signature. Analogous to `ISEQ_BODY(iseq)->param` in C. unsafe fn params<'a>(self) -> &'a IseqParameters { - unsafe { &(*(*self).body).param } + use crate::cast::IntoUsize; + unsafe { &*((*self).body.byte_add(ISEQ_BODY_OFFSET_PARAM.to_usize()) as *const IseqParameters) } } } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index a80f3d83c5f414..aed35c3c636846 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -595,40 +595,6 @@ pub type rb_jit_func_t = ::std::option::Option< ) -> VALUE, >; #[repr(C)] -pub struct rb_iseq_constant_body { - pub type_: rb_iseq_type, - pub iseq_size: ::std::os::raw::c_uint, - pub iseq_encoded: *mut VALUE, - pub param: rb_iseq_constant_body_rb_iseq_parameters, - pub location: rb_iseq_location_t, - pub insns_info: rb_iseq_constant_body_iseq_insn_info, - pub local_table: *const ID, - pub lvar_states: *mut rb_iseq_constant_body_lvar_state, - pub catch_table: *mut iseq_catch_table, - pub parent_iseq: *const rb_iseq_struct, - pub local_iseq: *mut rb_iseq_struct, - pub is_entries: *mut iseq_inline_storage_entry, - pub call_data: *mut rb_call_data, - pub variable: rb_iseq_constant_body__bindgen_ty_1, - pub local_table_size: ::std::os::raw::c_uint, - pub ic_size: ::std::os::raw::c_uint, - pub ise_size: ::std::os::raw::c_uint, - pub ivc_size: ::std::os::raw::c_uint, - pub icvarc_size: ::std::os::raw::c_uint, - pub ci_size: ::std::os::raw::c_uint, - pub stack_max: ::std::os::raw::c_uint, - pub builtin_attrs: ::std::os::raw::c_uint, - pub prism: bool, - pub mark_bits: rb_iseq_constant_body__bindgen_ty_2, - pub outer_variables: *mut rb_id_table, - pub mandatory_only_iseq: *const rb_iseq_t, - pub jit_entry: rb_jit_func_t, - pub jit_entry_calls: ::std::os::raw::c_ulong, - pub jit_exception: rb_jit_func_t, - pub jit_exception_calls: ::std::os::raw::c_ulong, - pub zjit_payload: *mut ::std::os::raw::c_void, -} -#[repr(C)] #[derive(Debug, Copy, Clone)] pub struct rb_iseq_constant_body_rb_iseq_parameters { pub flags: rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1, @@ -1866,6 +1832,8 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; +pub const ISEQ_BODY_OFFSET_PARAM: zjit_struct_offsets = 16; +pub type zjit_struct_offsets = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; From fb72ff7be0b927cdad518da4eca041c191a91404 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 16:14:40 -0500 Subject: [PATCH 1610/2435] CI: Avoid building ZJIT when LLVM is too old --- .github/workflows/compilers.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 97a698613a2f10..5e54d39e9fa6b9 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -132,11 +132,11 @@ jobs: - { uses: './.github/actions/compilers', name: 'clang 12', with: { tag: 'clang-12' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 11', with: { tag: 'clang-11' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'clang 10', with: { tag: 'clang-10' }, timeout-minutes: 5 } - # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o. - - { uses: './.github/actions/compilers', name: 'clang 9', with: { tag: 'clang-9', append_configure: '--disable-yjit' }, timeout-minutes: 5 } - - { uses: './.github/actions/compilers', name: 'clang 8', with: { tag: 'clang-8', append_configure: '--disable-yjit' }, timeout-minutes: 5 } - - { uses: './.github/actions/compilers', name: 'clang 7', with: { tag: 'clang-7', append_configure: '--disable-yjit' }, timeout-minutes: 5 } - - { uses: './.github/actions/compilers', name: 'clang 6', with: { tag: 'clang-6.0', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o and fail `make test-leaked-globals`. + - { uses: './.github/actions/compilers', name: 'clang 9', with: { tag: 'clang-9', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 8', with: { tag: 'clang-8', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 7', with: { tag: 'clang-7', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'clang 6', with: { tag: 'clang-6.0', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } compile5: name: 'omnibus compilations, #5' From 2bc9b5a85454d2536d18be32e0302842bde18a3f Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 16:55:26 -0500 Subject: [PATCH 1611/2435] tool/update-deps: Skip ZJIT and YJIT+ZJIT build objects --- tool/update-deps | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/update-deps b/tool/update-deps index c927d2483e9a7a..2d4a5674be72a7 100755 --- a/tool/update-deps +++ b/tool/update-deps @@ -326,6 +326,8 @@ def read_make_deps(cwd) deps.delete_if {|dep| /\.time\z/ =~ dep} # skip timestamp next if /\.o\z/ !~ target.to_s next if /libyjit.o\z/ =~ target.to_s # skip YJIT Rust object (no corresponding C source) + next if /libzjit.o\z/ =~ target.to_s # skip ZJIT Rust object (no corresponding C source) + next if /target\/release\/libruby.o\z/ =~ target.to_s # skip YJIT+ZJIT Rust object (no corresponding C source) next if /\.bundle\// =~ target.to_s next if /\A\./ =~ target.to_s # skip rules such as ".c.o" #p [curdir, target, deps] From 8296524fd6cf2fb9ae1eca4be722fe9ee2c26f37 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 17:16:27 -0500 Subject: [PATCH 1612/2435] ZJIT: Fix duplicate make rule warning in combo build ~/zjit/zjit.mk:30: warning: overriding commands for target `~/build-default/' ~/yjit/yjit.mk:26: warning: ignoring old commands for target `~/build-default/' ~/zjit/zjit.mk:30: warning: overriding commands for target `~/build-default/' ~/yjit/yjit.mk:26: warning: ignoring old commands for target `~/build-default/' --- zjit/zjit.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/zjit.mk b/zjit/zjit.mk index f31b948845678a..01274dc3073642 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -24,8 +24,8 @@ ZJIT_LIB_TOUCH = touch $@ # the "target" dir in the source directory through VPATH. BUILD_ZJIT_LIBS = $(TOP_BUILD_DIR)/$(ZJIT_LIBS) -# ZJIT_SUPPORT=yes when `configure` gets `--enable-zjit` -ifeq ($(ZJIT_SUPPORT),yes) +# In a ZJIT-only build (no YJIT) +ifneq ($(strip $(ZJIT_LIBS)),) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ECHO) 'building Rust ZJIT (release mode)' +$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) From addeafdd5b9e2404eef555f523bc92a6c373d294 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 17:20:44 -0500 Subject: [PATCH 1613/2435] YJIT: Fix duplicate make rule warning in combo build --- yjit/yjit.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yjit/yjit.mk b/yjit/yjit.mk index cf68edb29770b8..e019e4a08cf7b8 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -19,8 +19,8 @@ YJIT_LIB_TOUCH = touch $@ # the "target" dir in the source directory through VPATH. BUILD_YJIT_LIBS = $(TOP_BUILD_DIR)/$(YJIT_LIBS) -# YJIT_SUPPORT=yes when `configure` gets `--enable-yjit` -ifeq ($(YJIT_SUPPORT),yes) +# In a YJIT-only build (no ZJIT) +ifneq ($(strip $(YJIT_LIBS)),) yjit-libs: $(BUILD_YJIT_LIBS) $(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' From dce716e25b5101cc57ab0699db9265d961d59d82 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 20:26:51 -0500 Subject: [PATCH 1614/2435] ZJIT: Update `depend` for zjit.o --- depend | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/depend b/depend index 569f14a04da5bf..81ab8274710947 100644 --- a/depend +++ b/depend @@ -20241,6 +20241,7 @@ zjit.$(OBJEXT): $(top_srcdir)/internal/array.h zjit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h zjit.$(OBJEXT): $(top_srcdir)/internal/bignum.h zjit.$(OBJEXT): $(top_srcdir)/internal/bits.h +zjit.$(OBJEXT): $(top_srcdir)/internal/box.h zjit.$(OBJEXT): $(top_srcdir)/internal/class.h zjit.$(OBJEXT): $(top_srcdir)/internal/compile.h zjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -20252,6 +20253,7 @@ zjit.$(OBJEXT): $(top_srcdir)/internal/imemo.h zjit.$(OBJEXT): $(top_srcdir)/internal/numeric.h zjit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h zjit.$(OBJEXT): $(top_srcdir)/internal/serial.h +zjit.$(OBJEXT): $(top_srcdir)/internal/set_table.h zjit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h zjit.$(OBJEXT): $(top_srcdir)/internal/string.h zjit.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -20428,6 +20430,7 @@ zjit.$(OBJEXT): {$(VPATH)}internal/intern/re.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/select.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +zjit.$(OBJEXT): {$(VPATH)}internal/intern/set.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/signal.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h zjit.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20479,6 +20482,7 @@ zjit.$(OBJEXT): {$(VPATH)}vm_debug.h zjit.$(OBJEXT): {$(VPATH)}vm_insnhelper.h zjit.$(OBJEXT): {$(VPATH)}vm_opts.h zjit.$(OBJEXT): {$(VPATH)}vm_sync.h +zjit.$(OBJEXT): {$(VPATH)}yjit.h zjit.$(OBJEXT): {$(VPATH)}zjit.c zjit.$(OBJEXT): {$(VPATH)}zjit.h zjit.$(OBJEXT): {$(VPATH)}zjit.rbinc From 9a2710013c52d81174fa289b9002df1be379d39d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 21:16:23 -0500 Subject: [PATCH 1615/2435] YJIT: Fix unused_unsafe warning in `StatsAlloc` --- jit/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/jit/src/lib.rs b/jit/src/lib.rs index 6079d00f2fd886..c0f043131e0d74 100644 --- a/jit/src/lib.rs +++ b/jit/src/lib.rs @@ -1,4 +1,5 @@ //! Shared code between YJIT and ZJIT. +#![warn(unsafe_op_in_unsafe_fn)] // Adopt 2024 edition default when targeting 2021 editions use std::sync::atomic::{AtomicUsize, Ordering}; use std::alloc::{GlobalAlloc, Layout, System}; From ffe99a56de887bbc24e6ff63cd93f6a88fdd1cf4 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 1 Dec 2025 22:57:04 -0500 Subject: [PATCH 1616/2435] ZJIT: configure.ac logic to detect suitable build environment This runs the detection, but does nothing with the result. * Fixed version requirement in messages -- ZJIT requires >= 1.85 unlike YJIT. * New: Detect when rust 1.85 is available, and neither --enable-yjit nor --enable-zjit is passed to ./configure, include both YJIT and ZJIT in the build --- configure.ac | 55 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/configure.ac b/configure.ac index 21f2e5a466e382..ffdf86e1da21ef 100644 --- a/configure.ac +++ b/configure.ac @@ -3875,12 +3875,13 @@ AC_SUBST(INSTALL_STATIC_LIBRARY) [begin]_group "JIT section" && { AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix +AC_CHECK_TOOL(CARGO, [cargo], [no]) -dnl check if rustc is recent enough to build YJIT/ZJIT (rustc >= 1.58.0) +dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0) JIT_RUSTC_OK=no +JIT_TARGET_ARCH= AS_IF([test "$RUSTC" != "no"], - AC_MSG_CHECKING([whether ${RUSTC} works for YJIT/ZJIT]) - JIT_TARGET_ARCH= + AC_MSG_CHECKING([whether ${RUSTC} works for YJIT]) AS_CASE(["$target_cpu"], [arm64|aarch64], [JIT_TARGET_ARCH=aarch64], [x86_64], [JIT_TARGET_ARCH=x86_64], @@ -3914,33 +3915,46 @@ AS_IF([test "$cross_compiling" = no], ) ) -dnl build ZJIT in release mode if rustc >= 1.58.0 is present and we are on a supported platform -AC_ARG_ENABLE(zjit, - AS_HELP_STRING([--enable-zjit], - [enable in-process JIT compiler that requires Rust build tools. enabled by default on supported platforms if rustc 1.58.0+ is available]), - [ZJIT_SUPPORT=$enableval], - [AS_CASE(["$JIT_TARGET_OK:$JIT_RUSTC_OK:$YJIT_SUPPORT"], - [yes:yes:no], [ - ZJIT_SUPPORT=yes - ], - [ZJIT_SUPPORT=no] - )] -) - dnl build YJIT in release mode if rustc >= 1.58.0 is present and we are on a supported platform AC_ARG_ENABLE(yjit, AS_HELP_STRING([--enable-yjit], [enable in-process JIT compiler that requires Rust build tools. enabled by default on supported platforms if rustc 1.58.0+ is available]), [YJIT_SUPPORT=$enableval], - [AS_CASE(["$JIT_TARGET_OK:$JIT_RUSTC_OK:$ZJIT_SUPPORT"], - [yes:yes:no], [ + [AS_CASE(["$JIT_TARGET_OK:$JIT_RUSTC_OK"], + [yes:yes], [ YJIT_SUPPORT=yes ], [YJIT_SUPPORT=no] )] ) -CARGO= +dnl build ZJIT in release mode if rustc >= 1.85.0 is present and we are on a supported platform +ZJIT_SUPPORT=no +AC_ARG_ENABLE(zjit, + AS_HELP_STRING([--enable-zjit], + [enable experimental JIT compiler that requires Rust build tools. enabled by default on supported platforms if rustc 1.85.0+ is available]), + [ZJIT_SUPPORT=$enableval], + [AS_CASE(["$JIT_TARGET_OK"], + [yes], [ + rb_zjit_build_possible=no + AC_MSG_CHECKING([possible to build ZJIT])dnl only checked when --enable-zjit is not specified + # Fails in case rustc target doesn't match ruby target. Can happen on Rosetta, for example. + # 1.85.0 is the first stable version that supports the 2024 edition. + AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | + $RUSTC - --edition=2024 --emit asm=/dev/null 2>/dev/null], + AS_IF([test "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no"], [ + # When only building ZJIT, we don't need cargo; it's required for YJIT+ZJIT build. + # Assume that if rustc is new enough, then cargo is also. + # TODO(alan): Get rid of dependency on cargo in YJIT+ZJIT build. Cargo's offline mode + # still too unreliable: https://github.com/rust-lang/cargo/issues/10352 + rb_zjit_build_possible=yes + ]) + ) + AC_MSG_RESULT($rb_zjit_build_possible) + ] + )] +) + CARGO_BUILD_ARGS= YJIT_LIBS= JIT_CARGO_SUPPORT=no @@ -4019,9 +4033,8 @@ AS_CASE(["${ZJIT_SUPPORT}"], # if YJIT+ZJIT release build, or any build that requires Cargo AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x"$ZJIT_SUPPORT" != "xno" \)], [ - AC_CHECK_TOOL(CARGO, [cargo], [no]) AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install])) + AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) YJIT_LIBS= ZJIT_LIBS= From f559a9106c703c3ecadefa2f2eb26290b9ffa7fe Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 3 Dec 2025 16:55:55 -0500 Subject: [PATCH 1617/2435] ZJIT: configure.ac: Look for GNU make when detecting build environment Building ZJIT requires GNU make at the moment. To get access to `$gnumake`, lift the `make` flavour detection up to the environment section, before the JIT section runs. --- configure.ac | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/configure.ac b/configure.ac index ffdf86e1da21ef..9a35917cac88d3 100644 --- a/configure.ac +++ b/configure.ac @@ -296,6 +296,8 @@ AC_CHECK_TOOLS([OBJCOPY], [gobjcopy objcopy], [:]) AC_CHECK_TOOLS([OBJDUMP], [gobjdump objdump]) AC_CHECK_TOOLS([STRIP], [gstrip strip], [:]) +FIRSTMAKEFILE="" + # nm errors with Rust's LLVM bitcode when Rust uses a newer LLVM version than nm. # In case we're working with llvm-nm, tell it to not worry about the bitcode. AS_IF([${NM} --help 2>&1 | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) @@ -470,6 +472,8 @@ AS_CASE(["$target_os"], # so wrap clang to insert our fake wasm-opt, which does nothing, in PATH. CC_WRAPPER=`cd -P "${tooldir}" && pwd`/wasm-clangw CC="$CC_WRAPPER $CC" + + FIRSTMAKEFILE=GNUmakefile:wasm/GNUmakefile.in ]) cc_version= @@ -511,6 +515,8 @@ AS_CASE(["$target_os"], target_cpu=`echo $target_cpu | sed s/i.86/i386/` AS_CASE(["$target"], [-*], [ target="$target_cpu${target}"]) AS_CASE(["$target_alias"], [-*], [ target_alias="$target_cpu${target_alias}"]) + # cygwin/GNUmakefile.in is not exclusively for cygwin. + FIRSTMAKEFILE=GNUmakefile:cygwin/GNUmakefile.in AS_CASE(["$target_os"], [mingw*], [ test "$rb_cv_msvcrt" = "" && unset rb_cv_msvcrt @@ -613,6 +619,22 @@ AS_IF([test -f conf$$.dir/src/cdcmd], [ rm -fr conf$$.dir AC_MSG_RESULT([$CHDIR]) AC_SUBST(CHDIR) + +AS_CASE(["$FIRSTMAKEFILE"], [*GNUmakefile:*], [gnumake=yes], [ + AC_MSG_CHECKING([if ${MAKE-make} is GNU make]) + mkdir conftest.dir + echo "all:; @echo yes" > conftest.dir/GNUmakefile + echo "all:; @echo no" > conftest.dir/Makefile + gnumake=`(cd conftest.dir; ${MAKE-make})` + rm -fr conftest.dir + AS_CASE(["$gnumake"], + [*yes*], [ + FIRSTMAKEFILE=GNUmakefile:template/GNUmakefile.in + gnumake=yes], + [ + gnumake=no]) + AC_MSG_RESULT($gnumake) +]) } [begin]_group "compiler section" && { @@ -3512,7 +3534,6 @@ AC_SUBST(RUNRUBY) AC_SUBST(XRUBY) AC_SUBST(EXTOUT, [${EXTOUT=.ext}]) -FIRSTMAKEFILE="" LIBRUBY_A='lib$(RUBY_SO_NAME)-static.a' LIBRUBY='$(LIBRUBY_A)' LIBRUBYARG_STATIC='-l$(RUBY_SO_NAME)-static' @@ -3942,7 +3963,7 @@ AC_ARG_ENABLE(zjit, # 1.85.0 is the first stable version that supports the 2024 edition. AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | $RUSTC - --edition=2024 --emit asm=/dev/null 2>/dev/null], - AS_IF([test "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no"], [ + AS_IF([test "$gnumake" = "yes" -a \( "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no" \)], [ # When only building ZJIT, we don't need cargo; it's required for YJIT+ZJIT build. # Assume that if rustc is new enough, then cargo is also. # TODO(alan): Get rid of dependency on cargo in YJIT+ZJIT build. Cargo's offline mode @@ -4200,7 +4221,6 @@ enum { PLATFORM_DIR=win32 ]) LIBRUBY_ALIASES='' - FIRSTMAKEFILE=GNUmakefile:cygwin/GNUmakefile.in AS_IF([test x"$enable_shared" = xyes], [ LIBRUBY='lib$(RUBY_SO_NAME).dll.a' ], [ @@ -4210,7 +4230,6 @@ enum { ]) ], [wasi*], [ - FIRSTMAKEFILE=GNUmakefile:wasm/GNUmakefile.in AC_LIBOBJ([wasm/missing]) AC_LIBOBJ([wasm/runtime]) AC_LIBOBJ([wasm/fiber]) @@ -4227,21 +4246,6 @@ AC_ARG_ENABLE(debug-env, AS_HELP_STRING([--enable-debug-env], [enable RUBY_DEBUG environment variable]), [AC_SUBST(ENABLE_DEBUG_ENV, yes)]) -AS_CASE(["$FIRSTMAKEFILE"], [*GNUmakefile:*], [gnumake=yes], [ - AC_MSG_CHECKING([if ${MAKE-make} is GNU make]) - mkdir conftest.dir - echo "all:; @echo yes" > conftest.dir/GNUmakefile - echo "all:; @echo no" > conftest.dir/Makefile - gnumake=`(cd conftest.dir; ${MAKE-make})` - rm -fr conftest.dir - AS_CASE(["$gnumake"], - [*yes*], [ - FIRSTMAKEFILE=GNUmakefile:template/GNUmakefile.in - gnumake=yes], - [ - gnumake=no]) - AC_MSG_RESULT($gnumake) -]) AS_IF([test "$gnumake" = yes], [ NULLCMD=: ], [ AC_MSG_CHECKING([for safe null command for ${MAKE-make}]) mkdir conftest.dir From d396a66a82992d4ff3f4d52a6e7c493b560ae9b2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 4 Dec 2025 22:43:28 -0500 Subject: [PATCH 1618/2435] ZJIT: Build by default when build environment allows "Default" means when `--enable-zjit` is absent from `./configure` arguments. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 9a35917cac88d3..14b0234ef0f9aa 100644 --- a/configure.ac +++ b/configure.ac @@ -3972,6 +3972,7 @@ AC_ARG_ENABLE(zjit, ]) ) AC_MSG_RESULT($rb_zjit_build_possible) + ZJIT_SUPPORT=$rb_zjit_build_possible ] )] ) From c4c909b538a2114491be0785ec4aa3d26d2916c4 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 5 Dec 2025 22:00:24 +0000 Subject: [PATCH 1619/2435] ZJIT: Include local variable names in `Get|SetLocal` insn's print value (#15423) ZJIT: Print local variable names GetLocal and SetLocal instructions --- zjit/src/hir.rs | 57 ++++- zjit/src/hir/opt_tests.rs | 452 +++++++++++++++++++------------------- zjit/src/hir/tests.rs | 324 +++++++++++++-------------- 3 files changed, 433 insertions(+), 400 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 32d6f63b9ed74c..4323b145feb1b6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -956,8 +956,8 @@ impl Insn { } } - pub fn print<'a>(&self, ptr_map: &'a PtrPrintMap) -> InsnPrinter<'a> { - InsnPrinter { inner: self.clone(), ptr_map } + pub fn print<'a>(&self, ptr_map: &'a PtrPrintMap, iseq: Option) -> InsnPrinter<'a> { + InsnPrinter { inner: self.clone(), ptr_map, iseq } } /// Return true if the instruction needs to be kept around. For example, if the instruction @@ -1020,6 +1020,30 @@ impl Insn { pub struct InsnPrinter<'a> { inner: Insn, ptr_map: &'a PtrPrintMap, + iseq: Option, +} + +/// Get the name of a local variable given iseq, level, and ep_offset. +/// Returns +/// - `":name"` if iseq is available and name is a real identifier, +/// - `""` for anonymous locals. +/// - `None` if iseq is not available. +/// (When `Insn` is printed in a panic/debug message the `Display::fmt` method is called, which can't access an iseq.) +/// +/// This mimics local_var_name() from iseq.c. +fn get_local_var_name_for_printer(iseq: Option, level: u32, ep_offset: u32) -> Option { + let mut current_iseq = iseq?; + for _ in 0..level { + current_iseq = unsafe { rb_get_iseq_body_parent_iseq(current_iseq) }; + } + let local_idx = ep_offset_to_local_idx(current_iseq, ep_offset); + let id: ID = unsafe { rb_zjit_local_id(current_iseq, local_idx.try_into().unwrap()) }; + + if id.0 == 0 || unsafe { rb_id2str(id) } == Qfalse { + return Some(String::from("")); + } + + Some(format!(":{}", id.contents_lossy())) } static REGEXP_FLAGS: &[(u32, &str)] = &[ @@ -1302,9 +1326,18 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()), - &Insn::GetLocal { level, ep_offset, use_sp: true, rest_param } => write!(f, "GetLocal l{level}, SP@{}{}", ep_offset + 1, if rest_param { ", *" } else { "" }), - &Insn::GetLocal { level, ep_offset, use_sp: false, rest_param } => write!(f, "GetLocal l{level}, EP@{ep_offset}{}", if rest_param { ", *" } else { "" }), - Insn::SetLocal { val, level, ep_offset } => write!(f, "SetLocal l{level}, EP@{ep_offset}, {val}"), + &Insn::GetLocal { level, ep_offset, use_sp: true, rest_param } => { + let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); + write!(f, "GetLocal {name}l{level}, SP@{}{}", ep_offset + 1, if rest_param { ", *" } else { "" }) + }, + &Insn::GetLocal { level, ep_offset, use_sp: false, rest_param } => { + let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); + write!(f, "GetLocal {name}l{level}, EP@{ep_offset}{}", if rest_param { ", *" } else { "" }) + }, + &Insn::SetLocal { val, level, ep_offset } => { + let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); + write!(f, "SetLocal {name}l{level}, EP@{ep_offset}, {val}") + }, Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"), Insn::GetSpecialNumber { nth, .. } => write!(f, "GetSpecialNumber {nth}"), Insn::GetClassVar { id, .. } => write!(f, "GetClassVar :{}", id.contents_lossy()), @@ -1346,7 +1379,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { impl std::fmt::Display for Insn { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.print(&PtrPrintMap::identity()).fmt(f) + self.print(&PtrPrintMap::identity(), None).fmt(f) } } @@ -4052,7 +4085,7 @@ impl Function { }; - let opcode = insn.print(&ptr_map).to_string(); + let opcode = insn.print(&ptr_map, Some(self.iseq)).to_string(); // Traverse the worklist to get inputs for a given instruction. let mut inputs = VecDeque::new(); @@ -4606,7 +4639,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> { write!(f, "{insn_id}:{} = ", insn_type.print(&self.ptr_map))?; } } - writeln!(f, "{}", insn.print(&self.ptr_map))?; + writeln!(f, "{}", insn.print(&self.ptr_map, Some(fun.iseq)))?; } } Ok(()) @@ -4682,7 +4715,7 @@ impl<'a> std::fmt::Display for FunctionGraphvizPrinter<'a> { if let Insn::Jump(ref target) | Insn::IfTrue { ref target, .. } | Insn::IfFalse { ref target, .. } = insn { edges.push((insn_id, target.target)); } - write_encoded!(f, "{}", insn.print(&self.ptr_map))?; + write_encoded!(f, "{}", insn.print(&self.ptr_map, Some(fun.iseq)))?; writeln!(f, " ")?; } writeln!(f, ">];")?; @@ -6723,8 +6756,8 @@ mod graphviz_tests { bb0()  EntryPoint interpreter  v1:BasicObject = LoadSelf  - v2:BasicObject = GetLocal l0, SP@5  - v3:BasicObject = GetLocal l0, SP@4  + v2:BasicObject = GetLocal :x, l0, SP@5  + v3:BasicObject = GetLocal :y, l0, SP@4  Jump bb2(v1, v2, v3)  >]; bb0:v4 -> bb2:params:n; @@ -6774,7 +6807,7 @@ mod graphviz_tests { bb0()  EntryPoint interpreter  v1:BasicObject = LoadSelf  - v2:BasicObject = GetLocal l0, SP@4  + v2:BasicObject = GetLocal :c, l0, SP@4  Jump bb2(v1, v2)  >]; bb0:v3 -> bb2:params:n; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index a174ac1b69280e..5646af804a2d0f 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -219,7 +219,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :n, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -551,7 +551,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :object, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -580,7 +580,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -610,7 +610,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * + v2:ArrayExact = GetLocal :array, l0, SP@4, * Jump bb2(v1, v2) bb1(v5:BasicObject, v6:ArrayExact): EntryPoint JIT(0) @@ -623,8 +623,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :k, l0, SP@5 + v3:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -637,7 +637,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :k, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -650,7 +650,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -664,8 +664,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@5, * - v3:BasicObject = GetLocal l0, SP@4 + v2:ArrayExact = GetLocal :rest, l0, SP@5, * + v3:BasicObject = GetLocal :post, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): EntryPoint JIT(0) @@ -774,7 +774,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -784,7 +784,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) v23:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C] v24:BasicObject = CCallWithFrame v23, :C#fun_new_map@0x1038, block=0x1040 - v15:BasicObject = GetLocal l0, EP@3 + v15:BasicObject = GetLocal :o, l0, EP@3 CheckInterrupts Return v24 "); @@ -1064,8 +1064,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1091,8 +1091,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1119,7 +1119,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1146,7 +1146,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1173,8 +1173,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1201,7 +1201,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1228,7 +1228,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1316,7 +1316,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1343,7 +1343,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1370,7 +1370,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1397,7 +1397,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1451,7 +1451,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1481,7 +1481,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1574,7 +1574,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :a, l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -1630,8 +1630,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :aval, l0, SP@6 + v3:BasicObject = GetLocal :bval, l0, SP@5 v4:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): @@ -1776,8 +1776,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1807,8 +1807,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1838,8 +1838,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1869,8 +1869,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1901,8 +1901,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1933,8 +1933,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1964,8 +1964,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1995,8 +1995,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2026,8 +2026,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2057,8 +2057,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2088,8 +2088,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2143,7 +2143,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2540,7 +2540,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2568,7 +2568,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :x, l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -2636,7 +2636,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :c, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2700,10 +2700,10 @@ mod hir_opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) - SetLocal l0, EP@3, v13 + SetLocal :a, l0, EP@3, v13 v19:BasicObject = Send v8, 0x1000, :foo - v20:BasicObject = GetLocal l0, EP@3 - v24:BasicObject = GetLocal l0, EP@3 + v20:BasicObject = GetLocal :a, l0, EP@3 + v24:BasicObject = GetLocal :a, l0, EP@3 CheckInterrupts Return v24 "); @@ -3260,8 +3260,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -3287,8 +3287,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -3314,7 +3314,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :block, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4150,7 +4150,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4182,7 +4182,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4211,7 +4211,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4762,7 +4762,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4789,7 +4789,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4816,7 +4816,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4843,7 +4843,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4870,7 +4870,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4897,7 +4897,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4924,7 +4924,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4952,7 +4952,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -4980,7 +4980,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5007,7 +5007,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5037,7 +5037,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5074,7 +5074,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5102,7 +5102,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5131,8 +5131,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5161,8 +5161,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5190,8 +5190,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5256,7 +5256,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5295,7 +5295,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5485,7 +5485,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5518,7 +5518,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5587,7 +5587,7 @@ mod hir_opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:NilClass): v13:ArrayExact = NewArray - SetLocal l0, EP@3, v13 + SetLocal :result, l0, EP@3, v13 PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, A) v36:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) @@ -5597,8 +5597,8 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030) PatchPoint NoSingletonClass(Array@0x1020) v43:BasicObject = CCallVariadic v36, :zip@0x1058, v39 - v25:BasicObject = GetLocal l0, EP@3 - v29:BasicObject = GetLocal l0, EP@3 + v25:BasicObject = GetLocal :result, l0, EP@3 + v29:BasicObject = GetLocal :result, l0, EP@3 CheckInterrupts Return v29 "); @@ -5615,7 +5615,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :block, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5741,7 +5741,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5773,7 +5773,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5805,7 +5805,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5840,7 +5840,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5872,7 +5872,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5900,7 +5900,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5932,7 +5932,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -5961,8 +5961,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@5 + v3:BasicObject = GetLocal :v, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -5993,8 +5993,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@5 + v3:BasicObject = GetLocal :v, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6150,7 +6150,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6176,7 +6176,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6201,7 +6201,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6226,7 +6226,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6287,8 +6287,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@5 + v3:BasicObject = GetLocal :idx, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6319,8 +6319,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@5 + v3:BasicObject = GetLocal :idx, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6382,8 +6382,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :hash, l0, SP@5 + v3:BasicObject = GetLocal :key, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6413,8 +6413,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :hash, l0, SP@5 + v3:BasicObject = GetLocal :key, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6503,7 +6503,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6533,7 +6533,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6563,7 +6563,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6593,7 +6593,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6623,7 +6623,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6651,7 +6651,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arr, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6679,7 +6679,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6706,8 +6706,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@5 + v3:BasicObject = GetLocal :i, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6743,8 +6743,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@5 + v3:BasicObject = GetLocal :i, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -6779,9 +6779,9 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@6 + v3:BasicObject = GetLocal :idx, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): EntryPoint JIT(0) @@ -6820,9 +6820,9 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@6 + v3:BasicObject = GetLocal :idx, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): EntryPoint JIT(0) @@ -6859,9 +6859,9 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@6 + v3:BasicObject = GetLocal :idx, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): EntryPoint JIT(0) @@ -6889,7 +6889,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6922,7 +6922,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6950,7 +6950,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -6978,7 +6978,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7004,7 +7004,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7032,7 +7032,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7059,7 +7059,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7086,8 +7086,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7112,7 +7112,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7139,7 +7139,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7165,7 +7165,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7191,8 +7191,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7217,8 +7217,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7246,8 +7246,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7277,8 +7277,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7308,8 +7308,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7387,7 +7387,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7460,8 +7460,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7491,8 +7491,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7519,8 +7519,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7542,8 +7542,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7565,8 +7565,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7590,8 +7590,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -7614,7 +7614,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :hash, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7644,7 +7644,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :hash, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7674,7 +7674,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7706,7 +7706,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7741,7 +7741,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7775,7 +7775,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7810,7 +7810,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7845,7 +7845,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7879,7 +7879,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7913,7 +7913,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7946,7 +7946,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -7982,7 +7982,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8299,7 +8299,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8324,7 +8324,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8349,8 +8349,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8380,8 +8380,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8411,8 +8411,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8440,8 +8440,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8471,8 +8471,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8502,8 +8502,8 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :l, l0, SP@5 + v3:BasicObject = GetLocal :r, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -8533,7 +8533,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8563,7 +8563,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8592,7 +8592,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8623,7 +8623,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8652,7 +8652,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8679,7 +8679,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8709,7 +8709,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8739,7 +8739,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8769,7 +8769,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8801,7 +8801,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8834,7 +8834,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8864,7 +8864,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8894,7 +8894,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -8926,7 +8926,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9011,7 +9011,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :s, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9090,7 +9090,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9123,7 +9123,7 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :o, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -9217,9 +9217,9 @@ mod hir_opt_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :_b, l0, SP@6 + v4:BasicObject = GetLocal :_c, l0, SP@5 v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject): @@ -9228,9 +9228,9 @@ mod hir_opt_tests { Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:NilClass): CheckInterrupts - v27:BasicObject = GetLocal l0, EP@6 - SetLocal l0, EP@3, v27 - v39:BasicObject = GetLocal l0, EP@3 + v27:BasicObject = GetLocal :a, l0, EP@6 + SetLocal :formatted, l0, EP@3, v27 + v39:BasicObject = GetLocal :formatted, l0, EP@3 PatchPoint SingleRactorMode v56:HeapBasicObject = GuardType v14, HeapBasicObject v57:HeapBasicObject = GuardShape v56, 0x1000 @@ -9242,10 +9242,10 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) v65:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 - v48:BasicObject = GetLocal l0, EP@6 - v49:BasicObject = GetLocal l0, EP@5 - v50:BasicObject = GetLocal l0, EP@4 - v51:BasicObject = GetLocal l0, EP@3 + v48:BasicObject = GetLocal :a, l0, EP@6 + v49:BasicObject = GetLocal :_b, l0, EP@5 + v50:BasicObject = GetLocal :_c, l0, EP@4 + v51:BasicObject = GetLocal :formatted, l0, EP@3 CheckInterrupts Return v65 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 79ba1d30c53443..76be941fe529f9 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -22,8 +22,8 @@ mod snapshot_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -127,7 +127,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 v3:CPtr = LoadPC v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) v5:CBool = IsBitEqual v3, v4 @@ -199,7 +199,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -220,8 +220,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -242,7 +242,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -264,8 +264,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -286,7 +286,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -308,8 +308,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -392,8 +392,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :aval, l0, SP@5 + v3:BasicObject = GetLocal :bval, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -773,16 +773,16 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v10:BasicObject = GetLocal l2, EP@4 - SetLocal l1, EP@3, v10 - v15:BasicObject = GetLocal l1, EP@3 - v17:BasicObject = GetLocal l2, EP@4 + v10:BasicObject = GetLocal :l2, l2, EP@4 + SetLocal :l1, l1, EP@3, v10 + v15:BasicObject = GetLocal :l1, l1, EP@3 + v17:BasicObject = GetLocal :l2, l2, EP@4 v20:BasicObject = SendWithoutBlock v15, :+, v17 - SetLocal l2, EP@4, v20 - v25:BasicObject = GetLocal l2, EP@4 - v27:BasicObject = GetLocal l3, EP@5 + SetLocal :l2, l2, EP@4, v20 + v25:BasicObject = GetLocal :l2, l2, EP@4 + v27:BasicObject = GetLocal :l3, l3, EP@5 v30:BasicObject = SendWithoutBlock v25, :+, v27 - SetLocal l3, EP@5, v30 + SetLocal :l3, l3, EP@5, v30 CheckInterrupts Return v30 " @@ -800,7 +800,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :a, l0, SP@5 v3:NilClass = Const Value(nil) v4:CPtr = LoadPC v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) @@ -838,7 +838,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :a, l0, SP@5 v3:NilClass = Const Value(nil) v4:CPtr = LoadPC v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) @@ -873,7 +873,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 v3:CPtr = LoadPC v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) v5:CBool = IsBitEqual v3, v4 @@ -904,7 +904,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject): EntryPoint JIT(0) @@ -1021,7 +1021,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :cond, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1057,7 +1057,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :cond, l0, SP@5 v3:NilClass = Const Value(nil) Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject): @@ -1095,8 +1095,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1120,8 +1120,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1145,8 +1145,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1170,8 +1170,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1195,8 +1195,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1220,8 +1220,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1245,8 +1245,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1270,8 +1270,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1295,8 +1295,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1320,8 +1320,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1398,8 +1398,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1494,14 +1494,14 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v14:BasicObject = Send v9, 0x1000, :each - v15:BasicObject = GetLocal l0, EP@3 + v15:BasicObject = GetLocal :a, l0, EP@3 CheckInterrupts Return v14 "); @@ -1575,7 +1575,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1598,7 +1598,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1620,7 +1620,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1643,7 +1643,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1731,7 +1731,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :..., l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1749,7 +1749,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :..., l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1773,7 +1773,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1803,7 +1803,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * + v2:ArrayExact = GetLocal :*, l0, SP@4, * Jump bb2(v1, v2) bb1(v5:BasicObject, v6:ArrayExact): EntryPoint JIT(0) @@ -1828,7 +1828,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :..., l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -1850,10 +1850,10 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:ArrayExact = GetLocal l0, SP@7, * - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :a, l0, SP@8 + v3:ArrayExact = GetLocal :*, l0, SP@7, * + v4:BasicObject = GetLocal :**, l0, SP@6 + v5:BasicObject = GetLocal :&, l0, SP@5 v6:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5, v6) bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): @@ -1938,8 +1938,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1970,8 +1970,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -1997,8 +1997,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2029,8 +2029,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2071,8 +2071,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2103,8 +2103,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2137,8 +2137,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2153,7 +2153,7 @@ pub mod hir_build_tests { v30:StringExact = StringCopy v29 v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v37:StringExact = StringCopy v36 - v39:BasicObject = GetLocal l0, EP@3 + v39:BasicObject = GetLocal :buf, l0, EP@3 SideExit UnhandledNewarraySend(PACK_BUFFER) "); } @@ -2174,8 +2174,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2221,8 +2221,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5) @@ -2250,7 +2250,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2282,7 +2282,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2303,8 +2303,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2328,8 +2328,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2529,7 +2529,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2552,7 +2552,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2578,7 +2578,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2603,7 +2603,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2632,8 +2632,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2658,8 +2658,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2682,7 +2682,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2705,7 +2705,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2728,8 +2728,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2752,8 +2752,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2776,7 +2776,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2799,8 +2799,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :regexp, l0, SP@5 + v3:BasicObject = GetLocal :matchee, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -2927,7 +2927,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -2952,9 +2952,9 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :arg, l0, SP@6 + v3:BasicObject = GetLocal :exception, l0, SP@5 + v4:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4) bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): EntryPoint JIT(0) @@ -3000,10 +3000,10 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:BasicObject = GetLocal l0, SP@7 - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 + v2:BasicObject = GetLocal :name, l0, SP@8 + v3:BasicObject = GetLocal :encoding, l0, SP@7 + v4:BasicObject = GetLocal , l0, SP@6 + v5:BasicObject = GetLocal :block, l0, SP@5 v6:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5, v6) bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): @@ -3063,10 +3063,10 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:BasicObject = GetLocal l0, SP@5 - v5:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :full_mark, l0, SP@7 + v3:BasicObject = GetLocal :immediate_mark, l0, SP@6 + v4:BasicObject = GetLocal :immediate_sweep, l0, SP@5 + v5:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4, v5) bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): EntryPoint JIT(0) @@ -3134,7 +3134,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@4 Jump bb2(v1, v2) bb1(v5:BasicObject, v6:BasicObject): EntryPoint JIT(0) @@ -3369,8 +3369,8 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :x, l0, SP@5 + v3:BasicObject = GetLocal :y, l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) @@ -3395,7 +3395,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :o, l0, SP@6 v3:NilClass = Const Value(nil) v4:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4) @@ -3431,7 +3431,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 + v2:BasicObject = GetLocal :o, l0, SP@6 v3:NilClass = Const Value(nil) v4:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4) @@ -3458,7 +3458,7 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 + v2:BasicObject = GetLocal :o, l0, SP@7 v3:NilClass = Const Value(nil) v4:NilClass = Const Value(nil) v5:NilClass = Const Value(nil) @@ -3485,14 +3485,14 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :kw, l0, SP@5 + v3:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3) bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v15:BasicObject = GetLocal l0, EP@3 + v15:BasicObject = GetLocal , l0, EP@3 v16:BoolExact = FixnumBitCheck v15, 0 CheckInterrupts v19:CBool = Test v16 @@ -3526,40 +3526,40 @@ pub mod hir_build_tests { bb0(): EntryPoint interpreter v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@37 - v3:BasicObject = GetLocal l0, SP@36 - v4:BasicObject = GetLocal l0, SP@35 - v5:BasicObject = GetLocal l0, SP@34 - v6:BasicObject = GetLocal l0, SP@33 - v7:BasicObject = GetLocal l0, SP@32 - v8:BasicObject = GetLocal l0, SP@31 - v9:BasicObject = GetLocal l0, SP@30 - v10:BasicObject = GetLocal l0, SP@29 - v11:BasicObject = GetLocal l0, SP@28 - v12:BasicObject = GetLocal l0, SP@27 - v13:BasicObject = GetLocal l0, SP@26 - v14:BasicObject = GetLocal l0, SP@25 - v15:BasicObject = GetLocal l0, SP@24 - v16:BasicObject = GetLocal l0, SP@23 - v17:BasicObject = GetLocal l0, SP@22 - v18:BasicObject = GetLocal l0, SP@21 - v19:BasicObject = GetLocal l0, SP@20 - v20:BasicObject = GetLocal l0, SP@19 - v21:BasicObject = GetLocal l0, SP@18 - v22:BasicObject = GetLocal l0, SP@17 - v23:BasicObject = GetLocal l0, SP@16 - v24:BasicObject = GetLocal l0, SP@15 - v25:BasicObject = GetLocal l0, SP@14 - v26:BasicObject = GetLocal l0, SP@13 - v27:BasicObject = GetLocal l0, SP@12 - v28:BasicObject = GetLocal l0, SP@11 - v29:BasicObject = GetLocal l0, SP@10 - v30:BasicObject = GetLocal l0, SP@9 - v31:BasicObject = GetLocal l0, SP@8 - v32:BasicObject = GetLocal l0, SP@7 - v33:BasicObject = GetLocal l0, SP@6 - v34:BasicObject = GetLocal l0, SP@5 - v35:BasicObject = GetLocal l0, SP@4 + v2:BasicObject = GetLocal :k1, l0, SP@37 + v3:BasicObject = GetLocal :k2, l0, SP@36 + v4:BasicObject = GetLocal :k3, l0, SP@35 + v5:BasicObject = GetLocal :k4, l0, SP@34 + v6:BasicObject = GetLocal :k5, l0, SP@33 + v7:BasicObject = GetLocal :k6, l0, SP@32 + v8:BasicObject = GetLocal :k7, l0, SP@31 + v9:BasicObject = GetLocal :k8, l0, SP@30 + v10:BasicObject = GetLocal :k9, l0, SP@29 + v11:BasicObject = GetLocal :k10, l0, SP@28 + v12:BasicObject = GetLocal :k11, l0, SP@27 + v13:BasicObject = GetLocal :k12, l0, SP@26 + v14:BasicObject = GetLocal :k13, l0, SP@25 + v15:BasicObject = GetLocal :k14, l0, SP@24 + v16:BasicObject = GetLocal :k15, l0, SP@23 + v17:BasicObject = GetLocal :k16, l0, SP@22 + v18:BasicObject = GetLocal :k17, l0, SP@21 + v19:BasicObject = GetLocal :k18, l0, SP@20 + v20:BasicObject = GetLocal :k19, l0, SP@19 + v21:BasicObject = GetLocal :k20, l0, SP@18 + v22:BasicObject = GetLocal :k21, l0, SP@17 + v23:BasicObject = GetLocal :k22, l0, SP@16 + v24:BasicObject = GetLocal :k23, l0, SP@15 + v25:BasicObject = GetLocal :k24, l0, SP@14 + v26:BasicObject = GetLocal :k25, l0, SP@13 + v27:BasicObject = GetLocal :k26, l0, SP@12 + v28:BasicObject = GetLocal :k27, l0, SP@11 + v29:BasicObject = GetLocal :k28, l0, SP@10 + v30:BasicObject = GetLocal :k29, l0, SP@9 + v31:BasicObject = GetLocal :k30, l0, SP@8 + v32:BasicObject = GetLocal :k31, l0, SP@7 + v33:BasicObject = GetLocal :k32, l0, SP@6 + v34:BasicObject = GetLocal :k33, l0, SP@5 + v35:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) bb1(v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject, v52:BasicObject, v53:BasicObject, v54:BasicObject, v55:BasicObject, v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject): EntryPoint JIT(0) From 65995c22f892e103fdf2601fdccd0202483a4fca Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 23:08:48 +0100 Subject: [PATCH 1620/2435] [ruby/timeout] Exclude constantly-failing test on x86_64-darwin * https://github.com/ruby/ruby-dev-builder/actions/runs/19973218359/job/57293388626 https://github.com/ruby/timeout/commit/45816b1b26 --- test/test_timeout.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 3f94134fb04d9c..d6ae0c9b50b0ec 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -299,5 +299,6 @@ def test_ractor assert_equal :ok, r end; - end if defined?(::Ractor) && RUBY_VERSION >= '4.0' + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' && !RUBY_PLATFORM.include?('x86_64-darwin') + # Exclude on x86_64-darwin as it failed 4 times out of 4 tries in the CI of ruby/ruby-dev-builder end From 791acc5697afc8f256e652169f7c85a3d90b3f06 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 5 Dec 2025 17:08:20 -0500 Subject: [PATCH 1621/2435] Revert "gc.c: Pass shape_id to `newobj_init`" This reverts commit 228d13f6ed914d1e7f6bd2416e3f5be8283be865. This commit makes default.c and mmtk.c depend on shape.h, which prevents them from building independently. --- gc.c | 5 +++-- gc/default/default.c | 33 +++++++++++++++++---------------- gc/gc_impl.h | 2 +- gc/mmtk/mmtk.c | 5 ++--- shape.h | 9 ++------- 5 files changed, 25 insertions(+), 29 deletions(-) diff --git a/gc.c b/gc.c index 18badba00581d8..4f5f041fb348fb 100644 --- a/gc.c +++ b/gc.c @@ -622,7 +622,7 @@ typedef struct gc_function_map { void (*stress_set)(void *objspace_ptr, VALUE flag); VALUE (*stress_get)(void *objspace_ptr); // Object allocation - VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size); + VALUE (*new_obj)(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); size_t (*obj_slot_size)(VALUE obj); size_t (*heap_id_for_size)(void *objspace_ptr, size_t size); bool (*size_allocatable_p)(size_t size); @@ -993,7 +993,8 @@ gc_validate_pc(VALUE obj) static inline VALUE newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t size) { - VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, shape_id, wb_protected, size); + VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size); + RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); gc_validate_pc(obj); diff --git a/gc/default/default.c b/gc/default/default.c index f8fd40b44fd8b4..a03fda859cf4ca 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -32,7 +32,6 @@ #include "darray.h" #include "gc/gc.h" #include "gc/gc_impl.h" -#include "shape.h" #ifndef BUILDING_MODULAR_GC # include "probes.h" @@ -2148,13 +2147,15 @@ rb_gc_impl_source_location_cstr(int *ptr) #endif static inline VALUE -newobj_init(VALUE klass, VALUE flags, shape_id_t shape_id, int wb_protected, rb_objspace_t *objspace, VALUE obj) +newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj) { GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE); GC_ASSERT((flags & FL_WB_PROTECTED) == 0); RBASIC(obj)->flags = flags; *((VALUE *)&RBASIC(obj)->klass) = klass; - RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); +#if RBASIC_SHAPE_ID_FIELD + RBASIC(obj)->shape_id = 0; +#endif int t = flags & RUBY_T_MASK; if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) { @@ -2438,10 +2439,10 @@ newobj_alloc(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t he return obj; } -ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx)); +ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx)); static inline VALUE -newobj_slowpath(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx) +newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t heap_idx) { VALUE obj; unsigned int lev; @@ -2466,32 +2467,32 @@ newobj_slowpath(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *ob } obj = newobj_alloc(objspace, cache, heap_idx, true); - newobj_init(klass, flags, shape_id, wb_protected, objspace, obj); + newobj_init(klass, flags, wb_protected, objspace, obj); } RB_GC_CR_UNLOCK(lev); return obj; } -NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags, shape_id_t shape_id, +NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx)); -NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, shape_id_t shape_id, +NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx)); static VALUE -newobj_slowpath_wb_protected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) +newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) { - return newobj_slowpath(klass, flags, shape_id, objspace, cache, TRUE, heap_idx); + return newobj_slowpath(klass, flags, objspace, cache, TRUE, heap_idx); } static VALUE -newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, shape_id_t shape_id, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) +newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t heap_idx) { - return newobj_slowpath(klass, flags, shape_id, objspace, cache, FALSE, heap_idx); + return newobj_slowpath(klass, flags, objspace, cache, FALSE, heap_idx); } VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { VALUE obj; rb_objspace_t *objspace = objspace_ptr; @@ -2512,14 +2513,14 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags if (!RB_UNLIKELY(during_gc || ruby_gc_stressful) && wb_protected) { obj = newobj_alloc(objspace, cache, heap_idx, false); - newobj_init(klass, flags, shape_id, wb_protected, objspace, obj); + newobj_init(klass, flags, wb_protected, objspace, obj); } else { RB_DEBUG_COUNTER_INC(obj_newobj_slowpath); obj = wb_protected ? - newobj_slowpath_wb_protected(klass, flags, shape_id, objspace, cache, heap_idx) : - newobj_slowpath_wb_unprotected(klass, flags, shape_id, objspace, cache, heap_idx); + newobj_slowpath_wb_protected(klass, flags, objspace, cache, heap_idx) : + newobj_slowpath_wb_unprotected(klass, flags, objspace, cache, heap_idx); } return obj; diff --git a/gc/gc_impl.h b/gc/gc_impl.h index 65c0586d46bfad..3250483639e775 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -55,7 +55,7 @@ GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); // Object allocation -GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, uint32_t /* shape_id_t */ shape_id, bool wb_protected, size_t alloc_size); +GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size); GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); GC_IMPL_FN size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size); GC_IMPL_FN bool rb_gc_impl_size_allocatable_p(size_t size); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index a3bdf1cd4a9a42..e1678dcf6ab0b4 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -7,7 +7,6 @@ #include "gc/gc.h" #include "gc/gc_impl.h" -#include "shape.h" #include "gc/mmtk/mmtk.h" #include "ccan/list/list.h" @@ -604,7 +603,7 @@ rb_gc_impl_config_set(void *objspace_ptr, VALUE hash) // Object allocation VALUE -rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, shape_id_t shape_id, bool wb_protected, size_t alloc_size) +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { #define MMTK_ALLOCATION_SEMANTICS_DEFAULT 0 struct objspace *objspace = objspace_ptr; @@ -626,7 +625,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags VALUE *alloc_obj = mmtk_alloc(ractor_cache->mutator, alloc_size + 8, MMTk_MIN_OBJ_ALIGN, 0, MMTK_ALLOCATION_SEMANTICS_DEFAULT); alloc_obj++; alloc_obj[-1] = alloc_size; - alloc_obj[0] = RSHAPE_COMBINE_IN_FLAGS(flags, shape_id);; + alloc_obj[0] = flags; alloc_obj[1] = klass; mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size + 8, MMTK_ALLOCATION_SEMANTICS_DEFAULT); diff --git a/shape.h b/shape.h index bebfaba608c7fa..af6add9e08158a 100644 --- a/shape.h +++ b/shape.h @@ -162,12 +162,6 @@ RBASIC_SHAPE_ID_FOR_READ(VALUE obj) bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id); #endif -static inline VALUE -RSHAPE_COMBINE_IN_FLAGS(VALUE flags, shape_id_t shape_id) -{ - return (flags &SHAPE_FLAG_MASK) | (((VALUE)shape_id) << SHAPE_FLAG_SHIFT); -} - static inline void RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) { @@ -175,7 +169,8 @@ RBASIC_SET_SHAPE_ID_NO_CHECKS(VALUE obj, shape_id_t shape_id) RBASIC(obj)->shape_id = (VALUE)shape_id; #else // Object shapes are occupying top bits - RBASIC(obj)->flags = RSHAPE_COMBINE_IN_FLAGS(RBASIC(obj)->flags, shape_id); + RBASIC(obj)->flags &= SHAPE_FLAG_MASK; + RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); #endif } From 8f9838476dc8cc857859a0a93da285d792be7d3b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 5 Dec 2025 17:58:11 -0500 Subject: [PATCH 1622/2435] Fix fields object in embedded struct We don't set RSTRUCT_GEN_FIELDS when RCLASS_MAX_IV_COUNT(klass) != 0, so we need to set RSTRUCT_SET_FIELDS_OBJ to 0 otherwise it may have an invalid value and crash. --- struct.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/struct.c b/struct.c index a6155d4684249f..667d35424fb8d1 100644 --- a/struct.c +++ b/struct.c @@ -826,12 +826,17 @@ struct_alloc(VALUE klass) } NEWOBJ_OF(st, struct RStruct, klass, flags, embedded_size, 0); - if (RCLASS_MAX_IV_COUNT(klass) == 0 - && !rb_shape_obj_has_fields((VALUE)st) - && embedded_size < rb_gc_obj_slot_size((VALUE)st)) { - FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); + if (RCLASS_MAX_IV_COUNT(klass) == 0) { + if (!rb_shape_obj_has_fields((VALUE)st) + && embedded_size < rb_gc_obj_slot_size((VALUE)st)) { + FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); + RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); + } + } + else { RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); } + rb_mem_clear((VALUE *)st->as.ary, n); return (VALUE)st; From a7dc53b91c8475323b34d5a332fdb25d190e277d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Dec 2025 15:55:32 +1300 Subject: [PATCH 1623/2435] Add support for `u128`, `U128`, `s128` and `S128` integers to `IO::Buffer`. (#15399) --- bignum.c | 6 +- internal/bignum.h | 6 + internal/numeric.h | 43 +++++++ io_buffer.c | 70 +++++++++++ numeric.c | 223 ++++++++++++++++++++++++++++++++++++ test/ruby/test_io_buffer.rb | 147 ++++++++++++++++++++++++ 6 files changed, 492 insertions(+), 3 deletions(-) diff --git a/bignum.c b/bignum.c index 8d3eac8e0a9173..ed53d75149085d 100644 --- a/bignum.c +++ b/bignum.c @@ -4515,7 +4515,7 @@ rb_str2big_gmp(VALUE arg, int base, int badcheck) #if HAVE_LONG_LONG -static VALUE +VALUE rb_ull2big(unsigned LONG_LONG n) { long i; @@ -4537,7 +4537,7 @@ rb_ull2big(unsigned LONG_LONG n) return big; } -static VALUE +VALUE rb_ll2big(LONG_LONG n) { long neg = 0; @@ -4575,7 +4575,7 @@ rb_ll2inum(LONG_LONG n) #endif /* HAVE_LONG_LONG */ #ifdef HAVE_INT128_T -static VALUE +VALUE rb_uint128t2big(uint128_t n) { long i; diff --git a/internal/bignum.h b/internal/bignum.h index e5b6b425631f80..0692bafed30d73 100644 --- a/internal/bignum.h +++ b/internal/bignum.h @@ -169,7 +169,13 @@ VALUE rb_str2big_gmp(VALUE arg, int base, int badcheck); VALUE rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, int base, int flags); RUBY_SYMBOL_EXPORT_END +#if HAVE_LONG_LONG +VALUE rb_ull2big(unsigned LONG_LONG n); +VALUE rb_ll2big(LONG_LONG n); +#endif + #if defined(HAVE_INT128_T) +VALUE rb_uint128t2big(uint128_t n); VALUE rb_int128t2big(int128_t n); #endif diff --git a/internal/numeric.h b/internal/numeric.h index 58f42f41ac4bea..75181a7f168620 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -127,6 +127,49 @@ VALUE rb_int_bit_length(VALUE num); VALUE rb_int_uminus(VALUE num); VALUE rb_int_comp(VALUE num); +// Unified 128-bit integer structures that work with or without native support: +union rb_uint128 { +#ifdef WORDS_BIGENDIAN + struct { + uint64_t high; + uint64_t low; + } parts; +#else + struct { + uint64_t low; + uint64_t high; + } parts; +#endif +#ifdef HAVE_UINT128_T + uint128_t value; +#endif +}; +typedef union rb_uint128 rb_uint128_t; + +union rb_int128 { +#ifdef WORDS_BIGENDIAN + struct { + uint64_t high; + uint64_t low; + } parts; +#else + struct { + uint64_t low; + uint64_t high; + } parts; +#endif +#ifdef HAVE_UINT128_T + int128_t value; +#endif +}; +typedef union rb_int128 rb_int128_t; + +// Conversion functions for 128-bit integers: +rb_uint128_t rb_numeric_to_uint128(VALUE x); +rb_int128_t rb_numeric_to_int128(VALUE x); +VALUE rb_uint128_to_numeric(rb_uint128_t n); +VALUE rb_int128_to_numeric(rb_int128_t n); + static inline bool INT_POSITIVE_P(VALUE num) { diff --git a/io_buffer.c b/io_buffer.c index 89f169176f48b5..0d2cbdb4b7e704 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1864,6 +1864,9 @@ io_buffer_validate_type(size_t size, size_t offset) // :u64, :U64 | unsigned 64-bit integer. // :s64, :S64 | signed 64-bit integer. // +// :u128, :U128 | unsigned 128-bit integer. +// :s128, :S128 | signed 128-bit integer. +// // :f32, :F32 | 32-bit floating point number. // :f64, :F64 | 64-bit floating point number. @@ -1895,6 +1898,45 @@ ruby_swapf64(double value) return swap.value; } +// Structures and conversion functions are now in numeric.h/numeric.c +// Unified swap function for 128-bit integers (works with both signed and unsigned) +// Since both rb_uint128_t and rb_int128_t have the same memory layout, +// we can use a union to make the swap function work with both types +static inline rb_uint128_t +ruby_swap128_uint(rb_uint128_t x) +{ + rb_uint128_t result; +#ifdef HAVE_UINT128_T +#if __has_builtin(__builtin_bswap128) + result.value = __builtin_bswap128(x.value); +#else + // Manual byte swap for 128-bit integers + uint64_t low = (uint64_t)x.value; + uint64_t high = (uint64_t)(x.value >> 64); + low = ruby_swap64(low); + high = ruby_swap64(high); + result.value = ((uint128_t)low << 64) | high; +#endif +#else + // Fallback swap function using two 64-bit integers + // For big-endian data on little-endian host (or vice versa): + // 1. Swap bytes within each 64-bit part + // 2. Swap the order of the parts (since big-endian stores high first, little-endian stores low first) + result.parts.low = ruby_swap64(x.parts.high); + result.parts.high = ruby_swap64(x.parts.low); +#endif + return result; +} + +static inline rb_int128_t +ruby_swap128_int(rb_int128_t x) +{ + // Cast to unsigned, swap, then cast back + rb_uint128_t u = *(rb_uint128_t*)&x; + rb_uint128_t swapped = ruby_swap128_uint(u); + return *(rb_int128_t*)&swapped; +} + #define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \ static ID RB_IO_BUFFER_DATA_TYPE_##name; \ \ @@ -1941,6 +1983,11 @@ IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NU IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64) IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64) +IO_BUFFER_DECLARE_TYPE(u128, rb_uint128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint) +IO_BUFFER_DECLARE_TYPE(U128, rb_uint128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint) +IO_BUFFER_DECLARE_TYPE(s128, rb_int128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int) +IO_BUFFER_DECLARE_TYPE(S128, rb_int128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int) + IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32) IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32) IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64) @@ -1965,6 +2012,10 @@ io_buffer_buffer_type_size(ID buffer_type) IO_BUFFER_DATA_TYPE_SIZE(U64) IO_BUFFER_DATA_TYPE_SIZE(s64) IO_BUFFER_DATA_TYPE_SIZE(S64) + IO_BUFFER_DATA_TYPE_SIZE(u128) + IO_BUFFER_DATA_TYPE_SIZE(U128) + IO_BUFFER_DATA_TYPE_SIZE(s128) + IO_BUFFER_DATA_TYPE_SIZE(S128) IO_BUFFER_DATA_TYPE_SIZE(f32) IO_BUFFER_DATA_TYPE_SIZE(F32) IO_BUFFER_DATA_TYPE_SIZE(f64) @@ -2021,6 +2072,11 @@ rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *of IO_BUFFER_GET_VALUE(s64) IO_BUFFER_GET_VALUE(S64) + IO_BUFFER_GET_VALUE(u128) + IO_BUFFER_GET_VALUE(U128) + IO_BUFFER_GET_VALUE(s128) + IO_BUFFER_GET_VALUE(S128) + IO_BUFFER_GET_VALUE(f32) IO_BUFFER_GET_VALUE(F32) IO_BUFFER_GET_VALUE(f64) @@ -2050,6 +2106,10 @@ rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *of * * +:U64+: unsigned integer, 8 bytes, big-endian * * +:s64+: signed integer, 8 bytes, little-endian * * +:S64+: signed integer, 8 bytes, big-endian + * * +:u128+: unsigned integer, 16 bytes, little-endian + * * +:U128+: unsigned integer, 16 bytes, big-endian + * * +:s128+: signed integer, 16 bytes, little-endian + * * +:S128+: signed integer, 16 bytes, big-endian * * +:f32+: float, 4 bytes, little-endian * * +:F32+: float, 4 bytes, big-endian * * +:f64+: double, 8 bytes, little-endian @@ -2287,6 +2347,11 @@ rb_io_buffer_set_value(const void* base, size_t size, ID buffer_type, size_t *of IO_BUFFER_SET_VALUE(s64); IO_BUFFER_SET_VALUE(S64); + IO_BUFFER_SET_VALUE(u128); + IO_BUFFER_SET_VALUE(U128); + IO_BUFFER_SET_VALUE(s128); + IO_BUFFER_SET_VALUE(S128); + IO_BUFFER_SET_VALUE(f32); IO_BUFFER_SET_VALUE(F32); IO_BUFFER_SET_VALUE(f64); @@ -3859,6 +3924,11 @@ Init_IO_Buffer(void) IO_BUFFER_DEFINE_DATA_TYPE(s64); IO_BUFFER_DEFINE_DATA_TYPE(S64); + IO_BUFFER_DEFINE_DATA_TYPE(u128); + IO_BUFFER_DEFINE_DATA_TYPE(U128); + IO_BUFFER_DEFINE_DATA_TYPE(s128); + IO_BUFFER_DEFINE_DATA_TYPE(S128); + IO_BUFFER_DEFINE_DATA_TYPE(f32); IO_BUFFER_DEFINE_DATA_TYPE(F32); IO_BUFFER_DEFINE_DATA_TYPE(f64); diff --git a/numeric.c b/numeric.c index bc0edd6abe91f9..d9e837644ffa05 100644 --- a/numeric.c +++ b/numeric.c @@ -3420,6 +3420,229 @@ rb_num2ull(VALUE val) #endif /* HAVE_LONG_LONG */ +// Conversion functions for unified 128-bit integer structures, +// These work with or without native 128-bit integer support. + +#ifndef HAVE_UINT128_T +// Helper function to build 128-bit value from bignum digits (fallback path). +static inline void +rb_uint128_from_bignum_digits_fallback(rb_uint128_t *result, BDIGIT *digits, size_t length) +{ + // Build the 128-bit value from bignum digits: + for (long i = length - 1; i >= 0; i--) { + // Shift both low and high parts: + uint64_t carry = result->parts.low >> (64 - (SIZEOF_BDIGIT * CHAR_BIT)); + result->parts.low = (result->parts.low << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + result->parts.high = (result->parts.high << (SIZEOF_BDIGIT * CHAR_BIT)) | carry; + } +} + +// Helper function to convert absolute value of negative bignum to two's complement. +// Ruby stores negative bignums as absolute values, so we need to convert to two's complement. +static inline void +rb_uint128_twos_complement_negate(rb_uint128_t *value) +{ + if (value->parts.low == 0) { + value->parts.high = ~value->parts.high + 1; + } + else { + value->parts.low = ~value->parts.low + 1; + value->parts.high = ~value->parts.high + (value->parts.low == 0 ? 1 : 0); + } +} +#endif + +rb_uint128_t +rb_numeric_to_uint128(VALUE x) +{ + rb_uint128_t result = {0}; + if (RB_FIXNUM_P(x)) { + long value = RB_FIX2LONG(x); + if (value < 0) { + rb_raise(rb_eRangeError, "negative integer cannot be converted to unsigned 128-bit integer"); + } +#ifdef HAVE_UINT128_T + result.value = (uint128_t)value; +#else + result.parts.low = (uint64_t)value; + result.parts.high = 0; +#endif + return result; + } + else if (RB_BIGNUM_TYPE_P(x)) { + if (BIGNUM_NEGATIVE_P(x)) { + rb_raise(rb_eRangeError, "negative integer cannot be converted to unsigned 128-bit integer"); + } + size_t length = BIGNUM_LEN(x); +#ifdef HAVE_UINT128_T + if (length > roomof(SIZEOF_INT128_T, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'unsigned 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + result.value = 0; + for (long i = length - 1; i >= 0; i--) { + result.value = (result.value << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + } +#else + // Check if bignum fits in 128 bits (16 bytes) + if (length > roomof(16, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'unsigned 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + rb_uint128_from_bignum_digits_fallback(&result, digits, length); +#endif + return result; + } + else { + rb_raise(rb_eTypeError, "not an integer"); + } +} + +rb_int128_t +rb_numeric_to_int128(VALUE x) +{ + rb_int128_t result = {0}; + if (RB_FIXNUM_P(x)) { + long value = RB_FIX2LONG(x); +#ifdef HAVE_UINT128_T + result.value = (int128_t)value; +#else + if (value < 0) { + // Two's complement representation: for negative values, sign extend + // Convert to unsigned: for -1, we want all bits set + result.parts.low = (uint64_t)value; // This will be the two's complement representation + result.parts.high = UINT64_MAX; // Sign extend: all bits set for negative + } + else { + result.parts.low = (uint64_t)value; + result.parts.high = 0; + } +#endif + return result; + } + else if (RB_BIGNUM_TYPE_P(x)) { + size_t length = BIGNUM_LEN(x); +#ifdef HAVE_UINT128_T + if (length > roomof(SIZEOF_INT128_T, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + uint128_t unsigned_result = 0; + for (long i = length - 1; i >= 0; i--) { + unsigned_result = (unsigned_result << (SIZEOF_BDIGIT * CHAR_BIT)) | digits[i]; + } + if (BIGNUM_NEGATIVE_P(x)) { + // Convert from two's complement + // Maximum negative value is 2^127 + if (unsigned_result > ((uint128_t)1 << 127)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.value = -(int128_t)(unsigned_result - 1) - 1; + } + else { + // Maximum positive value is 2^127 - 1 + if (unsigned_result > (((uint128_t)1 << 127) - 1)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.value = (int128_t)unsigned_result; + } +#else + if (length > roomof(16, SIZEOF_BDIGIT)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + BDIGIT *digits = BIGNUM_DIGITS(x); + rb_uint128_t unsigned_result = {0}; + rb_uint128_from_bignum_digits_fallback(&unsigned_result, digits, length); + if (BIGNUM_NEGATIVE_P(x)) { + // Check if value fits in signed 128-bit (max negative is 2^127) + uint64_t max_neg_high = (uint64_t)1 << 63; + if (unsigned_result.parts.high > max_neg_high || (unsigned_result.parts.high == max_neg_high && unsigned_result.parts.low > 0)) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + // Convert from absolute value to two's complement (Ruby stores negative as absolute value) + rb_uint128_twos_complement_negate(&unsigned_result); + result.parts.low = unsigned_result.parts.low; + result.parts.high = (int64_t)unsigned_result.parts.high; // Sign extend + } + else { + // Check if value fits in signed 128-bit (max positive is 2^127 - 1) + // Max positive: high = 0x7FFFFFFFFFFFFFFF, low = 0xFFFFFFFFFFFFFFFF + uint64_t max_pos_high = ((uint64_t)1 << 63) - 1; + if (unsigned_result.parts.high > max_pos_high) { + rb_raise(rb_eRangeError, "bignum too big to convert into 'signed 128-bit integer'"); + } + result.parts.low = unsigned_result.parts.low; + result.parts.high = unsigned_result.parts.high; + } +#endif + return result; + } + else { + rb_raise(rb_eTypeError, "not an integer"); + } +} + +VALUE +rb_uint128_to_numeric(rb_uint128_t n) +{ +#ifdef HAVE_UINT128_T + if (n.value <= (uint128_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.value); + } + return rb_uint128t2big(n.value); +#else + // If high part is zero and low part fits in fixnum + if (n.parts.high == 0 && n.parts.low <= (uint64_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.parts.low); + } + // Convert to bignum by building it from the two 64-bit parts + VALUE bignum = rb_ull2big(n.parts.low); + if (n.parts.high > 0) { + VALUE high_bignum = rb_ull2big(n.parts.high); + // Multiply high part by 2^64 and add to low part + VALUE shifted_value = rb_int_lshift(high_bignum, INT2FIX(64)); + bignum = rb_int_plus(bignum, shifted_value); + } + return bignum; +#endif +} + +VALUE +rb_int128_to_numeric(rb_int128_t n) +{ +#ifdef HAVE_UINT128_T + if (FIXABLE(n.value)) { + return LONG2FIX((long)n.value); + } + return rb_int128t2big(n.value); +#else + int64_t high = (int64_t)n.parts.high; + // If it's a small positive value that fits in fixnum + if (high == 0 && n.parts.low <= (uint64_t)RUBY_FIXNUM_MAX) { + return LONG2FIX((long)n.parts.low); + } + // Check if it's negative (high bit of high part is set) + if (high < 0) { + // Negative value - convert from two's complement to absolute value + rb_uint128_t unsigned_value = {0}; + if (n.parts.low == 0) { + unsigned_value.parts.low = 0; + unsigned_value.parts.high = ~n.parts.high + 1; + } + else { + unsigned_value.parts.low = ~n.parts.low + 1; + unsigned_value.parts.high = ~n.parts.high + (unsigned_value.parts.low == 0 ? 1 : 0); + } + VALUE bignum = rb_uint128_to_numeric(unsigned_value); + return rb_int_uminus(bignum); + } + else { + // Positive value + return rb_uint128_to_numeric(*(rb_uint128_t*)&n); + } +#endif +} + /******************************************************************** * * Document-class: Integer diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 1e4a6e2fd86c40..9ff22b4bb356eb 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -405,6 +405,11 @@ def test_zero_length_get_string :u64 => [0, 2**64-1], :s64 => [-2**63, 0, 2**63-1], + :U128 => [0, 2**64, 2**127-1, 2**128-1], + :S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1], + :u128 => [0, 2**64, 2**127-1, 2**128-1], + :s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1], + :F32 => [-1.0, 0.0, 0.5, 1.0, 128.0], :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0], } @@ -759,4 +764,146 @@ def test_bug_21210 assert_predicate buf, :valid? end + + def test_128_bit_integers + buffer = IO::Buffer.new(32) + + # Test unsigned 128-bit integers + test_values_u128 = [ + 0, + 1, + 2**64 - 1, + 2**64, + 2**127 - 1, + 2**128 - 1, + ] + + test_values_u128.each do |value| + buffer.set_value(:u128, 0, value) + assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}" + + buffer.set_value(:U128, 0, value) + assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}" + end + + # Test signed 128-bit integers + test_values_s128 = [ + -2**127, + -2**63 - 1, + -1, + 0, + 1, + 2**63, + 2**127 - 1, + ] + + test_values_s128.each do |value| + buffer.set_value(:s128, 0, value) + assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}" + + buffer.set_value(:S128, 0, value) + assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}" + end + + # Test size_of + assert_equal 16, IO::Buffer.size_of(:u128) + assert_equal 16, IO::Buffer.size_of(:U128) + assert_equal 16, IO::Buffer.size_of(:s128) + assert_equal 16, IO::Buffer.size_of(:S128) + assert_equal 32, IO::Buffer.size_of([:u128, :u128]) + end + + def test_integer_endianness_swapping + # Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte + host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN + host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN + + # Test values that will produce different byte patterns when swapped + # Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value] + # expected_swapped_value is the result when writing as le_type and reading as be_type + # (or vice versa) on a little-endian host + test_cases = [ + [:u16, :U16, 0x1234, 0x3412], + [:s16, :S16, 0x1234, 0x3412], + [:u32, :U32, 0x12345678, 0x78563412], + [:s32, :S32, 0x12345678, 0x78563412], + [:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301], + [:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991], + [:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301], + [:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301], + [:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE], + [:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412], + [:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831], + [:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301], + ] + + test_cases.each do |le_type, be_type, value, expected_swapped| + buffer_size = IO::Buffer.size_of(le_type) + buffer = IO::Buffer.new(buffer_size * 2) + + # Test little-endian round-trip + buffer.set_value(le_type, 0, value) + result_le = buffer.get_value(le_type, 0) + assert_equal value, result_le, "#{le_type}: round-trip failed" + + # Test big-endian round-trip + buffer.set_value(be_type, buffer_size, value) + result_be = buffer.get_value(be_type, buffer_size) + assert_equal value, result_be, "#{be_type}: round-trip failed" + + # Verify byte patterns are different when endianness differs from host + le_bytes = buffer.get_string(0, buffer_size) + be_bytes = buffer.get_string(buffer_size, buffer_size) + + if host_is_le + # On little-endian host: le_type should match host, be_type should be swapped + # So the byte patterns should be different (unless value is symmetric) + # Read back with opposite endianness to verify swapping + result_le_read_as_be = buffer.get_value(be_type, 0) + result_be_read_as_le = buffer.get_value(le_type, buffer_size) + + # The swapped reads should NOT equal the original value (unless it's symmetric) + # For most values, this will be different + if value != 0 && value != -1 && value.abs != 1 + refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host" + refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host" + end + + # Verify that reading back with correct endianness works + assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host" + assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)" + elsif host_is_be + # On big-endian host: be_type should match host, le_type should be swapped + result_le_read_as_be = buffer.get_value(be_type, 0) + result_be_read_as_le = buffer.get_value(le_type, buffer_size) + + # The swapped reads should NOT equal the original value (unless it's symmetric) + if value != 0 && value != -1 && value.abs != 1 + refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host" + refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host" + end + + # Verify that reading back with correct endianness works + assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host" + assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)" + end + + # Verify that when we write with one endianness and read with the opposite, + # we get the expected swapped value + buffer.set_value(le_type, 0, value) + swapped_value_le_to_be = buffer.get_value(be_type, 0) + assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value" + + # Also verify the reverse direction + buffer.set_value(be_type, buffer_size, value) + swapped_value_be_to_le = buffer.get_value(le_type, buffer_size) + assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value" + + # Verify that writing the swapped value back and reading with original endianness + # gives us the original value (double-swap should restore original) + buffer.set_value(be_type, 0, swapped_value_le_to_be) + round_trip_value = buffer.get_value(le_type, 0) + assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value" + end + end end From 734dab5ec885107b9d1da81d5d071927e59fddee Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 5 Dec 2025 16:21:55 +0000 Subject: [PATCH 1624/2435] [ruby/stringio] [DOC] Link to on-page section, not class File doc https://github.com/ruby/stringio/commit/dc93aa51d2 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 603d9c5e22611e..5556426fdc387a 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -774,7 +774,7 @@ strio_set_lineno(VALUE self, VALUE lineno) * binmode -> self * * Sets the data mode in +self+ to binary mode; - * see {Data Mode}[rdoc-ref:File@Data+Mode]. + * see {Data Mode}[rdoc-ref:StringIO@Data+Mode]. * */ static VALUE From bbef73b2ff1fba2d72d16f5ee582063e6b6e8a1f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 22 Nov 2025 21:23:25 +0000 Subject: [PATCH 1625/2435] [DOC] Better multibyte-character data --- doc/string/aref.rdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/string/aref.rdoc b/doc/string/aref.rdoc index bd33c4c0504523..ee4c3d33d4e655 100644 --- a/doc/string/aref.rdoc +++ b/doc/string/aref.rdoc @@ -8,7 +8,7 @@ returns the 1-character substring found in self at character offset index: 'hello'[0] # => "h" 'hello'[4] # => "o" 'hello'[5] # => nil - 'тест'[2] # => "с" + 'Привет'[2] # => "и" 'こんにちは'[4] # => "は" With negative integer argument +index+ given, @@ -92,7 +92,7 @@ returns the matching substring of +self+, if found: 'hello'['ell'] # => "ell" 'hello'[''] # => "" 'hello'['nosuch'] # => nil - 'тест'['ес'] # => "ес" + 'Привет'['ив'] # => "ив" 'こんにちは'['んにち'] # => "んにち" Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. From da2c67388ab7db8102bd8fba6f8ed43df713ad66 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 18:22:34 +0000 Subject: [PATCH 1626/2435] [DOC] Tweaks for String#swapcase --- doc/string/swapcase.rdoc | 27 ++++++++++++++++++++------- string.c | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/string/swapcase.rdoc b/doc/string/swapcase.rdoc index 93859ec8237fcc..916e711b7ec7b2 100644 --- a/doc/string/swapcase.rdoc +++ b/doc/string/swapcase.rdoc @@ -5,15 +5,28 @@ Returns a string containing the characters in +self+, with cases reversed: Examples: - 'Hello World!'.swapcase # => "hELLO wORLD!" - 'тест'.swapcase # => "ТЕСТ" + 'Hello'.swapcase # => "hELLO" + 'Straße'.swapcase # => "sTRASSE" + 'Привет'.swapcase # => "пРИВЕТ" + 'RubyGems.org'.swapcase # => "rUBYgEMS.ORG" -Some characters (and even character sets) do not have casing: +The sizes of +self+ and the upcased result may differ: - '12345'.swapcase # => "12345" - 'こんにちは'.swapcase # => "こんにちは" + s = 'Straße' + s.size # => 6 + s.swapcase # => "sTRASSE" + s.swapcase.size # => 7 -The casing may be affected by the given +mapping+; -see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. +Some characters (and some character sets) do not have upcase and downcase versions; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: + + s = '1, 2, 3, ...' + s.swapcase == s # => true + s = 'こんにちは' + s.swapcase == s # => true + +The casing is affected by the given +mapping+, +which may be +:ascii+, +:fold+, or +:turkic+; +see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 34c79eca8e5cff..6f5040d8f86ace 100644 --- a/string.c +++ b/string.c @@ -8197,7 +8197,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(mapping) -> new_string + * swapcase(mapping = :ascii) -> new_string * * :include: doc/string/swapcase.rdoc * From e5e4175dbddb83eb5a53ec0d0cf3a5d0026c7bb9 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 18:01:35 +0000 Subject: [PATCH 1627/2435] [DOC] Tweaks for String#upcase --- doc/string/upcase.rdoc | 17 ++++++++++++----- string.c | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/string/upcase.rdoc b/doc/string/upcase.rdoc index 5a9cce217f7972..ad859e89736687 100644 --- a/doc/string/upcase.rdoc +++ b/doc/string/upcase.rdoc @@ -1,6 +1,9 @@ Returns a new string containing the upcased characters in +self+: - 'Hello, World!'.upcase # => "HELLO, WORLD!" + 'hello'.upcase # => "HELLO" + 'straße'.upcase # => "STRASSE" + 'привет'.upcase # => "ПРИВЕТ" + 'RubyGems.org'.upcase # => "RUBYGEMS.ORG" The sizes of +self+ and the upcased result may differ: @@ -9,12 +12,16 @@ The sizes of +self+ and the upcased result may differ: s.upcase # => "STRASSE" s.upcase.size # => 7 -Some characters (and some character sets) do not have upcased and downcased versions: +Some characters (and some character sets) do not have upcase and downcase versions; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: - s = 'よろしくお願いします' + s = '1, 2, 3, ...' + s.upcase == s # => true + s = 'こんにちは' s.upcase == s # => true -The casing may be affected by the given +mapping+; -see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. +The casing is affected by the given +mapping+, +which may be +:ascii+, +:fold+, or +:turkic+; +see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 6f5040d8f86ace..de8e37f5d3d1ce 100644 --- a/string.c +++ b/string.c @@ -7966,7 +7966,7 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * upcase(mapping) -> string + * upcase(mapping = :ascii) -> new_string * * :include: doc/string/upcase.rdoc */ From 2491a504ffd73e8fabad49fce020fcda484f0be2 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 17:16:37 +0000 Subject: [PATCH 1628/2435] [DOC] Tweaks for String#downcase --- doc/string/downcase.rdoc | 20 ++++++++++++++------ string.c | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/string/downcase.rdoc b/doc/string/downcase.rdoc index 0fb67daaebe634..d5fffa037b89d0 100644 --- a/doc/string/downcase.rdoc +++ b/doc/string/downcase.rdoc @@ -1,12 +1,20 @@ Returns a new string containing the downcased characters in +self+: - 'Hello, World!'.downcase # => "hello, world!" - 'ТЕСТ'.downcase # => "тест" - 'よろしくお願いします'.downcase # => "よろしくお願いします" + 'HELLO'.downcase # => "hello" + 'STRAẞE'.downcase # => "straße" + 'ПРИВЕТ'.downcase # => "привет" + 'RubyGems.org'.downcase # => "rubygems.org" -Some characters do not have upcased and downcased versions. +Some characters (and some character sets) do not have upcase and downcase versions; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: -The casing may be affected by the given +mapping+; -see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + s = '1, 2, 3, ...' + s.downcase == s # => true + s = 'こんにちは' + s.downcase == s # => true + +The casing is affected by the given +mapping+, +which may be +:ascii+, +:fold+, or +:turkic+; +see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index de8e37f5d3d1ce..ce90c07d158d80 100644 --- a/string.c +++ b/string.c @@ -8052,7 +8052,7 @@ rb_str_downcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * downcase(mapping) -> string + * downcase(mapping = :ascii) -> new_string * * :include: doc/string/downcase.rdoc * From bd64cf00a2e9c295b854c8a4bbb79672bfa1654a Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 21 Nov 2025 14:23:04 +0000 Subject: [PATCH 1629/2435] [DOC] Tweaks for String#capitalize --- doc/string/capitalize.rdoc | 28 ++++++++++++++++++++++++++++ string.c | 23 ++--------------------- 2 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 doc/string/capitalize.rdoc diff --git a/doc/string/capitalize.rdoc b/doc/string/capitalize.rdoc new file mode 100644 index 00000000000000..9b26c0215342db --- /dev/null +++ b/doc/string/capitalize.rdoc @@ -0,0 +1,28 @@ +Returns a string containing the characters in +self+, +each with possibly changed case: + +- The first character made uppercase. +- All other characters are made lowercase. + +Examples: + + 'hello'.capitalize # => "Hello" + 'HELLO'.capitalize # => "Hello" + 'straße'.capitalize # => "Straße" # Lowercase 'ß' not changed. + 'STRAẞE'.capitalize # => "Straße" # Uppercase 'ẞ' downcased to 'ß'. + 'привет'.capitalize # => "Привет" + 'ПРИВЕТ'.capitalize # => "Привет" + +Some characters (and some character sets) do not have upcase and downcase versions; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: + + s = '1, 2, 3, ...' + s.capitalize == s # => true + s = 'こんにちは' + s.capitalize == s # => true + +The casing is affected by the given +mapping+, +which may be +:ascii+, +:fold+, or +:turkic+; +see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index ce90c07d158d80..9327306384a232 100644 --- a/string.c +++ b/string.c @@ -8118,29 +8118,10 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize(mapping = :ascii) -> string + * capitalize(mapping = :ascii) -> new_string * - * Returns a string containing the characters in +self+, - * each with possibly changed case: + * :include: doc/string/capitalize.rdoc * - * - The first character is upcased. - * - All other characters are downcased. - * - * Examples: - * - * 'hello world'.capitalize # => "Hello world" - * 'HELLO WORLD'.capitalize # => "Hello world" - * - * Some characters do not have upcase and downcase, and so are not changed; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: - * - * '1, 2, 3, ...'.capitalize # => "1, 2, 3, ..." - * - * The casing is affected by the given +mapping+, - * which may be +:ascii+, +:fold+, or +:turkic+; - * see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. - * - * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE From 21f9a647fb7405e0a22656ed6405f86811a22a1f Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 4 Dec 2025 15:57:46 -0600 Subject: [PATCH 1630/2435] [ruby/stringio] [DOC] Class doc for StringIO (https://github.com/ruby/stringio/pull/178) https://github.com/ruby/stringio/commit/6449251678 --- doc/stringio/stringio.md | 692 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 683 insertions(+), 9 deletions(-) diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md index 345fc5f20707df..aebfa6d6f1f4c2 100644 --- a/doc/stringio/stringio.md +++ b/doc/stringio/stringio.md @@ -1,18 +1,39 @@ -\IO streams for strings, with access similar to -{IO}[rdoc-ref:IO]; -see {IO}[rdoc-ref:IO]. +\Class \StringIO supports accessing a string as a stream, +similar in some ways to [class IO][io class]. -### About the Examples +You can create a \StringIO instance using: + +- StringIO.new: returns a new \StringIO object containing the given string. +- StringIO.open: passes a new \StringIO object to the given block. + +Like an \IO stream, a \StringIO stream has certain properties: + +- **Read/write mode**: whether the stream may be read, written, appended to, etc.; + see [Read/Write Mode][read/write mode]. +- **Data mode**: text-only or binary; + see [Data Mode][data mode]. +- **Encodings**: internal and external encodings; + see [Encodings][encodings]. +- **Position**: where in the stream the next read or write is to occur; + see [Position][position]. +- **Line number**: a special, line-oriented, "position" (different from the position mentioned above); + see [Line Number][line number]. +- **Open/closed**: whether the stream is open or closed, for reading or writing. + see [Open/Closed Streams][open/closed streams]. +- **BOM**: byte mark order; + see [Byte Order Mark][bom (byte order mark)]. + +## About the Examples Examples on this page assume that \StringIO has been required: -``` +```ruby require 'stringio' ``` -And that these constants have been defined: +And that this constant has been defined: -``` +```ruby TEXT = <'r': read-only | No | Anywhere | Error | +| 'w': write-only | Yes | Error | Anywhere | +| 'a': append-only | No | Error | End only | +| 'r+': read/write | No | Anywhere | Anywhere | +| 'w+': read-write | Yes | Anywhere | Anywhere | +| 'a+': read/append | No | Anywhere | End only | + +Each section below describes a read/write mode. + +Any of the modes may be given as a string or as file constants; +example: + +```ruby +strio = StringIO.new('foo', 'a') +strio = StringIO.new('foo', File::WRONLY | File::APPEND) +``` + +#### `'r'`: Read-Only + +Mode specified as one of: + +- String: `'r'`. +- Constant: `File::RDONLY`. + +Initial state: + +```ruby +strio = StringIO.new('foobarbaz', 'r') +strio.pos # => 0 # Beginning-of-stream. +strio.string # => "foobarbaz" # Not cleared. +``` + +May be read anywhere: + +```ruby +strio.gets(3) # => "foo" +strio.gets(3) # => "bar" +strio.pos = 9 +strio.gets(3) # => nil +``` + +May not be written: + +```ruby +strio.write('foo') # Raises IOError: not opened for writing +``` + +#### `'w'`: Write-Only + +Mode specified as one of: + +- String: `'w'`. +- Constant: `File::WRONLY`. + +Initial state: + +```ruby +strio = StringIO.new('foo', 'w') +strio.pos # => 0 # Beginning of stream. +strio.string # => "" # Initially cleared. +``` + +May be written anywhere (even past end-of-stream): + +```ruby +strio.write('foobar') +strio.string # => "foobar" +strio.rewind +strio.write('FOO') +strio.string # => "FOObar" +strio.pos = 3 +strio.write('BAR') +strio.string # => "FOOBAR" +strio.pos = 9 +strio.write('baz') +strio.string # => "FOOBAR\u0000\u0000\u0000baz" # Null-padded. +``` + +May not be read: + +```ruby +strio.read # Raises IOError: not opened for reading +``` + +#### `'a'`: Append-Only + +Mode specified as one of: + +- String: `'a'`. +- Constant: `File::WRONLY | File::APPEND`. + +Initial state: + +```ruby +strio = StringIO.new('foo', 'a') +strio.pos # => 0 # Beginning-of-stream. +strio.string # => "foo" # Not cleared. +``` + +May be written only at the end; position does not affect writing: + +```ruby +strio.write('bar') +strio.string # => "foobar" +strio.write('baz') +strio.string # => "foobarbaz" +strio.pos = 400 +strio.write('bat') +strio.string # => "foobarbazbat" +``` + +May not be read: + +```ruby +strio.gets # Raises IOError: not opened for reading +``` + +#### `'r+'`: Read/Write + +Mode specified as one of: + +- String: `'r+'`. +- Constant: `File::RDRW`. + +Initial state: + +```ruby +strio = StringIO.new('foobar', 'r+') +strio.pos # => 0 # Beginning-of-stream. +strio.string # => "foobar" # Not cleared. +``` + +May be written anywhere (even past end-of-stream): + +```ruby +strio.write('FOO') +strio.string # => "FOObar" +strio.write('BAR') +strio.string # => "FOOBAR" +strio.write('BAZ') +strio.string # => "FOOBARBAZ" +strio.pos = 12 +strio.write('BAT') +strio.string # => "FOOBARBAZ\u0000\u0000\u0000BAT" # Null padded. +``` + +May be read anywhere: + +```ruby +strio.pos = 0 +strio.gets(3) # => "FOO" +strio.pos = 6 +strio.gets(3) # => "BAZ" +strio.pos = 400 +strio.gets(3) # => nil +``` + +#### `'w+'`: Read/Write (Initially Clear) + +Mode specified as one of: + +- String: `'w+'`. +- Constant: `File::RDWR | File::TRUNC`. + +Initial state: + +```ruby +strio = StringIO.new('foo', 'w+') +strio.pos # => 0 # Beginning-of-stream. +strio.string # => "" # Truncated. +``` + +May be written anywhere (even past end-of-stream): + +```ruby +strio.write('foobar') +strio.string # => "foobar" +strio.rewind +strio.write('FOO') +strio.string # => "FOObar" +strio.write('BAR') +strio.string # => "FOOBAR" +strio.write('BAZ') +strio.string # => "FOOBARBAZ" +strio.pos = 12 +strio.write('BAT') +strio.string # => "FOOBARBAZ\u0000\u0000\u0000BAT" # Null-padded. +``` + +May be read anywhere: + +```ruby +strio.rewind +strio.gets(3) # => "FOO" +strio.gets(3) # => "BAR" +strio.pos = 12 +strio.gets(3) # => "BAT" +strio.pos = 400 +strio.gets(3) # => nil +``` + +#### `'a+'`: Read/Append + +Mode specified as one of: + +- String: `'a+'`. +- Constant: `File::RDWR | File::APPEND`. + +Initial state: + +```ruby +strio = StringIO.new('foo', 'a+') +strio.pos # => 0 # Beginning-of-stream. +strio.string # => "foo" # Not cleared. +``` + +May be written only at the end; #rewind; position does not affect writing: + +```ruby +strio.write('bar') +strio.string # => "foobar" +strio.write('baz') +strio.string # => "foobarbaz" +strio.pos = 400 +strio.write('bat') +strio.string # => "foobarbazbat" +``` + +May be read anywhere: + +```ruby +strio.rewind +strio.gets(3) # => "foo" +strio.gets(3) # => "bar" +strio.pos = 9 +strio.gets(3) # => "bat" +strio.pos = 400 +strio.gets(3) # => nil +``` + +### Data Mode + +To specify whether the stream is to be treated as text or as binary data, +either of the following may be suffixed to any of the string read/write modes above: + +- `'t'`: Text; + initializes the encoding as Encoding::UTF_8. +- `'b'`: Binary; + initializes the encoding as Encoding::ASCII_8BIT. + +If neither is given, the stream defaults to text data. + +Examples: + +```ruby +strio = StringIO.new('foo', 'rt') +strio.external_encoding # => # +data = "\u9990\u9991\u9992\u9993\u9994" +strio = StringIO.new(data, 'rb') +strio.external_encoding # => # +``` + +When the data mode is specified, the read/write mode may not be omitted: + +```ruby +StringIO.new(data, 'b') # Raises ArgumentError: invalid access mode b +``` + +A text stream may be changed to binary by calling instance method #binmode; +a binary stream may not be changed to text. + +### Encodings + +A stream has an encoding; see the [encodings document][encodings document]. + +The initial encoding for a new or re-opened stream depends on its [data mode][data mode]: -RUSSIAN = 'тест' -DATA = "\u9990\u9991\u9992\u9993\u9994" +- Text: `Encoding::UTF_8`. +- Binary: `Encoding::ASCII_8BIT`. + +These instance methods are relevant: + +- #external_encoding: returns the current encoding of the stream as an `Encoding` object. +- #internal_encoding: returns +nil+; a stream does not have an internal encoding. +- #set_encoding: sets the encoding for the stream. +- #set_encoding_by_bom: sets the encoding for the stream to the stream's BOM (byte order mark). + +Examples: + +```ruby +strio = StringIO.new('foo', 'rt') # Text mode. +strio.external_encoding # => # +data = "\u9990\u9991\u9992\u9993\u9994" +strio = StringIO.new(data, 'rb') # Binary mode. +strio.external_encoding # => # +strio = StringIO.new('foo') +strio.external_encoding # => # +strio.set_encoding('US-ASCII') +strio.external_encoding # => # +``` + +### Position + +A stream has a _position_, and integer offset (in bytes) into the stream. +The initial position of a stream is zero. + +#### Getting and Setting the Position + +Each of these methods initializes (to zero) the position of a new or re-opened stream: + +- ::new: returns a new stream. +- ::open: passes a new stream to the block. +- #reopen: re-initializes the stream. + +Each of these methods queries, gets, or sets the position, without otherwise changing the stream: + +- #eof?: returns whether the position is at end-of-stream. +- #pos: returns the position. +- #pos=: sets the position. +- #rewind: sets the position to zero. +- #seek: sets the position. + +Examples: + +```ruby +strio = StringIO.new('foobar') +strio.pos # => 0 +strio.pos = 3 +strio.pos # => 3 +strio.eof? # => false +strio.rewind +strio.pos # => 0 +strio.seek(0, IO::SEEK_END) +strio.pos # => 6 +strio.eof? # => true +``` + +#### Position Before and After Reading + +Except for #pread, a stream reading method (see [Basic Reading][basic reading]) +begins reading at the current position. + +Except for #pread, a read method advances the position past the read substring. + +Examples: + +```ruby +strio = StringIO.new(TEXT) +strio.string # => "First line\nSecond line\n\nFourth line\nFifth line\n" +strio.pos # => 0 +strio.getc # => "F" +strio.pos # => 1 +strio.gets # => "irst line\n" +strio.pos # => 11 +strio.pos = 24 +strio.gets # => "Fourth line\n" +strio.pos # => 36 + +strio = StringIO.new('тест') # Four 2-byte characters. +strio.pos = 0 # At first byte of first character. +strio.read # => "тест" +strio.pos = 1 # At second byte of first character. +strio.read # => "\x82ест" +strio.pos = 2 # At first of second character. +strio.read # => "ест" + +strio = StringIO.new(TEXT) +strio.pos = 15 +a = [] +strio.each_line {|line| a.push(line) } +a # => ["nd line\n", "\n", "Fourth line\n", "Fifth line\n"] +strio.pos # => 47 ## End-of-stream. +``` + +#### Position Before and After Writing + +Each of these methods begins writing at the current position, +and advances the position to the end of the written substring: + +- #putc: writes the given character. +- #write: writes the given objects as strings. +- [Kernel#puts][kernel#puts]: writes given objects as strings, each followed by newline. + +Examples: + +```ruby +strio = StringIO.new('foo') +strio.pos # => 0 +strio.putc('b') +strio.string # => "boo" +strio.pos # => 1 +strio.write('r') +strio.string # => "bro" +strio.pos # => 2 +strio.puts('ew') +strio.string # => "brew\n" +strio.pos # => 5 +strio.pos = 8 +strio.write('foo') +strio.string # => "brew\n\u0000\u0000\u0000foo" +strio.pos # => 11 +``` + +Each of these methods writes _before_ the current position, and decrements the position +so that the written data is next to be read: + +- #ungetbyte: unshifts the given byte. +- #ungetc: unshifts the given character. + +Examples: + +```ruby +strio = StringIO.new('foo') +strio.pos = 2 +strio.ungetc('x') +strio.pos # => 1 +strio.string # => "fxo" +strio.ungetc('x') +strio.pos # => 0 +strio.string # => "xxo" +``` + +This method does not affect the position: + +- #truncate: truncates the stream's string to the given size. + +Examples: + +```ruby +strio = StringIO.new('foobar') +strio.pos # => 0 +strio.truncate(3) +strio.string # => "foo" +strio.pos # => 0 +strio.pos = 500 +strio.truncate(0) +strio.string # => "" +strio.pos # => 500 +``` + +### Line Number + +A stream has a line number, which initially is zero: + +- Method #lineno returns the line number. +- Method #lineno= sets the line number. + +The line number can be affected by reading (but never by writing); +in general, the line number is incremented each time the record separator (default: `"\n"`) is read. + +Examples: + +```ruby +strio = StringIO.new(TEXT) +strio.string # => "First line\nSecond line\n\nFourth line\nFifth line\n" +strio.lineno # => 0 +strio.gets # => "First line\n" +strio.lineno # => 1 +strio.getc # => "S" +strio.lineno # => 1 +strio.gets # => "econd line\n" +strio.lineno # => 2 +strio.gets # => "\n" +strio.lineno # => 3 +strio.gets # => "Fourth line\n" +strio.lineno # => 4 +``` + +Setting the position does not affect the line number: + +```ruby +strio.pos = 0 +strio.lineno # => 4 +strio.gets # => "First line\n" +strio.pos # => 11 +strio.lineno # => 5 +``` + +And setting the line number does not affect the position: + +```ruby +strio.lineno = 10 +strio.pos # => 11 +strio.gets # => "Second line\n" +strio.lineno # => 11 +strio.pos # => 23 ``` + +### Open/Closed Streams + +A new stream is open for either reading or writing, and may be open for both; +see [Read/Write Mode][read/write mode]. + +Each of these methods initializes the read/write mode for a new or re-opened stream: + +- ::new: returns a new stream. +- ::open: passes a new stream to the block. +- #reopen: re-initializes the stream. + +Other relevant methods: + +- #close: closes the stream for both reading and writing. +- #close_read: closes the stream for reading. +- #close_write: closes the stream for writing. +- #closed?: returns whether the stream is closed for both reading and writing. +- #closed_read?: returns whether the stream is closed for reading. +- #closed_write?: returns whether the stream is closed for writing. + +### BOM (Byte Order Mark) + +The string provided for ::new, ::open, or #reopen +may contain an optional [BOM][bom] (byte order mark) at the beginning of the string; +the BOM can affect the stream's encoding. + +The BOM (if provided): + +- Is stored as part of the stream's string. +- Does _not_ immediately affect the encoding. +- Is _initially_ considered part of the stream. + +```ruby +utf8_bom = "\xEF\xBB\xBF" +string = utf8_bom + 'foo' +string.bytes # => [239, 187, 191, 102, 111, 111] +strio.string.bytes.take(3) # => [239, 187, 191] # The BOM. +strio = StringIO.new(string, 'rb') +strio.string.bytes # => [239, 187, 191, 102, 111, 111] # BOM is part of the stored string. +strio.external_encoding # => # # Default for a binary stream. +strio.gets # => "\xEF\xBB\xBFfoo" # BOM is part of the stream. +``` + +You can call instance method #set_encoding_by_bom to "activate" the stored BOM; +after doing so the BOM: + +- Is _still_ stored as part of the stream's string. +- _Determines_ (and may have changed) the stream's encoding. +- Is _no longer_ considered part of the stream. + +```ruby +strio.set_encoding_by_bom +strio.string.bytes # => [239, 187, 191, 102, 111, 111] # BOM is still part of the stored string. +strio.external_encoding # => # # The new encoding. +strio.rewind # => 0 +strio.gets # => "foo" # BOM is not part of the stream. +``` + +## Basic Stream \IO + +### Basic Reading + +You can read from the stream using these instance methods: + +- #getbyte: reads and returns the next byte. +- #getc: reads and returns the next character. +- #gets: reads and returns all or part of the next line. +- #read: reads and returns all or part of the remaining data in the stream. +- #readlines: reads the remaining data the stream and returns an array of its lines. +- [Kernel#readline][kernel#readline]: like #gets, but raises an exception if at end-of-stream. + +You can iterate over the stream using these instance methods: + +- #each_byte: reads each remaining byte, passing it to the block. +- #each_char: reads each remaining character, passing it to the block. +- #each_codepoint: reads each remaining codepoint, passing it to the block. +- #each_line: reads all or part of each remaining line, passing the read string to the block + +This instance method is useful in a multi-threaded application: + +- #pread: reads and returns all or part of the stream. + +### Basic Writing + +You can write to the stream, advancing the position, using these instance methods: + +- #putc: writes a given character. +- #write: writes the given objects as strings. +- [Kernel#puts][kernel#puts] writes given objects as strings, each followed by newline. + +You can "unshift" to the stream using these instance methods; +each writes _before_ the current position, and decrements the position +so that the written data is next to be read. + +- #ungetbyte: unshifts the given byte. +- #ungetc: unshifts the given character. + +One more writing method: + +- #truncate: truncates the stream's string to the given size. + +## Line \IO + +Reading: + +- #gets: reads and returns the next line. +- [Kernel#readline][kernel#readline]: like #gets, but raises an exception if at end-of-stream. +- #readlines: reads the remaining data the stream and returns an array of its lines. +- #each_line: reads each remaining line, passing it to the block + +Writing: + +- [Kernel#puts][kernel#puts]: writes given objects, each followed by newline. + +## Character \IO + +Reading: + +- #each_char: reads each remaining character, passing it to the block. +- #getc: reads and returns the next character. + +Writing: + +- #putc: writes the given character. +- #ungetc.: unshifts the given character. + +## Byte \IO + +Reading: + +- #each_byte: reads each remaining byte, passing it to the block. +- #getbyte: reads and returns the next byte. + +Writing: + +- #ungetbyte: unshifts the given byte. + +## Codepoint \IO + +Reading: + +- #each_codepoint: reads each remaining codepoint, passing it to the block. + +[bom]: https://en.wikipedia.org/wiki/Byte_order_mark +[encodings document]: https://docs.ruby-lang.org/en/master/encodings_rdoc.html +[io class]: https://docs.ruby-lang.org/en/master/IO.html +[kernel#puts]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-puts +[kernel#readline]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-readline + +[basic reading]: rdoc-ref:StringIO@Basic+Reading +[basic writing]: rdoc-ref:StringIO@Basic+Writing +[bom (byte order mark)]: rdoc-ref:StringIO@BOM+-28Byte+Order+Mark-29 +[data mode]: rdoc-ref:StringIO@Data+Mode +[encodings]: rdoc-ref:StringIO@Encodings +[end-of-stream]: rdoc-ref:StringIO@End-of-Stream +[line number]: rdoc-ref:StringIO@Line+Number +[open/closed streams]: rdoc-ref:StringIO@Open-2FClosed+Streams +[position]: rdoc-ref:StringIO@Position +[read/write mode]: rdoc-ref:StringIO@Read-2FWrite+Mode From d490247df22fc1e800a6590fba52c5801b94302e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 17:30:14 +0900 Subject: [PATCH 1631/2435] [DOC] Link global variables to command line options --- doc/language/globals.md | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index b22363f1da1b71..716e62a7df87d5 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -176,7 +176,7 @@ No \English. ### `$/` (Input Record Separator) An input record separator, initially newline. -Set by the command-line option `-0`. +Set by the [command-line option `-0`]. Setting to non-nil value by other than the command-line option is deprecated. @@ -188,7 +188,9 @@ Aliased as `$-0`. ### `$\` (Output Record Separator) An output record separator, initially `nil`. -Copied from `$/` when the command-line option `-l` is given. + +Copied from `$/` when the [command-line option `-l`] is +given. Setting to non-nil value by other than the command-line option is deprecated. @@ -328,8 +330,8 @@ The value returned by method ARGF.filename. ### `$DEBUG` -Initially `true` if command-line option `-d` or `--debug` is given, -otherwise initially `false`; +Initially `true` if [command-line option `-d`] or +[`--debug`][command-line option `-d`] is given, otherwise initially `false`; may be set to either value in the running program. When `true`, prints each raised exception to `$stderr`. @@ -338,8 +340,8 @@ Aliased as `$-d`. ### `$VERBOSE` -Initially `true` if command-line option `-v` or `-w` is given, -otherwise initially `false`; +Initially `true` if [command-line option `-v`] or +[`-w`][command-line option `-w`] is given, otherwise initially `false`; may be set to either value, or to `nil`, in the running program. When `true`, enables Ruby warnings. @@ -353,7 +355,7 @@ Aliased as `$-v` and `$-w`. ### `$-F` The default field separator in String#split; must be a String or a -Regexp, and can be set with command-line option `-F`. +Regexp, and can be set with [command-line option `-F`]. Setting to non-nil value by other than the command-line option is deprecated. @@ -362,28 +364,28 @@ Aliased as `$;`. ### `$-a` -Whether command-line option `-a` was given; read-only. +Whether [command-line option `-a`] was given; read-only. ### `$-i` -Contains the extension given with command-line option `-i`, +Contains the extension given with [command-line option `-i`], or `nil` if none. An alias of ARGF.inplace_mode. ### `$-l` -Whether command-line option `-l` was set; read-only. +Whether [command-line option `-l`] was set; read-only. ### `$-p` -Whether command-line option `-p` was given; read-only. +Whether [command-line option `-p`] was given; read-only. ### `$F` -If the command line option `-a` is given, the array obtained by -splitting `$_` by `$-F` is assigned at the start of each `-l`/`-p` -loop. +If the [command-line option `-a`] is given, the array +obtained by splitting `$_` by `$-F` is assigned at the start of each +`-l`/`-p` loop. ## Deprecated @@ -594,3 +596,14 @@ Output: "Bar\n" "Baz\n" ``` + + +[command-line option `-0`]: rdoc-ref:language/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29 +[command-line option `-F`]: rdoc-ref:language/options.md@F-3A+Set+Input+Field+Separator +[command-line option `-a`]: rdoc-ref:language/options.md@a-3A+Split+Input+Lines+into+Fields +[command-line option `-d`]: rdoc-ref:language/options.md@d-3A+Set+-24DEBUG+to+true +[command-line option `-i`]: rdoc-ref:language/options.md@i-3A+Set+ARGF+In-Place+Mode +[command-line option `-l`]: rdoc-ref:language/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines +[command-line option `-p`]: rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing +[command-line option `-v`]: rdoc-ref:language/options.md@v-3A+Print+Version-3B+Set+-24VERBOSE +[command-line option `-w`]: rdoc-ref:language/options.md@w-3A+Synonym+for+-W1 From 7259c18c3aa89ce282a5f7ebad68c62980c605d5 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 5 Dec 2025 23:00:13 -0800 Subject: [PATCH 1632/2435] [DOC] Update NEWS about ZJIT (#15426) --- NEWS.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index b44df19ca2b9b2..3468f479596293 100644 --- a/NEWS.md +++ b/NEWS.md @@ -383,18 +383,19 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT +* ZJIT + * Introduce an experimental method-based JIT compiler. + To enable `--zjit` support, build Ruby with Rust 1.85.0 or later. + * As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT. + We encourage experimentation with ZJIT, but advise against deploying it in production for now. + * Our goal is to make ZJIT faster than YJIT and production-ready in Ruby 4.1. * YJIT - * YJIT stats + * `RubyVM::YJIT.runtime_stats` * `ratio_in_yjit` no longer works in the default build. Use `--enable-yjit=stats` on `configure` to enable it on `--yjit-stats`. * Add `invalidate_everything` to default stats, which is incremented when every code is invalidated by TracePoint. * Add `mem_size:` and `call_threshold:` options to `RubyVM::YJIT.enable`. -* ZJIT - * Add an experimental method-based JIT compiler. - Use `--enable-zjit` on `configure` to enable the `--zjit` support. - * As of Ruby 4.0.0-preview1, ZJIT is not yet ready for speeding up most benchmarks. - Please refrain from evaluating ZJIT just yet. Stay tuned for the Ruby 4.0 release. * RJIT * `--rjit` is removed. We will move the implementation of the third-party JIT API to the [ruby/rjit](https://github.com/ruby/rjit) repository. From 180020e1e53b96a3080f3ac731cb32bbbdee31d5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Dec 2025 20:35:08 +1300 Subject: [PATCH 1633/2435] Fix `io_pwrite` fiber scheduler hook. (#15428) Fix io_pwrite fiber scheduler hook. --- io.c | 13 ++++++++++--- scheduler.c | 8 ++++++-- vm_dump.c | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/io.c b/io.c index 549e7e1317ef30..91537895d2b3a5 100644 --- a/io.c +++ b/io.c @@ -6254,6 +6254,14 @@ internal_pwrite_func(void *_arg) { struct prdwr_internal_arg *arg = _arg; + return (VALUE)pwrite(arg->fd, arg->buf, arg->count, arg->offset); +} + +static VALUE +pwrite_internal_call(VALUE _arg) +{ + struct prdwr_internal_arg *arg = (struct prdwr_internal_arg *)_arg; + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_pwrite_memory(scheduler, arg->io->self, arg->offset, arg->buf, arg->count, 0); @@ -6263,8 +6271,7 @@ internal_pwrite_func(void *_arg) } } - - return (VALUE)pwrite(arg->fd, arg->buf, arg->count, arg->offset); + return rb_io_blocking_region_wait(arg->io, internal_pwrite_func, arg, RUBY_IO_WRITABLE); } /* @@ -6316,7 +6323,7 @@ rb_io_pwrite(VALUE io, VALUE str, VALUE offset) arg.buf = RSTRING_PTR(tmp); arg.count = (size_t)RSTRING_LEN(tmp); - n = (ssize_t)rb_io_blocking_region_wait(fptr, internal_pwrite_func, &arg, RUBY_IO_WRITABLE); + n = (ssize_t)pwrite_internal_call((VALUE)&arg); if (n < 0) rb_sys_fail_path(fptr->pathv); rb_str_tmp_frozen_release(str, tmp); diff --git a/scheduler.c b/scheduler.c index 8351fc9945fd8f..3e45cbe83e1d44 100644 --- a/scheduler.c +++ b/scheduler.c @@ -460,12 +460,14 @@ rb_fiber_scheduler_current_for_threadptr(rb_thread_t *thread) } } -VALUE -rb_fiber_scheduler_current(void) +VALUE rb_fiber_scheduler_current(void) { + RUBY_ASSERT(ruby_thread_has_gvl_p()); + return rb_fiber_scheduler_current_for_threadptr(GET_THREAD()); } +// This function is allowed to be called without holding the GVL. VALUE rb_fiber_scheduler_current_for_thread(VALUE thread) { return rb_fiber_scheduler_current_for_threadptr(rb_thread_ptr(thread)); @@ -929,6 +931,8 @@ fiber_scheduler_io_pwrite(VALUE _argument) { VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset) { + + if (!rb_respond_to(scheduler, id_io_pwrite)) { return RUBY_Qundef; } diff --git a/vm_dump.c b/vm_dump.c index c1a8d707359aa0..e2b4804ab0d583 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1436,7 +1436,7 @@ rb_vm_bugreport(const void *ctx, FILE *errout) "---------------------------------------------------\n"); kprintf("Total ractor count: %u\n", vm->ractor.cnt); kprintf("Ruby thread count for this ractor: %u\n", rb_ec_ractor_ptr(ec)->threads.cnt); - if (rb_fiber_scheduler_get() != Qnil) { + if (ec->thread_ptr->scheduler != Qnil) { kprintf("Note that the Fiber scheduler is enabled\n"); } kputs("\n"); From 42f5654b69244b5892ff2c2eba8d838064d2cd9f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 6 Dec 2025 21:44:14 +1300 Subject: [PATCH 1634/2435] Yield to scheduler if interrupts are pending. (#14700) --- ext/-test-/scheduler/extconf.rb | 2 + ext/-test-/scheduler/scheduler.c | 88 +++++++++++++++ include/ruby/fiber/scheduler.h | 8 ++ scheduler.c | 20 ++++ .../test_interrupt_with_scheduler.rb | 55 +++++++++ test/fiber/scheduler.rb | 104 ++++++++++-------- thread.c | 13 ++- 7 files changed, 242 insertions(+), 48 deletions(-) create mode 100644 ext/-test-/scheduler/extconf.rb create mode 100644 ext/-test-/scheduler/scheduler.c create mode 100644 test/-ext-/scheduler/test_interrupt_with_scheduler.rb diff --git a/ext/-test-/scheduler/extconf.rb b/ext/-test-/scheduler/extconf.rb new file mode 100644 index 00000000000000..159699bd8e3ac1 --- /dev/null +++ b/ext/-test-/scheduler/extconf.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +create_makefile("-test-/scheduler") diff --git a/ext/-test-/scheduler/scheduler.c b/ext/-test-/scheduler/scheduler.c new file mode 100644 index 00000000000000..f3badceb92fcc6 --- /dev/null +++ b/ext/-test-/scheduler/scheduler.c @@ -0,0 +1,88 @@ +#include "ruby/ruby.h" +#include "ruby/thread.h" +#include "ruby/fiber/scheduler.h" + +/* + * Test extension for reproducing the gRPC interrupt handling bug. + * + * This reproduces the exact issue from grpc/grpc commit 69f229e (June 2025): + * https://github.com/grpc/grpc/commit/69f229edd1d79ab7a7dfda98e3aef6fd807adcad + * + * The bug occurs when: + * 1. A fiber scheduler uses Thread.handle_interrupt(::SignalException => :never) + * (like Async::Scheduler does) + * 2. Native code uses rb_thread_call_without_gvl in a retry loop that checks + * the interrupted flag and retries (like gRPC's completion queue) + * 3. A signal (SIGINT/SIGTERM) is sent + * 4. The unblock_func sets interrupted=1, but Thread.handle_interrupt defers the signal + * 5. The loop sees interrupted=1 and retries without yielding to the scheduler + * 6. The deferred interrupt never gets processed -> infinite hang + * + * The fix is in vm_check_ints_blocking() in thread.c, which should yield to + * the fiber scheduler when interrupts are pending, allowing the scheduler to + * detect Thread.pending_interrupt? and exit its run loop. + */ + +struct blocking_state { + volatile int interrupted; +}; + +static void +unblock_callback(void *argument) +{ + struct blocking_state *blocking_state = (struct blocking_state *)argument; + blocking_state->interrupted = 1; +} + +static void * +blocking_operation(void *argument) +{ + struct blocking_state *blocking_state = (struct blocking_state *)argument; + + while (true) { + struct timeval tv = {1, 0}; // 1 second timeout. + + int result = select(0, NULL, NULL, NULL, &tv); + + if (result == -1 && errno == EINTR) { + blocking_state->interrupted = 1; + return NULL; + } + + // Otherwise, timeout -> loop again. + } + + return NULL; +} + +static VALUE +scheduler_blocking_loop(VALUE self) +{ + struct blocking_state blocking_state = { + .interrupted = 0, + }; + + while (true) { + blocking_state.interrupted = 0; + + rb_thread_call_without_gvl( + blocking_operation, &blocking_state, + unblock_callback, &blocking_state + ); + + // The bug: When interrupted, loop retries without yielding to scheduler. + // With Thread.handle_interrupt(:never), this causes an infinite hang, + // because the deferred interrupt never gets a chance to be processed. + } while (blocking_state.interrupted); + + return Qnil; +} + +void +Init_scheduler(void) +{ + VALUE mBug = rb_define_module("Bug"); + VALUE mScheduler = rb_define_module_under(mBug, "Scheduler"); + + rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 0); +} diff --git a/include/ruby/fiber/scheduler.h b/include/ruby/fiber/scheduler.h index b06884f596665e..537a3a7bb27a5c 100644 --- a/include/ruby/fiber/scheduler.h +++ b/include/ruby/fiber/scheduler.h @@ -167,6 +167,14 @@ VALUE rb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE duration); */ VALUE rb_fiber_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv); +/** + * Yield to the scheduler, to be resumed on the next scheduling cycle. + * + * @param[in] scheduler Target scheduler. + * @return What `scheduler.yield` returns. + */ +VALUE rb_fiber_scheduler_yield(VALUE scheduler); + /* Description TBW */ #if 0 VALUE rb_fiber_scheduler_timeout_after(VALUE scheduler, VALUE timeout, VALUE exception, VALUE message); diff --git a/scheduler.c b/scheduler.c index 3e45cbe83e1d44..63c22b55aaa8d5 100644 --- a/scheduler.c +++ b/scheduler.c @@ -28,6 +28,8 @@ static ID id_scheduler_close; static ID id_block; static ID id_unblock; +static ID id_yield; + static ID id_timeout_after; static ID id_kernel_sleep; static ID id_process_wait; @@ -321,6 +323,7 @@ Init_Fiber_Scheduler(void) id_block = rb_intern_const("block"); id_unblock = rb_intern_const("unblock"); + id_yield = rb_intern_const("yield"); id_timeout_after = rb_intern_const("timeout_after"); id_kernel_sleep = rb_intern_const("kernel_sleep"); @@ -538,6 +541,23 @@ rb_fiber_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv) return rb_funcallv(scheduler, id_kernel_sleep, argc, argv); } +/** + * Document-method: Fiber::Scheduler#yield + * call-seq: yield + * + * Yield to the scheduler, to be resumed on the next scheduling cycle. + */ +VALUE +rb_fiber_scheduler_yield(VALUE scheduler) +{ + // First try to call the scheduler's yield method, if it exists: + VALUE result = rb_check_funcall(scheduler, id_yield, 0, NULL); + if (!UNDEF_P(result)) return result; + + // Otherwise, we can emulate yield by sleeping for 0 seconds: + return rb_fiber_scheduler_kernel_sleep(scheduler, RB_INT2NUM(0)); +} + #if 0 /* * Document-method: Fiber::Scheduler#timeout_after diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb new file mode 100644 index 00000000000000..3f9a7f55a07d11 --- /dev/null +++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require 'test/unit' +require 'timeout' +require_relative '../../fiber/scheduler' + +class TestSchedulerInterruptHandling < Test::Unit::TestCase + def setup + pend("No fork support") unless Process.respond_to?(:fork) + require '-test-/scheduler' + end + + # Test without Thread.handle_interrupt - should work regardless of fix + def test_without_handle_interrupt_signal_works + IO.pipe do |input, output| + pid = fork do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Signal.trap(:INT) do + ::Thread.current.raise(Interrupt) + end + + Fiber.schedule do + # Yield to the scheduler: + sleep(0) + + output.puts "ready" + Bug::Scheduler.blocking_loop + end + end + + output.close + assert_equal "ready\n", input.gets + + sleep 0.1 # Ensure the child is in the blocking loop + $stderr.puts "Sending interrupt" + Process.kill(:INT, pid) + + reaper = Thread.new do + Process.waitpid2(pid) + end + + unless reaper.join(1) + Process.kill(:KILL, pid) + end + + _, status = reaper.value + + # It should be interrupted (not killed): + assert_not_equal 0, status.exitstatus + assert_equal true, status.signaled? + assert_equal Signal.list["INT"], status.termsig + end + end +end diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 2401cb30d34563..60261d69e2213f 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -65,69 +65,79 @@ def next_timeout end end - def run - # $stderr.puts [__method__, Fiber.current].inspect - + def run_once readable = writable = nil - while @readable.any? or @writable.any? or @waiting.any? or @blocking.any? - # May only handle file descriptors up to 1024... - begin - readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) - rescue IOError - # Ignore - this can happen if the IO is closed while we are waiting. - end + begin + readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) + rescue IOError + # Ignore - this can happen if the IO is closed while we are waiting. + end - # puts "readable: #{readable}" if readable&.any? - # puts "writable: #{writable}" if writable&.any? + # puts "readable: #{readable}" if readable&.any? + # puts "writable: #{writable}" if writable&.any? - selected = {} + selected = {} - readable&.each do |io| - if fiber = @readable.delete(io) - @writable.delete(io) if @writable[io] == fiber - selected[fiber] = IO::READABLE - elsif io == @urgent.first - @urgent.first.read_nonblock(1024) - end + readable&.each do |io| + if fiber = @readable.delete(io) + @writable.delete(io) if @writable[io] == fiber + selected[fiber] = IO::READABLE + elsif io == @urgent.first + @urgent.first.read_nonblock(1024) end + end - writable&.each do |io| - if fiber = @writable.delete(io) - @readable.delete(io) if @readable[io] == fiber - selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE - end + writable&.each do |io| + if fiber = @writable.delete(io) + @readable.delete(io) if @readable[io] == fiber + selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE end + end - selected.each do |fiber, events| - fiber.transfer(events) - end + selected.each do |fiber, events| + fiber.transfer(events) + end - if @waiting.any? - time = current_time - waiting, @waiting = @waiting, {} - - waiting.each do |fiber, timeout| - if fiber.alive? - if timeout <= time - fiber.transfer - else - @waiting[fiber] = timeout - end + if @waiting.any? + time = current_time + waiting, @waiting = @waiting, {} + + waiting.each do |fiber, timeout| + if fiber.alive? + if timeout <= time + fiber.transfer + else + @waiting[fiber] = timeout end end end + end - if @ready.any? - ready = nil + if @ready.any? + ready = nil - @lock.synchronize do - ready, @ready = @ready, [] - end + @lock.synchronize do + ready, @ready = @ready, [] + end - ready.each do |fiber| - fiber.transfer if fiber.alive? - end + ready.each do |fiber| + fiber.transfer if fiber.alive? + end + end + end + + def run + # $stderr.puts [__method__, Fiber.current].inspect + + # Use Thread.handle_interrupt like Async::Scheduler does + # This defers signal processing, which is the root cause of the gRPC bug + # See: https://github.com/socketry/async/blob/main/lib/async/scheduler.rb + Thread.handle_interrupt(::SignalException => :never) do + while @readable.any? or @writable.any? or @waiting.any? or @blocking.any? + run_once + + break if Thread.pending_interrupt? end end end diff --git a/thread.c b/thread.c index 7c88c979029a10..0beb84a5b4575b 100644 --- a/thread.c +++ b/thread.c @@ -221,7 +221,18 @@ vm_check_ints_blocking(rb_execution_context_t *ec) th->pending_interrupt_queue_checked = 0; RUBY_VM_SET_INTERRUPT(ec); } - return rb_threadptr_execute_interrupts(th, 1); + + int result = rb_threadptr_execute_interrupts(th, 1); + + // When a signal is received, we yield to the scheduler as soon as possible: + if (result || RUBY_VM_INTERRUPTED(ec)) { + VALUE scheduler = rb_fiber_scheduler_current(); + if (scheduler != Qnil) { + rb_fiber_scheduler_yield(scheduler); + } + } + + return result; } int From aae85926504e448637c336aaf41b2c1ed1a6b78b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 6 Dec 2025 11:09:27 +0100 Subject: [PATCH 1635/2435] [ruby/timeout] Test that Timeout does not expose extra constants https://github.com/ruby/timeout/commit/4de4b4759c --- test/test_timeout.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index d6ae0c9b50b0ec..3be9013f7abbc4 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -7,7 +7,10 @@ class TestTimeout < Test::Unit::TestCase def test_public_methods assert_equal [:timeout], Timeout.private_instance_methods(false) assert_equal [], Timeout.public_instance_methods(false) + assert_equal [:timeout], Timeout.singleton_class.public_instance_methods(false) + + assert_equal [:Error, :ExitException, :VERSION], Timeout.constants.sort end def test_work_is_done_in_same_thread_as_caller From f4f5f0a009f6335ad13b8651bf43a216442c49a7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Dec 2025 19:32:14 +0900 Subject: [PATCH 1636/2435] Add error case tests for `File.path` - for non-String argument - for NUL-contained argument - for ASCII-incompatible argument --- spec/ruby/core/file/path_spec.rb | 41 +++++++++++++++++++++++++++++++ test/ruby/test_file_exhaustive.rb | 21 +++++++++++----- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/spec/ruby/core/file/path_spec.rb b/spec/ruby/core/file/path_spec.rb index dfa0c4ec027c99..726febcc2bd0eb 100644 --- a/spec/ruby/core/file/path_spec.rb +++ b/spec/ruby/core/file/path_spec.rb @@ -37,4 +37,45 @@ path.should_receive(:to_path).and_return("abc") File.path(path).should == "abc" end + + it "raises TypeError when #to_path result is not a string" do + path = mock("path") + path.should_receive(:to_path).and_return(nil) + -> { File.path(path) }.should raise_error TypeError + + path = mock("path") + path.should_receive(:to_path).and_return(42) + -> { File.path(path) }.should raise_error TypeError + end + + it "raises ArgumentError for string argument contains NUL character" do + -> { File.path("\0") }.should raise_error ArgumentError + -> { File.path("a\0") }.should raise_error ArgumentError + -> { File.path("a\0c") }.should raise_error ArgumentError + end + + it "raises ArgumentError when #to_path result contains NUL character" do + path = mock("path") + path.should_receive(:to_path).and_return("\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0c") + -> { File.path(path) }.should raise_error ArgumentError + end + + it "raises Encoding::CompatibilityError for ASCII-incompatible string argument" do + path = "abc".encode(Encoding::UTF_32BE) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end + + it "raises Encoding::CompatibilityError when #to_path result is ASCII-incompatible" do + path = mock("path") + path.should_receive(:to_path).and_return("abc".encode(Encoding::UTF_32BE)) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 222578be269581..394dc47603f782 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -204,12 +204,21 @@ def test_path end conv_error = ->(method, msg = "converting with #{method}") { - o = Struct.new(method).new(42) - assert_raise(TypeError, msg) {File.path(o)} - o = Struct.new(method).new("abc".encode(Encoding::UTF_32BE)) - assert_raise(Encoding::CompatibilityError, msg) {File.path(o)} - o = Struct.new(method).new("\0") - assert_raise(ArgumentError, msg) {File.path(o)} + test = ->(&new) do + o = new.(42) + assert_raise(TypeError, msg) {File.path(o)} + + o = new.("abc".encode(Encoding::UTF_32BE)) + assert_raise(Encoding::CompatibilityError, msg) {File.path(o)} + + ["\0", "a\0", "a\0c"].each do |path| + o = new.(path) + assert_raise(ArgumentError, msg) {File.path(o)} + end + end + + test.call(&:itself) + test.call(&Struct.new(method).method(:new)) } conv_error[:to_path] From 0346206d3eab2a8e659be0dd52aea6fc7b0ebb06 Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Sat, 6 Dec 2025 00:37:53 +0000 Subject: [PATCH 1637/2435] [ruby/prism] Avoid out-of-bounds reads Fixes https://github.com/ruby/prism/pull/3790. https://github.com/ruby/prism/commit/173ccb84ad --- prism/prism.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 77ac74192e6f66..93392da3499fbe 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12762,7 +12762,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p return UP(pm_local_variable_target_node_create(parser, &message_loc, name, 0)); } - if (*call->message_loc.start == '_' || parser->encoding->alnum_char(call->message_loc.start, call->message_loc.end - call->message_loc.start)) { + if (peek_at(parser, call->message_loc.start) == '_' || parser->encoding->alnum_char(call->message_loc.start, call->message_loc.end - call->message_loc.start)) { if (multiple && PM_NODE_FLAG_P(call, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { pm_parser_err_node(parser, (const pm_node_t *) call, PM_ERR_UNEXPECTED_SAFE_NAVIGATION); } @@ -16118,7 +16118,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag static void parse_pattern_capture(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_constant_id_t capture, const pm_location_t *location) { // Skip this capture if it starts with an underscore. - if (*location->start == '_') return; + if (peek_at(parser, location->start) == '_') return; if (pm_constant_id_list_includes(captures, capture)) { pm_parser_err(parser, location->start, location->end, PM_ERR_PATTERN_CAPTURE_DUPLICATE); From e7f9abdc910bbb37d04e8e65e9bad5f36fa074e1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 5 Dec 2025 22:09:00 -0500 Subject: [PATCH 1638/2435] Sync doc/stringio in sync_default_gems.rb --- tool/sync_default_gems.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index c2f352d797ffc8..811e72fc008eff 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -266,6 +266,7 @@ def lib((upstream, branch), gemspec_in_subdir: false) ["ext/stringio", "ext/stringio"], ["test/stringio", "test/stringio"], ["stringio.gemspec", "ext/stringio/stringio.gemspec"], + ["doc/stringio", "doc/stringio"], ], exclude: [ "ext/stringio/README.md", ]), From 240afe50a1d3790d1570ad7bbc16f03bc8512b47 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 00:38:25 +0900 Subject: [PATCH 1639/2435] Suppress noisy outputs Fix up ruby/ruby#14700. --- test/-ext-/scheduler/test_interrupt_with_scheduler.rb | 4 +++- tool/sync_default_gems.rb | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb index 3f9a7f55a07d11..42a109b98b51a6 100644 --- a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb +++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb @@ -13,6 +13,8 @@ def setup def test_without_handle_interrupt_signal_works IO.pipe do |input, output| pid = fork do + STDERR.reopen(output) + scheduler = Scheduler.new Fiber.set_scheduler scheduler @@ -33,7 +35,7 @@ def test_without_handle_interrupt_signal_works assert_equal "ready\n", input.gets sleep 0.1 # Ensure the child is in the blocking loop - $stderr.puts "Sending interrupt" + # $stderr.puts "Sending interrupt" Process.kill(:INT, pid) reaper = Thread.new do diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 811e72fc008eff..c2f352d797ffc8 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -266,7 +266,6 @@ def lib((upstream, branch), gemspec_in_subdir: false) ["ext/stringio", "ext/stringio"], ["test/stringio", "test/stringio"], ["stringio.gemspec", "ext/stringio/stringio.gemspec"], - ["doc/stringio", "doc/stringio"], ], exclude: [ "ext/stringio/README.md", ]), From 7319db44fc835d04616369b7b61c33e57960dcde Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Dec 2025 17:53:54 +0900 Subject: [PATCH 1640/2435] [ruby/pathname] Define `to_path` alias directly The constant `TO_PATH` was defined for 1.9 compatibility but the code for it was dropped 10 years ago. https://github.com/ruby/pathname/commit/95ad4ceb19 --- pathname_builtin.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 1dedf5e08df546..d1b2947107a837 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -195,9 +195,6 @@ class Pathname # :stopdoc: - # to_path is implemented so Pathname objects are usable with File.open, etc. - TO_PATH = :to_path - SAME_PATHS = if File::FNM_SYSCASE.nonzero? # Avoid #zero? here because #casecmp can return nil. proc {|a, b| a.casecmp(b) == 0} @@ -264,7 +261,7 @@ def to_s end # to_path is implemented so Pathname objects are usable with File.open, etc. - alias_method TO_PATH, :to_s + alias to_path to_s def inspect # :nodoc: "#<#{self.class}:#{@path}>" From b675deeeb175b6434bf6abaa747ca3a471eb18ad Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Dec 2025 18:10:44 +0900 Subject: [PATCH 1641/2435] [ruby/pathname] Use `File.path` for conversion to path name This method has been defined since 1.9, as the standard conversion procedure. https://github.com/ruby/pathname/commit/8f582dc65d --- pathname_builtin.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index d1b2947107a837..0dee1446c07746 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -212,17 +212,7 @@ class Pathname # If +path+ contains a NUL character (\0), an ArgumentError is raised. # def initialize(path) - unless String === path - path = path.to_path if path.respond_to? :to_path - path = path.to_str if path.respond_to? :to_str - raise TypeError, "Pathname.new requires a String, #to_path or #to_str" unless String === path - end - - if path.include?("\0") - raise ArgumentError, "pathname contains \\0: #{path.inspect}" - end - - @path = path.dup + @path = File.path(path).dup end def freeze From da3b7d5ee3afb5ae8e97a906d114b56242a5ff27 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Dec 2025 18:13:58 +0900 Subject: [PATCH 1642/2435] [ruby/pathname] Freeze and hide internal constants https://github.com/ruby/pathname/commit/60f5d58d73 --- pathname_builtin.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 0dee1446c07746..3d78619c9e4041 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -201,6 +201,8 @@ class Pathname else proc {|a, b| a == b} end + SAME_PATHS.freeze + private_constant :SAME_PATHS attr_reader :path protected :path @@ -307,6 +309,9 @@ def sub_ext(repl) SEPARATOR_LIST = Regexp.quote File::SEPARATOR SEPARATOR_PAT = /#{SEPARATOR_LIST}/ end + SEPARATOR_LIST.freeze + SEPARATOR_PAT.freeze + private_constant :SEPARATOR_LIST, :SEPARATOR_LIST if File.dirname('A:') == 'A:.' # DOSish drive letter # Regexp that matches an absolute path. @@ -314,6 +319,7 @@ def sub_ext(repl) else ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/ end + ABSOLUTE_PATH.freeze private_constant :ABSOLUTE_PATH # :startdoc: From 2e828dd98feeec5bab5ea85f0661638524004a01 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 6 Dec 2025 10:37:56 -0500 Subject: [PATCH 1643/2435] Fix strict aliasing warning in ruby_swap128_int The following warnings are emitted. We can use type punning to prevent strict aliasing violations. io_buffer.c:1935:23: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] 1935 | rb_uint128_t u = *(rb_uint128_t*)&x; | ^~~~~~~~~~~~~~~~~ io_buffer.c:1937:13: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] 1937 | return *(rb_int128_t*)&swapped; | --- io_buffer.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 0d2cbdb4b7e704..b81527ff71147a 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1928,13 +1928,19 @@ ruby_swap128_uint(rb_uint128_t x) return result; } +union uint128_int128_conversion { + rb_uint128_t uint128; + rb_int128_t int128; +}; + static inline rb_int128_t ruby_swap128_int(rb_int128_t x) { - // Cast to unsigned, swap, then cast back - rb_uint128_t u = *(rb_uint128_t*)&x; - rb_uint128_t swapped = ruby_swap128_uint(u); - return *(rb_int128_t*)&swapped; + union uint128_int128_conversion conversion = { + .int128 = x + }; + conversion.uint128 = ruby_swap128_uint(conversion.uint128); + return conversion.int128; } #define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \ From 9dfb7bd7d44ac99bc4d7233cef00e0e6e0743905 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 13:14:45 +0100 Subject: [PATCH 1644/2435] [ruby/openssl] const correct ossl_bin2hex() This helper only reads from its in parameter. Making that const avoids a couple of casts in an upcoming change. https://github.com/ruby/openssl/commit/970d5764e3 --- ext/openssl/ossl.c | 2 +- ext/openssl/ossl.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index a19ff23b107c88..5fd6bff98b7bbe 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -122,7 +122,7 @@ ossl_buf2str(char *buf, int len) } void -ossl_bin2hex(unsigned char *in, char *out, size_t inlen) +ossl_bin2hex(const unsigned char *in, char *out, size_t inlen) { const char *hex = "0123456789abcdef"; size_t i; diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 93deafb4b68363..0b479a7200a1f4 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -131,7 +131,7 @@ do{\ * Convert binary string to hex string. The caller is responsible for * ensuring out has (2 * len) bytes of capacity. */ -void ossl_bin2hex(unsigned char *in, char *out, size_t len); +void ossl_bin2hex(const unsigned char *in, char *out, size_t len); /* * Our default PEM callback From d3aa7b889f572237467156f3c6bc3c68ef45e9c4 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 13:29:59 +0100 Subject: [PATCH 1645/2435] [ruby/openssl] Convert ossl_ocsp.c to opaque ASN1_STRING OpenSSL plans to make asn1_string_st opaque, the struct underlying most ASN.1 types such as ASN1_*STRING, ASN1_ENUMERATED, ASN1_INTEGER, etc. Most of ruby/openssl's C code can be straigtforwardly converted to use accessors available since OpenSS https://github.com/ruby/openssl/commit/374262435a --- ext/openssl/ossl_ocsp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 97ab24c347b556..93d8bc85671eac 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1550,8 +1550,9 @@ ossl_ocspcid_get_issuer_name_hash(VALUE self) GetOCSPCertId(self, id); OCSP_id_get0_info(&name_hash, NULL, NULL, NULL, id); - ret = rb_str_new(NULL, name_hash->length * 2); - ossl_bin2hex(name_hash->data, RSTRING_PTR(ret), name_hash->length); + ret = rb_str_new(NULL, ASN1_STRING_length(name_hash) * 2); + ossl_bin2hex(ASN1_STRING_get0_data(name_hash), RSTRING_PTR(ret), + ASN1_STRING_length(name_hash)); return ret; } @@ -1573,8 +1574,9 @@ ossl_ocspcid_get_issuer_key_hash(VALUE self) GetOCSPCertId(self, id); OCSP_id_get0_info(NULL, NULL, &key_hash, NULL, id); - ret = rb_str_new(NULL, key_hash->length * 2); - ossl_bin2hex(key_hash->data, RSTRING_PTR(ret), key_hash->length); + ret = rb_str_new(NULL, ASN1_STRING_length(key_hash) * 2); + ossl_bin2hex(ASN1_STRING_get0_data(key_hash), RSTRING_PTR(ret), + ASN1_STRING_length(key_hash)); return ret; } From 98c151b0e55e25217334a94c17102ea8382027f2 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 13:44:18 +0100 Subject: [PATCH 1646/2435] [ruby/openssl] Convert some of ossl_asn1.c to opaque ASN1_STRING This uses the normal accessors but leaves out BIT STRINGS, which will need compat implementations for ASN1_BIT_STRING_get_length() and ASN1_BIT_STRING_set1() for older libcryptos. https://github.com/openssl/openssl/issues/29184 https://github.com/openssl/openssl/issues/29185 https://github.com/ruby/openssl/commit/ba3d1cc5c2 --- ext/openssl/ossl_asn1.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index b21a79484ccc4a..dc72df5a63fe3e 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -114,7 +114,8 @@ ossl_time_split(VALUE time, time_t *sec, int *days) VALUE asn1str_to_str(const ASN1_STRING *str) { - return rb_str_new((const char *)str->data, str->length); + return rb_str_new((const char *)ASN1_STRING_get0_data(str), + ASN1_STRING_length(str)); } /* @@ -129,7 +130,7 @@ asn1integer_to_num(const ASN1_INTEGER *ai) if (!ai) { ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); } - if (ai->type == V_ASN1_ENUMERATED) + if (ASN1_STRING_type(ai) == V_ASN1_ENUMERATED) /* const_cast: workaround for old OpenSSL */ bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL); else From a07997bf8124b8aac516f8f70388e86fd24f4a2b Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 13:52:04 +0100 Subject: [PATCH 1647/2435] [ruby/openssl] Convert ossl_ns_spki.c to opaque ASN1_STRING https://github.com/ruby/openssl/commit/0941ebbda5 --- ext/openssl/ossl_ns_spki.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index 1d1498824620a9..8440c2ee82b909 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -230,13 +230,12 @@ ossl_spki_get_challenge(VALUE self) NETSCAPE_SPKI *spki; GetSPKI(self, spki); - if (spki->spkac->challenge->length <= 0) { + if (ASN1_STRING_length(spki->spkac->challenge) <= 0) { OSSL_Debug("Challenge.length <= 0?"); return rb_str_new(0, 0); } - return rb_str_new((const char *)spki->spkac->challenge->data, - spki->spkac->challenge->length); + return asn1str_to_str(spki->spkac->challenge); } /* From 38ad0806d7270926ff6fc5c23092aa3e822f386b Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 13:54:49 +0100 Subject: [PATCH 1648/2435] [ruby/openssl] Convert ossl_ts.c to opaque ASN1_STRING https://github.com/ruby/openssl/commit/8945f379b3 --- ext/openssl/ossl_ts.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 615eedc016a0f4..b31a854a63e9ac 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -259,7 +259,7 @@ ossl_ts_req_get_msg_imprint(VALUE self) mi = TS_REQ_get_msg_imprint(req); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); + ret = asn1str_to_str(hashed_msg); return ret; } @@ -470,7 +470,7 @@ ossl_ts_req_to_der(VALUE self) ossl_raise(eTimestampError, "Message imprint missing algorithm"); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - if (!hashed_msg->length) + if (!ASN1_STRING_length(hashed_msg)) ossl_raise(eTimestampError, "Message imprint missing hashed message"); return asn1_to_der((void *)req, (int (*)(void *, unsigned char **))i2d_TS_REQ); @@ -981,7 +981,7 @@ ossl_ts_token_info_get_msg_imprint(VALUE self) GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); - ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); + ret = asn1str_to_str(hashed_msg); return ret; } From 8d3da814c03e06ce331e7022b87500943b57fa4e Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Fri, 5 Dec 2025 14:15:08 +0100 Subject: [PATCH 1649/2435] [ruby/openssl] Convert ossl_x509ext.c to opaque ASN1_STRING https://github.com/ruby/openssl/commit/a41cf28bab --- ext/openssl/ossl_x509ext.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 85e2eff7a275c6..ef66ecc3fe8390 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -402,7 +402,7 @@ ossl_x509ext_get_value_der(VALUE obj) if ((value = X509_EXTENSION_get_data(ext)) == NULL) ossl_raise(eX509ExtError, NULL); - return rb_str_new((const char *)value->data, value->length); + return asn1str_to_str(value); } static VALUE From 1f0d41aa4d1a3b36e9640e5e8e64c030c71ed613 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 6 Dec 2025 12:07:08 -0500 Subject: [PATCH 1650/2435] [ruby/date] Call rb_gc_register_mark_object after object allocation It's possible that both half_days_in_day and day_in_nanoseconds are Ruby objects, which means that creating day_in_nanoseconds may trigger GC. Since half_days_in_day is not registered as a mark object until after day_in_nanoseconds is allocated, the GC may reclaim half_days_in_day. We can see this crash: ruby(rb_print_backtrace+0xb) [0x63a373c0] vm_dump.c:1105 ruby(rb_vm_bugreport) vm_dump.c:1450 ruby(rb_assert_failure_detail+0xdb) [0x6371d3a2] error.c:1216 ruby(RB_FL_TEST_RAW+0x0) [0x6371d3d5] error.c:1192 ruby(rb_assert_failure) (null):0 ruby(rb_gc_impl_writebarrier+0xb4) [0x636f01e4] gc/default/default.c:6103 ruby(pin_array_list_append+0x72) [0x638f9787] include/ruby/internal/gc.h:788 ruby(rb_vm_register_global_object) vm.c:4713 ruby(rb_gc_register_mark_object+0x3a) [0x6374144a] gc.c:3449 .ext/i686-linux-gnu/date_core.so(Init_date_core+0x204) [0xdbec86c4] ext/date/date_core.c:9511 .ext/i686-linux-gnu/date_core.so(Init_date_core) (null):0 ruby(dln_load_and_init+0x71) [0x6392c541] dln.c:521 ruby(dln_load_feature+0xd2) [0x6392c7d2] dln.c:566 ruby(load_ext+0xc3) [0x637931b3] load.c:1210 ruby(rb_vm_pop_frame+0x0) [0x638f80cd] vm.c:3120 ruby(rb_vm_call_cfunc_in_box) vm.c:3122 ruby(rb_long2num_inline+0x0) [0x637956f8] load.c:1353 ruby(require_internal) load.c:1354 ruby(rb_require_string_internal+0x60) [0x63795fa1] load.c:1457 ruby(rb_require_string) load.c:1443 https://github.com/ruby/date/commit/cbec5948e0 --- ext/date/date_core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index cf8ea3c0a44990..6bcf272b62d8b0 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -9496,6 +9496,7 @@ Init_date_core(void) sym_zone = ID2SYM(rb_intern_const("zone")); half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); + rb_gc_register_mark_object(half_days_in_day); #if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * @@ -9507,8 +9508,6 @@ Init_date_core(void) day_in_nanoseconds = f_mul(INT2FIX(DAY_IN_SECONDS), INT2FIX(SECOND_IN_NANOSECONDS)); #endif - - rb_gc_register_mark_object(half_days_in_day); rb_gc_register_mark_object(day_in_nanoseconds); positive_inf = +INFINITY; From 47c0dae188162798c50fdb2580d852ceec89f2ec Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Sat, 6 Dec 2025 17:58:56 +0100 Subject: [PATCH 1651/2435] [ruby/openssl] asn1integer_to_num: don't cast away const ASN1_ENUMERATED_to_BN() has been const-correct for a long time in all supported libcrytos, so we can remove this workaround. https://github.com/ruby/openssl/commit/d0f36a7c65 --- ext/openssl/ossl_asn1.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index dc72df5a63fe3e..628140a75e3ea4 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -131,8 +131,7 @@ asn1integer_to_num(const ASN1_INTEGER *ai) ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); } if (ASN1_STRING_type(ai) == V_ASN1_ENUMERATED) - /* const_cast: workaround for old OpenSSL */ - bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL); + bn = ASN1_ENUMERATED_to_BN(ai, NULL); else bn = ASN1_INTEGER_to_BN(ai, NULL); From 87bc106b8790d5beaccc46555538424fccb1f50f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 6 Dec 2025 17:37:40 +0000 Subject: [PATCH 1652/2435] [ruby/stringio] [DOC] Change link to on-page https://github.com/ruby/stringio/commit/a7c118d786 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 5556426fdc387a..9240929646d5ea 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1888,7 +1888,7 @@ strio_truncate(VALUE self, VALUE len) * external_encoding -> encoding or nil * * Returns an Encoding object that represents the encoding of the string; - * see {Encoding}[rdoc-ref:Encoding]: + * see {Encodings}[rdoc-ref:StringIO@Encodings]: * * strio = StringIO.new('foo') * strio.external_encoding # => # From 588347a088625b5c16eedbc5f3a7a1189a427e25 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 6 Dec 2025 11:47:34 -0500 Subject: [PATCH 1653/2435] Fix id2ref for multi-Ractor The id2ref table needs to be under a VM lock to ensure there are no race conditions. The following script crashes: o = Object.new ObjectSpace._id2ref(o.object_id) 10.times.map do Ractor.new do 10_000.times do a = Object.new a.object_id end end end.map(&:value) With: [BUG] Object ID seen, but not in _id2ref table: object_id=2800 object=T_OBJECT ruby 4.0.0dev (2025-12-06T15:15:43Z ractor-id2ref-fix e7f9abdc91) +PRISM [x86_64-linux] -- Control frame information ----------------------------------------------- c:0001 p:---- s:0003 e:000002 l:y b:---- DUMMY [FINISH] -- Threading information --------------------------------------------------- Total ractor count: 5 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- miniruby(rb_print_backtrace+0x14) [0x6047d09b2dff] vm_dump.c:1105 miniruby(rb_vm_bugreport) vm_dump.c:1450 miniruby(rb_bug_without_die_internal+0x5f) [0x6047d066bf57] error.c:1098 miniruby(rb_bug) error.c:1116 miniruby(rb_gc_get_ractor_newobj_cache+0x0) [0x6047d066c8dd] gc.c:2052 miniruby(gc_sweep_plane+0xad) [0x6047d079276d] gc/default/default.c:3513 miniruby(gc_sweep_page) gc/default/default.c:3605 miniruby(gc_sweep_step) gc/default/default.c:3886 miniruby(gc_sweep+0x1ba) [0x6047d0794cfa] gc/default/default.c:4154 miniruby(gc_start+0xbf2) [0x6047d0796742] gc/default/default.c:6519 miniruby(heap_prepare+0xcc) [0x6047d079748c] gc/default/default.c:2090 miniruby(heap_next_free_page) gc/default/default.c:2305 miniruby(newobj_cache_miss) gc/default/default.c:2412 miniruby(newobj_alloc+0xd) [0x6047d0798ff5] gc/default/default.c:2436 miniruby(rb_gc_impl_new_obj) gc/default/default.c:2515 miniruby(newobj_of) gc.c:996 miniruby(rb_wb_protected_newobj_of) gc.c:1046 miniruby(str_alloc_embed+0x28) [0x6047d08fda18] string.c:1019 miniruby(str_enc_new) string.c:1069 miniruby(prep_io+0x5) [0x6047d07cda14] io.c:9305 miniruby(prep_stdio) io.c:9347 miniruby(rb_io_prep_stdin) io.c:9365 miniruby(thread_start_func_2+0x77c) [0x6047d093a55c] thread.c:679 miniruby(thread_sched_lock_+0x0) [0x6047d093aacd] thread_pthread.c:2241 miniruby(co_start) thread_pthread_mn.c:469 --- bootstraptest/test_ractor.rb | 17 +++++++++++++++++ gc.c | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index bec742da4be0d5..af739ba4bc7fd7 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1116,6 +1116,23 @@ class C end.value RUBY +# Inserting into the id2ref table should be Ractor-safe +assert_equal 'ok', <<~'RUBY' + # Force all calls to Kernel#object_id to insert into the id2ref table + ObjectSpace._id2ref(Object.new.object_id) + + 10.times.map do + Ractor.new do + 10_000.times do + a = Object.new + a.object_id + end + end + end.map(&:value) + + :ok +RUBY + # Ractor.make_shareable(obj) assert_equal 'true', <<~'RUBY', frozen_string_literal: false class C diff --git a/gc.c b/gc.c index 4f5f041fb348fb..c660d1491f3a6d 100644 --- a/gc.c +++ b/gc.c @@ -1905,7 +1905,9 @@ object_id0(VALUE obj) RUBY_ASSERT(rb_shape_obj_has_id(obj)); if (RB_UNLIKELY(id2ref_tbl)) { - st_insert(id2ref_tbl, (st_data_t)id, (st_data_t)obj); + RB_VM_LOCKING() { + st_insert(id2ref_tbl, (st_data_t)id, (st_data_t)obj); + } } return id; } From f298beb2d973debe7ac080aecd28df543678adcf Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:20:50 -0600 Subject: [PATCH 1654/2435] [ruby/stringio] [DOC] Tweaks for StringIO#lineno (https://github.com/ruby/stringio/pull/191) https://github.com/ruby/stringio/commit/f2a2a5a99e --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 9240929646d5ea..68986c1a683eba 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -747,7 +747,7 @@ strio_copy(VALUE copy, VALUE orig) * lineno -> current_line_number * * Returns the current line number in +self+; - * see {Line Number}[rdoc-ref:IO@Line+Number]. + * see {Line Number}[rdoc-ref:StringIO@Line+Number]. */ static VALUE strio_get_lineno(VALUE self) From c9fe3cba39fda954e532424c2c082da915320da9 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:21:14 -0600 Subject: [PATCH 1655/2435] [ruby/stringio] [DOC] Tweaks for StringIO#lineno= (https://github.com/ruby/stringio/pull/192) https://github.com/ruby/stringio/commit/8b1ee03cbe --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 68986c1a683eba..c135603a28e5f5 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -760,7 +760,7 @@ strio_get_lineno(VALUE self) * lineno = new_line_number -> new_line_number * * Sets the current line number in +self+ to the given +new_line_number+; - * see {Line Number}[rdoc-ref:IO@Line+Number]. + * see {Line Number}[rdoc-ref:StringIO@Line+Number]. */ static VALUE strio_set_lineno(VALUE self, VALUE lineno) From 82577ac09005cf949e125ebe9bb4561002768e5a Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:21:26 -0600 Subject: [PATCH 1656/2435] [ruby/stringio] [DOC] Tweaks for StringIO#pos (https://github.com/ruby/stringio/pull/193) https://github.com/ruby/stringio/commit/90728bbbca --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index c135603a28e5f5..69642f8139a81e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -835,7 +835,7 @@ strio_reopen(int argc, VALUE *argv, VALUE self) * pos -> stream_position * * Returns the current position (in bytes); - * see {Position}[rdoc-ref:IO@Position]. + * see {Position}[rdoc-ref:StringIO@Position]. */ static VALUE strio_get_pos(VALUE self) From 33837abb81510d25088375bbfd74baba16ea2ed1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:21:38 -0600 Subject: [PATCH 1657/2435] [ruby/stringio] [DOC] Tweaks for StringIO#pos= (https://github.com/ruby/stringio/pull/194) https://github.com/ruby/stringio/commit/3cef1e0e5f --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 69642f8139a81e..56fb044a3a299f 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -848,7 +848,7 @@ strio_get_pos(VALUE self) * pos = new_position -> new_position * * Sets the current position (in bytes); - * see {Position}[rdoc-ref:IO@Position]. + * see {Position}[rdoc-ref:StringIO@Position]. */ static VALUE strio_set_pos(VALUE self, VALUE pos) From fb80f84fc7aac043db63e27948a54ce94fcf0da3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 2 Dec 2025 21:43:29 -0500 Subject: [PATCH 1658/2435] [DOC] Fix formatting in docs for String#[]= --- doc/string/aset.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/string/aset.rdoc b/doc/string/aset.rdoc index e06680d16c5435..cac3b67ef580dd 100644 --- a/doc/string/aset.rdoc +++ b/doc/string/aset.rdoc @@ -42,7 +42,7 @@ searches for a substring of size +length+ characters (as available) beginning at character offset specified by +start+. If argument +start+ is non-negative, -the offset is +start': +the offset is +start+: s = 'hello' s[0, 1] = 'foo' # => "foo" From 68eab91b147dee98873b54e97296a36301191d72 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 13:44:19 +0900 Subject: [PATCH 1659/2435] Allow to sync pathname manually Still development of the gem continues, sync as possible manually. --- tool/sync_default_gems.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index c2f352d797ffc8..9d62e954ce11f1 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -194,6 +194,12 @@ def lib((upstream, branch), gemspec_in_subdir: false) optparse: lib("ruby/optparse", gemspec_in_subdir: true).tap { it.mappings << ["doc/optparse", "doc/optparse"] }, + pathname: repo("ruby/pathname", [ + ["ext/pathname/pathname.c", "pathname.c"], + ["lib/pathname_builtin.rb", "pathname_builtin.rb"], + ["lib/pathname.rb", "lib/pathname.rb"], + ["test/pathname", "test/pathname"], + ]), pp: lib("ruby/pp"), prettyprint: lib("ruby/prettyprint"), prism: repo(["ruby/prism", "main"], [ From 941e70ab38d01d067b7bbbcdf8553893a9ca8b49 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 5 Dec 2025 22:09:00 -0500 Subject: [PATCH 1660/2435] Sync doc/stringio in sync_default_gems.rb --- tool/sync_default_gems.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 9d62e954ce11f1..780f923b55598e 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -272,6 +272,7 @@ def lib((upstream, branch), gemspec_in_subdir: false) ["ext/stringio", "ext/stringio"], ["test/stringio", "test/stringio"], ["stringio.gemspec", "ext/stringio/stringio.gemspec"], + ["doc/stringio", "doc/stringio"], ], exclude: [ "ext/stringio/README.md", ]), From eafaff9443daebba1f4e1a5b2a217b993f066360 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 8 Dec 2025 00:55:13 +1300 Subject: [PATCH 1661/2435] Re-introduce support for `io_close` hook. (#15434) --- io.c | 22 ++++++++++++---------- test/fiber/scheduler.rb | 7 +++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/io.c b/io.c index 91537895d2b3a5..e0e7d40548ee69 100644 --- a/io.c +++ b/io.c @@ -5551,18 +5551,9 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl) fptr->stdio_file = 0; fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE); - // wait for blocking operations to ensure they do not hit EBADF: + // Wait for blocking operations to ensure they do not hit EBADF: rb_thread_io_close_wait(fptr); - // Disable for now. - // if (!done && fd >= 0) { - // VALUE scheduler = rb_fiber_scheduler_current(); - // if (scheduler != Qnil) { - // VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self); - // if (!UNDEF_P(result)) done = 1; - // } - // } - if (!done && stdio_file) { // stdio_file is deallocated anyway even if fclose failed. if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(error)) { @@ -5574,6 +5565,15 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl) done = 1; } + VALUE scheduler = rb_fiber_scheduler_current(); + if (!done && fd >= 0 && scheduler != Qnil) { + VALUE result = rb_fiber_scheduler_io_close(scheduler, RB_INT2NUM(fd)); + + if (!UNDEF_P(result)) { + done = RTEST(result); + } + } + if (!done && fd >= 0) { // fptr->fd may be closed even if close fails. POSIX doesn't specify it. // We assumes it is closed. @@ -5724,10 +5724,12 @@ io_close_fptr(VALUE io) if (!fptr) return 0; if (fptr->fd < 0) return 0; + // This guards against multiple threads closing the same IO object: if (rb_thread_io_close_interrupt(fptr)) { /* calls close(fptr->fd): */ fptr_finalize_flush(fptr, FALSE, KEEPGVL); } + rb_io_fptr_cleanup(fptr, FALSE); return fptr; } diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 60261d69e2213f..029c5043dc1b14 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -255,6 +255,13 @@ def io_select(...) end.value end + # This hook is invoked by `IO#close`. Using a separate IO object + # demonstrates that the close operation is asynchronous. + def io_close(descriptor) + Fiber.blocking{IO.for_fd(descriptor.to_i).close} + return true + end + # This hook is invoked by `Kernel#sleep` and `Thread::Mutex#sleep`. def kernel_sleep(duration = nil) # $stderr.puts [__method__, duration, Fiber.current].inspect From 4080abecd6f7b2ce6f7e6a6d1801d3d9fcfa9a58 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 18:16:02 +0900 Subject: [PATCH 1662/2435] Ignore distclean failures Just clean the directory if it exists and is empty. --- prism/srcs.mk | 2 +- prism/srcs.mk.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/srcs.mk b/prism/srcs.mk index 167aa4dbe07e89..022662a00b5f30 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -14,7 +14,7 @@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ distclean-prism-srcs:: $(RM) prism/.srcs.mk.time - $(RMDIR) prism + $(RMDIRS) prism || $(NULLCMD) distclean-srcs-local:: distclean-prism-srcs diff --git a/prism/srcs.mk.in b/prism/srcs.mk.in index b67ce993709ac3..cc263fd1b4ef3f 100644 --- a/prism/srcs.mk.in +++ b/prism/srcs.mk.in @@ -22,7 +22,7 @@ prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ distclean-prism-srcs:: $(RM) prism/.srcs.mk.time - $(RMDIR) prism + $(RMDIRS) prism || $(NULLCMD) distclean-srcs-local:: distclean-prism-srcs From f2b250c150b947c3b2341796b2ce44dd94002055 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 00:06:21 +0900 Subject: [PATCH 1663/2435] [ruby/pathname] Define private method `same_paths?` for Ractor-safety https://github.com/ruby/pathname/commit/d33d18e5e2 --- pathname_builtin.rb | 12 +++++------- test/pathname/test_ractor.rb | 5 +++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 3d78619c9e4041..d9c5d5b6bad75d 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -195,14 +195,12 @@ class Pathname # :stopdoc: - SAME_PATHS = if File::FNM_SYSCASE.nonzero? + if File::FNM_SYSCASE.nonzero? # Avoid #zero? here because #casecmp can return nil. - proc {|a, b| a.casecmp(b) == 0} + private def same_paths?(a, b) a.casecmp(b) == 0 end else - proc {|a, b| a == b} + private def same_paths?(a, b) a == b end end - SAME_PATHS.freeze - private_constant :SAME_PATHS attr_reader :path protected :path @@ -836,12 +834,12 @@ def relative_path_from(base_directory) base_prefix, basename = r base_names.unshift basename if basename != '.' end - unless SAME_PATHS[dest_prefix, base_prefix] + unless same_paths?(dest_prefix, base_prefix) raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}" end while !dest_names.empty? && !base_names.empty? && - SAME_PATHS[dest_names.first, base_names.first] + same_paths?(dest_names.first, base_names.first) dest_names.shift base_names.shift end diff --git a/test/pathname/test_ractor.rb b/test/pathname/test_ractor.rb index f06b7501f3448b..737e4a4111d65c 100644 --- a/test/pathname/test_ractor.rb +++ b/test/pathname/test_ractor.rb @@ -20,6 +20,11 @@ class Ractor x.join(Pathname("b"), Pathname("c")) end assert_equal(Pathname("a/b/c"), r.value) + + r = Ractor.new Pathname("a") do |a| + Pathname("b").relative_path_from(a) + end + assert_equal(Pathname("../b"), r.value) end; end end From 806f2d3533ec5bbf6b4d598b36b1c9de7e786659 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 11:34:11 +0900 Subject: [PATCH 1664/2435] [ruby/pathname] [DOC] Pathname#freeze https://github.com/ruby/pathname/commit/4580540a2b --- pathname_builtin.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index d9c5d5b6bad75d..17299d42913a93 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -215,6 +215,9 @@ def initialize(path) @path = File.path(path).dup end + # + # Freze self. + # def freeze super @path.freeze From a8a188e1fc8568185c2ca460b5a2e800e9cac4bd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 12:29:41 +0900 Subject: [PATCH 1665/2435] [ruby/pathname] Add more tests for `Pathname#initialize` https://github.com/ruby/pathname/commit/a2edd25bc1 --- test/pathname/test_pathname.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index e2d6a09fb2f0c0..10ab1542205fa7 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -485,6 +485,13 @@ def test_initialize p2 = Pathname.new(p1) assert_equal(p1, p2) + obj = Object.new + assert_raise(TypeError) { Pathname.new(obj) } + + obj = Object.new + def obj.to_path; "a/path"; end + assert_equal("a/path", Pathname.new(obj).to_s) + obj = Object.new def obj.to_str; "a/b"; end assert_equal("a/b", Pathname.new(obj).to_s) @@ -494,6 +501,10 @@ def test_initialize_nul assert_raise(ArgumentError) { Pathname.new("a\0") } end + def test_initialize_encoding + assert_raise(Encoding::CompatibilityError) { Pathname.new("a".encode(Encoding::UTF_32BE)) } + end + def test_global_constructor p = Pathname.new('a') assert_equal(p, Pathname('a')) From db6071b5216ac58976f8ab8181ff6eab02dfa8be Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Dec 2025 12:31:42 +0900 Subject: [PATCH 1666/2435] [ruby/pathname] Raise the previous message Fix ruby/pathname#75. https://github.com/ruby/pathname/commit/5ba967b274 --- pathname_builtin.rb | 2 ++ test/pathname/test_pathname.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 17299d42913a93..878603d4d69cd0 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -213,6 +213,8 @@ class Pathname # def initialize(path) @path = File.path(path).dup + rescue TypeError => e + raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil end # diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index 10ab1542205fa7..3114d1458d229d 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -486,7 +486,7 @@ def test_initialize assert_equal(p1, p2) obj = Object.new - assert_raise(TypeError) { Pathname.new(obj) } + assert_raise_with_message(TypeError, /#to_path or #to_str/) { Pathname.new(obj) } obj = Object.new def obj.to_path; "a/path"; end From 379d22ce8418448ade3d410e5c76dd2ca0c7a8cf Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 7 Dec 2025 16:35:04 +0000 Subject: [PATCH 1667/2435] Bump RDoc version to 6.17.0 (#15439) --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 180e1dffcd4dae..bdda3311d0eb07 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.16.1 https://github.com/ruby/rdoc +rdoc 6.17.0 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.3 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From a4d142137332e449c2e037fa123a9d477d22093a Mon Sep 17 00:00:00 2001 From: git Date: Sun, 7 Dec 2025 16:35:47 +0000 Subject: [PATCH 1668/2435] [DOC] Update bundled gems list at 379d22ce8418448ade3d410e5c76dd --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 3468f479596293..24a36475360569 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,7 +213,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.16.1 +* rdoc 6.17.0 * win32ole 1.9.2 * irb 1.15.3 * reline 0.6.3 From 4f900c35bc01e12b948703b9e4b4f5d0e803f073 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 7 Dec 2025 10:24:08 -0500 Subject: [PATCH 1669/2435] Output ivar length for T_OBJECT in obj_info --- gc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index c660d1491f3a6d..5e3e44e726704d 100644 --- a/gc.c +++ b/gc.c @@ -4841,11 +4841,11 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU APPEND_F("(too_complex) len:%zu", hash_len); } else { - APPEND_F("(embed) len:%d", ROBJECT_FIELDS_CAPACITY(obj)); + APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj)); } } else { - APPEND_F("len:%d ptr:%p", ROBJECT_FIELDS_CAPACITY(obj), (void *)ROBJECT_FIELDS(obj)); + APPEND_F("len:%d capa:%d ptr:%p", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj), (void *)ROBJECT_FIELDS(obj)); } } break; From f2eece71990fd79a5d7e24999f8df5c1121253c6 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 7 Dec 2025 14:58:28 +0900 Subject: [PATCH 1670/2435] Remove the internal-only attribute from ruby_reset_timezone() The #ifdef is currently not taken because include/ruby/backward.h is not included at this point. The attribute is unnecessary in an internal header, so remove it. --- internal/time.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/time.h b/internal/time.h index e21b3574f6db7a..ad8133ed799c19 100644 --- a/internal/time.h +++ b/internal/time.h @@ -28,9 +28,6 @@ struct timeval rb_time_timeval(VALUE); RUBY_SYMBOL_EXPORT_BEGIN /* time.c (export) */ void ruby_reset_leap_second_info(void); -#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY -RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() -#endif void ruby_reset_timezone(const char *); RUBY_SYMBOL_EXPORT_END From be882278f22444e7d27db091bbd5f8bf63e882c2 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 7 Dec 2025 14:51:38 +0900 Subject: [PATCH 1671/2435] Move RBIMPL_ATTR_DEPRECATED_* macros to the appropriate header file Move these macros from include/ruby/backward.h to include/ruby/internal/attr/deprecated.h, alongside the other similar macros. include/ruby/internal/intern/vm.h cannot currently use them because include/ruby/backward.h is included too late. --- include/ruby/backward.h | 4 ---- include/ruby/internal/attr/deprecated.h | 7 +++++++ include/ruby/internal/intern/vm.h | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/ruby/backward.h b/include/ruby/backward.h index f804c2c36e9425..3a0fda9ec56dda 100644 --- a/include/ruby/backward.h +++ b/include/ruby/backward.h @@ -11,10 +11,6 @@ #include "ruby/internal/interpreter.h" #include "ruby/backward/2/attributes.h" -#define RBIMPL_ATTR_DEPRECATED_SINCE(ver) RBIMPL_ATTR_DEPRECATED(("since " #ver)) -#define RBIMPL_ATTR_DEPRECATED_INTERNAL(ver) RBIMPL_ATTR_DEPRECATED(("since "#ver", also internal")) -#define RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() RBIMPL_ATTR_DEPRECATED(("only for internal use")) - RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() void rb_clear_constant_cache(void); /* from version.c */ diff --git a/include/ruby/internal/attr/deprecated.h b/include/ruby/internal/attr/deprecated.h index e1bbdbd15ad0de..9c9dea1df2794a 100644 --- a/include/ruby/internal/attr/deprecated.h +++ b/include/ruby/internal/attr/deprecated.h @@ -72,4 +72,11 @@ # define RBIMPL_ATTR_DEPRECATED_EXT(msg) RBIMPL_ATTR_DEPRECATED(msg) #endif +#define RBIMPL_ATTR_DEPRECATED_SINCE(ver) \ + RBIMPL_ATTR_DEPRECATED(("since " #ver)) +#define RBIMPL_ATTR_DEPRECATED_INTERNAL(ver) \ + RBIMPL_ATTR_DEPRECATED(("since "#ver", also internal")) +#define RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() \ + RBIMPL_ATTR_DEPRECATED(("only for internal use")) + #endif /* RBIMPL_ATTR_DEPRECATED_H */ diff --git a/include/ruby/internal/intern/vm.h b/include/ruby/internal/intern/vm.h index 779f77fe91fcb5..0d25a9cdb041fc 100644 --- a/include/ruby/internal/intern/vm.h +++ b/include/ruby/internal/intern/vm.h @@ -95,7 +95,6 @@ VALUE rb_check_funcall(VALUE recv, ID mid, int argc, const VALUE *argv); */ VALUE rb_check_funcall_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat); -#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL /** * This API is practically a variant of rb_proc_call_kw() now. Historically * when there still was a concept called `$SAFE`, this was an API for that. @@ -112,7 +111,6 @@ VALUE rb_check_funcall_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int k */ RBIMPL_ATTR_DEPRECATED_INTERNAL(4.0) VALUE rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat); -#endif /** * Identical to rb_funcallv(), except it takes Ruby's array instead of C's. From 4655b174d5fa71b69781c56701be63a02215b12f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Dec 2025 12:05:54 +0900 Subject: [PATCH 1672/2435] [ruby/timeout] v0.5.0 https://github.com/ruby/timeout/commit/837d5aac73 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 36cd0f915bc7ff..1640542d6f806a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.4" + VERSION = "0.5.0" # Internal error raised to when a timeout is triggered. class ExitException < Exception From fbc5bb91207f759c69c3a460bd8bb39915b152d8 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 8 Dec 2025 03:07:35 +0000 Subject: [PATCH 1673/2435] Update default gems list at 4655b174d5fa71b69781c56701be63 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 24a36475360569..95c02be9513306 100644 --- a/NEWS.md +++ b/NEWS.md @@ -254,7 +254,7 @@ The following default gems are updated. * resolv 0.6.3 * stringio 3.1.9.dev * strscan 3.1.6.dev -* timeout 0.4.4 +* timeout 0.5.0 * uri 1.1.1 * weakref 0.1.4 * zlib 3.2.2 From 6a1f5b68cbd90031d849265b663986c5b8dd39dd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 11:57:24 +0900 Subject: [PATCH 1674/2435] Make `ruby_reset_timezone` internal It is used only in hash.c, when `ENV['TZ']` is set. --- internal/time.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/time.h b/internal/time.h index ad8133ed799c19..224d485b2e1ce3 100644 --- a/internal/time.h +++ b/internal/time.h @@ -28,7 +28,8 @@ struct timeval rb_time_timeval(VALUE); RUBY_SYMBOL_EXPORT_BEGIN /* time.c (export) */ void ruby_reset_leap_second_info(void); -void ruby_reset_timezone(const char *); RUBY_SYMBOL_EXPORT_END +void ruby_reset_timezone(const char *); + #endif /* INTERNAL_TIME_H */ From a82aa08fe0112aefd35e28dc5ca3f9ea9238ae17 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 12:04:11 +0900 Subject: [PATCH 1675/2435] Make `ruby_reset_leap_second_info` internal It is exported only for the extension library to test, but the method is no longer used since 29e31e72fb5a14194a78ec974c4ba56c33ad8d45. --- ext/-test-/time/leap_second.c | 15 --------------- internal/time.h | 1 - test/ruby/test_time_tz.rb | 1 - time.c | 2 ++ 4 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 ext/-test-/time/leap_second.c diff --git a/ext/-test-/time/leap_second.c b/ext/-test-/time/leap_second.c deleted file mode 100644 index ee7011fa97e983..00000000000000 --- a/ext/-test-/time/leap_second.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "ruby.h" -#include "internal/time.h" - -static VALUE -bug_time_s_reset_leap_second_info(VALUE klass) -{ - ruby_reset_leap_second_info(); - return Qnil; -} - -void -Init_time_leap_second(VALUE klass) -{ - rb_define_singleton_method(klass, "reset_leap_second_info", bug_time_s_reset_leap_second_info, 0); -} diff --git a/internal/time.h b/internal/time.h index 224d485b2e1ce3..1f3505f5bc7685 100644 --- a/internal/time.h +++ b/internal/time.h @@ -27,7 +27,6 @@ struct timeval rb_time_timeval(VALUE); RUBY_SYMBOL_EXPORT_BEGIN /* time.c (export) */ -void ruby_reset_leap_second_info(void); RUBY_SYMBOL_EXPORT_END void ruby_reset_timezone(const char *); diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb index f66cd9bec2eedc..473c3cabcb4d50 100644 --- a/test/ruby/test_time_tz.rb +++ b/test/ruby/test_time_tz.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'test/unit' -require '-test-/time' class TestTimeTZ < Test::Unit::TestCase has_right_tz = true diff --git a/time.c b/time.c index 2a121d5fcc8f74..b2eed2daf3b85c 100644 --- a/time.c +++ b/time.c @@ -746,6 +746,8 @@ get_tzname(int dst) } #endif +static void ruby_reset_leap_second_info(void); + void ruby_reset_timezone(const char *val) { From 1de15815a8b85f02ba73d9f7c30322530b471b5f Mon Sep 17 00:00:00 2001 From: yoshoku Date: Thu, 4 Dec 2025 09:04:11 +0900 Subject: [PATCH 1676/2435] [ruby/rubygems] Fix native extension loading in newgem template for RHEL-based systems Add fallback to `require` when `require_relative` fails to load native extensions. This addresses an issue on RHEL-based Linux distributions where Ruby scripts and built native extension shared libraries are installed in separate directories. https://github.com/ruby/rubygems/commit/68599bd107 --- lib/bundler/templates/newgem/lib/newgem.rb.tt | 2 +- spec/bundler/commands/newgem_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt index caf6e32f4abf30..3aedee0d25e92b 100644 --- a/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -2,7 +2,7 @@ require_relative "<%= File.basename(config[:namespaced_path]) %>/version" <%- if config[:ext] -%> -require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" +require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" <%- end -%> <%- config[:constant_array].each_with_index do |c, i| -%> diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 1d158726bed6a0..06c226f9e56a17 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -1681,6 +1681,13 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist end + it "generates native extension loading code" do + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb").read).to include(<<~RUBY) + require_relative "test_gem/version" + require "#{gem_name}/#{gem_name}" + RUBY + end + it "includes rake-compiler, but no Rust related changes" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') From ced333677fb97f2fc720dfb86213ea416b3ebcf4 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 8 Dec 2025 17:47:07 +0900 Subject: [PATCH 1677/2435] fix SEGV on clang-16/18 Maybe because of TLS/coroutine problem, CI fails on clang-16/18 ``` 1) Failure: TestTimeout#test_ractor [/tmp/ruby/src/trunk_clang_18/test/test_timeout.rb:288]: pid 307341 killed by SIGSEGV (signal 11) (core dumped) | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98: [BUG] Segmentation fault at 0x0000000000000030 | ruby 4.0.0dev (2025-12-07T16:51:02Z master 4f900c35bc) +PRISM [x86_64-linux] | | -- Control frame information ----------------------------------------------- | c:0006 p:---- s:0026 e:000025 l:y b:---- CFUNC :sleep | c:0005 p:---- s:0023 e:000022 l:y b:---- CFUNC :wait | c:0004 p:0020 s:0017 e:000016 l:n b:---- BLOCK /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98 [FINISH] | c:0003 p:---- s:0014 e:000013 l:y b:---- CFUNC :synchronize | c:0002 p:0072 s:0010 e:000009 l:n b:---- BLOCK /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96 [FINISH] | c:0001 p:---- s:0003 e:000002 l:y b:---- DUMMY [FINISH] | | -- Ruby level backtrace information ---------------------------------------- | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96:in 'block in create_timeout_thread' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96:in 'synchronize' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'block (2 levels) in create_timeout_thread' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'wait' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'sleep' | | -- Threading information --------------------------------------------------- | Total ractor count: 3 | Ruby thread count for this ractor: 2 | | -- Machine register context ------------------------------------------------ | RIP: 0x0000602b1e08a5b5 RBP: 0x000071c65facd130 RSP: 0x000071c6258842e0 | RAX: 0x0000000000000000 RBX: 0x000000006935f7c4 RCX: 0x0000000000000000 | RDX: 0x0000602b1e520c20 RDI: 0x000071c620012480 RSI: 0x000071c620012480 | R8: 0x0000000000000000 R9: 0x0000000000000000 R10: 0x0000000000000000 | R11: 0x0000000000000000 R12: 0x000071c65fa2f640 R13: 0x000071c65fb66e48 | R14: 0x0000000000000000 R15: 0xfdccaa3270000002 EFL: 0x0000000000010202 | | -- C level backtrace information ------------------------------------------- | /tmp/ruby/build/trunk_clang_18/ruby(rb_print_backtrace+0x14) [0x602b1e31a6ea] /tmp/ruby/src/trunk_clang_18/vm_dump.c:1105 | /tmp/ruby/build/trunk_clang_18/ruby(rb_vm_bugreport) /tmp/ruby/src/trunk_clang_18/vm_dump.c:1450 | /tmp/ruby/build/trunk_clang_18/ruby(rb_bug_for_fatal_signal+0x15c) [0x602b1e2d960c] /tmp/ruby/src/trunk_clang_18/error.c:1131 | /tmp/ruby/build/trunk_clang_18/ruby(sigsegv+0x5a) [0x602b1e05528a] /tmp/ruby/src/trunk_clang_18/signal.c:948 | /lib/x86_64-linux-gnu/libc.so.6(0x71c65fd46320) [0x71c65fd46320] | /tmp/ruby/build/trunk_clang_18/ruby(vm_check_ints_blocking+0x0) [0x602b1e08a5b5] /tmp/ruby/src/trunk_clang_18/vm_core.h:2097 | /tmp/ruby/build/trunk_clang_18/ruby(rb_current_execution_context) /tmp/ruby/src/trunk_clang_18/thread_sync.c:617 | /tmp/ruby/build/trunk_clang_18/ruby(rb_mutex_sleep) /tmp/ruby/src/trunk_clang_18/thread_sync.c:617 ``` This patch introduces workaround by acquiring EC before swithcing coroutine. --- thread_sync.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/thread_sync.c b/thread_sync.c index 6cc23f7d87b32d..8967e24e341bad 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -599,6 +599,8 @@ mutex_sleep_begin(VALUE _arguments) VALUE rb_mutex_sleep(VALUE self, VALUE timeout) { + rb_execution_context_t *ec = GET_EC(); + if (!NIL_P(timeout)) { // Validate the argument: rb_time_interval(timeout); @@ -614,7 +616,7 @@ rb_mutex_sleep(VALUE self, VALUE timeout) VALUE woken = rb_ensure(mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self); - RUBY_VM_CHECK_INTS_BLOCKING(GET_EC()); + RUBY_VM_CHECK_INTS_BLOCKING(ec); if (!woken) return Qnil; time_t end = time(0) - beg; return TIMET2NUM(end); From 159430e8b6a2c6b5a5a9c926676082fcaef7535e Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 8 Dec 2025 17:59:57 +0900 Subject: [PATCH 1678/2435] ignore Thread creation error on resource limited environment. ``` stderr output is not empty bootstraptest.test_ractor.rb_2446_1412.rb:23:in 'Ractor.new': can't create Thread: Cannot allocate memory (ThreadError) ``` --- bootstraptest/test_ractor.rb | 56 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index af739ba4bc7fd7..9042f945f7f4f4 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -2444,35 +2444,45 @@ def call_test(obj) # When creating bmethods in Ractors, they should only be usable from their # defining ractor, even if it is GC'd assert_equal 'ok', <<~'RUBY' -CLASSES = 1000.times.map { Class.new }.freeze -# This would be better to run in parallel, but there's a bug with lambda -# creation and YJIT causing crashes in dev mode -ractors = CLASSES.map do |klass| - Ractor.new(klass) do |klass| - Ractor.receive - klass.define_method(:foo) {} +begin + CLASSES = 1000.times.map { Class.new }.freeze + + # This would be better to run in parallel, but there's a bug with lambda + # creation and YJIT causing crashes in dev mode + ractors = CLASSES.map do |klass| + Ractor.new(klass) do |klass| + Ractor.receive + klass.define_method(:foo) {} + end end -end -ractors.each do |ractor| - ractor << nil - ractor.join -end + ractors.each do |ractor| + ractor << nil + ractor.join + end -ractors.clear -GC.start + ractors.clear + GC.start -any = 1000.times.map do - Ractor.new do - CLASSES.any? do |klass| - begin - klass.new.foo - true - rescue RuntimeError - false + any = 1000.times.map do + Ractor.new do + CLASSES.any? do |klass| + begin + klass.new.foo + true + rescue RuntimeError + false + end end end + end.map(&:value).none? && :ok +rescue ThreadError => e + # ignore limited memory machine + if /can\'t create Thread/ =~ e.message + :ok + else + raise end -end.map(&:value).none? && :ok +end RUBY From 27d60e29845cce3159856ef25d8be533ef402e3c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 17:59:21 +0900 Subject: [PATCH 1679/2435] [ruby/resolv] Fix warnings on cygwin https://github.com/ruby/resolv/commit/075e76f997 --- ext/win32/resolv/resolv.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/win32/resolv/resolv.c b/ext/win32/resolv/resolv.c index 066856dd984df0..4c56c61d7de579 100644 --- a/ext/win32/resolv/resolv.c +++ b/ext/win32/resolv/resolv.c @@ -21,7 +21,7 @@ w32error_make_error(DWORD e) FORMAT_MESSAGE_IGNORE_INSERTS, &source, e, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer), NULL)) { - snprintf(buffer, sizeof(buffer), "Unknown Error %u", (unsigned long)e); + snprintf(buffer, sizeof(buffer), "Unknown Error %lu", (unsigned long)e); } p = buffer; while ((p = strpbrk(p, "\r\n")) != NULL) { @@ -149,7 +149,6 @@ reg_each_key(VALUE self) { WCHAR wname[256]; HKEY hkey = DATA_PTR(self); - rb_encoding *utf8 = rb_utf8_encoding(); VALUE k = TypedData_Wrap_Struct(CLASS_OF(self), &hkey_type, NULL); DWORD i, e, n; for (i = 0; n = numberof(wname), (e = RegEnumKeyExW(hkey, i, wname, &n, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS; i++) { @@ -187,7 +186,7 @@ reg_value(VALUE self, VALUE name) case REG_DWORD: case REG_DWORD_BIG_ENDIAN: { DWORD d; - if (size != sizeof(d)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + if (size != sizeof(d)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_DWORD, &type, &d, &size)); if (type == REG_DWORD_BIG_ENDIAN) d = swap_dw(d); return ULONG2NUM(d); @@ -195,12 +194,12 @@ reg_value(VALUE self, VALUE name) case REG_QWORD: { QWORD q; - if (size != sizeof(q)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + if (size != sizeof(q)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_REG_QWORD, &type, &q, &size)); return ULL2NUM(q); } case REG_SZ: case REG_MULTI_SZ: case REG_EXPAND_SZ: - if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", size); + if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); buffer = ALLOCV_N(char, value_buffer, size); break; default: @@ -211,7 +210,6 @@ reg_value(VALUE self, VALUE name) switch (type) { case REG_MULTI_SZ: { const WCHAR *w = (WCHAR *)buffer; - rb_encoding *utf8 = rb_utf8_encoding(); result = rb_ary_new(); size /= sizeof(WCHAR); size -= 1; From 66b2cc3dabbedca1331507f8b4f2b274d0a47570 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 18:29:06 +0900 Subject: [PATCH 1680/2435] [ruby/resolv] Check the second RegGetValue type https://github.com/ruby/resolv/commit/3678de9e30 --- ext/win32/resolv/resolv.c | 49 +++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/ext/win32/resolv/resolv.c b/ext/win32/resolv/resolv.c index 4c56c61d7de579..b2d377df9fffc6 100644 --- a/ext/win32/resolv/resolv.c +++ b/ext/win32/resolv/resolv.c @@ -182,6 +182,15 @@ reg_value(VALUE self, VALUE name) e = RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, NULL, &size); if (e == ERROR_FILE_NOT_FOUND) return Qnil; w32error_check(e); +# define get_value_2nd(data, dsize) do { \ + DWORD type2 = type; \ + w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type2, data, dsize)); \ + if (type != type2) { \ + rb_raise(rb_eRuntimeError, "registry value type changed %lu -> %lu", \ + (unsigned long)type, (unsigned long)type2); \ + } \ + } while (0) + switch (type) { case REG_DWORD: case REG_DWORD_BIG_ENDIAN: { @@ -201,30 +210,30 @@ reg_value(VALUE self, VALUE name) case REG_SZ: case REG_MULTI_SZ: case REG_EXPAND_SZ: if (size % sizeof(WCHAR)) rb_raise(rb_eRuntimeError, "invalid size returned: %lu", (unsigned long)size); buffer = ALLOCV_N(char, value_buffer, size); + get_value_2nd(buffer, &size); + if (type == REG_MULTI_SZ) { + const WCHAR *w = (WCHAR *)buffer; + result = rb_ary_new(); + size /= sizeof(WCHAR); + size -= 1; + for (size_t i = 0; i < size; ++i) { + int n = lstrlenW(w+i); + rb_ary_push(result, wchar_to_utf8(w+i, n)); + i += n; + } + } + else { + result = wchar_to_utf8((WCHAR *)buffer, lstrlenW((WCHAR *)buffer)); + } + ALLOCV_END(value_buffer); break; default: result = rb_str_new(0, size); - buffer = RSTRING_PTR(result); - } - w32error_check(RegGetValueW(hkey, NULL, wname, RRF_RT_ANY, &type, buffer, &size)); - switch (type) { - case REG_MULTI_SZ: { - const WCHAR *w = (WCHAR *)buffer; - result = rb_ary_new(); - size /= sizeof(WCHAR); - size -= 1; - for (size_t i = 0; i < size; ++i) { - int n = lstrlenW(w+i); - rb_ary_push(result, wchar_to_utf8(w+i, n)); - i += n; - } - return result; - } - case REG_SZ: case REG_EXPAND_SZ: - return wchar_to_utf8((WCHAR *)buffer, lstrlenW((WCHAR *)buffer)); - default: - return result; + get_value_2nd(RSTRING_PTR(result), &size); + rb_str_set_len(result, size); + break; } + return result; } void From 956f8d490cde0bb6941f21d263287bd3a141348c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 8 Dec 2025 16:36:45 +0000 Subject: [PATCH 1681/2435] ZJIT: Avoid redundant SP save in codegen (#15448) --- zjit/src/codegen.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8c5fdc816d6450..73c092f72091c5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -790,7 +790,7 @@ fn gen_ccall_with_frame( // Can't use gen_prepare_non_leaf_call() because we need to adjust the SP // to account for the receiver and arguments (and block arguments if any) - gen_prepare_call_with_gc(asm, state, false); + gen_save_pc_for_gc(asm, state); gen_save_sp(asm, caller_stack_size); gen_spill_stack(jit, asm, state); gen_spill_locals(jit, asm, state); @@ -875,7 +875,7 @@ fn gen_ccall_variadic( // Can't use gen_prepare_non_leaf_call() because we need to adjust the SP // to account for the receiver and arguments (like gen_ccall_with_frame does) - gen_prepare_call_with_gc(asm, state, false); + gen_save_pc_for_gc(asm, state); gen_save_sp(asm, caller_stack_size); gen_spill_stack(jit, asm, state); gen_spill_locals(jit, asm, state); @@ -1304,8 +1304,8 @@ fn gen_send_without_block_direct( gen_stack_overflow_check(jit, asm, state, stack_growth); // Save cfp->pc and cfp->sp for the caller frame - gen_prepare_call_with_gc(asm, state, false); - // Special SP math. Can't use gen_prepare_non_leaf_call + // Can't use gen_prepare_non_leaf_call because we need special SP math. + gen_save_pc_for_gc(asm, state); gen_save_sp(asm, state.stack().len() - args.len() - 1); // -1 for receiver gen_spill_locals(jit, asm, state); @@ -2008,6 +2008,18 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso } } +/// Save only the PC to CFP. Use this when you need to call gen_save_sp() +/// immediately after with a custom stack size (e.g., gen_ccall_with_frame +/// adjusts SP to exclude receiver and arguments). +fn gen_save_pc_for_gc(asm: &mut Assembler, state: &FrameState) { + let opcode: usize = state.get_opcode().try_into().unwrap(); + let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) }; + + gen_incr_counter(asm, Counter::vm_write_pc_count); + asm_comment!(asm, "save PC to CFP"); + asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(next_pc)); +} + /// Save the current PC on the CFP as a preparation for calling a C function /// that may allocate objects and trigger GC. Use gen_prepare_non_leaf_call() /// if it may raise exceptions or call arbitrary methods. @@ -2017,13 +2029,7 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso /// However, to avoid marking uninitialized stack slots, this also updates SP, /// which may have cfp->sp for a past frame or a past non-leaf call. fn gen_prepare_call_with_gc(asm: &mut Assembler, state: &FrameState, leaf: bool) { - let opcode: usize = state.get_opcode().try_into().unwrap(); - let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) }; - - gen_incr_counter(asm, Counter::vm_write_pc_count); - asm_comment!(asm, "save PC to CFP"); - asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(next_pc)); - + gen_save_pc_for_gc(asm, state); gen_save_sp(asm, state.stack_size()); if leaf { asm.expect_leaf_ccall(state.stack_size()); From fd45496f919f202dd30a6a76e7e24fc07abbedc0 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 8 Dec 2025 11:59:27 -0500 Subject: [PATCH 1682/2435] Update ZJIT docs (#15449) --- NEWS.md | 2 +- doc/jit/zjit.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 95c02be9513306..033fc2ea76da1b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -384,7 +384,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT * ZJIT - * Introduce an experimental method-based JIT compiler. + * Introduce an [experimental method-based JIT compiler](https://docs.ruby-lang.org/en/master/jit/zjit_md.html). To enable `--zjit` support, build Ruby with Rust 1.85.0 or later. * As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT. We encourage experimentation with ZJIT, but advise against deploying it in production for now. diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index 7434d44b9d7d60..1e5a36fd5e6f04 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -1,7 +1,49 @@ # ZJIT: ADVANCED RUBY JIT PROTOTYPE +ZJIT is a method-based just-in-time (JIT) compiler for Ruby. It uses profile +information from the interpreter to guide optimization in the compiler. + +ZJIT is currently supported for macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs. +This project is open source and falls under the same license as CRuby. + +## Current Limitations + +ZJIT may not be suitable for certain applications. It currently only supports macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs. ZJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state information. +You can change how much executable memory is allocated using [ZJIT's command-line options](rdoc-ref:@Command-Line+Options). + ## Build Instructions +### For normal use + +To build ZJIT on macOS: + +```bash +./autogen.sh + +./configure \ + --enable-zjit \ + --prefix="$HOME"/.rubies/ruby-zjit \ + --disable-install-doc \ + --with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)" + +make -j miniruby +``` + +To build ZJIT on Linux: + +```bash +./autogen.sh + +./configure \ + --enable-zjit \ + --prefix="$HOME"/.rubies/ruby-zjit \ + --disable-install-doc + +make -j miniruby +``` + +### For development + To build ZJIT on macOS: ```bash @@ -47,6 +89,35 @@ make zjit-bindgen ## Documentation +### Command-Line Options + +See `ruby --help` for ZJIT-specific command-line options: + +``` +$ ruby --help +... +ZJIT options: + --zjit-mem-size=num + Max amount of memory that ZJIT can use in MiB (default: 128). + --zjit-call-threshold=num + Number of calls to trigger JIT (default: 30). + --zjit-num-profiles=num + Number of profiled calls before JIT (default: 5). + --zjit-stats[=quiet] + Enable collecting ZJIT statistics (=quiet to suppress output). + --zjit-disable Disable ZJIT for lazily enabling it with RubyVM::ZJIT.enable. + --zjit-perf Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf. + --zjit-log-compiled-iseqs=path + Log compiled ISEQs to the file. The file will be truncated. + --zjit-trace-exits[=counter] + Record source on side-exit. `Counter` picks specific counter. + --zjit-trace-exits-sample-rate=num + Frequency at which to record side exits. Must be `usize`. +$ +``` + +### Source level documentation + You can generate and open the source level documentation in your browser using: ```bash From bd752290e743e3465286b3c656c8e31869f1c4fc Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Tue, 9 Dec 2025 00:50:32 +0900 Subject: [PATCH 1683/2435] [ruby/timeout] Revert "Exclude constantly-failing test on x86_64-darwin" This reverts commit https://github.com/ruby/timeout/commit/45816b1b2602. https://github.com/ruby/timeout/commit/b54f91e9dd --- test/test_timeout.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 3be9013f7abbc4..51666b73d8e52a 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -302,6 +302,5 @@ def test_ractor assert_equal :ok, r end; - end if defined?(::Ractor) && RUBY_VERSION >= '4.0' && !RUBY_PLATFORM.include?('x86_64-darwin') - # Exclude on x86_64-darwin as it failed 4 times out of 4 tries in the CI of ruby/ruby-dev-builder + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' end From e61b79b384307ae3358c13d015be3550db7c0b53 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 14:02:08 +0900 Subject: [PATCH 1684/2435] Fix a typo in the deprecation warning message --- include/ruby/internal/fl_type.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index da8670a8086abf..9bbcb9d2b82433 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -260,7 +260,7 @@ ruby_fl_type { RUBY_FL_EXIVAR #if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) - RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it shoudl be used.")) + RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it should not be used.")) #elif defined(_MSC_VER) # pragma deprecated(RUBY_FL_EXIVAR) #endif From 90a9b64238eca659df9d59d35f6cf59fa3851119 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 4 Dec 2025 16:13:59 -0800 Subject: [PATCH 1685/2435] Use rb_current_ec_noinline in ractor_{lock,unlock} We're seeing an occasional crash on CI because this ends up inlined all the way into ractor_wait_receive. On llvm (possibly other compilers) the thread local address of ec ends up cached (not the value of ec, the address ec is read from). So if we are migrated to another native thread, that may be invalid. Using rb_current_ec_noinline avoids this problems. It would be good to adjust this code so that ec (or current ractor) is calculated once and then passed through to both lock and unlock. --- ractor.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ractor.c b/ractor.c index 9fec70a1dfab71..f65a4f79c1f8e8 100644 --- a/ractor.c +++ b/ractor.c @@ -72,15 +72,17 @@ ractor_lock(rb_ractor_t *r, const char *file, int line) ASSERT_ractor_unlocking(r); rb_native_mutex_lock(&r->sync.lock); - if (rb_current_execution_context(false)) { - VM_ASSERT(!GET_RACTOR()->malloc_gc_disabled); - GET_RACTOR()->malloc_gc_disabled = true; + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + VM_ASSERT(!cr->malloc_gc_disabled); + cr->malloc_gc_disabled = true; } #if RACTOR_CHECK_MODE > 0 - if (rb_current_execution_context(false) != NULL) { - rb_ractor_t *cr = rb_current_ractor_raw(false); - r->sync.locked_by = cr ? rb_ractor_self(cr) : Qundef; + if (ec != NULL) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + r->sync.locked_by = rb_ractor_self(cr); } #endif @@ -105,9 +107,11 @@ ractor_unlock(rb_ractor_t *r, const char *file, int line) r->sync.locked_by = Qnil; #endif - if (rb_current_execution_context(false)) { - VM_ASSERT(GET_RACTOR()->malloc_gc_disabled); - GET_RACTOR()->malloc_gc_disabled = false; + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + VM_ASSERT(cr->malloc_gc_disabled); + cr->malloc_gc_disabled = false; } rb_native_mutex_unlock(&r->sync.lock); From 66bda731901d4588c9df1a671022231ea0d03216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 8 Dec 2025 22:58:35 +0100 Subject: [PATCH 1686/2435] Remove unused local variables in test/ruby/test_io_buffer.rb (#15451) --- test/ruby/test_io_buffer.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 9ff22b4bb356eb..cc7998842313e8 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -852,9 +852,6 @@ def test_integer_endianness_swapping assert_equal value, result_be, "#{be_type}: round-trip failed" # Verify byte patterns are different when endianness differs from host - le_bytes = buffer.get_string(0, buffer_size) - be_bytes = buffer.get_string(buffer_size, buffer_size) - if host_is_le # On little-endian host: le_type should match host, be_type should be swapped # So the byte patterns should be different (unless value is symmetric) From 55ea3ec00f5166423cd7dcd67e220cd264a766f6 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 7 Dec 2025 12:43:44 -0500 Subject: [PATCH 1687/2435] Fix strict aliasing warning in rb_int128_to_numeric If we don't have uint128, then rb_int128_to_numeric emits a strict aliasing warning: numeric.c:3641:39: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] 3641 | return rb_uint128_to_numeric(*(rb_uint128_t*)&n); | ^~~~~~~~~~~~~~~~~ --- internal/numeric.h | 5 +++++ io_buffer.c | 5 ----- numeric.c | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/numeric.h b/internal/numeric.h index 75181a7f168620..d3905f048c2ab5 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -164,6 +164,11 @@ union rb_int128 { }; typedef union rb_int128 rb_int128_t; +union uint128_int128_conversion { + rb_uint128_t uint128; + rb_int128_t int128; +}; + // Conversion functions for 128-bit integers: rb_uint128_t rb_numeric_to_uint128(VALUE x); rb_int128_t rb_numeric_to_int128(VALUE x); diff --git a/io_buffer.c b/io_buffer.c index b81527ff71147a..55f1933194ef7b 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1928,11 +1928,6 @@ ruby_swap128_uint(rb_uint128_t x) return result; } -union uint128_int128_conversion { - rb_uint128_t uint128; - rb_int128_t int128; -}; - static inline rb_int128_t ruby_swap128_int(rb_int128_t x) { diff --git a/numeric.c b/numeric.c index d9e837644ffa05..1942a6164f616a 100644 --- a/numeric.c +++ b/numeric.c @@ -3638,7 +3638,10 @@ rb_int128_to_numeric(rb_int128_t n) } else { // Positive value - return rb_uint128_to_numeric(*(rb_uint128_t*)&n); + union uint128_int128_conversion conversion = { + .int128 = n + }; + return rb_uint128_to_numeric(conversion.uint128); } #endif } From ca8630b6363a706a43bfdb5a3359addcfb616d19 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Dec 2025 15:33:54 +0900 Subject: [PATCH 1688/2435] [ruby/rubygems] Extract and generate only bundler bin files instead of full installation. https://github.com/ruby/rubygems/commit/a70e573973 --- lib/rubygems/commands/setup_command.rb | 6 ++++-- test/rubygems/test_gem_commands_setup_command.rb | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 8fcece5d5ce252..175599967cf62d 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -402,8 +402,10 @@ def install_default_bundler_gem(bin_dir) install_dir: default_dir, wrappers: true ) - installer.install - File.delete installer.spec_file + # We need to install only executable and default spec files. + # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + installer.extract_bin + installer.generate_bin installer.write_default_spec ensure FileUtils.rm_f built_gem diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index dfd951268dc962..b33e05ab28dc55 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -31,6 +31,7 @@ def setup gemspec = util_spec "bundler", "9.9.9" do |s| s.bindir = "exe" s.executables = ["bundle", "bundler"] + s.files = ["lib/bundler.rb"] end File.open "bundler/bundler.gemspec", "w" do |io| @@ -222,6 +223,9 @@ def test_install_default_bundler_gem assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}" assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0" + + assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/exe/bundle" + assert_path_not_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/lib/bundler.rb" end def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir From 09f8b8e3ad46683f1166b5dc1d5ef01cacfae3e4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 11:40:32 +0100 Subject: [PATCH 1689/2435] Test that Ractor.shareable_proc keeps the original Proc intact --- bootstraptest/test_ractor.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 9042f945f7f4f4..98fa7c276741b8 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -167,6 +167,25 @@ [pr1.call == self, pr2.call == nil] } +# Ractor.shareable_proc keeps the original Proc intact +assert_equal '[SyntaxError, [Object, 43, 43], Binding]', %q{ + a = 42 + pr1 = Proc.new do + [self.class, eval("a"), binding.local_variable_get(:a)] + end + a += 1 + pr2 = Ractor.shareable_proc(&pr1) + + r = [] + begin + pr2.call + rescue SyntaxError + r << SyntaxError + end + + r << pr1.call << pr1.binding.class +} + # Ractor::IsolationError cases assert_equal '3', %q{ ok = 0 From 39a3b88659a04729a7eec30bbc5ea804a565b9eb Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 11:42:12 +0100 Subject: [PATCH 1690/2435] Fix some descriptions in bootstraptest/test_ractor.rb --- bootstraptest/test_ractor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 98fa7c276741b8..75286e2d0c941e 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -150,14 +150,14 @@ Ractor.shareable_lambda{ a }.call } -# Ractor.make_shareable issue for locals in proc [Bug #18023] +# Ractor.shareable_proc issue for locals in proc [Bug #18023] assert_equal '[:a, :b, :c, :d, :e]', %q{ v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e closure = Proc.new { [v1, v2, v3, v4, v5] } Ractor.shareable_proc(&closure).call } -# Ractor.make_shareable makes a copy of given Proc +# Ractor.shareable_proc makes a copy of given Proc assert_equal '[true, true]', %q{ pr1 = Proc.new do self From 4cb3a61b5ec7714f57a7f43409ccc74746e7df6e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 11:47:03 +0100 Subject: [PATCH 1691/2435] Fix Ractor test to not depend on the previous test --- bootstraptest/test_ractor.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 75286e2d0c941e..17b1c05173c7bb 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -200,9 +200,9 @@ begin cond = false - a = 1 - a = 2 if cond - Ractor.shareable_proc{a} + b = 1 + b = 2 if cond + Ractor.shareable_proc{b} rescue Ractor::IsolationError => e ok += 1 end From 007a70a15c2911845f83872b83d39eeca7f0f607 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 12:03:44 +0100 Subject: [PATCH 1692/2435] Test that Ractor.make_shareable mutates the original Proc --- bootstraptest/test_ractor.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 17b1c05173c7bb..81dc2d6b8d04be 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -186,6 +186,37 @@ r << pr1.call << pr1.binding.class } +# Ractor.make_shareable mutates the original Proc +# This is the current behavior, it's currently considered safe enough +# because in most cases it would raise anyway due to not-shared self or not-shared captured variable value +assert_equal '[[42, 42], Binding, true, SyntaxError, "Can\'t create Binding from isolated Proc"]', %q{ + a = 42 + pr1 = nil.instance_exec do + Proc.new do + [eval("a"), binding.local_variable_get(:a)] + end + end + + r = [pr1.call, pr1.binding.class] + + pr2 = Ractor.make_shareable(pr1) + r << pr1.equal?(pr2) + + begin + pr1.call + rescue SyntaxError + r << SyntaxError + end + + begin + r << pr1.binding + rescue ArgumentError + r << $!.message + end + + r +} + # Ractor::IsolationError cases assert_equal '3', %q{ ok = 0 From 55668d748457e7d9db4db93c050e80afe2e9bb47 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 5 Dec 2025 16:13:23 -0800 Subject: [PATCH 1693/2435] Only globally clear the flag being cleared This solution is not quite correct because it doesn't solve multiple Ractors subscribing to the same event, but this will avoid unrelated events clobbering the flags for other events. This however will work corretly for subscribing to global ObjectSpace GC events. --- vm_trace.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vm_trace.c b/vm_trace.c index 58424008fd4f0e..3a445892761521 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -127,9 +127,15 @@ update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events rb_clear_bf_ccs(); } - ruby_vm_event_flags = new_events; - ruby_vm_event_enabled_global_flags |= new_events; - rb_objspace_set_event_hook(new_events); + // FIXME: Which flags are enabled globally comes from multiple lists, one + // per-ractor and a global list. + // This incorrectly assumes the lists have mutually exclusive flags set. + // This is true for the global (objspace) events, but not for ex. multiple + // Ractors listening for the same iseq events. + rb_event_flag_t new_events_global = (ruby_vm_event_flags & ~prev_events) | new_events; + ruby_vm_event_flags = new_events_global; + ruby_vm_event_enabled_global_flags |= new_events_global; + rb_objspace_set_event_hook(new_events_global); // Invalidate JIT code as needed if (first_time_iseq_events_p || enable_c_call || enable_c_return) { From de94f88c6820c94b69df3343c6050cdb8cc0610e Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 3 Dec 2025 00:06:03 -0800 Subject: [PATCH 1694/2435] Register internal tracepoints globally Internal tracepoints only make sense to run globally, and they already took completely different paths. --- test/objspace/test_objspace.rb | 27 +++++++++++++++++++++++++ test/objspace/test_ractor.rb | 28 ++++++++++++++++++++++++++ vm.c | 2 ++ vm_core.h | 27 +++++++++++++++++++++++-- vm_trace.c | 36 +++++++++++++++++++++++++++------- 5 files changed, 111 insertions(+), 9 deletions(-) diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index e35fc0c14ecfe5..ea3c6aa7192146 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -288,6 +288,33 @@ def test_trace_object_allocations_gc_stress assert true # success end + def test_trace_object_allocations_with_other_tracepoint + # Test that ObjectSpace.trace_object_allocations isn't changed by changes + # to another tracepoint + line_tp = TracePoint.new(:line) { } + + ObjectSpace.trace_object_allocations_start + + obj1 = Object.new; line1 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1) + assert_equal line1, ObjectSpace.allocation_sourceline(obj1) + + line_tp.enable + + obj2 = Object.new; line2 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2) + assert_equal line2, ObjectSpace.allocation_sourceline(obj2) + + line_tp.disable + + obj3 = Object.new; line3 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3) + assert_equal line3, ObjectSpace.allocation_sourceline(obj3) + ensure + ObjectSpace.trace_object_allocations_stop + ObjectSpace.trace_object_allocations_clear + end + def test_trace_object_allocations_compaction omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) diff --git a/test/objspace/test_ractor.rb b/test/objspace/test_ractor.rb index eb3044cda3c3c3..996d83fbd214dd 100644 --- a/test/objspace/test_ractor.rb +++ b/test/objspace/test_ractor.rb @@ -52,4 +52,32 @@ def fin ractors.each(&:join) RUBY end + + def test_trace_object_allocations_with_ractor_tracepoint + # Test that ObjectSpace.trace_object_allocations works globally across all Ractors + assert_ractor(<<~'RUBY', require: 'objspace') + ObjectSpace.trace_object_allocations do + obj1 = Object.new; line1 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1) + assert_equal line1, ObjectSpace.allocation_sourceline(obj1) + + r = Ractor.new { + obj = Object.new; line = __LINE__ + [line, obj] + } + + obj2 = Object.new; line2 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2) + assert_equal line2, ObjectSpace.allocation_sourceline(obj2) + + expected_line, ractor_obj = r.value + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(ractor_obj) + assert_equal expected_line, ObjectSpace.allocation_sourceline(ractor_obj) + + obj3 = Object.new; line3 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3) + assert_equal line3, ObjectSpace.allocation_sourceline(obj3) + end + RUBY + end end diff --git a/vm.c b/vm.c index b15ee0ba236b57..f832fe401f124b 100644 --- a/vm.c +++ b/vm.c @@ -3314,6 +3314,8 @@ rb_vm_mark(void *ptr) rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); + rb_hook_list_mark(&vm->global_hooks); + rb_id_table_foreach_values(vm->negative_cme_table, vm_mark_negative_cme, NULL); rb_mark_tbl_no_pin(vm->overloaded_cme_table); for (i=0; ihooks; } +static inline rb_hook_list_t * +rb_vm_global_hooks(const rb_execution_context_t *ec) +{ + return &rb_ec_vm_ptr(ec)->global_hooks; +} + +static inline rb_hook_list_t * +rb_ec_hooks(const rb_execution_context_t *ec, rb_event_flag_t event) +{ + // Should be a single bit set + VM_ASSERT(event != 0 && ((event - 1) & event) == 0); + + if (event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) { + return rb_vm_global_hooks(ec); + } + else { + return rb_ec_ractor_hooks(ec); + } +} + #define EXEC_EVENT_HOOK(ec_, flag_, self_, id_, called_id_, klass_, data_) \ - EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_ractor_hooks(ec_), flag_, self_, id_, called_id_, klass_, data_, 0) + EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_hooks(ec_, flag_), flag_, self_, id_, called_id_, klass_, data_, 0) #define EXEC_EVENT_HOOK_AND_POP_FRAME(ec_, flag_, self_, id_, called_id_, klass_, data_) \ - EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_ractor_hooks(ec_), flag_, self_, id_, called_id_, klass_, data_, 1) + EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_hooks(ec_, flag_), flag_, self_, id_, called_id_, klass_, data_, 1) static inline void rb_exec_event_hook_script_compiled(rb_execution_context_t *ec, const rb_iseq_t *iseq, VALUE eval_script) diff --git a/vm_trace.c b/vm_trace.c index 3a445892761521..b2fc436a96b210 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -194,7 +194,17 @@ hook_list_connect(VALUE list_owner, rb_hook_list_t *list, rb_event_hook_t *hook, static void connect_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook) { - rb_hook_list_t *list = rb_ec_ractor_hooks(ec); + rb_hook_list_t *list; + + /* internal events are VM-global, non-internal are ractor-local */ + VM_ASSERT(!(hook->events & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) || !(hook->events & ~RUBY_INTERNAL_EVENT_OBJSPACE_MASK)); + + if (hook->events & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) { + list = rb_vm_global_hooks(ec); + } + else { + list = rb_ec_ractor_hooks(ec); + } hook_list_connect(Qundef, list, hook, TRUE); } @@ -278,11 +288,9 @@ clean_hooks_check(rb_hook_list_t *list) #define MATCH_ANY_FILTER_TH ((rb_thread_t *)1) -/* if func is 0, then clear all funcs */ static int -remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) +remove_event_hook_from_list(rb_hook_list_t *list, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) { - rb_hook_list_t *list = rb_ec_ractor_hooks(ec); int ret = 0; rb_event_hook_t *hook = list->hooks; @@ -303,6 +311,18 @@ remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th return ret; } +/* if func is 0, then clear all funcs */ +static int +remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) +{ + int ret = 0; + + ret += remove_event_hook_from_list(rb_ec_ractor_hooks(ec), filter_th, func, data); + ret += remove_event_hook_from_list(rb_vm_global_hooks(ec), filter_th, func, data); + + return ret; +} + static int rb_threadptr_remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) { @@ -427,8 +447,10 @@ rb_exec_event_hooks(rb_trace_arg_t *trace_arg, rb_hook_list_t *hooks, int pop_p) { rb_execution_context_t *ec = trace_arg->ec; - if (UNLIKELY(trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) { - if (ec->trace_arg && (ec->trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) { + if (UNLIKELY(trace_arg->event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK)) { + VM_ASSERT(hooks == rb_vm_global_hooks(ec)); + + if (ec->trace_arg && (ec->trace_arg->event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK)) { /* skip hooks because this thread doing INTERNAL_EVENT */ } else { @@ -436,7 +458,7 @@ rb_exec_event_hooks(rb_trace_arg_t *trace_arg, rb_hook_list_t *hooks, int pop_p) ec->trace_arg = trace_arg; /* only global hooks */ - exec_hooks_unprotected(ec, rb_ec_ractor_hooks(ec), trace_arg); + exec_hooks_unprotected(ec, hooks, trace_arg); ec->trace_arg = prev_trace_arg; } } From 576acb950232a8d979b94117050f577505d8a167 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Dec 2025 16:02:49 +0900 Subject: [PATCH 1695/2435] Remove `FORWARD_ARGS_WITH_RUBY2_KEYWORDS` check Because `FORWARD_ARGS_WITH_RUBY2_KEYWORDS` definition was removed by 4f77d8d3289ece0e3537d9273a5c745120bff59a. --- parse.y | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/parse.y b/parse.y index 32431434de9734..b700302e7b615f 100644 --- a/parse.y +++ b/parse.y @@ -6385,11 +6385,7 @@ f_args : f_arg ',' f_opt_arg(arg_value) ',' f_rest_arg opt_args_tail(args_tail) args_forward : tBDOT3 { -#ifdef FORWARD_ARGS_WITH_RUBY2_KEYWORDS - $$ = 0; -#else $$ = idFWD_KWREST; -#endif /*% ripper: args_forward! %*/ } ; @@ -14434,11 +14430,7 @@ new_args(struct parser_params *p, rb_node_args_aux_t *pre_args, rb_node_opt_arg_ args->opt_args = opt_args; -#ifdef FORWARD_ARGS_WITH_RUBY2_KEYWORDS - args->ruby2_keywords = args->forwarding; -#else args->ruby2_keywords = 0; -#endif nd_set_loc(RNODE(tail), loc); @@ -15049,9 +15041,7 @@ static void add_forwarding_args(struct parser_params *p) { arg_var(p, idFWD_REST); -#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS arg_var(p, idFWD_KWREST); -#endif arg_var(p, idFWD_BLOCK); arg_var(p, idFWD_ALL); } @@ -15094,15 +15084,11 @@ static NODE * new_args_forward_call(struct parser_params *p, NODE *leading, const YYLTYPE *loc, const YYLTYPE *argsloc) { NODE *rest = NEW_LVAR(idFWD_REST, loc); -#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS NODE *kwrest = list_append(p, NEW_LIST(0, loc), NEW_LVAR(idFWD_KWREST, loc)); -#endif rb_node_block_pass_t *block = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, loc), argsloc, &NULL_LOC); NODE *args = leading ? rest_arg_append(p, leading, rest, argsloc) : NEW_SPLAT(rest, loc, &NULL_LOC); block->forwarding = TRUE; -#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS args = arg_append(p, args, new_hash(p, kwrest, loc), argsloc); -#endif return arg_blk_pass(args, block); } From 056997cbcdd38b062518fe54e311c964f8bfd98f Mon Sep 17 00:00:00 2001 From: yui-knk Date: Tue, 9 Dec 2025 09:05:45 +0900 Subject: [PATCH 1696/2435] Remove needless `ruby2_keywords` field from `struct rb_args_info` `ruby2_keywords` is set only to be `0` in parse.y. However `args->ruby2_keywords` is initialized with `0` by `MEMZERO` in `rb_node_args_new` function and `body->param.flags.ruby2_keywords` is initialized with `0` by `ZALLOC` in `rb_iseq_constant_body_alloc` function, so `args->ruby2_keywords` does nothing for `body->param.flags.ruby2_keywords`. --- compile.c | 1 - parse.y | 2 -- rubyparser.h | 1 - 3 files changed, 4 deletions(-) diff --git a/compile.c b/compile.c index e6665c7e1d966f..a5d821eb810cf1 100644 --- a/compile.c +++ b/compile.c @@ -2106,7 +2106,6 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons EXPECT_NODE("iseq_set_arguments", node_args, NODE_ARGS, COMPILE_NG); - body->param.flags.ruby2_keywords = args->ruby2_keywords; body->param.lead_num = arg_size = (int)args->pre_args_num; if (body->param.lead_num > 0) body->param.flags.has_lead = TRUE; debugs(" - argc: %d\n", body->param.lead_num); diff --git a/parse.y b/parse.y index b700302e7b615f..cb73ea2ef026bb 100644 --- a/parse.y +++ b/parse.y @@ -14430,8 +14430,6 @@ new_args(struct parser_params *p, rb_node_args_aux_t *pre_args, rb_node_opt_arg_ args->opt_args = opt_args; - args->ruby2_keywords = 0; - nd_set_loc(RNODE(tail), loc); return tail; diff --git a/rubyparser.h b/rubyparser.h index ee4fe2e44da03d..5af8f6db62c308 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -782,7 +782,6 @@ struct rb_args_info { struct RNode_OPT_ARG *opt_args; unsigned int no_kwarg: 1; - unsigned int ruby2_keywords: 1; unsigned int forwarding: 1; }; From fab94ecd344b5804f9a1e6b38082049f37bacbd3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 13:04:34 +0100 Subject: [PATCH 1697/2435] [ruby/rubygems] Fix the config suggestion in the warning for `$ bundle` * `install_or_cli_help` does not exist for older Bundler like Bundler 2 and so results in a confusing error on Bundler 2: ``` $ bundle Could not find command "". ``` * See https://github.com/ruby/rubygems/pull/9136/files#r2592366837 * Merge the behavior of `install_or_cli_help` in `install`. https://github.com/ruby/rubygems/commit/9b4819ae18 --- lib/bundler/cli.rb | 17 +++++++++++------ spec/bundler/bundler/cli_spec.rb | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 36ce04eb2a5d05..1321c56ed740c1 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -120,12 +120,14 @@ def cli_help self.class.send(:class_options_help, shell) end - desc "install_or_cli_help", "Tries to run bundle install but prints a summary of bundler commands if there is no Gemfile", hide: true + desc "install_or_cli_help", "Deprecated alias of install", hide: true def install_or_cli_help + Bundler.ui.warn <<~MSG + `bundle install_or_cli_help` is a deprecated alias of `bundle install`. + It might be called due to the 'default_cli_command' being set to 'install_or_cli_help', + if so fix that by running `bundle config set default_cli_command install --global`. + MSG invoke_other_command("install") - rescue GemfileNotFound => error - Bundler.ui.error error.message, wrap: true - invoke_other_command("cli_help") end def self.default_command(meth = nil) @@ -136,12 +138,12 @@ def self.default_command(meth = nil) In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, - or you can continue to use the current behavior with `bundle config set default_cli_command install_or_cli_help --global`. + or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. This message will be removed after a default_cli_command value is set. MSG end - Bundler.settings[:default_cli_command] || "install_or_cli_help" + Bundler.settings[:default_cli_command] || "install" end class_option "no-color", type: :boolean, desc: "Disable colorization in output" @@ -287,6 +289,9 @@ def install Bundler.settings.temporary(no_install: false) do Install.new(options).run end + rescue GemfileNotFound => error + invoke_other_command("cli_help") + raise error # re-raise to show the error and get a failing exit status end map aliases_for("install") diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 4503cea6a0058c..7bc8fd3d36adaf 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -100,10 +100,11 @@ def out_with_macos_man_workaround end it "runs bundle install when default_cli_command set to install" do - bundle "config set default_cli_command install_or_cli_help" + bundle "config set default_cli_command install" bundle "", raise_on_error: false expect(out).to_not include("In a future version of Bundler") expect(err).to include("Could not locate Gemfile") + expect(exitstatus).to_not be_zero end end From 19172d64ebe909f185e28b1d8368a8a920f94a8b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 12:24:03 +0100 Subject: [PATCH 1698/2435] [ruby/rubygems] Fix indentation of the info message for default_cli_command * It looked like: In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. This message will be removed after a default_cli_command value is set. Bundler version 4.0.0 (2025-12-08 commit https://github.com/ruby/rubygems/commit/9b4819ae18) * And now looks like: In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. This message will be removed after a default_cli_command value is set. Bundler version 4.0.0 (2025-12-08 commit https://github.com/ruby/rubygems/commit/9b4819ae18) https://github.com/ruby/rubygems/commit/979dada8f3 --- lib/bundler/cli.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 1321c56ed740c1..17d8c42e6ecdc5 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -134,12 +134,13 @@ def self.default_command(meth = nil) return super if meth unless Bundler.settings[:default_cli_command] - Bundler.ui.info <<-MSG + Bundler.ui.info <<~MSG In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. This message will be removed after a default_cli_command value is set. + MSG end From 12c16f9dedb38af8111809229495e28e8d37b569 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 8 Dec 2025 17:39:34 +0100 Subject: [PATCH 1699/2435] [ruby/rubygems] Fix Bundler removing executables after creating them When running a fresh `bundle install` with gems that contains executables, Bundler will generate binstubs but soon after will remove them. This is a regression introduced in https://github.com/ruby/rubygems/commit/573ffad3ea4a. This results in doing `bundle install && bundle exec foo` to raise an error saying `foo` couldn't be found. This issue only appears if `BUNDLE_CLEAN` is set. At the end of the installation process, when Bundler has installed gems and generated binstubs, it runs the cleanup. 1. It [detects](https://github.com/ruby/rubygems/blob/4f8aa3b40cded3465bb2cd761e9ce7f8673b7fcb/bundler/lib/bundler/runtime.rb#L182) the executable for the current specs 2. Any existing executables not detected is then removed https://github.com/ruby/rubygems/blob/4f8aa3b40cded3465bb2cd761e9ce7f8673b7fcb/bundler/lib/bundler/runtime.rb#L194. The issue being that 1. now returns an empty array where as it should return the executables of the gems from the current bundle. The problem is in https://github.com/ruby/rubygems/commit/573ffad3ea4a where we removed the `executables` method from the `EndpointSpecification`. When Bundler reads the lockfile, it creates a `EndpointSpecification` object for each spec. At this point, the EndpointSpecification doesn't know about the `executables` of a gem. Once Bundler fetches the `gemspec` from the remote, it swaps the the "spec" with the real one and from here knows what executables the gem has. Reintroduce the `executables` method and the `bindir` in the EndpointSpecification class. From what I'm seeing, the removal of those wasn't needed to resolve the issue where Bundler remembers CLI flags. This is probably an oversight. https://github.com/ruby/rubygems/commit/b47f6b0247 --- lib/bundler/endpoint_specification.rb | 22 ++++++++++++++++++++++ spec/bundler/commands/install_spec.rb | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 95c6deea26bf4e..c06684657d598b 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -60,6 +60,28 @@ def load_paths end end + # needed for binstubs + def executables + if @remote_specification + @remote_specification.executables + elsif _local_specification + _local_specification.executables + else + super + end + end + + # needed for bundle clean + def bindir + if @remote_specification + @remote_specification.bindir + elsif _local_specification + _local_specification.bindir + else + super + end + end + # needed for post_install_messages during install def post_install_message if @remote_specification diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 3dc8aa0dc01757..41b3a865cd9dfd 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -1885,6 +1885,25 @@ def run expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) end + it "prevents removing binstubs when BUNDLE_CLEAN is set" do + build_repo4 do + build_gem "kamal", "4.0.6" do |s| + s.executables = ["kamal"] + end + end + + gemfile = <<~G + source "https://gem.repo4" + gem "kamal" + G + + install_gemfile(gemfile, env: { "BUNDLE_CLEAN" => "true", "BUNDLE_PATH" => "vendor/bundle" }) + + expected_executables = [vendored_gems("bin/kamal").to_s] + expected_executables << vendored_gems("bin/kamal.bat").to_s if Gem.win_platform? + expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) + end + it "preserves lockfile versions conservatively" do build_repo4 do build_gem "mypsych", "4.0.6" do |s| From 71354a9879c5e68b215469ef1b17eeb1ba308ada Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 8 Dec 2025 08:26:28 -0500 Subject: [PATCH 1700/2435] [ruby/prism] Fix hash pattern location when missing nodes https://github.com/ruby/prism/commit/0ad30561e2 --- prism/prism.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 93392da3499fbe..0cb8c9057054d1 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -4096,8 +4096,8 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme if (elements->size > 0) { if (rest) { - start = elements->nodes[0]->location.start; - end = rest->location.end; + start = MIN(rest->location.start, elements->nodes[0]->location.start); + end = MAX(rest->location.end, elements->nodes[elements->size - 1]->location.end); } else { start = elements->nodes[0]->location.start; end = elements->nodes[elements->size - 1]->location.end; @@ -4117,11 +4117,7 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme .closing_loc = { 0 } }; - pm_node_t *element; - PM_NODE_LIST_FOREACH(elements, index, element) { - pm_node_list_append(&node->elements, element); - } - + pm_node_list_concat(&node->elements, elements); return node; } From cbf39c3be5b71b7321cfba768417427e7c3bf4ad Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 8 Dec 2025 08:37:52 -0500 Subject: [PATCH 1701/2435] [ruby/prism] Fix up call target node when invalid When there is an invalid syntax tree, we need to make sure to fill in the required call operator location. https://github.com/ruby/prism/commit/937313d7f0 --- prism/prism.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index 0cb8c9057054d1..7f1d782f35cfa6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -3077,6 +3077,17 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { .message_loc = target->message_loc }; + /* It is possible to get here where we have parsed an invalid syntax tree + * where the call operator was not present. In that case we will have a + * problem because it is a required location. In this case we need to fill + * it in with a fake location so that the syntax tree remains valid. */ + if (node->call_operator_loc.start == NULL) { + node->call_operator_loc = (pm_location_t) { + .start = target->base.location.start, + .end = target->base.location.start + }; + } + // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. From 268cbb29c7f69ea5a6d45973091a6e5f5aa89403 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 8 Dec 2025 10:07:39 -0500 Subject: [PATCH 1702/2435] [ruby/prism] Fully handle unreferencing a block exit If a block exit has a further block exit in its subtree, we need to keep recursing. https://github.com/ruby/prism/commit/855d81a4a8 --- prism/prism.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 7f1d782f35cfa6..ace4b0b1abcf2c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12540,7 +12540,10 @@ pm_node_unreference_each(const pm_node_t *node, void *data) { ); } parser->current_block_exits->size--; - return false; + + /* Note returning true here because these nodes could have + * arguments that are themselves block exits. */ + return true; } index++; From 59314911a13d10eab2afbca62081311d702a1373 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 8 Dec 2025 10:18:42 -0500 Subject: [PATCH 1703/2435] [ruby/prism] Nested heredoc with newline terminator When you have a heredoc interpolated into another heredoc where the inner heredoc is terminated by a newline, you need to avoid adding the newline character a second time. https://github.com/ruby/prism/commit/8eeb5f358b --- prism/prism.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index ace4b0b1abcf2c..20037b5b9f6b6c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12031,7 +12031,10 @@ parser_lex(pm_parser_t *parser) { // string content. if (heredoc_lex_mode->indent == PM_HEREDOC_INDENT_TILDE) { const uint8_t *end = parser->current.end; - pm_newline_list_append(&parser->newline_list, end); + + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, end); + } // Here we want the buffer to only // include up to the backslash. From 25f277abe481b9109d59d28a2eee3f9998a9ad3a Mon Sep 17 00:00:00 2001 From: hi Date: Fri, 5 Dec 2025 23:56:32 +0900 Subject: [PATCH 1704/2435] Fix typos in gc.c and gc.rb --- gc.c | 2 +- gc.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index 5e3e44e726704d..79eec5d96bf7af 100644 --- a/gc.c +++ b/gc.c @@ -1816,7 +1816,7 @@ id2ref_tbl_mark(void *data) // It's very unlikely, but if enough object ids were generated, keys may be T_BIGNUM rb_mark_set(table); } - // We purposedly don't mark values, as they are weak references. + // We purposely don't mark values, as they are weak references. // rb_gc_obj_free_vm_weak_references takes care of cleaning them up. } diff --git a/gc.rb b/gc.rb index ccad5ef2c1dbfa..59adcbc62f64d6 100644 --- a/gc.rb +++ b/gc.rb @@ -37,7 +37,7 @@ module GC # interleaved with program execution both before the method returns and afterward; # therefore sweeping may not be completed before the return. # - # Note that these keword arguments are implementation- and version-dependent, + # Note that these keyword arguments are implementation- and version-dependent, # are not guaranteed to be future-compatible, # and may be ignored in some implementations. def self.start full_mark: true, immediate_mark: true, immediate_sweep: true From 79c57d747f7d1e6253a6df7b624a9930cb8a523c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 9 Dec 2025 17:22:03 +0900 Subject: [PATCH 1705/2435] Fixed by `misspell -w -error -source=text` --- concurrent_set.c | 2 +- lib/erb/util.rb | 2 +- lib/net/http.rb | 2 +- string.c | 2 +- zjit.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/concurrent_set.c b/concurrent_set.c index e48d9a46e89d3f..3aa61507aaa7d2 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -129,7 +129,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) new_capacity = old_capacity; } - // May cause GC and therefore deletes, so must hapen first. + // May cause GC and therefore deletes, so must happen first. VALUE new_set_obj = rb_concurrent_set_new(old_set->funcs, new_capacity); struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj); diff --git a/lib/erb/util.rb b/lib/erb/util.rb index adeb695aa81fec..d7d69eb4f159a3 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -9,7 +9,7 @@ require 'cgi/escape' # Load or define ERB::Escape#html_escape. -# We don't build the C extention 'cgi/escape' for JRuby, TruffleRuby, and WASM. +# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM. # miniruby (used by CRuby build scripts) also fails to load erb/escape.so. begin require 'erb/escape' diff --git a/lib/net/http.rb b/lib/net/http.rb index 8a4ff481872abc..3c061286121791 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1323,7 +1323,7 @@ def response_body_encoding=(value) # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass - # Sets wheter the proxy uses SSL; + # Sets whether the proxy uses SSL; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl diff --git a/string.c b/string.c index 9327306384a232..52d1f28cc1443f 100644 --- a/string.c +++ b/string.c @@ -3979,7 +3979,7 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) clear_cr: // If no fast path was hit, we clear the coderange. - // append_as_bytes is predominently meant to be used in + // append_as_bytes is predominantly meant to be used in // buffering situation, hence it's likely the coderange // will never be scanned, so it's not worth spending time // precomputing the coderange except for simple and common diff --git a/zjit.c b/zjit.c index d62c5c58c6334c..7731bc908f6ab9 100644 --- a/zjit.c +++ b/zjit.c @@ -103,7 +103,7 @@ rb_zjit_exit_locations_dict(VALUE *zjit_raw_samples, int *zjit_line_samples, int // Loop through the length of samples_len and add data to the // frames hash. Also push the current value onto the raw_samples - // and line_samples arrary respectively. + // and line_samples array respectively. for (int o = 0; o < num; o++) { rb_zjit_add_frame(frames, zjit_raw_samples[idx]); rb_ary_push(raw_samples, SIZET2NUM(zjit_raw_samples[idx])); From 99133a66f3e5c64c2c2f5dbca0d1be816254f636 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 13:11:55 +0100 Subject: [PATCH 1706/2435] [ruby/net-http] Check whether TCPSocket#initialize supports open_timeout once and without exceptions * See discussion in https://github.com/ruby/net-http/pull/224 * This check is known to work on at least CRuby, TruffleRuby and JRuby. * Exceptions show up with `ruby -d`/`$DEBUG == true` and would show for every Net::HTTP instance. https://github.com/ruby/net-http/commit/8c76f92779 --- lib/net/http.rb | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 3c061286121791..c63bdddcad621e 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1772,26 +1772,24 @@ def connect end private :connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) + end + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + def timeouted_connect(conn_addr, conn_port) - if @tcpsocket_supports_open_timeout == nil || @tcpsocket_supports_open_timeout == true - # Try to use built-in open_timeout in TCPSocket.open if: - # - The current Ruby runtime is known to support it, or - # - It is unknown whether the current Ruby runtime supports it (so we'll try). - begin - sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) - @tcpsocket_supports_open_timeout = true - return sock - rescue ArgumentError => e - raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)')) - @tcpsocket_supports_open_timeout = false - end + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } end - - # This Ruby runtime is known not to support `TCPSocket(open_timeout:)`. - # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue. - Timeout.timeout(@open_timeout, Net::OpenTimeout) { - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - } end private :timeouted_connect From ee6784f2899364f9bfd8d605b1bea7d78777a116 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 20 Oct 2025 13:30:46 +0900 Subject: [PATCH 1707/2435] Refine `copy_ext_file` - Define the error constants. - Use system calls to copy files if available. - Simplify fallback copying. - Copy without stdio buffering. --- box.c | 192 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 108 insertions(+), 84 deletions(-) diff --git a/box.c b/box.c index 3fbc4e9fe23a14..86424dedc8b1e8 100644 --- a/box.c +++ b/box.c @@ -20,6 +20,13 @@ #include +#ifdef HAVE_SYS_SENDFILE_H +# include +#endif +#ifdef HAVE_COPYFILE_H +#include +#endif + VALUE rb_cBox = 0; VALUE rb_cBoxEntry = 0; VALUE rb_mBoxLoader = 0; @@ -518,10 +525,21 @@ sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, con return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename); } -#ifdef _WIN32 +enum copy_error_type { + COPY_ERROR_NONE, + COPY_ERROR_SRC_OPEN, + COPY_ERROR_DST_OPEN, + COPY_ERROR_SRC_READ, + COPY_ERROR_DST_WRITE, + COPY_ERROR_SRC_STAT, + COPY_ERROR_DST_CHMOD, + COPY_ERROR_SYSERR +}; + static const char * -copy_ext_file_error(char *message, size_t size) +copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path) { +#ifdef _WIN32 int error = GetLastError(); char *p = message; size_t len = snprintf(message, size, "%d: ", error); @@ -536,120 +554,130 @@ copy_ext_file_error(char *message, size_t size) if (*p == '\n' || *p == '\r') *p = ' '; } - return message; -} #else -static const char * -copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path) -{ switch (copy_retvalue) { - case 1: + case COPY_ERROR_SRC_OPEN: snprintf(message, size, "can't open the extension path: %s", src_path); break; - case 2: + case COPY_ERROR_DST_OPEN: snprintf(message, size, "can't open the file to write: %s", dst_path); break; - case 3: + case COPY_ERROR_SRC_READ: snprintf(message, size, "failed to read the extension path: %s", src_path); break; - case 4: + case COPY_ERROR_DST_WRITE: snprintf(message, size, "failed to write the extension path: %s", dst_path); break; - case 5: + case COPY_ERROR_SRC_STAT: snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path); break; - case 6: + case COPY_ERROR_DST_CHMOD: snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path); break; + case COPY_ERROR_SYSERR: + snprintf(message, size, "failed to copy the extension: %s", strerror(errno)); + break; + case COPY_ERROR_NONE: /* shouldn't be called */ default: rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue); } +#endif return message; } + +#ifndef _WIN32 +static enum copy_error_type +copy_stream(int src_fd, int dst_fd) +{ + char buffer[1024]; + ssize_t rsize; + + while ((rsize = read(src_fd, buffer, sizeof(buffer))) != 0) { + if (rsize < 0) return COPY_ERROR_SRC_READ; + for (size_t written = 0; written < (size_t)rsize;) { + ssize_t wsize = write(dst_fd, buffer+written, rsize-written); + if (wsize < 0) return COPY_ERROR_DST_WRITE; + written += (size_t)wsize; + } + } + return COPY_ERROR_NONE; +} #endif -static int +static enum copy_error_type copy_ext_file(const char *src_path, const char *dst_path) { #if defined(_WIN32) - int rvalue; - WCHAR *w_src = rb_w32_mbstr_to_wstr(CP_UTF8, src_path, -1, NULL); WCHAR *w_dst = rb_w32_mbstr_to_wstr(CP_UTF8, dst_path, -1, NULL); if (!w_src || !w_dst) { + free(w_src); + free(w_dst); rb_memerror(); } - rvalue = CopyFileW(w_src, w_dst, FALSE) ? 0 : 1; + enum copy_error_type rvalue = CopyFileW(w_src, w_dst, TRUE) ? + COPY_ERROR_NONE : COPY_ERROR_SYSERR; free(w_src); free(w_dst); return rvalue; #else - FILE *src, *dst; - char buffer[1024]; - size_t read = 0, wrote, written = 0; - size_t maxread = sizeof(buffer); - int eof = 0; - int clean_read = 1; - int retvalue = 0; - - src = fopen(src_path, "rb"); - if (!src) { - return 1; +# ifdef O_BINARY + const int bin = O_BINARY; +# else + const int bin = 0; +# endif + const int src_fd = open(src_path, O_RDONLY|bin); + if (src_fd < 0) return COPY_ERROR_SRC_OPEN; + + struct stat src_st; + if (fstat(src_fd, &src_st)) { + close(src_fd); + return COPY_ERROR_SRC_STAT; } - dst = fopen(dst_path, "wb"); - if (!dst) { - return 2; + + const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU); + if (dst_fd < 0) { + close(src_fd); + return COPY_ERROR_DST_OPEN; } - while (!eof) { - if (clean_read) { - read = fread(buffer, 1, sizeof(buffer), src); - written = 0; - } - if (read > 0) { - wrote = fwrite(buffer+written, 1, read-written, dst); - if (wrote < read-written) { - if (ferror(dst)) { - retvalue = 4; - break; - } - else { // partial write - clean_read = 0; - written += wrote; - } - } - else { // Wrote the entire buffer to dst, next read is clean one - clean_read = 1; - } - } - if (read < maxread) { - if (clean_read && feof(src)) { - // If it's not clean, buffer should have bytes not written yet. - eof = 1; - } - else if (ferror(src)) { - retvalue = 3; - // Writes could be partial/dirty, but this load is failure anyway - break; - } - } + + enum copy_error_type ret = COPY_ERROR_NONE; + + if (fchmod(dst_fd, src_st.st_mode & 0777)) { + ret = COPY_ERROR_DST_CHMOD; + goto done; } - fclose(src); - fclose(dst); -#if defined(__CYGWIN__) - // On Cygwin, CopyFile-like operations may strip executable bits. - // Explicitly match destination file permissions to source. - if (retvalue == 0) { - struct stat st; - if (stat(src_path, &st) != 0) { - retvalue = 5; - } - else if (chmod(dst_path, st.st_mode & 0777) != 0) { - retvalue = 6; - } + + const size_t count_max = (SIZE_MAX >> 1) + 1; + (void)count_max; + +# ifdef HAVE_COPY_FILE_RANGE + for (;;) { + ssize_t written = copy_file_range(src_fd, NULL, dst_fd, NULL, count_max, 0); + if (written == 0) goto done; + if (written < 0) break; } -#endif - return retvalue; +# endif +# ifdef HAVE_FCOPYFILE + if (fcopyfile(src_fd, dst_fd, NULL, COPYFILE_DATA) == 0) { + goto done; + } +# endif +# ifdef USE_SENDFILE + for (;;) { + ssize_t written = sendfile(src_fd, dst_fd, NULL count_max); + if (written == 0) goto done; + if (written < 0) break; + } +# endif + ret = copy_stream(src_fd, dst_fd); + + done: + close(src_fd); + if (dst_fd >= 0) close(dst_fd); + if (ret != COPY_ERROR_NONE) unlink(dst_path); + return ret; #endif } @@ -700,7 +728,7 @@ VALUE rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) { char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN]; - int copy_error, wrote; + int wrote; const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); rb_box_t *box = rb_get_box_t(box_value); @@ -711,14 +739,10 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) if (wrote >= (int)sizeof(ext_path)) { rb_bug("Extension file path in the box was too long"); } - copy_error = copy_ext_file(src_path, ext_path); + enum copy_error_type copy_error = copy_ext_file(src_path, ext_path); if (copy_error) { char message[1024]; -#if defined(_WIN32) - copy_ext_file_error(message, sizeof(message)); -#else copy_ext_file_error(message, sizeof(message), copy_error, src_path, ext_path); -#endif rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %s): %s", ext_path, src_path, message); } // TODO: register the path to be clean-uped From 8d1eafa7a7289141338a6ace6570e6850e529347 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 12 Nov 2025 00:37:27 +0900 Subject: [PATCH 1708/2435] Remove duplicate path names in error message --- box.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/box.c b/box.c index 86424dedc8b1e8..e7065c1c298815 100644 --- a/box.c +++ b/box.c @@ -537,7 +537,7 @@ enum copy_error_type { }; static const char * -copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path) +copy_ext_file_error(char *message, size_t size, int copy_retvalue) { #ifdef _WIN32 int error = GetLastError(); @@ -557,25 +557,25 @@ copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *s #else switch (copy_retvalue) { case COPY_ERROR_SRC_OPEN: - snprintf(message, size, "can't open the extension path: %s", src_path); + strlcpy(message, "can't open the extension path", size); break; case COPY_ERROR_DST_OPEN: - snprintf(message, size, "can't open the file to write: %s", dst_path); + strlcpy(message, "can't open the file to write", size); break; case COPY_ERROR_SRC_READ: - snprintf(message, size, "failed to read the extension path: %s", src_path); + strlcpy(message, "failed to read the extension path", size); break; case COPY_ERROR_DST_WRITE: - snprintf(message, size, "failed to write the extension path: %s", dst_path); + strlcpy(message, "failed to write the extension path", size); break; case COPY_ERROR_SRC_STAT: - snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path); + strlcpy(message, "failed to stat the extension path to copy permissions", size); break; case COPY_ERROR_DST_CHMOD: - snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path); + strlcpy(message, "failed to set permissions to the copied extension path", size); break; case COPY_ERROR_SYSERR: - snprintf(message, size, "failed to copy the extension: %s", strerror(errno)); + strlcpy(message, strerror(errno), size); break; case COPY_ERROR_NONE: /* shouldn't be called */ default: @@ -742,8 +742,8 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) enum copy_error_type copy_error = copy_ext_file(src_path, ext_path); if (copy_error) { char message[1024]; - copy_ext_file_error(message, sizeof(message), copy_error, src_path, ext_path); - rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %s): %s", ext_path, src_path, message); + copy_ext_file_error(message, sizeof(message), copy_error); + rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %"PRIsVALUE"): %s", ext_path, path, message); } // TODO: register the path to be clean-uped return rb_str_new_cstr(ext_path); From 875c4c7dfdf9f9271fde47d6616249724af4d014 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 9 Dec 2025 13:51:04 +0900 Subject: [PATCH 1709/2435] [ruby/rubygems] Bump Bundler version to 4.0.1 (cherry picked from commit https://github.com/ruby/rubygems/commit/26c1db5a65a8) https://github.com/ruby/rubygems/commit/bbb5b767d0 --- lib/bundler/version.rb | 2 +- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 8cdc3067366a8e..411829b28a3d35 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.0".freeze + VERSION = "4.0.1".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 92b16c76064450..866a77125bcaed 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 90090e5fbfdc7e..67c887c84c2554 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 4cc6655e43d631..45062e42253876 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 77bdc4b6f90a2a..b45717d4e3eaca 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 354a57d660e99b..9fff0eb0fcdc89 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.0 + 4.0.1 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index b11a9607255aa1..ac581de02e7fd5 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.0 + 4.0.1 From 0da74e0aa0189b1d2ec9dadf7b580f9bcd6adee7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 9 Dec 2025 13:51:05 +0900 Subject: [PATCH 1710/2435] [ruby/rubygems] Bump Rubygems version to 4.0.1 (cherry picked from commit https://github.com/ruby/rubygems/commit/f3e5ebf5afe7) https://github.com/ruby/rubygems/commit/583b0222ad --- lib/rubygems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index eaeffc7a54d00b..55e727425fbe4d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.0" + VERSION = "4.0.1" end require_relative "rubygems/defaults" From 4b8e48a362e28fe3b89694dbb0d3ee1c36368583 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 9 Dec 2025 12:10:58 +0000 Subject: [PATCH 1711/2435] Update default gems list at 0da74e0aa0189b1d2ec9dadf7b580f [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 033fc2ea76da1b..e9dfa948a634a9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -232,8 +232,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.0 -* bundler 4.0.0 +* RubyGems 4.0.1 +* bundler 4.0.1 * date 3.5.0 * digest 3.2.1 * english 0.8.1 From 5ae2bd240f0adea46358f0a95bc26276d9f0cc31 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 9 Dec 2025 21:29:36 +0900 Subject: [PATCH 1712/2435] [DOC] Add Ruby::Box on NEWS --- NEWS.md | 8 ++++++++ doc/language/box.md | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index e9dfa948a634a9..784b92b717d1c5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -160,6 +160,12 @@ Note: We're only listing outstanding class updates. Ruby-related constants. This module was reserved in Ruby 3.4 and is now officially defined. [[Feature #20884]] +* Ruby::Box + + * A new (experimental) feature to provide separation about definitions. + For the detail of "Ruby Box", see [doc/language/box.md](doc/language/box.md). + [[Feature #21311]] [[Misc #21385]] + * Set * `Set` is now a core class, instead of an autoloaded stdlib class. @@ -427,8 +433,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21275]: https://bugs.ruby-lang.org/issues/21275 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 +[Feature #21311]: https://bugs.ruby-lang.org/issues/21311 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 +[Misc #21385]: https://bugs.ruby-lang.org/issues/21385 [Feature #21389]: https://bugs.ruby-lang.org/issues/21389 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21459]: https://bugs.ruby-lang.org/issues/21459 diff --git a/doc/language/box.md b/doc/language/box.md index ed62cbd072dcff..928c98eb3c32bb 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -1,6 +1,6 @@ # Ruby Box - Ruby's in-process separation of Classes and Modules -Ruby Box is designed to provide separated spaces in a Ruby process, to isolate applications and libraries. +Ruby Box is designed to provide separated spaces in a Ruby process, to isolate application codes, libraries and monkey patches. ## Known issues @@ -12,13 +12,11 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## TODOs * Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) -* Delete per-box extension files (.so) lazily or process exit -* Collect `rb_classext_t` entries for a box when GC collects the box +* Delete per-box extension files (.so) lazily or process exit (on Windows) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box -* Make an internal data container `Ruby::Box::Entry` invisible +* Make an internal data container class `Ruby::Box::Entry` invisible * More test cases about `$LOAD_PATH` and `$LOADED_FEATURES` -* Return classpath and nesting without the namespace prefix in the namespace itself [#21316](https://bugs.ruby-lang.org/issues/21316), [#21318](https://bugs.ruby-lang.org/issues/21318) ## How to use From edca81a1bb72a9dc54a37766d2c80790dec13884 Mon Sep 17 00:00:00 2001 From: Abrar Habib <86024593+dddictionary@users.noreply.github.com> Date: Tue, 9 Dec 2025 07:41:09 -0500 Subject: [PATCH 1713/2435] ZJIT: Add codegen for FixnumDiv (#15452) Fixes https://github.com/Shopify/ruby/issues/902 This pull request adds code generation for dividing fixnums. Testing confirms the normal case, flooring, and side-exiting on division by zero. --- jit.c | 6 ++++++ test/ruby/test_zjit.rb | 31 +++++++++++++++++++++++++++++++ yjit.c | 6 ------ yjit/bindgen/src/main.rs | 2 +- yjit/src/cruby.rs | 2 +- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 12 +++++++++++- zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 1 + zjit/src/stats.rs | 2 ++ 11 files changed, 56 insertions(+), 10 deletions(-) diff --git a/jit.c b/jit.c index ae592b2205c0be..ff44ac5b2e565d 100644 --- a/jit.c +++ b/jit.c @@ -761,6 +761,12 @@ rb_jit_fix_mod_fix(VALUE recv, VALUE obj) return rb_fix_mod_fix(recv, obj); } +VALUE +rb_jit_fix_div_fix(VALUE recv, VALUE obj) +{ + return rb_fix_div_fix(recv, obj); +} + // YJIT/ZJIT need this function to never allocate and never raise VALUE rb_yarv_str_eql_internal(VALUE str1, VALUE str2) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 03d2461fa0a0af..cc5143aee52b0b 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -960,6 +960,37 @@ def test(n) = C * n }, call_threshold: 2, insns: [:opt_mult] end + def test_fixnum_div + assert_compiles '12', %q{ + C = 48 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_floor + assert_compiles '0', %q{ + C = 3 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_div_zero + assert_runs '"divided by 0"', %q{ + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + + test(0) + test(0) + }, call_threshold: 2, insns: [:opt_div] + end + def test_opt_not assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) def test(obj) = !obj diff --git a/yjit.c b/yjit.c index 16debf6eca5555..6d909a0da61ee3 100644 --- a/yjit.c +++ b/yjit.c @@ -292,12 +292,6 @@ rb_yjit_rb_ary_subseq_length(VALUE ary, long beg) return rb_ary_subseq(ary, beg, len); } -VALUE -rb_yjit_fix_div_fix(VALUE recv, VALUE obj) -{ - return rb_fix_div_fix(recv, obj); -} - // Return non-zero when `obj` is an array and its last item is a // `ruby2_keywords` hash. We don't support this kind of splat. size_t diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 8d11b4216ef0b0..06c475f3c8b493 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -368,7 +368,7 @@ fn main() { .allowlist_function("rb_str_neq_internal") .allowlist_function("rb_yarv_ary_entry_internal") .allowlist_function("rb_yjit_ruby2_keywords_splat_p") - .allowlist_function("rb_yjit_fix_div_fix") + .allowlist_function("rb_jit_fix_div_fix") .allowlist_function("rb_jit_fix_mod_fix") .allowlist_function("rb_FL_TEST") .allowlist_function("rb_FL_TEST_RAW") diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 5562f73be26dab..6e6a1810c67dda 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -197,7 +197,7 @@ pub use rb_get_cikw_keywords_idx as get_cikw_keywords_idx; pub use rb_get_call_data_ci as get_call_data_ci; pub use rb_yarv_str_eql_internal as rb_str_eql_internal; pub use rb_yarv_ary_entry_internal as rb_ary_entry_internal; -pub use rb_yjit_fix_div_fix as rb_fix_div_fix; +pub use rb_jit_fix_div_fix as rb_fix_div_fix; pub use rb_jit_fix_mod_fix as rb_fix_mod_fix; pub use rb_FL_TEST as FL_TEST; pub use rb_FL_TEST_RAW as FL_TEST_RAW; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index b9e5fb3ab12843..d2347671bbacb8 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1174,7 +1174,6 @@ extern "C" { pub fn rb_str_neq_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE; pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; - pub fn rb_yjit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yjit_ruby2_keywords_splat_p(obj: VALUE) -> usize; pub fn rb_yjit_splat_varg_checks( sp: *mut VALUE, @@ -1312,6 +1311,7 @@ extern "C" { end: *mut ::std::os::raw::c_void, ); pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; + pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); } diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 30e85149744e22..7b968d14bbb678 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -279,6 +279,7 @@ fn main() { .allowlist_function("rb_jit_mark_unused") .allowlist_function("rb_jit_get_page_size") .allowlist_function("rb_jit_array_len") + .allowlist_function("rb_jit_fix_div_fix") .allowlist_function("rb_jit_iseq_builtin_attrs") .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_function("rb_zjit_iseq_inspect") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 73c092f72091c5..642991a4381265 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -396,6 +396,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumMult { left, right, state } => gen_fixnum_mult(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), + Insn::FixnumDiv { left, right, state } => gen_fixnum_div(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumEq { left, right } => gen_fixnum_eq(asm, opnd!(left), opnd!(right)), Insn::FixnumNeq { left, right } => gen_fixnum_neq(asm, opnd!(left), opnd!(right)), Insn::FixnumLt { left, right } => gen_fixnum_lt(asm, opnd!(left), opnd!(right)), @@ -484,7 +485,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), &Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)), &Insn::ArrayMax { state, .. } - | &Insn::FixnumDiv { state, .. } | &Insn::Throw { state, .. } => return Err(state), }; @@ -1704,6 +1704,16 @@ fn gen_fixnum_mult(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, rig asm.add(out_val, Opnd::UImm(1)) } +/// Compile Fixnum / Fixnum +fn gen_fixnum_div(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd { + gen_prepare_leaf_call_with_gc(asm, state); + + // Side exit if rhs is 0 + asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0))); + asm.je(side_exit(jit, state, FixnumDivByZero)); + asm_ccall!(asm, rb_jit_fix_div_fix, left, right) +} + /// Compile Fixnum == Fixnum fn gen_fixnum_eq(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { asm.cmp(left, right); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index aed35c3c636846..c436e20087ebbe 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2198,6 +2198,7 @@ unsafe extern "C" { start: *mut ::std::os::raw::c_void, end: *mut ::std::os::raw::c_void, ); + pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE); pub fn rb_jit_shape_capacity(shape_id: shape_id_t) -> attr_index_t; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4323b145feb1b6..abf48e04d64be6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -501,6 +501,7 @@ pub enum SideExitReason { BlockParamProxyNotIseqOrIfunc, StackOverflow, FixnumModByZero, + FixnumDivByZero, BoxFixnumOverflow, } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7a076b7cda2550..2272404b690b37 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -183,6 +183,7 @@ make_counters! { exit_fixnum_mult_overflow, exit_fixnum_lshift_overflow, exit_fixnum_mod_by_zero, + exit_fixnum_div_by_zero, exit_box_fixnum_overflow, exit_guard_type_failure, exit_guard_type_not_failure, @@ -492,6 +493,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { FixnumMultOverflow => exit_fixnum_mult_overflow, FixnumLShiftOverflow => exit_fixnum_lshift_overflow, FixnumModByZero => exit_fixnum_mod_by_zero, + FixnumDivByZero => exit_fixnum_div_by_zero, BoxFixnumOverflow => exit_box_fixnum_overflow, GuardType(_) => exit_guard_type_failure, GuardTypeNot(_) => exit_guard_type_not_failure, From c8441e8db52fe260545b83ffbe5278aff242bd14 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 20:53:50 -0500 Subject: [PATCH 1714/2435] ZJIT: Clean up opt_newarray_send --- zjit/src/hir.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index abf48e04d64be6..7b1374f9f467ff 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5252,20 +5252,25 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_opt_newarray_send => { let count = get_arg(pc, 0).as_usize(); let method = get_arg(pc, 1).as_u32(); - let elements = state.stack_pop_n(count)?; let (bop, insn) = match method { - VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), - VM_OPT_NEWARRAY_SEND_HASH => (BOP_HASH, Insn::ArrayHash { elements, state: exit_id }), + VM_OPT_NEWARRAY_SEND_MAX => { + let elements = state.stack_pop_n(count)?; + (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }) + } + VM_OPT_NEWARRAY_SEND_HASH => { + let elements = state.stack_pop_n(count)?; + (BOP_HASH, Insn::ArrayHash { elements, state: exit_id }) + } VM_OPT_NEWARRAY_SEND_INCLUDE_P => { - let target = elements[elements.len() - 1]; - let array_elements = elements[..elements.len() - 1].to_vec(); - (BOP_INCLUDE_P, Insn::ArrayInclude { elements: array_elements, target, state: exit_id }) - }, + let target = state.stack_pop()?; + let elements = state.stack_pop_n(count - 1)?; + (BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id }) + } _ => { // Unknown opcode; side-exit into the interpreter fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method) }); break; // End the block - }, + } }; if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, ARRAY_REDEFINED_OP_FLAG) } { // If the basic operation is already redefined, we cannot optimize it. From cb9510f539e12f7c58bf2e74186c6a48b62fec3c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 3 Dec 2025 21:16:27 -0500 Subject: [PATCH 1715/2435] ZJIT: Support opt_newarray_send with PACK_BUFFER --- test/ruby/test_zjit.rb | 28 +++++++++++++++++++++++++ zjit/src/codegen.rs | 29 ++++++++++++++++++++++++++ zjit/src/hir.rs | 30 +++++++++++++++++++++++++++ zjit/src/hir/tests.rs | 46 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index cc5143aee52b0b..ff9d45a0832835 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1129,6 +1129,34 @@ def test(x) }, insns: [:opt_duparray_send], call_threshold: 1 end + def test_opt_newarray_send_pack_buffer + assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{ + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), test(66, buf), test(67, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_pack_buffer_redefined + assert_compiles '["b", "A"]', %q{ + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + old_pack(fmt, buffer: buffer) + "b" + end + end + + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + def test_opt_newarray_send_hash assert_compiles 'Integer', %q{ def test(x) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 642991a4381265..43fdc2f06e0a5e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -481,6 +481,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), + Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), &Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)), @@ -1548,6 +1549,34 @@ fn gen_array_include( ) } +fn gen_array_pack_buffer( + jit: &JITState, + asm: &mut Assembler, + elements: Vec, + fmt: Opnd, + buffer: Opnd, + state: &FrameState, +) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); + + // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack. + // The elements are at the bottom of the virtual stack, followed by the fmt, followed by the buffer. + // Get a pointer to the first element on the Ruby stack. + let stack_bottom = state.stack().len() - elements.len() - 2; + let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32)); + + unsafe extern "C" { + fn rb_vm_opt_newarray_pack_buffer(ec: EcPtr, num: c_long, elts: *const VALUE, fmt: VALUE, buffer: VALUE) -> VALUE; + } + asm_ccall!( + asm, + rb_vm_opt_newarray_pack_buffer, + EC, array_len.into(), elements_ptr, fmt, buffer + ) +} + fn gen_dup_array_include( jit: &JITState, asm: &mut Assembler, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7b1374f9f467ff..523a757b30782e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -675,6 +675,7 @@ pub enum Insn { ArrayHash { elements: Vec, state: InsnId }, ArrayMax { elements: Vec, state: InsnId }, ArrayInclude { elements: Vec, target: InsnId, state: InsnId }, + ArrayPackBuffer { elements: Vec, fmt: InsnId, buffer: InsnId, state: InsnId }, DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId }, /// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`. ArrayExtend { left: InsnId, right: InsnId, state: InsnId }, @@ -1122,6 +1123,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } write!(f, " | {target}") } + Insn::ArrayPackBuffer { elements, fmt, buffer, .. } => { + write!(f, "ArrayPackBuffer ")?; + for element in elements { + write!(f, "{element}, ")?; + } + write!(f, "fmt: {fmt}, buf: {buffer}") + } Insn::DupArrayInclude { ary, target, .. } => { write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target) } @@ -1990,6 +1998,7 @@ impl Function { &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) }, + &ArrayPackBuffer { ref elements, fmt, buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: find!(buffer), state: find!(state) }, &DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) }, &ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, @@ -2144,6 +2153,7 @@ impl Function { Insn::FixnumBitCheck { .. } => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::ArrayInclude { .. } => types::BoolExact, + Insn::ArrayPackBuffer { .. } => types::String, Insn::DupArrayInclude { .. } => types::BoolExact, Insn::ArrayHash { .. } => types::Fixnum, Insn::GetGlobal { .. } => types::BasicObject, @@ -3658,6 +3668,12 @@ impl Function { worklist.push_back(target); worklist.push_back(state); } + &Insn::ArrayPackBuffer { ref elements, fmt, buffer, state } => { + worklist.extend(elements); + worklist.push_back(fmt); + worklist.push_back(buffer); + worklist.push_back(state); + } &Insn::DupArrayInclude { target, state, .. } => { worklist.push_back(target); worklist.push_back(state); @@ -4417,6 +4433,14 @@ impl Function { } Ok(()) } + Insn::ArrayPackBuffer { ref elements, fmt, buffer, .. } => { + self.assert_subtype(insn_id, fmt, types::BasicObject)?; + self.assert_subtype(insn_id, buffer, types::BasicObject)?; + for &element in elements { + self.assert_subtype(insn_id, element, types::BasicObject)?; + } + Ok(()) + } // Instructions with a Vec of Ruby objects Insn::InvokeBlock { ref args, .. } | Insn::NewArray { elements: ref args, .. } @@ -5266,6 +5290,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let elements = state.stack_pop_n(count - 1)?; (BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id }) } + VM_OPT_NEWARRAY_SEND_PACK_BUFFER => { + let buffer = state.stack_pop()?; + let fmt = state.stack_pop()?; + let elements = state.stack_pop_n(count - 2)?; + (BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer, state: exit_id }) + } _ => { // Unknown opcode; side-exit into the interpreter fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method) }); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 76be941fe529f9..84e48ed843fa2b 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2154,7 +2154,51 @@ pub mod hir_build_tests { v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v37:StringExact = StringCopy v36 v39:BasicObject = GetLocal :buf, l0, EP@3 - SideExit UnhandledNewarraySend(PACK_BUFFER) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK) + v42:String = ArrayPackBuffer v15, v16, fmt: v37, buf: v39 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_opt_newarray_send_pack_buffer_redefined() { + eval(r#" + class Array + def pack(fmt, buffer: nil) = 5 + end + def test(a,b) + sum = a+b + buf = "" + [a,b].pack 'C', buffer: buf + buf + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :a, l0, SP@7 + v3:BasicObject = GetLocal :b, l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v30:StringExact = StringCopy v29 + v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v37:StringExact = StringCopy v36 + v39:BasicObject = GetLocal :buf, l0, EP@3 + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK)) "); } From e8568bbcf20fafcb82d8b537b99762528dfbdc3e Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 9 Dec 2025 23:03:41 +0900 Subject: [PATCH 1716/2435] [DOC] Update Ruby Box documents (known issues) --- doc/language/box.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/language/box.md b/doc/language/box.md index 928c98eb3c32bb..dc4b3201b4eea4 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -5,14 +5,14 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## Known issues * Experimental warning is shown when ruby starts with `RUBY_BOX=1` (specify `-W:no-experimental` option to hide it) -* `bundle install` may fail -* `require 'active_support'` may fail -* A wrong current namespace detection happens sometimes in the root namespace +* Installing native extensions may fail under `RUBY_BOX=1` because of stack level too deep in extconf.rb +* `require 'active_support/core_ext'` may fail under `RUBY_BOX=1` +* Defined methods in a box may not be referred by built-in methods written in Ruby ## TODOs * Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) -* Delete per-box extension files (.so) lazily or process exit (on Windows) +* Delete per-box extension files (.dll) lazily or process exit (on Windows) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box * Make an internal data container class `Ruby::Box::Entry` invisible @@ -258,6 +258,16 @@ Ruby Box works in file scope. One `.rb` file runs in a single box. Once a file is loaded in a box `box`, all methods/procs defined/created in the file run in `box`. +### Utility methods + +Several methods are available for trying/testing Ruby Box. + +* `Ruby::Box.current` returns the current box +* `Ruby::Box.enabled?` returns true/false to represent `RUBY_BOX=1` is specified or not +* `Ruby::Box.root` returns the root box +* `Ruby::Box.main` returns the main box +* `Ruby::Box#eval` evaluates a Ruby code (String) in the receiver box, just like calling `#load` with a file + ## Implementation details #### ISeq inline method/constant cache @@ -294,6 +304,10 @@ It is a breaking change. Users can define methods using `Ruby::Box.root.eval(...)`, but it's clearly not ideal API. +#### Assigning values to global variables used by builtin methods + +Similar to monkey patching methods, global variables assigned in a box is separated from the root box. Methods defined in the root box referring a global variable can't find the re-assigned one. + #### Context of `$LOAD_PATH` and `$LOADED_FEATURES` Global variables `$LOAD_PATH` and `$LOADED_FEATURES` control `require` method behaviors. So those variables are determined by the loading box instead of the current box. From 573896a40ac25bd9febb2bbc0502b43ef36f9b9b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Nov 2025 11:05:17 +0900 Subject: [PATCH 1717/2435] Box: remove copied extension files --- box.c | 60 ++++++++++++++++++++++++++++++++++++++++--- internal/box.h | 13 +++++++++- load.c | 16 ++++-------- test/ruby/test_box.rb | 4 --- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/box.c b/box.c index e7065c1c298815..0ce8d0aee02f0f 100644 --- a/box.c +++ b/box.c @@ -62,6 +62,7 @@ bool ruby_box_crashed = false; // extern, changed only in vm.c VALUE rb_resolve_feature_path(VALUE klass, VALUE fname); static VALUE rb_box_inspect(VALUE obj); +static void cleanup_all_local_extensions(VALUE libmap); void rb_box_init_done(void) @@ -274,6 +275,8 @@ box_entry_free(void *ptr) st_foreach(box->classext_cow_classes, free_classext_for_box, (st_data_t)box); } + cleanup_all_local_extensions(box->ruby_dln_libmap); + box_root_free(ptr); xfree(ptr); } @@ -724,8 +727,57 @@ escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize } } +static void +box_ext_cleanup_mark(void *p) +{ + rb_gc_mark((VALUE)p); +} + +static void +box_ext_cleanup_free(void *p) +{ + VALUE path = (VALUE)p; + unlink(RSTRING_PTR(path)); +} + +static const rb_data_type_t box_ext_cleanup_type = { + "box_ext_cleanup", + {box_ext_cleanup_mark, box_ext_cleanup_free}, + .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +void +rb_box_cleanup_local_extension(VALUE cleanup) +{ + void *p = DATA_PTR(cleanup); + DATA_PTR(cleanup) = NULL; +#ifndef _WIN32 + if (p) box_ext_cleanup_free(p); +#endif +} + +static int +cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg) +{ +#if defined(_WIN32) + HMODULE h = (HMODULE)NUM2SVALUE(value); + WCHAR module_path[MAXPATHLEN]; + DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path)); + + FreeLibrary(h); + if (len > 0 && len < numberof(module_path)) DeleteFileW(module_path); +#endif + return ST_DELETE; +} + +static void +cleanup_all_local_extensions(VALUE libmap) +{ + rb_hash_foreach(libmap, cleanup_local_extension_i, 0); +} + VALUE -rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) +rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path, VALUE *cleanup) { char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN]; int wrote; @@ -739,14 +791,16 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path) if (wrote >= (int)sizeof(ext_path)) { rb_bug("Extension file path in the box was too long"); } + VALUE new_path = rb_str_new_cstr(ext_path); + *cleanup = TypedData_Wrap_Struct(0, &box_ext_cleanup_type, NULL); enum copy_error_type copy_error = copy_ext_file(src_path, ext_path); if (copy_error) { char message[1024]; copy_ext_file_error(message, sizeof(message), copy_error); rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %"PRIsVALUE"): %s", ext_path, path, message); } - // TODO: register the path to be clean-uped - return rb_str_new_cstr(ext_path); + DATA_PTR(*cleanup) = (void *)new_path; + return new_path; } // TODO: delete it just after dln_load? or delay it? diff --git a/internal/box.h b/internal/box.h index c341f046d3e147..72263cc9dc5e5d 100644 --- a/internal/box.h +++ b/internal/box.h @@ -3,6 +3,16 @@ #include "ruby/ruby.h" /* for VALUE */ +#if SIZEOF_VALUE <= SIZEOF_LONG +# define SVALUE2NUM(x) LONG2NUM((long)(x)) +# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x) +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG +# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x)) +# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x) +#else +# error Need integer for VALUE +#endif + /** * @author Ruby developers * @copyright This file is a part of the programming language Ruby. @@ -75,7 +85,8 @@ void rb_box_gc_update_references(void *ptr); rb_box_t * rb_get_box_t(VALUE ns); VALUE rb_get_box_object(rb_box_t *ns); -VALUE rb_box_local_extension(VALUE box, VALUE fname, VALUE path); +VALUE rb_box_local_extension(VALUE box, VALUE fname, VALUE path, VALUE *cleanup); +void rb_box_cleanup_local_extension(VALUE cleanup); void rb_initialize_main_box(void); void rb_box_init_done(void); diff --git a/load.c b/load.c index 5a0697a2626707..466517f465b098 100644 --- a/load.c +++ b/load.c @@ -27,16 +27,6 @@ #define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0) #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) -#if SIZEOF_VALUE <= SIZEOF_LONG -# define SVALUE2NUM(x) LONG2NUM((long)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x) -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x) -#else -# error Need integer for VALUE -#endif - enum { loadable_ext_rb = (0+ /* .rb extension is the first in both tables */ 1) /* offset by rb_find_file_ext() */ @@ -1203,11 +1193,15 @@ load_ext(VALUE path, VALUE fname) { VALUE loaded = path; const rb_box_t *box = rb_loading_box(); + VALUE cleanup = 0; if (BOX_USER_P(box)) { - loaded = rb_box_local_extension(box->box_object, fname, path); + loaded = rb_box_local_extension(box->box_object, fname, path, &cleanup); } rb_scope_visibility_set(METHOD_VISI_PUBLIC); void *handle = dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname)); + if (cleanup) { + rb_box_cleanup_local_extension(cleanup); + } RB_GC_GUARD(loaded); RB_GC_GUARD(fname); return (VALUE)handle; diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index c52c7564ae682e..2e4e07a86ad7e3 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -697,10 +697,6 @@ def test_root_and_main_methods assert !$LOADED_FEATURES.include?("/tmp/barbaz") assert !Object.const_defined?(:FooClass) end; - ensure - tmp = ENV["TMPDIR"] || ENV["TMP"] || Etc.systmpdir || "/tmp" - pat = "_ruby_ns_*."+RbConfig::CONFIG["DLEXT"] - File.unlink(*Dir.glob(pat, base: tmp).map {|so| "#{tmp}/#{so}"}) end def test_basic_box_detections From 07e85e1dba3e15c714044660f731a90ce86c8c24 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 2 Dec 2025 22:22:01 +0900 Subject: [PATCH 1718/2435] Box: add a test case about deleting .so/.dll files --- test/ruby/test_box.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 2e4e07a86ad7e3..1daf5d57d5a9e3 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -823,4 +823,17 @@ def test_mark_box_object_referred_only_from_binding assert_equal 42, b.eval('1+2') end; end + + def test_loaded_extension_deleted_in_user_box + require 'tmpdir' + Dir.mktmpdir do |tmpdir| + env = ENV_ENABLE_BOX.merge({'TMPDIR'=>tmpdir}) + assert_separately([env], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require "json" + tmpdirname = ENV['TMPDIR'] + assert_empty(Dir.children(tmpdirname)) + end; + end + end end From 1e6a479520cb13f927399c97d363558b3beeaea7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 3 Dec 2025 10:41:39 +0900 Subject: [PATCH 1719/2435] Box: relax the condition of clean up It is impossible to delete DLLs being loaded on Windows. I guess that unnamed (no accessible path on the filesystem) files are not allowed essentially. The only way is to relax the condition, such as no files are left after the process terminated, probably. --- test/ruby/test_box.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 1daf5d57d5a9e3..5d06b60cd77005 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -828,12 +828,11 @@ def test_loaded_extension_deleted_in_user_box require 'tmpdir' Dir.mktmpdir do |tmpdir| env = ENV_ENABLE_BOX.merge({'TMPDIR'=>tmpdir}) - assert_separately([env], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_ruby_status([env], "#{<<~"begin;"}\n#{<<~'end;'}") begin; require "json" - tmpdirname = ENV['TMPDIR'] - assert_empty(Dir.children(tmpdirname)) end; + assert_empty(Dir.children(tmpdir)) end end end From 1933f1291a788369b252be80b0d452716d35831a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 9 Dec 2025 23:44:06 +0900 Subject: [PATCH 1720/2435] [DOC] Clear one of known issues of Ruby Box --- doc/language/box.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/language/box.md b/doc/language/box.md index dc4b3201b4eea4..aebce7188b3179 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -12,7 +12,6 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## TODOs * Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) -* Delete per-box extension files (.dll) lazily or process exit (on Windows) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box * Make an internal data container class `Ruby::Box::Entry` invisible From 7ecfb1b2324d9488d4bd94801ed8a694d3c1ee0b Mon Sep 17 00:00:00 2001 From: YO4 Date: Mon, 8 Dec 2025 21:41:02 +0900 Subject: [PATCH 1721/2435] [ruby/resolv] use domain suffix from 'Domain' instead of 'NV Domain' 'NV Domain' does not change results of `powershell -command Get-DnsClientGlobalSetting`. 'Domain' do this. https://github.com/ruby/resolv/commit/d49e3d5b84 --- ext/win32/lib/win32/resolv.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/win32/lib/win32/resolv.rb b/ext/win32/lib/win32/resolv.rb index 9a74a753517ab3..8d631a21403f4c 100644 --- a/ext/win32/lib/win32/resolv.rb +++ b/ext/win32/lib/win32/resolv.rb @@ -63,13 +63,13 @@ def get_info if add_search = search.nil? search = [] - nvdom = params.value('NV Domain') + domain = params.value('Domain') - if nvdom and !nvdom.empty? - search = [ nvdom ] + if domain and !domain.empty? + search = [ domain ] udmnd = params.value('UseDomainNameDevolution') if udmnd&.nonzero? - if /^\w+\./ =~ nvdom + if /^\w+\./ =~ domain devo = $' end end From 76d845aa7a0fa07aa3e733528cbd7e48feccfd59 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 18 Nov 2025 16:19:49 -0700 Subject: [PATCH 1722/2435] ZJIT: Test additional arg passing scenarios --- test/ruby/test_zjit.rb | 75 ++++++++++++++++++++++++++++++++++++++- zjit/src/hir/opt_tests.rs | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index ff9d45a0832835..18e10f52068362 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -555,7 +555,8 @@ def test_send_kwarg def test(a:, b:) = [a, b] def entry = test(b: 2, a: 1) # change order entry - } + entry + }, call_threshold: 2 end def test_send_kwarg_optional @@ -567,6 +568,15 @@ def entry = test }, call_threshold: 2 end + def test_send_kwarg_optional_too_many + assert_compiles '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', %q{ + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] + def entry = test + entry + entry + }, call_threshold: 2 + end + def test_send_kwarg_required_and_optional assert_compiles '[3, 2]', %q{ def test(a:, b: 2) = [a, b] @@ -585,6 +595,28 @@ def entry = test(a: 3) }, call_threshold: 2 end + def test_send_kwarg_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) = s.each_line(chomp: true).to_a + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_and_block_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + def test_send_kwrest assert_compiles '{a: 3}', %q{ def test(**kwargs) = kwargs @@ -594,6 +626,47 @@ def entry = test(a: 3) }, call_threshold: 2 end + def test_send_req_kwreq + assert_compiles '[1, 3]', %q{ + def test(a, c:) = [a, c] + def entry = test(1, c: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_req_opt_kwreq_kwopt + assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{ + def test(a, b = 2, c:, d: 4) = [a, b, c, d] + def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + + def test_send_unexpected_keyword + assert_compiles ':error', %q{ + def test(a: 1) = a*2 + def entry + test(z: 2) + rescue ArgumentError + :error + end + + entry + entry + }, call_threshold: 2 + end + + def test_send_all_arg_types + assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{ + def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] + def entry = test(:req, :post, d: :kwr) {} + entry + entry + }, call_threshold: 2 + end + def test_send_ccall_variadic_with_different_receiver_classes assert_compiles '[true, true]', %q{ def test(obj) = obj.start_with?("a") diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 5646af804a2d0f..81a2cea80608de 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2862,6 +2862,70 @@ mod hir_opt_tests { "); } + #[test] + fn dont_optimize_ccall_with_kwarg() { + eval(" + def test = sprintf('%s', a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v11 + v14:Fixnum[1] = Const Value(1) + IncrCounter complex_arg_pass_caller_kwarg + v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14 + CheckInterrupts + Return v16 + "); + } + + #[test] + fn dont_optimize_ccall_with_block_and_kwarg() { + eval(" + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + test %(a\nb\nc) + test %() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :s, l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + v16:ArrayExact = NewArray + SetLocal :a, l0, EP@3, v16 + v22:TrueClass = Const Value(true) + IncrCounter complex_arg_pass_caller_kwarg + v24:BasicObject = Send v11, 0x1000, :each_line, v22 + v25:BasicObject = GetLocal :s, l0, EP@4 + v26:BasicObject = GetLocal :a, l0, EP@3 + v30:BasicObject = GetLocal :a, l0, EP@3 + CheckInterrupts + Return v30 + "); + } + #[test] fn dont_replace_get_constant_path_with_empty_ic() { eval(" From c42f4d80eabc6b9b4117253a2ba2ee0b9526f253 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Fri, 14 Nov 2025 14:50:39 -0700 Subject: [PATCH 1723/2435] ZJIT: Handle caller_kwarg in direct send when all keyword params are required --- test/ruby/test_zjit.rb | 18 ++++ zjit/src/codegen.rs | 31 ++++++- zjit/src/hir.rs | 134 +++++++++++++++++++++++++++-- zjit/src/hir/opt_tests.rs | 176 +++++++++++++++++++++++++++++++++++--- zjit/src/stats.rs | 13 ++- 5 files changed, 351 insertions(+), 21 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 18e10f52068362..81d940407063f1 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -617,6 +617,15 @@ def entry = test(%(a\nb\nc)) }, call_threshold: 2 end + def test_send_kwarg_with_too_many_args_to_c_call + assert_compiles '"a b c d {kwargs: :e}"', %q{ + def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) + def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) + entry + entry + }, call_threshold: 2 + end + def test_send_kwrest assert_compiles '{a: 3}', %q{ def test(**kwargs) = kwargs @@ -635,6 +644,15 @@ def entry = test(1, c: 3) }, call_threshold: 2 end + def test_send_req_opt_kwreq + assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{ + def test(a, b = 2, c:) = [a, b, c] + def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + def test_send_req_opt_kwreq_kwopt assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{ def test(a, b = 2, c:, d: 4) = [a, b, c, d] diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 43fdc2f06e0a5e..8c5946e6a6329b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -384,6 +384,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it. Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), + // Give up SendWithoutBlockDirect for 5+ args (plus 1 arg for keyword bits) since asm.ccall() doesn't support it. + Insn::SendWithoutBlockDirect { cd, state, args, iseq, .. } if args.len() + 2 > C_ARG_OPNDS.len() && unsafe { rb_get_iseq_flags_has_kw(*iseq) } => // +1 for self +1 for keyword bits + gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), @@ -1337,6 +1340,20 @@ fn gen_send_without_block_direct( specval, }); + // Write "keyword_bits" to the callee's frame if the callee accepts keywords. + // This is a synthetic local/parameter that the callee reads via checkkeyword to determine + // which optional keyword arguments need their defaults evaluated. + if unsafe { rb_get_iseq_flags_has_kw(iseq) } { + let keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) }; + let bits_start = unsafe { (*keyword).bits_start } as usize; + // Currently we only support required keywords, so all bits are 0 (all keywords specified). + // TODO: When supporting optional keywords, calculate actual unspecified_bits here. + let unspecified_bits = VALUE::fixnum_from_usize(0); + let bits_offset = (state.stack().len() - args.len() + bits_start) * SIZEOF_VALUE; + asm_comment!(asm, "write keyword bits to callee frame"); + asm.store(Opnd::mem(64, SP, bits_offset as i32), unspecified_bits.into()); + } + asm_comment!(asm, "switch to new SP register"); let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); @@ -1351,13 +1368,23 @@ fn gen_send_without_block_direct( let mut c_args = vec![recv]; c_args.extend(&args); + if unsafe { rb_get_iseq_flags_has_kw(iseq) } { + // Currently we only get to this point if all the accepted keyword args are required. + let unspecified_bits = 0; + // For each optional keyword that isn't passed we would `unspecified_bits |= (0x01 << idx)`. + c_args.push(VALUE::fixnum_from_usize(unspecified_bits).into()); + } + let params = unsafe { iseq.params() }; let num_optionals_passed = if params.flags.has_opt() != 0 { // See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c let lead_num = params.lead_num as u32; let opt_num = params.opt_num as u32; - assert!(args.len() as u32 <= lead_num + opt_num); - let num_optionals_passed = args.len() as u32 - lead_num; + let keyword = params.keyword; + let kw_req_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).required_num } } as u32; + let req_num = lead_num + kw_req_num; + assert!(args.len() as u32 <= req_num + opt_num); + let num_optionals_passed = args.len() as u32 - req_num; num_optionals_passed } else { 0 diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 523a757b30782e..442f0500c4650f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -616,6 +616,10 @@ pub enum SendFallbackReason { SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType), SendWithoutBlockBopRedefined, SendWithoutBlockOperandsNotFixnum, + SendWithoutBlockDirectKeywordMismatch, + SendWithoutBlockDirectOptionalKeywords, + SendWithoutBlockDirectKeywordCountMismatch, + SendWithoutBlockDirectMissingKeyword, SendPolymorphic, SendMegamorphic, SendNoProfiles, @@ -632,6 +636,8 @@ pub enum SendFallbackReason { /// The call has at least one feature on the caller or callee side that the optimizer does not /// support. ComplexArgPass, + /// Caller has keyword arguments but callee doesn't expect them; need to convert to hash. + UnexpectedKeywordArgs, /// Initial fallback reason for every instruction, which should be mutated to /// a more actionable reason when an attempt to specialize the instruction fails. Uncategorized(ruby_vminsn_type), @@ -1570,11 +1576,22 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq use Counter::*; if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) } if 0 != params.flags.has_post() { count_failure(complex_arg_pass_param_post) } - if 0 != params.flags.has_kw() { count_failure(complex_arg_pass_param_kw) } - if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } if 0 != params.flags.has_block() { count_failure(complex_arg_pass_param_block) } if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) } + if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } + if 0 != params.flags.has_kw() { + let keyword = params.keyword; + if !keyword.is_null() { + let num = unsafe { (*keyword).num }; + let required_num = unsafe { (*keyword).required_num }; + // Only support required keywords for now (no optional keywords) + if num != required_num { + count_failure(complex_arg_pass_param_kw_opt) + } + } + } + if !can_send { function.set_dynamic_send_reason(send_insn, ComplexArgPass); return false; @@ -1583,9 +1600,12 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq // Because we exclude e.g. post parameters above, they are also excluded from the sum below. let lead_num = params.lead_num; let opt_num = params.opt_num; + let keyword = params.keyword; + let kw_req_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).required_num } }; + let req_num = lead_num + kw_req_num; can_send = c_int::try_from(args.len()) .as_ref() - .map(|argc| (lead_num..=lead_num + opt_num).contains(argc)) + .map(|argc| (req_num..=req_num + opt_num).contains(argc)) .unwrap_or(false); if !can_send { function.set_dynamic_send_reason(send_insn, ArgcParamMismatch); @@ -2299,6 +2319,72 @@ impl Function { } } + /// Reorder keyword arguments to match the callee's expectation. + /// + /// Returns Ok with reordered arguments if successful, or Err with the fallback reason if not. + fn reorder_keyword_arguments( + &self, + args: &[InsnId], + kwarg: *const rb_callinfo_kwarg, + iseq: IseqPtr, + ) -> Result, SendFallbackReason> { + let callee_keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) }; + if callee_keyword.is_null() { + // Caller is passing kwargs but callee doesn't expect them. + return Err(SendWithoutBlockDirectKeywordMismatch); + } + + let caller_kw_count = unsafe { get_cikw_keyword_len(kwarg) } as usize; + let callee_kw_count = unsafe { (*callee_keyword).num } as usize; + let callee_kw_required = unsafe { (*callee_keyword).required_num } as usize; + let callee_kw_table = unsafe { (*callee_keyword).table }; + + // For now, only handle the case where all keywords are required. + if callee_kw_count != callee_kw_required { + return Err(SendWithoutBlockDirectOptionalKeywords); + } + if caller_kw_count != callee_kw_count { + return Err(SendWithoutBlockDirectKeywordCountMismatch); + } + + // The keyword arguments are the last arguments in the args vector. + let kw_args_start = args.len() - caller_kw_count; + + // Build a mapping from caller keywords to their positions. + let mut caller_kw_order: Vec = Vec::with_capacity(caller_kw_count); + for i in 0..caller_kw_count { + let sym = unsafe { get_cikw_keywords_idx(kwarg, i as i32) }; + let id = unsafe { rb_sym2id(sym) }; + caller_kw_order.push(id); + } + + // Reorder keyword arguments to match callee expectation. + let mut reordered_kw_args: Vec = Vec::with_capacity(callee_kw_count); + for i in 0..callee_kw_count { + let expected_id = unsafe { *callee_kw_table.add(i) }; + + // Find where this keyword is in the caller's order + let mut found = false; + for (j, &caller_id) in caller_kw_order.iter().enumerate() { + if caller_id == expected_id { + reordered_kw_args.push(args[kw_args_start + j]); + found = true; + break; + } + } + + if !found { + // Required keyword not provided by caller which will raise an ArgumentError. + return Err(SendWithoutBlockDirectMissingKeyword); + } + } + + // Replace the keyword arguments with the reordered ones. + let mut processed_args = args[..kw_args_start].to_vec(); + processed_args.extend(reordered_kw_args); + Ok(processed_args) + } + /// Resolve the receiver type for method dispatch optimization. /// /// Takes the receiver's Type, receiver HIR instruction, and ISEQ instruction index. @@ -2514,6 +2600,7 @@ impl Function { cme = unsafe { rb_aliased_callable_method_entry(cme) }; def_type = unsafe { get_cme_def_type(cme) }; } + if def_type == VM_METHOD_TYPE_ISEQ { // TODO(max): Allow non-iseq; cache cme // Only specialize positional-positional calls @@ -2529,7 +2616,21 @@ impl Function { if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state }); + + let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; + let processed_args = if !kwarg.is_null() { + match self.reorder_keyword_arguments(&args, kwarg, iseq) { + Ok(reordered) => reordered, + Err(reason) => { + self.set_dynamic_send_reason(insn_id, reason); + self.push_insn_id(block, insn_id); continue; + } + } + } else { + args.clone() + }; + + let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, state }); self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_BMETHOD { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; @@ -2564,7 +2665,21 @@ impl Function { if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state }); + + let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; + let processed_args = if !kwarg.is_null() { + match self.reorder_keyword_arguments(&args, kwarg, iseq) { + Ok(reordered) => reordered, + Err(reason) => { + self.set_dynamic_send_reason(insn_id, reason); + self.push_insn_id(block, insn_id); continue; + } + } + } else { + args.clone() + }; + + let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, state }); self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. @@ -3106,7 +3221,7 @@ impl Function { // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding - if unspecializable_call_type(ci_flags) { + if unspecializable_c_call_type(ci_flags) { fun.count_complex_call_features(block, ci_flags); fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); @@ -5018,9 +5133,14 @@ fn unhandled_call_type(flags: u32) -> Result<(), CallType> { Ok(()) } +/// If a given call to a c func uses overly complex arguments, then we won't specialize. +fn unspecializable_c_call_type(flags: u32) -> bool { + ((flags & VM_CALL_KWARG) != 0) || + unspecializable_call_type(flags) +} + /// If a given call uses overly complex arguments, then we won't specialize. fn unspecializable_call_type(flags: u32) -> bool { - ((flags & VM_CALL_KWARG) != 0) || ((flags & VM_CALL_ARGS_SPLAT) != 0) || ((flags & VM_CALL_ARGS_BLOCKARG) != 0) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 81a2cea80608de..a6099f47bedcc9 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2761,10 +2761,10 @@ mod hir_opt_tests { } #[test] - fn dont_specialize_call_to_iseq_with_kw() { + fn specialize_call_to_iseq_with_multiple_required_kw() { eval(" - def foo(a:) = 1 - def test = foo(a: 1) + def foo(a:, b:) = [a, b] + def test = foo(a: 1, b: 2) test test "); @@ -2779,7 +2779,162 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:Fixnum[1] = Const Value(1) - IncrCounter complex_arg_pass_caller_kwarg + v13:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn specialize_call_to_iseq_with_required_kw_reorder() { + eval(" + def foo(a:, b:, c:) = [a, b, c] + def test = foo(c: 3, a: 1, b: 2) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[3] = Const Value(3) + v13:Fixnum[1] = Const Value(1) + v15:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v25:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v13, v15, v11 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn specialize_call_to_iseq_with_positional_and_required_kw_reorder() { + eval(" + def foo(x, a:, b:) = [x, a, b] + def test = foo(0, b: 2, a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[0] = Const Value(0) + v13:Fixnum[2] = Const Value(2) + v15:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v25:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v11, v15, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn dont_specialize_call_with_positional_and_optional_kw() { + eval(" + def foo(x, a: 1) = [x, a] + def test = foo(0, a: 2) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[0] = Const Value(0) + v13:Fixnum[2] = Const Value(2) + IncrCounter complex_arg_pass_param_kw_opt + v15:BasicObject = SendWithoutBlock v6, :foo, v11, v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn specialize_call_with_pos_optional_and_req_kw() { + eval(" + def foo(r, x = 2, a:, b:) = [x, a] + def test = [foo(1, a: 3, b: 4), foo(1, 2, b: 4, a: 3)] # with and without the optional, change kw order + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[1] = Const Value(1) + v13:Fixnum[3] = Const Value(3) + v15:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v37:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v38:BasicObject = SendWithoutBlockDirect v37, :foo (0x1038), v11, v13, v15 + v20:Fixnum[1] = Const Value(1) + v22:Fixnum[2] = Const Value(2) + v24:Fixnum[4] = Const Value(4) + v26:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v41:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v42:BasicObject = SendWithoutBlockDirect v41, :foo (0x1038), v20, v22, v26, v24 + v30:ArrayExact = NewArray v38, v42 + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_send_call_to_iseq_with_optional_kw() { + eval(" + def foo(a: 1) = a + def test = foo(a: 2) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[2] = Const Value(2) + IncrCounter complex_arg_pass_param_kw_opt v13:BasicObject = SendWithoutBlock v6, :foo, v11 CheckInterrupts Return v13 @@ -2805,7 +2960,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:Fixnum[1] = Const Value(1) - IncrCounter complex_arg_pass_caller_kwarg + IncrCounter complex_arg_pass_param_kwrest v13:BasicObject = SendWithoutBlock v6, :foo, v11 CheckInterrupts Return v13 @@ -2813,7 +2968,7 @@ mod hir_opt_tests { } #[test] - fn dont_specialize_call_to_iseq_with_param_kw() { + fn dont_specialize_call_to_iseq_with_optional_param_kw() { eval(" def foo(int: 1) = int + 1 def test = foo @@ -2830,7 +2985,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - IncrCounter complex_arg_pass_param_kw + IncrCounter complex_arg_pass_param_kw_opt v11:BasicObject = SendWithoutBlock v6, :foo CheckInterrupts Return v11 @@ -2882,7 +3037,6 @@ mod hir_opt_tests { v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v11 v14:Fixnum[1] = Const Value(1) - IncrCounter complex_arg_pass_caller_kwarg v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14 CheckInterrupts Return v16 @@ -3180,8 +3334,8 @@ mod hir_opt_tests { v13:NilClass = Const Value(nil) PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010) v46:HashExact = ObjectAllocClass Hash:VALUE(0x1008) - IncrCounter complex_arg_pass_param_kw IncrCounter complex_arg_pass_param_block + IncrCounter complex_arg_pass_param_kw_opt v20:BasicObject = SendWithoutBlock v46, :initialize CheckInterrupts CheckInterrupts @@ -9028,9 +9182,9 @@ mod hir_opt_tests { bb2(v6:BasicObject): v11:Fixnum[1] = Const Value(1) IncrCounter complex_arg_pass_param_rest - IncrCounter complex_arg_pass_param_kw - IncrCounter complex_arg_pass_param_kwrest IncrCounter complex_arg_pass_param_block + IncrCounter complex_arg_pass_param_kwrest + IncrCounter complex_arg_pass_param_kw_opt v13:BasicObject = SendWithoutBlock v6, :fancy, v11 CheckInterrupts Return v13 diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 2272404b690b37..7d54f40c8d5dbd 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -222,6 +222,10 @@ make_counters! { send_fallback_too_many_args_for_lir, send_fallback_send_without_block_bop_redefined, send_fallback_send_without_block_operands_not_fixnum, + send_fallback_send_without_block_direct_keyword_mismatch, + send_fallback_send_without_block_direct_optional_keywords, + send_fallback_send_without_block_direct_keyword_count_mismatch, + send_fallback_send_without_block_direct_missing_keyword, send_fallback_send_polymorphic, send_fallback_send_megamorphic, send_fallback_send_no_profiles, @@ -231,6 +235,8 @@ make_counters! { // The call has at least one feature on the caller or callee side // that the optimizer does not support. send_fallback_one_or_more_complex_arg_pass, + // Caller has keyword arguments but callee doesn't expect them. + send_fallback_unexpected_keyword_args, send_fallback_bmethod_non_iseq_proc, send_fallback_obj_to_string_not_string, send_fallback_send_cfunc_variadic, @@ -347,7 +353,7 @@ make_counters! { // Unsupported parameter features complex_arg_pass_param_rest, complex_arg_pass_param_post, - complex_arg_pass_param_kw, + complex_arg_pass_param_kw_opt, complex_arg_pass_param_kwrest, complex_arg_pass_param_block, complex_arg_pass_param_forwardable, @@ -546,12 +552,17 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter TooManyArgsForLir => send_fallback_too_many_args_for_lir, SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined, SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum, + SendWithoutBlockDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch, + SendWithoutBlockDirectOptionalKeywords => send_fallback_send_without_block_direct_optional_keywords, + SendWithoutBlockDirectKeywordCountMismatch=> send_fallback_send_without_block_direct_keyword_count_mismatch, + SendWithoutBlockDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword, SendPolymorphic => send_fallback_send_polymorphic, SendMegamorphic => send_fallback_send_megamorphic, SendNoProfiles => send_fallback_send_no_profiles, SendCfuncVariadic => send_fallback_send_cfunc_variadic, SendCfuncArrayVariadic => send_fallback_send_cfunc_array_variadic, ComplexArgPass => send_fallback_one_or_more_complex_arg_pass, + UnexpectedKeywordArgs => send_fallback_unexpected_keyword_args, ArgcParamMismatch => send_fallback_argc_param_mismatch, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, From f0b288adf7b3550dc5be6a02316c5822ff47ca82 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Fri, 5 Dec 2025 19:57:08 -0700 Subject: [PATCH 1724/2435] ZJIT: Put keyword bits in callee frame rather than c_args --- zjit/src/codegen.rs | 10 ---------- zjit/src/hir.rs | 17 +++++++++++++++++ zjit/src/hir/opt_tests.rs | 3 ++- zjit/src/hir/tests.rs | 15 ++++++++++----- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8c5946e6a6329b..05b704d66a4bb4 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -384,9 +384,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio // Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it. Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), - // Give up SendWithoutBlockDirect for 5+ args (plus 1 arg for keyword bits) since asm.ccall() doesn't support it. - Insn::SendWithoutBlockDirect { cd, state, args, iseq, .. } if args.len() + 2 > C_ARG_OPNDS.len() && unsafe { rb_get_iseq_flags_has_kw(*iseq) } => // +1 for self +1 for keyword bits - gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), @@ -1368,13 +1365,6 @@ fn gen_send_without_block_direct( let mut c_args = vec![recv]; c_args.extend(&args); - if unsafe { rb_get_iseq_flags_has_kw(iseq) } { - // Currently we only get to this point if all the accepted keyword args are required. - let unspecified_bits = 0; - // For each optional keyword that isn't passed we would `unspecified_bits |= (0x01 << idx)`. - c_args.push(VALUE::fixnum_from_usize(unspecified_bits).into()); - } - let params = unsafe { iseq.params() }; let num_optionals_passed = if params.flags.has_opt() != 0 { // See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 442f0500c4650f..6d1fe70e271869 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6239,12 +6239,29 @@ fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId, jit_ent let lead_num: usize = params.lead_num.try_into().expect("iseq param lead_num >= 0"); let passed_opt_num = jit_entry_idx; + // If the iseq has keyword parameters, the keyword bits local will be appended to the local table. + let kw_bits_idx: Option = if unsafe { rb_get_iseq_flags_has_kw(iseq) } { + let keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) }; + if !keyword.is_null() { + Some(unsafe { (*keyword).bits_start } as usize) + } else { + None + } + } else { + None + }; + let self_param = fun.push_insn(jit_entry_block, Insn::Param); let mut entry_state = FrameState::new(iseq); for local_idx in 0..num_locals(iseq) { if (lead_num + passed_opt_num..lead_num + opt_num).contains(&local_idx) { // Omitted optionals are locals, so they start as nils before their code run entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Const { val: Const::Value(Qnil) })); + } else if Some(local_idx) == kw_bits_idx { + // We currently only support required keywords so the unspecified bits will always be zero. + // TODO: Make this a parameter when we start writing anything other than zero. + let unspecified_bits = VALUE::fixnum_from_usize(0); + entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Const { val: Const::Value(unspecified_bits) })); } else if local_idx < param_size { entry_state.locals.push(fun.push_insn(jit_entry_block, Insn::Param)); } else { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index a6099f47bedcc9..0a0737469371d7 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -626,8 +626,9 @@ mod hir_opt_tests { v2:BasicObject = GetLocal :k, l0, SP@5 v3:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + bb1(v6:BasicObject, v7:BasicObject): EntryPoint JIT(0) + v8:Fixnum[0] = Const Value(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): CheckInterrupts diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 84e48ed843fa2b..7ef7671fa444f6 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3000,8 +3000,9 @@ pub mod hir_build_tests { v3:BasicObject = GetLocal :exception, l0, SP@5 v4:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): EntryPoint JIT(0) + v10:Fixnum[0] = Const Value(0) Jump bb2(v7, v8, v9, v10) bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): v19:Float = InvokeBuiltin rb_f_float, v12, v13, v14 @@ -3050,8 +3051,9 @@ pub mod hir_build_tests { v5:BasicObject = GetLocal :block, l0, SP@5 v6:NilClass = Const Value(nil) Jump bb2(v1, v2, v3, v4, v5, v6) - bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): + bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v13:BasicObject): EntryPoint JIT(0) + v12:Fixnum[0] = Const Value(0) v14:NilClass = Const Value(nil) Jump bb2(v9, v10, v11, v12, v13, v14) bb2(v16:BasicObject, v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:NilClass): @@ -3112,8 +3114,9 @@ pub mod hir_build_tests { v4:BasicObject = GetLocal :immediate_sweep, l0, SP@5 v5:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject): EntryPoint JIT(0) + v12:Fixnum[0] = Const Value(0) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject): v25:FalseClass = Const Value(false) @@ -3532,8 +3535,9 @@ pub mod hir_build_tests { v2:BasicObject = GetLocal :kw, l0, SP@5 v3:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + bb1(v6:BasicObject, v7:BasicObject): EntryPoint JIT(0) + v8:Fixnum[0] = Const Value(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v15:BasicObject = GetLocal , l0, EP@3 @@ -3605,8 +3609,9 @@ pub mod hir_build_tests { v34:BasicObject = GetLocal :k33, l0, SP@5 v35:BasicObject = GetLocal , l0, SP@4 Jump bb2(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) - bb1(v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject, v52:BasicObject, v53:BasicObject, v54:BasicObject, v55:BasicObject, v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject): + bb1(v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject, v52:BasicObject, v53:BasicObject, v54:BasicObject, v55:BasicObject, v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject): EntryPoint JIT(0) + v72:Fixnum[0] = Const Value(0) Jump bb2(v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66, v67, v68, v69, v70, v71, v72) bb2(v74:BasicObject, v75:BasicObject, v76:BasicObject, v77:BasicObject, v78:BasicObject, v79:BasicObject, v80:BasicObject, v81:BasicObject, v82:BasicObject, v83:BasicObject, v84:BasicObject, v85:BasicObject, v86:BasicObject, v87:BasicObject, v88:BasicObject, v89:BasicObject, v90:BasicObject, v91:BasicObject, v92:BasicObject, v93:BasicObject, v94:BasicObject, v95:BasicObject, v96:BasicObject, v97:BasicObject, v98:BasicObject, v99:BasicObject, v100:BasicObject, v101:BasicObject, v102:BasicObject, v103:BasicObject, v104:BasicObject, v105:BasicObject, v106:BasicObject, v107:BasicObject, v108:BasicObject): SideExit TooManyKeywordParameters From 98390d9360b8b8c82f798f51567587882c4e5c00 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 9 Dec 2025 17:39:15 +0100 Subject: [PATCH 1725/2435] Don't declare `rbimpl_check_typeddata` as pure [Bug #21771] It may raise so it's incorrect and can lead to the compiler optimizing the call out. --- include/ruby/internal/core/rtypeddata.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index ebcac2c8469db6..aed4bd89b893f6 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -607,7 +607,6 @@ RTYPEDDATA_TYPE(VALUE obj) return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK); } -RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** * @private From bd0d08b6d20e4145e472578d47164fcce14c0abf Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 9 Dec 2025 13:29:58 -0700 Subject: [PATCH 1726/2435] ZJIT: Show send fallback reason in HIR dump (#15454) This adds comments to the hir dump output like this: v13:BasicObject = SendWithoutBlock v6, :test, v11 # SendFallbackReason: Complex argument passing --- zjit/src/hir.rs | 51 ++++++++++++++-- zjit/src/hir/opt_tests.rs | 74 +++++++++++------------ zjit/src/hir/tests.rs | 124 +++++++++++++++++++------------------- 3 files changed, 144 insertions(+), 105 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6d1fe70e271869..4e597db936c345 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -643,6 +643,40 @@ pub enum SendFallbackReason { Uncategorized(ruby_vminsn_type), } +impl Display for SendFallbackReason { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + SendWithoutBlockPolymorphic => write!(f, "SendWithoutBlock: polymorphic call site"), + SendWithoutBlockMegamorphic => write!(f, "SendWithoutBlock: megamorphic call site"), + SendWithoutBlockNoProfiles => write!(f, "SendWithoutBlock: no profile data available"), + SendWithoutBlockCfuncNotVariadic => write!(f, "SendWithoutBlock: C function is not variadic"), + SendWithoutBlockCfuncArrayVariadic => write!(f, "SendWithoutBlock: C function expects array variadic"), + SendWithoutBlockNotOptimizedMethodType(method_type) => write!(f, "SendWithoutBlock: unsupported method type {:?}", method_type), + SendWithoutBlockNotOptimizedMethodTypeOptimized(opt_type) => write!(f, "SendWithoutBlock: unsupported optimized method type {:?}", opt_type), + SendWithoutBlockBopRedefined => write!(f, "SendWithoutBlock: basic operation was redefined"), + SendWithoutBlockOperandsNotFixnum => write!(f, "SendWithoutBlock: operands are not fixnums"), + SendWithoutBlockDirectKeywordMismatch => write!(f, "SendWithoutBlockDirect: keyword mismatch"), + SendWithoutBlockDirectOptionalKeywords => write!(f, "SendWithoutBlockDirect: optional keywords"), + SendWithoutBlockDirectKeywordCountMismatch => write!(f, "SendWithoutBlockDirect: keyword count mismatch"), + SendWithoutBlockDirectMissingKeyword => write!(f, "SendWithoutBlockDirect: missing keyword"), + SendPolymorphic => write!(f, "Send: polymorphic call site"), + SendMegamorphic => write!(f, "Send: megamorphic call site"), + SendNoProfiles => write!(f, "Send: no profile data available"), + SendCfuncVariadic => write!(f, "Send: C function is variadic"), + SendCfuncArrayVariadic => write!(f, "Send: C function expects array variadic"), + SendNotOptimizedMethodType(method_type) => write!(f, "Send: unsupported method type {:?}", method_type), + CCallWithFrameTooManyArgs => write!(f, "CCallWithFrame: too many arguments"), + ObjToStringNotString => write!(f, "ObjToString: result is not a string"), + TooManyArgsForLir => write!(f, "Too many arguments for LIR"), + BmethodNonIseqProc => write!(f, "Bmethod: Proc object is not defined by an ISEQ"), + ArgcParamMismatch => write!(f, "Argument count does not match parameter count"), + ComplexArgPass => write!(f, "Complex argument passing"), + UnexpectedKeywordArgs => write!(f, "Unexpected Keyword Args"), + Uncategorized(insn) => write!(f, "Uncategorized({})", insn_name(*insn as usize)), + } + } +} + /// An instruction in the SSA IR. The output of an instruction is referred to by the index of /// the instruction ([`InsnId`]). SSA form enables this, and [`UnionFind`] ([`Function::find`]) /// helps with editing. @@ -1203,11 +1237,12 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") } - Insn::SendWithoutBlock { recv, cd, args, .. } => { + Insn::SendWithoutBlock { recv, cd, args, reason, .. } => { write!(f, "SendWithoutBlock {recv}, :{}", ruby_call_method_name(*cd))?; for arg in args { write!(f, ", {arg}")?; } + write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::SendWithoutBlockDirect { recv, cd, iseq, args, .. } => { @@ -1217,7 +1252,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } - Insn::Send { recv, cd, args, blockiseq, .. } => { + Insn::Send { recv, cd, args, blockiseq, reason, .. } => { // For tests, we want to check HIR snippets textually. Addresses change // between runs, making tests fail. Instead, pick an arbitrary hex value to // use as a "pointer" so we can check the rest of the HIR. @@ -1225,27 +1260,31 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { for arg in args { write!(f, ", {arg}")?; } + write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } - Insn::SendForward { recv, cd, args, blockiseq, .. } => { + Insn::SendForward { recv, cd, args, blockiseq, reason, .. } => { write!(f, "SendForward {recv}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?; for arg in args { write!(f, ", {arg}")?; } + write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } - Insn::InvokeSuper { recv, blockiseq, args, .. } => { + Insn::InvokeSuper { recv, blockiseq, args, reason, .. } => { write!(f, "InvokeSuper {recv}, {:p}", self.ptr_map.map_ptr(blockiseq))?; for arg in args { write!(f, ", {arg}")?; } + write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } - Insn::InvokeBlock { args, .. } => { + Insn::InvokeBlock { args, reason, .. } => { write!(f, "InvokeBlock")?; for arg in args { write!(f, ", {arg}")?; } + write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::InvokeBuiltin { bf, args, leaf, .. } => { @@ -2044,7 +2083,7 @@ impl Function { /// Update DynamicSendReason for the instruction at insn_id fn set_dynamic_send_reason(&mut self, insn_id: InsnId, dynamic_send_reason: SendFallbackReason) { use Insn::*; - if get_option!(stats) { + if get_option!(stats) || get_option!(dump_hir_opt).is_some() || cfg!(test) { match self.insns.get_mut(insn_id.0).unwrap() { Send { reason, .. } | SendForward { reason, .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 0a0737469371d7..3e2db528158090 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -812,7 +812,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = SendWithoutBlock v6, :foo + v11:BasicObject = SendWithoutBlock v6, :foo # SendFallbackReason: SendWithoutBlock: unsupported method type Null CheckInterrupts Return v11 "); @@ -2444,7 +2444,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) v12:Fixnum[0] = Const Value(0) - v14:BasicObject = SendWithoutBlock v10, :itself, v12 + v14:BasicObject = SendWithoutBlock v10, :itself, v12 # SendFallbackReason: SendWithoutBlock: unsupported method type Cfunc CheckInterrupts Return v14 "); @@ -2670,7 +2670,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo + v11:BasicObject = Send v6, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq CheckInterrupts Return v11 "); @@ -2702,7 +2702,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) SetLocal :a, l0, EP@3, v13 - v19:BasicObject = Send v8, 0x1000, :foo + v19:BasicObject = Send v8, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq v20:BasicObject = GetLocal :a, l0, EP@3 v24:BasicObject = GetLocal :a, l0, EP@3 CheckInterrupts @@ -2730,7 +2730,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v11:Fixnum[1] = Const Value(1) IncrCounter complex_arg_pass_param_rest - v13:BasicObject = SendWithoutBlock v6, :foo, v11 + v13:BasicObject = SendWithoutBlock v6, :foo, v11 # SendFallbackReason: Complex argument passing CheckInterrupts Return v13 "); @@ -2755,7 +2755,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v11:Fixnum[10] = Const Value(10) IncrCounter complex_arg_pass_param_post - v13:BasicObject = SendWithoutBlock v6, :foo, v11 + v13:BasicObject = SendWithoutBlock v6, :foo, v11 # SendFallbackReason: Complex argument passing CheckInterrupts Return v13 "); @@ -2871,7 +2871,7 @@ mod hir_opt_tests { v11:Fixnum[0] = Const Value(0) v13:Fixnum[2] = Const Value(2) IncrCounter complex_arg_pass_param_kw_opt - v15:BasicObject = SendWithoutBlock v6, :foo, v11, v13 + v15:BasicObject = SendWithoutBlock v6, :foo, v11, v13 # SendFallbackReason: Complex argument passing CheckInterrupts Return v15 "); @@ -2936,7 +2936,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v11:Fixnum[2] = Const Value(2) IncrCounter complex_arg_pass_param_kw_opt - v13:BasicObject = SendWithoutBlock v6, :foo, v11 + v13:BasicObject = SendWithoutBlock v6, :foo, v11 # SendFallbackReason: Complex argument passing CheckInterrupts Return v13 "); @@ -2962,7 +2962,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v11:Fixnum[1] = Const Value(1) IncrCounter complex_arg_pass_param_kwrest - v13:BasicObject = SendWithoutBlock v6, :foo, v11 + v13:BasicObject = SendWithoutBlock v6, :foo, v11 # SendFallbackReason: Complex argument passing CheckInterrupts Return v13 "); @@ -2987,7 +2987,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): IncrCounter complex_arg_pass_param_kw_opt - v11:BasicObject = SendWithoutBlock v6, :foo + v11:BasicObject = SendWithoutBlock v6, :foo # SendFallbackReason: Complex argument passing CheckInterrupts Return v11 "); @@ -3012,7 +3012,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): IncrCounter complex_arg_pass_param_kwrest - v11:BasicObject = SendWithoutBlock v6, :foo + v11:BasicObject = SendWithoutBlock v6, :foo # SendFallbackReason: Complex argument passing CheckInterrupts Return v11 "); @@ -3038,7 +3038,7 @@ mod hir_opt_tests { v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v11 v14:Fixnum[1] = Const Value(1) - v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14 + v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14 # SendFallbackReason: Complex argument passing CheckInterrupts Return v16 "); @@ -3072,7 +3072,7 @@ mod hir_opt_tests { SetLocal :a, l0, EP@3, v16 v22:TrueClass = Const Value(true) IncrCounter complex_arg_pass_caller_kwarg - v24:BasicObject = Send v11, 0x1000, :each_line, v22 + v24:BasicObject = Send v11, 0x1000, :each_line, v22 # SendFallbackReason: Complex argument passing v25:BasicObject = GetLocal :s, l0, EP@4 v26:BasicObject = GetLocal :a, l0, EP@3 v30:BasicObject = GetLocal :a, l0, EP@3 @@ -3337,7 +3337,7 @@ mod hir_opt_tests { v46:HashExact = ObjectAllocClass Hash:VALUE(0x1008) IncrCounter complex_arg_pass_param_block IncrCounter complex_arg_pass_param_kw_opt - v20:BasicObject = SendWithoutBlock v46, :initialize + v20:BasicObject = SendWithoutBlock v46, :initialize # SendFallbackReason: Complex argument passing CheckInterrupts CheckInterrupts Return v46 @@ -3541,7 +3541,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): GuardBlockParamProxy l0 v15:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - v17:BasicObject = Send v8, 0x1008, :tap, v15 + v17:BasicObject = Send v8, 0x1008, :tap, v15 # SendFallbackReason: Uncategorized(send) CheckInterrupts Return v17 "); @@ -4018,7 +4018,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) v22:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 - v14:BasicObject = SendWithoutBlock v22, :freeze + v14:BasicObject = SendWithoutBlock v22, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v14 "); @@ -4041,7 +4041,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:HashExact = NewHash v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v10, :freeze, v12 + v14:BasicObject = SendWithoutBlock v10, :freeze, v12 # SendFallbackReason: SendWithoutBlock: unsupported method type Cfunc CheckInterrupts Return v14 "); @@ -4111,7 +4111,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v22:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 - v14:BasicObject = SendWithoutBlock v22, :freeze + v14:BasicObject = SendWithoutBlock v22, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v14 "); @@ -4134,7 +4134,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:ArrayExact = NewArray v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v10, :freeze, v12 + v14:BasicObject = SendWithoutBlock v10, :freeze, v12 # SendFallbackReason: SendWithoutBlock: unsupported method type Cfunc CheckInterrupts Return v14 "); @@ -4205,7 +4205,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) v23:BasicObject = CCallWithFrame v11, :String#dup@0x1040 - v15:BasicObject = SendWithoutBlock v23, :freeze + v15:BasicObject = SendWithoutBlock v23, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v15 "); @@ -4229,7 +4229,7 @@ mod hir_opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 v13:NilClass = Const Value(nil) - v15:BasicObject = SendWithoutBlock v11, :freeze, v13 + v15:BasicObject = SendWithoutBlock v11, :freeze, v13 # SendFallbackReason: SendWithoutBlock: unsupported method type Cfunc CheckInterrupts Return v15 "); @@ -4300,7 +4300,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) v23:BasicObject = CCallWithFrame v11, :String#dup@0x1040 - v15:BasicObject = SendWithoutBlock v23, :-@ + v15:BasicObject = SendWithoutBlock v23, :-@ # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v15 "); @@ -4805,7 +4805,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:Fixnum[100] = Const Value(100) - v13:BasicObject = SendWithoutBlock v6, :identity, v11 + v13:BasicObject = SendWithoutBlock v6, :identity, v11 # SendFallbackReason: Bmethod: Proc object is not defined by an ISEQ CheckInterrupts Return v13 "); @@ -4828,7 +4828,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :bmethod + v11:BasicObject = Send v6, 0x1000, :bmethod # SendFallbackReason: Send: unsupported method type Bmethod CheckInterrupts Return v11 "); @@ -5667,7 +5667,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = SendWithoutBlock v6, :foo + v11:BasicObject = SendWithoutBlock v6, :foo # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v11 "); @@ -5710,7 +5710,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendWithoutBlock v9, :foo + v14:BasicObject = SendWithoutBlock v9, :foo # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v14 "); @@ -5844,7 +5844,7 @@ mod hir_opt_tests { GuardBlockParamProxy l0 v16:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) IncrCounter complex_arg_pass_caller_blockarg - v18:BasicObject = Send v13, 0x1008, :map, v16 + v18:BasicObject = Send v13, 0x1008, :map, v16 # SendFallbackReason: Complex argument passing CheckInterrupts Return v18 "); @@ -5870,7 +5870,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo + v11:BasicObject = Send v6, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq CheckInterrupts Return v11 "); @@ -7589,7 +7589,7 @@ mod hir_opt_tests { v12:Fixnum[3] = Const Value(3) v14:Fixnum[3] = Const Value(3) v16:Fixnum[3] = Const Value(3) - v18:BasicObject = SendWithoutBlock v10, :foo, v12, v14, v16 + v18:BasicObject = SendWithoutBlock v10, :foo, v12, v14, v16 # SendFallbackReason: Argument count does not match parameter count CheckInterrupts Return v18 "); @@ -7639,7 +7639,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v10:Fixnum[0] = Const Value(0) - v12:BasicObject = SendWithoutBlock v10, :foo + v12:BasicObject = SendWithoutBlock v10, :foo # SendFallbackReason: Argument count does not match parameter count CheckInterrupts Return v12 "); @@ -7662,7 +7662,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[4] = Const Value(4) v12:Fixnum[1] = Const Value(1) - v14:BasicObject = SendWithoutBlock v10, :succ, v12 + v14:BasicObject = SendWithoutBlock v10, :succ, v12 # SendFallbackReason: SendWithoutBlock: unsupported method type Cfunc CheckInterrupts Return v14 "); @@ -7816,7 +7816,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:BasicObject = SendWithoutBlock v11, :^ + v17:BasicObject = SendWithoutBlock v11, :^ # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v17 "); @@ -8491,17 +8491,17 @@ mod hir_opt_tests { v13:ArrayExact = NewArray v19:ArrayExact = ToArray v13 IncrCounter complex_arg_pass_caller_splat - v21:BasicObject = SendWithoutBlock v8, :foo, v19 + v21:BasicObject = SendWithoutBlock v8, :foo, v19 # SendFallbackReason: Complex argument passing v25:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v26:StringExact = StringCopy v25 PatchPoint NoEPEscape(test) v31:ArrayExact = ToArray v13 IncrCounter complex_arg_pass_caller_splat - v33:BasicObject = SendWithoutBlock v26, :display, v31 + v33:BasicObject = SendWithoutBlock v26, :display, v31 # SendFallbackReason: Complex argument passing PatchPoint NoEPEscape(test) v41:ArrayExact = ToArray v13 IncrCounter complex_arg_pass_caller_splat - v43:BasicObject = SendWithoutBlock v8, :itself, v41 + v43:BasicObject = SendWithoutBlock v8, :itself, v41 # SendFallbackReason: Complex argument passing CheckInterrupts Return v43 "); @@ -9186,7 +9186,7 @@ mod hir_opt_tests { IncrCounter complex_arg_pass_param_block IncrCounter complex_arg_pass_param_kwrest IncrCounter complex_arg_pass_param_kw_opt - v13:BasicObject = SendWithoutBlock v6, :fancy, v11 + v13:BasicObject = SendWithoutBlock v6, :fancy, v11 # SendFallbackReason: Complex argument passing CheckInterrupts Return v13 "); @@ -9210,7 +9210,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): IncrCounter complex_arg_pass_param_forwardable - v11:BasicObject = SendWithoutBlock v6, :forwardable + v11:BasicObject = SendWithoutBlock v6, :forwardable # SendFallbackReason: Complex argument passing CheckInterrupts Return v11 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 7ef7671fa444f6..49f092337e9816 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -524,7 +524,7 @@ pub mod hir_build_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) v12:Fixnum[2] = Const Value(2) - v15:BasicObject = SendWithoutBlock v10, :+, v12 + v15:BasicObject = SendWithoutBlock v10, :+, v12 # SendFallbackReason: Uncategorized(opt_plus) CheckInterrupts Return v15 "); @@ -777,11 +777,11 @@ pub mod hir_build_tests { SetLocal :l1, l1, EP@3, v10 v15:BasicObject = GetLocal :l1, l1, EP@3 v17:BasicObject = GetLocal :l2, l2, EP@4 - v20:BasicObject = SendWithoutBlock v15, :+, v17 + v20:BasicObject = SendWithoutBlock v15, :+, v17 # SendFallbackReason: Uncategorized(opt_plus) SetLocal :l2, l2, EP@4, v20 v25:BasicObject = GetLocal :l2, l2, EP@4 v27:BasicObject = GetLocal :l3, l3, EP@5 - v30:BasicObject = SendWithoutBlock v25, :+, v27 + v30:BasicObject = SendWithoutBlock v25, :+, v27 # SendFallbackReason: Uncategorized(opt_plus) SetLocal :l3, l3, EP@5, v30 CheckInterrupts Return v30 @@ -1102,7 +1102,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :+, v12 + v19:BasicObject = SendWithoutBlock v11, :+, v12 # SendFallbackReason: Uncategorized(opt_plus) CheckInterrupts Return v19 "); @@ -1127,7 +1127,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :-, v12 + v19:BasicObject = SendWithoutBlock v11, :-, v12 # SendFallbackReason: Uncategorized(opt_minus) CheckInterrupts Return v19 "); @@ -1152,7 +1152,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :*, v12 + v19:BasicObject = SendWithoutBlock v11, :*, v12 # SendFallbackReason: Uncategorized(opt_mult) CheckInterrupts Return v19 "); @@ -1177,7 +1177,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :/, v12 + v19:BasicObject = SendWithoutBlock v11, :/, v12 # SendFallbackReason: Uncategorized(opt_div) CheckInterrupts Return v19 "); @@ -1202,7 +1202,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :%, v12 + v19:BasicObject = SendWithoutBlock v11, :%, v12 # SendFallbackReason: Uncategorized(opt_mod) CheckInterrupts Return v19 "); @@ -1227,7 +1227,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :==, v12 + v19:BasicObject = SendWithoutBlock v11, :==, v12 # SendFallbackReason: Uncategorized(opt_eq) CheckInterrupts Return v19 "); @@ -1252,7 +1252,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :!=, v12 + v19:BasicObject = SendWithoutBlock v11, :!=, v12 # SendFallbackReason: Uncategorized(opt_neq) CheckInterrupts Return v19 "); @@ -1277,7 +1277,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<, v12 + v19:BasicObject = SendWithoutBlock v11, :<, v12 # SendFallbackReason: Uncategorized(opt_lt) CheckInterrupts Return v19 "); @@ -1302,7 +1302,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<=, v12 + v19:BasicObject = SendWithoutBlock v11, :<=, v12 # SendFallbackReason: Uncategorized(opt_le) CheckInterrupts Return v19 "); @@ -1327,7 +1327,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>, v12 + v19:BasicObject = SendWithoutBlock v11, :>, v12 # SendFallbackReason: Uncategorized(opt_gt) CheckInterrupts Return v19 "); @@ -1368,7 +1368,7 @@ pub mod hir_build_tests { bb4(v26:BasicObject, v27:BasicObject, v28:BasicObject): PatchPoint NoEPEscape(test) v34:Fixnum[0] = Const Value(0) - v37:BasicObject = SendWithoutBlock v28, :>, v34 + v37:BasicObject = SendWithoutBlock v28, :>, v34 # SendFallbackReason: Uncategorized(opt_gt) CheckInterrupts v40:CBool = Test v37 IfTrue v40, bb3(v26, v27, v28) @@ -1379,9 +1379,9 @@ pub mod hir_build_tests { bb3(v53:BasicObject, v54:BasicObject, v55:BasicObject): PatchPoint NoEPEscape(test) v62:Fixnum[1] = Const Value(1) - v65:BasicObject = SendWithoutBlock v54, :+, v62 + v65:BasicObject = SendWithoutBlock v54, :+, v62 # SendFallbackReason: Uncategorized(opt_plus) v70:Fixnum[1] = Const Value(1) - v73:BasicObject = SendWithoutBlock v55, :-, v70 + v73:BasicObject = SendWithoutBlock v55, :-, v70 # SendFallbackReason: Uncategorized(opt_minus) Jump bb4(v53, v65, v73) "); } @@ -1405,7 +1405,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>=, v12 + v19:BasicObject = SendWithoutBlock v11, :>=, v12 # SendFallbackReason: Uncategorized(opt_ge) CheckInterrupts Return v19 "); @@ -1472,7 +1472,7 @@ pub mod hir_build_tests { bb2(v6:BasicObject): v11:Fixnum[2] = Const Value(2) v13:Fixnum[3] = Const Value(3) - v15:BasicObject = SendWithoutBlock v6, :bar, v11, v13 + v15:BasicObject = SendWithoutBlock v6, :bar, v11, v13 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v15 "); @@ -1500,7 +1500,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = Send v9, 0x1000, :each + v14:BasicObject = Send v9, 0x1000, :each # SendFallbackReason: Uncategorized(send) v15:BasicObject = GetLocal :a, l0, EP@3 CheckInterrupts Return v14 @@ -1559,7 +1559,7 @@ pub mod hir_build_tests { v18:StringExact = StringCopy v17 v20:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) v21:StringExact = StringCopy v20 - v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 + v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v23 "); @@ -1582,7 +1582,7 @@ pub mod hir_build_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v15:ArrayExact = ToArray v9 - v17:BasicObject = SendWithoutBlock v8, :foo, v15 + v17:BasicObject = SendWithoutBlock v8, :foo, v15 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v17 "); @@ -1604,7 +1604,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = Send v8, 0x1000, :foo, v9 + v15:BasicObject = Send v8, 0x1000, :foo, v9 # SendFallbackReason: Uncategorized(send) CheckInterrupts Return v15 "); @@ -1627,7 +1627,7 @@ pub mod hir_build_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): v14:Fixnum[1] = Const Value(1) - v16:BasicObject = SendWithoutBlock v8, :foo, v14 + v16:BasicObject = SendWithoutBlock v8, :foo, v14 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v16 "); @@ -1649,7 +1649,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendWithoutBlock v8, :foo, v9 + v15:BasicObject = SendWithoutBlock v8, :foo, v9 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v15 "); @@ -1672,7 +1672,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 + v11:BasicObject = InvokeSuper v6, 0x1000 # SendFallbackReason: Uncategorized(invokesuper) CheckInterrupts Return v11 "); @@ -1693,7 +1693,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 + v11:BasicObject = InvokeSuper v6, 0x1000 # SendFallbackReason: Uncategorized(invokesuper) CheckInterrupts Return v11 "); @@ -1715,7 +1715,7 @@ pub mod hir_build_tests { Jump bb2(v4) bb2(v6:BasicObject): v11:NilClass = Const Value(nil) - v13:BasicObject = InvokeSuper v6, 0x1000, v11 + v13:BasicObject = InvokeSuper v6, 0x1000, v11 # SendFallbackReason: Uncategorized(invokesuper) CheckInterrupts Return v13 "); @@ -1782,12 +1782,12 @@ pub mod hir_build_tests { v14:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) v16:HashExact = NewHash PatchPoint NoEPEscape(test) - v21:BasicObject = SendWithoutBlock v14, :core#hash_merge_kwd, v16, v9 + v21:BasicObject = SendWithoutBlock v14, :core#hash_merge_kwd, v16, v9 # SendFallbackReason: Uncategorized(opt_send_without_block) v23:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) v26:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v28:Fixnum[1] = Const Value(1) - v30:BasicObject = SendWithoutBlock v23, :core#hash_merge_ptr, v21, v26, v28 - v32:BasicObject = SendWithoutBlock v8, :foo, v30 + v30:BasicObject = SendWithoutBlock v23, :core#hash_merge_ptr, v21, v26, v28 # SendFallbackReason: Uncategorized(opt_send_without_block) + v32:BasicObject = SendWithoutBlock v8, :foo, v30 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v32 "); @@ -1812,7 +1812,7 @@ pub mod hir_build_tests { v15:ArrayExact = ToNewArray v9 v17:Fixnum[1] = Const Value(1) ArrayPush v15, v17 - v21:BasicObject = SendWithoutBlock v8, :foo, v15 + v21:BasicObject = SendWithoutBlock v8, :foo, v15 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v21 "); @@ -1834,7 +1834,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendForward v8, 0x1000, :foo, v9 + v15:BasicObject = SendForward v8, 0x1000, :foo, v9 # SendFallbackReason: Uncategorized(sendforward) CheckInterrupts Return v15 "); @@ -1891,11 +1891,11 @@ pub mod hir_build_tests { v16:CBool = IsMethodCFunc v11, :new IfFalse v16, bb3(v6, v13, v11) v18:HeapBasicObject = ObjectAlloc v11 - v20:BasicObject = SendWithoutBlock v18, :initialize + v20:BasicObject = SendWithoutBlock v18, :initialize # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Jump bb4(v6, v18, v20) bb3(v24:BasicObject, v25:NilClass, v26:BasicObject): - v29:BasicObject = SendWithoutBlock v26, :new + v29:BasicObject = SendWithoutBlock v26, :new # SendFallbackReason: Uncategorized(opt_send_without_block) Jump bb4(v24, v29, v25) bb4(v32:BasicObject, v33:BasicObject, v34:BasicObject): CheckInterrupts @@ -2008,7 +2008,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) SideExit UnhandledNewarraySend(MIN) "); } @@ -2040,13 +2040,13 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH) v32:Fixnum = ArrayHash v15, v16 PatchPoint NoEPEscape(test) v39:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v40:ArrayExact = ArrayDup v39 - v42:BasicObject = SendWithoutBlock v14, :puts, v40 + v42:BasicObject = SendWithoutBlock v14, :puts, v40 # SendFallbackReason: Uncategorized(opt_send_without_block) PatchPoint NoEPEscape(test) CheckInterrupts Return v32 @@ -2082,7 +2082,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH)) "); } @@ -2114,7 +2114,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) v31:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v32:StringExact = StringCopy v31 SideExit UnhandledNewarraySend(PACK) @@ -2148,7 +2148,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v30:StringExact = StringCopy v29 v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) @@ -2192,7 +2192,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) v29:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v30:StringExact = StringCopy v29 v36:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) @@ -2229,13 +2229,13 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P) v33:BoolExact = ArrayInclude v15, v16 | v16 PatchPoint NoEPEscape(test) v40:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v41:ArrayExact = ArrayDup v40 - v43:BasicObject = SendWithoutBlock v14, :puts, v41 + v43:BasicObject = SendWithoutBlock v14, :puts, v41 # SendFallbackReason: Uncategorized(opt_send_without_block) PatchPoint NoEPEscape(test) CheckInterrupts Return v33 @@ -2276,7 +2276,7 @@ pub mod hir_build_tests { v12:NilClass = Const Value(nil) Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 + v25:BasicObject = SendWithoutBlock v15, :+, v16 # SendFallbackReason: Uncategorized(opt_plus) SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_INCLUDE_P)) "); } @@ -2355,7 +2355,7 @@ pub mod hir_build_tests { Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v18:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v18, :length + v21:BasicObject = SendWithoutBlock v18, :length # SendFallbackReason: Uncategorized(opt_length) CheckInterrupts Return v21 "); @@ -2380,7 +2380,7 @@ pub mod hir_build_tests { Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v18:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v18, :size + v21:BasicObject = SendWithoutBlock v18, :size # SendFallbackReason: Uncategorized(opt_size) CheckInterrupts Return v21 "); @@ -2685,7 +2685,7 @@ pub mod hir_build_tests { bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): v16:NilClass = Const Value(nil) v20:Fixnum[1] = Const Value(1) - v24:BasicObject = SendWithoutBlock v11, :[]=, v12, v20 + v24:BasicObject = SendWithoutBlock v11, :[]=, v12, v20 # SendFallbackReason: Uncategorized(opt_aset) CheckInterrupts Return v20 "); @@ -2709,7 +2709,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :[], v12 + v19:BasicObject = SendWithoutBlock v11, :[], v12 # SendFallbackReason: Uncategorized(opt_aref) CheckInterrupts Return v19 "); @@ -2732,7 +2732,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendWithoutBlock v9, :empty? + v15:BasicObject = SendWithoutBlock v9, :empty? # SendFallbackReason: Uncategorized(opt_empty_p) CheckInterrupts Return v15 "); @@ -2755,7 +2755,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendWithoutBlock v9, :succ + v15:BasicObject = SendWithoutBlock v9, :succ # SendFallbackReason: Uncategorized(opt_succ) CheckInterrupts Return v15 "); @@ -2779,7 +2779,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :&, v12 + v19:BasicObject = SendWithoutBlock v11, :&, v12 # SendFallbackReason: Uncategorized(opt_and) CheckInterrupts Return v19 "); @@ -2803,7 +2803,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :|, v12 + v19:BasicObject = SendWithoutBlock v11, :|, v12 # SendFallbackReason: Uncategorized(opt_or) CheckInterrupts Return v19 "); @@ -2826,7 +2826,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): - v15:BasicObject = SendWithoutBlock v9, :! + v15:BasicObject = SendWithoutBlock v9, :! # SendFallbackReason: Uncategorized(opt_not) CheckInterrupts Return v15 "); @@ -2850,7 +2850,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :=~, v12 + v19:BasicObject = SendWithoutBlock v11, :=~, v12 # SendFallbackReason: Uncategorized(opt_regexpmatch2) CheckInterrupts Return v19 "); @@ -2880,7 +2880,7 @@ pub mod hir_build_tests { v12:BasicObject = PutSpecialObject CBase v14:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) v16:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) - v18:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v12, v14, v16 + v18:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v12, v14, v16 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v18 "); @@ -2980,7 +2980,7 @@ pub mod hir_build_tests { CheckInterrupts v16:CBool = IsNil v9 IfTrue v16, bb3(v8, v9, v9) - v19:BasicObject = SendWithoutBlock v9, :itself + v19:BasicObject = SendWithoutBlock v9, :itself # SendFallbackReason: Uncategorized(opt_send_without_block) Jump bb3(v8, v9, v19) bb3(v21:BasicObject, v22:BasicObject, v23:BasicObject): CheckInterrupts @@ -3065,7 +3065,7 @@ pub mod hir_build_tests { v35:CBool[true] = Test v32 IfFalse v35, bb3(v16, v17, v18, v19, v20, v25) PatchPoint NoEPEscape(open) - v42:BasicObject = InvokeBlock, v25 + v42:BasicObject = InvokeBlock, v25 # SendFallbackReason: Uncategorized(invokeblock) v45:BasicObject = InvokeBuiltin dir_s_close, v16, v25 CheckInterrupts Return v42 @@ -3190,12 +3190,12 @@ pub mod hir_build_tests { v13:NilClass = Const Value(nil) v16:Fixnum[0] = Const Value(0) v18:Fixnum[1] = Const Value(1) - v21:BasicObject = SendWithoutBlock v9, :[], v16, v18 + v21:BasicObject = SendWithoutBlock v9, :[], v16, v18 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts v25:CBool = Test v21 IfTrue v25, bb3(v8, v9, v13, v9, v16, v18, v21) v29:Fixnum[2] = Const Value(2) - v32:BasicObject = SendWithoutBlock v9, :[]=, v16, v18, v29 + v32:BasicObject = SendWithoutBlock v9, :[]=, v16, v18, v29 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts Return v29 bb3(v38:BasicObject, v39:BasicObject, v40:NilClass, v41:BasicObject, v42:Fixnum[0], v43:Fixnum[1], v44:BasicObject): @@ -3398,7 +3398,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v10:BasicObject = InvokeBlock + v10:BasicObject = InvokeBlock # SendFallbackReason: Uncategorized(invokeblock) CheckInterrupts Return v10 "); @@ -3423,7 +3423,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v6, v7, v8) bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v18:BasicObject = InvokeBlock, v11, v12 + v18:BasicObject = InvokeBlock, v11, v12 # SendFallbackReason: Uncategorized(invokeblock) CheckInterrupts Return v18 "); @@ -3547,7 +3547,7 @@ pub mod hir_build_tests { IfTrue v19, bb3(v10, v11, v12) v22:Fixnum[1] = Const Value(1) v24:Fixnum[1] = Const Value(1) - v27:BasicObject = SendWithoutBlock v22, :+, v24 + v27:BasicObject = SendWithoutBlock v22, :+, v24 # SendFallbackReason: Uncategorized(opt_plus) PatchPoint NoEPEscape(test) Jump bb3(v10, v27, v12) bb3(v32:BasicObject, v33:BasicObject, v34:BasicObject): From 6409715212d22699bd2751a363b050a5d8b94b83 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 2 Dec 2025 17:34:36 -0800 Subject: [PATCH 1727/2435] Fix allocationless anonymous splat keyword argument check Previously, if an argument splat and keywords are provided by the caller, it did not check whether the method/proc accepted keywords before avoiding the allocation. This is incorrect, because if the method/proc does not accept keywords, the keywords passed by the caller are added as a positional argument, so there must be an allocation to avoid mutating the positional splat argument. Add a check that if the caller passes keywords, the method/proc must accept keywords in order to optimize. If the caller passes a keyword splat, either the method/proc must accept keywords, or the keyword splat must be empty in order to optimize. If keywords are explicitly disallowed via `**nil`, the optimization should be skipped, because the array is mutated before the ArgumentError exception is raised. In addition to a test for the correct behavior, add an allocation test for a method that accepts an anonymous splat without keywords. Fixes [Bug #21757] --- test/ruby/test_allocation.rb | 53 ++++++++++++++++++++++++++++++++++++ test/ruby/test_call.rb | 29 ++++++++++++++++++++ vm_args.c | 26 ++++++++++++++---- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb index 6ade391c951848..90d7c04f9b0a2b 100644 --- a/test/ruby/test_allocation.rb +++ b/test/ruby/test_allocation.rb @@ -527,6 +527,59 @@ def self.splat_and_keyword_splat(*b, **kw#{block}); end RUBY end + def test_anonymous_splat_parameter + only_block = block.empty? ? block : block[2..] + check_allocations(<<~RUBY) + def self.anon_splat(*#{block}); end + + check_allocations(1, 1, "anon_splat(1, a: 2#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat(1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **hash1#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat(1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat(*nil, **nill#{block})") + check_allocations(0, 0, "anon_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat(*array1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat(#{only_block})") + check_allocations(1, 1, "anon_splat(a: 2#{block})") + check_allocations(0, 0, "anon_splat(**empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "anon_splat(*array1, **hash1, **empty_hash#{block})") + + unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + check_allocations(0, 0, "anon_splat(*array1, **nil#{block})") + check_allocations(1, 0, "anon_splat(*r2k_empty_array#{block})") + check_allocations(1, 1, "anon_splat(*r2k_array#{block})") + check_allocations(1, 0, "anon_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "anon_splat(*r2k_array1#{block})") + end + RUBY + end + def test_anonymous_splat_and_anonymous_keyword_splat_parameters only_block = block.empty? ? block : block[2..] check_allocations(<<~RUBY) diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 7843f3b476e6c9..1b30ed34d8826f 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -423,6 +423,35 @@ def self.s(*, kw: 0, **kws) [*->(*a){a}.call(*), kw, kws] end assert_equal([1, 2, {}], s(*r2ka)) end + def test_anon_splat_mutated_bug_21757 + args = [1, 2] + kw = {bug: true} + + def self.m(*); end + m(*args, bug: true) + assert_equal(2, args.length) + + proc = ->(*) { } + proc.(*args, bug: true) + assert_equal(2, args.length) + + def self.m2(*); end + m2(*args, **kw) + assert_equal(2, args.length) + + proc = ->(*) { } + proc.(*args, **kw) + assert_equal(2, args.length) + + def self.m3(*, **nil); end + assert_raise(ArgumentError) { m3(*args, bug: true) } + assert_equal(2, args.length) + + proc = ->(*, **nil) { } + assert_raise(ArgumentError) { proc.(*args, bug: true) } + assert_equal(2, args.length) + end + def test_kwsplat_block_eval_order def self.t(**kw, &b) [kw, b] end diff --git a/vm_args.c b/vm_args.c index 64ed88d0e1dcce..8d4042e35566ae 100644 --- a/vm_args.c +++ b/vm_args.c @@ -638,12 +638,26 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co given_argc == ISEQ_BODY(iseq)->param.lead_num + (kw_flag ? 2 : 1) && !ISEQ_BODY(iseq)->param.flags.has_opt && !ISEQ_BODY(iseq)->param.flags.has_post && - !ISEQ_BODY(iseq)->param.flags.ruby2_keywords && - (!kw_flag || - !ISEQ_BODY(iseq)->param.flags.has_kw || - !ISEQ_BODY(iseq)->param.flags.has_kwrest || - !ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg)) { - args->rest_dupped = true; + !ISEQ_BODY(iseq)->param.flags.ruby2_keywords) { + if (kw_flag) { + if (ISEQ_BODY(iseq)->param.flags.has_kw || + ISEQ_BODY(iseq)->param.flags.has_kwrest) { + args->rest_dupped = true; + } + else if (kw_flag & VM_CALL_KW_SPLAT) { + VALUE kw_hash = locals[args->argc - 1]; + if (kw_hash == Qnil || + (RB_TYPE_P(kw_hash, T_HASH) && RHASH_EMPTY_P(kw_hash))) { + args->rest_dupped = true; + } + } + + } + else if (!ISEQ_BODY(iseq)->param.flags.has_kw && + !ISEQ_BODY(iseq)->param.flags.has_kwrest && + !ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg) { + args->rest_dupped = true; + } } } From 1e7cf7b2bc1f9b356b2e980e1e18548618da6363 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 10 Oct 2025 16:32:43 -0700 Subject: [PATCH 1728/2435] Fix refinement modification of method visibility in superclass Previously, this didn't work correctly, resulting in a SystemStackError. This fixes the issue by finding the related superclass method entry, and updating the orig_me in the refinement method to point to the superclass method. Fixes [Bug #21446] --- test/ruby/test_refinement.rb | 23 +++++++++++++++++++++++ vm_method.c | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index bdc6667b8e81fe..209e55294b1889 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -1933,6 +1933,29 @@ module PublicCows end; end + def test_public_in_refine_for_method_in_superclass + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug21446 = '[ruby-core:122558] [Bug #21446]' + + class CowSuper + private + def moo() "Moo"; end + end + class Cow < CowSuper + end + + module PublicCows + refine(Cow) { + public :moo + } + end + + using PublicCows + assert_equal("Moo", Cow.new.moo, bug21446) + end; + end + module SuperToModule class Parent end diff --git a/vm_method.c b/vm_method.c index c4f391b5afe38a..dbc5ad97eded92 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1281,6 +1281,7 @@ check_override_opt_method(VALUE klass, VALUE mid) } } +static inline rb_method_entry_t* search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined); /* * klass->method_table[mid] = method_entry(defined_class, visi, def) * @@ -1321,7 +1322,12 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil if (RB_TYPE_P(klass, T_MODULE) && FL_TEST(klass, RMODULE_IS_REFINEMENT)) { VALUE refined_class = rb_refinement_module_get_refined_class(klass); + bool search_superclass = type == VM_METHOD_TYPE_ZSUPER && !lookup_method_table(refined_class, mid); rb_add_refined_method_entry(refined_class, mid); + if (search_superclass) { + rb_method_entry_t *me = lookup_method_table(refined_class, mid); + me->def->body.refined.orig_me = search_method0(refined_class, mid, NULL, true); + } } if (type == VM_METHOD_TYPE_REFINED) { rb_method_entry_t *old_me = lookup_method_table(RCLASS_ORIGIN(klass), mid); From 76fb0d244b95a23116bfe72bb2422395c3a76477 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 22 Nov 2025 19:51:46 -0800 Subject: [PATCH 1729/2435] Use actual class instead of singleton class in frozen error message With the following code: ```ruby object = [] object.singleton_class object.freeze object.instance_variable_set(:@a, 42) ``` The previous error message was: ``` can't modify frozen #>: [] ``` With this change, the error message is: ``` can't modify frozen Array: [] ``` Since we show the inspect value of the affected object, I think including the singleton class instead of the actual class if it exists makes the error message harder to understand. --- error.c | 2 +- test/ruby/test_frozen.rb | 16 ++++++++++++++++ test/ruby/test_zjit.rb | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/error.c b/error.c index ed66e6c8488d0f..e1a01b985aae16 100644 --- a/error.c +++ b/error.c @@ -4163,7 +4163,7 @@ rb_error_frozen_object(VALUE frozen_obj) rb_yjit_lazy_push_frame(GET_EC()->cfp->pc); VALUE mesg = rb_sprintf("can't modify frozen %"PRIsVALUE": ", - CLASS_OF(frozen_obj)); + rb_obj_class(frozen_obj)); VALUE exc = rb_exc_new_str(rb_eFrozenError, mesg); rb_ivar_set(exc, id_recv, frozen_obj); diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb index 2918a2afd822c7..6721cb112863f3 100644 --- a/test/ruby/test_frozen.rb +++ b/test/ruby/test_frozen.rb @@ -27,4 +27,20 @@ def test_setting_ivar_on_frozen_string_with_ivars str.freeze assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) } end + + def test_setting_ivar_on_frozen_string_with_singleton_class + str = "str" + str.singleton_class + str.freeze + assert_raise_with_message(FrozenError, "can't modify frozen String: \"str\"") { str.instance_variable_set(:@a, 1) } + end + + class A + freeze + end + + def test_setting_ivar_on_frozen_class + assert_raise_with_message(FrozenError, "can't modify frozen Class: TestFrozen::A") { A.instance_variable_set(:@a, 1) } + assert_raise_with_message(FrozenError, "can't modify frozen Class: #") { A.singleton_class.instance_variable_set(:@a, 1) } + end end diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 81d940407063f1..49b3616425ecd3 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2057,7 +2057,7 @@ def self.test = @@x = 42 end def test_setclassvariable_raises - assert_compiles '"can\'t modify frozen #: Foo"', %q{ + assert_compiles '"can\'t modify frozen Class: Foo"', %q{ class Foo def self.test = @@x = 42 freeze From e436dba9fee4c75104212ad0fd16ab7f753985c4 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 22 Nov 2025 19:58:43 -0800 Subject: [PATCH 1730/2435] Use rb_error_frozen_object in rb_class_modify_check This provides information on the class of the frozen object. It also results in a much simpler implementation. Fixes [Bug #21374] --- eval.c | 32 +------------------ spec/ruby/core/exception/frozen_error_spec.rb | 4 ++- spec/ruby/language/def_spec.rb | 9 ++++-- test/ruby/test_class.rb | 2 +- test/ruby/test_module.rb | 6 ++-- 5 files changed, 14 insertions(+), 39 deletions(-) diff --git a/eval.c b/eval.c index 4fbb0e799735d4..ee5bc43f9cc4c1 100644 --- a/eval.c +++ b/eval.c @@ -428,40 +428,10 @@ rb_class_modify_check(VALUE klass) rb_class_set_initialized(klass); } if (OBJ_FROZEN(klass)) { - const char *desc; - if (RCLASS_SINGLETON_P(klass)) { - desc = "object"; klass = RCLASS_ATTACHED_OBJECT(klass); - if (!SPECIAL_CONST_P(klass)) { - switch (BUILTIN_TYPE(klass)) { - case T_MODULE: - case T_ICLASS: - desc = "Module"; - break; - case T_CLASS: - desc = "Class"; - break; - default: - break; - } - } - } - else { - switch (BUILTIN_TYPE(klass)) { - case T_MODULE: - case T_ICLASS: - desc = "module"; - break; - case T_CLASS: - desc = "class"; - break; - default: - Check_Type(klass, T_CLASS); - UNREACHABLE; - } } - rb_frozen_error_raise(klass, "can't modify frozen %s: %"PRIsVALUE, desc, klass); + rb_error_frozen_object(klass); } } diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 51eb79cacef6ae..1cf35496d40d11 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -26,9 +26,11 @@ def o.x; end object = Object.new object.freeze + msg_class = RUBY_VERSION >= "4" ? "Object" : "object" + -> { def object.x; end - }.should raise_error(FrozenError, "can't modify frozen object: #{object}") + }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{object}") object = [].freeze -> { object << nil }.should raise_error(FrozenError, "can't modify frozen Array: []") diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index 296d4787d01c45..5cc36d61f0a927 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -97,7 +97,8 @@ def foo(a); end def foo; end end }.should raise_error(FrozenError) { |e| - e.message.should == "can't modify frozen module: #{e.receiver}" + msg_class = RUBY_VERSION >= "4" ? "Module" : "module" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } -> { @@ -106,7 +107,8 @@ def foo; end def foo; end end }.should raise_error(FrozenError){ |e| - e.message.should == "can't modify frozen class: #{e.receiver}" + msg_class = RUBY_VERSION >= "4" ? "Class" : "class" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } end end @@ -283,7 +285,8 @@ def obj.==(other) it "raises FrozenError with the correct class name" do obj = Object.new obj.freeze - -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen object: #{obj}") + msg_class = RUBY_VERSION >= "4" ? "Object" : "object" + -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{obj}") obj = Object.new c = obj.singleton_class diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 1faecd0cb25e13..22078514ad5cd3 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -601,7 +601,7 @@ def test_singleton_class_of_frozen_object obj = Object.new c = obj.singleton_class obj.freeze - assert_raise_with_message(FrozenError, /frozen object/) { + assert_raise_with_message(FrozenError, /frozen Object/) { c.class_eval {def f; end} } end diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 62b2a7164fb4ff..3a47c2551a813e 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -3016,17 +3016,17 @@ def test_frozen_visibility bug11532 = '[ruby-core:70828] [Bug #11532]' c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(FrozenError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen Class/, bug11532) { c.class_eval {private_constant :A} } c = Class.new {const_set(:A, 1); private_constant :A}.freeze - assert_raise_with_message(FrozenError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen Class/, bug11532) { c.class_eval {public_constant :A} } c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(FrozenError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen Class/, bug11532) { c.class_eval {deprecate_constant :A} } end From 29c29c2b7e972359ab83038c5dc27a7e53ae65c7 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 9 Dec 2025 18:14:49 -0500 Subject: [PATCH 1731/2435] ZJIT: Add dump to file for --zjit-stats (#15414) * ZJIT: Add dump to file for --zjit-stats * ZJIT: Rename --zjit-stats=quiet to --zjit-stats-quiet --- zjit.c | 1 + zjit.rb | 11 +++++++++++ zjit/src/options.rs | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/zjit.c b/zjit.c index 7731bc908f6ab9..75cca281a45a91 100644 --- a/zjit.c +++ b/zjit.c @@ -315,6 +315,7 @@ VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key); VALUE rb_zjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_print_stats_p(rb_execution_context_t *ec, VALUE self); +VALUE rb_zjit_get_stats_file_path_p(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_get_exit_locations(rb_execution_context_t *ec, VALUE self); diff --git a/zjit.rb b/zjit.rb index d128adead6fe39..e2aa55f764eb9a 100644 --- a/zjit.rb +++ b/zjit.rb @@ -10,6 +10,9 @@ module RubyVM::ZJIT # Blocks that are called when YJIT is enabled @jit_hooks = [] # Avoid calling a Ruby method here to avoid interfering with compilation tests + if Primitive.rb_zjit_get_stats_file_path_p + at_exit { print_stats_file } + end if Primitive.rb_zjit_print_stats_p at_exit { print_stats } end @@ -333,6 +336,14 @@ def print_stats $stderr.write stats_string end + # Print ZJIT stats to file + def print_stats_file + filename = Primitive.rb_zjit_get_stats_file_path_p + File.open(filename, "wb") do |file| + file.write stats_string + end + end + def dump_locations # :nodoc: return unless trace_exit_locations_enabled? diff --git a/zjit/src/options.rs b/zjit/src/options.rs index b7e2c71cefcd65..ea9dc3249b82ff 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -51,6 +51,9 @@ pub struct Options { /// Print stats on exit (when stats is also true) pub print_stats: bool, + /// Print stats to file on exit (when stats is also true) + pub print_stats_file: Option, + /// Enable debug logging pub debug: bool, @@ -103,6 +106,7 @@ impl Default for Options { num_profiles: DEFAULT_NUM_PROFILES, stats: false, print_stats: false, + print_stats_file: None, debug: false, disable: false, disable_hir_opt: false, @@ -131,7 +135,10 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ "Number of calls to trigger JIT (default: 30)."), ("--zjit-num-profiles=num", "Number of profiled calls before JIT (default: 5)."), - ("--zjit-stats[=quiet]", "Enable collecting ZJIT statistics (=quiet to suppress output)."), + ("--zjit-stats-quiet", + "Collect ZJIT stats and suppress output."), + ("--zjit-stats[=file]", + "Collect ZJIT stats (=file to write to a file)."), ("--zjit-disable", "Disable ZJIT for lazily enabling it with RubyVM::ZJIT.enable."), ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), @@ -303,13 +310,28 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { Err(_) => return None, }, + + ("stats-quiet", _) => { + options.stats = true; + options.print_stats = false; + } + ("stats", "") => { options.stats = true; options.print_stats = true; } - ("stats", "quiet") => { + ("stats", path) => { + // Truncate the file if it exists + std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) + .map_err(|e| eprintln!("Failed to open file '{}': {}", path, e)) + .ok(); + let canonical_path = std::fs::canonicalize(opt_val).unwrap_or_else(|_| opt_val.into()); options.stats = true; - options.print_stats = false; + options.print_stats_file = Some(canonical_path); } ("trace-exits", exits) => { @@ -488,3 +510,14 @@ pub extern "C" fn rb_zjit_print_stats_p(_ec: EcPtr, _self: VALUE) -> VALUE { Qfalse } } + +/// Return path if stats should be printed at exit to a specified file, else Qnil. +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_get_stats_file_path_p(_ec: EcPtr, _self: VALUE) -> VALUE { + if let Some(opts) = unsafe { OPTIONS.as_ref() } { + if let Some(ref path) = opts.print_stats_file { + return rust_str_to_ruby(path.as_os_str().to_str().unwrap()); + } + } + Qnil +} From 264c469bc2cd6a9320cb1545d83ebda69e421f49 Mon Sep 17 00:00:00 2001 From: Yuji Teshima <36704166+yujiteshima@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:28:18 +0900 Subject: [PATCH 1732/2435] Fix typo in thread_pthread.c [ci skip] (#15465) Fix typo in thread_pthread.c: threre -> there --- thread_pthread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thread_pthread.c b/thread_pthread.c index 93b32e55c0f72b..0a19f7f0310af1 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1459,7 +1459,7 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) vm->ractor.sync.lock_owner = cr; } - // do not release ractor_sched_lock and threre is no newly added (resumed) thread + // do not release ractor_sched_lock and there is no newly added (resumed) thread // thread_sched_setup_running_threads } From f9eb0d8da2cb570c6cf7754e4078ed0de266a52c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Dec 2025 09:22:09 +0900 Subject: [PATCH 1733/2435] Use `ruby_version_is` As the markers for spec/mspec/tool/remove_old_guards.rb. --- spec/ruby/core/exception/frozen_error_spec.rb | 2 +- spec/ruby/language/def_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 1cf35496d40d11..af2e92566192eb 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -26,7 +26,7 @@ def o.x; end object = Object.new object.freeze - msg_class = RUBY_VERSION >= "4" ? "Object" : "object" + msg_class = ruby_version_is("4.0") ? "Object" : "object" -> { def object.x; end diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index 5cc36d61f0a927..0cf1790791a0fb 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -97,7 +97,7 @@ def foo(a); end def foo; end end }.should raise_error(FrozenError) { |e| - msg_class = RUBY_VERSION >= "4" ? "Module" : "module" + msg_class = ruby_version_is("4.0") ? "Module" : "module" e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } @@ -107,7 +107,7 @@ def foo; end def foo; end end }.should raise_error(FrozenError){ |e| - msg_class = RUBY_VERSION >= "4" ? "Class" : "class" + msg_class = ruby_version_is("4.0") ? "Class" : "class" e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" } end @@ -285,7 +285,7 @@ def obj.==(other) it "raises FrozenError with the correct class name" do obj = Object.new obj.freeze - msg_class = RUBY_VERSION >= "4" ? "Object" : "object" + msg_class = ruby_version_is("4.0") ? "Object" : "object" -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{obj}") obj = Object.new From 3bb97e7707a0be8c371cb9c704cb1e21062e1fc6 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 10 Dec 2025 04:32:34 +0900 Subject: [PATCH 1734/2435] `_RUBY_DEBUG_LOG` usable anywhere even if `USE_RUBY_DEBUG_LOG=0`. It becomes `fprintf(stderr, ...)`. --- debug.c | 18 ++++++++++++++++++ vm_debug.h | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/debug.c b/debug.c index b92faa8f369398..4daee2bd1cbd0d 100644 --- a/debug.c +++ b/debug.c @@ -708,4 +708,22 @@ ruby_debug_log_dump(const char *fname, unsigned int n) fclose(fp); } } + +#else + +#undef ruby_debug_log +void +ruby_debug_log(const char *file, int line, const char *func_name, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "[%s:%d] %s: ", file, line, func_name); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); +} + #endif // #if USE_RUBY_DEBUG_LOG diff --git a/vm_debug.h b/vm_debug.h index d0bc81574a31b5..cf80232f3a5eb0 100644 --- a/vm_debug.h +++ b/vm_debug.h @@ -88,6 +88,10 @@ void ruby_debug_log(const char *file, int line, const char *func_name, const cha void ruby_debug_log_print(unsigned int n); bool ruby_debug_log_filter(const char *func_name, const char *file_name); +// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified. +// You can use this macro for temporary usage (you should not commit it). +#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__) + #if RBIMPL_COMPILER_IS(GCC) && defined(__OPTIMIZE__) # define ruby_debug_log(...) \ RB_GNUC_EXTENSION_BLOCK( \ @@ -97,10 +101,6 @@ bool ruby_debug_log_filter(const char *func_name, const char *file_name); RBIMPL_WARNING_POP()) #endif -// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified. -// You can use this macro for temporary usage (you should not commit it). -#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__) - #if USE_RUBY_DEBUG_LOG # define RUBY_DEBUG_LOG_ENABLED(func_name, file_name) \ (ruby_debug_log_mode && ruby_debug_log_filter(func_name, file_name)) From 3636277dc5837bcedcd5ef43d49423194064a676 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Dec 2025 12:09:50 +0900 Subject: [PATCH 1735/2435] Add `NUM2PTR` and `PTR2NUM` macros These macros have been defined here and there, so collect them. --- box.c | 2 +- compile.c | 2 -- ext/-test-/fatal/invalid.c | 6 ------ gc.c | 7 +------ include/ruby/internal/arithmetic/intptr_t.h | 12 ++++++++++++ internal/box.h | 10 ---------- load.c | 4 ++-- spec/ruby/optional/capi/ext/digest_spec.c | 2 ++ yjit.c | 2 -- zjit.c | 2 -- 10 files changed, 18 insertions(+), 31 deletions(-) diff --git a/box.c b/box.c index 0ce8d0aee02f0f..7907e0ff632a1e 100644 --- a/box.c +++ b/box.c @@ -760,7 +760,7 @@ static int cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg) { #if defined(_WIN32) - HMODULE h = (HMODULE)NUM2SVALUE(value); + HMODULE h = (HMODULE)NUM2PTR(value); WCHAR module_path[MAXPATHLEN]; DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path)); diff --git a/compile.c b/compile.c index a5d821eb810cf1..bcf22243cfc7af 100644 --- a/compile.c +++ b/compile.c @@ -610,8 +610,6 @@ branch_coverage_valid_p(rb_iseq_t *iseq, int first_line) return 1; } -#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - static VALUE setup_branch(const rb_code_location_t *loc, const char *type, VALUE structure, VALUE key) { diff --git a/ext/-test-/fatal/invalid.c b/ext/-test-/fatal/invalid.c index 393465416a0f6c..6fd970b181191c 100644 --- a/ext/-test-/fatal/invalid.c +++ b/ext/-test-/fatal/invalid.c @@ -1,11 +1,5 @@ #include -#if SIZEOF_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULONG(x) -#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULL(x) -#endif - static VALUE invalid_call(VALUE obj, VALUE address) { diff --git a/gc.c b/gc.c index 79eec5d96bf7af..5f0f2307c8c9fd 100644 --- a/gc.c +++ b/gc.c @@ -2122,14 +2122,9 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) static VALUE id2ref(VALUE objid) { -#if SIZEOF_LONG == SIZEOF_VOIDP -#define NUM2PTR(x) NUM2ULONG(x) -#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -#define NUM2PTR(x) NUM2ULL(x) -#endif objid = rb_to_int(objid); if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) { - VALUE ptr = NUM2PTR(objid); + VALUE ptr = (VALUE)NUM2PTR(objid); if (SPECIAL_CONST_P(ptr)) { if (ptr == Qtrue) return Qtrue; if (ptr == Qfalse) return Qfalse; diff --git a/include/ruby/internal/arithmetic/intptr_t.h b/include/ruby/internal/arithmetic/intptr_t.h index a354f4469cdf95..70090f88e6b37c 100644 --- a/include/ruby/internal/arithmetic/intptr_t.h +++ b/include/ruby/internal/arithmetic/intptr_t.h @@ -32,6 +32,18 @@ #define rb_int_new rb_int2inum /**< @alias{rb_int2inum} */ #define rb_uint_new rb_uint2inum /**< @alias{rb_uint2inum} */ +// These definitions are same as fiddle/conversions.h +#if SIZEOF_VOIDP <= SIZEOF_LONG +# define PTR2NUM(x) (LONG2NUM((long)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULONG(x))) +#elif SIZEOF_VOIDP <= SIZEOF_LONG_LONG +# define PTR2NUM(x) (LL2NUM((LONG_LONG)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULL(x))) +#else +// should have been an error in ruby/internal/value.h +# error Need integer for VALUE +#endif + RBIMPL_SYMBOL_EXPORT_BEGIN() /** diff --git a/internal/box.h b/internal/box.h index 72263cc9dc5e5d..b62b6a9bc946f2 100644 --- a/internal/box.h +++ b/internal/box.h @@ -3,16 +3,6 @@ #include "ruby/ruby.h" /* for VALUE */ -#if SIZEOF_VALUE <= SIZEOF_LONG -# define SVALUE2NUM(x) LONG2NUM((long)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x) -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x) -#else -# error Need integer for VALUE -#endif - /** * @author Ruby developers * @copyright This file is a part of the programming language Ruby. diff --git a/load.c b/load.c index 466517f465b098..144f095b04d2d3 100644 --- a/load.c +++ b/load.c @@ -1345,7 +1345,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa reset_ext_config = true; ext_config_push(th, &prev_ext_config); handle = rb_vm_call_cfunc_in_box(box->top_self, load_ext, path, fname, path, box); - rb_hash_aset(box->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle)); + rb_hash_aset(box->ruby_dln_libmap, path, PTR2NUM(handle)); break; } result = TAG_RETURN; @@ -1666,7 +1666,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) if (NIL_P(handle)) { return NULL; } - return dln_symbol((void *)NUM2SVALUE(handle), symbol); + return dln_symbol(NUM2PTR(handle), symbol); } void diff --git a/spec/ruby/optional/capi/ext/digest_spec.c b/spec/ruby/optional/capi/ext/digest_spec.c index 9993238cf227d3..65c8defa20adce 100644 --- a/spec/ruby/optional/capi/ext/digest_spec.c +++ b/spec/ruby/optional/capi/ext/digest_spec.c @@ -135,7 +135,9 @@ VALUE digest_spec_context_size(VALUE self, VALUE meta) { return SIZET2NUM(algo->ctx_size); } +#ifndef PTR2NUM #define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) +#endif VALUE digest_spec_context(VALUE self, VALUE digest) { return PTR2NUM(context); diff --git a/yjit.c b/yjit.c index 6d909a0da61ee3..f3c256093eda0b 100644 --- a/yjit.c +++ b/yjit.c @@ -64,8 +64,6 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); // The "_yjit_" part is for trying to be informative. We might want different // suffixes for symbols meant for Rust and symbols meant for broader CRuby. -# define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - // For a given raw_sample (frame), set the hash with the caller's // name, file, and line number. Return the hash with collected frame_info. static void diff --git a/zjit.c b/zjit.c index 75cca281a45a91..df44821fe54643 100644 --- a/zjit.c +++ b/zjit.c @@ -35,8 +35,6 @@ enum zjit_struct_offsets { ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param) }; -#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - // For a given raw_sample (frame), set the hash with the caller's // name, file, and line number. Return the hash with collected frame_info. static void From df4fc0f7fcda6c552084ea0638c7185b4a98c939 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 14:07:11 +0900 Subject: [PATCH 1736/2435] [ruby/psych] v5.3.0 https://github.com/ruby/psych/commit/d8053b0d16 --- ext/psych/lib/psych/versions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 3202b10296f549..2c2319e7a38e67 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,7 +2,7 @@ module Psych # The version of Psych you are using - VERSION = '5.2.6' + VERSION = '5.3.0' if RUBY_ENGINE == 'jruby' DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze From e4786376d68fc62a0bfcdd4d0e644de0e41e0d4d Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 05:09:13 +0000 Subject: [PATCH 1737/2435] Update default gems list at df4fc0f7fcda6c552084ea0638c718 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 784b92b717d1c5..a92df627636bd0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -256,7 +256,7 @@ The following default gems are updated. * optparse 0.8.0 * pp 0.6.3 * prism 1.6.0 -* psych 5.2.6 +* psych 5.3.0 * resolv 0.6.3 * stringio 3.1.9.dev * strscan 3.1.6.dev From 814f23747b5fd7b0d5fb6cd8e45833ec39482858 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 14:11:45 +0900 Subject: [PATCH 1738/2435] [ruby/resolv] v0.7.0 https://github.com/ruby/resolv/commit/a0e89bbe48 --- lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index b98c7ecdd2aa32..0e62aaf8510496 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -35,7 +35,7 @@ class Resolv # The version string - VERSION = "0.6.3" + VERSION = "0.7.0" ## # Looks up the first IP address for +name+. From 238e69d1258663e3385853dca36719c08e089f06 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 05:13:39 +0000 Subject: [PATCH 1739/2435] Update default gems list at 814f23747b5fd7b0d5fb6cd8e45833 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index a92df627636bd0..8ea6e93b1b5acc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -257,7 +257,7 @@ The following default gems are updated. * pp 0.6.3 * prism 1.6.0 * psych 5.3.0 -* resolv 0.6.3 +* resolv 0.7.0 * stringio 3.1.9.dev * strscan 3.1.6.dev * timeout 0.5.0 From ec862b41dc9aaa2a22d80961b62417a347bc84ec Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 9 Dec 2025 21:18:03 -0800 Subject: [PATCH 1740/2435] ZJIT: Prohibit ZJIT support with USE_FLONUM=0 (#15471) --- .github/workflows/compilers.yml | 2 +- zjit.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 5e54d39e9fa6b9..8c0ca54e0b100b 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -244,7 +244,7 @@ jobs: - { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' }, timeout-minutes: 5 } - - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } compileX: name: 'omnibus compilations, #10' diff --git a/zjit.c b/zjit.c index df44821fe54643..05fb3e1f028ff6 100644 --- a/zjit.c +++ b/zjit.c @@ -31,6 +31,10 @@ #include +// This build config impacts the pointer tagging scheme and we only want to +// support one scheme for simplicity. +STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); + enum zjit_struct_offsets { ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param) }; From 5f444cba4741b2ff0e1e95f4a179329b1ebc74a2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 14:21:59 +0900 Subject: [PATCH 1741/2435] [ruby/ipaddr] v1.2.8 https://github.com/ruby/ipaddr/commit/93ef50bc04 --- lib/ipaddr.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 513b7778a92f0b..725ff309d27f48 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -41,7 +41,7 @@ class IPAddr # The version string - VERSION = "1.2.7" + VERSION = "1.2.8" # 32 bit mask for IPv4 IN4MASK = 0xffffffff From ab80d05fef77c20abb77b08b0c14882a8772ecfb Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 05:23:39 +0000 Subject: [PATCH 1742/2435] Update default gems list at 5f444cba4741b2ff0e1e95f4a17932 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 8ea6e93b1b5acc..0c2682d8b96aa3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -250,6 +250,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.4.0.dev +* ipaddr 1.2.8 * json 2.17.1 * net-http 0.8.0 * openssl 4.0.0.pre From 4523a905327d8438f845f5a75822225ccd041beb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 14:28:10 +0900 Subject: [PATCH 1743/2435] [ruby/date] v3.5.1 https://github.com/ruby/date/commit/1d0aadc295 --- ext/date/lib/date.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb index b33f6e65f466b0..0cb763017f22c0 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,7 +4,7 @@ require 'date_core' class Date - VERSION = "3.5.0" # :nodoc: + VERSION = "3.5.1" # :nodoc: # call-seq: # infinite? -> false From 74376fefbb2d79d9c2df355445689058fab828e6 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 05:30:06 +0000 Subject: [PATCH 1744/2435] Update default gems list at 4523a905327d8438f845f5a7582222 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 0c2682d8b96aa3..8c1d15142f71b4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -240,7 +240,7 @@ The following default gems are updated. * RubyGems 4.0.1 * bundler 4.0.1 -* date 3.5.0 +* date 3.5.1 * digest 3.2.1 * english 0.8.1 * erb 6.0.0 From bbee62abbd26e3bf526dbbfddd17d72b81402a72 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 14:48:32 +0900 Subject: [PATCH 1745/2435] We don't need to check the latest release of pathname Pathname is now embedded class of Ruby --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 780f923b55598e..6945c6cdce52a6 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -427,7 +427,7 @@ def sync_default_gems(gem) end def check_prerelease_version(gem) - return if ["rubygems", "mmtk", "cgi"].include?(gem) + return if ["rubygems", "mmtk", "cgi", "pathname"].include?(gem) require "net/https" require "json" From 842f91aec09e419481af6358657e06973f2410c2 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 23:15:47 -0600 Subject: [PATCH 1746/2435] [ruby/stringio] [DOC] Tweaks for StringIO#getc (https://github.com/ruby/stringio/pull/189) https://github.com/ruby/stringio/commit/e3d16d30ed --- doc/stringio/getc.rdoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/stringio/getc.rdoc b/doc/stringio/getc.rdoc index c021789c911b8d..b2ab46843c8466 100644 --- a/doc/stringio/getc.rdoc +++ b/doc/stringio/getc.rdoc @@ -12,9 +12,9 @@ Returns +nil+ if at end-of-stream: Returns characters, not bytes: - strio = StringIO.new('тест') - strio.getc # => "т" - strio.getc # => "е" + strio = StringIO.new('Привет') + strio.getc # => "П" + strio.getc # => "р" strio = StringIO.new('こんにちは') strio.getc # => "こ" @@ -31,4 +31,4 @@ in other cases that need not be true: strio.pos = 5 # => 5 # At third byte of second character; returns byte. strio.getc # => "\x93" -Related: StringIO.getbyte. +Related: #getbyte, #putc, #ungetc. From 668fe01182df185c3592061e22087b6454132fec Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 5 Dec 2025 16:07:45 +0000 Subject: [PATCH 1747/2435] [ruby/stringio] [DOC] Fix link https://github.com/ruby/stringio/commit/e2d24ae8d7 --- doc/stringio/stringio.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md index aebfa6d6f1f4c2..8931d1c30c7d79 100644 --- a/doc/stringio/stringio.md +++ b/doc/stringio/stringio.md @@ -324,7 +324,7 @@ a binary stream may not be changed to text. ### Encodings -A stream has an encoding; see the [encodings document][encodings document]. +A stream has an encoding; see [Encodings][encodings document]. The initial encoding for a new or re-opened stream depends on its [data mode][data mode]: @@ -683,7 +683,7 @@ Reading: - #each_codepoint: reads each remaining codepoint, passing it to the block. [bom]: https://en.wikipedia.org/wiki/Byte_order_mark -[encodings document]: https://docs.ruby-lang.org/en/master/encodings_rdoc.html +[encodings document]: https://docs.ruby-lang.org/en/master/language/encodings_rdoc.html [io class]: https://docs.ruby-lang.org/en/master/IO.html [kernel#puts]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-puts [kernel#readline]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-readline From f623fcc7d069cdfcaf25285c986ed995530a686f Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:18:18 -0600 Subject: [PATCH 1748/2435] [ruby/stringio] [DOC] Tweaks for StringIO.getbyte (https://github.com/ruby/stringio/pull/188) https://github.com/ruby/stringio/commit/66360ee5f1 --- doc/stringio/getbyte.rdoc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc index 48c334b5252a58..5e524941bca4ae 100644 --- a/doc/stringio/getbyte.rdoc +++ b/doc/stringio/getbyte.rdoc @@ -14,16 +14,18 @@ Returns +nil+ if at end-of-stream: Returns a byte, not a character: - s = 'тест' - s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + s = 'Привет' + s.bytes + # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] strio = StringIO.new(s) - strio.getbyte # => 209 - strio.getbyte # => 130 + strio.getbyte # => 208 + strio.getbyte # => 159 s = 'こんにちは' - s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + s.bytes + # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] strio = StringIO.new(s) strio.getbyte # => 227 strio.getbyte # => 129 -Related: StringIO.getc. +Related: #each_byte, #ungetbyte, #getc. From 5bc65db5550719a808858af361d5152931469c88 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 6 Dec 2025 17:20:13 -0600 Subject: [PATCH 1749/2435] [ruby/stringio] [DOC] Tweaks for StringIO#gets (https://github.com/ruby/stringio/pull/190) https://github.com/ruby/stringio/commit/77209fac20 --- doc/stringio/gets.rdoc | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc index 892c3feb53a9bf..bbefeb008ae245 100644 --- a/doc/stringio/gets.rdoc +++ b/doc/stringio/gets.rdoc @@ -19,10 +19,10 @@ With no arguments given, reads a line using the default record separator strio.eof? # => true strio.gets # => nil - strio = StringIO.new('тест') # Four 2-byte characters. + strio = StringIO.new('Привет') # Six 2-byte characters strio.pos # => 0 - strio.gets # => "тест" - strio.pos # => 8 + strio.gets # => "Привет" + strio.pos # => 12 Argument +sep+ @@ -67,11 +67,11 @@ but in other cases the position may be anywhere: The position need not be at a character boundary: - strio = StringIO.new('тест') # Four 2-byte characters. - strio.pos = 2 # At beginning of second character. - strio.gets # => "ест" - strio.pos = 3 # In middle of second character. - strio.gets # => "\xB5ст" + strio = StringIO.new('Привет') # Six 2-byte characters. + strio.pos = 2 # At beginning of second character. + strio.gets # => "ривет" + strio.pos = 3 # In middle of second character. + strio.gets # => "\x80ивет" Special Record Separators @@ -95,4 +95,5 @@ removes the trailing newline (if any) from the returned line: strio.gets # => "First line\n" strio.gets(chomp: true) # => "Second line" -Related: StringIO.each_line. +Related: #each_line, #readlines, +{Kernel#puts}[rdoc-ref:Kernel#puts]. From b4a1f170583eb5553814261c311b183cbb390ba2 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 15 Nov 2025 07:47:46 -0600 Subject: [PATCH 1750/2435] [ruby/stringio] [DOC] Tweaks for StringIO#each_line (https://github.com/ruby/stringio/pull/165) Adds to "Position": pos inside a character. Makes a couple of minor corrections. --------- https://github.com/ruby/stringio/commit/ff332abafa Co-authored-by: Sutou Kouhei --- doc/stringio/each_line.md | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 doc/stringio/each_line.md diff --git a/doc/stringio/each_line.md b/doc/stringio/each_line.md new file mode 100644 index 00000000000000..e29640a12a9203 --- /dev/null +++ b/doc/stringio/each_line.md @@ -0,0 +1,189 @@ +With a block given calls the block with each remaining line (see "Position" below) in the stream; +returns `self`. + +Leaves stream position at end-of-stream. + +**No Arguments** + +With no arguments given, +reads lines using the default record separator +(global variable `$/`, whose initial value is `"\n"`). + +```ruby +strio = StringIO.new(TEXT) +strio.each_line {|line| p line } +strio.eof? # => true +``` + +Output: + +``` +"First line\n" +"Second line\n" +"\n" +"Fourth line\n" +"Fifth line\n" +``` + +**Argument `sep`** + +With only string argument `sep` given, +reads lines using that string as the record separator: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(' ') {|line| p line } +``` + +Output: + +``` +"First " +"line\nSecond " +"line\n\nFourth " +"line\nFifth " +"line\n" +``` + +**Argument `limit`** + +With only integer argument `limit` given, +reads lines using the default record separator; +also limits the size (in characters) of each line to the given limit: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(10) {|line| p line } +``` + +Output: + +``` +"First line" +"\n" +"Second lin" +"e\n" +"\n" +"Fourth lin" +"e\n" +"Fifth line" +"\n" +``` + +**Arguments `sep` and `limit`** + +With arguments `sep` and `limit` both given, +honors both: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(' ', 10) {|line| p line } +``` + +Output: + +``` +"First " +"line\nSecon" +"d " +"line\n\nFour" +"th " +"line\nFifth" +" " +"line\n" +``` + +**Position** + +As stated above, method `each` _remaining_ line in the stream. + +In the examples above each `strio` object starts with its position at beginning-of-stream; +but in other cases the position may be anywhere (see StringIO#pos): + +```ruby +strio = StringIO.new(TEXT) +strio.pos = 30 # Set stream position to character 30. +strio.each_line {|line| p line } +``` + +Output: + +``` +" line\n" +"Fifth line\n" +``` + +In all the examples above, the stream position is at the beginning of a character; +in other cases, that need not be so: + +```ruby +s = 'こんにちは' # Five 3-byte characters. +strio = StringIO.new(s) +strio.pos = 3 # At beginning of second character. +strio.each_line {|line| p line } +strio.pos = 4 # At second byte of second character. +strio.each_line {|line| p line } +strio.pos = 5 # At third byte of second character. +strio.each_line {|line| p line } +``` + +Output: + +``` +"んにちは" +"\x82\x93にちは" +"\x93にちは" +``` + +**Special Record Separators** + +Like some methods in class `IO`, StringIO.each honors two special record separators; +see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values]. + +```ruby +strio = StringIO.new(TEXT) +strio.each_line('') {|line| p line } # Read as paragraphs (separated by blank lines). +``` + +Output: + +``` +"First line\nSecond line\n\n" +"Fourth line\nFifth line\n" +``` + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(nil) {|line| p line } # "Slurp"; read it all. +``` + +Output: + +``` +"First line\nSecond line\n\nFourth line\nFifth line\n" +``` + +**Keyword Argument `chomp`** + +With keyword argument `chomp` given as `true` (the default is `false`), +removes trailing newline (if any) from each line: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(chomp: true) {|line| p line } +``` + +Output: + +``` +"First line" +"Second line" +"" +"Fourth line" +"Fifth line" +``` + +With no block given, returns a new {Enumerator}[https://docs.ruby-lang.org/en/master/Enumerator.html]. + + +Related: StringIO.each_byte, StringIO.each_char, StringIO.each_codepoint. From 6ec5c5f1c8ac438752b53061d0cdd68a7e4ca8f9 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 15 Nov 2025 07:48:35 -0600 Subject: [PATCH 1751/2435] [ruby/stringio] [DOC] Doc for StringIO.size (https://github.com/ruby/stringio/pull/171) https://github.com/ruby/stringio/commit/95a111017a --- doc/stringio/size.rdoc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/stringio/size.rdoc diff --git a/doc/stringio/size.rdoc b/doc/stringio/size.rdoc new file mode 100644 index 00000000000000..9323adf8c3783a --- /dev/null +++ b/doc/stringio/size.rdoc @@ -0,0 +1,5 @@ +Returns the number of bytes in the string in +self+: + + StringIO.new('hello').size # => 5 # Five 1-byte characters. + StringIO.new('тест').size # => 8 # Four 2-byte characters. + StringIO.new('こんにちは').size # => 15 # Five 3-byte characters. From 254653db8521618e08aaccaa63efdb42bd6ee84b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 15:38:52 +0900 Subject: [PATCH 1752/2435] [ruby/win32-registry] v0.1.2 https://github.com/ruby/win32-registry/commit/2a6ab00f67 --- ext/win32/win32-registry.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec index 9b65af4a5b9976..9bd57bd7d1368f 100644 --- a/ext/win32/win32-registry.gemspec +++ b/ext/win32/win32-registry.gemspec @@ -1,7 +1,7 @@ # frozen_string_literal: true Gem::Specification.new do |spec| spec.name = "win32-registry" - spec.version = "0.1.1" + spec.version = "0.1.2" spec.authors = ["U.Nakamura"] spec.email = ["usa@garbagecollect.jp"] From a8b7fb7ed6990df00514240a247ed9dd97bbbf8b Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 06:40:40 +0000 Subject: [PATCH 1753/2435] Update default gems list at 254653db8521618e08aaccaa63efdb [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 8c1d15142f71b4..769a9c53d66062 100644 --- a/NEWS.md +++ b/NEWS.md @@ -234,7 +234,7 @@ releases. The following default gem is added. -* win32-registry 0.1.1 +* win32-registry 0.1.2 The following default gems are updated. From 8e87f201cf54b112642ed0421ddabd57336d672e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 15:42:31 +0900 Subject: [PATCH 1754/2435] [ruby/optparse] v0.8.1 https://github.com/ruby/optparse/commit/f2e31e81a5 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index aae73c86b32787..97178e284bd53c 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -426,7 +426,7 @@ # class OptionParser # The version string - VERSION = "0.8.0" + VERSION = "0.8.1" # An alias for compatibility Version = VERSION From 492b1c73b35ab97d17d48ddd868e61cb76703dac Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 06:44:02 +0000 Subject: [PATCH 1755/2435] Update default gems list at 8e87f201cf54b112642ed0421ddabd [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 769a9c53d66062..6cf5c8a450a0e4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -254,7 +254,7 @@ The following default gems are updated. * json 2.17.1 * net-http 0.8.0 * openssl 4.0.0.pre -* optparse 0.8.0 +* optparse 0.8.1 * pp 0.6.3 * prism 1.6.0 * psych 5.3.0 From 81fbdff8fdf2ae7afb2fa19319ff7d40379521fe Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 24 Nov 2025 16:50:29 -0800 Subject: [PATCH 1756/2435] Use continuation bit in concurrent set This refactors the concurrent set to examine and reserve a slot via CAS with the hash, before then doing the same with the key. This allows us to use an extra bit from the hash as a "continuation bit" which marks whether we have ever probed past this key while inserting. When that bit isn't set on deletion we can clear the field instead of placing a tombstone. --- bootstraptest/test_ractor.rb | 3 + concurrent_set.c | 173 +++++++++++++++++++++++++---------- 2 files changed, 129 insertions(+), 47 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 81dc2d6b8d04be..13c4652d3760a8 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1494,6 +1494,9 @@ class C unless a[i].equal?(b[i]) raise [a[i], b[i]].inspect end + unless a[i] == i.to_s + raise [i, a[i], b[i]].inspect + end end :ok } diff --git a/concurrent_set.c b/concurrent_set.c index 3aa61507aaa7d2..eebf7df9cb6407 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -4,6 +4,9 @@ #include "ruby/atomic.h" #include "vm_sync.h" +#define CONCURRENT_SET_CONTINUATION_BIT ((VALUE)1 << (sizeof(VALUE) * CHAR_BIT - 1)) +#define CONCURRENT_SET_HASH_MASK (~CONCURRENT_SET_CONTINUATION_BIT) + enum concurrent_set_special_values { CONCURRENT_SET_EMPTY, CONCURRENT_SET_DELETED, @@ -24,6 +27,36 @@ struct concurrent_set { struct concurrent_set_entry *entries; }; +static void +concurrent_set_mark_continuation(struct concurrent_set_entry *entry, VALUE curr_hash_and_flags) +{ + if (curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT) return; + + RUBY_ASSERT((curr_hash_and_flags & CONCURRENT_SET_HASH_MASK) != 0); + + VALUE new_hash = curr_hash_and_flags | CONCURRENT_SET_CONTINUATION_BIT; + VALUE prev_hash = rbimpl_atomic_value_cas(&entry->hash, curr_hash_and_flags, new_hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + + // At the moment we only expect to be racing concurrently against another + // thread also setting the continuation bit. + // In the future if deletion is concurrent this will need adjusting + RUBY_ASSERT(prev_hash == curr_hash_and_flags || prev_hash == new_hash); + (void)prev_hash; +} + +static VALUE +concurrent_set_hash(const struct concurrent_set *set, VALUE key) +{ + VALUE hash = set->funcs->hash(key); + hash &= CONCURRENT_SET_HASH_MASK; + if (hash == 0) { + hash ^= CONCURRENT_SET_HASH_MASK; + } + RUBY_ASSERT(hash != 0); + RUBY_ASSERT(!(hash & CONCURRENT_SET_CONTINUATION_BIT)); + return hash; +} + static void concurrent_set_free(void *ptr) { @@ -141,13 +174,9 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue; if (!RB_SPECIAL_CONST_P(key) && rb_objspace_garbage_object_p(key)) continue; - VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - 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)); + VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK; + RUBY_ASSERT(hash != 0); + RUBY_ASSERT(hash == concurrent_set_hash(old_set, key)); // Insert key into new_set. struct concurrent_set_probe probe; @@ -156,20 +185,19 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) while (true) { struct concurrent_set_entry *entry = &new_set->entries[idx]; - if (entry->key == CONCURRENT_SET_EMPTY) { - new_set->size++; + if (entry->hash == CONCURRENT_SET_EMPTY) { + RUBY_ASSERT(entry->key == CONCURRENT_SET_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 >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); - } + RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); + entry->hash |= CONCURRENT_SET_CONTINUATION_BIT; idx = concurrent_set_probe_next(&probe); } } @@ -203,20 +231,37 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) if (hash == 0) { // We don't need to recompute the hash on every retry because it should // never change. - hash = set->funcs->hash(key); + hash = concurrent_set_hash(set, key); } - RUBY_ASSERT(hash == set->funcs->hash(key)); + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); struct concurrent_set_probe probe; int idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE); + VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK; + bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT; + + if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) { + return 0; + } + + if (curr_hash != hash) { + if (!continuation) { + return 0; + } + idx = concurrent_set_probe_next(&probe); + continue; + } + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { case CONCURRENT_SET_EMPTY: - return 0; + // In-progress insert: hash written but key not yet + break; case CONCURRENT_SET_DELETED: break; case CONCURRENT_SET_MOVED: @@ -225,13 +270,9 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) goto retry; default: { - VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - if (curr_hash != 0 && curr_hash != hash) break; - if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && 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. - rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + // Skip it and let the GC pass clean it up break; } @@ -241,6 +282,10 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) return curr_key; } + if (!continuation) { + return 0; + } + break; } } @@ -266,23 +311,49 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) if (hash == 0) { // We don't need to recompute the hash on every retry because it should // never change. - hash = set->funcs->hash(key); + hash = concurrent_set_hash(set, key); } - RUBY_ASSERT(hash == set->funcs->hash(key)); + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); struct concurrent_set_probe probe; int idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE); + VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK; + + if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) { + if (!inserting) { + key = set->funcs->create(key, data); + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); + inserting = true; + } + + // Reserve this slot for our hash value + curr_hash_and_flags = rbimpl_atomic_value_cas(&entry->hash, CONCURRENT_SET_EMPTY, hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + if (curr_hash_and_flags != CONCURRENT_SET_EMPTY) { + // Lost race, retry same slot to check winner's hash + continue; + } + + // CAS succeeded, so these are the values stored + curr_hash_and_flags = hash; + curr_hash = hash; + // Fall through to try to claim key + } + + if (curr_hash != hash) { + goto probe_next; + } + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { - case CONCURRENT_SET_EMPTY: { - // Not in set + case CONCURRENT_SET_EMPTY: if (!inserting) { key = set->funcs->create(key, data); - RUBY_ASSERT(hash == set->funcs->hash(key)); + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); inserting = true; } @@ -293,14 +364,11 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) if (UNLIKELY(load_factor_reached)) { concurrent_set_try_resize(set_obj, set_obj_ptr); - goto retry; } - curr_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); - if (curr_key == CONCURRENT_SET_EMPTY) { - rbimpl_atomic_value_store(&entry->hash, hash, RBIMPL_ATOMIC_RELAXED); - + VALUE prev_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + if (prev_key == CONCURRENT_SET_EMPTY) { RB_GC_GUARD(set_obj); return key; } @@ -311,22 +379,16 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) // Another thread won the race, try again at the same location. continue; } - } case CONCURRENT_SET_DELETED: break; case CONCURRENT_SET_MOVED: // Wait RB_VM_LOCKING(); - goto retry; - default: { - VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - if (curr_hash != 0 && curr_hash != hash) break; - + default: if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && 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. - rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + // Skip it and let the GC pass clean it up break; } @@ -343,15 +405,33 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) return curr_key; } - break; - } } + probe_next: + RUBY_ASSERT(curr_hash_and_flags != CONCURRENT_SET_EMPTY); + concurrent_set_mark_continuation(entry, curr_hash_and_flags); idx = concurrent_set_probe_next(&probe); } } +static void +concurrent_set_delete_entry_locked(struct concurrent_set *set, struct concurrent_set_entry *entry) +{ + ASSERT_vm_locking_with_barrier(); + + if (entry->hash & CONCURRENT_SET_CONTINUATION_BIT) { + entry->hash = CONCURRENT_SET_CONTINUATION_BIT; + entry->key = CONCURRENT_SET_DELETED; + set->deleted_entries++; + } + else { + entry->hash = CONCURRENT_SET_EMPTY; + entry->key = CONCURRENT_SET_EMPTY; + set->size--; + } +} + VALUE rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) { @@ -359,7 +439,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); - VALUE hash = set->funcs->hash(key); + VALUE hash = concurrent_set_hash(set, key); struct concurrent_set_probe probe; int idx = concurrent_set_probe_start(&probe, set, hash); @@ -379,8 +459,8 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) break; default: if (key == curr_key) { - entry->key = CONCURRENT_SET_DELETED; - set->deleted_entries++; + RUBY_ASSERT((entry->hash & CONCURRENT_SET_HASH_MASK) == hash); + concurrent_set_delete_entry_locked(set, entry); return curr_key; } break; @@ -399,7 +479,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key for (unsigned int i = 0; i < set->capacity; i++) { struct concurrent_set_entry *entry = &set->entries[i]; - VALUE key = set->entries[i].key; + VALUE key = entry->key; switch (key) { case CONCURRENT_SET_EMPTY: @@ -414,8 +494,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key case ST_STOP: return; case ST_DELETE: - set->entries[i].key = CONCURRENT_SET_DELETED; - set->deleted_entries++; + concurrent_set_delete_entry_locked(set, entry); break; } break; From 462df17f8689e9ee87a45b88bb3283fd339e1247 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 9 Dec 2025 02:17:38 -0800 Subject: [PATCH 1757/2435] Attempt to reuse garbage slots in concurrent hash This removes all allocations from the find_or_insert loop, which requires us to start the search over after calling the provided create function. In exchange that allows us to assume that all concurrent threads insert will get the same view of the GC state, and so should all be attempting to clear and reuse a slot containing a garbage object. --- concurrent_set.c | 85 ++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/concurrent_set.c b/concurrent_set.c index eebf7df9cb6407..376b20d7d4e45c 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -222,11 +222,14 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) VALUE set_obj; VALUE hash = 0; + struct concurrent_set *set; + struct concurrent_set_probe probe; + int idx; retry: set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); - struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + set = RTYPEDDATA_GET_DATA(set_obj); if (hash == 0) { // We don't need to recompute the hash on every retry because it should @@ -235,8 +238,7 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) } RUBY_ASSERT(hash == concurrent_set_hash(set, key)); - struct concurrent_set_probe probe; - int idx = concurrent_set_probe_start(&probe, set, hash); + idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; @@ -299,37 +301,43 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) { RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); - bool inserting = false; - VALUE set_obj; - VALUE hash = 0; + // First attempt to find + { + VALUE result = rb_concurrent_set_find(set_obj_ptr, key); + if (result) return result; + } - retry: - set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); + // First time we need to call create, and store the hash + VALUE set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); + struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + key = set->funcs->create(key, data); + VALUE hash = concurrent_set_hash(set, key); + + struct concurrent_set_probe probe; + int idx; + + goto start_search; + +retry: + // On retries we only need to load the hash object + set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); + RUBY_ASSERT(set_obj); + set = RTYPEDDATA_GET_DATA(set_obj); - if (hash == 0) { - // We don't need to recompute the hash on every retry because it should - // never change. - hash = concurrent_set_hash(set, key); - } RUBY_ASSERT(hash == concurrent_set_hash(set, key)); - struct concurrent_set_probe probe; - int idx = concurrent_set_probe_start(&probe, set, hash); +start_search: + idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE); VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK; + bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT; if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) { - if (!inserting) { - key = set->funcs->create(key, data); - RUBY_ASSERT(hash == concurrent_set_hash(set, key)); - inserting = true; - } - // Reserve this slot for our hash value curr_hash_and_flags = rbimpl_atomic_value_cas(&entry->hash, CONCURRENT_SET_EMPTY, hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); if (curr_hash_and_flags != CONCURRENT_SET_EMPTY) { @@ -340,6 +348,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) // CAS succeeded, so these are the values stored curr_hash_and_flags = hash; curr_hash = hash; + // Fall through to try to claim key } @@ -350,13 +359,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { - case CONCURRENT_SET_EMPTY: - if (!inserting) { - key = set->funcs->create(key, data); - RUBY_ASSERT(hash == concurrent_set_hash(set, key)); - inserting = true; - } - + case CONCURRENT_SET_EMPTY: { rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED); // Load_factor reached at 75% full. ex: prev_size: 32, capacity: 64, load_factor: 50%. @@ -369,6 +372,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) VALUE prev_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); if (prev_key == CONCURRENT_SET_EMPTY) { + RUBY_ASSERT(rb_concurrent_set_find(set_obj_ptr, key) == key); RB_GC_GUARD(set_obj); return key; } @@ -379,6 +383,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) // Another thread won the race, try again at the same location. continue; } + } case CONCURRENT_SET_DELETED: break; case CONCURRENT_SET_MOVED: @@ -386,22 +391,26 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) RB_VM_LOCKING(); goto retry; default: + // We're never GC during our search + // If the continuation bit wasn't set at the start of our search, + // any concurrent find with the same hash value would also look at + // this location and try to swap curr_key if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && 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 let the GC pass clean it up - break; + if (continuation) { + goto probe_next; + } + rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_EMPTY, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + continue; } if (set->funcs->cmp(key, curr_key)) { - // We've found a match. + // We've found a live match. RB_GC_GUARD(set_obj); - if (inserting) { - // We created key using set->funcs->create, but we didn't end - // up inserting it into the set. Free it here to prevent memory - // leaks. - if (set->funcs->free) set->funcs->free(key); - } + // We created key using set->funcs->create, but we didn't end + // up inserting it into the set. Free it here to prevent memory + // leaks. + if (set->funcs->free) set->funcs->free(key); return curr_key; } From 375025a3864fc944dc9f42909a6c5386c749d5fd Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 9 Dec 2025 15:42:04 -0800 Subject: [PATCH 1758/2435] Fix typo and shadowing --- concurrent_set.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/concurrent_set.c b/concurrent_set.c index 376b20d7d4e45c..234b6408b6b938 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -167,14 +167,14 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj); for (int i = 0; i < old_capacity; i++) { - struct concurrent_set_entry *entry = &old_entries[i]; - VALUE key = rbimpl_atomic_value_exchange(&entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE); + struct concurrent_set_entry *old_entry = &old_entries[i]; + VALUE key = rbimpl_atomic_value_exchange(&old_entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(key != CONCURRENT_SET_MOVED); if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue; if (!RB_SPECIAL_CONST_P(key) && rb_objspace_garbage_object_p(key)) continue; - VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK; + VALUE hash = rbimpl_atomic_value_load(&old_entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK; RUBY_ASSERT(hash != 0); RUBY_ASSERT(hash == concurrent_set_hash(old_set, key)); From 14ff851185bb8ff399e98b74cc107302a4e08e18 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 24 Nov 2025 08:48:19 +0100 Subject: [PATCH 1759/2435] [ruby/forwardable] Simpler and faster check for the delegation fastpath Fix: https://github.com/ruby/forwardable/issues/35 [Bug #21708] Trying to compile code to check if a method can use the delegation fastpath is a bit wasteful and cause `RUPYOPT=-d` to be full of misleading errors. It's simpler and faster to use a simple regexp to do the same check. https://github.com/ruby/forwardable/commit/de1fbd182e --- lib/forwardable.rb | 34 ++++++++++++----------------- lib/forwardable/forwardable.gemspec | 2 +- lib/forwardable/impl.rb | 17 --------------- 3 files changed, 15 insertions(+), 38 deletions(-) delete mode 100644 lib/forwardable/impl.rb diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 76267c2cd18c57..040f467b231a0d 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -109,8 +109,6 @@ # +delegate.rb+. # module Forwardable - require 'forwardable/impl' - # Version of +forwardable.rb+ VERSION = "1.3.3" VERSION.freeze @@ -206,37 +204,33 @@ def self._delegator_method(obj, accessor, method, ali) if Module === obj ? obj.method_defined?(accessor) || obj.private_method_defined?(accessor) : obj.respond_to?(accessor, true) - accessor = "#{accessor}()" + accessor = "(#{accessor}())" end args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' method_call = ".__send__(:#{method}, #{args})" - if _valid_method?(method) + if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/) loc, = caller_locations(2,1) pre = "_ =" mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method " - method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}" - begin; - unless defined? _.#{method} - ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 - _#{method_call} - else - _.#{method}(#{args}) - end - end; + method_call = <<~RUBY.chomp + if defined?(_.#{method}) + _.#{method}(#{args}) + else + ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 + _#{method_call} + end + RUBY end - _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) - begin; + eval(<<~RUBY, nil, __FILE__, __LINE__ + 1) proc do def #{ali}(#{args}) - #{pre} - begin - #{accessor} - end#{method_call} + #{pre}#{accessor} + #{method_call} end end - end; + RUBY end end diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec index 9ad59c5f8a8830..1b539bcfcb3daf 100644 --- a/lib/forwardable/forwardable.gemspec +++ b/lib/forwardable/forwardable.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.licenses = ["Ruby", "BSD-2-Clause"] spec.required_ruby_version = '>= 2.4.0' - spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"] + spec.files = ["forwardable.gemspec", "lib/forwardable.rb"] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb deleted file mode 100644 index 0322c136db4a64..00000000000000 --- a/lib/forwardable/impl.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Forwardable - # :stopdoc: - - def self._valid_method?(method) - catch {|tag| - eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__) - } - rescue SyntaxError - false - else - true - end - - def self._compile_method(src, file, line) - eval(src, nil, file, line) - end -end From e8a55274f202df1cfddc25aa14da34ba5a0e538d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 10 Dec 2025 16:07:17 +0900 Subject: [PATCH 1760/2435] [ruby/forwardable] v1.4.0 https://github.com/ruby/forwardable/commit/0257b590c2 --- lib/forwardable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 040f467b231a0d..175d6d9c6b6858 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -110,7 +110,7 @@ # module Forwardable # Version of +forwardable.rb+ - VERSION = "1.3.3" + VERSION = "1.4.0" VERSION.freeze # Version for backward compatibility From ef4490d6b166fc1fd802d58faceeb038737afc7e Mon Sep 17 00:00:00 2001 From: git Date: Wed, 10 Dec 2025 07:09:05 +0000 Subject: [PATCH 1761/2435] Update default gems list at e8a55274f202df1cfddc25aa14da34 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 6cf5c8a450a0e4..e7d1da919e631d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -247,6 +247,7 @@ The following default gems are updated. * etc 1.4.6 * fcntl 1.3.0 * fileutils 1.8.0 +* forwardable 1.4.0 * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.4.0.dev From c5608ab4d79f409aaa469b4f4a20962a0ba4a688 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 3 Dec 2025 15:35:03 +0100 Subject: [PATCH 1762/2435] Monitor: avoid repeated calls to `rb_fiber_current()` That call is surprisingly expensive, so trying doing it once in `#synchronize` and then passing the fiber to enter and exit saves quite a few cycles. --- ext/monitor/monitor.c | 103 +++++++++++++++++++++++++++++------------- thread_sync.c | 51 ++++++++++----------- 2 files changed, 95 insertions(+), 59 deletions(-) diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 819c6e76996f61..3106c0302378f1 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -50,10 +50,10 @@ monitor_ptr(VALUE monitor) return mc; } -static int -mc_owner_p(struct rb_monitor *mc) +static bool +mc_owner_p(struct rb_monitor *mc, VALUE current_fiber) { - return mc->owner == rb_fiber_current(); + return mc->owner == current_fiber; } /* @@ -67,17 +67,44 @@ monitor_try_enter(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { + VALUE current_fiber = rb_fiber_current(); + if (!mc_owner_p(mc, current_fiber)) { if (!rb_mutex_trylock(mc->mutex)) { return Qfalse; } - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); + RB_OBJ_WRITE(monitor, &mc->owner, current_fiber); mc->count = 0; } mc->count += 1; return Qtrue; } + +struct monitor_args { + VALUE monitor; + struct rb_monitor *mc; + VALUE current_fiber; +}; + +static inline void +monitor_args_init(struct monitor_args *args, VALUE monitor) +{ + args->monitor = monitor; + args->mc = monitor_ptr(monitor); + args->current_fiber = rb_fiber_current(); +} + +static void +monitor_enter0(struct monitor_args *args) +{ + if (!mc_owner_p(args->mc, args->current_fiber)) { + rb_mutex_lock(args->mc->mutex); + RB_OBJ_WRITE(args->monitor, &args->mc->owner, args->current_fiber); + args->mc->count = 0; + } + args->mc->count++; +} + /* * call-seq: * enter -> nil @@ -87,27 +114,44 @@ monitor_try_enter(VALUE monitor) static VALUE monitor_enter(VALUE monitor) { - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); - mc->count = 0; - } - mc->count++; + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_enter0(&args); return Qnil; } +static inline void +monitor_check_owner0(struct monitor_args *args) +{ + if (!mc_owner_p(args->mc, args->current_fiber)) { + rb_raise(rb_eThreadError, "current fiber not owner"); + } +} + /* :nodoc: */ static VALUE monitor_check_owner(VALUE monitor) { - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current fiber not owner"); - } + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_check_owner0(&args); return Qnil; } +static void +monitor_exit0(struct monitor_args *args) +{ + monitor_check_owner(args->monitor); + + if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count); + args->mc->count--; + + if (args->mc->count == 0) { + RB_OBJ_WRITE(args->monitor, &args->mc->owner, Qnil); + rb_mutex_unlock(args->mc->mutex); + } +} + /* * call-seq: * exit -> nil @@ -117,17 +161,9 @@ monitor_check_owner(VALUE monitor) static VALUE monitor_exit(VALUE monitor) { - monitor_check_owner(monitor); - - struct rb_monitor *mc = monitor_ptr(monitor); - - if (mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)mc->count); - mc->count--; - - if (mc->count == 0) { - RB_OBJ_WRITE(monitor, &mc->owner, Qnil); - rb_mutex_unlock(mc->mutex); - } + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_exit0(&args); return Qnil; } @@ -144,7 +180,7 @@ static VALUE monitor_owned_p(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); - return (rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc)) ? Qtrue : Qfalse; + return rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_fiber_current()) ? Qtrue : Qfalse; } static VALUE @@ -210,9 +246,10 @@ monitor_sync_body(VALUE monitor) } static VALUE -monitor_sync_ensure(VALUE monitor) +monitor_sync_ensure(VALUE v_args) { - return monitor_exit(monitor); + monitor_exit0((struct monitor_args *)v_args); + return Qnil; } /* @@ -226,8 +263,10 @@ monitor_sync_ensure(VALUE monitor) static VALUE monitor_synchronize(VALUE monitor) { - monitor_enter(monitor); - return rb_ensure(monitor_sync_body, monitor, monitor_sync_ensure, monitor); + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_enter0(&args); + return rb_ensure(monitor_sync_body, (VALUE)&args, monitor_sync_ensure, (VALUE)&args); } void diff --git a/thread_sync.c b/thread_sync.c index 8967e24e341bad..9fb1639e9b7dd2 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -239,23 +239,34 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) } static void -mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber) +mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) { - rb_mutex_t *mutex = mutex_ptr(self); - mutex->thread = th->self; mutex->fiber_serial = rb_fiber_serial(fiber); } static void -mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self) +mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) { - rb_mutex_t *mutex = mutex_ptr(self); - - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); thread_mutex_insert(th, mutex); } +static inline bool +mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +{ + if (mutex->fiber_serial == 0) { + RUBY_DEBUG_LOG("%p ok", mutex); + + mutex_locked(mutex, th, fiber); + return true; + } + else { + RUBY_DEBUG_LOG("%p ng", mutex); + return false; + } +} + /* * call-seq: * mutex.try_lock -> true or false @@ -266,21 +277,7 @@ mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self) VALUE rb_mutex_trylock(VALUE self) { - rb_mutex_t *mutex = mutex_ptr(self); - - if (mutex->fiber_serial == 0) { - RUBY_DEBUG_LOG("%p ok", mutex); - - rb_fiber_t *fiber = GET_EC()->fiber_ptr; - rb_thread_t *th = GET_THREAD(); - - mutex_locked(th, fiber, self); - return Qtrue; - } - else { - RUBY_DEBUG_LOG("%p ng", mutex); - return Qfalse; - } + return RBOOL(mutex_trylock(mutex_ptr(self), GET_THREAD(), GET_EC()->fiber_ptr)); } static VALUE @@ -321,7 +318,7 @@ do_mutex_lock(VALUE self, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (rb_mutex_trylock(self) == Qfalse) { + if (!mutex_trylock(mutex, th, fiber)) { if (mutex->fiber_serial == rb_fiber_serial(fiber)) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } @@ -342,7 +339,7 @@ do_mutex_lock(VALUE self, int interruptible_p) rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter); if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } } else { @@ -383,7 +380,7 @@ do_mutex_lock(VALUE self, int interruptible_p) // unlocked by another thread while sleeping if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } rb_ractor_sleeper_threads_dec(th->ractor); @@ -402,7 +399,7 @@ do_mutex_lock(VALUE self, int interruptible_p) } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } } else { @@ -421,7 +418,7 @@ do_mutex_lock(VALUE self, int interruptible_p) } if (saved_ints) th->ec->interrupt_flag = saved_ints; - if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(th, fiber, self); + if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(mutex, th, fiber); } RUBY_DEBUG_LOG("%p locked", mutex); From 6777d1012d24b4dd3585809030333ccfb1c46799 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 10 Dec 2025 10:35:37 +0100 Subject: [PATCH 1763/2435] Modernize Monitor TypedData Make it embedded and compaction aware. --- ext/monitor/monitor.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 3106c0302378f1..aeb96d7701242d 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -4,28 +4,35 @@ struct rb_monitor { long count; - const VALUE owner; - const VALUE mutex; + VALUE owner; + VALUE mutex; }; static void monitor_mark(void *ptr) { struct rb_monitor *mc = ptr; - rb_gc_mark(mc->owner); - rb_gc_mark(mc->mutex); + rb_gc_mark_movable(mc->owner); + rb_gc_mark_movable(mc->mutex); } -static size_t -monitor_memsize(const void *ptr) +static void +monitor_compact(void *ptr) { - return sizeof(struct rb_monitor); + struct rb_monitor *mc = ptr; + mc->owner = rb_gc_location(mc->owner); + mc->mutex = rb_gc_location(mc->mutex); } static const rb_data_type_t monitor_data_type = { - "monitor", - {monitor_mark, RUBY_TYPED_DEFAULT_FREE, monitor_memsize,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "monitor", + .function = { + .dmark = monitor_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = NULL, // Fully embeded + .dcompact = monitor_compact, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; static VALUE From 023c6d808a85bd8fb711ac3986972618037f12ad Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 10 Dec 2025 10:54:18 +0100 Subject: [PATCH 1764/2435] [ruby/json] Add a specific error for unescaped newlines It's the most likely control character so it's worth giving a better error message for it. https://github.com/ruby/json/commit/1da3fd9233 --- ext/json/parser/parser.c | 3 +++ test/json/json_parser_test.rb | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index c84c7ed660a06d..45de8d1ff62f1d 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -752,6 +752,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser break; default: if ((unsigned char)*pe < 0x20) { + if (*pe == '\n') { + raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + } raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); } raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 257e4f1736a2d0..3f0fb7522dc671 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -164,6 +164,14 @@ def test_parse_complex_objects end end + def test_parse_control_chars_in_string + 0.upto(31) do |ord| + assert_raise JSON::ParserError do + parse(%("#{ord.chr}")) + end + end + end + def test_parse_arrays assert_equal([1,2,3], parse('[1,2,3]')) assert_equal([1.2,2,3], parse('[1.2,2,3]')) From 2b66fc763a6657c9a25719c5f70ae7b66abc2232 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 10 Dec 2025 12:42:33 +0100 Subject: [PATCH 1765/2435] Fix typos in comment of rb_current_execution_context() --- vm_core.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm_core.h b/vm_core.h index 0a1914f57a955b..4195ea5e59c9a4 100644 --- a/vm_core.h +++ b/vm_core.h @@ -2084,9 +2084,9 @@ rb_current_execution_context(bool expect_ec) * and the address of the `ruby_current_ec` can be stored on a function * frame. However, this address can be mis-used after native thread * migration of a coroutine. - * 1) Get `ptr =&ruby_current_ec` op NT1 and store it on the frame. + * 1) Get `ptr = &ruby_current_ec` on NT1 and store it on the frame. * 2) Context switch and resume it on the NT2. - * 3) `ptr` is used on NT2 but it accesses to the TLS on NT1. + * 3) `ptr` is used on NT2 but it accesses the TLS of NT1. * This assertion checks such misusage. * * To avoid accidents, `GET_EC()` should be called once on the frame. From ed18a212abe2d92feb441db2b6f084c0c77a65e4 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 10 Dec 2025 11:08:55 -0500 Subject: [PATCH 1766/2435] ZJIT: Check if shape is too complex before reading ivar by index (#15478) This fixes a crash when the new shape after a transition is too complex; we need to check that it's not complex before trying to read by index. --- zjit/src/hir.rs | 4 ++-- zjit/src/hir/opt_tests.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4e597db936c345..1771f960c3f952 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3155,8 +3155,6 @@ impl Function { // We're only looking at T_OBJECT so ignore all of the imemo stuff. assert!(recv_type.flags().is_t_object()); next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(class, current_shape_id.0, id) }); - let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) }; - assert!(ivar_result, "New shape must have the ivar index"); // If the VM ran out of shapes, or this class generated too many leaf, // it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table). let new_shape_too_complex = unsafe { rb_jit_shape_too_complex_p(next_shape_id.0) }; @@ -3165,6 +3163,8 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_new_shape_too_complex)); self.push_insn_id(block, insn_id); continue; } + let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) }; + assert!(ivar_result, "New shape must have the ivar index"); let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) }; let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) }; // If the new shape has a different capacity, or is TOO_COMPLEX, we'll have to diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 3e2db528158090..2e20c8fe8a73e2 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3932,6 +3932,38 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_specialize_setivar_when_next_shape_is_too_complex() { + eval(r#" + class AboutToBeTooComplex + def test = @abc = 5 + end + SHAPE_MAX_VARIATIONS = 8 # see shape.h + SHAPE_MAX_VARIATIONS.times do + AboutToBeTooComplex.new.instance_variable_set(:"@a#{_1}", 1) + end + AboutToBeTooComplex.new.test + TEST = AboutToBeTooComplex.instance_method(:test) + "#); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + IncrCounter setivar_fallback_new_shape_too_complex + SetIvar v6, :@abc, v10 + CheckInterrupts + Return v10 + "); + } + #[test] fn test_elide_freeze_with_frozen_hash() { eval(" From 1eb10ca3cb6cff98bb8c0946ed905921586c7d52 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 10 Dec 2025 09:45:09 -0800 Subject: [PATCH 1767/2435] ZJIT: Exclude failing ruby-bench benchmarks (#15479) --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index ed559d64e10d05..d6194825a445a7 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1' + bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 37a9000d704c5a..64bb0903f8ae7c 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1' + bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 From 41ee65899a7a6c6abca9701957bc84f598d2491a Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 8 Dec 2025 15:54:26 -0800 Subject: [PATCH 1768/2435] Always treat encoding as TYPEDDATA Encodings are RTypedData, not the deprecated RData. Although the structures are compatible we should use the correct API. --- encoding.c | 12 ++++++------ ext/-test-/string/fstring.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/encoding.c b/encoding.c index 3d5c1d777283fe..d14d371b04c319 100644 --- a/encoding.c +++ b/encoding.c @@ -222,7 +222,7 @@ enc_check_encoding(VALUE obj) if (!is_obj_encoding(obj)) { return -1; } - return check_encoding(RDATA(obj)->data); + return check_encoding(RTYPEDDATA_GET_DATA(obj)); } NORETURN(static void not_encoding(VALUE enc)); @@ -240,7 +240,7 @@ must_encoding(VALUE enc) if (index < 0) { not_encoding(enc); } - return DATA_PTR(enc); + return RTYPEDDATA_GET_DATA(enc); } static rb_encoding * @@ -328,7 +328,7 @@ str_to_encoding(VALUE enc) rb_encoding * rb_to_encoding(VALUE enc) { - if (enc_check_encoding(enc) >= 0) return RDATA(enc)->data; + if (enc_check_encoding(enc) >= 0) return RTYPEDDATA_GET_DATA(enc); return str_to_encoding(enc); } @@ -336,7 +336,7 @@ rb_encoding * rb_find_encoding(VALUE enc) { int idx; - if (enc_check_encoding(enc) >= 0) return RDATA(enc)->data; + if (enc_check_encoding(enc) >= 0) return RTYPEDDATA_GET_DATA(enc); idx = str_find_encindex(enc); if (idx < 0) return NULL; return rb_enc_from_index(idx); @@ -1345,7 +1345,7 @@ enc_inspect(VALUE self) if (!is_data_encoding(self)) { not_encoding(self); } - if (!(enc = DATA_PTR(self)) || rb_enc_from_index(rb_enc_to_index(enc)) != enc) { + if (!(enc = RTYPEDDATA_GET_DATA(self)) || rb_enc_from_index(rb_enc_to_index(enc)) != enc) { rb_raise(rb_eTypeError, "broken Encoding"); } @@ -1368,7 +1368,7 @@ enc_inspect(VALUE self) static VALUE enc_name(VALUE self) { - return rb_fstring_cstr(rb_enc_name((rb_encoding*)DATA_PTR(self))); + return rb_fstring_cstr(rb_enc_name((rb_encoding*)RTYPEDDATA_GET_DATA(self))); } static int diff --git a/ext/-test-/string/fstring.c b/ext/-test-/string/fstring.c index 92a846a93c2e5c..0b5940f28c46ca 100644 --- a/ext/-test-/string/fstring.c +++ b/ext/-test-/string/fstring.c @@ -19,13 +19,13 @@ bug_s_fstring_fake_str(VALUE self) VALUE bug_s_rb_enc_interned_str(VALUE self, VALUE encoding) { - return rb_enc_interned_str("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); + return rb_enc_interned_str("foo", 3, NIL_P(encoding) ? NULL : RTYPEDDATA_GET_DATA(encoding)); } VALUE bug_s_rb_enc_str_new(VALUE self, VALUE encoding) { - return rb_enc_str_new("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); + return rb_enc_str_new("foo", 3, NIL_P(encoding) ? NULL : RTYPEDDATA_GET_DATA(encoding)); } void From 330ddccfee1c986add758384b31b0677935bfa3a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 10 Dec 2025 10:10:54 -0800 Subject: [PATCH 1769/2435] ubuntu.yml: Add a ruby-bench job without ZJIT (#15480) --- .github/workflows/ubuntu.yml | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 9f3ad412f7ba6c..46ad7c82128e23 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -202,6 +202,56 @@ jobs: steps: *make-steps + # Separated from `make` job to avoid making it a required status check + ruby-bench: + strategy: + matrix: + include: + # Using the same setup as ZJIT jobs + - bench_opts: '--warmup=1 --bench=1' + + runs-on: ubuntu-24.04 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') + )}} + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - uses: ./.github/actions/setup/ubuntu + + - uses: ./.github/actions/setup/directories + with: + srcdir: src + builddir: build + makeup: true + + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix="$(pwd)/install" + + - run: make install + + - name: Checkout ruby-bench + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + repository: ruby/ruby-bench + path: ruby-bench + + - name: Run ruby-bench + run: ruby run_benchmarks.rb -e "ruby::../build/install/bin/ruby" ${{ matrix.bench_opts }} + working-directory: ruby-bench + + - uses: ./.github/actions/slack + with: + label: ruby-bench ${{ matrix.bench_opts }} ${{ matrix.ruby_opts }} + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} + result: if: ${{ always() }} name: ${{ github.workflow }} result From 3640cfe5e0f3ed30f90ae622ef92e6c154213a09 Mon Sep 17 00:00:00 2001 From: Alex Rocha <9896751+alexcrocha@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:30:04 -0800 Subject: [PATCH 1770/2435] ZJIT: Use inline format args (#15482) --- zjit/src/asm/arm64/arg/sf.rs | 2 +- zjit/src/asm/arm64/inst/atomic.rs | 2 +- zjit/src/asm/arm64/inst/load_literal.rs | 2 +- zjit/src/asm/arm64/inst/load_register.rs | 2 +- zjit/src/asm/arm64/inst/load_store.rs | 2 +- .../asm/arm64/inst/load_store_exclusive.rs | 2 +- zjit/src/asm/arm64/inst/mov.rs | 2 +- zjit/src/asm/arm64/inst/reg_pair.rs | 2 +- zjit/src/asm/arm64/inst/test_bit.rs | 2 +- zjit/src/asm/arm64/mod.rs | 2 +- zjit/src/asm/mod.rs | 2 +- zjit/src/backend/arm64/mod.rs | 4 ++-- zjit/src/codegen.rs | 4 ++-- zjit/src/cruby.rs | 2 +- zjit/src/hir.rs | 18 ++++++++--------- zjit/src/hir_type/mod.rs | 2 +- zjit/src/json.rs | 20 +++++++++---------- zjit/src/options.rs | 10 +++++----- zjit/src/state.rs | 2 +- zjit/src/stats.rs | 6 +++--- 20 files changed, 45 insertions(+), 45 deletions(-) diff --git a/zjit/src/asm/arm64/arg/sf.rs b/zjit/src/asm/arm64/arg/sf.rs index c2fd33302c1ef8..b6091821e906fb 100644 --- a/zjit/src/asm/arm64/arg/sf.rs +++ b/zjit/src/asm/arm64/arg/sf.rs @@ -13,7 +13,7 @@ impl From for Sf { match num_bits { 64 => Sf::Sf64, 32 => Sf::Sf32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/atomic.rs b/zjit/src/asm/arm64/inst/atomic.rs index dce9affedf8838..0917a4fd1c17ea 100644 --- a/zjit/src/asm/arm64/inst/atomic.rs +++ b/zjit/src/asm/arm64/inst/atomic.rs @@ -14,7 +14,7 @@ impl From for Size { match num_bits { 64 => Size::Size64, 32 => Size::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/load_literal.rs b/zjit/src/asm/arm64/inst/load_literal.rs index a429c0fb53d082..37b5f3c7a7546b 100644 --- a/zjit/src/asm/arm64/inst/load_literal.rs +++ b/zjit/src/asm/arm64/inst/load_literal.rs @@ -15,7 +15,7 @@ impl From for Opc { match num_bits { 64 => Opc::Size64, 32 => Opc::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/load_register.rs b/zjit/src/asm/arm64/inst/load_register.rs index 3d94e8da1f8f26..80813ffc870a42 100644 --- a/zjit/src/asm/arm64/inst/load_register.rs +++ b/zjit/src/asm/arm64/inst/load_register.rs @@ -25,7 +25,7 @@ impl From for Size { match num_bits { 64 => Size::Size64, 32 => Size::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/load_store.rs b/zjit/src/asm/arm64/inst/load_store.rs index e78edd56752aae..d38e851ed73e30 100644 --- a/zjit/src/asm/arm64/inst/load_store.rs +++ b/zjit/src/asm/arm64/inst/load_store.rs @@ -15,7 +15,7 @@ impl From for Size { match num_bits { 64 => Size::Size64, 32 => Size::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/load_store_exclusive.rs b/zjit/src/asm/arm64/inst/load_store_exclusive.rs index 1106b4cb372eb1..30cb663bdb9c67 100644 --- a/zjit/src/asm/arm64/inst/load_store_exclusive.rs +++ b/zjit/src/asm/arm64/inst/load_store_exclusive.rs @@ -17,7 +17,7 @@ impl From for Size { match num_bits { 64 => Size::Size64, 32 => Size::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/mov.rs b/zjit/src/asm/arm64/inst/mov.rs index 70814938056f15..e9f9091713107b 100644 --- a/zjit/src/asm/arm64/inst/mov.rs +++ b/zjit/src/asm/arm64/inst/mov.rs @@ -27,7 +27,7 @@ impl From for Hw { 16 => Hw::LSL16, 32 => Hw::LSL32, 48 => Hw::LSL48, - _ => panic!("Invalid value for shift: {}", shift) + _ => panic!("Invalid value for shift: {shift}"), } } } diff --git a/zjit/src/asm/arm64/inst/reg_pair.rs b/zjit/src/asm/arm64/inst/reg_pair.rs index 9bffcd847925c1..39a44c24167370 100644 --- a/zjit/src/asm/arm64/inst/reg_pair.rs +++ b/zjit/src/asm/arm64/inst/reg_pair.rs @@ -26,7 +26,7 @@ impl From for Opc { match num_bits { 64 => Opc::Opc64, 32 => Opc::Opc32, - _ => panic!("Invalid number of bits: {}", num_bits) + _ => panic!("Invalid number of bits: {num_bits}"), } } } diff --git a/zjit/src/asm/arm64/inst/test_bit.rs b/zjit/src/asm/arm64/inst/test_bit.rs index f7aeca70fda8af..45f0c2317ec974 100644 --- a/zjit/src/asm/arm64/inst/test_bit.rs +++ b/zjit/src/asm/arm64/inst/test_bit.rs @@ -17,7 +17,7 @@ impl From for B5 { match bit_num { 0..=31 => B5::B532, 32..=63 => B5::B564, - _ => panic!("Invalid bit number: {}", bit_num) + _ => panic!("Invalid bit number: {bit_num}"), } } } diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index 4094d101fbc32c..c30520cd693f14 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -241,7 +241,7 @@ pub fn asr(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, shift: A64Opnd) { SBFM::asr(rd.reg_no, rn.reg_no, shift.try_into().unwrap(), rd.num_bits).into() }, - _ => panic!("Invalid operand combination to asr instruction: asr {:?}, {:?}, {:?}", rd, rn, shift), + _ => panic!("Invalid operand combination to asr instruction: asr {rd:?}, {rn:?}, {shift:?}"), }; cb.write_bytes(&bytes); diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index 9b792f5f376325..90496245295f88 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -354,7 +354,7 @@ impl fmt::LowerHex for CodeBlock { for pos in 0..self.write_pos { let mem_block = &*self.mem_block.borrow(); let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() }; - fmtr.write_fmt(format_args!("{:02x}", byte))?; + fmtr.write_fmt(format_args!("{byte:02x}"))?; } Ok(()) } diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 78fb69b0b04d5b..f927f261cf39b8 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1311,7 +1311,7 @@ impl Assembler { 64 | 32 => stur(cb, src, dest.into()), 16 => sturh(cb, src, dest.into()), 8 => sturb(cb, src, dest.into()), - num_bits => panic!("unexpected dest num_bits: {} (src: {:?}, dest: {:?})", num_bits, src, dest), + num_bits => panic!("unexpected dest num_bits: {num_bits} (src: {src:?}, dest: {dest:?})"), } }, Insn::Load { opnd, out } | @@ -1331,7 +1331,7 @@ impl Assembler { 64 | 32 => ldur(cb, out.into(), opnd.into()), 16 => ldurh(cb, out.into(), opnd.into()), 8 => ldurb(cb, out.into(), opnd.into()), - num_bits => panic!("unexpected num_bits: {}", num_bits) + num_bits => panic!("unexpected num_bits: {num_bits}"), }; }, Opnd::Value(value) => { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 05b704d66a4bb4..11b60c546c9716 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -160,7 +160,7 @@ fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) { return; }; let mut file = std::io::BufWriter::new(file); - let Ok(_) = writeln!(file, "{:#x} {:#x} zjit::{}", start_ptr, code_size, iseq_name) else { + let Ok(_) = writeln!(file, "{start_ptr:#x} {code_size:#x} zjit::{iseq_name}") else { debug!("Failed to write {iseq_name} to perf map file: {perf_map}"); return; }; @@ -2019,7 +2019,7 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, let expected_opnd: Opnd = match expected { crate::hir::Const::Value(v) => { Opnd::Value(v) } crate::hir::Const::CInt64(v) => { v.into() } - _ => panic!("gen_guard_bit_equals: unexpected hir::Const {:?}", expected), + _ => panic!("gen_guard_bit_equals: unexpected hir::Const {expected:?}"), }; asm.cmp(val, expected_opnd); asm.jnz(side_exit(jit, state, GuardBitEquals(expected))); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index e653602874df77..2bd44d21443b8d 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -986,7 +986,7 @@ pub fn rb_bug_panic_hook() { // You may also use ZJIT_RB_BUG=1 to trigger this on dev builds. if release_build || env::var("ZJIT_RB_BUG").is_ok() { // Abort with rb_bug(). It has a length limit on the message. - let panic_message = &format!("{}", panic_info)[..]; + let panic_message = &format!("{panic_info}")[..]; let len = std::cmp::min(0x100, panic_message.len()) as c_int; unsafe { rb_bug(b"ZJIT: %*s\0".as_ref().as_ptr() as *const c_char, len, panic_message.as_ptr()); } } else { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1771f960c3f952..1731667442792b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -176,7 +176,7 @@ impl From for SpecialObjectType { VM_SPECIAL_OBJECT_VMCORE => SpecialObjectType::VMCore, VM_SPECIAL_OBJECT_CBASE => SpecialObjectType::CBase, VM_SPECIAL_OBJECT_CONST_BASE => SpecialObjectType::ConstBase, - _ => panic!("Invalid special object type: {}", value), + _ => panic!("Invalid special object type: {value}"), } } } @@ -337,7 +337,7 @@ impl std::fmt::Display for RangeType { impl std::fmt::Debug for RangeType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self) + write!(f, "{self}") } } @@ -346,7 +346,7 @@ impl From for RangeType { match flag { 0 => RangeType::Inclusive, 1 => RangeType::Exclusive, - _ => panic!("Invalid range flag: {}", flag), + _ => panic!("Invalid range flag: {flag}"), } } } @@ -369,7 +369,7 @@ impl TryFrom for SpecialBackrefSymbol { '`' => Ok(SpecialBackrefSymbol::PreMatch), '\'' => Ok(SpecialBackrefSymbol::PostMatch), '+' => Ok(SpecialBackrefSymbol::LastGroup), - c => Err(format!("invalid backref symbol: '{}'", c)), + c => Err(format!("invalid backref symbol: '{c}'")), } } } @@ -574,7 +574,7 @@ impl std::fmt::Display for SideExitReason { SideExitReason::UnhandledNewarraySend(VM_OPT_NEWARRAY_SEND_PACK) => write!(f, "UnhandledNewarraySend(PACK)"), SideExitReason::UnhandledNewarraySend(VM_OPT_NEWARRAY_SEND_PACK_BUFFER) => write!(f, "UnhandledNewarraySend(PACK_BUFFER)"), SideExitReason::UnhandledNewarraySend(VM_OPT_NEWARRAY_SEND_INCLUDE_P) => write!(f, "UnhandledNewarraySend(INCLUDE_P)"), - SideExitReason::UnhandledDuparraySend(method_id) => write!(f, "UnhandledDuparraySend({})", method_id), + SideExitReason::UnhandledDuparraySend(method_id) => write!(f, "UnhandledDuparraySend({method_id})"), SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"), SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"), SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())), @@ -3572,10 +3572,10 @@ impl Function { // rb_zjit_singleton_class_p also checks if it's a class if unsafe { rb_zjit_singleton_class_p(class) } { let class_name = get_class_name(unsafe { rb_class_attached_object(class) }); - format!("{}.{}", class_name, method_name) + format!("{class_name}.{method_name}") } else { let class_name = get_class_name(class); - format!("{}#{}", class_name, method_name) + format!("{class_name}#{method_name}") } } @@ -4372,7 +4372,7 @@ impl Function { .insert("name", function_name) .insert("passes", passes) .build(); - writeln!(file, "{}", json).unwrap(); + writeln!(file, "{json}").unwrap(); } /// Validates the following: @@ -4500,7 +4500,7 @@ impl Function { fn assert_subtype(&self, user: InsnId, operand: InsnId, expected: Type) -> Result<(), ValidationError> { let actual = self.type_of(operand); if !actual.is_subtype(expected) { - return Err(ValidationError::MismatchedOperandType(user, operand, format!("{}", expected), format!("{}", actual))); + return Err(ValidationError::MismatchedOperandType(user, operand, format!("{expected}"), format!("{actual}"))); } Ok(()) } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index a7ffcd1b77e882..206d94f42bdb64 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -96,7 +96,7 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val & u8::MAX as u64), Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val & u16::MAX as u64), Specialization::Int(val) if ty.is_subtype(types::CUInt32) => write!(f, "[{}]", val & u32::MAX as u64), - Specialization::Int(val) if ty.is_subtype(types::CUInt64) => write!(f, "[{}]", val), + Specialization::Int(val) if ty.is_subtype(types::CUInt64) => write!(f, "[{val}]"), Specialization::Int(val) if ty.is_subtype(types::CPtr) => write!(f, "[{}]", Const::CPtr(val as *const u8).print(printer.ptr_map)), Specialization::Int(val) => write!(f, "[{val}]"), Specialization::Double(val) => write!(f, "[{val}]"), diff --git a/zjit/src/json.rs b/zjit/src/json.rs index 1e63ba7b425902..fa4b2168217868 100644 --- a/zjit/src/json.rs +++ b/zjit/src/json.rs @@ -43,9 +43,9 @@ impl Json { match self { Json::Null => writer.write_all(b"null"), Json::Bool(b) => writer.write_all(if *b { b"true" } else { b"false" }), - Json::Integer(i) => write!(writer, "{}", i), - Json::UnsignedInteger(u) => write!(writer, "{}", u), - Json::Floating(f) => write!(writer, "{}", f), + Json::Integer(i) => write!(writer, "{i}"), + Json::UnsignedInteger(u) => write!(writer, "{u}"), + Json::Floating(f) => write!(writer, "{f}"), Json::String(s) => return Self::write_str(writer, s), Json::Array(jsons) => return Self::write_array(writer, jsons), Json::Object(map) => return Self::write_object(writer, map), @@ -69,9 +69,9 @@ impl Json { '\x0C' => write!(writer, "\\f")?, ch if ch.is_control() => { let code_point = ch as u32; - write!(writer, "\\u{:04X}", code_point)? + write!(writer, "\\u{code_point:04X}")? } - _ => write!(writer, "{}", ch)?, + _ => write!(writer, "{ch}")?, }; } @@ -83,7 +83,7 @@ impl Json { writer.write_all(b"[")?; let mut prefix = ""; for item in jsons { - write!(writer, "{}", prefix)?; + write!(writer, "{prefix}")?; item.marshal(writer)?; prefix = ", "; } @@ -96,7 +96,7 @@ impl Json { let mut prefix = ""; for (k, v) in pairs { // Escape the keys, despite not being `Json::String` objects. - write!(writer, "{}", prefix)?; + write!(writer, "{prefix}")?; Self::write_str(writer, k)?; writer.write_all(b":")?; v.marshal(writer)?; @@ -112,7 +112,7 @@ impl std::fmt::Display for Json { let mut buf = Vec::new(); self.marshal(&mut buf).map_err(|_| std::fmt::Error)?; let s = String::from_utf8(buf).map_err(|_| std::fmt::Error)?; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -225,8 +225,8 @@ impl From for JsonError { impl fmt::Display for JsonError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - JsonError::FloatError(v) => write!(f, "Cannot serialize float {}", v), - JsonError::IoError(e) => write!(f, "{}", e), + JsonError::FloatError(v) => write!(f, "Cannot serialize float {v}"), + JsonError::IoError(e) => write!(f, "{e}"), } } } diff --git a/zjit/src/options.rs b/zjit/src/options.rs index ea9dc3249b82ff..40b49146b726a3 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -247,11 +247,11 @@ fn parse_jit_list(path_like: &str) -> HashSet { } } } else { - eprintln!("Failed to read JIT list from '{}'", path_like); + eprintln!("Failed to read JIT list from '{path_like}'"); } eprintln!("JIT list:"); for item in &result { - eprintln!(" {}", item); + eprintln!(" {item}"); } result } @@ -373,7 +373,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { .write(true) .truncate(true) .open(opt_val) - .map_err(|e| eprintln!("Failed to open file '{}': {}", opt_val, e)) + .map_err(|e| eprintln!("Failed to open file '{opt_val}': {e}")) .ok(); let opt_val = std::fs::canonicalize(opt_val).unwrap_or_else(|_| opt_val.into()); options.dump_hir_graphviz = Some(opt_val); @@ -400,7 +400,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { _ => { let valid_options = DUMP_LIR_ALL.iter().map(|opt| format!("{opt:?}")).collect::>().join(", "); eprintln!("invalid --zjit-dump-lir option: '{filter}'"); - eprintln!("valid --zjit-dump-lir options: all, {}", valid_options); + eprintln!("valid --zjit-dump-lir options: all, {valid_options}"); return None; } }; @@ -421,7 +421,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { .write(true) .truncate(true) .open(opt_val) - .map_err(|e| eprintln!("Failed to open file '{}': {}", opt_val, e)) + .map_err(|e| eprintln!("Failed to open file '{opt_val}': {e}")) .ok(); let opt_val = std::fs::canonicalize(opt_val).unwrap_or_else(|_| opt_val.into()); options.log_compiled_iseqs = Some(opt_val); diff --git a/zjit/src/state.rs b/zjit/src/state.rs index fd59161812a7ee..a807be3f12d4c9 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -241,7 +241,7 @@ impl ZJITState { return; } }; - if let Err(e) = writeln!(file, "{}", iseq_name) { + if let Err(e) = writeln!(file, "{iseq_name}") { eprintln!("ZJIT: Failed to write to file '{}': {}", filename.display(), e); } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7d54f40c8d5dbd..37345f93435185 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -786,21 +786,21 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> // Set not inlined cfunc counters let not_inlined_cfuncs = ZJITState::get_not_inlined_cfunc_counter_pointers(); for (signature, counter) in not_inlined_cfuncs.iter() { - let key_string = format!("not_inlined_cfuncs_{}", signature); + let key_string = format!("not_inlined_cfuncs_{signature}"); set_stat_usize!(hash, &key_string, **counter); } // Set not annotated cfunc counters let not_annotated_cfuncs = ZJITState::get_not_annotated_cfunc_counter_pointers(); for (signature, counter) in not_annotated_cfuncs.iter() { - let key_string = format!("not_annotated_cfuncs_{}", signature); + let key_string = format!("not_annotated_cfuncs_{signature}"); set_stat_usize!(hash, &key_string, **counter); } // Set ccall counters let ccall = ZJITState::get_ccall_counter_pointers(); for (signature, counter) in ccall.iter() { - let key_string = format!("ccall_{}", signature); + let key_string = format!("ccall_{signature}"); set_stat_usize!(hash, &key_string, **counter); } From ccfd31162a8455ac2502ebaf90873b966a866216 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 10 Dec 2025 21:23:08 +0100 Subject: [PATCH 1771/2435] Remove object_id in NEWOBJ tracepoint test Generating an object_id for any type other than T_OBJECT (and T_CLASS) will inevitably allocate an IMEMO/fields objects, which isn't supported in a NEWOBJ tracepoint. See: https://bugs.ruby-lang.org/issues/21710#note-23 --- ext/-test-/tracepoint/tracepoint.c | 20 -------------------- test/-ext-/tracepoint/test_tracepoint.rb | 18 ------------------ 2 files changed, 38 deletions(-) diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c index 7f7aa246628858..001d9513b29fb2 100644 --- a/ext/-test-/tracepoint/tracepoint.c +++ b/ext/-test-/tracepoint/tracepoint.c @@ -86,25 +86,6 @@ tracepoint_specify_normal_and_internal_events(VALUE self) return Qnil; /* should not be reached */ } -int rb_objspace_internal_object_p(VALUE obj); - -static void -on_newobj_event(VALUE tpval, void *data) -{ - VALUE obj = rb_tracearg_object(rb_tracearg_from_tracepoint(tpval)); - if (!rb_objspace_internal_object_p(obj)) rb_obj_id(obj); -} - -static VALUE -add_object_id(RB_UNUSED_VAR(VALUE _)) -{ - VALUE tp = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, NULL); - rb_tracepoint_enable(tp); - rb_yield(Qnil); - rb_tracepoint_disable(tp); - return Qnil; -} - void Init_gc_hook(VALUE); void @@ -114,5 +95,4 @@ Init_tracepoint(void) Init_gc_hook(tp_mBug); rb_define_module_function(tp_mBug, "tracepoint_track_objspace_events", tracepoint_track_objspace_events, 0); rb_define_module_function(tp_mBug, "tracepoint_specify_normal_and_internal_events", tracepoint_specify_normal_and_internal_events, 0); - rb_define_singleton_method(tp_mBug, "tracepoint_add_object_id", add_object_id, 0); } diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb index 2256f58bc710b0..debddd83d043fe 100644 --- a/test/-ext-/tracepoint/test_tracepoint.rb +++ b/test/-ext-/tracepoint/test_tracepoint.rb @@ -82,24 +82,6 @@ def run(hook) end end - def test_tracepoint_add_object_id - Bug.tracepoint_add_object_id do - klass = Struct.new - 2.times { klass.new } - - klass = Struct.new(:a) - 2.times { klass.new } - - klass = Struct.new(:a, :b, :c) - 2.times { klass.new } - - 2.times { Set.new } # To test T_DATA / TypedData RUBY_TYPED_EMBEDDABLE - 2.times { Proc.new { } } # To test T_DATA / TypedData non embeddable - - 2.times { Object.new } - end - end - def test_teardown_with_active_GC_end_hook assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start') end From c8909030974772b4f19742bac55875e32674d27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20L=C3=BCtke?= Date: Wed, 10 Dec 2025 16:18:08 -0500 Subject: [PATCH 1772/2435] ZJIT: Fold LoadField on frozen objects to constants (#15483) * ZJIT: Fold LoadField on frozen objects to constants When accessing instance variables from frozen objects via attr_reader/ attr_accessor, fold the LoadField instruction to a constant at compile time. This enables further optimizations like constant propagation. - Add fold_getinstancevariable_frozen optimization in Function::optimize - Check if receiver type has a known ruby_object() that is frozen - Read the field value at compile time and replace with Const instruction - Add 10 unit tests covering various value types (fixnum, string, symbol, nil, true/false) and negative cases (unfrozen, dynamic receiver) * Run zjit-test-update * Add a test that we don't fold non-BasicObject * Small cleanups --------- Co-authored-by: Max Bernstein Co-authored-by: Aaron Patterson --- zjit/src/hir.rs | 13 ++ zjit/src/hir/opt_tests.rs | 431 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 444 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1731667442792b..deae6c3b653681 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3682,6 +3682,19 @@ impl Function { // Don't bother re-inferring the type of val; we already know it. continue; } + Insn::LoadField { recv, offset, return_type, .. } if return_type.is_subtype(types::BasicObject) => { + let recv_type = self.type_of(recv); + match recv_type.ruby_object() { + Some(recv_obj) if recv_obj.is_frozen() => { + let recv_ptr = recv_obj.as_ptr() as *const VALUE; + // Rust pointer .add() scales by size_of::() and offset is + // already scaled by SIZEOF_VALUE, so undo that. + let val = unsafe { *recv_ptr.add(offset as usize / SIZEOF_VALUE) }; + self.new_insn(Insn::Const { val: Const::Value(val) }) + } + _ => insn_id, + } + } Insn::AnyToString { str, .. } if self.is_a(str, types::String) => { self.make_equal_to(insn_id, str); // Don't bother re-inferring the type of str; we already know it. diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2e20c8fe8a73e2..62ea7c11b0c672 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -9501,4 +9501,435 @@ mod hir_opt_tests { Return v65 "); } + + #[test] + fn test_fold_load_field_frozen_constant_object() { + // Basic case: frozen constant object with attr_accessor + eval(" + class TestFrozen + attr_accessor :a + def initialize + @a = 1 + end + end + + FROZEN_OBJ = TestFrozen.new.freeze + + def test = FROZEN_OBJ.a + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_OBJ) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestFrozen@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_load_field_frozen_multiple_ivars() { + // Frozen object with multiple instance variables + eval(" + class TestMultiIvars + attr_accessor :a, :b, :c + def initialize + @a = 10 + @b = 20 + @c = 30 + end + end + + MULTI_FROZEN = TestMultiIvars.new.freeze + + def test = MULTI_FROZEN.b + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:13: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MULTI_FROZEN) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestMultiIvars@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:Fixnum[20] = Const Value(20) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_load_field_frozen_string_value() { + // Frozen object with a string ivar + eval(r#" + class TestFrozenStr + attr_accessor :name + def initialize + @name = "hello" + end + end + + FROZEN_STR = TestFrozenStr.new.freeze + + def test = FROZEN_STR.name + test + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_STR) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestFrozenStr@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:StringExact[VALUE(0x1050)] = Const Value(VALUE(0x1050)) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_load_field_frozen_nil_value() { + // Frozen object with nil ivar + eval(" + class TestFrozenNil + attr_accessor :value + def initialize + @value = nil + end + end + + FROZEN_NIL = TestFrozenNil.new.freeze + + def test = FROZEN_NIL.value + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_NIL) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestFrozenNil@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:NilClass = Const Value(nil) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_no_fold_load_field_unfrozen_object() { + // Non-frozen object should NOT be folded + eval(" + class TestUnfrozen + attr_accessor :a + def initialize + @a = 1 + end + end + + UNFROZEN_OBJ = TestUnfrozen.new + + def test = UNFROZEN_OBJ.a + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, UNFROZEN_OBJ) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestUnfrozen@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v26:BasicObject = LoadField v25, :@a@0x1049 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_fold_load_field_frozen_with_attr_reader() { + // Using attr_reader instead of attr_accessor + eval(" + class TestAttrReader + attr_reader :value + def initialize(v) + @value = v + end + end + + FROZEN_READER = TestAttrReader.new(42).freeze + + def test = FROZEN_READER.value + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_READER) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestAttrReader@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_load_field_frozen_symbol_value() { + // Frozen object with a symbol ivar + eval(" + class TestFrozenSym + attr_accessor :sym + def initialize + @sym = :hello + end + end + + FROZEN_SYM = TestFrozenSym.new.freeze + + def test = FROZEN_SYM.sym + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_SYM) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestFrozenSym@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:StaticSymbol[:hello] = Const Value(VALUE(0x1050)) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_load_field_frozen_true_false() { + // Frozen object with boolean ivars + eval(" + class TestFrozenBool + attr_accessor :flag + def initialize + @flag = true + end + end + + FROZEN_TRUE = TestFrozenBool.new.freeze + + def test = FROZEN_TRUE.flag + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:11: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, FROZEN_TRUE) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestFrozenBool@0x1010) + v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 + v27:TrueClass = Const Value(true) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_no_fold_load_field_dynamic_receiver() { + // Dynamic receiver (not a constant) should NOT be folded even if object is frozen + eval(" + class TestDynamic + attr_accessor :val + def initialize + @val = 99 + end + end + + def test(obj) = obj.val + o = TestDynamic.new.freeze + test o + test o + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:9: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :obj, l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(TestDynamic@0x1000, val@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(TestDynamic@0x1000) + v21:HeapObject[class_exact:TestDynamic] = GuardType v9, HeapObject[class_exact:TestDynamic] + v24:HeapObject[class_exact:TestDynamic] = GuardShape v21, 0x1038 + v25:BasicObject = LoadField v24, :@val@0x1039 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_fold_load_field_frozen_nested_access() { + // Accessing multiple fields from frozen constant in sequence + eval(" + class TestNestedAccess + attr_accessor :x, :y + def initialize + @x = 100 + @y = 200 + end + end + + NESTED_FROZEN = TestNestedAccess.new.freeze + + def test = NESTED_FROZEN.x + NESTED_FROZEN.y + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:12: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, NESTED_FROZEN) + v28:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(TestNestedAccess@0x1010) + v39:HeapObject[VALUE(0x1008)] = GuardShape v28, 0x1048 + v50:Fixnum[100] = Const Value(100) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1050, NESTED_FROZEN) + v34:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1058, cme:0x1060) + PatchPoint NoSingletonClass(TestNestedAccess@0x1010) + v42:HeapObject[VALUE(0x1008)] = GuardShape v34, 0x1048 + v51:Fixnum[200] = Const Value(200) + PatchPoint MethodRedefined(Integer@0x1088, +@0x1090, cme:0x1098) + v52:Fixnum[300] = Const Value(300) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v52 + "); + } + + #[test] + fn test_dont_fold_load_field_with_primitive_return_type() { + eval(r#" + S = "abc".freeze + def test = S.bytesize + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, S) + v20:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(String@0x1010, bytesize@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(String@0x1010) + v24:CInt64 = LoadField v20, :len@0x1048 + v25:Fixnum = BoxFixnum v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } } From 96c804de1f6a495c339f85069a648d37dadfbc80 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Dec 2025 22:08:04 -0500 Subject: [PATCH 1773/2435] ZJIT: Remove unused includes from zjit.c --- zjit.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/zjit.c b/zjit.c index 05fb3e1f028ff6..cb5a01734f9721 100644 --- a/zjit.c +++ b/zjit.c @@ -23,14 +23,6 @@ #include "ruby/debug.h" #include "internal/cont.h" -// For mmapp(), sysconf() -#ifndef _WIN32 -#include -#include -#endif - -#include - // This build config impacts the pointer tagging scheme and we only want to // support one scheme for simplicity. STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); From b0ea9070d4f42f1844af7eac347ddfb9b4760439 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Dec 2025 22:09:38 -0500 Subject: [PATCH 1774/2435] YJIT: For rustc build, remove cargo touch(1) workaround --- yjit/yjit.mk | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/yjit/yjit.mk b/yjit/yjit.mk index e019e4a08cf7b8..777dcdd43e4ffc 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -9,12 +9,6 @@ YJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/jit/src/lib.rs \ ) -# Because of Cargo cache, if the actual binary is not changed from the -# previous build, the mtime is preserved as the cached file. -# This means the target is not updated actually, and it will need to -# rebuild at the next build. -YJIT_LIB_TOUCH = touch $@ - # Absolute path to match RUST_LIB rules to avoid picking # the "target" dir in the source directory through VPATH. BUILD_YJIT_LIBS = $(TOP_BUILD_DIR)/$(YJIT_LIBS) @@ -24,8 +18,7 @@ ifneq ($(strip $(YJIT_LIBS)),) yjit-libs: $(BUILD_YJIT_LIBS) $(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' - +$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) - $(YJIT_LIB_TOUCH) + $(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) endif ifneq ($(YJIT_SUPPORT),no) From 1bab216062e94e8856ddf3760806b189b4ac5b09 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Dec 2025 22:12:12 -0500 Subject: [PATCH 1775/2435] ZJIT: For rustc build, remove cargo touch(1) workaround --- zjit/zjit.mk | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 01274dc3073642..68fc5778ec6994 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -14,12 +14,6 @@ ZJIT_SRC_FILES = $(wildcard \ $(RUST_LIB): $(ZJIT_SRC_FILES) -# Because of Cargo cache, if the actual binary is not changed from the -# previous build, the mtime is preserved as the cached file. -# This means the target is not updated actually, and it will need to -# rebuild at the next build. -ZJIT_LIB_TOUCH = touch $@ - # Absolute path to match RUST_LIB rules to avoid picking # the "target" dir in the source directory through VPATH. BUILD_ZJIT_LIBS = $(TOP_BUILD_DIR)/$(ZJIT_LIBS) @@ -28,8 +22,7 @@ BUILD_ZJIT_LIBS = $(TOP_BUILD_DIR)/$(ZJIT_LIBS) ifneq ($(strip $(ZJIT_LIBS)),) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ECHO) 'building Rust ZJIT (release mode)' - +$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) - $(ZJIT_LIB_TOUCH) + $(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) endif # By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` From 121d0da02580693ac5f872578284e26e2668b577 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Dec 2025 22:30:22 -0500 Subject: [PATCH 1776/2435] JITs: Move cargo-specific variables into conditional --- defs/jit.mk | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/defs/jit.mk b/defs/jit.mk index a537d803002856..67cc6fdce76215 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -1,11 +1,5 @@ # Make recipes that deal with the rust code of YJIT and ZJIT. -# Because of Cargo cache, if the actual binary is not changed from the -# previous build, the mtime is preserved as the cached file. -# This means the target is not updated actually, and it will need to -# rebuild at the next build. -RUST_LIB_TOUCH = touch $@ - ifneq ($(JIT_CARGO_SUPPORT),no) # Show Cargo progress when doing `make V=1` @@ -13,6 +7,12 @@ CARGO_VERBOSE_0 = -q CARGO_VERBOSE_1 = CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) +# Because of Cargo cache, if the actual binary is not changed from the +# previous build, the mtime is preserved as the cached file. +# This means the target is not updated actually, and it will need to +# rebuild at the next build. +RUST_LIB_TOUCH = touch $@ + # NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below. # ld: warning: object file (target/debug/libjit.a()) was built for # newer macOS version (15.2) than being linked (15.0) @@ -30,7 +30,7 @@ $(RUST_LIB): $(srcdir)/ruby.rs MACOSX_DEPLOYMENT_TARGET=11.0 \ $(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS) $(RUST_LIB_TOUCH) -endif +endif # ifneq ($(JIT_CARGO_SUPPORT),no) RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols $(RUST_LIBOBJ): $(RUST_LIB) From 029a48176cf9fd367d52d8c9f87cb9f77d425a43 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Dec 2025 22:25:05 -0500 Subject: [PATCH 1777/2435] JITs: Drop cargo and use just rustc for release combo build So we don't expose builders to network flakiness which cannot be worked around using cargo's --offline flag. --- Cargo.toml | 15 +++++++-- common.mk | 6 ++-- configure.ac | 80 +++++++++++++++++++++++++------------------- defs/jit.mk | 25 ++++++++++++++ template/Makefile.in | 2 ++ yjit/Cargo.toml | 4 +-- yjit/yjit.mk | 11 +++++- zjit/Cargo.toml | 4 +-- zjit/zjit.mk | 11 +++++- 9 files changed, 110 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec2ce880ca4c48..521129d92d2863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,15 @@ -# Using Cargo's workspace feature to build all the Rust code in -# into a single package. -# TODO(alan) notes about rust version requirements. Undecided yet. +# This is the root Cargo [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) +# and the root package for all the rust code that are statically linked into ruby. Rust tooling +# limitations means all Rust code need to share a single archive library (staticlib) at the +# integration point with non-rust code. (See rustlang/rust#44322 and #104707 for a taste of +# the linking challenges.) +# +# Do not add required dependencies. This is a policy that helps downstream consumers and give +# us tight control over what we ship. All of the optional dependencies are used exclusively +# during development. +# +# Release builds avoid Cargo entirely because offline builds can fail even when none of the +# optional dependencies are built (rust-lang/cargo#10352). [workspace] members = ["zjit", "yjit", "jit"] diff --git a/common.mk b/common.mk index 35c23159800160..50abfeab8a4df6 100644 --- a/common.mk +++ b/common.mk @@ -268,9 +268,8 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \ # For release builds YJIT_RUSTC_ARGS = --crate-name=yjit \ - --crate-type=staticlib \ + $(JIT_RUST_FLAGS) \ --edition=2021 \ - --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ @@ -279,9 +278,8 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ '$(top_srcdir)/yjit/src/lib.rs' ZJIT_RUSTC_ARGS = --crate-name=zjit \ - --crate-type=staticlib \ + $(JIT_RUST_FLAGS) \ --edition=2024 \ - --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ diff --git a/configure.ac b/configure.ac index 14b0234ef0f9aa..ef4b76e5376883 100644 --- a/configure.ac +++ b/configure.ac @@ -3896,7 +3896,6 @@ AC_SUBST(INSTALL_STATIC_LIBRARY) [begin]_group "JIT section" && { AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix -AC_CHECK_TOOL(CARGO, [cargo], [no]) dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0) JIT_RUSTC_OK=no @@ -3963,11 +3962,7 @@ AC_ARG_ENABLE(zjit, # 1.85.0 is the first stable version that supports the 2024 edition. AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | $RUSTC - --edition=2024 --emit asm=/dev/null 2>/dev/null], - AS_IF([test "$gnumake" = "yes" -a \( "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no" \)], [ - # When only building ZJIT, we don't need cargo; it's required for YJIT+ZJIT build. - # Assume that if rustc is new enough, then cargo is also. - # TODO(alan): Get rid of dependency on cargo in YJIT+ZJIT build. Cargo's offline mode - # still too unreliable: https://github.com/rust-lang/cargo/issues/10352 + AS_IF([test "$gnumake" = "yes"], [ rb_zjit_build_possible=yes ]) ) @@ -4053,36 +4048,49 @@ AS_CASE(["${ZJIT_SUPPORT}"], AC_DEFINE(USE_ZJIT, 0) ]) -# if YJIT+ZJIT release build, or any build that requires Cargo -AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x"$ZJIT_SUPPORT" != "xno" \)], [ - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) - - YJIT_LIBS= - ZJIT_LIBS= - - # There's more processing below to get the feature set for the - # top-level crate, so capture at this point for feature set of - # just the zjit crate. - ZJIT_TEST_FEATURES="${rb_cargo_features}" +JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"' +RLIB_DIR= +AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], + [no:yes:yes], [ # release build of YJIT+ZJIT + YJIT_LIBS= + ZJIT_LIBS= + JIT_RUST_FLAGS="--crate-type=rlib" + RLIB_DIR="target/release" + RUST_LIB="target/release/libruby.a" + ], + [no:*], [], + [*], [ # JIT_CARGO_SUPPORT not "no" -- cargo required. + AC_CHECK_TOOL(CARGO, [cargo], [no]) + AS_IF([test x"$CARGO" = "xno"], + AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) + + YJIT_LIBS= + ZJIT_LIBS= + + # There's more processing below to get the feature set for the + # top-level crate, so capture at this point for feature set of + # just the zjit crate. + ZJIT_TEST_FEATURES="${rb_cargo_features}" + + AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ + rb_cargo_features="$rb_cargo_features,yjit" + ]) + AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ + AC_SUBST(ZJIT_TEST_FEATURES) + rb_cargo_features="$rb_cargo_features,zjit" + ]) + # if YJIT and ZJIT release mode + AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ + JIT_CARGO_SUPPORT=release + ]) + CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" + AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ + RUST_LIB="target/debug/libruby.a" + ], [ + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" + ]) + ], - AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ - rb_cargo_features="$rb_cargo_features,yjit" - ]) - AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ - AC_SUBST(ZJIT_TEST_FEATURES) - rb_cargo_features="$rb_cargo_features,zjit" - ]) - # if YJIT and ZJIT release mode - AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ - JIT_CARGO_SUPPORT=release - ]) - CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" - AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ - RUST_LIB="target/debug/libruby.a" - ], [ - RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" - ]) ]) # In case either we're linking rust code @@ -4098,6 +4106,7 @@ AS_IF([test -n "$RUST_LIB"], [ dnl These variables end up in ::RbConfig::CONFIG AC_SUBST(RUSTC)dnl Rust compiler command +AC_SUBST(JIT_RUST_FLAGS)dnl the common rustc flags for JIT crates such as zjit AC_SUBST(CARGO)dnl Cargo command for Rust builds AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes @@ -4108,6 +4117,7 @@ AC_SUBST(ZJIT_LIBS)dnl path to the .a library of ZJIT AC_SUBST(ZJIT_OBJ)dnl for optionally building the C parts of ZJIT AC_SUBST(JIT_OBJ)dnl for optionally building C glue code for Rust FFI AC_SUBST(RUST_LIB)dnl path to the rust .a library that contains either or both JITs +AC_SUBST(RLIB_DIR)dnl subpath of build directory for .rlib files AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code } diff --git a/defs/jit.mk b/defs/jit.mk index 67cc6fdce76215..e893098ca26486 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -30,6 +30,31 @@ $(RUST_LIB): $(srcdir)/ruby.rs MACOSX_DEPLOYMENT_TARGET=11.0 \ $(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS) $(RUST_LIB_TOUCH) +else ifneq ($(strip $(RLIB_DIR)),) # combo build + +$(RUST_LIB): $(srcdir)/ruby.rs + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) --edition=2024 \ + '-L$(@D)' \ + --extern=yjit \ + --extern=zjit \ + --crate-type=staticlib \ + --cfg 'feature="yjit"' \ + --cfg 'feature="zjit"' \ + '--out-dir=$(@D)' \ + '$(top_srcdir)/ruby.rs' + +# Absolute path to avoid VPATH ambiguity +JIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libjit.rlib +$(YJIT_RLIB): $(JIT_RLIB) +$(ZJIT_RLIB): $(JIT_RLIB) +$(JIT_RLIB): + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) --crate-name=jit \ + --edition=2024 \ + $(JIT_RUST_FLAGS) \ + '--out-dir=$(@D)' \ + '$(top_srcdir)/jit/src/lib.rs' endif # ifneq ($(JIT_CARGO_SUPPORT),no) RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols diff --git a/template/Makefile.in b/template/Makefile.in index 7bc40b9d019604..fd41ddca9d90d5 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -114,6 +114,8 @@ JIT_CARGO_SUPPORT=@JIT_CARGO_SUPPORT@ CARGO_TARGET_DIR=@abs_top_builddir@/target CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@ ZJIT_TEST_FEATURES=@ZJIT_TEST_FEATURES@ +JIT_RUST_FLAGS=@JIT_RUST_FLAGS@ +RLIB_DIR=@RLIB_DIR@ RUST_LIB=@RUST_LIB@ RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@) LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index e2f1d84ffd3d8c..d3124e608c6a3c 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -10,8 +10,8 @@ rust-version = "1.58.0" # Minimally supported rust version publish = false # Don't publish to crates.io [dependencies] -# No required dependencies to simplify build process. TODO: Link to yet to be -# written rationale. Optional For development and testing purposes +# No required dependencies to simplify build process. +# Optional For development and testing purposes. capstone = { version = "0.13.0", optional = true } jit = { version = "0.1.0", path = "../jit" } diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 777dcdd43e4ffc..22d256fee78c14 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -19,7 +19,16 @@ yjit-libs: $(BUILD_YJIT_LIBS) $(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' $(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) -endif +else ifneq ($(strip $(RLIB_DIR)),) # combo build +# Absolute path to avoid VPATH ambiguity +YJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libyjit.rlib + +$(YJIT_RLIB): $(YJIT_SRC_FILES) + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(YJIT_RUSTC_ARGS) + +$(RUST_LIB): $(YJIT_RLIB) +endif # ifneq ($(strip $(YJIT_LIBS)),) ifneq ($(YJIT_SUPPORT),no) $(RUST_LIB): $(YJIT_SRC_FILES) diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index c97c845a6eff7f..ef656c5252027c 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -6,8 +6,8 @@ rust-version = "1.85.0" # Minimally supported rust version publish = false # Don't publish to crates.io [dependencies] -# No required dependencies to simplify build process. TODO: Link to yet to be -# written rationale. Optional For development and testing purposes +# No required dependencies to simplify build process. +# Optional For development and testing purposes. capstone = { version = "0.13.0", optional = true } jit = { version = "0.1.0", path = "../jit" } diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 68fc5778ec6994..2116775a91a182 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -23,7 +23,16 @@ ifneq ($(strip $(ZJIT_LIBS)),) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ECHO) 'building Rust ZJIT (release mode)' $(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) -endif +else ifneq ($(strip $(RLIB_DIR)),) # combo build +# Absolute path to avoid VPATH ambiguity +ZJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libzjit.rlib + +$(ZJIT_RLIB): $(ZJIT_SRC_FILES) + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(ZJIT_RUSTC_ARGS) + +$(RUST_LIB): $(ZJIT_RLIB) +endif # ifneq ($(strip $(ZJIT_LIBS)),) # By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` ZJIT_BENCH_OPTS = $(RUN_OPTS) --enable-gems From b208f46f48f88cf4d0b813edbfab07214d085e89 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 10 Dec 2025 16:26:22 -0500 Subject: [PATCH 1778/2435] ZJIT: Don't fold LoadField with negative offsets and use byte_add No point doing the manual size unit conversion for add. Sorry, no new tests since there is no way to generate a LoadField with a negative offset from ruby code AFAICT. Careful with the `as` casts. --- zjit/src/hir.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index deae6c3b653681..bc6f1fb5f564fa 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3682,14 +3682,14 @@ impl Function { // Don't bother re-inferring the type of val; we already know it. continue; } - Insn::LoadField { recv, offset, return_type, .. } if return_type.is_subtype(types::BasicObject) => { + Insn::LoadField { recv, offset, return_type, .. } if return_type.is_subtype(types::BasicObject) && + u32::try_from(offset).is_ok() => { + let offset = (offset as u32).to_usize(); let recv_type = self.type_of(recv); match recv_type.ruby_object() { Some(recv_obj) if recv_obj.is_frozen() => { let recv_ptr = recv_obj.as_ptr() as *const VALUE; - // Rust pointer .add() scales by size_of::() and offset is - // already scaled by SIZEOF_VALUE, so undo that. - let val = unsafe { *recv_ptr.add(offset as usize / SIZEOF_VALUE) }; + let val = unsafe { recv_ptr.byte_add(offset).read() }; self.new_insn(Insn::Const { val: Const::Value(val) }) } _ => insn_id, From 5828872ec4814a5a07f5f4c7686c543127619197 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 10 Dec 2025 13:13:21 -0800 Subject: [PATCH 1779/2435] Update Ractor warning message Although the Ractor API is still experimental and may change, and there may be some implementation issues, we should no longer say that there are many. Hopefully we can remove this warning entirely for Ruby 4.1 --- bootstraptest/test_ractor.rb | 2 +- ractor.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 13c4652d3760a8..0b7a43272c3ca9 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1661,7 +1661,7 @@ def hello = nil } # check experimental warning -assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ +assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor API is experimental/, %q{ Warning[:experimental] = $VERBOSE = true STDERR.reopen(STDOUT) eval("Ractor.new{}.value", nil, "test_ractor.rb", 1) diff --git a/ractor.rb b/ractor.rb index d992f0a04702c9..70ce1a9fffecb9 100644 --- a/ractor.rb +++ b/ractor.rb @@ -230,8 +230,8 @@ def self.new(*args, name: nil, &block) b = block # TODO: builtin bug raise ArgumentError, "must be called with a block" unless block if __builtin_cexpr!("RBOOL(ruby_single_main_ractor)") - Kernel.warn("Ractor is experimental, and the behavior may change in future versions of Ruby! " \ - "Also there are many implementation issues.", uplevel: 0, category: :experimental) + Kernel.warn("Ractor API is experimental and may change in future versions of Ruby.", + uplevel: 0, category: :experimental) end loc = caller_locations(1, 1).first loc = "#{loc.path}:#{loc.lineno}" From 1c29fbeca00d4be63e3f537609b88a8f10115ae4 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 10 Dec 2025 12:58:23 -0800 Subject: [PATCH 1780/2435] GC_DEBUG_STRESS_TO_CLASS should only be for debug I believe this was accidentally left in as part of 2beb3798bac52624c3170138f8ef65869f1da6c0 --- gc/default/default.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index a03fda859cf4ca..6a2035dccc9c99 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -316,7 +316,7 @@ int ruby_rgengc_debug; #endif #ifndef GC_DEBUG_STRESS_TO_CLASS -# define GC_DEBUG_STRESS_TO_CLASS 1 +# define GC_DEBUG_STRESS_TO_CLASS RUBY_DEBUG #endif typedef enum { @@ -9428,6 +9428,7 @@ rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid) VALUE rb_ident_hash_new_with_size(st_index_t size); +#if GC_DEBUG_STRESS_TO_CLASS /* * call-seq: * GC.add_stress_to_class(class[, ...]) @@ -9477,6 +9478,7 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self) return Qnil; } +#endif void * rb_gc_impl_objspace_alloc(void) @@ -9582,10 +9584,10 @@ rb_gc_impl_init(void) rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1); } - if (GC_DEBUG_STRESS_TO_CLASS) { - rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1); - rb_define_singleton_method(rb_mGC, "remove_stress_to_class", rb_gcdebug_remove_stress_to_class, -1); - } +#if GC_DEBUG_STRESS_TO_CLASS + rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1); + rb_define_singleton_method(rb_mGC, "remove_stress_to_class", rb_gcdebug_remove_stress_to_class, -1); +#endif /* internal methods */ rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency_m, 0); From c7d56e90d381f0bf115a5c76cbef9df6ae19f4c9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 10 Dec 2025 16:07:39 -0800 Subject: [PATCH 1781/2435] ZJIT: Re-compile ISEQs invalidated by PatchPoint (#15459) --- zjit/src/backend/arm64/mod.rs | 4 +- zjit/src/backend/lir.rs | 8 +-- zjit/src/backend/x86_64/mod.rs | 4 +- zjit/src/codegen.rs | 117 ++++++++++++++++++++++++--------- zjit/src/gc.rs | 61 ++++++++++++----- zjit/src/invariants.rs | 74 ++++++++++++++------- zjit/src/payload.rs | 52 +++++++++++---- zjit/src/stats.rs | 9 ++- 8 files changed, 236 insertions(+), 93 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index f927f261cf39b8..55a65e3ea6161e 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -903,8 +903,8 @@ impl Assembler { } } } - &mut Insn::PatchPoint { ref target, invariant, payload } => { - split_patch_point(asm, target, invariant, payload); + &mut Insn::PatchPoint { ref target, invariant, version } => { + split_patch_point(asm, target, invariant, version); } _ => { asm.push_insn(insn); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index d7c6740d13a1e4..f4ed3d7cb78ce6 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -9,7 +9,7 @@ use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_ use crate::hir::{Invariant, SideExitReason}; use crate::options::{TraceExits, debug, get_option}; use crate::cruby::VALUE; -use crate::payload::IseqPayload; +use crate::payload::IseqVersionRef; use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; @@ -531,7 +531,7 @@ pub enum Insn { Or { left: Opnd, right: Opnd, out: Opnd }, /// Patch point that will be rewritten to a jump to a side exit on invalidation. - PatchPoint { target: Target, invariant: Invariant, payload: *mut IseqPayload }, + PatchPoint { target: Target, invariant: Invariant, version: IseqVersionRef }, /// Make sure the last PatchPoint has enough space to insert a jump. /// We insert this instruction at the end of each block so that the jump @@ -2358,8 +2358,8 @@ impl Assembler { out } - pub fn patch_point(&mut self, target: Target, invariant: Invariant, payload: *mut IseqPayload) { - self.push_insn(Insn::PatchPoint { target, invariant, payload }); + pub fn patch_point(&mut self, target: Target, invariant: Invariant, version: IseqVersionRef) { + self.push_insn(Insn::PatchPoint { target, invariant, version }); } pub fn pad_patch_point(&mut self) { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 5e975e1bd03ccf..9f780617cc4ac5 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -640,8 +640,8 @@ impl Assembler { }; asm.store(dest, src); } - &mut Insn::PatchPoint { ref target, invariant, payload } => { - split_patch_point(asm, target, invariant, payload); + &mut Insn::PatchPoint { ref target, invariant, version } => { + split_patch_point(asm, target, invariant, version); } _ => { asm.push_insn(insn); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 11b60c546c9716..dbcffb04273a75 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -13,7 +13,7 @@ use crate::invariants::{ track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption }; use crate::gc::append_gc_offsets; -use crate::payload::{get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; +use crate::payload::{get_or_create_iseq_payload, IseqCodePtrs, IseqVersion, IseqVersionRef, IseqStatus}; use crate::state::ZJITState; use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; @@ -25,6 +25,9 @@ use crate::hir_type::{types, Type}; use crate::options::get_option; use crate::cast::IntoUsize; +/// At the moment, we support recompiling each ISEQ only once. +pub const MAX_ISEQ_VERSIONS: usize = 2; + /// Sentinel program counter stored in C frames when runtime checks are enabled. const PC_POISON: Option<*const VALUE> = if cfg!(feature = "runtime_checks") { Some(usize::MAX as *const VALUE) @@ -37,6 +40,9 @@ struct JITState { /// Instruction sequence for the method being compiled iseq: IseqPtr, + /// ISEQ version that is being compiled, which will be used by PatchPoint + version: IseqVersionRef, + /// Low-level IR Operands indexed by High-level IR's Instruction ID opnds: Vec>, @@ -52,9 +58,10 @@ struct JITState { impl JITState { /// Create a new JITState instance - fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize) -> Self { + fn new(iseq: IseqPtr, version: IseqVersionRef, num_insns: usize, num_blocks: usize) -> Self { JITState { iseq, + version, opnds: vec![None; num_insns], labels: vec![None; num_blocks], jit_entries: Vec::default(), @@ -134,11 +141,10 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool) } /// Stub a branch for a JIT-to-JIT call -fn gen_iseq_call(cb: &mut CodeBlock, caller_iseq: IseqPtr, iseq_call: &IseqCallRef) -> Result<(), CompileError> { +pub fn gen_iseq_call(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result<(), CompileError> { // Compile a function stub let stub_ptr = gen_function_stub(cb, iseq_call.clone()).inspect_err(|err| { - debug!("{err:?}: gen_function_stub failed: {} -> {}", - iseq_get_location(caller_iseq, 0), iseq_get_location(iseq_call.iseq.get(), 0)); + debug!("{err:?}: gen_function_stub failed: {}", iseq_get_location(iseq_call.iseq.get(), 0)); })?; // Update the JIT-to-JIT call to call the stub @@ -197,29 +203,36 @@ pub fn gen_entry_trampoline(cb: &mut CodeBlock) -> Result fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> Result { // Return an existing pointer if it's already compiled let payload = get_or_create_iseq_payload(iseq); - match &payload.status { - IseqStatus::Compiled(code_ptrs) => return Ok(code_ptrs.clone()), - IseqStatus::CantCompile(err) => return Err(err.clone()), - IseqStatus::NotCompiled => {}, + let last_status = payload.versions.last().map(|version| &unsafe { version.as_ref() }.status); + match last_status { + Some(IseqStatus::Compiled(code_ptrs)) => return Ok(code_ptrs.clone()), + Some(IseqStatus::CantCompile(err)) => return Err(err.clone()), + _ => {}, + } + // If the ISEQ already hax MAX_ISEQ_VERSIONS, do not compile a new version. + if payload.versions.len() == MAX_ISEQ_VERSIONS { + return Err(CompileError::IseqVersionLimitReached); } // Compile the ISEQ - let code_ptrs = gen_iseq_body(cb, iseq, function, payload); + let mut version = IseqVersion::new(iseq); + let code_ptrs = gen_iseq_body(cb, iseq, version, function); match &code_ptrs { Ok(code_ptrs) => { - payload.status = IseqStatus::Compiled(code_ptrs.clone()); + unsafe { version.as_mut() }.status = IseqStatus::Compiled(code_ptrs.clone()); incr_counter!(compiled_iseq_count); } Err(err) => { - payload.status = IseqStatus::CantCompile(err.clone()); + unsafe { version.as_mut() }.status = IseqStatus::CantCompile(err.clone()); incr_counter!(failed_iseq_count); } } + payload.versions.push(version); code_ptrs } /// Compile an ISEQ into machine code -fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Result { +fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, mut version: IseqVersionRef, function: Option<&Function>) -> Result { // If we ran out of code region, we shouldn't attempt to generate new code. if cb.has_dropped_bytes() { return Err(CompileError::OutOfMemory); @@ -232,23 +245,23 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, }; // Compile the High-level IR - let (iseq_code_ptrs, gc_offsets, iseq_calls) = gen_function(cb, iseq, function)?; + let (iseq_code_ptrs, gc_offsets, iseq_calls) = gen_function(cb, iseq, version, function)?; // Stub callee ISEQs for JIT-to-JIT calls for iseq_call in iseq_calls.iter() { - gen_iseq_call(cb, iseq, iseq_call)?; + gen_iseq_call(cb, iseq_call)?; } // Prepare for GC - payload.iseq_calls.extend(iseq_calls); - append_gc_offsets(iseq, &gc_offsets); + unsafe { version.as_mut() }.outgoing.extend(iseq_calls); + append_gc_offsets(iseq, version, &gc_offsets); Ok(iseq_code_ptrs) } /// Compile a function -fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(IseqCodePtrs, Vec, Vec), CompileError> { +fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, function: &Function) -> Result<(IseqCodePtrs, Vec, Vec), CompileError> { let num_spilled_params = max_num_params(function).saturating_sub(ALLOC_REGS.len()); - let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks()); + let mut jit = JITState::new(iseq, version, function.num_insns(), function.num_blocks()); let mut asm = Assembler::new_with_stack_slots(num_spilled_params); // Compile each basic block @@ -724,17 +737,16 @@ fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf /// Record a patch point that should be invalidated on a given invariant fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invariant, state: &FrameState) { - let payload_ptr = get_or_create_iseq_payload_ptr(jit.iseq); let invariant = *invariant; let exit = build_side_exit(jit, state); // Let compile_exits compile a side exit. Let scratch_split lower it with split_patch_point. - asm.patch_point(Target::SideExit { exit, reason: PatchPoint(invariant) }, invariant, payload_ptr); + asm.patch_point(Target::SideExit { exit, reason: PatchPoint(invariant) }, invariant, jit.version); } /// This is used by scratch_split to lower PatchPoint into PadPatchPoint and PosMarker. /// It's called at scratch_split so that we can use the Label after side-exit deduplication in compile_exits. -pub fn split_patch_point(asm: &mut Assembler, target: &Target, invariant: Invariant, payload_ptr: *mut IseqPayload) { +pub fn split_patch_point(asm: &mut Assembler, target: &Target, invariant: Invariant, version: IseqVersionRef) { let Target::Label(exit_label) = *target else { unreachable!("PatchPoint's target should have been lowered to Target::Label by compile_exits: {target:?}"); }; @@ -747,25 +759,25 @@ pub fn split_patch_point(asm: &mut Assembler, target: &Target, invariant: Invari let side_exit_ptr = cb.resolve_label(exit_label); match invariant { Invariant::BOPRedefined { klass, bop } => { - track_bop_assumption(klass, bop, code_ptr, side_exit_ptr, payload_ptr); + track_bop_assumption(klass, bop, code_ptr, side_exit_ptr, version); } Invariant::MethodRedefined { klass: _, method: _, cme } => { - track_cme_assumption(cme, code_ptr, side_exit_ptr, payload_ptr); + track_cme_assumption(cme, code_ptr, side_exit_ptr, version); } Invariant::StableConstantNames { idlist } => { - track_stable_constant_names_assumption(idlist, code_ptr, side_exit_ptr, payload_ptr); + track_stable_constant_names_assumption(idlist, code_ptr, side_exit_ptr, version); } Invariant::NoTracePoint => { - track_no_trace_point_assumption(code_ptr, side_exit_ptr, payload_ptr); + track_no_trace_point_assumption(code_ptr, side_exit_ptr, version); } Invariant::NoEPEscape(iseq) => { - track_no_ep_escape_assumption(iseq, code_ptr, side_exit_ptr, payload_ptr); + track_no_ep_escape_assumption(iseq, code_ptr, side_exit_ptr, version); } Invariant::SingleRactorMode => { - track_single_ractor_assumption(code_ptr, side_exit_ptr, payload_ptr); + track_single_ractor_assumption(code_ptr, side_exit_ptr, version); } Invariant::NoSingletonClass { klass } => { - track_no_singleton_class_assumption(klass, code_ptr, side_exit_ptr, payload_ptr); + track_no_singleton_class_assumption(klass, code_ptr, side_exit_ptr, version); } } }); @@ -2383,8 +2395,9 @@ c_callable! { // code path can be made read-only. But you still need the check as is while holding the VM lock in any case. let cb = ZJITState::get_code_block(); let payload = get_or_create_iseq_payload(iseq); - let compile_error = match &payload.status { - IseqStatus::CantCompile(err) => Some(err), + let last_status = payload.versions.last().map(|version| &unsafe { version.as_ref() }.status); + let compile_error = match last_status { + Some(IseqStatus::CantCompile(err)) => Some(err), _ if cb.has_dropped_bytes() => Some(&CompileError::OutOfMemory), _ => None, }; @@ -2398,7 +2411,15 @@ c_callable! { // Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point. let code_ptr = with_time_stat(compile_time_ns, || function_stub_hit_body(cb, &iseq_call)); + if code_ptr.is_ok() { + if let Some(version) = payload.versions.last_mut() { + unsafe { version.as_mut() }.incoming.push(iseq_call); + } + } let code_ptr = code_ptr.unwrap_or_else(|compile_error| { + // We'll use this Rc again, so increment the ref count decremented by from_raw. + unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); } + prepare_for_exit(iseq, cfp, sp, &compile_error); ZJITState::get_exit_trampoline_with_counter() }); @@ -2715,3 +2736,37 @@ impl IseqCall { }); } } + +#[cfg(test)] +mod tests { + use crate::codegen::MAX_ISEQ_VERSIONS; + use crate::cruby::test_utils::*; + use crate::payload::*; + + #[test] + fn test_max_iseq_versions() { + eval(&format!(" + TEST = -1 + def test = TEST + + # compile and invalidate MAX+1 times + i = 0 + while i < {MAX_ISEQ_VERSIONS} + 1 + test; test # compile a version + + Object.send(:remove_const, :TEST) + TEST = i + + i += 1 + end + ")); + + // It should not exceed MAX_ISEQ_VERSIONS + let iseq = get_method_iseq("self", "test"); + let payload = get_or_create_iseq_payload(iseq); + assert_eq!(payload.versions.len(), MAX_ISEQ_VERSIONS); + + // The last call should not discard the JIT code + assert!(matches!(unsafe { payload.versions.last().unwrap().as_ref() }.status, IseqStatus::Compiled(_))); + } +} diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index 93a9b10e562c4a..48d841a1048c39 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -1,8 +1,9 @@ //! This module is responsible for marking/moving objects on GC. +use std::ptr::null; use std::{ffi::c_void, ops::Range}; use crate::{cruby::*, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; -use crate::payload::{IseqPayload, get_or_create_iseq_payload, payload_ptr_as_mut}; +use crate::payload::{IseqPayload, IseqVersionRef, get_or_create_iseq_payload}; use crate::stats::Counter::gc_time_ns; use crate::state::gc_mark_raw_samples; @@ -50,7 +51,13 @@ pub extern "C" fn rb_zjit_iseq_free(iseq: IseqPtr) { if !ZJITState::has_instance() { return; } + // TODO(Shopify/ruby#682): Free `IseqPayload` + let payload = get_or_create_iseq_payload(iseq); + for version in payload.versions.iter_mut() { + unsafe { version.as_mut() }.iseq = null(); + } + let invariants = ZJITState::get_invariants(); invariants.forget_iseq(iseq); } @@ -93,14 +100,16 @@ fn iseq_mark(payload: &IseqPayload) { // Mark objects baked in JIT code let cb = ZJITState::get_code_block(); - for &offset in payload.gc_offsets.iter() { - let value_ptr: *const u8 = offset.raw_ptr(cb); - // Creating an unaligned pointer is well defined unlike in C. - let value_ptr = value_ptr as *const VALUE; - - unsafe { - let object = value_ptr.read_unaligned(); - rb_gc_mark_movable(object); + for version in payload.versions.iter() { + for &offset in unsafe { version.as_ref() }.gc_offsets.iter() { + let value_ptr: *const u8 = offset.raw_ptr(cb); + // Creating an unaligned pointer is well defined unlike in C. + let value_ptr = value_ptr as *const VALUE; + + unsafe { + let object = value_ptr.read_unaligned(); + rb_gc_mark_movable(object); + } } } } @@ -115,8 +124,26 @@ fn iseq_update_references(payload: &mut IseqPayload) { } }); - // Move ISEQ references in IseqCall - for iseq_call in payload.iseq_calls.iter_mut() { + for &version in payload.versions.iter() { + iseq_version_update_references(version); + } +} + +fn iseq_version_update_references(mut version: IseqVersionRef) { + // Move ISEQ in the payload + unsafe { version.as_mut() }.iseq = unsafe { rb_gc_location(version.as_ref().iseq.into()) }.as_iseq(); + + // Move ISEQ references in incoming IseqCalls + for iseq_call in unsafe { version.as_mut() }.incoming.iter_mut() { + let old_iseq = iseq_call.iseq.get(); + let new_iseq = unsafe { rb_gc_location(VALUE(old_iseq as usize)) }.0 as IseqPtr; + if old_iseq != new_iseq { + iseq_call.iseq.set(new_iseq); + } + } + + // Move ISEQ references in outgoing IseqCalls + for iseq_call in unsafe { version.as_mut() }.outgoing.iter_mut() { let old_iseq = iseq_call.iseq.get(); let new_iseq = unsafe { rb_gc_location(VALUE(old_iseq as usize)) }.0 as IseqPtr; if old_iseq != new_iseq { @@ -126,7 +153,7 @@ fn iseq_update_references(payload: &mut IseqPayload) { // Move objects baked in JIT code let cb = ZJITState::get_code_block(); - for &offset in payload.gc_offsets.iter() { + for &offset in unsafe { version.as_ref() }.gc_offsets.iter() { let value_ptr: *const u8 = offset.raw_ptr(cb); // Creating an unaligned pointer is well defined unlike in C. let value_ptr = value_ptr as *const VALUE; @@ -146,9 +173,8 @@ fn iseq_update_references(payload: &mut IseqPayload) { } /// Append a set of gc_offsets to the iseq's payload -pub fn append_gc_offsets(iseq: IseqPtr, offsets: &Vec) { - let payload = get_or_create_iseq_payload(iseq); - payload.gc_offsets.extend(offsets); +pub fn append_gc_offsets(iseq: IseqPtr, mut version: IseqVersionRef, offsets: &Vec) { + unsafe { version.as_mut() }.gc_offsets.extend(offsets); // Call writebarrier on each newly added value let cb = ZJITState::get_code_block(); @@ -166,9 +192,8 @@ pub fn append_gc_offsets(iseq: IseqPtr, offsets: &Vec) { /// We do this when invalidation rewrites some code with a jump instruction /// and GC offsets are corrupted by the rewrite, assuming no on-stack code /// will step into the instruction with the GC offsets after invalidation. -pub fn remove_gc_offsets(payload_ptr: *mut IseqPayload, removed_range: &Range) { - let payload = payload_ptr_as_mut(payload_ptr); - payload.gc_offsets.retain(|&gc_offset| { +pub fn remove_gc_offsets(mut version: IseqVersionRef, removed_range: &Range) { + unsafe { version.as_mut() }.gc_offsets.retain(|&gc_offset| { let offset_range = gc_offset..(gc_offset.add_bytes(SIZEOF_VALUE)); !ranges_overlap(&offset_range, removed_range) }); diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index b6f5abe58bcd8e..749e0a9e1d38d9 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -3,7 +3,9 @@ use std::{collections::{HashMap, HashSet}, mem}; use crate::{backend::lir::{Assembler, asm_comment}, cruby::{ID, IseqPtr, RedefinitionFlag, VALUE, iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock}, hir::Invariant, options::debug, state::{ZJITState, zjit_enabled_p}, virtualmem::CodePtr}; -use crate::payload::IseqPayload; +use crate::payload::{IseqVersionRef, IseqStatus, get_or_create_iseq_payload}; +use crate::codegen::{MAX_ISEQ_VERSIONS, gen_iseq_call}; +use crate::cruby::{rb_iseq_reset_jit_func, iseq_get_location}; use crate::stats::with_time_stat; use crate::stats::Counter::invalidation_time_ns; use crate::gc::remove_gc_offsets; @@ -19,7 +21,25 @@ macro_rules! compile_patch_points { asm.compile(cb).expect("can write existing code"); }); // Stop marking GC offsets corrupted by the jump instruction - remove_gc_offsets(patch_point.payload_ptr, &written_range); + remove_gc_offsets(patch_point.version, &written_range); + + // If the ISEQ doesn't have max versions, invalidate this version. + let mut version = patch_point.version; + let iseq = unsafe { version.as_ref() }.iseq; + if !iseq.is_null() { + let payload = get_or_create_iseq_payload(iseq); + if unsafe { version.as_ref() }.status != IseqStatus::Invalidated && payload.versions.len() < MAX_ISEQ_VERSIONS { + unsafe { version.as_mut() }.status = IseqStatus::Invalidated; + unsafe { rb_iseq_reset_jit_func(version.as_ref().iseq) }; + + // Recompile JIT-to-JIT calls into the invalidated ISEQ + for incoming in unsafe { version.as_ref() }.incoming.iter() { + if let Err(err) = gen_iseq_call($cb, incoming) { + debug!("{err:?}: gen_iseq_call failed on PatchPoint: {}", iseq_get_location(incoming.iseq.get(), 0)); + } + } + } + } } }); }; @@ -32,17 +52,17 @@ struct PatchPoint { patch_point_ptr: CodePtr, /// Code pointer to a side exit side_exit_ptr: CodePtr, - /// Raw pointer to the ISEQ payload - payload_ptr: *mut IseqPayload, + /// ISEQ version to be invalidated + version: IseqVersionRef, } impl PatchPoint { /// PatchPointer constructor - fn new(patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, payload_ptr: *mut IseqPayload) -> PatchPoint { + fn new(patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, version: IseqVersionRef) -> PatchPoint { Self { patch_point_ptr, side_exit_ptr, - payload_ptr, + version, } } } @@ -206,13 +226,13 @@ pub fn track_no_ep_escape_assumption( iseq: IseqPtr, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, - payload_ptr: *mut IseqPayload, + version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); invariants.no_ep_escape_iseq_patch_points.entry(iseq).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } @@ -227,13 +247,13 @@ pub fn track_bop_assumption( bop: ruby_basic_operators, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, - payload_ptr: *mut IseqPayload, + version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); invariants.bop_patch_points.entry((klass, bop)).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } @@ -242,13 +262,13 @@ pub fn track_cme_assumption( cme: *const rb_callable_method_entry_t, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, - payload_ptr: *mut IseqPayload, + version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); invariants.cme_patch_points.entry(cme).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } @@ -257,7 +277,7 @@ pub fn track_stable_constant_names_assumption( idlist: *const ID, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, - payload_ptr: *mut IseqPayload, + version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); @@ -271,7 +291,7 @@ pub fn track_stable_constant_names_assumption( invariants.constant_state_patch_points.entry(id).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); idx += 1; @@ -283,13 +303,13 @@ pub fn track_no_singleton_class_assumption( klass: VALUE, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, - payload_ptr: *mut IseqPayload, + version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); invariants.no_singleton_class_patch_points.entry(klass).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } @@ -339,12 +359,16 @@ pub extern "C" fn rb_zjit_constant_state_changed(id: ID) { } /// Track the JIT code that assumes that the interpreter is running with only one ractor -pub fn track_single_ractor_assumption(patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, payload_ptr: *mut IseqPayload) { +pub fn track_single_ractor_assumption( + patch_point_ptr: CodePtr, + side_exit_ptr: CodePtr, + version: IseqVersionRef, +) { let invariants = ZJITState::get_invariants(); invariants.single_ractor_patch_points.insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } @@ -368,19 +392,23 @@ pub extern "C" fn rb_zjit_before_ractor_spawn() { }); } -pub fn track_no_trace_point_assumption(patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, payload_ptr: *mut IseqPayload) { +pub fn track_no_trace_point_assumption( + patch_point_ptr: CodePtr, + side_exit_ptr: CodePtr, + version: IseqVersionRef, +) { let invariants = ZJITState::get_invariants(); invariants.no_trace_point_patch_points.insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, - payload_ptr, + version, )); } #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_tracing_invalidate_all() { use crate::payload::{get_or_create_iseq_payload, IseqStatus}; - use crate::cruby::{for_each_iseq, rb_iseq_reset_jit_func}; + use crate::cruby::for_each_iseq; if !zjit_enabled_p() { return; @@ -393,7 +421,9 @@ pub extern "C" fn rb_zjit_tracing_invalidate_all() { for_each_iseq(|iseq| { let payload = get_or_create_iseq_payload(iseq); - payload.status = IseqStatus::NotCompiled; + if let Some(version) = payload.versions.last_mut() { + unsafe { version.as_mut() }.status = IseqStatus::Invalidated; + } unsafe { rb_iseq_reset_jit_func(iseq) }; }); diff --git a/zjit/src/payload.rs b/zjit/src/payload.rs index 1fb3f919946dbb..8540d5e35c498e 100644 --- a/zjit/src/payload.rs +++ b/zjit/src/payload.rs @@ -1,4 +1,5 @@ use std::ffi::c_void; +use std::ptr::NonNull; use crate::codegen::IseqCallRef; use crate::stats::CompileError; use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr}; @@ -6,27 +7,55 @@ use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr}; /// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. #[derive(Debug)] pub struct IseqPayload { - /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. - pub status: IseqStatus, - /// Type information of YARV instruction operands pub profile: IseqProfile, + /// JIT code versions. Different versions should have different assumptions. + pub versions: Vec, +} + +impl IseqPayload { + fn new(iseq_size: u32) -> Self { + Self { + profile: IseqProfile::new(iseq_size), + versions: vec![], + } + } +} + +/// JIT code version. When the same ISEQ is compiled with a different assumption, a new version is created. +#[derive(Debug)] +pub struct IseqVersion { + /// ISEQ pointer. Stored here to minimize the size of PatchPoint. + pub iseq: IseqPtr, + + /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. + pub status: IseqStatus, /// GC offsets of the JIT code. These are the addresses of objects that need to be marked. pub gc_offsets: Vec, - /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. - pub iseq_calls: Vec, + /// JIT-to-JIT calls from the ISEQ. The IseqPayload's ISEQ is the caller of it. + pub outgoing: Vec, + + /// JIT-to-JIT calls to the ISEQ. The IseqPayload's ISEQ is the callee of it. + pub incoming: Vec, } -impl IseqPayload { - fn new(iseq_size: u32) -> Self { - Self { +/// We use a raw pointer instead of Rc to save space for refcount +pub type IseqVersionRef = NonNull; + +impl IseqVersion { + /// Allocate a new IseqVersion to be compiled + pub fn new(iseq: IseqPtr) -> IseqVersionRef { + let version = Self { + iseq, status: IseqStatus::NotCompiled, - profile: IseqProfile::new(iseq_size), gc_offsets: vec![], - iseq_calls: vec![], - } + outgoing: vec![], + incoming: vec![], + }; + let version_ptr = Box::into_raw(Box::new(version)); + NonNull::new(version_ptr).expect("no null from Box") } } @@ -44,6 +73,7 @@ pub enum IseqStatus { Compiled(IseqCodePtrs), CantCompile(CompileError), NotCompiled, + Invalidated, } /// Get a pointer to the payload object associated with an ISEQ. Create one if none exists. diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 37345f93435185..25f0c638be2998 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -285,6 +285,7 @@ make_counters! { } // compile_error_: Compile error reasons + compile_error_iseq_version_limit_reached, compile_error_iseq_stack_too_large, compile_error_exception_handler, compile_error_out_of_memory, @@ -428,6 +429,7 @@ pub fn send_fallback_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { /// Reason why ZJIT failed to produce any JIT code #[derive(Clone, Debug, PartialEq)] pub enum CompileError { + IseqVersionLimitReached, IseqStackTooLarge, ExceptionHandler, OutOfMemory, @@ -441,9 +443,10 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { use crate::stats::CompileError::*; use crate::stats::Counter::*; match compile_error { - IseqStackTooLarge => compile_error_iseq_stack_too_large, - ExceptionHandler => compile_error_exception_handler, - OutOfMemory => compile_error_out_of_memory, + IseqVersionLimitReached => compile_error_iseq_version_limit_reached, + IseqStackTooLarge => compile_error_iseq_stack_too_large, + ExceptionHandler => compile_error_exception_handler, + OutOfMemory => compile_error_out_of_memory, ParseError(parse_error) => match parse_error { StackUnderflow(_) => compile_error_parse_stack_underflow, MalformedIseq(_) => compile_error_parse_malformed_iseq, From 83e080705cc7aba4571b23e7034a3edd84cc26a6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Dec 2025 10:28:03 +0900 Subject: [PATCH 1782/2435] Remove an excess closing bracket --- configure.ac | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure.ac b/configure.ac index ef4b76e5376883..f992e4c015332a 100644 --- a/configure.ac +++ b/configure.ac @@ -4089,8 +4089,6 @@ AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], ], [ RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" ]) - ], - ]) # In case either we're linking rust code From c76ba839b153805f0498229284fea1a809308dbc Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Thu, 11 Dec 2025 17:34:57 +0900 Subject: [PATCH 1783/2435] Allow String#strip etc. to take optional character selectors [Feature #21552] Co-Authored-By: Claude --- string.c | 216 ++++++++++++++++++++++++++++++++++----- test/ruby/test_string.rb | 111 ++++++++++++++++++++ 2 files changed, 301 insertions(+), 26 deletions(-) diff --git a/string.c b/string.c index 52d1f28cc1443f..51f721c41eb398 100644 --- a/string.c +++ b/string.c @@ -10184,6 +10184,22 @@ rb_str_chomp(int argc, VALUE *argv, VALUE str) return rb_str_subseq(str, 0, chompped_length(str, rs)); } +static void +tr_setup_table_multi(char table[TR_TABLE_SIZE], VALUE *tablep, VALUE *ctablep, + VALUE str, int num_selectors, VALUE *selectors) +{ + int i; + + for (i=0; i= e) return 0; + + /* remove leading characters in the table */ + while (s < e) { + int n; + unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); + + if (!tr_find(cc, table, del, nodel)) break; + s += n; + } + return s - start; +} + /* * call-seq: - * lstrip! -> self or nil + * lstrip!(*selectors) -> self or nil * * Like String#lstrip, except that: * @@ -10220,16 +10255,28 @@ lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) */ static VALUE -rb_str_lstrip_bang(VALUE str) +rb_str_lstrip_bang(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start, *s; long olen, loffset; + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + } + if (loffset > 0) { long len = olen-loffset; s = start + loffset; @@ -10244,7 +10291,7 @@ rb_str_lstrip_bang(VALUE str) /* * call-seq: - * lstrip -> new_string + * lstrip(*selectors) -> new_string * * Returns a copy of +self+ with leading whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: @@ -10255,16 +10302,39 @@ rb_str_lstrip_bang(VALUE str) * s.lstrip * # => "abc\u0000\t\n\v\f\r " * + * If +selectors+ are given, removes characters of +selectors+ from the beginning of +self+: + * + * s = "---abc+++" + * s.lstrip("-") # => "abc+++" + * + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".lstrip("0-9") # "abc56789" + * "01234abc56789".lstrip("0-9", "^4-6") # "4abc56789" + * * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_lstrip(VALUE str) +rb_str_lstrip(int argc, VALUE *argv, VALUE str) { char *start; long len, loffset; + + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + RSTRING_GETMEM(str, start, len); - loffset = lstrip_offset(str, start, start+len, STR_ENC_GET(str)); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+len, STR_ENC_GET(str), table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+len, STR_ENC_GET(str)); + } if (loffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, loffset, len - loffset); } @@ -10298,9 +10368,33 @@ rstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) return e - t; } +static long +rstrip_offset_table(VALUE str, const char *s, const char *e, rb_encoding *enc, + char table[TR_TABLE_SIZE], VALUE del, VALUE nodel) +{ + const char *t; + char *tp; + + rb_str_check_dummy_enc(enc); + if (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN) { + rb_raise(rb_eEncCompatError, "invalid byte sequence in %s", rb_enc_name(enc)); + } + if (!s || s >= e) return 0; + t = e; + + /* remove trailing characters in the table */ + while ((tp = rb_enc_prev_char(s, t, e, enc)) != NULL) { + unsigned int c = rb_enc_codepoint(tp, e, enc); + if (!tr_find(c, table, del, nodel)) break; + t = tp; + } + + return e - t; +} + /* * call-seq: - * rstrip! -> self or nil + * rstrip!(*selectors) -> self or nil * * Like String#rstrip, except that: * @@ -10311,16 +10405,27 @@ rstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) */ static VALUE -rb_str_rstrip_bang(VALUE str) +rb_str_rstrip_bang(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start; long olen, roffset; + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - roffset = rstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + roffset = rstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + roffset = rstrip_offset(str, start, start+olen, enc); + } if (roffset > 0) { long len = olen - roffset; @@ -10334,7 +10439,7 @@ rb_str_rstrip_bang(VALUE str) /* * call-seq: - * rstrip -> new_string + * rstrip(*selectors) -> new_string * * Returns a copy of +self+ with trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: @@ -10344,20 +10449,41 @@ rb_str_rstrip_bang(VALUE str) * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.rstrip # => "\u0000\t\n\v\f\r abc" * + * If +selectors+ are given, removes characters of +selectors+ from the end of +self+: + * + * s = "---abc+++" + * s.rstrip("+") # => "---abc" + * + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".rstrip("0-9") # "01234abc" + * "01234abc56789".rstrip("0-9", "^4-6") # "01234abc56" + * * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_rstrip(VALUE str) +rb_str_rstrip(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start; long olen, roffset; + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - roffset = rstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + roffset = rstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + roffset = rstrip_offset(str, start, start+olen, enc); + } if (roffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, 0, olen-roffset); } @@ -10365,7 +10491,7 @@ rb_str_rstrip(VALUE str) /* * call-seq: - * strip! -> self or nil + * strip!(*selectors) -> self or nil * * Like String#strip, except that: * @@ -10376,17 +10502,30 @@ rb_str_rstrip(VALUE str) */ static VALUE -rb_str_strip_bang(VALUE str) +rb_str_strip_bang(int argc, VALUE *argv, VALUE str) { char *start; long olen, loffset, roffset; rb_encoding *enc; + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); - roffset = rstrip_offset(str, start+loffset, start+olen, enc); + + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + roffset = rstrip_offset_table(str, start+loffset, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + roffset = rstrip_offset(str, start+loffset, start+olen, enc); + } if (loffset > 0 || roffset > 0) { long len = olen-roffset; @@ -10404,7 +10543,7 @@ rb_str_strip_bang(VALUE str) /* * call-seq: - * strip -> new_string + * strip(*selectors) -> new_string * * Returns a copy of +self+ with leading and trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: @@ -10414,19 +10553,44 @@ rb_str_strip_bang(VALUE str) * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.strip # => "abc" * + * If +selectors+ are given, removes characters of +selectors+ from both ends of +self+: + * + * s = "---abc+++" + * s.strip("-+") # => "abc" + * s.strip("+-") # => "abc" + * + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".strip("0-9") # "abc" + * "01234abc56789".strip("0-9", "^4-6") # "4abc56" + * * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_strip(VALUE str) +rb_str_strip(int argc, VALUE *argv, VALUE str) { char *start; long olen, loffset, roffset; rb_encoding *enc = STR_ENC_GET(str); + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); - roffset = rstrip_offset(str, start+loffset, start+olen, enc); + + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + roffset = rstrip_offset_table(str, start+loffset, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + roffset = rstrip_offset(str, start+loffset, start+olen, enc); + } if (loffset <= 0 && roffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, loffset, olen-loffset-roffset); @@ -12714,9 +12878,9 @@ Init_String(void) rb_define_method(rb_cString, "gsub", rb_str_gsub, -1); rb_define_method(rb_cString, "chop", rb_str_chop, 0); rb_define_method(rb_cString, "chomp", rb_str_chomp, -1); - rb_define_method(rb_cString, "strip", rb_str_strip, 0); - rb_define_method(rb_cString, "lstrip", rb_str_lstrip, 0); - rb_define_method(rb_cString, "rstrip", rb_str_rstrip, 0); + rb_define_method(rb_cString, "strip", rb_str_strip, -1); + rb_define_method(rb_cString, "lstrip", rb_str_lstrip, -1); + rb_define_method(rb_cString, "rstrip", rb_str_rstrip, -1); rb_define_method(rb_cString, "delete_prefix", rb_str_delete_prefix, 1); rb_define_method(rb_cString, "delete_suffix", rb_str_delete_suffix, 1); @@ -12724,9 +12888,9 @@ Init_String(void) rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1); rb_define_method(rb_cString, "chop!", rb_str_chop_bang, 0); rb_define_method(rb_cString, "chomp!", rb_str_chomp_bang, -1); - rb_define_method(rb_cString, "strip!", rb_str_strip_bang, 0); - rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_bang, 0); - rb_define_method(rb_cString, "rstrip!", rb_str_rstrip_bang, 0); + rb_define_method(rb_cString, "strip!", rb_str_strip_bang, -1); + rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_bang, -1); + rb_define_method(rb_cString, "rstrip!", rb_str_rstrip_bang, -1); rb_define_method(rb_cString, "delete_prefix!", rb_str_delete_prefix_bang, 1); rb_define_method(rb_cString, "delete_suffix!", rb_str_delete_suffix_bang, 1); diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 1fe0629331ec30..7227f903c72fd0 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2049,6 +2049,117 @@ def test_strip! assert_equal(S("x") ,a) end + def test_strip_with_selectors + assert_equal(S("abc"), S("---abc+++").strip("-+")) + assert_equal(S("abc"), S("+++abc---").strip("-+")) + assert_equal(S("abc"), S("+-+abc-+-").strip("-+")) + assert_equal(S(""), S("---+++").strip("-+")) + assert_equal(S("abc "), S("---abc ").strip("-")) + assert_equal(S(" abc"), S(" abc+++").strip("+")) + + # Test with multibyte characters + assert_equal(S("abc"), S("あああabcいいい").strip("あい")) + assert_equal(S("abc"), S("いいいabcあああ").strip("あい")) + + # Test with NUL characters + assert_equal(S("abc\0"), S("---abc\0--").strip("-")) + assert_equal(S("\0abc"), S("--\0abc---").strip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").strip("-+")) + assert_equal(S("abc"), S("abc").strip("")) + + # Test with range + assert_equal(S("abc"), S("012abc345").strip("0-9")) + assert_equal(S("abc"), S("012abc345").strip("^a-z")) + + # Test with multiple selectors + assert_equal(S("4abc56"), S("01234abc56789").strip("0-9", "^4-6")) + end + + def test_strip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("+++abc---") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("abc") + assert_nil(a.strip!("-+")) + assert_equal(S("abc"), a) + + # Test with multibyte characters + a = S("あああabcいいい") + assert_equal(S("abc"), a.strip!("あい")) + assert_equal(S("abc"), a) + end + + def test_lstrip_with_selectors + assert_equal(S("abc+++"), S("---abc+++").lstrip("-")) + assert_equal(S("abc---"), S("+++abc---").lstrip("+")) + assert_equal(S("abc"), S("---abc").lstrip("-")) + assert_equal(S(""), S("---").lstrip("-")) + + # Test with multibyte characters + assert_equal(S("abcいいい"), S("あああabcいいい").lstrip("あ")) + + # Test with NUL characters + assert_equal(S("\0abc+++"), S("--\0abc+++").lstrip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").lstrip("-")) + + # Test with range + assert_equal(S("abc345"), S("012abc345").lstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("4abc56789"), S("01234abc56789").lstrip("0-9", "^4-6")) + end + + def test_lstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc+++"), a.lstrip!("-")) + assert_equal(S("abc+++"), a) + + a = S("abc") + assert_nil(a.lstrip!("-")) + assert_equal(S("abc"), a) + end + + def test_rstrip_with_selectors + assert_equal(S("---abc"), S("---abc+++").rstrip("+")) + assert_equal(S("+++abc"), S("+++abc---").rstrip("-")) + assert_equal(S("abc"), S("abc+++").rstrip("+")) + assert_equal(S(""), S("+++").rstrip("+")) + + # Test with multibyte characters + assert_equal(S("あああabc"), S("あああabcいいい").rstrip("い")) + + # Test with NUL characters + assert_equal(S("---abc\0"), S("---abc\0++").rstrip("+")) + + # Test without modification + assert_equal(S("abc"), S("abc").rstrip("-")) + + # Test with range + assert_equal(S("012abc"), S("012abc345").rstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("01234abc56"), S("01234abc56789").rstrip("0-9", "^4-6")) + end + + def test_rstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("---abc"), a.rstrip!("+")) + assert_equal(S("---abc"), a) + + a = S("abc") + assert_nil(a.rstrip!("+")) + assert_equal(S("abc"), a) + end + def test_sub assert_equal(S("h*llo"), S("hello").sub(/[aeiou]/, S('*'))) assert_equal(S("hllo"), S("hello").sub(/([aeiou])/, S('<\1>'))) From 87f0701b6106569a8486c9623dc6b0b32438355c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 11 Dec 2025 17:06:28 +0900 Subject: [PATCH 1784/2435] Use base: with Dir.glob for bundler.gemspec --- lib/bundler/bundler.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 49319e81b4f08c..350292772a5367 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -34,7 +34,8 @@ Gem::Specification.new do |s| # It should match the RubyGems version shipped with `required_ruby_version` above s.required_rubygems_version = ">= 3.4.1" - s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + base = File.expand_path('../../..', __FILE__) + s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH, base: base).reject {|f| File.directory?(File.join(base, f)) } # include the gemspec itself because warbler breaks w/o it s.files += %w[lib/bundler/bundler.gemspec] From 240e7999de6f6293a577528972dbc220c6d2cc5a Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Thu, 11 Dec 2025 17:57:49 +0900 Subject: [PATCH 1785/2435] [DOC] Update NEWS for `*selectors` arguments added to `String#strip` etc. --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index e7d1da919e631d..88e244ebfe9fb0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -189,6 +189,9 @@ Note: We're only listing outstanding class updates. * Update Unicode to Version 17.0.0 and Emoji Version 17.0. [[Feature #19908]][[Feature #20724]][[Feature #21275]] (also applies to Regexp) + * `String#strip`, `strip!`, `lstrip`, `lstrip!`, `rstrip`, and `rstrip!` + are extended to accept `*selectors` arguments. [[Feature #21552]] + * Thread * Introduce support for `Thread#raise(cause:)` argument similar to From 278a93a1bb721427ab1a2fa8d68c6f916c39de1d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 11 Dec 2025 18:18:09 +0900 Subject: [PATCH 1786/2435] This change didn't fix the original issue. Revert "Use base: with Dir.glob for bundler.gemspec" This reverts commit 87f0701b6106569a8486c9623dc6b0b32438355c. --- lib/bundler/bundler.gemspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 350292772a5367..49319e81b4f08c 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -34,8 +34,7 @@ Gem::Specification.new do |s| # It should match the RubyGems version shipped with `required_ruby_version` above s.required_rubygems_version = ">= 3.4.1" - base = File.expand_path('../../..', __FILE__) - s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH, base: base).reject {|f| File.directory?(File.join(base, f)) } + s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } # include the gemspec itself because warbler breaks w/o it s.files += %w[lib/bundler/bundler.gemspec] From c5b51bdd8c69ae038daf80cb227df854ca0110f1 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Tue, 2 Dec 2025 11:03:02 +0900 Subject: [PATCH 1787/2435] [Bug #21712] Allow `.()` call for command with block This commit allows codes like `a b do end.()` and `a b do end&.()`. --- parse.y | 6 ++++++ test/ripper/test_parser_events.rb | 7 +++++++ test/ruby/test_parse.rb | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/parse.y b/parse.y index cb73ea2ef026bb..2a6de39236dc4c 100644 --- a/parse.y +++ b/parse.y @@ -5235,6 +5235,12 @@ block_call : command do_block $$ = new_command_qcall(p, $2, $1, $3, $4, $5, &@3, &@$); /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, $:4), $:5) %*/ } + | block_call call_op2 paren_args + { + $$ = new_qcall(p, $2, $1, idCall, $3, &@2, &@$); + nd_set_line($$, @2.end_pos.lineno); + /*% ripper: method_add_arg!(call!($:1, $:2, ID2VAL(idCall)), $:3) %*/ + } ; method_call : fcall paren_args diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index aa7434c083f2a2..3e72c7a331530a 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -480,6 +480,13 @@ def test_call assert_equal true, thru_call assert_equal "[call(vcall(foo),.,call,[])]", tree + thru_call = false + assert_nothing_raised { + tree = parse("a b do end.()", :on_call) {thru_call = true} + } + assert_equal true, thru_call + assert_equal "[call(command(a,[vcall(b)],&do_block(,bodystmt([void()]))),.,call,[])]", tree + thru_call = false assert_nothing_raised { tree = parse("self::foo", :on_call) {thru_call = true} diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 78a5638647af67..9fa4dad41e2a86 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -352,6 +352,21 @@ def test_call_method assert_equal("foobar", b) end + def test_call_command + a = b = nil + o = Object.new + def o.m(*arg); proc {|a| arg.join + a }; end + + assert_nothing_raised do + o.instance_eval <<-END, __FILE__, __LINE__+1 + a = o.m "foo", "bar" do end.("buz") + b = o.m "foo", "bar" do end::("buz") + END + end + assert_equal("foobarbuz", a) + assert_equal("foobarbuz", b) + end + def test_xstring assert_raise(Errno::ENOENT) do eval("``") From dc41cf332613db24af512bc7e959fc9a17d85d59 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Dec 2025 18:42:07 +0900 Subject: [PATCH 1788/2435] [Misc #21690] Sync parser_bits.h from internal/bits.h --- parser_bits.h | 107 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/parser_bits.h b/parser_bits.h index cbe42db39631be..f894dde33e5e9e 100644 --- a/parser_bits.h +++ b/parser_bits.h @@ -30,7 +30,7 @@ #include /* for uintptr_t */ #include "internal/compilers.h" /* for MSC_VERSION_SINCE */ -#if defined(_MSC_VER) +#ifdef _MSC_VER # include /* for _byteswap_uint64 */ #endif @@ -57,9 +57,6 @@ # pragma intrinsic(_rotl64) # pragma intrinsic(_rotr64) # endif -#endif - -#if defined(_MSC_VER) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # ifdef _WIN64 @@ -90,6 +87,7 @@ #define UNSIGNED_INTEGER_MAX(T) ((T)~(T)0) +#ifndef MUL_OVERFLOW_SIGNED_INTEGER_P #if __has_builtin(__builtin_mul_overflow_p) # define MUL_OVERFLOW_P(a, b) \ __builtin_mul_overflow_p((a), (b), (__typeof__(a * b))0) @@ -118,15 +116,100 @@ MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, FIXNUM_MIN, FIXNUM_MAX) #endif -#ifdef MUL_OVERFLOW_P +#if defined(MUL_OVERFLOW_P) && defined(USE___BUILTIN_MUL_OVERFLOW_LONG_LONG) # define MUL_OVERFLOW_LONG_LONG_P(a, b) MUL_OVERFLOW_P(a, b) +#else +# define MUL_OVERFLOW_LONG_LONG_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, LLONG_MIN, LLONG_MAX) +#endif + +#ifdef MUL_OVERFLOW_P # define MUL_OVERFLOW_LONG_P(a, b) MUL_OVERFLOW_P(a, b) # define MUL_OVERFLOW_INT_P(a, b) MUL_OVERFLOW_P(a, b) #else -# define MUL_OVERFLOW_LONG_LONG_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, LLONG_MIN, LLONG_MAX) # define MUL_OVERFLOW_LONG_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, LONG_MIN, LONG_MAX) # define MUL_OVERFLOW_INT_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, INT_MIN, INT_MAX) #endif +#endif + +#ifndef ADD_OVERFLOW_SIGNED_INTEGER_P +#if __has_builtin(__builtin_add_overflow_p) +# define ADD_OVERFLOW_P(a, b) \ + __builtin_add_overflow_p((a), (b), (__typeof__(a * b))0) +#elif __has_builtin(__builtin_add_overflow) +# define ADD_OVERFLOW_P(a, b) \ + __extension__ ({ __typeof__(a) c; __builtin_add_overflow((a), (b), &c); }) +#endif + +#define ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + (a) > 0 ? (b) > (max) - (a) : (b) < (min) - (a)) + +#if __has_builtin(__builtin_add_overflow_p) +/* __builtin_add_overflow_p can take bitfield */ +/* and GCC permits bitfields for integers other than int */ +# define ADD_OVERFLOW_FIXNUM_P(a, b) \ + __extension__ ({ \ + struct { long fixnum : sizeof(long) * CHAR_BIT - 1; } c = { 0 }; \ + __builtin_add_overflow_p((a), (b), c.fixnum); \ + }) +#else +# define ADD_OVERFLOW_FIXNUM_P(a, b) \ + ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, FIXNUM_MIN, FIXNUM_MAX) +#endif + +#if defined(ADD_OVERFLOW_P) && defined(USE___BUILTIN_ADD_OVERFLOW_LONG_LONG) +# define ADD_OVERFLOW_LONG_LONG_P(a, b) ADD_OVERFLOW_P(a, b) +#else +# define ADD_OVERFLOW_LONG_LONG_P(a, b) ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, LLONG_MIN, LLONG_MAX) +#endif + +#ifdef ADD_OVERFLOW_P +# define ADD_OVERFLOW_LONG_P(a, b) ADD_OVERFLOW_P(a, b) +# define ADD_OVERFLOW_INT_P(a, b) ADD_OVERFLOW_P(a, b) +#else +# define ADD_OVERFLOW_LONG_P(a, b) ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, LONG_MIN, LONG_MAX) +# define ADD_OVERFLOW_INT_P(a, b) ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, INT_MIN, INT_MAX) +#endif +#endif + +#ifndef SUB_OVERFLOW_SIGNED_INTEGER_P +#if __has_builtin(__builtin_sub_overflow_p) +# define SUB_OVERFLOW_P(a, b) \ + __builtin_sub_overflow_p((a), (b), (__typeof__(a * b))0) +#elif __has_builtin(__builtin_sub_overflow) +# define SUB_OVERFLOW_P(a, b) \ + __extension__ ({ __typeof__(a) c; __builtin_sub_overflow((a), (b), &c); }) +#endif + +#define SUB_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + (b) > 0 ? (a) < (min) + (b) : (a) > (max) + (b)) + +#if __has_builtin(__builtin_sub_overflow_p) +/* __builtin_sub_overflow_p can take bitfield */ +/* and GCC permits bitfields for integers other than int */ +# define SUB_OVERFLOW_FIXNUM_P(a, b) \ + __extension__ ({ \ + struct { long fixnum : sizeof(long) * CHAR_BIT - 1; } c = { 0 }; \ + __builtin_sub_overflow_p((a), (b), c.fixnum); \ + }) +#else +# define SUB_OVERFLOW_FIXNUM_P(a, b) \ + SUB_OVERFLOW_SIGNED_INTEGER_P(a, b, FIXNUM_MIN, FIXNUM_MAX) +#endif + +#if defined(SUB_OVERFLOW_P) && defined(USE___BUILTIN_SUB_OVERFLOW_LONG_LONG) +# define SUB_OVERFLOW_LONG_LONG_P(a, b) SUB_OVERFLOW_P(a, b) +#else +# define SUB_OVERFLOW_LONG_LONG_P(a, b) SUB_OVERFLOW_SIGNED_INTEGER_P(a, b, LLONG_MIN, LLONG_MAX) +#endif + +#ifdef SUB_OVERFLOW_P +# define SUB_OVERFLOW_LONG_P(a, b) SUB_OVERFLOW_P(a, b) +# define SUB_OVERFLOW_INT_P(a, b) SUB_OVERFLOW_P(a, b) +#else +# define SUB_OVERFLOW_LONG_P(a, b) SUB_OVERFLOW_SIGNED_INTEGER_P(a, b, LONG_MIN, LONG_MAX) +# define SUB_OVERFLOW_INT_P(a, b) SUB_OVERFLOW_SIGNED_INTEGER_P(a, b, INT_MIN, INT_MAX) +#endif +#endif #ifdef HAVE_UINT128_T # define bit_length(x) \ @@ -394,9 +477,9 @@ rb_popcount32(uint32_t x) #else x = (x & 0x55555555) + (x >> 1 & 0x55555555); x = (x & 0x33333333) + (x >> 2 & 0x33333333); - x = (x & 0x0f0f0f0f) + (x >> 4 & 0x0f0f0f0f); - x = (x & 0x001f001f) + (x >> 8 & 0x001f001f); - x = (x & 0x0000003f) + (x >>16 & 0x0000003f); + x = (x & 0x07070707) + (x >> 4 & 0x07070707); + x = (x & 0x000f000f) + (x >> 8 & 0x000f000f); + x = (x & 0x0000001f) + (x >>16 & 0x0000001f); return (unsigned int)x; #endif @@ -424,9 +507,9 @@ rb_popcount64(uint64_t x) x = (x & 0x5555555555555555) + (x >> 1 & 0x5555555555555555); x = (x & 0x3333333333333333) + (x >> 2 & 0x3333333333333333); x = (x & 0x0707070707070707) + (x >> 4 & 0x0707070707070707); - x = (x & 0x001f001f001f001f) + (x >> 8 & 0x001f001f001f001f); - x = (x & 0x0000003f0000003f) + (x >>16 & 0x0000003f0000003f); - x = (x & 0x000000000000007f) + (x >>32 & 0x000000000000007f); + x = (x & 0x000f000f000f000f) + (x >> 8 & 0x000f000f000f000f); + x = (x & 0x0000001f0000001f) + (x >>16 & 0x0000001f0000001f); + x = (x & 0x000000000000003f) + (x >>32 & 0x000000000000003f); return (unsigned int)x; #endif From 281a000d6669d3fbb1c0a2f940cbb9c7c01732e6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 31 Mar 2023 01:09:22 +0900 Subject: [PATCH 1789/2435] [Bug #19558] Allow ASCII range to mix with Unicode dump --- string.c | 10 ++++++---- test/ruby/test_string.rb | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/string.c b/string.c index 51f721c41eb398..5b7169ab12ffaa 100644 --- a/string.c +++ b/string.c @@ -7576,10 +7576,6 @@ undump_after_backslash(VALUE undumped, const char **ss, const char *s_end, rb_en } break; case 'x': - if (*utf8) { - rb_raise(rb_eRuntimeError, "hex escape and Unicode escape are mixed"); - } - *binary = true; if (++s >= s_end) { rb_raise(rb_eRuntimeError, "invalid hex escape"); } @@ -7587,6 +7583,12 @@ undump_after_backslash(VALUE undumped, const char **ss, const char *s_end, rb_en if (hexlen != 2) { rb_raise(rb_eRuntimeError, "invalid hex escape"); } + if (!ISASCII(*buf)) { + if (*utf8) { + rb_raise(rb_eRuntimeError, "hex escape and Unicode escape are mixed"); + } + *binary = true; + } rb_str_cat(undumped, (char *)buf, 1); s += hexlen; break; diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 7227f903c72fd0..2458d38ef4b80b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -872,6 +872,10 @@ def test_undump assert_equal('\#', S('"\\\\#"').undump) assert_equal('\#{', S('"\\\\\#{"').undump) + assert_undump("\0\u{ABCD}") + assert_undump(S('"\x00\u3042"'.force_encoding("SJIS"))) + assert_undump(S('"\u3042\x7E"'.force_encoding("SJIS"))) + assert_raise(RuntimeError) { S('\u3042').undump } assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump } assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump } @@ -4027,6 +4031,10 @@ def assert_byteindex(expected, string, match, *rest) def assert_byterindex(expected, string, match, *rest) assert_index_like(:byterindex, expected, string, match, *rest) end + + def assert_undump(str, *rest) + assert_equal(str, str.dump.undump, *rest) + end end class TestString2 < TestString From 6ad4e6a298e7b5d0f925aacecc5d375968ebde15 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 11:09:05 +0100 Subject: [PATCH 1790/2435] [ruby/json] Add `allow_control_characters` parsing option While it's not allowed by the spec, some parsers like Oj do accept it, and it can be blocking a transition. Having this feature can help people migrate. https://github.com/ruby/json/commit/3459499cb3 --- ext/json/parser/parser.c | 36 +++++++++++++++++++++-------------- test/json/json_parser_test.rb | 6 ++++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 45de8d1ff62f1d..8f9729ef28a7fb 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -7,7 +7,7 @@ static VALUE CNaN, CInfinity, CMinusInfinity; static ID i_new, i_try_convert, i_uminus, i_encode; -static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, +static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_allow_control_characters, sym_symbolize_names, sym_freeze, sym_decimal_class, sym_on_load, sym_allow_duplicate_key; static int binary_encindex; @@ -335,6 +335,7 @@ typedef struct JSON_ParserStruct { int max_nesting; bool allow_nan; bool allow_trailing_comma; + bool allow_control_characters; bool symbolize_names; bool freeze; } JSON_ParserConfig; @@ -752,12 +753,15 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser break; default: if ((unsigned char)*pe < 0x20) { - if (*pe == '\n') { - raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + if (!config->allow_control_characters) { + if (*pe == '\n') { + raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + } + raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); } - raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); + } else { + raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); } - raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); break; } } @@ -1009,7 +1013,9 @@ static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfi break; } default: - raise_parse_error("invalid ASCII control character in string: %s", state); + if (!config->allow_control_characters) { + raise_parse_error("invalid ASCII control character in string: %s", state); + } break; } @@ -1430,14 +1436,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) { JSON_ParserConfig *config = (JSON_ParserConfig *)data; - if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } - else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } - else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } - else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } - else if (key == sym_freeze) { config->freeze = RTEST(val); } - else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; } - else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } - else if (key == sym_decimal_class) { + if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } + else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } + else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } + else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); } + else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } + else if (key == sym_freeze) { config->freeze = RTEST(val); } + else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; } + else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } + else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { config->decimal_class = val; @@ -1650,6 +1657,7 @@ void Init_parser(void) sym_max_nesting = ID2SYM(rb_intern("max_nesting")); sym_allow_nan = ID2SYM(rb_intern("allow_nan")); sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma")); + sym_allow_control_characters = ID2SYM(rb_intern("allow_control_characters")); sym_symbolize_names = ID2SYM(rb_intern("symbolize_names")); sym_freeze = ID2SYM(rb_intern("freeze")); sym_on_load = ID2SYM(rb_intern("on_load")); diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 3f0fb7522dc671..d29f8077b1d138 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -172,6 +172,12 @@ def test_parse_control_chars_in_string end end + def test_parse_allowed_control_chars_in_string + 0.upto(31) do |ord| + assert_equal ord.chr, parse(%("#{ord.chr}"), allow_control_characters: true) + end + end + def test_parse_arrays assert_equal([1,2,3], parse('[1,2,3]')) assert_equal([1.2,2,3], parse('[1.2,2,3]')) From 6b469b7e40f94f25b50463de2190d540d646e3f1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 11:55:40 +0100 Subject: [PATCH 1791/2435] [ruby/json] Release 2.18.0 https://github.com/ruby/json/commit/1cdd2122d5 --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 4ed61c43c8b187..631beba83e91b6 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.17.1' + VERSION = '2.18.0' end From 674ddf4b7dc2558afd5570105a9fd7e24f68230b Mon Sep 17 00:00:00 2001 From: git Date: Thu, 11 Dec 2025 10:59:34 +0000 Subject: [PATCH 1792/2435] Update default gems list at 6b469b7e40f94f25b50463de2190d5 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 88e244ebfe9fb0..3e2d37c84cfda9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -255,7 +255,7 @@ The following default gems are updated. * io-nonblock 0.3.2 * io-wait 0.4.0.dev * ipaddr 1.2.8 -* json 2.17.1 +* json 2.18.0 * net-http 0.8.0 * openssl 4.0.0.pre * optparse 0.8.1 From d6b40320b2cfecf9802e664a4869353d195fcc6d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 11 Dec 2025 19:34:01 +0900 Subject: [PATCH 1793/2435] Append found lib/ entries to spec.files to support out-of-place builds --- tool/rbinstall.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index 936ef8242d7ecb..874c3ef1d9af91 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -870,16 +870,25 @@ def load_gemspec(file, base = nil, files: nil) code = File.read(file, encoding: "utf-8:-") code.gsub!(/^ *#.*/, "") - files = files ? files.map(&:dump).join(", ") : "" + spec_files = files ? files.map(&:dump).join(", ") : "" code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split(\([^\)]*\))?/m) do - "[" + files + "]" + "[" + spec_files + "]" end \ or code.gsub!(/IO\.popen\(.*git.*?\)/) do - "[" + files + "] || itself" + "[" + spec_files + "] || itself" end spec = eval(code, binding, file) + # for out-of-place build + collected_files = files ? spec.files.concat(files).uniq : spec.files + spec.files = collected_files.map do |f| + if !File.exist?(File.join(base || ".", f)) && f.end_with?(".rb") + "lib/#{f}" + else + f + end + end unless Gem::Specification === spec raise TypeError, "[#{file}] isn't a Gem::Specification (#{spec.class} instead)." end From 46d8e6d99321b764c4ce529ca1b0c39670193e67 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 11:20:43 +0100 Subject: [PATCH 1794/2435] [ruby/timeout] Reset the interrupt mask when creating the Timeout thread * Add tests related to Thread.handle_interrupt * Fixes https://github.com/ruby/timeout/issues/41 https://github.com/ruby/timeout/commit/a52720e82a --- lib/timeout.rb | 50 ++++++++++--------- test/test_timeout.rb | 113 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 23 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 1640542d6f806a..0260fd1ca74b4f 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -83,36 +83,40 @@ def self.instance end def create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until @queue.empty? and !requests.empty? # wait to have at least one request - req = @queue.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline + # Threads unexpectedly inherit the interrupt mask: https://github.com/ruby/timeout/issues/41 + # So reset the interrupt mask to the default one for the timeout thread + Thread.handle_interrupt(Object => :immediate) do + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline - now = 0.0 - @queue_mutex.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? - @condvar.wait(@queue_mutex, closest_deadline - now) + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end end - end - requests.each do |req| - req.interrupt if req.expired?(now) + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) end - requests.reject!(&:done?) end - end - if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) - ThreadGroup::Default.add(watcher) - end + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end end def ensure_timeout_thread_created diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 51666b73d8e52a..01beadbda6c481 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -4,6 +4,12 @@ class TestTimeout < Test::Unit::TestCase + private def kill_timeout_thread + thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread) + thread.kill + thread.join + end + def test_public_methods assert_equal [:timeout], Timeout.private_instance_methods(false) assert_equal [], Timeout.public_instance_methods(false) @@ -221,6 +227,24 @@ def o.each end end + def test_handle_interrupt_with_exception_class + bug11344 = '[ruby-dev:49179] [Bug #11344]' + ok = false + assert_raise(Timeout::Error) { + Thread.handle_interrupt(Timeout::Error => :never) { + Timeout.timeout(0.01, Timeout::Error) { + sleep 0.2 + ok = true + Thread.handle_interrupt(Timeout::Error => :on_blocking) { + sleep 0.2 + raise "unreachable" + } + } + } + } + assert(ok, bug11344) + end + def test_handle_interrupt bug11344 = '[ruby-dev:49179] [Bug #11344]' ok = false @@ -231,6 +255,7 @@ def test_handle_interrupt ok = true Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { sleep 0.2 + raise "unreachable" } } } @@ -238,6 +263,94 @@ def test_handle_interrupt assert(ok, bug11344) end + def test_handle_interrupt_with_interrupt_mask_inheritance + issue = 'https://github.com/ruby/timeout/issues/41' + + [ + -> {}, # not blocking so no opportunity to interrupt + -> { sleep 5 } + ].each_with_index do |body, idx| + # We need to create a new Timeout thread + kill_timeout_thread + + # Create the timeout thread under a handle_interrupt(:never) + # due to the interrupt mask being inherited + Thread.handle_interrupt(Object => :never) { + assert_equal :ok, Timeout.timeout(1) { :ok } + } + + # Ensure a simple timeout works and the interrupt mask was not inherited + assert_raise(Timeout::Error) { + Timeout.timeout(0.001) { sleep 1 } + } + + r = [] + # This raises Timeout::ExitException and not Timeout::Error for the non-blocking body + # because of the handle_interrupt(:never) which delays raising Timeout::ExitException + # on the main thread until getting outside of that handle_interrupt(:never) call. + # For this reason we document handle_interrupt(Timeout::ExitException) should not be used. + exc = idx == 0 ? Timeout::ExitException : Timeout::Error + assert_raise(exc) { + Thread.handle_interrupt(Timeout::ExitException => :never) { + Timeout.timeout(0.1) do + sleep 0.2 + r << :sleep_before_done + Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { + r << :body + body.call + } + ensure + sleep 0.2 + r << :ensure_sleep_done + end + } + } + assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue) + end + end + + # Same as above but with an exception class + def test_handle_interrupt_with_interrupt_mask_inheritance_with_exception_class + issue = 'https://github.com/ruby/timeout/issues/41' + + [ + -> {}, # not blocking so no opportunity to interrupt + -> { sleep 5 } + ].each do |body| + # We need to create a new Timeout thread + kill_timeout_thread + + # Create the timeout thread under a handle_interrupt(:never) + # due to the interrupt mask being inherited + Thread.handle_interrupt(Object => :never) { + assert_equal :ok, Timeout.timeout(1) { :ok } + } + + # Ensure a simple timeout works and the interrupt mask was not inherited + assert_raise(Timeout::Error) { + Timeout.timeout(0.001) { sleep 1 } + } + + r = [] + assert_raise(Timeout::Error) { + Thread.handle_interrupt(Timeout::Error => :never) { + Timeout.timeout(0.1, Timeout::Error) do + sleep 0.2 + r << :sleep_before_done + Thread.handle_interrupt(Timeout::Error => :on_blocking) { + r << :body + body.call + } + ensure + sleep 0.2 + r << :ensure_sleep_done + end + } + } + assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue) + end + end + def test_fork omit 'fork not supported' unless Process.respond_to?(:fork) r, w = IO.pipe From c678e1bdd3e43814814bb8ab44420de412f6235d Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 12:29:09 +0100 Subject: [PATCH 1795/2435] [ruby/timeout] Revise Timeout.timeout docs and add a section about `ensure` https://github.com/ruby/timeout/commit/7cfa5a6778 --- lib/timeout.rb | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 0260fd1ca74b4f..428104863afe9a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -22,7 +22,7 @@ module Timeout # The version VERSION = "0.5.0" - # Internal error raised to when a timeout is triggered. + # Internal exception raised to when a timeout is triggered. class ExitException < Exception def exception(*) # :nodoc: self @@ -177,7 +177,7 @@ def finished # :startdoc: - # Perform an operation in a block, raising an error if it takes longer than + # Perform an operation in a block, raising an exception if it takes longer than # +sec+ seconds to complete. # # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number @@ -190,12 +190,18 @@ def finished # Omitting will use the default, "execution expired" # # Returns the result of the block *if* the block completed before - # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. + # +sec+ seconds, otherwise raises an exception, based on the value of +klass+. # - # The exception thrown to terminate the given block cannot be rescued inside - # the block unless +klass+ is given explicitly. However, the block can use - # ensure to prevent the handling of the exception. For that reason, this - # method cannot be relied on to enforce timeouts for untrusted blocks. + # The exception raised to terminate the given block is the given +klass+, or + # Timeout::ExitException if +klass+ is not given. The reason for that behavior + # is that Timeout::Error inherits from RuntimeError and might be caught unexpectedly by `rescue`. + # Timeout::ExitException inherits from Exception so it will only be rescued by `rescue Exception`. + # Note that the Timeout::ExitException is translated to a Timeout::Error once it reaches the Timeout.timeout call, + # so outside that call it will be a Timeout::Error. + # + # In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout. + # Also, the block can use +ensure+ to prevent the handling of the exception. + # For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking # Scheduler#timeout_after. @@ -203,6 +209,45 @@ def finished # Note that this is both a method of module Timeout, so you can include # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). + # + # ==== Ensuring the exception does not fire inside ensure blocks + # + # When using Timeout.timeout it can be desirable to ensure the timeout exception does not fire inside an +ensure+ block. + # The simplest and best way to do so it to put the Timeout.timeout call inside the body of the begin/ensure/end: + # + # begin + # Timeout.timeout(sec) { some_long_operation } + # ensure + # cleanup # safe, cannot be interrupt by timeout + # end + # + # If that is not feasible, e.g. if there are +ensure+ blocks inside +some_long_operation+, + # they need to not be interrupted by timeout, and it's not possible to move these ensure blocks outside, + # one can use Thread.handle_interrupt to delay the timeout exception like so: + # + # Thread.handle_interrupt(Timeout::Error => :never) { + # Timeout.timeout(sec, Timeout::Error) do + # setup # timeout cannot happen here, no matter how long it takes + # Thread.handle_interrupt(Timeout::Error => :immediate) { + # some_long_operation # timeout can happen here + # } + # ensure + # cleanup # timeout cannot happen here, no matter how long it takes + # end + # } + # + # An important thing to note is the need to pass an exception klass to Timeout.timeout, + # otherwise it does not work. Specifically, using +Thread.handle_interrupt(Timeout::ExitException => ...)+ + # is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that. + # + # Note that Thread.handle_interrupt is somewhat dangerous because if setup or cleanup hangs + # then the current thread will hang too and the timeout will never fire. + # Also note the block might run for longer than +sec+ seconds: + # e.g. some_long_operation executes for +sec+ seconds + whatever time cleanup takes. + # + # If you want the timeout to only happen on blocking operations one can use :on_blocking + # instead of :immediate. However, that means if the block uses no blocking operations after +sec+ seconds, + # the block will not be interrupted. def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec From 9865048a345a466b935226fa24515b32be664582 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 14:26:08 +0100 Subject: [PATCH 1796/2435] [ruby/timeout] Encapsulate adding a timeout Request https://github.com/ruby/timeout/commit/cb2ba88fed --- lib/timeout.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 428104863afe9a..6aa938cdcf8b3a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -54,8 +54,6 @@ def self.handle_timeout(message) # :nodoc: private_constant :GET_TIME class State - attr_reader :condvar, :queue, :queue_mutex # shared with Timeout.timeout() - def initialize @condvar = ConditionVariable.new @queue = Queue.new @@ -132,6 +130,13 @@ def ensure_timeout_thread_created end end end + + def add_request(request) + @queue_mutex.synchronize do + @queue << request + @condvar.signal + end + end end private_constant :State @@ -263,10 +268,7 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - state.queue_mutex.synchronize do - state.queue << request - state.condvar.signal - end + state.add_request(request) begin return yield(sec) ensure From b49ff7cc700ffdba26fabaaf8167eee189797edf Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 14:16:49 +0100 Subject: [PATCH 1797/2435] [ruby/timeout] Make Timeout.timeout work in a trap handler on CRuby * Fixes https://github.com/ruby/timeout/issues/17 https://github.com/ruby/timeout/commit/1a499a8f96 --- lib/timeout.rb | 28 +++++++++++++++++++++++++--- test/test_timeout.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 6aa938cdcf8b3a..9969fa2e57b5d7 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -123,7 +123,7 @@ def ensure_timeout_thread_created # In that case, just return and let the main thread create the Timeout thread. return if @timeout_thread_mutex.owned? - @timeout_thread_mutex.synchronize do + Sync.synchronize @timeout_thread_mutex do unless @timeout_thread&.alive? @timeout_thread = create_timeout_thread end @@ -132,7 +132,7 @@ def ensure_timeout_thread_created end def add_request(request) - @queue_mutex.synchronize do + Sync.synchronize @queue_mutex do @queue << request @condvar.signal end @@ -153,6 +153,7 @@ def initialize(thread, timeout, exception_class, message) @done = false # protected by @mutex end + # Only called by the timeout thread, so does not need Sync.synchronize def done? @mutex.synchronize do @done @@ -163,6 +164,7 @@ def expired?(now) now >= @deadline end + # Only called by the timeout thread, so does not need Sync.synchronize def interrupt @mutex.synchronize do unless @done @@ -173,13 +175,33 @@ def interrupt end def finished - @mutex.synchronize do + Sync.synchronize @mutex do @done = true end end end private_constant :Request + module Sync + # Calls mutex.synchronize(&block) but if that fails on CRuby due to being in a trap handler, + # run mutex.synchronize(&block) in a separate Thread instead. + def self.synchronize(mutex, &block) + begin + mutex.synchronize(&block) + rescue ThreadError => e + raise e unless e.message == "can't be called from trap context" + # Workaround CRuby issue https://bugs.ruby-lang.org/issues/19473 + # which raises on Mutex#synchronize in trap handler. + # It's expensive to create a Thread just for this, + # but better than failing. + Thread.new { + mutex.synchronize(&block) + }.join + end + end + end + private_constant :Sync + # :startdoc: # Perform an operation in a block, raising an exception if it takes longer than diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 01beadbda6c481..7421b5ba4174c8 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -416,4 +416,33 @@ def test_ractor assert_equal :ok, r end; end if defined?(::Ractor) && RUBY_VERSION >= '4.0' + + def test_timeout_in_trap_handler + # https://github.com/ruby/timeout/issues/17 + + # Test as if this was the first timeout usage + kill_timeout_thread + + rd, wr = IO.pipe + + trap("SIGUSR1") do + begin + Timeout.timeout(0.1) do + sleep 1 + end + rescue Timeout::Error + wr.write "OK" + wr.close + else + wr.write "did not raise" + ensure + wr.close + end + end + + Process.kill :USR1, Process.pid + + assert_equal "OK", rd.read + rd.close + end end From 965ae7f386d7b403c4cc32d260beab4acce31856 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 15:58:23 +0100 Subject: [PATCH 1798/2435] Fix typo in Fiber.[] docs --- cont.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cont.c b/cont.c index acfcefc81e89f2..8d5efeaac431a1 100644 --- a/cont.c +++ b/cont.c @@ -2201,7 +2201,7 @@ rb_fiber_storage_set(VALUE self, VALUE value) * Returns the value of the fiber storage variable identified by +key+. * * The +key+ must be a symbol, and the value is set by Fiber#[]= or - * Fiber#store. + * Fiber#storage. * * See also Fiber::[]=. */ From 4ab45e8f0cdf008db89c54c14a8214b915b80ab9 Mon Sep 17 00:00:00 2001 From: kares Date: Thu, 11 Dec 2025 10:53:31 +0100 Subject: [PATCH 1799/2435] [ruby/psych] bump snakeyaml-engine to 2.10 (jruby) https://github.com/ruby/psych/commit/506bf75ab2 --- ext/psych/lib/psych/versions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 2c2319e7a38e67..942495db9e894e 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -5,6 +5,6 @@ module Psych VERSION = '5.3.0' if RUBY_ENGINE == 'jruby' - DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze + DEFAULT_SNAKEYAML_VERSION = '2.10'.freeze end end From 3831a82d1916025f7dcdebcb9b66a0cd4fc98f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 11 Dec 2025 16:12:35 +0100 Subject: [PATCH 1800/2435] [ruby/json] Revert "Skip test failing with JRuby in CI" This reverts commit https://github.com/ruby/json/commit/b7e1734d9ca0. https://github.com/ruby/json/commit/5793694ee6 --- test/json/json_parser_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index d29f8077b1d138..ec9391909d779d 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -843,7 +843,6 @@ def test_parse_whitespace_after_newline def test_frozen parser_config = JSON::Parser::Config.new({}).freeze - omit "JRuby failure in CI" if RUBY_ENGINE == "jruby" assert_raise FrozenError do parser_config.send(:initialize, {}) end From b5604833a37bf8cac132906fbf8297d6d4ae9976 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Thu, 11 Dec 2025 17:38:39 +0100 Subject: [PATCH 1801/2435] Fix Set#^ to not mutate its argument (#15296) * test(set): add test Set#xor does not mutate other_set * Fix Set#^ to not mutate its argument --- set.c | 12 +++++++----- test/ruby/test_set.rb | 11 +++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/set.c b/set.c index 7ef7c11d45de56..0c657cf66d8118 100644 --- a/set.c +++ b/set.c @@ -1291,15 +1291,17 @@ set_xor_i(st_data_t key, st_data_t data) static VALUE set_i_xor(VALUE set, VALUE other) { - VALUE new_set; + VALUE new_set = rb_obj_dup(set); + if (rb_obj_is_kind_of(other, rb_cSet)) { - new_set = other; + set_iter(other, set_xor_i, (st_data_t)new_set); } else { - new_set = set_s_alloc(rb_obj_class(set)); - set_merge_enum_into(new_set, other); + VALUE tmp = set_s_alloc(rb_cSet); + set_merge_enum_into(tmp, other); + set_iter(tmp, set_xor_i, (st_data_t)new_set); } - set_iter(set, set_xor_i, (st_data_t)new_set); + return new_set; } diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 6dec0d41ae9a48..e8ac3e329e6678 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -703,6 +703,17 @@ def test_xor } end + def test_xor_does_not_mutate_other_set + a = Set[1] + b = Set[1, 2] + original_b = b.dup + + result = a ^ b + + assert_equal(original_b, b) + assert_equal(Set[2], result) + end + def test_eq set1 = Set[2,3,1] set2 = Set[1,2,3] From 459c377222e746a5e39756bf5d648d16893e831c Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 5 Dec 2025 23:32:15 -0800 Subject: [PATCH 1802/2435] Assume result from allocator will be valid This adds a fastpath in class_call_alloc_func to simply return if the class matches the one expected. I think we could probably just remove this check, or move it to the debug build. --- object.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/object.c b/object.c index bcafab3c3d4bf0..158bb0b219256c 100644 --- a/object.c +++ b/object.c @@ -2238,8 +2238,10 @@ class_call_alloc_func(rb_alloc_func_t allocator, VALUE klass) obj = (*allocator)(klass); - if (rb_obj_class(obj) != rb_class_real(klass)) { - rb_raise(rb_eTypeError, "wrong instance allocation"); + if (UNLIKELY(RBASIC_CLASS(obj) != klass)) { + if (rb_obj_class(obj) != rb_class_real(klass)) { + rb_raise(rb_eTypeError, "wrong instance allocation"); + } } return obj; } From 32e6dc0f31b98cf17dd9ace46561d74a55966b20 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 5 Dec 2025 23:31:33 -0800 Subject: [PATCH 1803/2435] Speed up class allocator search This rewrites the class allocator search to be faster. Instead of using RCLASS_SUPER, which is now even slower due to Box, we can scan the superclasses list to find a class where the allocator is defined. This also disallows allocating from an ICLASS. Previously I believe that was only done for FrozenCore, and that was changed in e596cf6e93dbf121e197cccfec8a69902e00eda3. --- internal/class.h | 4 ++-- vm_method.c | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/class.h b/internal/class.h index 296a07ae29e9f3..ea68b07fc20968 100644 --- a/internal/class.h +++ b/internal/class.h @@ -658,8 +658,8 @@ RCLASS_SET_REFINED_CLASS(VALUE klass, VALUE refined) static inline rb_alloc_func_t RCLASS_ALLOCATOR(VALUE klass) { - RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_ICLASS)); - if (RCLASS_SINGLETON_P(klass) || RB_TYPE_P(klass, T_ICLASS)) { + RBIMPL_ASSERT_TYPE(klass, T_CLASS); + if (RCLASS_SINGLETON_P(klass)) { return 0; } return RCLASS_EXT_PRIME(klass)->as.class.allocator; diff --git a/vm_method.c b/vm_method.c index dbc5ad97eded92..17f68fc258ad16 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1635,10 +1635,20 @@ rb_undef_alloc_func(VALUE klass) rb_alloc_func_t rb_get_alloc_func(VALUE klass) { - Check_Type(klass, T_CLASS); + RBIMPL_ASSERT_TYPE(klass, T_CLASS); - for (; klass; klass = RCLASS_SUPER(klass)) { - rb_alloc_func_t allocator = RCLASS_ALLOCATOR(klass); + rb_alloc_func_t allocator = RCLASS_ALLOCATOR(klass); + if (allocator == UNDEF_ALLOC_FUNC) return 0; + if (allocator) return allocator; + + VALUE *superclasses = RCLASS_SUPERCLASSES(klass); + size_t depth = RCLASS_SUPERCLASS_DEPTH(klass); + + for (size_t i = depth; i > 0; i--) { + klass = superclasses[i - 1]; + RBIMPL_ASSERT_TYPE(klass, T_CLASS); + + allocator = RCLASS_ALLOCATOR(klass); if (allocator == UNDEF_ALLOC_FUNC) break; if (allocator) return allocator; } From 89e09e4daf1b27c94dbf326c8f5b0b5f864a6e72 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Sat, 6 Dec 2025 02:16:59 -0800 Subject: [PATCH 1804/2435] Add assumption to free_vm_weak_references Help the compiler know that we always get a heap object here. --- gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gc.c b/gc.c index 5f0f2307c8c9fd..8b6142b139650e 100644 --- a/gc.c +++ b/gc.c @@ -2061,6 +2061,7 @@ obj_free_object_id(VALUE obj) void rb_gc_obj_free_vm_weak_references(VALUE obj) { + ASSUME(!RB_SPECIAL_CONST_P(obj)); obj_free_object_id(obj); if (rb_obj_gen_fields_p(obj)) { From d02c97157476bbd9774f2bf6425a69166b99da1b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 10:57:06 -0800 Subject: [PATCH 1805/2435] Stop bumping RUBY_PATCHLEVEL in release versions (#15502) [[Misc #21770]](https://bugs.ruby-lang.org/issues/21770) --- test/ruby/test_rubyoptions.rb | 6 ++++++ tool/merger.rb | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index a057e64a4a3596..2ec6478a7fd058 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1319,4 +1319,10 @@ def test_free_at_exit_env_var def test_toplevel_ruby assert_instance_of Module, ::Ruby end + + def test_ruby_patchlevel + # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0. + # Released versions have RUBY_PATCHLEVEL 0, and un-released versions have -1. + assert_include [-1, 0], RUBY_PATCHLEVEL + end end diff --git a/tool/merger.rb b/tool/merger.rb index 795e97a86ee529..2eedded66c1a1b 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -65,7 +65,8 @@ def version_up(teeny: false) if teeny v[2].succ! end - if pl != '-1' # trunk does not have patchlevel + # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0. + if Integer(v[0]) <= 3 pl.succ! end From 1b7c8b7993c596cba7251dad16ff8e7234fd976f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 21:19:25 +0100 Subject: [PATCH 1806/2435] [ruby/timeout] Skip signal test on windows Windows has no SIGUSR1. There might be another usable signal, but this is breaking ruby master so I just want a quick fix for now. https://github.com/ruby/timeout/commit/b19043e8d0 --- test/test_timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 7421b5ba4174c8..199b18e7bc9fb9 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -444,5 +444,5 @@ def test_timeout_in_trap_handler assert_equal "OK", rd.read rd.close - end + end if Signal.list["USR1"] # Windows has no SIGUSR1 end From 832aac6c283c59a1bcd3dc81badd380d0fc4dc2f Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 11 Dec 2025 02:15:11 -0500 Subject: [PATCH 1807/2435] Tune AS_CASE indentation style and remove `[*]` for default case There are many indentation styles for AS_CASE in this file but no one uses `[*]` for the default case. --- configure.ac | 76 ++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/configure.ac b/configure.ac index f992e4c015332a..766c75089c87d6 100644 --- a/configure.ac +++ b/configure.ac @@ -4051,44 +4051,44 @@ AS_CASE(["${ZJIT_SUPPORT}"], JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"' RLIB_DIR= AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], - [no:yes:yes], [ # release build of YJIT+ZJIT - YJIT_LIBS= - ZJIT_LIBS= - JIT_RUST_FLAGS="--crate-type=rlib" - RLIB_DIR="target/release" - RUST_LIB="target/release/libruby.a" - ], - [no:*], [], - [*], [ # JIT_CARGO_SUPPORT not "no" -- cargo required. - AC_CHECK_TOOL(CARGO, [cargo], [no]) - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) - - YJIT_LIBS= - ZJIT_LIBS= - - # There's more processing below to get the feature set for the - # top-level crate, so capture at this point for feature set of - # just the zjit crate. - ZJIT_TEST_FEATURES="${rb_cargo_features}" - - AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ - rb_cargo_features="$rb_cargo_features,yjit" - ]) - AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ - AC_SUBST(ZJIT_TEST_FEATURES) - rb_cargo_features="$rb_cargo_features,zjit" - ]) - # if YJIT and ZJIT release mode - AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ - JIT_CARGO_SUPPORT=release - ]) - CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" - AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ - RUST_LIB="target/debug/libruby.a" - ], [ - RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" - ]) +[no:yes:yes], [ # release build of YJIT+ZJIT + YJIT_LIBS= + ZJIT_LIBS= + JIT_RUST_FLAGS="--crate-type=rlib" + RLIB_DIR="target/release" + RUST_LIB="target/release/libruby.a" +], +[no:*], [], +[ # JIT_CARGO_SUPPORT not "no" -- cargo required. + AC_CHECK_TOOL(CARGO, [cargo], [no]) + AS_IF([test x"$CARGO" = "xno"], + AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) + + YJIT_LIBS= + ZJIT_LIBS= + + # There's more processing below to get the feature set for the + # top-level crate, so capture at this point for feature set of + # just the zjit crate. + ZJIT_TEST_FEATURES="${rb_cargo_features}" + + AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ + rb_cargo_features="$rb_cargo_features,yjit" + ]) + AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ + AC_SUBST(ZJIT_TEST_FEATURES) + rb_cargo_features="$rb_cargo_features,zjit" + ]) + # if YJIT and ZJIT release mode + AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ + JIT_CARGO_SUPPORT=release + ]) + CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" + AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ + RUST_LIB="target/debug/libruby.a" + ], [ + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" + ]) ]) # In case either we're linking rust code From c092c294d4ef08135c5d63db00824b3ec27274d2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 11 Dec 2025 02:41:55 -0500 Subject: [PATCH 1808/2435] ZJIT: [DOC] Mention build prerequisites --- doc/jit/zjit.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index 1e5a36fd5e6f04..c66235269bd265 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -13,6 +13,10 @@ You can change how much executable memory is allocated using [ZJIT's command-lin ## Build Instructions +Refer to [Building Ruby](rdoc-ref:contributing/building_ruby.md) for general build prerequists. +Additionally, ZJIT requires Rust 1.85.0 or later. Release builds need only `rustc`. Development +builds require `cargo` and may download dependencies. + ### For normal use To build ZJIT on macOS: From eb889e474d02f79565f5d8cf001d86bd8aeb737d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 11 Dec 2025 02:55:05 -0500 Subject: [PATCH 1809/2435] ZJIT: s/checking possible to build ZJIT/checking prerequisites for ZJIT/ Reads better to me: > checking prerequisites for ZJIT... yes --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 766c75089c87d6..5dadf13168a867 100644 --- a/configure.ac +++ b/configure.ac @@ -3957,7 +3957,7 @@ AC_ARG_ENABLE(zjit, [AS_CASE(["$JIT_TARGET_OK"], [yes], [ rb_zjit_build_possible=no - AC_MSG_CHECKING([possible to build ZJIT])dnl only checked when --enable-zjit is not specified + AC_MSG_CHECKING([prerequisites for ZJIT])dnl only checked when --enable-zjit is not specified # Fails in case rustc target doesn't match ruby target. Can happen on Rosetta, for example. # 1.85.0 is the first stable version that supports the 2024 edition. AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | From f8f8ff61062080887ad26a36bd58b51e631eee80 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 11 Dec 2025 03:06:54 -0500 Subject: [PATCH 1810/2435] auto_request_review.yml: Update path for jit related docs --- .github/auto_request_review.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 814150c90e1da9..38496d5cebbebe 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -2,16 +2,15 @@ files: 'yjit*': [team:jit] 'yjit/**/*': [team:jit] 'yjit/src/cruby_bindings.inc.rs': [] - 'doc/yjit/*': [team:jit] 'bootstraptest/test_yjit*': [team:jit] 'test/ruby/test_yjit*': [team:jit] 'zjit*': [team:jit] 'zjit/**/*': [team:jit] 'zjit/src/cruby_bindings.inc.rs': [] - 'doc/zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit] 'defs/jit.mk': [team:jit] 'tool/zjit_bisect.rb': [team:jit] + 'doc/jit/*': [team:jit] # Skip github workflow files because the team don't necessarily need to review dependabot updates for GitHub Actions. It's noisy in notifications, and they're auto-merged anyway. options: ignore_draft: true From fb80587f88e2cb1d52098fe967aed6c43d74a82e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 21:24:19 +0100 Subject: [PATCH 1811/2435] [ruby/timeout] Add windows to CI matrix https://github.com/ruby/timeout/commit/c8d63ce3fe --- test/test_timeout.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 199b18e7bc9fb9..42db4ebbbe6de2 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -425,7 +425,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - trap("SIGUSR1") do + signal = Signal.list["USR1"] ? :USR1 : :TERM + + trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -440,9 +442,9 @@ def test_timeout_in_trap_handler end end - Process.kill :USR1, Process.pid + Process.kill signal, Process.pid assert_equal "OK", rd.read rd.close - end if Signal.list["USR1"] # Windows has no SIGUSR1 + end end From 64062792c61d13d1eca9f637db3ed7da12a61154 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 11 Dec 2025 16:12:00 -0500 Subject: [PATCH 1812/2435] ZJIT: Check method visibility when optimizing sends (#15501) Fix https://github.com/Shopify/ruby/issues/874 --- zjit/src/hir.rs | 39 +++++++- zjit/src/hir/opt_tests.rs | 197 ++++++++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 5 + 3 files changed, 239 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bc6f1fb5f564fa..4992f177991d47 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -614,6 +614,7 @@ pub enum SendFallbackReason { SendWithoutBlockCfuncArrayVariadic, SendWithoutBlockNotOptimizedMethodType(MethodType), SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType), + SendWithoutBlockNotOptimizedNeedPermission, SendWithoutBlockBopRedefined, SendWithoutBlockOperandsNotFixnum, SendWithoutBlockDirectKeywordMismatch, @@ -626,6 +627,7 @@ pub enum SendFallbackReason { SendCfuncVariadic, SendCfuncArrayVariadic, SendNotOptimizedMethodType(MethodType), + SendNotOptimizedNeedPermission, CCallWithFrameTooManyArgs, ObjToStringNotString, TooManyArgsForLir, @@ -653,6 +655,8 @@ impl Display for SendFallbackReason { SendWithoutBlockCfuncArrayVariadic => write!(f, "SendWithoutBlock: C function expects array variadic"), SendWithoutBlockNotOptimizedMethodType(method_type) => write!(f, "SendWithoutBlock: unsupported method type {:?}", method_type), SendWithoutBlockNotOptimizedMethodTypeOptimized(opt_type) => write!(f, "SendWithoutBlock: unsupported optimized method type {:?}", opt_type), + SendWithoutBlockNotOptimizedNeedPermission => write!(f, "SendWithoutBlock: method private or protected and no FCALL"), + SendNotOptimizedNeedPermission => write!(f, "Send: method private or protected and no FCALL"), SendWithoutBlockBopRedefined => write!(f, "SendWithoutBlock: basic operation was redefined"), SendWithoutBlockOperandsNotFixnum => write!(f, "SendWithoutBlock: operands are not fixnums"), SendWithoutBlockDirectKeywordMismatch => write!(f, "SendWithoutBlockDirect: keyword mismatch"), @@ -2634,6 +2638,16 @@ impl Function { // Load an overloaded cme if applicable. See vm_search_cc(). // It allows you to use a faster ISEQ if possible. cme = unsafe { rb_check_overloaded_cme(cme, ci) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedNeedPermission); + self.push_insn_id(block, insn_id); continue; + } + } let mut def_type = unsafe { get_cme_def_type(cme) }; while def_type == VM_METHOD_TYPE_ALIAS { cme = unsafe { rb_aliased_callable_method_entry(cme) }; @@ -3256,7 +3270,18 @@ impl Function { return Err(()); } + let ci_flags = unsafe { vm_ci_flag(call_info) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, ci_flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + fun.set_dynamic_send_reason(send_insn_id, SendNotOptimizedNeedPermission); + return Err(()); + } + } // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding @@ -3398,6 +3423,18 @@ impl Function { return Err(()); } + let ci_flags = unsafe { vm_ci_flag(call_info) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, ci_flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockNotOptimizedNeedPermission); + return Err(()); + } + } + // Find the `argc` (arity) of the C method, which describes the parameters it expects let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; let cfunc_argc = unsafe { get_mct_argc(cfunc) }; @@ -3410,8 +3447,6 @@ impl Function { return Err(()); } - let ci_flags = unsafe { vm_ci_flag(call_info) }; - // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { fun.count_complex_call_features(block, ci_flags); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 62ea7c11b0c672..a602121a0cc51d 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -9932,4 +9932,201 @@ mod hir_opt_tests { Return v25 "); } + + #[test] + fn optimize_call_to_private_method_iseq_with_fcall() { + eval(r#" + class C + def callprivate = secret + private def secret = 42 + end + C.new.callprivate + "#); + assert_snapshot!(hir_string_proc("C.instance_method(:callprivate)"), @r" + fn callprivate@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v18:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v21:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_private_method_iseq() { + eval(r#" + class C + private def secret = 42 + end + Obj = C.new + def test = Obj.secret rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn optimize_call_to_private_method_cfunc_with_fcall() { + eval(r#" + class BasicObject + def callprivate = initialize rescue $! + end + Obj = BasicObject.new.callprivate + "#); + assert_snapshot!(hir_string_proc("BasicObject.instance_method(:callprivate)"), @r" + fn callprivate@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(BasicObject@0x1000, initialize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(BasicObject@0x1000) + v20:BasicObjectExact = GuardType v6, BasicObjectExact + v21:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_private_method_cfunc() { + eval(r#" + Obj = BasicObject.new + def test = Obj.initialize rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn dont_optimize_call_to_private_top_level_method() { + eval(r#" + def toplevel_method = :OK + Obj = Object.new + def test = Obj.toplevel_method rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:ObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :toplevel_method # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn optimize_call_to_protected_method_iseq_with_fcall() { + eval(r#" + class C + def callprotected = secret + protected def secret = 42 + end + C.new.callprotected + "#); + assert_snapshot!(hir_string_proc("C.instance_method(:callprotected)"), @r" + fn callprotected@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v18:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v21:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_protected_method_iseq() { + eval(r#" + class C + protected def secret = 42 + end + Obj = C.new + def test = Obj.secret rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 25f0c638be2998..38e8df170d33fd 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -219,6 +219,7 @@ make_counters! { send_fallback_send_without_block_cfunc_array_variadic, send_fallback_send_without_block_not_optimized_method_type, send_fallback_send_without_block_not_optimized_method_type_optimized, + send_fallback_send_without_block_not_optimized_need_permission, send_fallback_too_many_args_for_lir, send_fallback_send_without_block_bop_redefined, send_fallback_send_without_block_operands_not_fixnum, @@ -230,6 +231,7 @@ make_counters! { send_fallback_send_megamorphic, send_fallback_send_no_profiles, send_fallback_send_not_optimized_method_type, + send_fallback_send_not_optimized_need_permission, send_fallback_ccall_with_frame_too_many_args, send_fallback_argc_param_mismatch, // The call has at least one feature on the caller or callee side @@ -552,6 +554,8 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, SendWithoutBlockNotOptimizedMethodTypeOptimized(_) => send_fallback_send_without_block_not_optimized_method_type_optimized, + SendWithoutBlockNotOptimizedNeedPermission + => send_fallback_send_without_block_not_optimized_need_permission, TooManyArgsForLir => send_fallback_too_many_args_for_lir, SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined, SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum, @@ -569,6 +573,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter ArgcParamMismatch => send_fallback_argc_param_mismatch, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, + SendNotOptimizedNeedPermission => send_fallback_send_not_optimized_need_permission, CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, ObjToStringNotString => send_fallback_obj_to_string_not_string, Uncategorized(_) => send_fallback_uncategorized, From dc58d58a723cf56d2a59db52252b82755248b539 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 11 Dec 2025 17:12:03 -0500 Subject: [PATCH 1813/2435] [ruby/timeout] Fix failing timeout test ``` Run options: "--ruby=./miniruby -I../ruby/lib -I. -I.ext/common ../ruby/tool/runruby.rb --extout=.ext -- --disable-gems" --excludes-dir=../ruby/test/.excludes --name=!/memory_leak/ --seed=9843 [ 1/31] TestTimeout#test_timeout_in_trap_handler = 0.00 s 1) Error: TestTimeout#test_timeout_in_trap_handler: NoMethodError: undefined method 'kill' for nil /Users/luke/workspace/ruby-dev/ruby/test/test_timeout.rb:9:in 'TestTimeout#kill_timeout_thread' /Users/luke/workspace/ruby-dev/ruby/test/test_timeout.rb:424:in 'TestTimeout#test_timeout_in_trap_handler' Finished tests in 2.715032s, 11.4179 tests/s, 52.3014 assertions/s. 31 tests, 142 assertions, 0 failures, 1 errors, 0 skips ruby -v: ruby 4.0.0dev (2025-12-11T21:56:23Z fix_timeout_test https://github.com/ruby/timeout/commit/1c5eacbf9a) +PRISM [arm64-darwin24] make: *** [yes-test-all] Error 1 ``` https://github.com/ruby/timeout/commit/e5bc1de901 --- test/test_timeout.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 42db4ebbbe6de2..91940085c46ac3 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -6,8 +6,10 @@ class TestTimeout < Test::Unit::TestCase private def kill_timeout_thread thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread) - thread.kill - thread.join + if thread + thread.kill + thread.join + end end def test_public_methods From 07b2356a6ad314b9a7b2bb9fc0527b440f004faa Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 10 Dec 2025 11:44:27 +0100 Subject: [PATCH 1814/2435] Mutex: avoid repeated calls to `GET_EC` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That call is surprisingly expensive, so trying doing it once in `#synchronize` and then passing the EC to lock and unlock saves quite a few cycles. Before: ``` ruby 4.0.0dev (2025-12-10T09:30:18Z master c5608ab4d7) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 1.888M i/100ms Monitor 1.633M i/100ms Calculating ------------------------------------- Mutex 22.610M (± 0.2%) i/s (44.23 ns/i) - 113.258M in 5.009097s Monitor 19.148M (± 0.3%) i/s (52.22 ns/i) - 96.366M in 5.032755s ``` After: ``` ruby 4.0.0dev (2025-12-10T10:40:07Z speedup-mutex 1c901cd4f8) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 2.095M i/100ms Monitor 1.578M i/100ms Calculating ------------------------------------- Mutex 24.456M (± 0.4%) i/s (40.89 ns/i) - 123.584M in 5.053418s Monitor 19.176M (± 0.1%) i/s (52.15 ns/i) - 96.243M in 5.018977s ``` Bench: ``` require 'bundler/inline' gemfile do gem "benchmark-ips" end mutex = Mutex.new require "monitor" monitor = Monitor.new Benchmark.ips do |x| x.report("Mutex") { mutex.synchronize { } } x.report("Monitor") { monitor.synchronize { } } end ``` --- eval.c | 9 +- internal/eval.h | 2 + thread_sync.c | 222 +++++++++++++++++++++++------------------------- thread_sync.rb | 83 ++++++++++++++++++ 4 files changed, 197 insertions(+), 119 deletions(-) diff --git a/eval.c b/eval.c index ee5bc43f9cc4c1..0c80872bee76e5 100644 --- a/eval.c +++ b/eval.c @@ -1133,12 +1133,11 @@ rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) } VALUE -rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) +rb_ec_ensure(rb_execution_context_t *ec, VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) { enum ruby_tag_type state; volatile VALUE result = Qnil; VALUE errinfo; - rb_execution_context_t * volatile ec = GET_EC(); EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { result = (*b_proc) (data1); @@ -1155,6 +1154,12 @@ rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE dat return result; } +VALUE +rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) +{ + return rb_ec_ensure(GET_EC(), b_proc, data1, e_proc, data2); +} + static ID frame_func_id(const rb_control_frame_t *cfp) { diff --git a/internal/eval.h b/internal/eval.h index 4c1c045b4e1042..17ade0a7f1e667 100644 --- a/internal/eval.h +++ b/internal/eval.h @@ -11,6 +11,7 @@ * header (related to this file, but not the same role). */ #include "ruby/ruby.h" /* for ID */ +#include "vm_core.h" /* for ID */ #define id_signo ruby_static_id_signo #define id_status ruby_static_id_status @@ -30,6 +31,7 @@ VALUE rb_exception_setup(int argc, VALUE *argv); void rb_refinement_setup(struct rb_refinements_data *data, VALUE module, VALUE klass); void rb_vm_using_module(VALUE module); VALUE rb_top_main_class(const char *method); +VALUE rb_ec_ensure(rb_execution_context_t *ec, VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2); /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); diff --git a/thread_sync.c b/thread_sync.c index 9fb1639e9b7dd2..9942d08e0a5c8f 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -123,7 +123,7 @@ rb_mutex_num_waiting(rb_mutex_t *mutex) rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber); static bool -locked_p(rb_mutex_t *mutex) +mutex_locked_p(rb_mutex_t *mutex) { return mutex->fiber_serial != 0; } @@ -132,7 +132,7 @@ static void mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; - if (locked_p(mutex)) { + if (mutex_locked_p(mutex)) { const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), NULL); if (err) rb_bug("%s", err); } @@ -179,36 +179,18 @@ mutex_alloc(VALUE klass) return obj; } -/* - * call-seq: - * Thread::Mutex.new -> mutex - * - * Creates a new Mutex - */ -static VALUE -mutex_initialize(VALUE self) -{ - return self; -} - VALUE rb_mutex_new(void) { return mutex_alloc(rb_cMutex); } -/* - * call-seq: - * mutex.locked? -> true or false - * - * Returns +true+ if this lock is currently held by some thread. - */ VALUE rb_mutex_locked_p(VALUE self) { rb_mutex_t *mutex = mutex_ptr(self); - return RBOOL(locked_p(mutex)); + return RBOOL(mutex_locked_p(mutex)); } static void @@ -267,17 +249,16 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) } } -/* - * call-seq: - * mutex.try_lock -> true or false - * - * Attempts to obtain the lock and returns immediately. Returns +true+ if the - * lock was granted. - */ +static VALUE +rb_mut_trylock(rb_execution_context_t *ec, VALUE self) +{ + return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, ec->fiber_ptr)); +} + VALUE rb_mutex_trylock(VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), GET_THREAD(), GET_EC()->fiber_ptr)); + return rb_mut_trylock(GET_EC(), self); } static VALUE @@ -303,13 +284,28 @@ delete_from_waitq(VALUE value) static inline rb_atomic_t threadptr_get_interrupts(rb_thread_t *th); +struct mutex_args { + VALUE self; + rb_mutex_t *mutex; + rb_execution_context_t *ec; +}; + +static inline void +mutex_args_init(struct mutex_args *args, VALUE mutex) +{ + args->self = mutex; + args->mutex = mutex_ptr(mutex); + args->ec = GET_EC(); +} + static VALUE -do_mutex_lock(VALUE self, int interruptible_p) +do_mutex_lock(struct mutex_args *args, int interruptible_p) { - rb_execution_context_t *ec = GET_EC(); + VALUE self = args->self; + rb_execution_context_t *ec = args->ec; rb_thread_t *th = ec->thread_ptr; rb_fiber_t *fiber = ec->fiber_ptr; - rb_mutex_t *mutex = mutex_ptr(self); + rb_mutex_t *mutex = args->mutex; rb_atomic_t saved_ints = 0; /* When running trap handler */ @@ -432,35 +428,40 @@ do_mutex_lock(VALUE self, int interruptible_p) static VALUE mutex_lock_uninterruptible(VALUE self) { - return do_mutex_lock(self, 0); + struct mutex_args args; + mutex_args_init(&args, self); + return do_mutex_lock(&args, 0); +} + +static VALUE +rb_mut_lock(rb_execution_context_t *ec, VALUE self) +{ + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + return do_mutex_lock(&args, 1); } -/* - * call-seq: - * mutex.lock -> self - * - * Attempts to grab the lock and waits if it isn't available. - * Raises +ThreadError+ if +mutex+ was locked by the current thread. - */ VALUE rb_mutex_lock(VALUE self) { - return do_mutex_lock(self, 1); + struct mutex_args args; + mutex_args_init(&args, self); + return do_mutex_lock(&args, 1); +} + +static VALUE +rb_mut_owned_p(rb_execution_context_t *ec, VALUE self) +{ + return mutex_owned_p(ec->fiber_ptr, mutex_ptr(self)); } -/* - * call-seq: - * mutex.owned? -> true or false - * - * Returns +true+ if this lock is currently held by current thread. - */ VALUE rb_mutex_owned_p(VALUE self) { - rb_fiber_t *fiber = GET_EC()->fiber_ptr; - rb_mutex_t *mutex = mutex_ptr(self); - - return mutex_owned_p(fiber, mutex); + return rb_mut_owned_p(GET_EC(), self); } static const char * @@ -508,6 +509,24 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) return NULL; } +static void +do_mutex_unlock(struct mutex_args *args) +{ + const char *err; + rb_mutex_t *mutex = args->mutex; + rb_thread_t *th = rb_ec_thread_ptr(args->ec); + + err = rb_mutex_unlock_th(mutex, th, args->ec->fiber_ptr); + if (err) rb_raise(rb_eThreadError, "%s", err); +} + +static VALUE +do_mutex_unlock_safe(VALUE args) +{ + do_mutex_unlock((struct mutex_args *)args); + return Qnil; +} + /* * call-seq: * mutex.unlock -> self @@ -518,13 +537,21 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) VALUE rb_mutex_unlock(VALUE self) { - const char *err; - rb_mutex_t *mutex = mutex_ptr(self); - rb_thread_t *th = GET_THREAD(); - - err = rb_mutex_unlock_th(mutex, th, GET_EC()->fiber_ptr); - if (err) rb_raise(rb_eThreadError, "%s", err); + struct mutex_args args; + mutex_args_init(&args, self); + do_mutex_unlock(&args); + return self; +} +static VALUE +rb_mut_unlock(rb_execution_context_t *ec, VALUE self) +{ + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + do_mutex_unlock(&args); return self; } @@ -593,17 +620,15 @@ mutex_sleep_begin(VALUE _arguments) return woken; } -VALUE -rb_mutex_sleep(VALUE self, VALUE timeout) +static VALUE +rb_mut_sleep(rb_execution_context_t *ec, VALUE self, VALUE timeout) { - rb_execution_context_t *ec = GET_EC(); - if (!NIL_P(timeout)) { // Validate the argument: rb_time_interval(timeout); } - rb_mutex_unlock(self); + rb_mut_unlock(ec, self); time_t beg = time(0); struct rb_mutex_sleep_arguments arguments = { @@ -611,7 +636,7 @@ rb_mutex_sleep(VALUE self, VALUE timeout) .timeout = timeout, }; - VALUE woken = rb_ensure(mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self); + VALUE woken = rb_ec_ensure(ec, mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self); RUBY_VM_CHECK_INTS_BLOCKING(ec); if (!woken) return Qnil; @@ -619,61 +644,32 @@ rb_mutex_sleep(VALUE self, VALUE timeout) return TIMET2NUM(end); } -/* - * call-seq: - * mutex.sleep(timeout = nil) -> number or nil - * - * Releases the lock and sleeps +timeout+ seconds if it is given and - * non-nil or forever. Raises +ThreadError+ if +mutex+ wasn't locked by - * the current thread. - * - * When the thread is next woken up, it will attempt to reacquire - * the lock. - * - * Note that this method can wakeup without explicit Thread#wakeup call. - * For example, receiving signal and so on. - * - * Returns the slept time in seconds if woken up, or +nil+ if timed out. - */ -static VALUE -mutex_sleep(int argc, VALUE *argv, VALUE self) +VALUE +rb_mutex_sleep(VALUE self, VALUE timeout) { - VALUE timeout; - - timeout = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; - return rb_mutex_sleep(self, timeout); + return rb_mut_sleep(GET_EC(), self, timeout); } -/* - * call-seq: - * mutex.synchronize { ... } -> result of the block - * - * Obtains a lock, runs the block, and releases the lock when the block - * completes. See the example under Thread::Mutex. - */ VALUE -rb_mutex_synchronize(VALUE mutex, VALUE (*func)(VALUE arg), VALUE arg) +rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) { - rb_mutex_lock(mutex); - return rb_ensure(func, arg, rb_mutex_unlock, mutex); + struct mutex_args args; + mutex_args_init(&args, self); + do_mutex_lock(&args, 1); + return rb_ec_ensure(args.ec, func, arg, do_mutex_unlock_safe, (VALUE)&args); } -/* - * call-seq: - * mutex.synchronize { ... } -> result of the block - * - * Obtains a lock, runs the block, and releases the lock when the block - * completes. See the example under Thread::Mutex. - */ -static VALUE -rb_mutex_synchronize_m(VALUE self) +VALUE +rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) { - if (!rb_block_given_p()) { - rb_raise(rb_eThreadError, "must be called with a block"); - } - - return rb_mutex_synchronize(self, rb_yield, Qundef); + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + do_mutex_lock(&args, 1); + return rb_ec_ensure(args.ec, rb_yield, Qundef, do_mutex_unlock_safe, (VALUE)&args); } void @@ -1688,14 +1684,6 @@ Init_thread_sync(void) /* Mutex */ DEFINE_CLASS(Mutex, Object); rb_define_alloc_func(rb_cMutex, mutex_alloc); - rb_define_method(rb_cMutex, "initialize", mutex_initialize, 0); - rb_define_method(rb_cMutex, "locked?", rb_mutex_locked_p, 0); - rb_define_method(rb_cMutex, "try_lock", rb_mutex_trylock, 0); - rb_define_method(rb_cMutex, "lock", rb_mutex_lock, 0); - rb_define_method(rb_cMutex, "unlock", rb_mutex_unlock, 0); - rb_define_method(rb_cMutex, "sleep", mutex_sleep, -1); - rb_define_method(rb_cMutex, "synchronize", rb_mutex_synchronize_m, 0); - rb_define_method(rb_cMutex, "owned?", rb_mutex_owned_p, 0); /* Queue */ DEFINE_CLASS(Queue, Object); diff --git a/thread_sync.rb b/thread_sync.rb index f8fa69900b39d0..28c70b1e9ce8fb 100644 --- a/thread_sync.rb +++ b/thread_sync.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Thread class Queue # call-seq: @@ -65,4 +67,85 @@ def push(object, non_block = false, timeout: nil) alias_method :enq, :push alias_method :<<, :push end + + class Mutex + # call-seq: + # Thread::Mutex.new -> mutex + # + # Creates a new Mutex + def initialize + end + + # call-seq: + # mutex.locked? -> true or false + # + # Returns +true+ if this lock is currently held by some thread. + def locked? + Primitive.cexpr! %q{ RBOOL(mutex_locked_p(mutex_ptr(self))) } + end + + # call-seq: + # mutex.owned? -> true or false + # + # Returns +true+ if this lock is currently held by current thread. + def owned? + Primitive.rb_mut_owned_p + end + + # call-seq: + # mutex.lock -> self + # + # Attempts to grab the lock and waits if it isn't available. + # Raises +ThreadError+ if +mutex+ was locked by the current thread. + def lock + Primitive.rb_mut_lock + end + + # call-seq: + # mutex.try_lock -> true or false + # + # Attempts to obtain the lock and returns immediately. Returns +true+ if the + # lock was granted. + def try_lock + Primitive.rb_mut_trylock + end + + # call-seq: + # mutex.lock -> self + # + # Attempts to grab the lock and waits if it isn't available. + # Raises +ThreadError+ if +mutex+ was locked by the current thread. + def unlock + Primitive.rb_mut_unlock + end + + # call-seq: + # mutex.synchronize { ... } -> result of the block + # + # Obtains a lock, runs the block, and releases the lock when the block + # completes. See the example under Thread::Mutex. + def synchronize + raise ThreadError, "must be called with a block" unless defined?(yield) + + Primitive.rb_mut_synchronize + end + + # call-seq: + # mutex.sleep(timeout = nil) -> number or nil + # + # Releases the lock and sleeps +timeout+ seconds if it is given and + # non-nil or forever. Raises +ThreadError+ if +mutex+ wasn't locked by + # the current thread. + # + # When the thread is next woken up, it will attempt to reacquire + # the lock. + # + # Note that this method can wakeup without explicit Thread#wakeup call. + # For example, receiving signal and so on. + # + # Returns the slept time in seconds if woken up, or +nil+ if timed out. + def sleep(timeout = nil) + Primitive.rb_mut_sleep(timeout) + end + end end From 8210a117814662e5ad405d5824c5f1d100f450a5 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 22:05:16 +0100 Subject: [PATCH 1815/2435] test_ractor.rb: old object while calling _id2ref --- bootstraptest/test_ractor.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 0b7a43272c3ca9..6b35bbb46be896 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1169,7 +1169,8 @@ class C # Inserting into the id2ref table should be Ractor-safe assert_equal 'ok', <<~'RUBY' # Force all calls to Kernel#object_id to insert into the id2ref table - ObjectSpace._id2ref(Object.new.object_id) + obj = Object.new + ObjectSpace._id2ref(obj.object_id) rescue nil 10.times.map do Ractor.new do From 0564214a00450371527c7bd69fc13618e5f25f30 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 14:27:44 -0800 Subject: [PATCH 1816/2435] tool/merger.rb: Update the tag format for Ruby 4.0+ --- tool/merger.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tool/merger.rb b/tool/merger.rb index 2eedded66c1a1b..a205fd96831c6b 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -114,7 +114,13 @@ def tag(relname) abort 'no relname is given and not in a release branch even if this is patch release' end end - tagname = "v#{v.join('_')}#{("_#{pl}" if v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl)}" + if /^(?:preview|rc)/ =~ pl + tagname = "v#{v.join('.')}-#{pl}" + elsif Integer(v[0]) >= 4 + tagname = "v#{v.join('.')}" + else + tagname = "v#{v.join('_')}" + end unless execute('git', 'diff', '--exit-code') abort 'uncommitted changes' From a973526c050fec044ffd7ceeba0ac8e8a1fed299 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 14:41:36 -0800 Subject: [PATCH 1817/2435] tool/format-release: Fix the tag format for Ruby 4.0+ --- tool/format-release | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tool/format-release b/tool/format-release index b7ad74a095c6fd..b38263f9f4fb05 100755 --- a/tool/format-release +++ b/tool/format-release @@ -62,19 +62,18 @@ eom if z != 0 prev_tag = nil elsif y != 0 - prev_tag = "v#{x}_#{y-1}_0" prev_ver = "#{x}.#{y-1}.0" + prev_tag = version_tag(prev_ver) else # y == 0 && z == 0 case x when 3 - prev_tag = "v2_7_0" prev_ver = "2.7.0" when 4 - prev_tag = "v3_4_0" prev_ver = "3.4.0" else raise "it doesn't know what is the previous version of '#{version}'" end + prev_tag = version_tag(prev_ver) end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" @@ -95,7 +94,7 @@ eom if prev_tag # show diff shortstat - tag = "v#{version.gsub(/[.\-]/, '_')}" + tag = version_tag(version) stat = `git -C #{rubydir} diff -l0 --shortstat #{prev_tag}..#{tag}` files_changed, insertions, deletions = stat.scan(/\d+/) end @@ -189,7 +188,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= <= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end end def main From 04299ca184daa836b143eabb7835540928076595 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 23:54:22 +0100 Subject: [PATCH 1818/2435] monitor.c: skip GET_EC() on exit --- ext/monitor/monitor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index aeb96d7701242d..c43751c4e21f72 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -148,7 +148,7 @@ monitor_check_owner(VALUE monitor) static void monitor_exit0(struct monitor_args *args) { - monitor_check_owner(args->monitor); + monitor_check_owner0(args); if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count); args->mc->count--; From aff0c6dad2486e939e7f6678c519314925dad866 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 15:52:46 -0800 Subject: [PATCH 1819/2435] tool/merger.rb: Support the new format in remove_tag --- tool/merger.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tool/merger.rb b/tool/merger.rb index a205fd96831c6b..4c096087fc9a25 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -142,10 +142,12 @@ def remove_tag(relname) unless relname raise ArgumentError, 'relname is not specified' end - if /^v/ !~ relname - tagname = "v#{relname.gsub(/[.-]/, '_')}" - else + if relname.start_with?('v') tagname = relname + elsif Integer(relname.split('.', 2).first) >= 4 + tagname = "v#{relname}" + else + tagname = "v#{relname.gsub(/[.-]/, '_')}" end execute('git', 'tag', '-d', tagname) From 12bf3a99d72f5f6f0a7633863e285029aa407c57 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:18:37 -0800 Subject: [PATCH 1820/2435] update-www-meta.rb: Update the tag format for Ruby 4.0+ Just copied format-release fixes in a973526c050fec044ffd7ceeba0ac8e8a1fed299 to this file. --- tool/releng/update-www-meta.rb | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tool/releng/update-www-meta.rb b/tool/releng/update-www-meta.rb index 8a5651dcd05cf1..100f0bee181806 100755 --- a/tool/releng/update-www-meta.rb +++ b/tool/releng/update-www-meta.rb @@ -49,13 +49,18 @@ def self.parse(wwwdir, version) if z != 0 prev_tag = nil elsif y != 0 - prev_tag = "v#{x}_#{y-1}_0" prev_ver = "#{x}.#{y-1}.0" - elsif x == 3 && y == 0 && z == 0 - prev_tag = "v2_7_0" - prev_ver = "2.7.0" - else - raise "unexpected version for prev_ver '#{version}'" + prev_tag = version_tag(prev_ver) + else # y == 0 && z == 0 + case x + when 3 + prev_ver = "2.7.0" + when 4 + prev_ver = "3.4.0" + else + raise "it doesn't know what is the previous version of '#{version}'" + end + prev_tag = version_tag(prev_ver) end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" @@ -76,7 +81,7 @@ def self.parse(wwwdir, version) if prev_tag # show diff shortstat - tag = "v#{version.gsub(/[.\-]/, '_')}" + tag = version_tag(version) rubydir = File.expand_path(File.join(__FILE__, '../../../')) puts %`git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` stat = `git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` @@ -155,7 +160,7 @@ def self.update_releases_yml(ver, xy, ary, wwwdir, files_changed, insertions, de date = Time.now.utc # use utc to use previous day in midnight entry = <= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end end # Confirm current directory is www.ruby-lang.org's working directory From 58f9aca0ccc51a1f9c02280438518c9be8047dbd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:33:49 -0800 Subject: [PATCH 1821/2435] make-snapshot: Drop X.Y.Z-pN support We no longer make patchlevel releases. --- tool/make-snapshot | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 8251fa6324f970..cdda0c59940f39 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -274,10 +274,6 @@ def package(vcs, rev, destdir, tmp = nil) prerelease = true tag = "#{$4}#{$5}" url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") - when /\A(.*)\.(.*)\.(.*)-p(\d+)/ - patchlevel = true - tag = "p#{$4}" - url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}") when /\A(\d+)\.(\d+)(?:\.(\d+))?\z/ if $3 && ($1 > "2" || $1 == "2" && $2 >= "1") patchlevel = true From 0ecf68963571b70103f7fa35b7403585585d032c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:35:12 -0800 Subject: [PATCH 1822/2435] make-snapshot: Make preview/rc match stricter to make it a bit more consistent with the other branch --- tool/make-snapshot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index cdda0c59940f39..2ea35f8feaa2da 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -270,7 +270,7 @@ def package(vcs, rev, destdir, tmp = nil) when /\Astable\z/ vcs.branch_list("ruby_[0-9]*") {|n| url = n[/\Aruby_\d+_\d+\z/]} url &&= vcs.branch(url) - when /\A(.*)\.(.*)\.(.*)-(preview|rc)(\d+)/ + when /\A(\d+)\.(\d+)\.(\d+)-(preview|rc)(\d+)/ prerelease = true tag = "#{$4}#{$5}" url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") From 6601640c68b02f0bf1ad58d9122e76c84b735f91 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:37:49 -0800 Subject: [PATCH 1823/2435] make-snapshot: Branch if it's X.Y.Z or X.Y using when It just seems like a completely different input, so it makes more sense to me to have it as a separate case. Also, we don't need to support Ruby 2.0 or older. --- tool/make-snapshot | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 2ea35f8feaa2da..0a03fb2a227ae7 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -274,14 +274,12 @@ def package(vcs, rev, destdir, tmp = nil) prerelease = true tag = "#{$4}#{$5}" url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") - when /\A(\d+)\.(\d+)(?:\.(\d+))?\z/ - if $3 && ($1 > "2" || $1 == "2" && $2 >= "1") - patchlevel = true - tag = "" - url = vcs.tag("v#{$1}_#{$2}_#{$3}") - else - url = vcs.branch("ruby_#{rev.tr('.', '_')}") - end + when /\A(\d+)\.(\d+)\.(\d+)\z/ + patchlevel = true + tag = "" + url = vcs.tag("v#{$1}_#{$2}_#{$3}") + when /\A(\d+)\.(\d+)\z/ + url = vcs.branch("ruby_#{rev.tr('.', '_')}") else warn "#{$0}: unknown version - #{rev}" return From 06a6ad44f6faf35542e0bd1e15658340d449c2cf Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:42:23 -0800 Subject: [PATCH 1824/2435] make-snapshot: Remove an unnecessary variable This is a refactoring change, which should have no impact on behaviors. Now, if patchlevel is true, tag is empty. So `if patchlevel` always does nothing. Given that prerelease is false and tag is not nil, removing `if patchlevel` should have no impact. --- tool/make-snapshot | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 0a03fb2a227ae7..041b1a0f2aba6d 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -253,7 +253,6 @@ end def package(vcs, rev, destdir, tmp = nil) pwd = Dir.pwd - patchlevel = false prerelease = false if rev and revision = rev[/@(\h+)\z/, 1] rev = $` @@ -275,7 +274,6 @@ def package(vcs, rev, destdir, tmp = nil) tag = "#{$4}#{$5}" url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") when /\A(\d+)\.(\d+)\.(\d+)\z/ - patchlevel = true tag = "" url = vcs.tag("v#{$1}_#{$2}_#{$3}") when /\A(\d+)\.(\d+)\z/ @@ -351,13 +349,7 @@ def package(vcs, rev, destdir, tmp = nil) [api_major_version, api_minor_version, version_teeny].join('.') end version or return - if patchlevel - unless tag.empty? - versionhdr ||= File.read("#{v}/version.h") - patchlevel = versionhdr[/^\#define\s+RUBY_PATCHLEVEL\s+(\d+)/, 1] - tag = (patchlevel ? "p#{patchlevel}" : vcs.revision_name(revision)) - end - elsif prerelease + if prerelease versionhdr ||= File.read("#{v}/version.h") versionhdr.sub!(/^\#\s*define\s+RUBY_PATCHLEVEL_STR\s+"\K.+?(?=")/, tag) or raise "no match of RUBY_PATCHLEVEL_STR to replace" File.write("#{v}/version.h", versionhdr) From 1f0ca55750413603057fabef39550feb9e7fc3c8 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:59:26 -0800 Subject: [PATCH 1825/2435] make-snapshot: Update the tag format for Ruby 4.0+ (#15514) --- tool/make-snapshot | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 041b1a0f2aba6d..4af6a855ebb793 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -272,10 +272,18 @@ def package(vcs, rev, destdir, tmp = nil) when /\A(\d+)\.(\d+)\.(\d+)-(preview|rc)(\d+)/ prerelease = true tag = "#{$4}#{$5}" - url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") + if Integer($1) >= 4 + url = vcs.tag("v#{rev}") + else + url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") + end when /\A(\d+)\.(\d+)\.(\d+)\z/ tag = "" - url = vcs.tag("v#{$1}_#{$2}_#{$3}") + if Integer($1) >= 4 + url = vcs.tag("v#{rev}") + else + url = vcs.tag("v#{$1}_#{$2}_#{$3}") + end when /\A(\d+)\.(\d+)\z/ url = vcs.branch("ruby_#{rev.tr('.', '_')}") else From 3a76625915e57eb328d23ae5dd621d8bf45b30e0 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Thu, 11 Dec 2025 18:28:11 -0700 Subject: [PATCH 1826/2435] ZJIT: Don't specialize calls with kwsplat (#15513) --- test/ruby/test_zjit.rb | 9 +++++++++ zjit/src/hir.rs | 1 + zjit/src/hir/opt_tests.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 49b3616425ecd3..232c46079f5b38 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -626,6 +626,15 @@ def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) }, call_threshold: 2 end + def test_send_kwsplat + assert_compiles '3', %q{ + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + }, call_threshold: 2 + end + def test_send_kwrest assert_compiles '{a: 3}', %q{ def test(**kwargs) = kwargs diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4992f177991d47..1cdc5e003a7cf2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5229,6 +5229,7 @@ fn unspecializable_c_call_type(flags: u32) -> bool { /// If a given call uses overly complex arguments, then we won't specialize. fn unspecializable_call_type(flags: u32) -> bool { ((flags & VM_CALL_ARGS_SPLAT) != 0) || + ((flags & VM_CALL_KW_SPLAT) != 0) || ((flags & VM_CALL_ARGS_BLOCKARG) != 0) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index a602121a0cc51d..3946c9d03c0714 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2993,6 +2993,33 @@ mod hir_opt_tests { "); } + #[test] + fn dont_specialize_call_to_iseq_with_call_kwsplat() { + eval(" + def foo(a:) = a + def test = foo(**{a: 1}) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:HashExact = HashDup v11 + IncrCounter complex_arg_pass_caller_kw_splat + v14:BasicObject = SendWithoutBlock v6, :foo, v12 # SendFallbackReason: Complex argument passing + CheckInterrupts + Return v14 + "); + } + #[test] fn dont_specialize_call_to_iseq_with_param_kwrest() { eval(" From faac344d1698b3f6b7e2bd8afbca1b7fbc46b9df Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:45:07 -0800 Subject: [PATCH 1827/2435] make-snapshot: Fix Psych::DisallowedClass with newer psych ``` $ tool/format-release ../www.ruby-lang.org 4.0.0-preview2 . /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:99:in 'Psych::ClassLoader::Restricted#find': Tried to load unspecified class: Date (Psych::DisallowedClass) from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:28:in 'Psych::ClassLoader#load' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:40:in 'Psych::ClassLoader#date' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/scalar_scanner.rb:65:in 'Psych::ScalarScanner#tokenize' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:65:in 'Psych::Visitors::ToRuby#deserialize' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:129:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Scalar' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:346:in 'block in Psych::Visitors::ToRuby#revive_hash' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Array#each' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Enumerable#each_slice' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Psych::Visitors::ToRuby#revive_hash' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:168:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Mapping' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'block in Psych::Visitors::ToRuby#register_empty' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'Array#each' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'Psych::Visitors::ToRuby#register_empty' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:147:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Sequence' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:319:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Document' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych.rb:336:in 'Psych.safe_load' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych.rb:371:in 'Psych.load' from tool/format-release:80:in 'Tarball.parse' from tool/format-release:269:in 'Object#main' from tool/format-release:272:in '
' ``` --- tool/format-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/format-release b/tool/format-release index b38263f9f4fb05..0505df700b0946 100755 --- a/tool/format-release +++ b/tool/format-release @@ -77,7 +77,7 @@ eom end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" - info = YAML.load(URI(uri).read) + info = YAML.unsafe_load(URI(uri).read) if info.size != 1 raise "unexpected info.yml '#{uri}'" end From ec4c46709b74ae11a1d90ad3b2e8c9c79ee4afee Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:43:46 -0800 Subject: [PATCH 1828/2435] tool/format-release: Carve out the version format logic to share it with tool/releng/update-www-meta.rb and another place I'm going to modify next. --- tool/format-release | 39 ++++++---------------------------- tool/releng/update-www-meta.rb | 39 ++++++---------------------------- tool/ruby-version.rb | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 66 deletions(-) create mode 100644 tool/ruby-version.rb diff --git a/tool/format-release b/tool/format-release index 0505df700b0946..67c8760e58547b 100755 --- a/tool/format-release +++ b/tool/format-release @@ -9,6 +9,7 @@ end require "open-uri" require "yaml" +require_relative "./ruby-version" Diffy::Diff.default_options.merge!( include_diff_info: true, @@ -54,27 +55,7 @@ eom unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version raise "unexpected version string '#{version}'" end - x = $1.to_i - y = $2.to_i - z = $3.to_i - # previous tag for git diff --shortstat - # It's only for x.y.0 release - if z != 0 - prev_tag = nil - elsif y != 0 - prev_ver = "#{x}.#{y-1}.0" - prev_tag = version_tag(prev_ver) - else # y == 0 && z == 0 - case x - when 3 - prev_ver = "2.7.0" - when 4 - prev_ver = "3.4.0" - else - raise "it doesn't know what is the previous version of '#{version}'" - end - prev_tag = version_tag(prev_ver) - end + teeny = Integer($3) uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" info = YAML.unsafe_load(URI(uri).read) @@ -92,9 +73,10 @@ eom tarballs << tarball end - if prev_tag + if teeny == 0 # show diff shortstat - tag = version_tag(version) + tag = RubyVersion.tag(version) + prev_tag = RubyVersion.tag(RubyVersion.previous(version)) stat = `git -C #{rubydir} diff -l0 --shortstat #{prev_tag}..#{tag}` files_changed, insertions, deletions = stat.scan(/\d+/) end @@ -188,7 +170,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= <= 4 - "v#{version}" - else - "v#{version.tr('.-', '_')}" - end - end end def main diff --git a/tool/releng/update-www-meta.rb b/tool/releng/update-www-meta.rb index 100f0bee181806..0dd5b2563122e7 100755 --- a/tool/releng/update-www-meta.rb +++ b/tool/releng/update-www-meta.rb @@ -1,6 +1,7 @@ #!/usr/bin/env ruby require "open-uri" require "yaml" +require_relative "../ruby-version" class Tarball attr_reader :version, :size, :sha1, :sha256, :sha512 @@ -41,27 +42,7 @@ def self.parse(wwwdir, version) unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version raise "unexpected version string '#{version}'" end - x = $1.to_i - y = $2.to_i - z = $3.to_i - # previous tag for git diff --shortstat - # It's only for x.y.0 release - if z != 0 - prev_tag = nil - elsif y != 0 - prev_ver = "#{x}.#{y-1}.0" - prev_tag = version_tag(prev_ver) - else # y == 0 && z == 0 - case x - when 3 - prev_ver = "2.7.0" - when 4 - prev_ver = "3.4.0" - else - raise "it doesn't know what is the previous version of '#{version}'" - end - prev_tag = version_tag(prev_ver) - end + teeny = Integer($3) uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" info = YAML.load(URI(uri).read) @@ -79,9 +60,10 @@ def self.parse(wwwdir, version) tarballs << tarball end - if prev_tag + if teeny == 0 # show diff shortstat - tag = version_tag(version) + tag = RubyVersion.tag(version) + prev_tag = RubyVersion.tag(RubyVersion.previous(version)) rubydir = File.expand_path(File.join(__FILE__, '../../../')) puts %`git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` stat = `git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` @@ -160,7 +142,7 @@ def self.update_releases_yml(ver, xy, ary, wwwdir, files_changed, insertions, de date = Time.now.utc # use utc to use previous day in midnight entry = <= 4 - "v#{version}" - else - "v#{version.tr('.-', '_')}" - end - end end # Confirm current directory is www.ruby-lang.org's working directory diff --git a/tool/ruby-version.rb b/tool/ruby-version.rb new file mode 100644 index 00000000000000..aaaa345e75b356 --- /dev/null +++ b/tool/ruby-version.rb @@ -0,0 +1,37 @@ +module RubyVersion + def self.tag(version) + major_version = Integer(version.split('.', 2)[0]) + if major_version >= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end + + # Return the previous version to be used for release diff links. + # For a ".0" version, it returns the previous ".0" version. + # For a non-".0" version, it returns the previous teeny version. + def self.previous(version) + unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version + raise "unexpected version string '#{version}'" + end + major = Integer($1) + minor = Integer($2) + teeny = Integer($3) + + if teeny != 0 + "#{major}.#{minor}.#{teeny-1}" + elsif minor != 0 # && teeny == 0 + "#{major}.#{minor-1}.#{teeny}" + else # minor == 0 && teeny == 0 + case major + when 3 + "2.7.0" + when 4 + "3.4.0" + else + raise "it doesn't know what is the previous version of '#{version}'" + end + end + end +end From 8fba4b0f6008b68845be89861ddb73190d53511e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:54:48 -0800 Subject: [PATCH 1829/2435] tool/format-release: Fix a wrong method reference --- tool/format-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/format-release b/tool/format-release index 67c8760e58547b..8bb61542433fb2 100755 --- a/tool/format-release +++ b/tool/format-release @@ -170,7 +170,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= < Date: Thu, 11 Dec 2025 17:57:37 -0800 Subject: [PATCH 1830/2435] release.yml: Fix tag conversion for Ruby 4.0 and PREVIOUS_RELEASE_TAG for any .0 releases --- .github/workflows/release.yml | 4 ++-- tool/ruby-version.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) mode change 100644 => 100755 tool/ruby-version.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03227bcd7234fd..3caeee9a3b8796 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,9 +49,9 @@ jobs: - name: Create a release on GitHub run: | - RELEASE_TAG=$(echo v${{ env.RUBY_VERSION }} | sed 's/\./_/g') + RELEASE_TAG=$(ruby tool/ruby-version.rb tag "${{ env.RUBY_VERSION }}") echo $RELEASE_TAG - PREVIOUS_RELEASE_TAG=$(echo $RELEASE_TAG | awk 'BEGIN {FS="_"; OFS="_"}{ $NF=$NF-1; print }') + PREVIOUS_RELEASE_TAG=$(ruby tool/ruby-version.rb previous-tag "${{ env.RUBY_VERSION }}") echo $PREVIOUS_RELEASE_TAG tool/gen-github-release.rb $PREVIOUS_RELEASE_TAG $RELEASE_TAG --no-dry-run env: diff --git a/tool/ruby-version.rb b/tool/ruby-version.rb old mode 100644 new mode 100755 index aaaa345e75b356..3bbec576e13ad4 --- a/tool/ruby-version.rb +++ b/tool/ruby-version.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + module RubyVersion def self.tag(version) major_version = Integer(version.split('.', 2)[0]) @@ -35,3 +37,16 @@ def self.previous(version) end end end + +if __FILE__ == $0 + case ARGV[0] + when "tag" + print RubyVersion.tag(ARGV[1]) + when "previous" + print RubyVersion.previous(ARGV[1]) + when "previous-tag" + print RubyVersion.tag(RubyVersion.previous(ARGV[1])) + else + "#{$0}: unexpected command #{ARGV[0].inspect}" + end +end From 04494d9e4064a57accc8309da9b35754a3d24973 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 12:00:35 +0900 Subject: [PATCH 1831/2435] `Binding#local_variable_defined?` must not handle numbered parameters [Bug #21776] --- proc.c | 3 +++ test/ruby/test_proc.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/proc.c b/proc.c index 2df8fbee5e23a9..887fcae384b5fb 100644 --- a/proc.c +++ b/proc.c @@ -640,6 +640,9 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) const rb_env_t *env; if (!lid) return Qfalse; + if (rb_numparam_id_p(lid)) { + return Qfalse; + } GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 2cd97ca32464ab..d6bd8e724ee046 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1659,29 +1659,35 @@ def test_numparam_is_not_local_variables assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) "bar".tap do _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end end @@ -1690,31 +1696,39 @@ def test_it_is_not_local_variable it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end end From 50e5c542cc0541fb38e52766d88d87bd8a96b072 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 12:39:59 +0900 Subject: [PATCH 1832/2435] Win32: Remove the workaround for console reading bug It has been fixed at Windows 8, and we already have dropped the support Windows 8 and olders. --- win32/win32.c | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index 66ce195092f5e5..3d7e54932402da 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -7223,9 +7223,6 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) size_t len; size_t ret; OVERLAPPED ol; - BOOL isconsole; - BOOL islineinput = FALSE; - int start = 0; if (is_socket(sock)) return rb_w32_recv(fd, buf, size, 0); @@ -7248,25 +7245,8 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) } ret = 0; - isconsole = is_console(_osfhnd(fd)) && (osver.dwMajorVersion < 6 || (osver.dwMajorVersion == 6 && osver.dwMinorVersion < 2)); - if (isconsole) { - DWORD mode; - GetConsoleMode((HANDLE)_osfhnd(fd),&mode); - islineinput = (mode & ENABLE_LINE_INPUT) != 0; - } retry: - /* get rid of console reading bug */ - if (isconsole) { - constat_reset((HANDLE)_osfhnd(fd)); - if (start) - len = 1; - else { - len = 0; - start = 1; - } - } - else - len = size; + len = size; size -= len; if (setup_overlapped(&ol, fd, FALSE, offset)) { @@ -7337,8 +7317,7 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) ret += read; if (read >= len) { buf = (char *)buf + read; - if (err != ERROR_OPERATION_ABORTED && - !(isconsole && len == 1 && (!islineinput || *((char *)buf - 1) == '\n')) && size > 0) + if (err != ERROR_OPERATION_ABORTED && size > 0) goto retry; } if (read == 0) From f939f0433ab53bc1a8d567e0b52a09a95ce78bfb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 12:54:24 +0900 Subject: [PATCH 1833/2435] Win32: Deprecate Windows version info API `dwMajorVersion` alone has no meaning since Windows 7. Use API in VersionHelper.h instead. --- include/ruby/win32.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 8d9f9ddd80be6b..4255661dd49f2f 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -262,7 +262,6 @@ struct ifaddrs { #endif extern void rb_w32_sysinit(int *, char ***); -extern DWORD rb_w32_osid(void); extern int flock(int fd, int oper); extern int rb_w32_io_cancelable_p(int); extern int rb_w32_is_socket(int); @@ -306,7 +305,11 @@ extern void rb_w32_free_environ(char **); extern int rb_w32_map_errno(DWORD); extern const char *WSAAPI rb_w32_inet_ntop(int,const void *,char *,size_t); extern int WSAAPI rb_w32_inet_pton(int,const char *,void *); -extern DWORD rb_w32_osver(void); + +RBIMPL_ATTR_DEPRECATED(("as Windows 9x is not supported already")) +extern DWORD rb_w32_osid(void); +RBIMPL_ATTR_DEPRECATED(("by Windows Version Helper APIs")) +extern DWORD rb_w32_osver(void); extern int rb_w32_uchown(const char *, int, int); extern int rb_w32_ulink(const char *, const char *); From 5541c0d896d220923e795aa4f87ceb6237d53c4b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 13:04:09 +0900 Subject: [PATCH 1834/2435] Win32: Make `rb_w32_osid` return Windows NT always Since support for Windows 9x was dropped over a decade ago. --- include/ruby/win32.h | 2 +- win32/win32.c | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 4255661dd49f2f..ae11a61481e74e 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -307,7 +307,7 @@ extern const char *WSAAPI rb_w32_inet_ntop(int,const void *,char *,size_t); extern int WSAAPI rb_w32_inet_pton(int,const char *,void *); RBIMPL_ATTR_DEPRECATED(("as Windows 9x is not supported already")) -extern DWORD rb_w32_osid(void); +static inline DWORD rb_w32_osid(void) {return VER_PLATFORM_WIN32_NT;} RBIMPL_ATTR_DEPRECATED(("by Windows Version Helper APIs")) extern DWORD rb_w32_osver(void); diff --git a/win32/win32.c b/win32/win32.c index 3d7e54932402da..97dc7148085c55 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -314,15 +314,6 @@ get_version(void) GetVersionEx(&osver); } -#ifdef _M_IX86 -/* License: Artistic or GPL */ -DWORD -rb_w32_osid(void) -{ - return osver.dwPlatformId; -} -#endif - /* License: Artistic or GPL */ DWORD rb_w32_osver(void) From 1794cfe12fe61dedebadead542927f9fef4104eb Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 14:44:09 +0900 Subject: [PATCH 1835/2435] Binding#local_variable_defined? raises a NameError for numbered params. [Bug #21776] --- proc.c | 3 ++- test/ruby/test_proc.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/proc.c b/proc.c index 887fcae384b5fb..1a57757d846ceb 100644 --- a/proc.c +++ b/proc.c @@ -641,7 +641,8 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) if (!lid) return Qfalse; if (rb_numparam_id_p(lid)) { - return Qfalse; + rb_name_err_raise("numbered parameter '%1$s' is not a local variable", + bindval, ID2SYM(lid)); } GetBindingPtr(bindval, bind); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index d6bd8e724ee046..2eeb7a94ebb706 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1659,35 +1659,35 @@ def test_numparam_is_not_local_variables assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } "bar".tap do _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end end From 5ef4f88d5e0cea9a36702f8165a4d47a11c2a703 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 12 Dec 2025 15:01:27 +0900 Subject: [PATCH 1836/2435] use `ractor_sched_lock` instead of using `rb_native_mutex_lock` directly. --- thread_pthread_mn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 2211d4d1067c39..ad4d0915e790b2 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -1078,12 +1078,12 @@ timer_thread_polling(rb_vm_t *vm) switch (r) { case 0: // timeout - rb_native_mutex_lock(&vm->ractor.sched.lock); + ractor_sched_lock(vm, NULL); { // (1-1) timeslice timer_thread_check_timeslice(vm); } - rb_native_mutex_unlock(&vm->ractor.sched.lock); + ractor_sched_unlock(vm, NULL); break; case -1: // error From d9cc7621f39130c7c678af9eb72f7a28b539be9f Mon Sep 17 00:00:00 2001 From: git Date: Fri, 12 Dec 2025 06:55:08 +0000 Subject: [PATCH 1837/2435] Update bundled gems list as of 2025-12-12 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3e2d37c84cfda9..cd214a365b4597 100644 --- a/NEWS.md +++ b/NEWS.md @@ -275,7 +275,7 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.26.2 +* minitest 5.27.0 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index bdda3311d0eb07..d71137d3e11fab 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.26.2 https://github.com/minitest/minitest +minitest 5.27.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.3 https://github.com/test-unit/test-unit From 175a0d5f1ad545d5f722e959a766eeed52e29ca1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 12 Dec 2025 09:35:55 +0100 Subject: [PATCH 1838/2435] [ruby/timeout] Restore original signal handler in test_timeout_in_trap_handler https://github.com/ruby/timeout/commit/4ae8631acf --- test/test_timeout.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 91940085c46ac3..fead81f571929d 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -427,9 +427,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - signal = Signal.list["USR1"] ? :USR1 : :TERM + signal = :TERM - trap(signal) do + original_handler = trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -444,9 +444,13 @@ def test_timeout_in_trap_handler end end - Process.kill signal, Process.pid + begin + Process.kill signal, Process.pid - assert_equal "OK", rd.read - rd.close + assert_equal "OK", rd.read + rd.close + ensure + trap(signal, original_handler) + end end end From 0022a87800f7355419b9bf2c1995b4c3dce832df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:08:25 +0000 Subject: [PATCH 1839/2435] Bump actions/cache in /.github/actions/setup/directories Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...a7833574556fa59680c1b7cb190c1735db73ebf0) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 916e8b5acd0c4c..21cc817cba81cb 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -100,7 +100,7 @@ runs: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache From cf97a14c78cf18bc98ff49b6d4b6bd337daeab60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:03:43 +0000 Subject: [PATCH 1840/2435] Bump actions/cache from 4.3.0 to 5.0.0 Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...a7833574556fa59680c1b7cb190c1735db73ebf0) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 67ea1cacae2cf1..1230af953e03c0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -88,7 +88,7 @@ jobs: - name: Restore vcpkg artifact id: restore-vcpkg - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -100,7 +100,7 @@ jobs: if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - name: Save vcpkg artifact - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} From 7e7a1db579dad504a26e42d5f3efa5b2968389af Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 12 Dec 2025 08:55:45 +0100 Subject: [PATCH 1841/2435] Define Thread::ConditionVariable in thread_sync.rb It's more consistent with Mutex, but also the `#wait` method benefits from receiving the execution context instead of having to call `GET_EC`. --- thread_sync.c | 75 ++++++++++++-------------------------------------- thread_sync.rb | 44 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 9942d08e0a5c8f..e54963a4fe3178 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -1560,21 +1560,8 @@ condvar_alloc(VALUE klass) return obj; } -/* - * Document-method: ConditionVariable::new - * - * Creates a new condition variable instance. - */ - -static VALUE -rb_condvar_initialize(VALUE self) -{ - struct rb_condvar *cv = condvar_ptr(self); - ccan_list_head_init(&cv->waitq); - return self; -} - struct sleep_call { + rb_execution_context_t *ec; VALUE mutex; VALUE timeout; }; @@ -1585,65 +1572,44 @@ static VALUE do_sleep(VALUE args) { struct sleep_call *p = (struct sleep_call *)args; - return rb_funcallv(p->mutex, id_sleep, 1, &p->timeout); + if (CLASS_OF(p->mutex) == rb_cMutex) { + return rb_mut_sleep(p->ec, p->mutex, p->timeout); + } + else { + return rb_funcallv(p->mutex, id_sleep, 1, &p->timeout); + } } -/* - * Document-method: Thread::ConditionVariable#wait - * call-seq: wait(mutex, timeout=nil) - * - * Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup. - * - * If +timeout+ is given, this method returns after +timeout+ seconds passed, - * even if no other thread doesn't signal. - * - * This method may wake up spuriously due to underlying implementation details. - * - * Returns the slept result on +mutex+. - */ - static VALUE -rb_condvar_wait(int argc, VALUE *argv, VALUE self) +rb_condvar_wait(rb_execution_context_t *ec, VALUE self, VALUE mutex, VALUE timeout) { - rb_execution_context_t *ec = GET_EC(); - struct rb_condvar *cv = condvar_ptr(self); - struct sleep_call args; - - rb_scan_args(argc, argv, "11", &args.mutex, &args.timeout); + struct sleep_call args = { + .ec = ec, + .mutex = mutex, + .timeout = timeout, + }; struct sync_waiter sync_waiter = { - .self = args.mutex, + .self = mutex, .th = ec->thread_ptr, .fiber = nonblocking_fiber(ec->fiber_ptr) }; ccan_list_add_tail(&cv->waitq, &sync_waiter.node); - return rb_ensure(do_sleep, (VALUE)&args, delete_from_waitq, (VALUE)&sync_waiter); + return rb_ec_ensure(ec, do_sleep, (VALUE)&args, delete_from_waitq, (VALUE)&sync_waiter); } -/* - * Document-method: Thread::ConditionVariable#signal - * - * Wakes up the first thread in line waiting for this lock. - */ - static VALUE -rb_condvar_signal(VALUE self) +rb_condvar_signal(rb_execution_context_t *ec, VALUE self) { struct rb_condvar *cv = condvar_ptr(self); wakeup_one(&cv->waitq); return self; } -/* - * Document-method: Thread::ConditionVariable#broadcast - * - * Wakes up all threads waiting for this lock. - */ - static VALUE -rb_condvar_broadcast(VALUE self) +rb_condvar_broadcast(rb_execution_context_t *ec, VALUE self) { struct rb_condvar *cv = condvar_ptr(self); wakeup_all(&cv->waitq); @@ -1726,13 +1692,6 @@ Init_thread_sync(void) id_sleep = rb_intern("sleep"); - rb_define_method(rb_cConditionVariable, "initialize", rb_condvar_initialize, 0); - rb_undef_method(rb_cConditionVariable, "initialize_copy"); - rb_define_method(rb_cConditionVariable, "marshal_dump", undumpable, 0); - rb_define_method(rb_cConditionVariable, "wait", rb_condvar_wait, -1); - rb_define_method(rb_cConditionVariable, "signal", rb_condvar_signal, 0); - rb_define_method(rb_cConditionVariable, "broadcast", rb_condvar_broadcast, 0); - rb_provide("thread.rb"); } diff --git a/thread_sync.rb b/thread_sync.rb index 28c70b1e9ce8fb..a722b7ec1a2595 100644 --- a/thread_sync.rb +++ b/thread_sync.rb @@ -148,4 +148,48 @@ def sleep(timeout = nil) Primitive.rb_mut_sleep(timeout) end end + + class ConditionVariable + # Document-method: ConditionVariable::new + # + # Creates a new condition variable instance. + def initialize + end + + undef_method :initialize_copy + + # :nodoc: + def marshal_dump + raise TypeError, "can't dump #{self.class}" + end + + # Document-method: Thread::ConditionVariable#signal + # + # Wakes up the first thread in line waiting for this lock. + def signal + Primitive.rb_condvar_signal + end + + # Document-method: Thread::ConditionVariable#broadcast + # + # Wakes up all threads waiting for this lock. + def broadcast + Primitive.rb_condvar_broadcast + end + + # Document-method: Thread::ConditionVariable#wait + # call-seq: wait(mutex, timeout=nil) + # + # Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup. + # + # If +timeout+ is given, this method returns after +timeout+ seconds passed, + # even if no other thread doesn't signal. + # + # This method may wake up spuriously due to underlying implementation details. + # + # Returns the slept result on +mutex+. + def wait(mutex, timeout=nil) + Primitive.rb_condvar_wait(mutex, timeout) + end + end end From ff831eb0572b2d8f794acca478ea77c7bfefbc61 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 12 Dec 2025 09:10:04 +0100 Subject: [PATCH 1842/2435] thead_sync.c: directly pass the execution context to yield Saves one more call to GET_EC() --- internal/vm.h | 1 + thread_sync.c | 9 +++++++-- vm_eval.c | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/vm.h b/internal/vm.h index 7fae590d198fdd..09dfaf182e9a92 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -69,6 +69,7 @@ const char *rb_type_str(enum ruby_value_type type); VALUE rb_check_funcall_default(VALUE, ID, int, const VALUE *, VALUE); VALUE rb_check_funcall_basic_kw(VALUE, ID, VALUE, int, const VALUE*, int); VALUE rb_yield_1(VALUE val); +VALUE rb_ec_yield(struct rb_execution_context_struct *ec, VALUE val); VALUE rb_yield_force_blockarg(VALUE values); VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, int min_argc, int max_argc, diff --git a/thread_sync.c b/thread_sync.c index e54963a4fe3178..6af6aaddd6241d 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -650,7 +650,6 @@ rb_mutex_sleep(VALUE self, VALUE timeout) return rb_mut_sleep(GET_EC(), self, timeout); } - VALUE rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) { @@ -660,6 +659,12 @@ rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) return rb_ec_ensure(args.ec, func, arg, do_mutex_unlock_safe, (VALUE)&args); } +static VALUE +do_ec_yield(VALUE _ec) +{ + return rb_ec_yield((rb_execution_context_t *)_ec, Qundef); +} + VALUE rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) { @@ -669,7 +674,7 @@ rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) .ec = ec, }; do_mutex_lock(&args, 1); - return rb_ec_ensure(args.ec, rb_yield, Qundef, do_mutex_unlock_safe, (VALUE)&args); + return rb_ec_ensure(args.ec, do_ec_yield, (VALUE)ec, do_mutex_unlock_safe, (VALUE)&args); } void diff --git a/vm_eval.c b/vm_eval.c index 12bdabc3302f68..34560d704a15a5 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1379,6 +1379,17 @@ rb_yield(VALUE val) } } +VALUE +rb_ec_yield(rb_execution_context_t *ec, VALUE val) +{ + if (UNDEF_P(val)) { + return vm_yield(ec, 0, NULL, RB_NO_KEYWORDS); + } + else { + return vm_yield(ec, 1, &val, RB_NO_KEYWORDS); + } +} + #undef rb_yield_values VALUE rb_yield_values(int n, ...) From e2fe0aae43fee4815c1fc0896a2f03de35bfd873 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 12 Dec 2025 22:12:41 +1300 Subject: [PATCH 1843/2435] Avoid race condition in `test_without_handle_interrupt_signal_works`. (#15504) --- ext/-test-/scheduler/scheduler.c | 13 ++++++++----- .../scheduler/test_interrupt_with_scheduler.rb | 9 +++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/-test-/scheduler/scheduler.c b/ext/-test-/scheduler/scheduler.c index f3badceb92fcc6..f8384f597e35bb 100644 --- a/ext/-test-/scheduler/scheduler.c +++ b/ext/-test-/scheduler/scheduler.c @@ -1,5 +1,6 @@ #include "ruby/ruby.h" #include "ruby/thread.h" +#include "ruby/io.h" #include "ruby/fiber/scheduler.h" /* @@ -24,6 +25,7 @@ */ struct blocking_state { + int notify_descriptor; volatile int interrupted; }; @@ -39,14 +41,14 @@ blocking_operation(void *argument) { struct blocking_state *blocking_state = (struct blocking_state *)argument; - while (true) { - struct timeval tv = {1, 0}; // 1 second timeout. + write(blocking_state->notify_descriptor, "x", 1); + while (!blocking_state->interrupted) { + struct timeval tv = {1, 0}; // 1 second timeout. int result = select(0, NULL, NULL, NULL, &tv); if (result == -1 && errno == EINTR) { blocking_state->interrupted = 1; - return NULL; } // Otherwise, timeout -> loop again. @@ -56,9 +58,10 @@ blocking_operation(void *argument) } static VALUE -scheduler_blocking_loop(VALUE self) +scheduler_blocking_loop(VALUE self, VALUE notify) { struct blocking_state blocking_state = { + .notify_descriptor = rb_io_descriptor(notify), .interrupted = 0, }; @@ -84,5 +87,5 @@ Init_scheduler(void) VALUE mBug = rb_define_module("Bug"); VALUE mScheduler = rb_define_module_under(mBug, "Scheduler"); - rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 0); + rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 1); } diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb index 42a109b98b51a6..eb7a0647e581b9 100644 --- a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb +++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb @@ -26,23 +26,20 @@ def test_without_handle_interrupt_signal_works # Yield to the scheduler: sleep(0) - output.puts "ready" - Bug::Scheduler.blocking_loop + Bug::Scheduler.blocking_loop(output) end end output.close - assert_equal "ready\n", input.gets + assert_equal "x", input.read(1) - sleep 0.1 # Ensure the child is in the blocking loop - # $stderr.puts "Sending interrupt" Process.kill(:INT, pid) reaper = Thread.new do Process.waitpid2(pid) end - unless reaper.join(1) + unless reaper.join(10) Process.kill(:KILL, pid) end From d428d086c23219090d68eb2d027498c6ea999b89 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 12 Dec 2025 18:14:50 +0900 Subject: [PATCH 1844/2435] Simplify the code `thread_sched_to_waiting_common0` is no longer needed. --- thread_pthread.c | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 0a19f7f0310af1..66323598607398 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -972,33 +972,16 @@ thread_sched_wakeup_next_thread(struct rb_thread_sched *sched, rb_thread_t *th, } } -// running -> waiting -// -// to_dead: false -// th will run dedicated task. -// run another ready thread. -// to_dead: true -// th will be dead. -// run another ready thread. +// running -> dead (locked) static void -thread_sched_to_waiting_common0(struct rb_thread_sched *sched, rb_thread_t *th, bool to_dead) +thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) { - RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - - if (!to_dead) native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); - RUBY_DEBUG_LOG("%sth:%u", to_dead ? "to_dead " : "", rb_th_serial(th)); + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - bool can_switch = to_dead ? !th_has_dedicated_nt(th) : false; - thread_sched_wakeup_next_thread(sched, th, can_switch); -} + thread_sched_wakeup_next_thread(sched, th, !th_has_dedicated_nt(th)); -// running -> dead (locked) -static void -thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) -{ - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, true); RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_EXITED, th); } @@ -1019,8 +1002,12 @@ thread_sched_to_dead(struct rb_thread_sched *sched, rb_thread_t *th) static void thread_sched_to_waiting_common(struct rb_thread_sched *sched, rb_thread_t *th) { - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, false); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); + + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); + + native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + thread_sched_wakeup_next_thread(sched, th, false); } // running -> waiting From fa7cddc969f1eccbb377cfc752bbf82ee2887dde Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 14:53:17 +0900 Subject: [PATCH 1845/2435] Add Binding#implicit_parameters, etc. This changeset introduces: * `Binding#implicit_parameters` * `Binding#implicit_parameter_get` * `Binding#implicit_parameter_defined?` [Bug #21049] --- proc.c | 86 +++++++++++++++++++++++++++++++++++++++--- test/ruby/test_proc.rb | 54 ++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/proc.c b/proc.c index 1a57757d846ceb..ac56218e7aaa85 100644 --- a/proc.c +++ b/proc.c @@ -409,7 +409,7 @@ bind_eval(int argc, VALUE *argv, VALUE bindval) } static const VALUE * -get_local_variable_ptr(const rb_env_t **envp, ID lid) +get_local_variable_ptr(const rb_env_t **envp, ID lid, bool search_outer) { const rb_env_t *env = *envp; do { @@ -446,7 +446,7 @@ get_local_variable_ptr(const rb_env_t **envp, ID lid) *envp = NULL; return NULL; } - } while ((env = rb_vm_env_prev_env(env)) != NULL); + } while (search_outer && (env = rb_vm_env_prev_env(env)) != NULL); *envp = NULL; return NULL; @@ -548,7 +548,7 @@ bind_local_variable_get(VALUE bindval, VALUE sym) GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - if ((ptr = get_local_variable_ptr(&env, lid)) != NULL) { + if ((ptr = get_local_variable_ptr(&env, lid, TRUE)) != NULL) { return *ptr; } @@ -600,7 +600,7 @@ bind_local_variable_set(VALUE bindval, VALUE sym, VALUE val) GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - if ((ptr = get_local_variable_ptr(&env, lid)) == NULL) { + if ((ptr = get_local_variable_ptr(&env, lid, TRUE)) == NULL) { /* not found. create new env */ ptr = rb_binding_add_dynavars(bindval, bind, 1, &lid); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); @@ -647,7 +647,80 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - return RBOOL(get_local_variable_ptr(&env, lid)); + return RBOOL(get_local_variable_ptr(&env, lid, TRUE)); +} + +/* + * call-seq: + * binding.implicit_parameters -> Array + * + * TODO + */ +static VALUE +bind_implicit_parameters(VALUE bindval) +{ + const rb_binding_t *bind; + const rb_env_t *env; + + // TODO: it + + GetBindingPtr(bindval, bind); + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + return rb_vm_env_numbered_parameters(env); +} + +/* + * call-seq: + * binding.implicit_parameter_get(symbol) -> obj + * + * TODO + */ +static VALUE +bind_implicit_parameter_get(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + const VALUE *ptr; + const rb_env_t *env; + + if (!lid || !rb_numparam_id_p(lid)) { + rb_name_err_raise("'%1$s' is not an implicit parameter", + bindval, ID2SYM(lid)); + } + + GetBindingPtr(bindval, bind); + + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + if ((ptr = get_local_variable_ptr(&env, lid, FALSE)) != NULL) { + return *ptr; + } + + rb_name_err_raise("implicit parameter '%1$s' is not defined for %2$s", bindval, ID2SYM(lid)); + UNREACHABLE_RETURN(Qundef); +} + +/* + * call-seq: + * binding.implicit_parameter_defined?(symbol) -> obj + * + * TODO + * + */ +static VALUE +bind_implicit_parameter_defined_p(VALUE bindval, VALUE sym) +{ + ID lid = check_local_id(bindval, &sym); + const rb_binding_t *bind; + const rb_env_t *env; + + if (!lid || !rb_numparam_id_p(lid)) { + rb_name_err_raise("'%1$s' is not an implicit parameter", + bindval, ID2SYM(lid)); + } + + GetBindingPtr(bindval, bind); + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + return RBOOL(get_local_variable_ptr(&env, lid, FALSE)); } /* @@ -4607,6 +4680,9 @@ Init_Binding(void) rb_define_method(rb_cBinding, "local_variable_get", bind_local_variable_get, 1); rb_define_method(rb_cBinding, "local_variable_set", bind_local_variable_set, 2); rb_define_method(rb_cBinding, "local_variable_defined?", bind_local_variable_defined_p, 1); + rb_define_method(rb_cBinding, "implicit_parameters", bind_implicit_parameters, 0); + rb_define_method(rb_cBinding, "implicit_parameter_get", bind_implicit_parameter_get, 1); + rb_define_method(rb_cBinding, "implicit_parameter_defined?", bind_implicit_parameter_defined_p, 1); rb_define_method(rb_cBinding, "receiver", bind_receiver, 0); rb_define_method(rb_cBinding, "source_location", bind_location, 0); rb_define_global_function("binding", rb_f_binding, 0); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 2eeb7a94ebb706..d6b2d804698644 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1691,6 +1691,60 @@ def test_numparam_is_not_local_variables end end + def test_implicit_parameters + x = x = 1 + assert_raise(NameError) { binding.implicit_parameter_get(:x) } + assert_raise(NameError) { binding.implicit_parameter_defined?(:x) } + + "foo".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + def test_it_is_not_local_variable "foo".tap do it From 129d74c96b0b8f1b93704b34e66d2c18f4835e1b Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 16:43:25 +0900 Subject: [PATCH 1846/2435] Binding#implicit_parameters, etc. support the implicit "it" parameter [Bug #21049] --- defs/id.def | 2 + iseq.c | 13 +++-- parse.y | 4 +- prism_compile.c | 3 +- proc.c | 23 ++++++-- test/ruby/test_proc.rb | 129 ++++++++++++++++++++++++++++++++++++++++- 6 files changed, 159 insertions(+), 15 deletions(-) diff --git a/defs/id.def b/defs/id.def index 0c32b0d1d4ab97..cd3a8480a241df 100644 --- a/defs/id.def +++ b/defs/id.def @@ -78,6 +78,8 @@ firstline, predefined = __LINE__+1, %[\ _7 NUMPARAM_7 _8 NUMPARAM_8 _9 NUMPARAM_9 + ItImplicit + it It "/*NULL*/" NULL empty? diff --git a/iseq.c b/iseq.c index b53362b2444709..726501d45cd27e 100644 --- a/iseq.c +++ b/iseq.c @@ -19,6 +19,7 @@ #endif #include "eval_intern.h" +#include "id.h" #include "id_table.h" #include "internal.h" #include "internal/bits.h" @@ -3363,7 +3364,7 @@ iseq_data_to_ary(const rb_iseq_t *iseq) for (i=0; ilocal_table_size; i++) { ID lid = iseq_body->local_table[i]; if (lid) { - if (rb_id2str(lid)) { + if (lid != idItImplicit && rb_id2str(lid)) { rb_ary_push(locals, ID2SYM(lid)); } else { /* hidden variable from id_internal() */ @@ -3673,10 +3674,10 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) ID req, opt, rest, block, key, keyrest; #define PARAM_TYPE(type) rb_ary_push(a = rb_ary_new2(2), ID2SYM(type)) #define PARAM_ID(i) body->local_table[(i)] -#define PARAM(i, type) ( \ - PARAM_TYPE(type), \ - rb_id2str(PARAM_ID(i)) ? \ - rb_ary_push(a, ID2SYM(PARAM_ID(i))) : \ +#define PARAM(i, type) ( \ + PARAM_TYPE(type), \ + PARAM_ID(i) != idItImplicit && rb_id2str(PARAM_ID(i)) ? \ + rb_ary_push(a, ID2SYM(PARAM_ID(i))) : \ a) CONST_ID(req, "req"); @@ -3695,7 +3696,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) if (is_proc) { for (i = 0; i < body->param.lead_num; i++) { PARAM_TYPE(opt); - if (rb_id2str(PARAM_ID(i))) { + if (PARAM_ID(i) != idItImplicit && rb_id2str(PARAM_ID(i))) { rb_ary_push(a, ID2SYM(PARAM_ID(i))); } rb_ary_push(args, a); diff --git a/parse.y b/parse.y index 2a6de39236dc4c..496dc21b11baec 100644 --- a/parse.y +++ b/parse.y @@ -13035,14 +13035,14 @@ gettable(struct parser_params *p, ID id, const YYLTYPE *loc) } # endif /* method call without arguments */ - if (dyna_in_block(p) && id == rb_intern("it") && !(DVARS_TERMINAL_P(p->lvtbl->args) || DVARS_TERMINAL_P(p->lvtbl->args->prev))) { + if (dyna_in_block(p) && id == idIt && !(DVARS_TERMINAL_P(p->lvtbl->args) || DVARS_TERMINAL_P(p->lvtbl->args->prev))) { if (numparam_used_p(p)) return 0; if (p->max_numparam == ORDINAL_PARAM) { compile_error(p, "ordinary parameter is defined"); return 0; } if (!p->it_id) { - p->it_id = internal_id(p); + p->it_id = idItImplicit; vtable_add(p->lvtbl->args, p->it_id); } NODE *node = NEW_DVAR(p->it_id, loc); diff --git a/prism_compile.c b/prism_compile.c index d3c66691a8325b..77b940ce6c6008 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6398,8 +6398,7 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod } if (scope_node->parameters != NULL && PM_NODE_TYPE_P(scope_node->parameters, PM_IT_PARAMETERS_NODE)) { - ID local = rb_make_temporary_id(local_index); - local_table_for_iseq->ids[local_index++] = local; + local_table_for_iseq->ids[local_index++] = idItImplicit; } // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) diff --git a/proc.c b/proc.c index ac56218e7aaa85..01864794e8b0ca 100644 --- a/proc.c +++ b/proc.c @@ -514,6 +514,12 @@ rb_numparam_id_p(ID id) return (tNUMPARAM_1 << ID_SCOPE_SHIFT) <= id && id < ((tNUMPARAM_1 + 9) << ID_SCOPE_SHIFT); } +int +rb_implicit_param_p(ID id) +{ + return id == idItImplicit || rb_numparam_id_p(id); +} + /* * call-seq: * binding.local_variable_get(symbol) -> obj @@ -662,9 +668,13 @@ bind_implicit_parameters(VALUE bindval) const rb_binding_t *bind; const rb_env_t *env; - // TODO: it - GetBindingPtr(bindval, bind); + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); + + if (RBOOL(get_local_variable_ptr(&env, idItImplicit, FALSE))) { + return rb_ary_new_from_args(1, ID2SYM(idIt)); + } + env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); return rb_vm_env_numbered_parameters(env); } @@ -683,7 +693,9 @@ bind_implicit_parameter_get(VALUE bindval, VALUE sym) const VALUE *ptr; const rb_env_t *env; - if (!lid || !rb_numparam_id_p(lid)) { + if (lid == idIt) lid = idItImplicit; + + if (!lid || !rb_implicit_param_p(lid)) { rb_name_err_raise("'%1$s' is not an implicit parameter", bindval, ID2SYM(lid)); } @@ -695,6 +707,7 @@ bind_implicit_parameter_get(VALUE bindval, VALUE sym) return *ptr; } + if (lid == idItImplicit) lid = idIt; rb_name_err_raise("implicit parameter '%1$s' is not defined for %2$s", bindval, ID2SYM(lid)); UNREACHABLE_RETURN(Qundef); } @@ -713,7 +726,9 @@ bind_implicit_parameter_defined_p(VALUE bindval, VALUE sym) const rb_binding_t *bind; const rb_env_t *env; - if (!lid || !rb_numparam_id_p(lid)) { + if (lid == idIt) lid = idItImplicit; + + if (!lid || !rb_implicit_param_p(lid)) { rb_name_err_raise("'%1$s' is not an implicit parameter", bindval, ID2SYM(lid)); } diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index d6b2d804698644..9ac1875234814b 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1691,7 +1691,7 @@ def test_numparam_is_not_local_variables end end - def test_implicit_parameters + def test_implicit_parameters_for_numparams x = x = 1 assert_raise(NameError) { binding.implicit_parameter_get(:x) } assert_raise(NameError) { binding.implicit_parameter_defined?(:x) } @@ -1702,23 +1702,29 @@ def test_implicit_parameters assert_equal("foo", binding.implicit_parameter_get(:_1)) assert_equal(nil, binding.implicit_parameter_get(:_5)) assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } assert_equal(true, binding.implicit_parameter_defined?(:_1)) assert_equal(true, binding.implicit_parameter_defined?(:_5)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) "bar".tap do assert_equal([], binding.implicit_parameters) assert_raise(NameError) { binding.implicit_parameter_get(:_1) } assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } assert_equal(false, binding.implicit_parameter_defined?(:_1)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) end assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) assert_equal("foo", binding.implicit_parameter_get(:_1)) assert_equal(nil, binding.implicit_parameter_get(:_5)) assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } assert_equal(true, binding.implicit_parameter_defined?(:_1)) assert_equal(true, binding.implicit_parameter_defined?(:_5)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) end "foo".tap do @@ -1727,21 +1733,25 @@ def test_implicit_parameters assert_raise(NameError) { binding.implicit_parameter_get(:_6) } assert_equal(false, binding.implicit_parameter_defined?(:_1)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) "bar".tap do _5 and flunk assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) assert_equal("bar", binding.implicit_parameter_get(:_1)) assert_equal(nil, binding.implicit_parameter_get(:_5)) assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } assert_equal(true, binding.implicit_parameter_defined?(:_1)) assert_equal(true, binding.implicit_parameter_defined?(:_5)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) end assert_equal([], binding.implicit_parameters) assert_raise(NameError) { binding.implicit_parameter_get(:_1) } assert_raise(NameError) { binding.implicit_parameter_get(:_6) } assert_equal(false, binding.implicit_parameter_defined?(:_1)) assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) end end @@ -1786,6 +1796,123 @@ def test_it_is_not_local_variable end end + def test_implicit_parameters_for_it + "foo".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + end + + def test_implicit_parameters_for_it_complex + "foo".tap do + it = "bar" + + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + it = "bar" + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + end + + def test_implicit_parameters_for_it_and_numparams + "foo".tap do + it or flunk + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + + "foo".tap do + _5 and flunk + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_5) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + end + def test_local_variable_set_wb assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30) b = binding From 04422384ddf57159321f7fb214c457a378f65837 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 16:52:26 +0900 Subject: [PATCH 1847/2435] Add docs to Binding#numbered_parameters, etc. --- proc.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/proc.c b/proc.c index 01864794e8b0ca..23b6bf1a9a6f4d 100644 --- a/proc.c +++ b/proc.c @@ -660,7 +660,21 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) * call-seq: * binding.implicit_parameters -> Array * - * TODO + * Returns the names of numbered parameters and "it" parameter + * that are defined in the binding. + * + * def foo + * [42].each do + * it + * binding.implicit_parameters #=> [:it] + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameters #=> [:_1, :_2] + * end + * end + * */ static VALUE bind_implicit_parameters(VALUE bindval) @@ -683,7 +697,21 @@ bind_implicit_parameters(VALUE bindval) * call-seq: * binding.implicit_parameter_get(symbol) -> obj * - * TODO + * Returns the value of the numbered parameter or "it" parameter. + * + * def foo + * [42].each do + * it + * binding.implicit_parameter_get(:it) #=> 42 + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameter_get(:_1) #=> :k + * binding.implicit_parameter_get(:_2) #=> 42 + * end + * end + * */ static VALUE bind_implicit_parameter_get(VALUE bindval, VALUE sym) @@ -716,7 +744,23 @@ bind_implicit_parameter_get(VALUE bindval, VALUE sym) * call-seq: * binding.implicit_parameter_defined?(symbol) -> obj * - * TODO + * Returns +true+ if the numbered parameter or "it" parameter exists. + * + * def foo + * [42].each do + * it + * binding.implicit_parameter_defined?(:it) #=> true + * binding.implicit_parameter_defined?(:_1) #=> false + * end + * + * { k: 42 }.each do + * _2 + * binding.implicit_parameter_defined?(:_1) #=> true + * binding.implicit_parameter_defined?(:_2) #=> true + * binding.implicit_parameter_defined?(:_3) #=> false + * binding.implicit_parameter_defined?(:it) #=> false + * end + * end * */ static VALUE From f939cf40ba46f3a8136495702793748fa30c12c3 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 22:54:31 +0900 Subject: [PATCH 1848/2435] Update NEWS about `Binding#implicit_parameters`, etc. [Bug #21049] --- NEWS.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index cd214a365b4597..09fc053ef0b478 100644 --- a/NEWS.md +++ b/NEWS.md @@ -61,8 +61,13 @@ Note: We're only listing outstanding class updates. * Binding * `Binding#local_variables` does no longer include numbered parameters. - Also, `Binding#local_variable_get` and `Binding#local_variable_set` reject - to handle numbered parameters. [[Bug #21049]] + Also, `Binding#local_variable_get`, `Binding#local_variable_set`, and + `Binding#local_variable_defined?` reject to handle numbered parameters. + [[Bug #21049]] + + * `Binding#implicit_parameters`, `Binding#implicit_parameter_get`, and + `Binding#implicit_parameter_defined?` have been added to access + numbered parameters and "it" parameter. [[Bug #21049]] * File From b8ba9cebb9472c125c946e36ea4455e7aae284a6 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 13 Dec 2025 01:40:49 +0900 Subject: [PATCH 1849/2435] Fix binding.implicit_parameters_get/defined segfault when wrong name string is passed (#15530) --- proc.c | 4 ++-- test/ruby/test_proc.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/proc.c b/proc.c index 23b6bf1a9a6f4d..9cd4c5b0c9f488 100644 --- a/proc.c +++ b/proc.c @@ -725,7 +725,7 @@ bind_implicit_parameter_get(VALUE bindval, VALUE sym) if (!lid || !rb_implicit_param_p(lid)) { rb_name_err_raise("'%1$s' is not an implicit parameter", - bindval, ID2SYM(lid)); + bindval, sym); } GetBindingPtr(bindval, bind); @@ -774,7 +774,7 @@ bind_implicit_parameter_defined_p(VALUE bindval, VALUE sym) if (!lid || !rb_implicit_param_p(lid)) { rb_name_err_raise("'%1$s' is not an implicit parameter", - bindval, ID2SYM(lid)); + bindval, sym); } GetBindingPtr(bindval, bind); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 9ac1875234814b..92cdfc6757da22 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1913,6 +1913,14 @@ def test_implicit_parameters_for_it_and_numparams end end + def test_implicit_parameter_invalid_name + message_pattern = /is not an implicit parameter/ + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?("wrong_implicit_parameter_name_#{rand(10000)}") } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get("wrong_implicit_parameter_name_#{rand(10000)}") } + end + def test_local_variable_set_wb assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30) b = binding From bb4a6f39519c101b6bdc847d11cad55f19e62e9a Mon Sep 17 00:00:00 2001 From: Ryan Davis Date: Sun, 7 Dec 2025 01:11:04 -0800 Subject: [PATCH 1850/2435] [ruby/prism] Fixed Prism::Translation::RubyParser's comment processing Tests were failing in Flay under Prism. https://github.com/ruby/prism/commit/af9b3640a8 --- lib/prism/translation/ruby_parser.rb | 62 +++++++++++++++++++++------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 2ca7da0bf2a5d5..5149756addefe4 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -415,14 +415,18 @@ def visit_class_node(node) visit(node.constant_path) end - if node.body.nil? - s(node, :class, name, visit(node.superclass)) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :class, name, visit(node.superclass)) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end # ``` @@ -611,7 +615,9 @@ def visit_def_node(node) s(node, :defs, visit(node.receiver), name) end + attach_comments(result, node) result.line(node.name_loc.start_line) + if node.parameters.nil? result << s(node, :args).line(node.name_loc.start_line) else @@ -1270,14 +1276,18 @@ def visit_module_node(node) visit(node.constant_path) end - if node.body.nil? - s(node, :module, name) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :module, name) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end # ``` @@ -1820,6 +1830,17 @@ def visit_yield_node(node) private + # Attach prism comments to the given sexp. + def attach_comments(sexp, node) + return unless node.comments + return if node.comments.empty? + + extra = node.location.start_line - node.comments.last.location.start_line + comments = node.comments.map(&:slice) + comments.concat([nil] * [0, extra].max) + sexp.comments = comments.join("\n") + end + # Create a new compiler with the given options. def copy_compiler(in_def: self.in_def, in_pattern: self.in_pattern) Compiler.new(file, in_def: in_def, in_pattern: in_pattern) @@ -1898,6 +1919,14 @@ def parse_file(filepath) translate(Prism.parse_file(filepath, partial_script: true), filepath) end + # Parse the give file and translate it into the + # seattlerb/ruby_parser gem's Sexp format. This method is + # provided for API compatibility to RubyParser and takes an + # optional +timeout+ argument. + def process(ruby, file = "(string)", timeout = nil) + Timeout.timeout(timeout) { parse(ruby, file) } + end + class << self # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. @@ -1922,6 +1951,7 @@ def translate(result, filepath) raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}" end + result.attach_comments! result.value.accept(Compiler.new(filepath)) end end From 3a0596b9a51121ebb7d3db06c438bf4182507c2a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 12 Dec 2025 13:14:00 -0500 Subject: [PATCH 1851/2435] ZJIT: Add Shape type to HIR (#15528) It's just a nicety (they fit fine as CUInt32) but this makes printing look nicer in real execution and also in tests (helps with #15489). Co-authored-by: Randy Stauner --- zjit/src/codegen.rs | 4 ++ zjit/src/hir.rs | 9 ++-- zjit/src/hir/opt_tests.rs | 26 +++++------ zjit/src/hir_type/gen_hir_type.rb | 1 + zjit/src/hir_type/hir_type.inc.rs | 71 ++++++++++++++++--------------- zjit/src/hir_type/mod.rs | 7 +++ 6 files changed, 68 insertions(+), 50 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index dbcffb04273a75..8c7d9a3b4a66ee 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -364,6 +364,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Const { val: Const::CInt64(val) } => gen_const_long(val), &Insn::Const { val: Const::CUInt16(val) } => gen_const_uint16(val), &Insn::Const { val: Const::CUInt32(val) } => gen_const_uint32(val), + &Insn::Const { val: Const::CShape(val) } => { + assert_eq!(SHAPE_ID_NUM_BITS, 32); + gen_const_uint32(val.0) + } Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1cdc5e003a7cf2..859454bac5f324 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -303,6 +303,7 @@ pub enum Const { CUInt8(u8), CUInt16(u16), CUInt32(u32), + CShape(ShapeId), CUInt64(u64), CPtr(*const u8), CDouble(f64), @@ -389,6 +390,7 @@ impl<'a> std::fmt::Display for ConstPrinter<'a> { // number than {:?} does and we don't know why. // We'll have to resolve that first. Const::CPtr(val) => write!(f, "CPtr({:?})", self.ptr_map.map_ptr(val)), + &Const::CShape(shape_id) => write!(f, "CShape({:p})", self.ptr_map.map_shape(shape_id)), _ => write!(f, "{:?}", self.inner), } } @@ -468,7 +470,7 @@ impl PtrPrintMap { } /// Map shape ID into a pointer for printing - fn map_shape(&self, id: ShapeId) -> *const c_void { + pub fn map_shape(&self, id: ShapeId) -> *const c_void { self.map_ptr(id.0 as *const c_void) } } @@ -2140,6 +2142,7 @@ impl Function { Insn::Const { val: Const::CUInt8(val) } => Type::from_cint(types::CUInt8, *val as i64), Insn::Const { val: Const::CUInt16(val) } => Type::from_cint(types::CUInt16, *val as i64), Insn::Const { val: Const::CUInt32(val) } => Type::from_cint(types::CUInt32, *val as i64), + Insn::Const { val: Const::CShape(val) } => Type::from_cint(types::CShape, val.0 as i64), Insn::Const { val: Const::CUInt64(val) } => Type::from_cint(types::CUInt64, *val as i64), Insn::Const { val: Const::CPtr(val) } => Type::from_cptr(*val), Insn::Const { val: Const::CDouble(val) } => Type::from_double(*val), @@ -3206,8 +3209,7 @@ impl Function { self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); if next_shape_id != recv_type.shape() { // Write the new shape ID - assert_eq!(SHAPE_ID_NUM_BITS, 32); - let shape_id = self.push_insn(block, Insn::Const { val: Const::CUInt32(next_shape_id.0) }); + let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) }); let shape_id_offset = unsafe { rb_shape_id_offset() }; self.push_insn(block, Insn::StoreField { recv: self_val, id: ID!(_shape_id), offset: shape_id_offset, val: shape_id }); } @@ -4773,6 +4775,7 @@ impl Function { Const::CUInt8(_) => self.assert_subtype(insn_id, val, types::CUInt8), Const::CUInt16(_) => self.assert_subtype(insn_id, val, types::CUInt16), Const::CUInt32(_) => self.assert_subtype(insn_id, val, types::CUInt32), + Const::CShape(_) => self.assert_subtype(insn_id, val, types::CShape), Const::CUInt64(_) => self.assert_subtype(insn_id, val, types::CUInt64), Const::CBool(_) => self.assert_subtype(insn_id, val, types::CBool), Const::CDouble(_) => self.assert_subtype(insn_id, val, types::CDouble), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 3946c9d03c0714..ab5bcf2022b64c 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3813,8 +3813,8 @@ mod hir_opt_tests { v20:HeapBasicObject = GuardShape v19, 0x1000 StoreField v20, :@foo@0x1001, v10 WriteBarrier v20, v10 - v23:CUInt32[4194311] = Const CUInt32(4194311) - StoreField v20, :_shape_id@0x1002, v23 + v23:CShape[0x1002] = Const CShape(0x1002) + StoreField v20, :_shape_id@0x1003, v23 CheckInterrupts Return v10 "); @@ -3845,16 +3845,16 @@ mod hir_opt_tests { v26:HeapBasicObject = GuardShape v25, 0x1000 StoreField v26, :@foo@0x1001, v10 WriteBarrier v26, v10 - v29:CUInt32[4194311] = Const CUInt32(4194311) - StoreField v26, :_shape_id@0x1002, v29 + v29:CShape[0x1002] = Const CShape(0x1002) + StoreField v26, :_shape_id@0x1003, v29 v16:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode v31:HeapBasicObject = GuardType v6, HeapBasicObject - v32:HeapBasicObject = GuardShape v31, 0x1003 + v32:HeapBasicObject = GuardShape v31, 0x1002 StoreField v32, :@bar@0x1004, v16 WriteBarrier v32, v16 - v35:CUInt32[4194312] = Const CUInt32(4194312) - StoreField v32, :_shape_id@0x1002, v35 + v35:CShape[0x1005] = Const CShape(0x1005) + StoreField v32, :_shape_id@0x1003, v35 CheckInterrupts Return v16 "); @@ -6095,8 +6095,8 @@ mod hir_opt_tests { v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 StoreField v29, :@foo@0x1039, v16 WriteBarrier v29, v16 - v32:CUInt32[4194311] = Const CUInt32(4194311) - StoreField v29, :_shape_id@0x103a, v32 + v32:CShape[0x103a] = Const CShape(0x103a) + StoreField v29, :_shape_id@0x103b, v32 CheckInterrupts Return v16 "); @@ -6130,8 +6130,8 @@ mod hir_opt_tests { v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 StoreField v29, :@foo@0x1039, v16 WriteBarrier v29, v16 - v32:CUInt32[4194311] = Const CUInt32(4194311) - StoreField v29, :_shape_id@0x103a, v32 + v32:CShape[0x103a] = Const CShape(0x103a) + StoreField v29, :_shape_id@0x103b, v32 CheckInterrupts Return v16 "); @@ -9514,8 +9514,8 @@ mod hir_opt_tests { v57:HeapBasicObject = GuardShape v56, 0x1000 StoreField v57, :@formatted@0x1001, v39 WriteBarrier v57, v39 - v60:CUInt32[4194311] = Const CUInt32(4194311) - StoreField v57, :_shape_id@0x1002, v60 + v60:CShape[0x1002] = Const CShape(0x1002) + StoreField v57, :_shape_id@0x1003, v60 v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index e51d0c04e1a324..9576d2b1c06f19 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -139,6 +139,7 @@ def final_type name, base: $object, c_name: nil signed.subtype "CInt#{width}" unsigned.subtype "CUInt#{width}" } +unsigned.subtype "CShape" # Assign individual bits to type leaves and union bit patterns to nodes with subtypes num_bits = 0 diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index a330440612a739..b388b3a0d10780 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -19,58 +19,59 @@ mod bits { pub const CInt8: u64 = 1u64 << 10; pub const CNull: u64 = 1u64 << 11; pub const CPtr: u64 = 1u64 << 12; + pub const CShape: u64 = 1u64 << 13; pub const CSigned: u64 = CInt16 | CInt32 | CInt64 | CInt8; - pub const CUInt16: u64 = 1u64 << 13; - pub const CUInt32: u64 = 1u64 << 14; - pub const CUInt64: u64 = 1u64 << 15; - pub const CUInt8: u64 = 1u64 << 16; - pub const CUnsigned: u64 = CUInt16 | CUInt32 | CUInt64 | CUInt8; + pub const CUInt16: u64 = 1u64 << 14; + pub const CUInt32: u64 = 1u64 << 15; + pub const CUInt64: u64 = 1u64 << 16; + pub const CUInt8: u64 = 1u64 << 17; + pub const CUnsigned: u64 = CShape | CUInt16 | CUInt32 | CUInt64 | CUInt8; pub const CValue: u64 = CBool | CDouble | CInt | CNull | CPtr; - pub const CallableMethodEntry: u64 = 1u64 << 17; - pub const Class: u64 = 1u64 << 18; - pub const DynamicSymbol: u64 = 1u64 << 19; + pub const CallableMethodEntry: u64 = 1u64 << 18; + pub const Class: u64 = 1u64 << 19; + pub const DynamicSymbol: u64 = 1u64 << 20; pub const Empty: u64 = 0u64; - pub const FalseClass: u64 = 1u64 << 20; - pub const Fixnum: u64 = 1u64 << 21; + pub const FalseClass: u64 = 1u64 << 21; + pub const Fixnum: u64 = 1u64 << 22; pub const Float: u64 = Flonum | HeapFloat; - pub const Flonum: u64 = 1u64 << 22; + pub const Flonum: u64 = 1u64 << 23; pub const Hash: u64 = HashExact | HashSubclass; - pub const HashExact: u64 = 1u64 << 23; - pub const HashSubclass: u64 = 1u64 << 24; + pub const HashExact: u64 = 1u64 << 24; + pub const HashSubclass: u64 = 1u64 << 25; pub const HeapBasicObject: u64 = BasicObject & !Immediate; - pub const HeapFloat: u64 = 1u64 << 25; + pub const HeapFloat: u64 = 1u64 << 26; pub const HeapObject: u64 = Object & !Immediate; pub const Immediate: u64 = FalseClass | Fixnum | Flonum | NilClass | StaticSymbol | TrueClass | Undef; pub const Integer: u64 = Bignum | Fixnum; pub const Module: u64 = Class | ModuleExact | ModuleSubclass; - pub const ModuleExact: u64 = 1u64 << 26; - pub const ModuleSubclass: u64 = 1u64 << 27; - pub const NilClass: u64 = 1u64 << 28; + pub const ModuleExact: u64 = 1u64 << 27; + pub const ModuleSubclass: u64 = 1u64 << 28; + pub const NilClass: u64 = 1u64 << 29; pub const Numeric: u64 = Float | Integer | NumericExact | NumericSubclass; - pub const NumericExact: u64 = 1u64 << 29; - pub const NumericSubclass: u64 = 1u64 << 30; + pub const NumericExact: u64 = 1u64 << 30; + pub const NumericSubclass: u64 = 1u64 << 31; pub const Object: u64 = Array | FalseClass | Hash | Module | NilClass | Numeric | ObjectExact | ObjectSubclass | Range | Regexp | Set | String | Symbol | TrueClass; - pub const ObjectExact: u64 = 1u64 << 31; - pub const ObjectSubclass: u64 = 1u64 << 32; + pub const ObjectExact: u64 = 1u64 << 32; + pub const ObjectSubclass: u64 = 1u64 << 33; pub const Range: u64 = RangeExact | RangeSubclass; - pub const RangeExact: u64 = 1u64 << 33; - pub const RangeSubclass: u64 = 1u64 << 34; + pub const RangeExact: u64 = 1u64 << 34; + pub const RangeSubclass: u64 = 1u64 << 35; pub const Regexp: u64 = RegexpExact | RegexpSubclass; - pub const RegexpExact: u64 = 1u64 << 35; - pub const RegexpSubclass: u64 = 1u64 << 36; + pub const RegexpExact: u64 = 1u64 << 36; + pub const RegexpSubclass: u64 = 1u64 << 37; pub const RubyValue: u64 = BasicObject | CallableMethodEntry | Undef; pub const Set: u64 = SetExact | SetSubclass; - pub const SetExact: u64 = 1u64 << 37; - pub const SetSubclass: u64 = 1u64 << 38; - pub const StaticSymbol: u64 = 1u64 << 39; + pub const SetExact: u64 = 1u64 << 38; + pub const SetSubclass: u64 = 1u64 << 39; + pub const StaticSymbol: u64 = 1u64 << 40; pub const String: u64 = StringExact | StringSubclass; - pub const StringExact: u64 = 1u64 << 40; - pub const StringSubclass: u64 = 1u64 << 41; + pub const StringExact: u64 = 1u64 << 41; + pub const StringSubclass: u64 = 1u64 << 42; pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | HashSubclass | ModuleSubclass | NumericSubclass | ObjectSubclass | RangeSubclass | RegexpSubclass | SetSubclass | StringSubclass; pub const Symbol: u64 = DynamicSymbol | StaticSymbol; - pub const TrueClass: u64 = 1u64 << 42; - pub const Undef: u64 = 1u64 << 43; - pub const AllBitPatterns: [(&str, u64); 70] = [ + pub const TrueClass: u64 = 1u64 << 43; + pub const Undef: u64 = 1u64 << 44; + pub const AllBitPatterns: [(&str, u64); 71] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -125,6 +126,7 @@ mod bits { ("CUInt64", CUInt64), ("CUInt32", CUInt32), ("CUInt16", CUInt16), + ("CShape", CShape), ("CPtr", CPtr), ("CNull", CNull), ("CSigned", CSigned), @@ -142,7 +144,7 @@ mod bits { ("ArrayExact", ArrayExact), ("Empty", Empty), ]; - pub const NumTypeBits: u64 = 44; + pub const NumTypeBits: u64 = 45; } pub mod types { use super::*; @@ -165,6 +167,7 @@ pub mod types { pub const CInt8: Type = Type::from_bits(bits::CInt8); pub const CNull: Type = Type::from_bits(bits::CNull); pub const CPtr: Type = Type::from_bits(bits::CPtr); + pub const CShape: Type = Type::from_bits(bits::CShape); pub const CSigned: Type = Type::from_bits(bits::CSigned); pub const CUInt16: Type = Type::from_bits(bits::CUInt16); pub const CUInt32: Type = Type::from_bits(bits::CUInt32); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 206d94f42bdb64..c87f1313b577d3 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -92,6 +92,8 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R Specialization::Int(val) if ty.is_subtype(types::CInt8) => write!(f, "[{}]", (val & u8::MAX as u64) as i8), Specialization::Int(val) if ty.is_subtype(types::CInt16) => write!(f, "[{}]", (val & u16::MAX as u64) as i16), Specialization::Int(val) if ty.is_subtype(types::CInt32) => write!(f, "[{}]", (val & u32::MAX as u64) as i32), + Specialization::Int(val) if ty.is_subtype(types::CShape) => + write!(f, "[{:p}]", printer.ptr_map.map_shape(crate::cruby::ShapeId((val & u32::MAX as u64) as u32))), Specialization::Int(val) if ty.is_subtype(types::CInt64) => write!(f, "[{}]", val as i64), Specialization::Int(val) if ty.is_subtype(types::CUInt8) => write!(f, "[{}]", val & u8::MAX as u64), Specialization::Int(val) if ty.is_subtype(types::CUInt16) => write!(f, "[{}]", val & u16::MAX as u64), @@ -258,6 +260,7 @@ impl Type { Const::CUInt8(v) => Self::from_cint(types::CUInt8, v as i64), Const::CUInt16(v) => Self::from_cint(types::CUInt16, v as i64), Const::CUInt32(v) => Self::from_cint(types::CUInt32, v as i64), + Const::CShape(v) => Self::from_cint(types::CShape, v.0 as i64), Const::CUInt64(v) => Self::from_cint(types::CUInt64, v as i64), Const::CPtr(v) => Self::from_cptr(v), Const::CDouble(v) => Self::from_double(v), @@ -526,6 +529,10 @@ impl Type { if self.is_subtype(types::CUInt8) || self.is_subtype(types::CInt8) { return 1; } if self.is_subtype(types::CUInt16) || self.is_subtype(types::CInt16) { return 2; } if self.is_subtype(types::CUInt32) || self.is_subtype(types::CInt32) { return 4; } + if self.is_subtype(types::CShape) { + use crate::cruby::{SHAPE_ID_NUM_BITS, BITS_PER_BYTE}; + return (SHAPE_ID_NUM_BITS as usize / BITS_PER_BYTE).try_into().unwrap(); + } // CUInt64, CInt64, CPtr, CNull, CDouble, or anything else defaults to 8 bytes crate::cruby::SIZEOF_VALUE as u8 } From 309d6ef9c3792d1116809620a01240c7b8f4406e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 11 Dec 2025 21:23:37 +0000 Subject: [PATCH 1852/2435] ZJIT: Inline `Hash#[]=` --- test/ruby/test_zjit.rb | 71 +++++++++++++++++++++++++++++ zjit/src/codegen.rs | 6 +++ zjit/src/cruby_methods.rs | 7 +++ zjit/src/hir.rs | 17 +++++-- zjit/src/hir/opt_tests.rs | 96 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 232c46079f5b38..50ffb6138c34bd 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1429,6 +1429,77 @@ def test = {}.freeze }, insns: [:opt_hash_freeze], call_threshold: 1 end + def test_opt_aset_hash + assert_compiles '42', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_opt_aset_hash_returns_value + assert_compiles '100', %q{ + def test(h, k, v) + h[k] = v + end + test({}, :key, 100) + test({}, :key, 100) + }, call_threshold: 2 + end + + def test_opt_aset_hash_string_key + assert_compiles '"bar"', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, "foo", "bar") + test(h, "foo", "bar") + h["foo"] + }, call_threshold: 2 + end + + def test_opt_aset_hash_subclass + assert_compiles '42', %q{ + class MyHash < Hash; end + def test(h, k, v) + h[k] = v + end + h = MyHash.new + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_few_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h.[]= 123 + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_many_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h[:a, :b] = :c + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + def test_opt_ary_freeze assert_compiles "[[], 5]", %q{ def test = [].freeze diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8c7d9a3b4a66ee..67f53a3477c099 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -484,6 +484,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))), &Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) }, &Insn::HashAref { hash, key, state } => { gen_hash_aref(jit, asm, opnd!(hash), opnd!(key), &function.frame_state(state)) }, + &Insn::HashAset { hash, key, val, state } => { no_output!(gen_hash_aset(jit, asm, opnd!(hash), opnd!(key), opnd!(val), &function.frame_state(state))) }, &Insn::ArrayPush { array, val, state } => { no_output!(gen_array_push(asm, opnd!(array), opnd!(val), &function.frame_state(state))) }, &Insn::ToNewArray { val, state } => { gen_to_new_array(jit, asm, opnd!(val), &function.frame_state(state)) }, &Insn::ToArray { val, state } => { gen_to_array(jit, asm, opnd!(val), &function.frame_state(state)) }, @@ -1065,6 +1066,11 @@ fn gen_hash_aref(jit: &mut JITState, asm: &mut Assembler, hash: Opnd, key: Opnd, asm_ccall!(asm, rb_hash_aref, hash, key) } +fn gen_hash_aset(jit: &mut JITState, asm: &mut Assembler, hash: Opnd, key: Opnd, val: Opnd, state: &FrameState) { + gen_prepare_non_leaf_call(jit, asm, state); + asm_ccall!(asm, rb_hash_aset, hash, key, val); +} + fn gen_array_push(asm: &mut Assembler, array: Opnd, val: Opnd, state: &FrameState) { gen_prepare_leaf_call_with_gc(asm, state); asm_ccall!(asm, rb_ary_push, array, val); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 71bf6ab83df0ad..14fced81acaab0 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -229,6 +229,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "push", inline_array_push); annotate!(rb_cArray, "pop", inline_array_pop); annotate!(rb_cHash, "[]", inline_hash_aref); + annotate!(rb_cHash, "[]=", inline_hash_aset); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p); @@ -356,6 +357,12 @@ fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins None } +fn inline_hash_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[key, val] = args else { return None; }; + let _ = fun.push_insn(block, hir::Insn::HashAset { hash: recv, key, val, state }); + // Hash#[]= returns the value, not the hash + Some(val) +} fn inline_string_bytesize(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if args.is_empty() && fun.likely_a(recv, types::String, state) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 859454bac5f324..05cbc07d8802eb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -733,6 +733,7 @@ pub enum Insn { ArrayLength { array: InsnId }, HashAref { hash: InsnId, key: InsnId, state: InsnId }, + HashAset { hash: InsnId, key: InsnId, val: InsnId, state: InsnId }, HashDup { val: InsnId, state: InsnId }, /// Allocate an instance of the `val` object without calling `#initialize` on it. @@ -991,7 +992,8 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } + | Insn::HashAset { .. } => false, _ => true, } } @@ -1182,6 +1184,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") } Insn::HashDup { val, .. } => { write!(f, "HashDup {val}") } Insn::HashAref { hash, key, .. } => { write!(f, "HashAref {hash}, {key}")} + Insn::HashAset { hash, key, val, .. } => { write!(f, "HashAset {hash}, {key}, {val}")} Insn::ObjectAlloc { val, .. } => { write!(f, "ObjectAlloc {val}") } &Insn::ObjectAllocClass { class, .. } => { let class_name = get_class_name(class); @@ -2034,6 +2037,7 @@ impl Function { &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, &HashDup { val, state } => HashDup { val: find!(val), state }, &HashAref { hash, key, state } => HashAref { hash: find!(hash), key: find!(key), state }, + &HashAset { hash, key, val, state } => HashAset { hash: find!(hash), key: find!(key), val: find!(val), state }, &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, recv, ref args, name, return_type, elidable } => CCall { cfunc, recv: find!(recv), args: find_vec!(args), name, return_type, elidable }, @@ -2131,7 +2135,7 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } - | Insn::StoreField { .. } | Insn::WriteBarrier { .. } => + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } => panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -4004,6 +4008,12 @@ impl Function { worklist.push_back(key); worklist.push_back(state); } + &Insn::HashAset { hash, key, val, state } => { + worklist.push_back(hash); + worklist.push_back(key); + worklist.push_back(val); + worklist.push_back(state); + } &Insn::Send { recv, ref args, state, .. } | &Insn::SendForward { recv, ref args, state, .. } | &Insn::SendWithoutBlock { recv, ref args, state, .. } @@ -4699,7 +4709,8 @@ impl Function { self.assert_subtype(insn_id, index, types::Fixnum) } // Instructions with Hash operands - Insn::HashAref { hash, .. } => self.assert_subtype(insn_id, hash, types::Hash), + Insn::HashAref { hash, .. } + | Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::Hash), Insn::HashDup { val, .. } => self.assert_subtype(insn_id, val, types::HashExact), // Other Insn::ObjectAllocClass { class, .. } => { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index ab5bcf2022b64c..3e751a1bbd1778 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6738,6 +6738,102 @@ mod hir_opt_tests { "); } + #[test] + fn test_hash_aset_literal() { + eval(" + def test + h = {} + h[1] = 3 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact = NewHash + PatchPoint NoEPEscape(test) + v22:Fixnum[1] = Const Value(1) + v24:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Hash@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + HashAset v13, v22, v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_hash_aset_profiled() { + eval(" + def test(hash, key, val) + hash[key] = val + end + test({}, 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :hash, l0, SP@6 + v3:BasicObject = GetLocal :key, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v35:HashExact = GuardType v13, HashExact + HashAset v35, v14, v15 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_hash_aset_subclass() { + eval(" + class C < Hash; end + def test(hash, key, val) + hash[key] = val + end + test(C.new, 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :hash, l0, SP@6 + v3:BasicObject = GetLocal :key, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v35:HashSubclass[class_exact:C] = GuardType v13, HashSubclass[class_exact:C] + HashAset v35, v14, v15 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v15 + "); + } + #[test] fn test_optimize_thread_current() { eval(" From 4f900e3ce9cefa76bc2c94e0e591acd51bf0e638 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Dec 2025 17:42:40 +0000 Subject: [PATCH 1853/2435] ZJIT: Only optimize `[]` and `[]=` for exact Hash, not Hash subclasses --- zjit/src/cruby_methods.rs | 24 ++++++++++++++++++------ zjit/src/hir.rs | 2 +- zjit/src/hir/opt_tests.rs | 10 ++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 14fced81acaab0..60060f149c850d 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -350,18 +350,30 @@ fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins } fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { - if let &[key] = args { + let &[key] = args else { return None; }; + + // Only optimize exact Hash, not subclasses + if fun.likely_a(recv, types::HashExact, state) { + let recv = fun.coerce_to(block, recv, types::HashExact, state); let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); - return Some(result); + Some(result) + } else { + None } - None } fn inline_hash_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[key, val] = args else { return None; }; - let _ = fun.push_insn(block, hir::Insn::HashAset { hash: recv, key, val, state }); - // Hash#[]= returns the value, not the hash - Some(val) + + // Only optimize exact Hash, not subclasses + if fun.likely_a(recv, types::HashExact, state) { + let recv = fun.coerce_to(block, recv, types::HashExact, state); + let _ = fun.push_insn(block, hir::Insn::HashAset { hash: recv, key, val, state }); + // Hash#[]= returns the value, not the hash + Some(val) + } else { + None + } } fn inline_string_bytesize(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 05cbc07d8802eb..77bd172396fdda 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4710,7 +4710,7 @@ impl Function { } // Instructions with Hash operands Insn::HashAref { hash, .. } - | Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::Hash), + | Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::HashExact), Insn::HashDup { val, .. } => self.assert_subtype(insn_id, val, types::HashExact), // Other Insn::ObjectAllocClass { class, .. } => { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 3e751a1bbd1778..4c62971c1eacb1 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6678,7 +6678,7 @@ mod hir_opt_tests { } #[test] - fn test_hash_aref_subclass() { + fn test_no_optimize_hash_aref_subclass() { eval(" class C < Hash; end def test(hash, key) @@ -6701,8 +6701,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v27:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] - v28:BasicObject = HashAref v27, v12 - IncrCounter inline_cfunc_optimized_send_count + v28:BasicObject = CCallWithFrame v27, :Hash#[]@0x1038, v12 CheckInterrupts Return v28 "); @@ -6803,7 +6802,7 @@ mod hir_opt_tests { } #[test] - fn test_hash_aset_subclass() { + fn test_no_optimize_hash_aset_subclass() { eval(" class C < Hash; end def test(hash, key, val) @@ -6827,8 +6826,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, []=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v35:HashSubclass[class_exact:C] = GuardType v13, HashSubclass[class_exact:C] - HashAset v35, v14, v15 - IncrCounter inline_cfunc_optimized_send_count + v36:BasicObject = CCallWithFrame v35, :Hash#[]=@0x1038, v14, v15 CheckInterrupts Return v15 "); From 6147b695870ce82ee3ad5305ce095b63889b8d9d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 14 Nov 2025 11:31:23 -0500 Subject: [PATCH 1854/2435] Array#rfind Implement Array#rfind, which is the same as find except from the other side of the Array. Also implemented Array#find (as opposed to the generic one on Enumerable because it is significantly faster and to keep the implementations together. [Feature #21678] --- array.c | 95 +++++++++++++++++++++++++++++++++++++++++ test/ruby/test_array.rb | 17 ++++++++ 2 files changed, 112 insertions(+) diff --git a/array.c b/array.c index a6aeeeeca156b4..1352b5c046ae1d 100644 --- a/array.c +++ b/array.c @@ -2088,6 +2088,99 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) return RARRAY_AREF(ary, idx); } +/* + * call-seq: + * find(if_none_proc = nil) {|element| ... } -> object or nil + * find(if_none_proc = nil) -> enumerator + * + * Returns the first element for which the block returns a truthy value. + * + * With a block given, calls the block with successive elements of the array; + * returns the first element for which the block returns a truthy value: + * + * (0..9).find {|element| element > 2} # => 3 + * + * If no such element is found, calls +if_none_proc+ and returns its return value. + * + * (0..9).find(proc {false}) {|element| element > 12} # => false + * {foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1] + * {foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * + * With no block given, returns an Enumerator. + * + */ + +static VALUE +rb_ary_find(int argc, VALUE *argv, VALUE ary) +{ + VALUE if_none; + long idx; + + RETURN_ENUMERATOR(ary, argc, argv); + if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; + + for (idx = 0; idx < RARRAY_LEN(ary); idx++) { + VALUE elem = RARRAY_AREF(ary, idx); + if (RTEST(rb_yield(elem))) { + return elem; + } + } + + if (!NIL_P(if_none)) { + return rb_funcallv(if_none, idCall, 0, 0); + } + return Qnil; +} + +/* + * call-seq: + * rfind(if_none_proc = nil) {|element| ... } -> object or nil + * rfind(if_none_proc = nil) -> enumerator + * + * Returns the last element for which the block returns a truthy value. + * + * With a block given, calls the block with successive elements of the array in + * reverse order; returns the last element for which the block returns a truthy + * value: + * + * (0..9).rfind {|element| element < 5} # => 4 + * + * If no such element is found, calls +if_none_proc+ and returns its return value. + * + * (0..9).rfind(proc {false}) {|element| element < -2} # => false + * {foo: 0, bar: 1, baz: 2}.rfind {|key, value| key.start_with?('b') } # => [:baz, 2] + * {foo: 0, bar: 1, baz: 2}.rfind(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * + * With no block given, returns an Enumerator. + * + */ + +static VALUE +rb_ary_rfind(int argc, VALUE *argv, VALUE ary) +{ + VALUE if_none; + long len, idx; + + RETURN_ENUMERATOR(ary, argc, argv); + if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; + + idx = RARRAY_LEN(ary); + while (idx--) { + VALUE elem = RARRAY_AREF(ary, idx); + if (RTEST(rb_yield(elem))) { + return elem; + } + + len = RARRAY_LEN(ary); + idx = (idx >= len) ? len : idx; + } + + if (!NIL_P(if_none)) { + return rb_funcallv(if_none, idCall, 0, 0); + } + return Qnil; +} + /* * call-seq: * find_index(object) -> integer or nil @@ -8816,6 +8909,8 @@ Init_Array(void) rb_define_method(rb_cArray, "length", rb_ary_length, 0); rb_define_method(rb_cArray, "size", rb_ary_length, 0); rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0); + rb_define_method(rb_cArray, "find", rb_ary_find, -1); + rb_define_method(rb_cArray, "rfind", rb_ary_rfind, -1); rb_define_method(rb_cArray, "find_index", rb_ary_index, -1); rb_define_method(rb_cArray, "index", rb_ary_index, -1); rb_define_method(rb_cArray, "rindex", rb_ary_rindex, -1); diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index a3ac0a6a0b420d..d93b86e7953b1a 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -3584,6 +3584,23 @@ def test_array_safely_modified_by_sort_block assert_equal((1..67).to_a.reverse, var_0) end + def test_find + ary = [1, 2, 3, 4, 5] + assert_equal(2, ary.find {|x| x % 2 == 0 }) + assert_equal(nil, ary.find {|x| false }) + assert_equal(:foo, ary.find(proc { :foo }) {|x| false }) + end + + def test_rfind + ary = [1, 2, 3, 4, 5] + assert_equal(4, ary.rfind {|x| x % 2 == 0 }) + assert_equal(1, ary.rfind {|x| x < 2 }) + assert_equal(5, ary.rfind {|x| x > 4 }) + assert_equal(nil, ary.rfind {|x| false }) + assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false }) + assert_equal(nil, ary.rfind {|x| ary.clear; false }) + end + private def need_continuation unless respond_to?(:callcc, true) From 7909ce2a839ba1c3e134239189e6aa2de3b6b630 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Fri, 12 Dec 2025 14:24:40 -0500 Subject: [PATCH 1855/2435] move th->event_serial to rb_thread_sched_item (#15500) --- thread_pthread.c | 2 +- thread_pthread.h | 3 ++- thread_pthread_mn.c | 6 +++--- vm_core.h | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 66323598607398..a2e42da13e70b1 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -2988,7 +2988,7 @@ timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now, uint32_t *event_serial) static void timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial) { - if (sched->running != th && th->event_serial == event_serial) { + if (sched->running != th && th->sched.event_serial == event_serial) { thread_sched_to_ready_common(sched, th, true, false); } } diff --git a/thread_pthread.h b/thread_pthread.h index f579e53781a74c..992e9fb0803d67 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -48,7 +48,7 @@ struct rb_thread_sched_waiting { struct ccan_list_node node; }; -// per-Thead scheduler helper data +// per-Thread scheduler helper data struct rb_thread_sched_item { struct { struct ccan_list_node ubf; @@ -70,6 +70,7 @@ struct rb_thread_sched_item { } node; struct rb_thread_sched_waiting waiting_reason; + uint32_t event_serial; bool finished; bool malloc_stack; diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index ad4d0915e790b2..5c21f212e4ab37 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -65,7 +65,7 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, volatile bool timedout = false, need_cancel = false; - uint32_t event_serial = ++th->event_serial; // overflow is okay + uint32_t event_serial = ++th->sched.event_serial; // overflow is okay if (ubf_set(th, ubf_event_waiting, (void *)th)) { return false; @@ -81,11 +81,11 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); if (th->sched.waiting_reason.flags == thread_sched_waiting_none) { - th->event_serial++; + th->sched.event_serial++; // timer thread has dequeued us already, but it won't try to wake us because we bumped our serial } else if (RUBY_VM_INTERRUPTED(th->ec)) { - th->event_serial++; // make sure timer thread doesn't try to wake us + th->sched.event_serial++; // make sure timer thread doesn't try to wake us need_cancel = true; } else { diff --git a/vm_core.h b/vm_core.h index 4195ea5e59c9a4..d391c4258a4337 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1130,7 +1130,6 @@ typedef struct rb_thread_struct { struct rb_thread_sched_item sched; bool mn_schedulable; rb_atomic_t serial; // only for RUBY_DEBUG_LOG() - uint32_t event_serial; VALUE last_status; /* $? */ From 5903ed7ba9ca60546aa0dd97e92b3d381b7918d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Thu, 11 Dec 2025 16:46:14 +0100 Subject: [PATCH 1856/2435] Prevent ifunc procs from being made shareable [Bug #21775] --- test/ruby/test_ractor.rb | 12 ++++++++++++ vm.c | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 09c470911a5aaa..6ae511217aca09 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -201,6 +201,18 @@ def test_max_cpu_1 RUBY end + def test_symbol_proc_is_shareable + pr = :symbol.to_proc + assert_make_shareable(pr) + end + + # [Bug #21775] + def test_ifunc_proc_not_shareable + h = Hash.new { self } + pr = h.to_proc + assert_unshareable(pr, /not supported yet/, exception: RuntimeError) + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) diff --git a/vm.c b/vm.c index f832fe401f124b..99f96ff7a0dbc2 100644 --- a/vm.c +++ b/vm.c @@ -1594,7 +1594,10 @@ rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self) proc->is_isolated = TRUE; } else { - VALUE proc_self = vm_block_self(vm_proc_block(self)); + const struct rb_block *block = vm_proc_block(self); + if (block->type != block_type_symbol) rb_raise(rb_eRuntimeError, "not supported yet"); + + VALUE proc_self = vm_block_self(block); if (!rb_ractor_shareable_p(proc_self)) { rb_raise(rb_eRactorIsolationError, "Proc's self is not shareable: %" PRIsVALUE, From 3add3db797c4216423fdaa4bef6e2ee3c7630303 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Fri, 12 Dec 2025 14:47:43 -0500 Subject: [PATCH 1857/2435] Fewer calls to `GET_EC()` and `GET_THREAD()` (#15506) The changes are to `io.c` and `thread.c`. I changed the API of 2 exported thread functions from `internal/thread.h` that didn't look like they had any use in C extensions: * rb_thread_wait_for_single_fd * rb_thread_io_wait I didn't change the following exported internal function because it's used in C extensions: * rb_thread_fd_select I added a comment to note that this function, although internal, is used in C extensions. --- include/ruby/fiber/scheduler.h | 14 ++++++++- include/ruby/internal/intern/select.h | 2 ++ internal/thread.h | 4 +-- io.c | 41 ++++++++++++++++----------- scheduler.c | 11 +++++-- thread.c | 29 ++++++++++--------- 6 files changed, 64 insertions(+), 37 deletions(-) diff --git a/include/ruby/fiber/scheduler.h b/include/ruby/fiber/scheduler.h index 537a3a7bb27a5c..4d764f68ae06ed 100644 --- a/include/ruby/fiber/scheduler.h +++ b/include/ruby/fiber/scheduler.h @@ -27,6 +27,7 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() #define RUBY_FIBER_SCHEDULER_VERSION 3 struct timeval; +struct rb_thread_struct; /** * Wrap a `ssize_t` and `int errno` into a single `VALUE`. This interface should @@ -118,7 +119,7 @@ VALUE rb_fiber_scheduler_current(void); /** * Identical to rb_fiber_scheduler_current(), except it queries for that of the - * passed thread instead of the implicit current one. + * passed thread value instead of the implicit current one. * * @param[in] thread Target thread. * @exception rb_eTypeError `thread` is not a thread. @@ -127,6 +128,17 @@ VALUE rb_fiber_scheduler_current(void); */ VALUE rb_fiber_scheduler_current_for_thread(VALUE thread); +/** + * Identical to rb_fiber_scheduler_current_for_thread(), except it expects + * a threadptr instead of a thread value. + * + * @param[in] thread Target thread. + * @exception rb_eTypeError `thread` is not a thread. + * @retval RUBY_Qnil No scheduler is in effect in `thread`. + * @retval otherwise The scheduler that is in effect in `thread`. + */ +VALUE rb_fiber_scheduler_current_for_threadptr(struct rb_thread_struct *thread); + /** * Converts the passed timeout to an expression that rb_fiber_scheduler_block() * etc. expects. diff --git a/include/ruby/internal/intern/select.h b/include/ruby/internal/intern/select.h index 6ba84c6e63464e..ba75213618ca74 100644 --- a/include/ruby/internal/intern/select.h +++ b/include/ruby/internal/intern/select.h @@ -72,6 +72,8 @@ struct timeval; * someone else, vastly varies among operating systems. You would better avoid * touching an fd from more than one threads. * + * NOTE: this function is used in native extensions, so change its API with care. + * * @internal * * Although any file descriptors are possible here, it makes completely no diff --git a/internal/thread.h b/internal/thread.h index 21efeeebc085d7..ea891b4372f8f4 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -56,8 +56,8 @@ VALUE rb_mutex_owned_p(VALUE self); VALUE rb_exec_recursive_outer_mid(VALUE (*f)(VALUE g, VALUE h, int r), VALUE g, VALUE h, ID mid); void ruby_mn_threads_params(void); -int rb_thread_io_wait(struct rb_io *io, int events, struct timeval * timeout); -int rb_thread_wait_for_single_fd(int fd, int events, struct timeval * timeout); +int rb_thread_io_wait(struct rb_thread_struct *th, struct rb_io *io, int events, struct timeval * timeout); +int rb_thread_wait_for_single_fd(struct rb_thread_struct *th, int fd, int events, struct timeval * timeout); size_t rb_thread_io_close_interrupt(struct rb_io *); void rb_thread_io_close_wait(struct rb_io *); diff --git a/io.c b/io.c index e0e7d40548ee69..e739cee3795eec 100644 --- a/io.c +++ b/io.c @@ -1291,7 +1291,8 @@ internal_writev_func(void *ptr) static ssize_t rb_io_read_memory(rb_io_t *fptr, void *buf, size_t count) { - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, fptr->self, buf, count, 0); @@ -1301,7 +1302,7 @@ rb_io_read_memory(rb_io_t *fptr, void *buf, size_t count) } struct io_internal_read_struct iis = { - .th = rb_thread_current(), + .th = th->self, .fptr = fptr, .nonblock = 0, .fd = fptr->fd, @@ -1324,7 +1325,8 @@ rb_io_read_memory(rb_io_t *fptr, void *buf, size_t count) static ssize_t rb_io_write_memory(rb_io_t *fptr, const void *buf, size_t count) { - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, buf, count, 0); @@ -1334,7 +1336,7 @@ rb_io_write_memory(rb_io_t *fptr, const void *buf, size_t count) } struct io_internal_write_struct iis = { - .th = rb_thread_current(), + .th = th->self, .fptr = fptr, .nonblock = 0, .fd = fptr->fd, @@ -1360,7 +1362,9 @@ rb_writev_internal(rb_io_t *fptr, const struct iovec *iov, int iovcnt) { if (!iovcnt) return 0; - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { // This path assumes at least one `iov`: VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, iov[0].iov_base, iov[0].iov_len, 0); @@ -1371,7 +1375,7 @@ rb_writev_internal(rb_io_t *fptr, const struct iovec *iov, int iovcnt) } struct io_internal_writev_struct iis = { - .th = rb_thread_current(), + .th = th->self, .fptr = fptr, .nonblock = 0, .fd = fptr->fd, @@ -1453,7 +1457,8 @@ io_fflush(rb_io_t *fptr) VALUE rb_io_wait(VALUE io, VALUE events, VALUE timeout) { - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { return rb_fiber_scheduler_io_wait(scheduler, io, events, timeout); @@ -1474,7 +1479,7 @@ rb_io_wait(VALUE io, VALUE events, VALUE timeout) tv = &tv_storage; } - int ready = rb_thread_io_wait(fptr, RB_NUM2INT(events), tv); + int ready = rb_thread_io_wait(th, fptr, RB_NUM2INT(events), tv); if (ready < 0) { rb_sys_fail(0); @@ -1498,17 +1503,15 @@ io_from_fd(int fd) } static int -io_wait_for_single_fd(int fd, int events, struct timeval *timeout) +io_wait_for_single_fd(int fd, int events, struct timeval *timeout, rb_thread_t *th, VALUE scheduler) { - VALUE scheduler = rb_fiber_scheduler_current(); - if (scheduler != Qnil) { return RTEST( rb_fiber_scheduler_io_wait(scheduler, io_from_fd(fd), RB_INT2NUM(events), rb_fiber_scheduler_make_timeout(timeout)) ); } - return rb_thread_wait_for_single_fd(fd, events, timeout); + return rb_thread_wait_for_single_fd(th, fd, events, timeout); } int @@ -1516,7 +1519,8 @@ rb_io_wait_readable(int f) { io_fd_check_closed(f); - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); switch (errno) { case EINTR: @@ -1536,7 +1540,7 @@ rb_io_wait_readable(int f) ); } else { - io_wait_for_single_fd(f, RUBY_IO_READABLE, NULL); + io_wait_for_single_fd(f, RUBY_IO_READABLE, NULL, th, scheduler); } return TRUE; @@ -1550,7 +1554,8 @@ rb_io_wait_writable(int f) { io_fd_check_closed(f); - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); switch (errno) { case EINTR: @@ -1579,7 +1584,7 @@ rb_io_wait_writable(int f) ); } else { - io_wait_for_single_fd(f, RUBY_IO_WRITABLE, NULL); + io_wait_for_single_fd(f, RUBY_IO_WRITABLE, NULL, th, scheduler); } return TRUE; @@ -1591,7 +1596,9 @@ rb_io_wait_writable(int f) int rb_wait_for_single_fd(int fd, int events, struct timeval *timeout) { - return io_wait_for_single_fd(fd, events, timeout); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); + return io_wait_for_single_fd(fd, events, timeout, th, scheduler); } int diff --git a/scheduler.c b/scheduler.c index 63c22b55aaa8d5..592bdcd1ef45ef 100644 --- a/scheduler.c +++ b/scheduler.c @@ -451,7 +451,7 @@ rb_fiber_scheduler_set(VALUE scheduler) } static VALUE -rb_fiber_scheduler_current_for_threadptr(rb_thread_t *thread) +fiber_scheduler_current_for_threadptr(rb_thread_t *thread) { RUBY_ASSERT(thread); @@ -467,13 +467,18 @@ VALUE rb_fiber_scheduler_current(void) { RUBY_ASSERT(ruby_thread_has_gvl_p()); - return rb_fiber_scheduler_current_for_threadptr(GET_THREAD()); + return fiber_scheduler_current_for_threadptr(GET_THREAD()); } // This function is allowed to be called without holding the GVL. VALUE rb_fiber_scheduler_current_for_thread(VALUE thread) { - return rb_fiber_scheduler_current_for_threadptr(rb_thread_ptr(thread)); + return fiber_scheduler_current_for_threadptr(rb_thread_ptr(thread)); +} + +VALUE rb_fiber_scheduler_current_for_threadptr(rb_thread_t *thread) +{ + return fiber_scheduler_current_for_threadptr(thread); } /* diff --git a/thread.c b/thread.c index 0beb84a5b4575b..e9d36cb1f69bc0 100644 --- a/thread.c +++ b/thread.c @@ -226,7 +226,7 @@ vm_check_ints_blocking(rb_execution_context_t *ec) // When a signal is received, we yield to the scheduler as soon as possible: if (result || RUBY_VM_INTERRUPTED(ec)) { - VALUE scheduler = rb_fiber_scheduler_current(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { rb_fiber_scheduler_yield(scheduler); } @@ -1075,7 +1075,7 @@ thread_join_sleep(VALUE arg) } while (!thread_finished(target_th)) { - VALUE scheduler = rb_fiber_scheduler_current(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (!limit) { if (scheduler != Qnil) { @@ -1424,17 +1424,18 @@ rb_thread_sleep_deadly(void) static void rb_thread_sleep_deadly_allow_spurious_wakeup(VALUE blocker, VALUE timeout, rb_hrtime_t end) { - VALUE scheduler = rb_fiber_scheduler_current(); + rb_thread_t *th = GET_THREAD(); + VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); if (scheduler != Qnil) { rb_fiber_scheduler_block(scheduler, blocker, timeout); } else { RUBY_DEBUG_LOG("..."); if (end) { - sleep_hrtime_until(GET_THREAD(), end, SLEEP_SPURIOUS_CHECK); + sleep_hrtime_until(th, end, SLEEP_SPURIOUS_CHECK); } else { - sleep_forever(GET_THREAD(), SLEEP_DEADLOCKABLE); + sleep_forever(th, SLEEP_DEADLOCKABLE); } } } @@ -4601,7 +4602,7 @@ wait_for_single_fd_blocking_region(rb_thread_t *th, struct pollfd *fds, nfds_t n * returns a mask of events */ static int -thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) +thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout) { struct pollfd fds[1] = {{ .fd = fd, @@ -4614,8 +4615,8 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) enum ruby_tag_type state; volatile int lerrno; - rb_execution_context_t *ec = GET_EC(); - rb_thread_t *th = rb_ec_thread_ptr(ec); + RUBY_ASSERT(th); + rb_execution_context_t *ec = th->ec; if (io) { blocking_operation.ec = ec; @@ -4749,7 +4750,7 @@ init_set_fd(int fd, rb_fdset_t *fds) } static int -thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) +thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout) { rb_fdset_t rfds, wfds, efds; struct select_args args; @@ -4758,7 +4759,7 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) struct rb_io_blocking_operation blocking_operation; if (io) { args.io = io; - blocking_operation.ec = GET_EC(); + blocking_operation.ec = th->ec; rb_io_blocking_operation_enter(io, &blocking_operation); args.blocking_operation = &blocking_operation; } @@ -4783,15 +4784,15 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) #endif /* ! USE_POLL */ int -rb_thread_wait_for_single_fd(int fd, int events, struct timeval *timeout) +rb_thread_wait_for_single_fd(rb_thread_t *th, int fd, int events, struct timeval *timeout) { - return thread_io_wait(NULL, fd, events, timeout); + return thread_io_wait(th, NULL, fd, events, timeout); } int -rb_thread_io_wait(struct rb_io *io, int events, struct timeval * timeout) +rb_thread_io_wait(rb_thread_t *th, struct rb_io *io, int events, struct timeval * timeout) { - return thread_io_wait(io, io->fd, events, timeout); + return thread_io_wait(th, io, io->fd, events, timeout); } /* From 2884f53519c4b86072d5fc3b41a71cee697af8ba Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 12 Dec 2025 13:42:00 -0500 Subject: [PATCH 1858/2435] YJIT: Add missing local variable type update for fallback setlocal blocks Previously, the chain_depth>0 version of setlocal blocks did not update the type of the local variable in the context. This can leave the context with stale type information and trigger panics like in [Bug #21772] or lead to miscompilation. To trigger the issue, YJIT needs to see the same ISEQ before and after environment escape and have tracked type info before the escape. To trigger in ISEQs that do not send with a block, it probably requires Kernel#binding or the use of include/ruby/debug.h APIs. --- bootstraptest/test_yjit.rb | 19 +++++++++++++++++++ yjit/src/codegen.rs | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index bd44f3ac3ece0b..fc592520e80b06 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -5416,3 +5416,22 @@ def foo 10.times.map { foo.dup }.last } + +# regression test for [Bug #21772] +# local variable type tracking desync +assert_normal_exit %q{ + def some_method = 0 + + def test_body(key) + some_method + key = key.to_s # setting of local relevant + + key == "symbol" + end + + def jit_caller = test_body("session_id") + + jit_caller # first iteration, non-escaped environment + alias some_method binding # induce environment escape + test_body(:symbol) +} diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 865f80ca4f0cab..4a53e8834c5fb7 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2518,6 +2518,7 @@ fn gen_setlocal_generic( ep_offset: u32, level: u32, ) -> Option { + // Post condition: The type of of the set local is updated in the Context. let value_type = asm.ctx.get_opnd_type(StackOpnd(0)); // Fallback because of write barrier @@ -2539,6 +2540,11 @@ fn gen_setlocal_generic( ); asm.stack_pop(1); + // Set local type in the context + if level == 0 { + let local_idx = ep_offset_to_local_idx(jit.get_iseq(), ep_offset).as_usize(); + asm.ctx.set_local_type(local_idx, value_type); + } return Some(KeepCompiling); } @@ -2591,6 +2597,7 @@ fn gen_setlocal_generic( ); } + // Set local type in the context if level == 0 { let local_idx = ep_offset_to_local_idx(jit.get_iseq(), ep_offset).as_usize(); asm.ctx.set_local_type(local_idx, value_type); From e7a38b32e0516e6b7d74f6cd55480cb453486691 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 10 Dec 2025 10:20:02 -0800 Subject: [PATCH 1859/2435] Store Encoding#name as an attribute When debugging the fstring table, I found "UTF-8" to be the most common interned strings in many benchmarks. We have a fixed, limited number of these strings, so we might as well permanently cache their fstrings. --- encoding.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/encoding.c b/encoding.c index d14d371b04c319..be8237281ba06d 100644 --- a/encoding.c +++ b/encoding.c @@ -56,7 +56,7 @@ int rb_encdb_alias(const char *alias, const char *orig); #pragma GCC visibility pop #endif -static ID id_encoding; +static ID id_encoding, id_i_name; VALUE rb_cEncoding; #define ENCODING_LIST_CAPA 256 @@ -136,6 +136,7 @@ static VALUE enc_new(rb_encoding *encoding) { VALUE enc = TypedData_Wrap_Struct(rb_cEncoding, &encoding_data_type, (void *)encoding); + rb_ivar_set(enc, id_i_name, rb_fstring_cstr(encoding->name)); RB_OBJ_SET_FROZEN_SHAREABLE(enc); return enc; } @@ -1356,20 +1357,6 @@ enc_inspect(VALUE self) rb_enc_autoload_p(enc) ? " (autoload)" : ""); } -/* - * call-seq: - * enc.name -> string - * enc.to_s -> string - * - * Returns the name of the encoding. - * - * Encoding::UTF_8.name #=> "UTF-8" - */ -static VALUE -enc_name(VALUE self) -{ - return rb_fstring_cstr(rb_enc_name((rb_encoding*)RTYPEDDATA_GET_DATA(self))); -} static int enc_names_i(st_data_t name, st_data_t idx, st_data_t args) @@ -1513,7 +1500,7 @@ static VALUE enc_dump(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 1); - return enc_name(self); + return rb_attr_get(self, id_i_name); } /* :nodoc: */ @@ -2002,12 +1989,19 @@ Init_Encoding(void) VALUE list; int i; + id_i_name = rb_intern_const("@name"); rb_cEncoding = rb_define_class("Encoding", rb_cObject); rb_define_alloc_func(rb_cEncoding, enc_s_alloc); rb_undef_method(CLASS_OF(rb_cEncoding), "new"); - rb_define_method(rb_cEncoding, "to_s", enc_name, 0); + + /* The name of the encoding. + * + * Encoding::UTF_8.name #=> "UTF-8" + */ + rb_attr(rb_cEncoding, rb_intern("name"), TRUE, FALSE, Qfalse); + rb_define_alias(rb_cEncoding, "to_s", "name"); + rb_define_method(rb_cEncoding, "inspect", enc_inspect, 0); - rb_define_method(rb_cEncoding, "name", enc_name, 0); rb_define_method(rb_cEncoding, "names", enc_names, 0); rb_define_method(rb_cEncoding, "dummy?", enc_dummy_p, 0); rb_define_method(rb_cEncoding, "ascii_compatible?", enc_ascii_compatible_p, 0); From 176e384bcffd9d3de5ca4b9e56781b3c2b7bdfdd Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 11 Dec 2025 15:56:38 -0800 Subject: [PATCH 1860/2435] Cache filesystem_encindex --- encoding.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/encoding.c b/encoding.c index be8237281ba06d..749cbd586d00be 100644 --- a/encoding.c +++ b/encoding.c @@ -97,6 +97,8 @@ static rb_encoding *global_enc_ascii, *global_enc_utf_8, *global_enc_us_ascii; +static int filesystem_encindex = ENCINDEX_ASCII_8BIT; + #define GLOBAL_ENC_TABLE_LOCKING(tbl) \ for (struct enc_table *tbl = &global_enc_table, **locking = &tbl; \ locking; \ @@ -1589,12 +1591,7 @@ rb_locale_encoding(void) int rb_filesystem_encindex(void) { - int idx; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - idx = enc_registered(enc_table, "filesystem"); - } - if (idx < 0) idx = ENCINDEX_ASCII_8BIT; - return idx; + return filesystem_encindex; } rb_encoding * @@ -1646,7 +1643,9 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha } if (def == &default_external) { - enc_alias_internal(enc_table, "filesystem", Init_enc_set_filesystem_encoding()); + int fs_idx = Init_enc_set_filesystem_encoding(); + enc_alias_internal(enc_table, "filesystem", fs_idx); + filesystem_encindex = fs_idx; } } From 2f151e76b5dc578026706b31f054d5caf5374b05 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 12 Dec 2025 18:29:02 -0500 Subject: [PATCH 1861/2435] ZJIT: Allow ccalls above 7 arguments (#15312) ZJIT: Add stack support for CCalls --- zjit/src/asm/arm64/opnd.rs | 2 + zjit/src/backend/arm64/mod.rs | 120 +++++++++++++++------------------ zjit/src/backend/lir.rs | 47 +++++++++++-- zjit/src/backend/x86_64/mod.rs | 16 +---- zjit/src/codegen.rs | 13 +--- 5 files changed, 99 insertions(+), 99 deletions(-) diff --git a/zjit/src/asm/arm64/opnd.rs b/zjit/src/asm/arm64/opnd.rs index 667533ab938e0e..d7185ac23b38ff 100644 --- a/zjit/src/asm/arm64/opnd.rs +++ b/zjit/src/asm/arm64/opnd.rs @@ -98,6 +98,8 @@ pub const X2_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 2 }; pub const X3_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 3 }; pub const X4_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 4 }; pub const X5_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 5 }; +pub const X6_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 6 }; +pub const X7_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 7 }; // caller-save registers pub const X9_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 9 }; diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 55a65e3ea6161e..e00ea270d52dea 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -24,13 +24,15 @@ pub const EC: Opnd = Opnd::Reg(X20_REG); pub const SP: Opnd = Opnd::Reg(X21_REG); // C argument registers on this platform -pub const C_ARG_OPNDS: [Opnd; 6] = [ +pub const C_ARG_OPNDS: [Opnd; 8] = [ Opnd::Reg(X0_REG), Opnd::Reg(X1_REG), Opnd::Reg(X2_REG), Opnd::Reg(X3_REG), Opnd::Reg(X4_REG), - Opnd::Reg(X5_REG) + Opnd::Reg(X5_REG), + Opnd::Reg(X6_REG), + Opnd::Reg(X7_REG) ]; // C return value register on this platform @@ -199,6 +201,8 @@ pub const ALLOC_REGS: &[Reg] = &[ X3_REG, X4_REG, X5_REG, + X6_REG, + X7_REG, X11_REG, X12_REG, ]; @@ -231,7 +235,7 @@ impl Assembler { /// Get a list of all of the caller-saved registers pub fn get_caller_save_regs() -> Vec { - vec![X1_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG] + vec![X1_REG, X6_REG, X7_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG] } /// How many bytes a call and a [Self::frame_setup] would change native SP @@ -488,31 +492,13 @@ impl Assembler { } */ Insn::CCall { opnds, .. } => { - assert!(opnds.len() <= C_ARG_OPNDS.len()); - - // Load each operand into the corresponding argument - // register. - // Note: the iteration order is reversed to avoid corrupting x0, - // which is both the return value and first argument register - if !opnds.is_empty() { - let mut args: Vec<(Opnd, Opnd)> = vec![]; - for (idx, opnd) in opnds.iter_mut().enumerate().rev() { - // If the value that we're sending is 0, then we can use - // the zero register, so in this case we'll just send - // a UImm of 0 along as the argument to the move. - let value = match opnd { - Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0), - Opnd::Mem(_) => split_memory_address(asm, *opnd), - _ => *opnd - }; - args.push((C_ARG_OPNDS[idx], value)); - } - asm.parallel_mov(args); - } - - // Now we push the CCall without any arguments so that it - // just performs the call. - *opnds = vec![]; + opnds.iter_mut().for_each(|opnd| { + *opnd = match opnd { + Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0), + Opnd::Mem(_) => split_memory_address(asm, *opnd), + _ => *opnd + }; + }); asm.push_insn(insn); }, Insn::Cmp { left, right } => { @@ -1857,17 +1843,19 @@ mod tests { assert_disasm_snapshot!(cb.disasm(), @r" 0x0: str x1, [sp, #-0x10]! - 0x4: str x9, [sp, #-0x10]! - 0x8: str x10, [sp, #-0x10]! - 0xc: str x11, [sp, #-0x10]! - 0x10: str x12, [sp, #-0x10]! - 0x14: str x13, [sp, #-0x10]! - 0x18: str x14, [sp, #-0x10]! - 0x1c: str x15, [sp, #-0x10]! - 0x20: mrs x16, nzcv - 0x24: str x16, [sp, #-0x10]! + 0x4: str x6, [sp, #-0x10]! + 0x8: str x7, [sp, #-0x10]! + 0xc: str x9, [sp, #-0x10]! + 0x10: str x10, [sp, #-0x10]! + 0x14: str x11, [sp, #-0x10]! + 0x18: str x12, [sp, #-0x10]! + 0x1c: str x13, [sp, #-0x10]! + 0x20: str x14, [sp, #-0x10]! + 0x24: str x15, [sp, #-0x10]! + 0x28: mrs x16, nzcv + 0x2c: str x16, [sp, #-0x10]! "); - assert_snapshot!(cb.hexdump(), @"e10f1ff8e90f1ff8ea0f1ff8eb0f1ff8ec0f1ff8ed0f1ff8ee0f1ff8ef0f1ff810423bd5f00f1ff8"); + assert_snapshot!(cb.hexdump(), @"e10f1ff8e60f1ff8e70f1ff8e90f1ff8ea0f1ff8eb0f1ff8ec0f1ff8ed0f1ff8ee0f1ff8ef0f1ff810423bd5f00f1ff8"); } #[test] @@ -1887,9 +1875,11 @@ mod tests { 0x18: ldr x11, [sp], #0x10 0x1c: ldr x10, [sp], #0x10 0x20: ldr x9, [sp], #0x10 - 0x24: ldr x1, [sp], #0x10 + 0x24: ldr x7, [sp], #0x10 + 0x28: ldr x6, [sp], #0x10 + 0x2c: ldr x1, [sp], #0x10 "); - assert_snapshot!(cb.hexdump(), @"10421bd5f00741f8ef0741f8ee0741f8ed0741f8ec0741f8eb0741f8ea0741f8e90741f8e10741f8"); + assert_snapshot!(cb.hexdump(), @"10421bd5f00741f8ef0741f8ee0741f8ed0741f8ec0741f8eb0741f8ea0741f8e90741f8e70741f8e60741f8e10741f8"); } #[test] @@ -2658,14 +2648,14 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x15, x0 - 0x4: mov x0, x1 - 0x8: mov x1, x15 - 0xc: mov x16, #0 - 0x10: blr x16 + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x15, x1 + 0x4: mov x1, x0 + 0x8: mov x0, x15 + 0xc: mov x16, #0 + 0x10: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae1030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0301aae10300aae0030faa100080d200023fd6"); } #[test] @@ -2681,17 +2671,17 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x15, x2 - 0x4: mov x2, x3 - 0x8: mov x3, x15 - 0xc: mov x15, x0 - 0x10: mov x0, x1 - 0x14: mov x1, x15 - 0x18: mov x16, #0 - 0x1c: blr x16 + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x15, x1 + 0x4: mov x1, x0 + 0x8: mov x0, x15 + 0xc: mov x15, x3 + 0x10: mov x3, x2 + 0x14: mov x2, x15 + 0x18: mov x16, #0 + 0x1c: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0302aae20303aae3030faaef0300aae00301aae1030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0301aae10300aae0030faaef0303aae30302aae2030faa100080d200023fd6"); } #[test] @@ -2706,15 +2696,15 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @" - 0x0: mov x15, x0 - 0x4: mov x0, x1 - 0x8: mov x1, x2 - 0xc: mov x2, x15 - 0x10: mov x16, #0 - 0x14: blr x16 + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: mov x15, x1 + 0x4: mov x1, x2 + 0x8: mov x2, x0 + 0xc: mov x0, x15 + 0x10: mov x16, #0 + 0x14: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae10302aae2030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0301aae10302aae20300aae0030faa100080d200023fd6"); } #[test] diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f4ed3d7cb78ce6..af19f056d339ef 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1559,9 +1559,8 @@ impl Assembler frame_setup_idxs.push(asm.insns.len()); } - let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { - (Insn::ParallelMov { .. }, Some(Insn::CCall { .. })) | - (Insn::CCall { .. }, _) if !pool.is_empty() => { + let before_ccall = match &insn { + Insn::CCall { .. } if !pool.is_empty() => { // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { @@ -1612,6 +1611,25 @@ impl Assembler if cfg!(target_arch = "x86_64") && saved_regs.len() % 2 == 1 { asm.cpush(Opnd::Reg(saved_regs.last().unwrap().0)); } + if let Insn::ParallelMov { moves } = &insn { + if moves.len() > C_ARG_OPNDS.len() { + let difference = moves.len().saturating_sub(C_ARG_OPNDS.len()); + + #[cfg(target_arch = "x86_64")] + let offset = { + // double quadword alignment + ((difference + 3) / 4) * 4 + }; + + #[cfg(target_arch = "aarch64")] + let offset = { + // quadword alignment + if difference % 2 == 0 { difference } else { difference + 1 } + }; + + asm.sub_into(NATIVE_STACK_PTR, (offset * 8).into()); + } + } } // Allocate a register for the output operand if it exists @@ -1703,7 +1721,23 @@ impl Assembler // Push instruction(s) let is_ccall = matches!(insn, Insn::CCall { .. }); match insn { - Insn::ParallelMov { moves } => { + Insn::CCall { opnds, fptr, start_marker, end_marker, out } => { + let mut moves: Vec<(Opnd, Opnd)> = vec![]; + let num_reg_args = opnds.len().min(C_ARG_OPNDS.len()); + let num_stack_args = opnds.len().saturating_sub(C_ARG_OPNDS.len()); + + if num_stack_args > 0 { + for (i, opnd) in opnds.iter().skip(num_reg_args).enumerate() { + moves.push((Opnd::mem(64, NATIVE_STACK_PTR, 8 * i as i32), *opnd)); + } + } + + if num_reg_args > 0 { + for (i, opnd) in opnds.iter().take(num_reg_args).enumerate() { + moves.push((C_ARG_OPNDS[i], *opnd)); + } + } + // For trampolines that use scratch registers, attempt to lower ParallelMov without scratch_reg. if let Some(moves) = Self::resolve_parallel_moves(&moves, None) { for (dst, src) in moves { @@ -1713,13 +1747,12 @@ impl Assembler // If it needs a scratch_reg, leave it to *_split_with_scratch_regs to handle it. asm.push_insn(Insn::ParallelMov { moves }); } - } - Insn::CCall { opnds, fptr, start_marker, end_marker, out } => { + // Split start_marker and end_marker here to avoid inserting push/pop between them. if let Some(start_marker) = start_marker { asm.push_insn(Insn::PosMarker(start_marker)); } - asm.push_insn(Insn::CCall { opnds, fptr, start_marker: None, end_marker: None, out }); + asm.push_insn(Insn::CCall { opnds: vec![], fptr, start_marker: None, end_marker: None, out }); if let Some(end_marker) = end_marker { asm.push_insn(Insn::PosMarker(end_marker)); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 9f780617cc4ac5..4af704644910f3 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -351,21 +351,7 @@ impl Assembler { }; asm.push_insn(insn); }, - Insn::CCall { opnds, .. } => { - assert!(opnds.len() <= C_ARG_OPNDS.len()); - - // Load each operand into the corresponding argument register. - if !opnds.is_empty() { - let mut args: Vec<(Opnd, Opnd)> = vec![]; - for (idx, opnd) in opnds.iter_mut().enumerate() { - args.push((C_ARG_OPNDS[idx], *opnd)); - } - asm.parallel_mov(args); - } - - // Now we push the CCall without any arguments so that it - // just performs the call. - *opnds = vec![]; + Insn::CCall { .. } => { asm.push_insn(insn); }, Insn::Lea { .. } => { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 67f53a3477c099..07f519db1c0e7d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -398,15 +398,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Send { cd, blockiseq, state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::SendWithoutBlock { cd, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason), - // Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it. + // Give up SendWithoutBlockDirect for 6+ args since the callee doesn't know how to read arguments from the stack. Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), - // Ensure we have enough room fit ec, self, and arguments - // TODO remove this check when we have stack args (we can use Time.new to test it) - Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), Insn::InvokeBuiltin { bf, leaf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, *leaf, opnds!(args)), &Insn::EntryPoint { jit_entry_idx } => no_output!(gen_entry_point(jit, asm, jit_entry_idx)), Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))), @@ -453,10 +450,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), - // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). - // There's no test case for this because no core cfuncs have this many parameters. But C extensions could have such methods. - Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => - gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, recv, name, args, cme, state, blockiseq, .. } => gen_ccall_with_frame(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), Insn::CCallVariadic { cfunc, recv, name, args, cme, state, blockiseq, return_type: _, elidable: _ } => { @@ -722,10 +715,6 @@ fn gen_fixnum_bit_check(asm: &mut Assembler, val: Opnd, index: u8) -> Opnd { } fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, leaf: bool, args: Vec) -> lir::Opnd { - assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, - "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", - unsafe { std::ffi::CStr::from_ptr(bf.name).to_str().unwrap() }, - bf.argc); if leaf { gen_prepare_leaf_call_with_gc(asm, state); } else { From 8f81d2b5c3d7e41a116b3b81a628b998d8e283be Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Fri, 12 Dec 2025 14:35:41 -0700 Subject: [PATCH 1862/2435] ZJIT: Don't inline non-parameter locals --- test/ruby/test_zjit.rb | 15 +++++++++++++++ zjit/src/hir.rs | 6 ++++++ zjit/src/hir/opt_tests.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 50ffb6138c34bd..df7d2cf2234631 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -340,6 +340,21 @@ def test(n) } end + def test_return_nonparam_local + # Use dead code (if false) to create a local without initialization instructions. + assert_compiles 'nil', %q{ + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test + }, call_threshold: 2 + end + def test_setlocal_on_eval assert_compiles '1', %q{ @b = binding diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 77bd172396fdda..dfb8bbaecb55b4 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1731,6 +1731,12 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: let ep_offset = unsafe { *rb_iseq_pc_at_idx(iseq, 1) }.as_u32(); let local_idx = ep_offset_to_local_idx(iseq, ep_offset); + // Only inline if the local is a parameter (not a method-defined local) as we are indexing args. + let param_size = unsafe { rb_get_iseq_body_param_size(iseq) } as usize; + if local_idx >= param_size { + return None; + } + if unsafe { rb_simple_iseq_p(iseq) } { return Some(IseqReturn::LocalVariable(local_idx.try_into().unwrap())); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 4c62971c1eacb1..e3c74c86364007 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -761,6 +761,44 @@ mod hir_opt_tests { "); } + #[test] + fn test_no_inline_nonparam_local_return() { + // Methods that return non-parameter local variables should NOT be inlined, + // because the local variable index will be out of bounds for args. + // The method must have a parameter so param_size > 0, and return a local + // that's not a parameter so local_idx >= param_size. + // Use dead code (if false) to create a local without initialization instructions, + // resulting in just getlocal + leave which enters the inlining code path. + eval(" + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:8: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v11 + CheckInterrupts + Return v21 + "); + } + #[test] fn test_optimize_send_to_aliased_cfunc() { eval(" From eb7acd75e7ae28ef1a22a7cbf5a50f99667aa8a6 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Fri, 12 Dec 2025 18:00:17 -0700 Subject: [PATCH 1863/2435] ZJIT: Nil-fill locals in direct send (#15536) Avoid garbage reads from locals in eval. Before the fix the test fails with <"[\"x\", \"x\", \"x\", \"x\"]"> expected but was <"[\"x\", \"x\", \"x\", \"x286326928\"]">. --- test/ruby/test_zjit.rb | 15 +++++++++++++++ zjit/src/codegen.rs | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index df7d2cf2234631..25804ad5e92a01 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -355,6 +355,21 @@ def test = foo(1) }, call_threshold: 2 end + def test_nonparam_local_nil_in_jit_call + # Non-parameter locals must be initialized to nil in JIT-to-JIT calls. + # Use dead code (if false) to create locals without initialization instructions. + # Then eval a string that accesses the uninitialized locals. + assert_compiles '["x", "x", "x", "x"]', %q{ + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + }, call_threshold: 2 + end + def test_setlocal_on_eval assert_compiles '1', %q{ @b = binding diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 07f519db1c0e7d..46952dc12215a8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1391,6 +1391,16 @@ fn gen_send_without_block_direct( 0 }; + // Fill non-parameter locals with nil (they may be read by eval before being written) + let num_params = params.size.to_usize(); + if local_size > num_params { + asm_comment!(asm, "initialize non-parameter locals to nil"); + for local_idx in num_params..local_size { + let offset = local_size_and_idx_to_bp_offset(local_size, local_idx); + asm.store(Opnd::mem(64, SP, -offset * SIZEOF_VALUE_I32), Qnil.into()); + } + } + // Make a method call. The target address will be rewritten once compiled. let iseq_call = IseqCall::new(iseq, num_optionals_passed); let dummy_ptr = cb.get_write_ptr().raw_ptr(cb); @@ -2369,8 +2379,8 @@ c_callable! { let pc = unsafe { rb_iseq_pc_at_idx(iseq, entry_insn_idxs[iseq_call.jit_entry_idx.to_usize()]) }; unsafe { rb_set_cfp_pc(cfp, pc) }; - // JIT-to-JIT calls don't set SP or fill nils to uninitialized (non-argument) locals. - // We need to set them if we side-exit from function_stub_hit. + // Successful JIT-to-JIT calls fill nils to non-parameter locals in generated code. + // If we side-exit from function_stub_hit (before JIT code runs), we need to set them here. fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, compile_error: &CompileError) { unsafe { // Set SP which gen_push_frame() doesn't set From 6ed5574b19ff2692aff0e3b6fbe2d69d3a202618 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Dec 2025 17:27:01 -0800 Subject: [PATCH 1864/2435] Revert "ZJIT: Exclude failing ruby-bench benchmarks (#15479)" This reverts commit 1eb10ca3cb6cff98bb8c0946ed905921586c7d52. This should have been fixed by https://github.com/ruby/ruby/pull/15536. --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index d6194825a445a7..ed559d64e10d05 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 64bb0903f8ae7c..37a9000d704c5a 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 From e1f5e61d6b1b9e937a58afcae644da0917f6dd49 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 12 Dec 2025 18:00:18 -0500 Subject: [PATCH 1865/2435] YJIT: Fix panic from overly loose filtering in identity method inlining Credits to @rwstauner for noticing this issue in GH-15533. --- bootstraptest/test_yjit.rb | 15 +++++++++++++++ yjit/src/codegen.rs | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index fc592520e80b06..cf1605cf407ac3 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -5435,3 +5435,18 @@ def jit_caller = test_body("session_id") alias some_method binding # induce environment escape test_body(:symbol) } + +# regression test for missing check in identity method inlining +assert_normal_exit %q{ + # Use dead code (if false) to create a local + # without initialization instructions. + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test +} diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 4a53e8834c5fb7..35d1713ed83ae9 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7466,6 +7466,12 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, block: Opti let ep_offset = unsafe { *rb_iseq_pc_at_idx(iseq, 1) }.as_u32(); let local_idx = ep_offset_to_local_idx(iseq, ep_offset); + // Only inline getlocal on a parameter. DCE in the IESQ builder can + // make a two-instruction ISEQ that does not return a parameter. + if local_idx >= unsafe { get_iseq_body_param_size(iseq) } { + return None; + } + if unsafe { rb_simple_iseq_p(iseq) } { return Some(IseqReturn::LocalVariable(local_idx)); } else if unsafe { rb_iseq_only_kwparam_p(iseq) } { From 71dd272506a31f28d216f26b0568028ed600f6ed Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Sat, 13 Dec 2025 13:52:13 +0900 Subject: [PATCH 1866/2435] Remove useless rb_check_arity() calls --- string.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/string.c b/string.c index 5b7169ab12ffaa..060a1467a06f55 100644 --- a/string.c +++ b/string.c @@ -10263,8 +10263,6 @@ rb_str_lstrip_bang(int argc, VALUE *argv, VALUE str) char *start, *s; long olen, loffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10324,8 +10322,6 @@ rb_str_lstrip(int argc, VALUE *argv, VALUE str) char *start; long len, loffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - RSTRING_GETMEM(str, start, len); if (argc > 0) { char table[TR_TABLE_SIZE]; @@ -10413,8 +10409,6 @@ rb_str_rstrip_bang(int argc, VALUE *argv, VALUE str) char *start; long olen, roffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10472,8 +10466,6 @@ rb_str_rstrip(int argc, VALUE *argv, VALUE str) char *start; long olen, roffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); if (argc > 0) { @@ -10510,8 +10502,6 @@ rb_str_strip_bang(int argc, VALUE *argv, VALUE str) long olen, loffset, roffset; rb_encoding *enc; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10577,8 +10567,6 @@ rb_str_strip(int argc, VALUE *argv, VALUE str) long olen, loffset, roffset; rb_encoding *enc = STR_ENC_GET(str); - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - RSTRING_GETMEM(str, start, olen); if (argc > 0) { From 9dbbdcc33fb8cafb0f85b3442d8be3d030acd711 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Dec 2025 20:31:49 +0900 Subject: [PATCH 1867/2435] [ruby/io-console] Remove useless rb_check_arity() call https://github.com/ruby/io-console/commit/df444b93f1 --- ext/io/console/console.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 3c8bb82083aabe..4a68edd69e7b44 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -1651,8 +1651,6 @@ console_dev(int argc, VALUE *argv, VALUE klass) VALUE con = 0; VALUE sym = 0; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - if (argc) { Check_Type(sym = argv[0], T_SYMBOL); } From 2912825829d8280cfc7862adaceb8ccf680dad69 Mon Sep 17 00:00:00 2001 From: YO4 Date: Mon, 8 Dec 2025 21:10:14 +0900 Subject: [PATCH 1868/2435] [ruby/io-console] avoid jumping scroll position when winsize changed On windows, IO.console.winsize= now respects the current view area and screen buffer size. https://github.com/ruby/io-console/commit/817aa65ea3 --- ext/io/console/console.c | 65 +++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 4a68edd69e7b44..1f8b71fc867335 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -840,6 +840,9 @@ console_winsize(VALUE io) return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws))); } +static VALUE console_scroll(VALUE io, int line); +static VALUE console_goto(VALUE io, VALUE y, VALUE x); + /* * call-seq: * io.winsize = [rows, columns] @@ -856,6 +859,8 @@ console_set_winsize(VALUE io, VALUE size) #if defined _WIN32 HANDLE wh; int newrow, newcol; + COORD oldsize; + SMALL_RECT oldwindow; BOOL ret; #endif VALUE row, col, xpixel, ypixel; @@ -891,18 +896,62 @@ console_set_winsize(VALUE io, VALUE size) if (!GetConsoleScreenBufferInfo(wh, &ws)) { rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); } + oldsize = ws.dwSize; + oldwindow = ws.srWindow; + if (ws.srWindow.Right + 1 < newcol) { + ws.dwSize.X = newcol; + } + if (ws.dwSize.Y < newrow) { + ws.dwSize.Y = newrow; + } + /* expand screen buffer first if needed */ + if (!SetConsoleScreenBufferSize(wh, ws.dwSize)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); + } + /* refresh ws for new dwMaximumWindowSize */ + if (!GetConsoleScreenBufferInfo(wh, &ws)) { + rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); + } + /* check new size before modifying buffer content */ + if (newrow <= 0 || newcol <= 0 || + newrow > ws.dwMaximumWindowSize.Y || + newcol > ws.dwMaximumWindowSize.X) { + SetConsoleScreenBufferSize(wh, oldsize); + /* remove scrollbar if possible */ + SetConsoleWindowInfo(wh, TRUE, &oldwindow); + rb_raise(rb_eArgError, "out of range winsize: [%d, %d]", newrow, newcol); + } + /* shrink screen buffer width */ ws.dwSize.X = newcol; - ret = SetConsoleScreenBufferSize(wh, ws.dwSize); + /* shrink screen buffer height if window height were the same */ + if (oldsize.Y == ws.srWindow.Bottom - ws.srWindow.Top + 1) { + ws.dwSize.Y = newrow; + } ws.srWindow.Left = 0; - ws.srWindow.Top = 0; - ws.srWindow.Right = newcol-1; - ws.srWindow.Bottom = newrow-1; + ws.srWindow.Right = newcol - 1; + ws.srWindow.Bottom = ws.srWindow.Top + newrow -1; + if (ws.dwCursorPosition.Y > ws.srWindow.Bottom) { + console_scroll(io, ws.dwCursorPosition.Y - ws.srWindow.Bottom); + ws.dwCursorPosition.Y = ws.srWindow.Bottom; + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + } + if (ws.srWindow.Bottom > ws.dwSize.Y - 1) { + console_scroll(io, ws.srWindow.Bottom - (ws.dwSize.Y - 1)); + ws.dwCursorPosition.Y -= ws.srWindow.Bottom - (ws.dwSize.Y - 1); + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + ws.srWindow.Bottom = ws.dwSize.Y - 1; + } + ws.srWindow.Top = ws.srWindow.Bottom - (newrow - 1); + /* perform changes to winsize */ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { - rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); + int last_error = LAST_ERROR; + SetConsoleScreenBufferSize(wh, oldsize); + rb_syserr_fail(last_error, "SetConsoleWindowInfo"); } - /* retry when shrinking buffer after shrunk window */ - if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) { - rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); + /* perform screen buffer shrinking if necessary */ + if ((ws.dwSize.Y < oldsize.Y || ws.dwSize.X < oldsize.X) && + !SetConsoleScreenBufferSize(wh, ws.dwSize)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); } /* remove scrollbar if possible */ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { From c8fd840599cbd312094247e772dc509656347c0d Mon Sep 17 00:00:00 2001 From: YO4 Date: Mon, 8 Dec 2025 21:10:14 +0900 Subject: [PATCH 1869/2435] [ruby/io-console] console_goto respects scroll position on windows https://github.com/ruby/io-console/commit/d2a6c69697 --- ext/io/console/console.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 1f8b71fc867335..ef3fe2eae58230 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -1303,11 +1303,17 @@ static VALUE console_goto(VALUE io, VALUE y, VALUE x) { #ifdef _WIN32 - COORD pos; - int fd = GetWriteFD(io); - pos.X = NUM2UINT(x); - pos.Y = NUM2UINT(y); - if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X = NUM2UINT(x); + pos->Y = ws.srWindow.Top + NUM2UINT(y); + if (!SetConsoleCursorPosition(h, *pos)) { rb_syserr_fail(LAST_ERROR, 0); } #else From 3a4ad76f9d63cf4773814d0a76e2e033207193fd Mon Sep 17 00:00:00 2001 From: YO4 Date: Mon, 8 Dec 2025 23:23:21 +0900 Subject: [PATCH 1870/2435] [ruby/io-console] console_cursor_pos respects scroll position on windows https://github.com/ruby/io-console/commit/ae33785820 --- ext/io/console/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index ef3fe2eae58230..d1611c0ec4e48c 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -1270,7 +1270,7 @@ console_cursor_pos(VALUE io) if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { rb_syserr_fail(LAST_ERROR, 0); } - return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); + return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y - ws.srWindow.Top), UINT2NUM(ws.dwCursorPosition.X)); #else static const struct query_args query = {"\033[6n", 0}; VALUE resp = console_vt_response(0, 0, io, &query); From 8f2c479fa50a61ff40b3f7dad0d69ca6cd35f3e3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Dec 2025 21:15:21 +0900 Subject: [PATCH 1871/2435] [ruby/io-console] strip trailing spaces [ci skip] https://github.com/ruby/io-console/commit/379e7c17ed --- ext/io/console/console.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index d1611c0ec4e48c..e544a561ee7cd4 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -933,12 +933,12 @@ console_set_winsize(VALUE io, VALUE size) if (ws.dwCursorPosition.Y > ws.srWindow.Bottom) { console_scroll(io, ws.dwCursorPosition.Y - ws.srWindow.Bottom); ws.dwCursorPosition.Y = ws.srWindow.Bottom; - console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); } if (ws.srWindow.Bottom > ws.dwSize.Y - 1) { console_scroll(io, ws.srWindow.Bottom - (ws.dwSize.Y - 1)); ws.dwCursorPosition.Y -= ws.srWindow.Bottom - (ws.dwSize.Y - 1); - console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); + console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X)); ws.srWindow.Bottom = ws.dwSize.Y - 1; } ws.srWindow.Top = ws.srWindow.Bottom - (newrow - 1); From 0561eb94250f5f856ff2594f86f897295b680582 Mon Sep 17 00:00:00 2001 From: Steven Johnstone Date: Wed, 19 Nov 2025 09:30:57 +0000 Subject: [PATCH 1872/2435] [ruby/prism] Prevent an infinite loop parsing a capture name Fixes https://github.com/ruby/prism/pull/3729. https://github.com/ruby/prism/commit/6e5347803c --- prism/prism.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index 20037b5b9f6b6c..7059db8f1d6c49 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -20395,6 +20395,9 @@ pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, con } size_t length = pm_strspn_hexadecimal_digit(cursor, end - cursor); + if (length == 0) { + break; + } uint32_t value = escape_unicode(parser, cursor, length); (void) pm_buffer_append_unicode_codepoint(unescaped, value); From 79a6ec74831cc47d022b86dfabe3c774eaaf91ca Mon Sep 17 00:00:00 2001 From: Akinori Musha Date: Thu, 20 Nov 2025 23:36:21 +0900 Subject: [PATCH 1873/2435] Enumerator.produce accepts an optional `size` keyword argument When not specified, the size is unknown (`nil`). Previously, the size was always `Float::INFINITY` and not specifiable. [Feature #21701] --- NEWS.md | 25 ++++++++++++++++++++ enumerator.c | 46 +++++++++++++++++++++++++++++++----- test/ruby/test_enumerator.rb | 36 ++++++++++++++++++---------- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/NEWS.md b/NEWS.md index 09fc053ef0b478..1d637891d38a55 100644 --- a/NEWS.md +++ b/NEWS.md @@ -33,6 +33,30 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Enumerator + + * `Enumerator.produce` now accepts an optional `size` keyword argument + to specify the size of the enumerator. It can be an integer, + `Float::INFINITY`, a callable object (such as a lambda), or `nil` to + indicate unknown size. When not specified, the size is unknown (`nil`). + Previously, the size was always `Float::INFINITY` and not specifiable. + + ```ruby + # Infinite enumerator + enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) + enum.size # => Float::INFINITY + + # Finite enumerator with known/computable size + abs_dir = File.expand_path("./baz") # => "/foo/bar/baz" + traverser = Enumerator.produce(abs_dir, size: -> { abs_dir.count("/") + 1 }) { + raise StopIteration if it == "/" + File.dirname(it) + } + traverser.size # => 4 + ``` + + [[Feature #21701]] + * Kernel * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, @@ -454,3 +478,4 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 +[Feature #21701]: https://bugs.ruby-lang.org/issues/21701 diff --git a/enumerator.c b/enumerator.c index cbdd5629cf1046..c2b2bfa9a032dd 100644 --- a/enumerator.c +++ b/enumerator.c @@ -221,6 +221,7 @@ struct yielder { struct producer { VALUE init; VALUE proc; + VALUE size; }; typedef struct MEMO *lazyenum_proc_func(VALUE, struct MEMO *, VALUE, long); @@ -2876,6 +2877,7 @@ producer_mark_and_move(void *p) struct producer *ptr = p; rb_gc_mark_and_move(&ptr->init); rb_gc_mark_and_move(&ptr->proc); + rb_gc_mark_and_move(&ptr->size); } #define producer_free RUBY_TYPED_DEFAULT_FREE @@ -2919,12 +2921,13 @@ producer_allocate(VALUE klass) obj = TypedData_Make_Struct(klass, struct producer, &producer_data_type, ptr); ptr->init = Qundef; ptr->proc = Qundef; + ptr->size = Qnil; return obj; } static VALUE -producer_init(VALUE obj, VALUE init, VALUE proc) +producer_init(VALUE obj, VALUE init, VALUE proc, VALUE size) { struct producer *ptr; @@ -2936,6 +2939,7 @@ producer_init(VALUE obj, VALUE init, VALUE proc) RB_OBJ_WRITE(obj, &ptr->init, init); RB_OBJ_WRITE(obj, &ptr->proc, proc); + RB_OBJ_WRITE(obj, &ptr->size, size); return obj; } @@ -2986,12 +2990,18 @@ producer_each(VALUE obj) static VALUE producer_size(VALUE obj, VALUE args, VALUE eobj) { - return DBL2NUM(HUGE_VAL); + struct producer *ptr = producer_ptr(obj); + VALUE size = ptr->size; + + if (NIL_P(size)) return Qnil; + if (RB_INTEGER_TYPE_P(size) || RB_FLOAT_TYPE_P(size)) return size; + + return rb_funcall(size, id_call, 0); } /* * call-seq: - * Enumerator.produce(initial = nil) { |prev| block } -> enumerator + * Enumerator.produce(initial = nil, size: nil) { |prev| block } -> enumerator * * Creates an infinite enumerator from any block, just called over and * over. The result of the previous iteration is passed to the next one. @@ -3023,19 +3033,43 @@ producer_size(VALUE obj, VALUE args, VALUE eobj) * PATTERN = %r{\d+|[-/+*]} * Enumerator.produce { scanner.scan(PATTERN) }.slice_after { scanner.eos? }.first * # => ["7", "+", "38", "/", "6"] + * + * The optional +size+ keyword argument specifies the size of the enumerator, + * which can be retrieved by Enumerator#size. It can be an integer, + * +Float::INFINITY+, a callable object (such as a lambda), or +nil+ to + * indicate unknown size. When not specified, the size is unknown (+nil+). + * + * # Infinite enumerator + * enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) + * enum.size # => Float::INFINITY + * + * # Finite enumerator with known/computable size + * abs_dir = File.expand_path("./baz") # => "/foo/bar/baz" + * traverser = Enumerator.produce(abs_dir, size: -> { abs_dir.count("/") + 1 }) { + * raise StopIteration if it == "/" + * File.dirname(it) + * } + * traverser.size # => 4 */ static VALUE enumerator_s_produce(int argc, VALUE *argv, VALUE klass) { - VALUE init, producer; + VALUE init, producer, opts, size; + ID keyword_ids[1]; if (!rb_block_given_p()) rb_raise(rb_eArgError, "no block given"); - if (rb_scan_args(argc, argv, "01", &init) == 0) { + keyword_ids[0] = rb_intern("size"); + rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "01:", &init, &opts); + rb_get_kwargs(opts, keyword_ids, 0, 1, &size); + + size = UNDEF_P(size) ? Qnil : convert_to_feasible_size_value(size); + + if (argc == 0 || (argc == 1 && !NIL_P(opts))) { init = Qundef; } - producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc()); + producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc(), size); return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS); } diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index ddba1c09cae905..5fabea645d2b8d 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -886,12 +886,13 @@ def test_chain_undef_methods def test_produce assert_raise(ArgumentError) { Enumerator.produce } + assert_raise(ArgumentError) { Enumerator.produce(a: 1, b: 1) {} } # Without initial object passed_args = [] enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ } assert_instance_of(Enumerator, enum) - assert_equal Float::INFINITY, enum.size + assert_nil enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [nil, 1, 2], passed_args @@ -899,22 +900,14 @@ def test_produce passed_args = [] enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ } assert_instance_of(Enumerator, enum) - assert_equal Float::INFINITY, enum.size + assert_nil enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [1, 2], passed_args - # With initial keyword arguments - passed_args = [] - enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)} - assert_instance_of(Enumerator, enum) - assert_equal Float::INFINITY, enum.size - assert_equal [{b: 1}, [1], :a, nil], enum.take(4) - assert_equal [{b: 1}, [1], :a], passed_args - # Raising StopIteration words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/) enum = Enumerator.produce { words.shift or raise StopIteration } - assert_equal Float::INFINITY, enum.size + assert_nil enum.size assert_instance_of(Enumerator, enum) assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a @@ -924,7 +917,7 @@ def test_produce obj.respond_to?(:first) or raise StopIteration obj.first } - assert_equal Float::INFINITY, enum.size + assert_nil enum.size assert_instance_of(Enumerator, enum) assert_nothing_raised { assert_equal [ @@ -935,6 +928,25 @@ def test_produce "abc", ], enum.to_a } + + # With size keyword argument + enum = Enumerator.produce(1, size: 10) { |obj| obj.succ } + assert_equal 10, enum.size + assert_equal [1, 2, 3], enum.take(3) + + enum = Enumerator.produce(1, size: -> { 5 }) { |obj| obj.succ } + assert_equal 5, enum.size + + enum = Enumerator.produce(1, size: nil) { |obj| obj.succ } + assert_equal nil, enum.size + + enum = Enumerator.produce(1, size: Float::INFINITY) { |obj| obj.succ } + assert_equal Float::INFINITY, enum.size + + # Without initial value but with size + enum = Enumerator.produce(size: 3) { |obj| (obj || 0).succ } + assert_equal 3, enum.size + assert_equal [1, 2, 3], enum.take(3) end def test_chain_each_lambda From 6513cf9058978e06eb3433fa33c8f70bd1100352 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 00:30:34 +0900 Subject: [PATCH 1874/2435] Export `GIT` Propagate the value given with `--with-git` configure option to tool/lib/vcs.rb. --- defs/gmake.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/defs/gmake.mk b/defs/gmake.mk index 36d65c0ea0b4c8..2422151009599c 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -2,6 +2,7 @@ reconfig config.status: export MAKE:=$(MAKE) export BASERUBY:=$(BASERUBY) +export GIT override gnumake_recursive := $(if $(findstring n,$(firstword $(MFLAGS))),,+) override mflags := $(filter-out -j%,$(MFLAGS)) MSPECOPT += $(if $(filter -j%,$(MFLAGS)),-j) From e8d32dddc04b34e2454b1c37b271bc242dddb06e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 4 Dec 2025 23:15:57 +0900 Subject: [PATCH 1875/2435] [ruby/openssl] ossl.c: implement OpenSSL::OpenSSLError#detailed_message An OpenSSL function sometimes puts more than one error entry into the thread-local OpenSSL error queue. Currently, we use the highest-level entry for generating the exception message and discard the rest. Let ossl_make_error() capture all current OpenSSL error queue contents into OpenSSL::OpenSSLError#errors and extend OpenSSL::OpenSSLError#detailed_message to include the information. An example: $ ruby -Ilib -ropenssl -e'OpenSSL::X509::ExtensionFactory.new.create_ext("a", "b")' -e:1:in 'OpenSSL::X509::ExtensionFactory#create_ext': a = b: error in extension (name=a, value=b) (OpenSSL::X509::ExtensionError) OpenSSL error queue reported 2 errors: error:11000082:X509 V3 routines:do_ext_nconf:unknown extension name error:11000080:X509 V3 routines:X509V3_EXT_nconf_int:error in extension (name=a, value=b) from -e:1:in '
' https://github.com/ruby/openssl/commit/d28f7a9a13 --- ext/openssl/extconf.rb | 6 ++- ext/openssl/ossl.c | 93 ++++++++++++++++++++++++++++++++++----- test/openssl/test_ossl.rb | 17 +++++-- 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 6c178c12f2b4f4..a897c86b657e9c 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -38,8 +38,12 @@ $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") +# Missing in TruffleRuby +have_func("rb_call_super_kw(0, NULL, 0)", "ruby.h") +# Ruby 3.1 have_func("rb_io_descriptor", "ruby/io.h") -have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 +have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") +# Ruby 3.2 have_func("rb_io_timeout", "ruby/io.h") Logging::message "=== Checking for system dependent stuff... ===\n" diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 5fd6bff98b7bbe..9c63d9451aa59d 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -254,12 +254,17 @@ ossl_to_der_if_possible(VALUE obj) /* * Errors */ +static ID id_i_errors; + +static void collect_errors_into(VALUE ary); + VALUE ossl_make_error(VALUE exc, VALUE str) { unsigned long e; const char *data; int flags; + VALUE errors = rb_ary_new(); if (NIL_P(str)) str = rb_str_new(NULL, 0); @@ -276,10 +281,12 @@ ossl_make_error(VALUE exc, VALUE str) rb_str_cat_cstr(str, msg ? msg : "(null)"); if (flags & ERR_TXT_STRING && data) rb_str_catf(str, " (%s)", data); - ossl_clear_error(); + collect_errors_into(errors); } - return rb_exc_new_str(exc, str); + VALUE obj = rb_exc_new_str(exc, str); + rb_ivar_set(obj, id_i_errors, errors); + return obj; } void @@ -300,13 +307,12 @@ ossl_raise(VALUE exc, const char *fmt, ...) rb_exc_raise(ossl_make_error(exc, err)); } -void -ossl_clear_error(void) +static void +collect_errors_into(VALUE ary) { - if (dOSSL == Qtrue) { + if (dOSSL == Qtrue || !NIL_P(ary)) { unsigned long e; const char *file, *data, *func, *lib, *reason; - char append[256] = ""; int line, flags; #ifdef HAVE_ERR_GET_ERROR_ALL @@ -318,13 +324,18 @@ ossl_clear_error(void) lib = ERR_lib_error_string(e); reason = ERR_reason_error_string(e); + VALUE str = rb_sprintf("error:%08lX:%s:%s:%s", e, lib ? lib : "", + func ? func : "", reason ? reason : ""); if (flags & ERR_TXT_STRING) { if (!data) data = "(null)"; - snprintf(append, sizeof(append), " (%s)", data); + rb_str_catf(str, " (%s)", data); } - rb_warn("error on stack: error:%08lX:%s:%s:%s%s", e, lib ? lib : "", - func ? func : "", reason ? reason : "", append); + + if (dOSSL == Qtrue) + rb_warn("error on stack: %"PRIsVALUE, str); + if (!NIL_P(ary)) + rb_ary_push(ary, str); } } else { @@ -332,6 +343,47 @@ ossl_clear_error(void) } } +void +ossl_clear_error(void) +{ + collect_errors_into(Qnil); +} + +/* + * call-seq: + * ossl_error.detailed_message(**) -> string + * + * Returns the exception message decorated with the captured \OpenSSL error + * queue entries. + */ +static VALUE +osslerror_detailed_message(int argc, VALUE *argv, VALUE self) +{ + VALUE str; +#ifdef HAVE_RB_CALL_SUPER_KW + // Ruby >= 3.2 + if (RTEST(rb_funcall(rb_eException, rb_intern("method_defined?"), 1, + ID2SYM(rb_intern("detailed_message"))))) + str = rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); + else +#endif + str = rb_funcall(self, rb_intern("message"), 0); + VALUE errors = rb_attr_get(self, id_i_errors); + + // OpenSSLError was not created by ossl_make_error() + if (!RB_TYPE_P(errors, T_ARRAY)) + return str; + + str = rb_str_resurrect(str); + rb_str_catf(str, "\nOpenSSL error queue reported %ld errors:", + RARRAY_LEN(errors)); + for (long i = 0; i < RARRAY_LEN(errors); i++) { + VALUE err = RARRAY_AREF(errors, i); + rb_str_catf(str, "\n%"PRIsVALUE, err); + } + return str; +} + /* * call-seq: * OpenSSL.errors -> [String...] @@ -1009,10 +1061,26 @@ Init_openssl(void) rb_global_variable(&eOSSLError); /* - * Generic error, - * common for all classes under OpenSSL module + * Generic error class for OpenSSL. All error classes in this library + * inherit from this class. + * + * This class indicates that an error was reported by the underlying + * \OpenSSL library. + */ + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); + /* + * \OpenSSL error queue entries captured at the time the exception was + * raised. The same information is printed to stderr if OpenSSL.debug is + * set to +true+. + * + * This is an array of zero or more strings, ordered from the oldest to the + * newest. The format of the strings is not stable and may vary across + * versions of \OpenSSL or versions of this Ruby extension. + * + * See also the man page ERR_get_error(3). */ - eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); + rb_attr(eOSSLError, rb_intern_const("errors"), 1, 0, 0); + rb_define_method(eOSSLError, "detailed_message", osslerror_detailed_message, -1); /* * Init debug core @@ -1028,6 +1096,7 @@ Init_openssl(void) * Get ID of to_der */ ossl_s_to_der = rb_intern("to_der"); + id_i_errors = rb_intern("@errors"); /* * Init components diff --git a/test/openssl/test_ossl.rb b/test/openssl/test_ossl.rb index 554038bbdb58eb..51262985f5655e 100644 --- a/test/openssl/test_ossl.rb +++ b/test/openssl/test_ossl.rb @@ -66,16 +66,27 @@ def test_memcmp_timing end if ENV["OSSL_TEST_ALL"] == "1" def test_error_data - # X509V3_EXT_nconf_nid() called from OpenSSL::X509::ExtensionFactory#create_ext is a function - # that uses ERR_raise_data() to append additional information about the error. + # X509V3_EXT_nconf_nid() called from + # OpenSSL::X509::ExtensionFactory#create_ext is a function that uses + # ERR_raise_data() to append additional information about the error. # # The generated message should look like: # "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)" # "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)" + # + # The string inside parentheses is the ERR_TXT_STRING data, and is appended + # by ossl_make_error(), so we check it here. ef = OpenSSL::X509::ExtensionFactory.new - assert_raise_with_message(OpenSSL::X509::ExtensionError, /value=(IP:)?not.a.valid.ip.address\)/) { + e = assert_raise(OpenSSL::X509::ExtensionError) { ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address") } + assert_match(/not.a.valid.ip.address\)\z/, e.message) + + # We currently craft the strings based on ERR_error_string()'s style: + # error:::: (data) + assert_instance_of(Array, e.errors) + assert_match(/\Aerror:.*not.a.valid.ip.address\)\z/, e.errors.last) + assert_include(e.detailed_message, "not.a.valid.ip.address") end end From b423204cb3851d1ad22d76578f7b516f1543c0dc Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 13 Dec 2025 17:15:20 +0100 Subject: [PATCH 1876/2435] Fix documentation of RB_PASS_CALLED_KEYWORDS in C API --- include/ruby/internal/eval.h | 15 ++++++++++----- include/ruby/internal/intern/cont.h | 9 ++++++--- include/ruby/internal/intern/enumerator.h | 12 ++++++++---- include/ruby/internal/intern/object.h | 3 ++- include/ruby/internal/intern/proc.h | 12 ++++++++---- include/ruby/internal/intern/vm.h | 6 ++++-- include/ruby/internal/iterator.h | 3 ++- 7 files changed, 40 insertions(+), 20 deletions(-) diff --git a/include/ruby/internal/eval.h b/include/ruby/internal/eval.h index 5bcbb97746053a..23aa1d958076fe 100644 --- a/include/ruby/internal/eval.h +++ b/include/ruby/internal/eval.h @@ -155,7 +155,8 @@ VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eNoMethodError No such method. * @exception rb_eException Any exceptions happen inside. * @return What the method evaluates to. @@ -189,7 +190,8 @@ VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eNoMethodError No such method. * @exception rb_eNoMethodError The method is private or protected. * @exception rb_eException Any exceptions happen inside. @@ -261,7 +263,8 @@ VALUE rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eNoMethodError No such method. * @exception rb_eNoMethodError The method is private or protected. * @exception rb_eException Any exceptions happen inside. @@ -307,7 +310,8 @@ VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VAL * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eNoMethodError No such method. * @exception rb_eNoMethodError The method is private or protected. * @exception rb_eException Any exceptions happen inside. @@ -335,7 +339,8 @@ VALUE rb_call_super(int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eNoMethodError No super method are there. * @exception rb_eException Any exceptions happen inside. * @return What the super method evaluates to. diff --git a/include/ruby/internal/intern/cont.h b/include/ruby/internal/intern/cont.h index e3c89557fda47d..2d813ceb9dbc63 100644 --- a/include/ruby/internal/intern/cont.h +++ b/include/ruby/internal/intern/cont.h @@ -148,7 +148,8 @@ VALUE rb_fiber_resume(VALUE fiber, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eFiberError `fiber` is terminated etc. * @exception rb_eException Any exceptions happen in `fiber`. * @return Either what was yielded or the last value of the fiber body. @@ -192,7 +193,8 @@ VALUE rb_fiber_yield(int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eException What was raised using `Fiber#raise`. * @return (See rb_fiber_resume() for details) */ @@ -247,7 +249,8 @@ VALUE rb_fiber_transfer(VALUE fiber, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eFiberError (See above) * @exception rb_eException What was raised using `Fiber#raise`. * @return (See rb_fiber_resume() for details) diff --git a/include/ruby/internal/intern/enumerator.h b/include/ruby/internal/intern/enumerator.h index 20e5d7c6fcd7b1..00804d786adaa3 100644 --- a/include/ruby/internal/intern/enumerator.h +++ b/include/ruby/internal/intern/enumerator.h @@ -100,7 +100,8 @@ VALUE rb_enumeratorize_with_size(VALUE recv, VALUE meth, int argc, const VALUE * * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eTypeError `meth` is not an instance of ::rb_cSymbol. * @return A new instance of ::rb_cEnumerator which, when yielded, * enumerates by calling `meth` on `recv` with `argv`. @@ -186,7 +187,8 @@ RBIMPL_SYMBOL_EXPORT_END() * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @return A new instance of ::rb_cEnumerator which, when yielded, * enumerates by calling the current method on `recv` with `argv`. */ @@ -220,7 +222,8 @@ RBIMPL_SYMBOL_EXPORT_END() * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @note This macro may return inside. */ #define RETURN_SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat) do { \ @@ -250,7 +253,8 @@ RBIMPL_SYMBOL_EXPORT_END() * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @note This macro may return inside. */ #define RETURN_ENUMERATOR_KW(obj, argc, argv, kw_splat) \ diff --git a/include/ruby/internal/intern/object.h b/include/ruby/internal/intern/object.h index 9daad7d046aea7..3897639a0ae6ce 100644 --- a/include/ruby/internal/intern/object.h +++ b/include/ruby/internal/intern/object.h @@ -80,7 +80,8 @@ VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eTypeError `klass`'s allocator is undefined. * @exception rb_eException Any exceptions can happen inside. * @return An allocated new instance of `klass`. diff --git a/include/ruby/internal/intern/proc.h b/include/ruby/internal/intern/proc.h index b8c3c5e1466126..2635d672ebd01a 100644 --- a/include/ruby/internal/intern/proc.h +++ b/include/ruby/internal/intern/proc.h @@ -101,7 +101,8 @@ VALUE rb_proc_call(VALUE recv, VALUE args); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `args`' last is not a keyword argument. * - RB_PASS_KEYWORDS `args`' last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eException Any exceptions happen inside. * @return What the proc evaluates to. */ @@ -141,7 +142,8 @@ VALUE rb_proc_call_with_block(VALUE recv, int argc, const VALUE *argv, VALUE pro * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `args`' last is not a keyword argument. * - RB_PASS_KEYWORDS `args`' last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eException Any exceptions happen inside. * @return What the proc evaluates to. */ @@ -245,7 +247,8 @@ VALUE rb_method_call(int argc, const VALUE *argv, VALUE recv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `args`' last is not a keyword argument. * - RB_PASS_KEYWORDS `args`' last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eTypeError `recv` is not a method. * @exception rb_eException Any exceptions happen inside. * @return What the method returns. @@ -279,7 +282,8 @@ VALUE rb_method_call_with_block(int argc, const VALUE *argv, VALUE recv, VALUE p * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `args`' last is not a keyword argument. * - RB_PASS_KEYWORDS `args`' last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @exception rb_eTypeError `recv` is not a method. * @exception rb_eException Any exceptions happen inside. * @return What the method returns. diff --git a/include/ruby/internal/intern/vm.h b/include/ruby/internal/intern/vm.h index 0d25a9cdb041fc..f0b54c702c4f5c 100644 --- a/include/ruby/internal/intern/vm.h +++ b/include/ruby/internal/intern/vm.h @@ -89,7 +89,8 @@ VALUE rb_check_funcall(VALUE recv, ID mid, int argc, const VALUE *argv); * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @retval RUBY_Qundef `recv` doesn't respond to `mid`. * @retval otherwise What the method evaluates to. */ @@ -106,7 +107,8 @@ VALUE rb_check_funcall_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int k * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `arg`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `arg`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @return What the command evaluates to. */ RBIMPL_ATTR_DEPRECATED_INTERNAL(4.0) diff --git a/include/ruby/internal/iterator.h b/include/ruby/internal/iterator.h index 5f706460f86f4e..60e3535fd97a24 100644 --- a/include/ruby/internal/iterator.h +++ b/include/ruby/internal/iterator.h @@ -337,7 +337,8 @@ VALUE rb_block_call(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_cal * @param[in] kw_splat Handling of keyword parameters: * - RB_NO_KEYWORDS `argv`'s last is not a keyword argument. * - RB_PASS_KEYWORDS `argv`'s last is a keyword argument. - * - RB_PASS_CALLED_KEYWORDS it depends if there is a passed block. + * - RB_PASS_CALLED_KEYWORDS Pass keyword arguments if the current method + * was called with keyword arguments. * @return What `obj.mid` returns. */ VALUE rb_block_call_kw(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t proc, VALUE data2, int kw_splat); From 0159a98bc18641b3aa644e50b3d1081b1e447cf1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 05:30:53 +0900 Subject: [PATCH 1877/2435] [Feature #20925] Skip infinite loop test --- tool/rbs_skip_tests | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index 28133153739292..4a5307a03ee4bb 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -93,3 +93,6 @@ test_relative_path_from(PathnameInstanceTest) test_slash(PathnameInstanceTest) test_Pathname(PathnameKernelTest) test_initialize(PathnameSingletonTest) + +# [Feature #20925] `size` of Enumerator created by `produce` returns `nil` instead of `Float::INFINITY`, unless `size:` is specified. +test_produce(EnumeratorSingletonTest) From c198436f08801adb6e3f3dfdf50d47f4ebf5eab0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 10:09:41 +0900 Subject: [PATCH 1878/2435] Run omnibus compilations without git --- .github/actions/compilers/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index 9a90376d706b7b..b554151091f436 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -47,6 +47,7 @@ grouped ${srcdir}/configure \ --enable-debug-env \ --disable-install-doc \ --with-ext=-test-/cxxanyargs,+ \ + --without-git \ ${enable_shared} \ ${INPUT_APPEND_CONFIGURE} \ CFLAGS="${INPUT_CFLAGS}" \ From 01db5d71a851ae79ed501eb64c1491b072d44f2c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 00:31:53 +0900 Subject: [PATCH 1879/2435] Removed duplicate code --- ext/extmk.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ext/extmk.rb b/ext/extmk.rb index 888eba6c676d41..eebfb5ba2a9771 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -139,14 +139,6 @@ def extract_makefile(makefile, keep = true) true end -def create_makefile(target, srcprefix = nil) - if $static and target.include?("/") - base = File.basename(target) - $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" - end - super -end - def extmake(target, basedir = 'ext', maybestatic = true) FileUtils.mkpath target unless File.directory?(target) begin From bc2a8a002a6c41fc1b28e02e15e2fb2b72d1b66e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 00:32:50 +0900 Subject: [PATCH 1880/2435] [Bug #21779] Uniquify `InitVM` functions as well as `Init` Avoid possible name conflict when `--with-static-linked-ext`. --- ext/extmk.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/extmk.rb b/ext/extmk.rb index eebfb5ba2a9771..8f847f4f3ab619 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -559,6 +559,7 @@ def create_makefile(*args, &block) if $static and (target = args.first).include?("/") base = File.basename(target) $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" + $defs << "-DInitVM_#{base}=InitVM_#{target.tr('/', '_')}" end return super end From c26057ebafb23b063190d31d5b4d19a0e0a1306c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 00:41:02 +0900 Subject: [PATCH 1881/2435] [Bug #21779] Do not export InitVM functions Fix ruby/io-console#105. --- configure.ac | 1 + template/Makefile.in | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5dadf13168a867..2bbce78fd05c25 100644 --- a/configure.ac +++ b/configure.ac @@ -3700,6 +3700,7 @@ AS_CASE("$enable_shared", [yes], [ LIBRUBY_DLDFLAGS="$LIBRUBY_DLDFLAGS "'-current_version $(RUBY_PROGRAM_VERSION)' AS_IF([test "$visibility_option" = ld], [ LIBRUBY_DLDFLAGS="$LIBRUBY_DLDFLAGS "'-Wl,-unexported_symbol,_Init_*' + LIBRUBY_DLDFLAGS="$LIBRUBY_DLDFLAGS "'-Wl,-unexported_symbol,_InitVM_*' LIBRUBY_DLDFLAGS="$LIBRUBY_DLDFLAGS "'-Wl,-unexported_symbol,_ruby_static_id_*' LIBRUBY_DLDFLAGS="$LIBRUBY_DLDFLAGS "'-Wl,-unexported_symbol,*_threadptr_*' ]) diff --git a/template/Makefile.in b/template/Makefile.in index fd41ddca9d90d5..443c394cb4f84d 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -339,7 +339,8 @@ $(LIBRUBY_SO): @-[ -n "$(EXTSTATIC)" ] || $(PRE_LIBRUBY_UPDATE) $(ECHO) linking shared-library $@ $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) $(DLDOBJS) $(SOLIBS) $(EXTSOLIBS) $(OUTFLAG)$@ - -$(Q) $(OBJCOPY) -w -L '$(SYMBOL_PREFIX)Init_*' -L '$(SYMBOL_PREFIX)ruby_static_id_*' \ + -$(Q) $(OBJCOPY) -w -L '$(SYMBOL_PREFIX)Init_*' -L '$(SYMBOL_PREFIX)InitVM_*' \ + -L '$(SYMBOL_PREFIX)ruby_static_id_*' \ -L '$(SYMBOL_PREFIX)*_threadptr_*' -L '$(SYMBOL_PREFIX)*_ec_*' $@ $(Q) $(POSTLINK) @-$(MINIRUBY) -e 'so, *aliases = ARGV; aliases.uniq!; aliases.delete(File.basename(so)); \ From 3a4d534b5bf68c783aaea34d3f9e33bc96f49a85 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Dec 2025 22:04:43 +0900 Subject: [PATCH 1882/2435] ZJIT: Fix tests about `--zjit-stats-quiet` option The `--zjit-stats-quiet` and `--zjit-stats=quiet` options differ. The latter option, `=quiet`, does print stats to the file "quiet", but does not suppress output like yjit option `--yjit-stats=quiet`. Fix up ruby/ruby#15414, 29c29c2b7e972359ab83038c5dc27a7e53ae65c7 --- test/ruby/test_zjit.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 25804ad5e92a01..a2d4917886152a 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -28,7 +28,7 @@ def test_stats_enabled end def test_stats_quiet - # Test that --zjit-stats=quiet collects stats but doesn't print them + # Test that --zjit-stats-quiet collects stats but doesn't print them script = <<~RUBY def test = 42 test @@ -44,7 +44,7 @@ def test = 42 assert_includes(err, stats_header) assert_equal("true\n", out) - # With --zjit-stats=quiet, stats should NOT be printed but still enabled + # With --zjit-stats-quiet, stats should NOT be printed but still enabled out, err, status = eval_with_jit(script, stats: :quiet) assert_success(out, err, status) refute_includes(err, stats_header) @@ -88,7 +88,7 @@ def test_zjit_disable end def test_zjit_enable_respects_existing_options - assert_separately(['--zjit-disable', '--zjit-stats=quiet'], <<~RUBY) + assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY) refute_predicate RubyVM::ZJIT, :enabled? assert_predicate RubyVM::ZJIT, :stats_enabled? @@ -3732,7 +3732,14 @@ def eval_with_jit( if zjit args << "--zjit-call-threshold=#{call_threshold}" args << "--zjit-num-profiles=#{num_profiles}" - args << "--zjit-stats#{"=#{stats}" unless stats == true}" if stats + case stats + when true + args << "--zjit-stats" + when :quiet + args << "--zjit-stats-quiet" + else + args << "--zjit-stats=#{stats}" if stats + end args << "--zjit-debug" if debug if allowed_iseqs jitlist = Tempfile.new("jitlist") From f6ef4efaa46cdd5a3cd4aa6e3918f5eec9358557 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Dec 2025 22:13:36 +0900 Subject: [PATCH 1883/2435] ZJIT: Add a test for `--zjit-stats=` option Fix up ruby/ruby#15414, 29c29c2b7e972359ab83038c5dc27a7e53ae65c7 --- test/ruby/test_zjit.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index a2d4917886152a..805ecb98b20ff3 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -49,6 +49,19 @@ def test = 42 assert_success(out, err, status) refute_includes(err, stats_header) assert_equal("true\n", out) + + # With --zjit-stats=, stats should be printed to the path + Tempfile.create("zjit-stats-") {|tmp| + stats_file = tmp.path + tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...") + tmp.close + + out, err, status = eval_with_jit(script, stats: stats_file) + assert_success(out, err, status) + refute_includes(err, stats_header) + assert_equal("true\n", out) + assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten" + } end def test_enable_through_env From 9e22037eb50d7abe6385901c9b029347fd383305 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 11:58:21 +0900 Subject: [PATCH 1884/2435] [ruby/io-console] bump up to 0.8.2 https://github.com/ruby/io-console/commit/fbc7e1f31f --- ext/io/console/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index e544a561ee7cd4..7ddaf071a8833a 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -4,7 +4,7 @@ */ static const char *const -IO_CONSOLE_VERSION = "0.8.1"; +IO_CONSOLE_VERSION = "0.8.2"; #include "ruby.h" #include "ruby/io.h" From ab95abd44100a8dc07e270a0783f5c6b7ce584a7 Mon Sep 17 00:00:00 2001 From: git Date: Sun, 14 Dec 2025 02:59:58 +0000 Subject: [PATCH 1885/2435] Update default gems list at 9e22037eb50d7abe6385901c9b0293 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 1d637891d38a55..947d3d5eeee93f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -280,7 +280,7 @@ The following default gems are updated. * fcntl 1.3.0 * fileutils 1.8.0 * forwardable 1.4.0 -* io-console 0.8.1 +* io-console 0.8.2 * io-nonblock 0.3.2 * io-wait 0.4.0.dev * ipaddr 1.2.8 From 711d14992e89aa922f10f62431dda6420a038979 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 16:11:02 +0900 Subject: [PATCH 1886/2435] Adjust indents [ci skip] --- parse.y | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/parse.y b/parse.y index 496dc21b11baec..4754435fab0d21 100644 --- a/parse.y +++ b/parse.y @@ -4569,10 +4569,10 @@ primary : inline_primary m->nd_plen = 1; m->nd_next = $for_var; break; - case NODE_MASGN: /* e.each {|*internal_var| a, b, c = (internal_var.length == 1 && Array === (tmp = internal_var[0]) ? tmp : internal_var); ... } */ + case NODE_MASGN: /* e.each {|*internal_var| a, b, c = (internal_var.length == 1 && Array === (tmp = internal_var[0]) ? tmp : internal_var); ... } */ m->nd_next = node_assign(p, $for_var, NEW_FOR_MASGN(internal_var, &@for_var), NO_LEX_CTXT, &@for_var); break; - default: /* e.each {|*internal_var| @a, B, c[1], d.attr = internal_val; ... } */ + default: /* e.each {|*internal_var| @a, B, c[1], d.attr = internal_val; ... } */ m->nd_next = node_assign(p, (NODE *)NEW_MASGN(NEW_LIST($for_var, &@for_var), 0, &@for_var), internal_var, NO_LEX_CTXT, &@for_var); } /* {|*internal_id| = internal_id; ... } */ @@ -4692,7 +4692,7 @@ primary : inline_primary if (!p->ctxt.in_defined) { switch (p->ctxt.in_rescue) { case before_rescue: yyerror1(&@1, "Invalid retry without rescue"); break; - case after_rescue: /* ok */ break; + case after_rescue: /* ok */ break; case after_else: yyerror1(&@1, "Invalid retry after else"); break; case after_ensure: yyerror1(&@1, "Invalid retry after ensure"); break; } @@ -5122,10 +5122,10 @@ numparam : { ; it_id : { - $$ = p->it_id; - p->it_id = 0; - } - ; + $$ = p->it_id; + p->it_id = 0; + } + ; lambda : tLAMBDA[lpar] { @@ -5441,7 +5441,7 @@ p_top_expr : p_top_expr_body } ; -p_top_expr_body : p_expr +p_top_expr_body : p_expr | p_expr ',' { $$ = new_array_pattern_tail(p, 0, 1, 0, 0, &@$); @@ -5794,7 +5794,7 @@ p_value : p_primitive | p_const ; -p_primitive : inline_primary +p_primitive : inline_primary | keyword_variable { if (!($$ = gettable(p, $1, &@$))) $$ = NEW_ERROR(&@$); @@ -6058,7 +6058,7 @@ xstring_contents: /* none */ } ; -regexp_contents: /* none */ +regexp_contents : /* none */ { $$ = 0; /*% ripper: regexp_new! %*/ @@ -6205,14 +6205,14 @@ user_variable : ident_or_const | nonlocal_var ; -keyword_variable : keyword_nil {$$ = KWD2EID(nil, $1);} - | keyword_self {$$ = KWD2EID(self, $1);} - | keyword_true {$$ = KWD2EID(true, $1);} - | keyword_false {$$ = KWD2EID(false, $1);} - | keyword__FILE__ {$$ = KWD2EID(_FILE__, $1);} - | keyword__LINE__ {$$ = KWD2EID(_LINE__, $1);} - | keyword__ENCODING__ {$$ = KWD2EID(_ENCODING__, $1);} - ; +keyword_variable: keyword_nil {$$ = KWD2EID(nil, $1);} + | keyword_self {$$ = KWD2EID(self, $1);} + | keyword_true {$$ = KWD2EID(true, $1);} + | keyword_false {$$ = KWD2EID(false, $1);} + | keyword__FILE__ {$$ = KWD2EID(_FILE__, $1);} + | keyword__LINE__ {$$ = KWD2EID(_LINE__, $1);} + | keyword__ENCODING__ {$$ = KWD2EID(_ENCODING__, $1);} + ; var_ref : user_variable { From 7969b654181af13f547afb88834f017694881353 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Jan 2025 02:47:18 +0900 Subject: [PATCH 1887/2435] [ruby/openssl] x509cert: update doc for OpenSSL::X509::Certificate#== Mention the underlying OpenSSL function. Add a note about the unreliable comparison when called on an incomplete object. Fixes https://github.com/ruby/openssl/issues/844 https://github.com/ruby/openssl/commit/736af5b3c7 --- ext/openssl/ossl_x509cert.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 4d69008fdd9a81..95679c7d24ea72 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -671,6 +671,12 @@ ossl_x509_add_extension(VALUE self, VALUE extension) * * Compares the two certificates. Note that this takes into account all fields, * not just the issuer name and the serial number. + * + * This method uses X509_cmp() from OpenSSL, which compares certificates based + * on their cached DER encodings. The comparison can be unreliable if a + * certificate is incomplete. + * + * See also the man page X509_cmp(3). */ static VALUE ossl_x509_eq(VALUE self, VALUE other) From 674c3d73e0f92d730bd2e544be344585a638ab37 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 14 Dec 2025 19:33:16 +0900 Subject: [PATCH 1888/2435] [ruby/openssl] pkcs7: raise OpenSSL::PKCS7::PKCS7Error in #initialize When d2i_PKCS7_bio() and PEM_read_bio_PKCS7() fail to decode the input, OpenSSL::PKCS7.new currently raises ArgumentError. The usual practice in ruby/openssl where an error originates from the underlying OpenSSL library is to raise OpenSSL::OpenSSLError. Raise OpenSSL::PKCS7::PKCS7Error instead for consistency with OpenSSL::PKCS7.read_smime and all other existing #initialize methods that handle DER/PEM-encoded inputs. https://github.com/ruby/openssl/commit/67a608ce53 --- ext/openssl/ossl_pkcs7.c | 4 ++-- test/openssl/test_pkcs7.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 88f06c8831c188..6e51fd42b90fac 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -390,10 +390,10 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) } BIO_free(in); if (!p7) - ossl_raise(rb_eArgError, "Could not parse the PKCS7"); + ossl_raise(ePKCS7Error, "Could not parse the PKCS7"); if (!p7->d.ptr) { PKCS7_free(p7); - ossl_raise(rb_eArgError, "No content in PKCS7"); + ossl_raise(ePKCS7Error, "No content in PKCS7"); } RTYPEDDATA_DATA(self) = p7; diff --git a/test/openssl/test_pkcs7.rb b/test/openssl/test_pkcs7.rb index 85ee68c6d18fde..b3129c0cdfe610 100644 --- a/test/openssl/test_pkcs7.rb +++ b/test/openssl/test_pkcs7.rb @@ -304,7 +304,7 @@ def test_data def test_empty_signed_data_ruby_bug_19974 data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n" - assert_raise(ArgumentError) { OpenSSL::PKCS7.new(data) } + assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(data) } data = < Date: Fri, 12 Dec 2025 13:08:53 -0800 Subject: [PATCH 1889/2435] [ruby/prism] Define RubyParser::SyntaxError directly and drop require for ruby_parser. Had to add a require of sexp since that came in indirectly via ruby_parser. https://github.com/ruby/prism/commit/df677c324f --- lib/prism/translation/ruby_parser.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 5149756addefe4..a33ea2306c1967 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -2,12 +2,17 @@ # :markup: markdown begin - require "ruby_parser" + require "sexp" rescue LoadError - warn(%q{Error: Unable to load ruby_parser. Add `gem "ruby_parser"` to your Gemfile.}) + warn(%q{Error: Unable to load sexp. Add `gem "sexp"` to your Gemfile.}) exit(1) end +class RubyParser # :nodoc: + class SyntaxError < RuntimeError # :nodoc: + end +end + module Prism module Translation # This module is the entry-point for converting a prism syntax tree into the From ca08351546602cc55f67739fb5c8f616ce8aa94e Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 14 Dec 2025 11:48:23 -0500 Subject: [PATCH 1890/2435] [ruby/prism] Unreference the block node before destroying it https://github.com/ruby/prism/commit/fc150b1588 --- prism/prism.c | 1 + 1 file changed, 1 insertion(+) diff --git a/prism/prism.c b/prism/prism.c index 7059db8f1d6c49..e10c39bb59f365 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18492,6 +18492,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // yield node. if (arguments.block != NULL) { pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); + pm_node_unreference(parser, arguments.block); pm_node_destroy(parser, arguments.block); arguments.block = NULL; } From 529b49144eaac5eb8deb5036e459bec9f7940175 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 14 Dec 2025 11:59:05 -0500 Subject: [PATCH 1891/2435] [ruby/prism] Only set location end when it is larger https://github.com/ruby/prism/commit/65595d6c2c --- prism/prism.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index e10c39bb59f365..677e65056f8f74 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2075,7 +2075,10 @@ pm_arguments_node_arguments_append(pm_arguments_node_t *node, pm_node_t *argumen node->base.location.start = argument->location.start; } - node->base.location.end = argument->location.end; + if (node->base.location.end < argument->location.end) { + node->base.location.end = argument->location.end; + } + pm_node_list_append(&node->arguments, argument); if (PM_NODE_TYPE_P(argument, PM_SPLAT_NODE)) { From cd27337d0b2141757d19d00e8f6bb86723b843e9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 14 Dec 2025 13:31:54 -0500 Subject: [PATCH 1892/2435] [DOC] Fix backticks in Numeric#floor --- numeric.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index 1942a6164f616a..813b81b98379bf 100644 --- a/numeric.c +++ b/numeric.c @@ -2601,7 +2601,7 @@ flo_truncate(int argc, VALUE *argv, VALUE num) * floor(ndigits = 0) -> float or integer * * Returns the largest float or integer that is less than or equal to +self+, - * as specified by the given `ndigits`, + * as specified by the given +ndigits+, * which must be an * {integer-convertible object}[rdoc-ref:implicit_conversion.rdoc@Integer-Convertible+Objects]. * From 3e5c4ebe4bab556af2dd5d1ea83fa6d16ac0578b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 14 Dec 2025 13:32:27 -0500 Subject: [PATCH 1893/2435] [DOC] Fix backticks in Numeric#ceil --- numeric.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index 813b81b98379bf..f96535cd2461d2 100644 --- a/numeric.c +++ b/numeric.c @@ -2621,7 +2621,7 @@ num_floor(int argc, VALUE *argv, VALUE num) * ceil(ndigits = 0) -> float or integer * * Returns the smallest float or integer that is greater than or equal to +self+, - * as specified by the given `ndigits`, + * as specified by the given +ndigits+, * which must be an * {integer-convertible object}[rdoc-ref:implicit_conversion.rdoc@Integer-Convertible+Objects]. * From 490a03bad9d3c18f66ae004055145db34d8ba599 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:26:42 +0100 Subject: [PATCH 1894/2435] [ruby/prism] Fix `sexp_processor` gem reference It's https://rubygems.org/gems/sexp_processor, not https://rubygems.org/gems/sexp https://github.com/ruby/prism/commit/b8a00a5f15 --- lib/prism/translation/ruby_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index a33ea2306c1967..1fb0e278462eeb 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -4,7 +4,7 @@ begin require "sexp" rescue LoadError - warn(%q{Error: Unable to load sexp. Add `gem "sexp"` to your Gemfile.}) + warn(%q{Error: Unable to load sexp. Add `gem "sexp_processor"` to your Gemfile.}) exit(1) end From e7cf07ba3e6758e3e6d34754422505c75fdf9c80 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 14 Dec 2025 22:28:41 +0000 Subject: [PATCH 1895/2435] [DOC] Fix link in MakeMakefile --- lib/mkmf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 201732d274ebf4..38a5a15fb5fdd0 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -40,7 +40,7 @@ def quote end ## -# mkmf.rb is used by Ruby C extensions to generate a Makefile which will +# \Module \MakeMakefile is used by Ruby C extensions to generate a Makefile which will # correctly compile and link the C extension to Ruby and a third-party # library. module MakeMakefile From 58940377e6f27f39d5c3baa0faa15655fa873f2c Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 25 Nov 2025 14:17:03 -0500 Subject: [PATCH 1896/2435] [ruby/rubygems] Write gem files atomically This change updates `write_binary` to use a new class, `AtomicFileWriter.open` to write the gem's files. This implementation is borrowed from Active Support's [`atomic_write`](https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb). Atomic write will write the files to a temporary file and then once created, sets permissions and renames the file. If the file is corrupted - ie on failed download, an error occurs, or for some other reason, the real file will not be created. The changes made here make `verify_gz` obsolete, we don't need to verify it if we have successfully created the file atomically. If it exists, it is not corrupt. If it is corrupt, the file won't exist on disk. While writing tests for this functionality I replaced the `RemoteFetcher` stub with `FakeFetcher` except for where we really do need to overwrite the `RemoteFetcher`. The new test implementation is much clearer on what it's trying to accomplish versus the prior test implementation. https://github.com/ruby/rubygems/commit/0cd4b54291 --- lib/bundler/shared_helpers.rb | 3 +- lib/rubygems.rb | 11 +- lib/rubygems/util/atomic_file_writer.rb | 67 ++++++++++++ test/rubygems/test_gem_installer.rb | 34 +++--- test/rubygems/test_gem_remote_fetcher.rb | 134 +++++++++++------------ 5 files changed, 158 insertions(+), 91 deletions(-) create mode 100644 lib/rubygems/util/atomic_file_writer.rb diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 6419e4299760b7..2aa8abe0a078b0 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -105,7 +105,8 @@ def set_bundle_environment def filesystem_access(path, action = :write, &block) yield(path.dup) rescue Errno::EACCES => e - raise unless e.message.include?(path.to_s) || action == :create + path_basename = File.basename(path.to_s) + raise unless e.message.include?(path_basename) || action == :create raise PermissionError.new(path, action) rescue Errno::EAGAIN diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 55e727425fbe4d..a66e5378436600 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -17,6 +17,7 @@ module Gem require_relative "rubygems/errors" require_relative "rubygems/target_rbconfig" require_relative "rubygems/win_platform" +require_relative "rubygems/util/atomic_file_writer" ## # RubyGems is the Ruby standard for publishing and managing third party @@ -833,14 +834,12 @@ def self.read_binary(path) end ## - # Safely write a file in binary mode on all platforms. + # Atomically write a file in binary mode on all platforms. def self.write_binary(path, data) - File.binwrite(path, data) - rescue Errno::ENOSPC - # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. - File.delete(path) if File.exist?(path) - raise + Gem::AtomicFileWriter.open(path) do |file| + file.write(data) + end end ## diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb new file mode 100644 index 00000000000000..7d1d6a74168f95 --- /dev/null +++ b/lib/rubygems/util/atomic_file_writer.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Based on ActiveSupport's AtomicFile implementation +# Copyright (c) David Heinemeier Hansson +# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb +# Licensed under the MIT License + +module Gem + class AtomicFileWriter + ## + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + + def self.open(file_name) + temp_dir = File.dirname(file_name) + require "tempfile" unless defined?(Tempfile) + + Tempfile.create(".#{File.basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_value = yield temp_file + temp_file.close + + original_permissions = if File.exist?(file_name) + File.stat(file_name) + else + # If not possible, probe which are the default permissions in the + # destination directory. + probe_permissions_in(File.dirname(file_name)) + end + + # Set correct permissions on new file + if original_permissions + begin + File.chown(original_permissions.uid, original_permissions.gid, temp_file.path) + File.chmod(original_permissions.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + # Overwrite original file with temp file + File.rename(temp_file.path, file_name) + return_value + end + end + + def self.probe_permissions_in(dir) # :nodoc: + basename = [ + ".permissions_check", + Thread.current.object_id, + Process.pid, + rand(1_000_000), + ].join(".") + + file_name = File.join(dir, basename) + File.open(file_name, "w") {} + File.stat(file_name) + rescue Errno::ENOENT + nil + ensure + begin + File.unlink(file_name) if File.exist?(file_name) + rescue SystemCallError + end + end + end +end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 293fe1e823dd1a..0220a41f88a4f2 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -2385,25 +2385,31 @@ def test_leaves_no_empty_cached_spec_when_no_more_disk_space installer = Gem::Installer.for_spec @spec installer.gem_home = @gemhome - File.singleton_class.class_eval do - alias_method :original_binwrite, :binwrite - - def binwrite(path, data) + assert_raise(Errno::ENOSPC) do + Gem::AtomicFileWriter.open(@spec.spec_file) do raise Errno::ENOSPC end end - assert_raise Errno::ENOSPC do - installer.write_spec - end - assert_path_not_exist @spec.spec_file - ensure - File.singleton_class.class_eval do - remove_method :binwrite - alias_method :binwrite, :original_binwrite - remove_method :original_binwrite - end + end + + def test_write_default_spec + @spec = setup_base_spec + @spec.files = %w[a.rb b.rb c.rb] + + installer = Gem::Installer.for_spec @spec + installer.gem_home = @gemhome + + installer.write_default_spec + + assert_path_exist installer.default_spec_file + + loaded = Gem::Specification.load installer.default_spec_file + + assert_equal @spec.files, loaded.files + assert_equal @spec.name, loaded.name + assert_equal @spec.version, loaded.version end def test_dir diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 5c1d89fad6934a..9badd75b427169 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -60,7 +60,7 @@ def test_cache_update_path uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = util_fuck_with_fetcher "hello" + fetcher = fake_fetcher(uri.to_s, "hello") data = fetcher.cache_update_path uri, path @@ -75,7 +75,7 @@ def test_cache_update_path_with_utf8_internal_encoding path = File.join @tempdir, "file" data = String.new("\xC8").force_encoding(Encoding::BINARY) - fetcher = util_fuck_with_fetcher data + fetcher = fake_fetcher(uri.to_s, data) written_data = fetcher.cache_update_path uri, path @@ -88,7 +88,7 @@ def test_cache_update_path_no_update uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = util_fuck_with_fetcher "hello" + fetcher = fake_fetcher(uri.to_s, "hello") data = fetcher.cache_update_path uri, path, false @@ -97,103 +97,79 @@ def test_cache_update_path_no_update assert_path_not_exist path end - def util_fuck_with_fetcher(data, blow = false) - fetcher = Gem::RemoteFetcher.fetcher - fetcher.instance_variable_set :@test_data, data - - if blow - def fetcher.fetch_path(arg, *rest) - # OMG I'm such an ass - class << self; remove_method :fetch_path; end - def self.fetch_path(arg, *rest) - @test_arg = arg - @test_data - end + def test_cache_update_path_overwrites_existing_file + uri = Gem::URI "http://example/file" + path = File.join @tempdir, "file" - raise Gem::RemoteFetcher::FetchError.new("haha!", "") - end - else - def fetcher.fetch_path(arg, *rest) - @test_arg = arg - @test_data - end - end + # Create existing file with old content + File.write(path, "old content") + assert_equal "old content", File.read(path) + + fetcher = fake_fetcher(uri.to_s, "new content") - fetcher + data = fetcher.cache_update_path uri, path + + assert_equal "new content", data + assert_equal "new content", File.read(path) end def test_download - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com") - assert_equal("http://gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_auth - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://user:password@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:password@gems.example.com") - assert_equal("http://user:password@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_token - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://token@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token@gems.example.com") - assert_equal("http://token@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_x_oauth_basic - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://token:x-oauth-basic@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token:x-oauth-basic@gems.example.com") - assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_encoded_auth - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://user:%25pas%25sword@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:%25pas%25sword@gems.example.com") - assert_equal("http://user:%25pas%25sword@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end @@ -235,8 +211,9 @@ def test_download_local_space def test_download_install_dir a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) install_dir = File.join @tempdir, "more_gems" @@ -245,8 +222,7 @@ def test_download_install_dir actual = fetcher.download(@a1, "http://gems.example.com", install_dir) assert_equal a1_cache_gem, actual - assert_equal("http://gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end @@ -282,7 +258,12 @@ def test_download_read_only FileUtils.chmod 0o555, @a1.cache_dir FileUtils.chmod 0o555, @gemhome - fetcher = util_fuck_with_fetcher File.read(@a1_gem) + fetcher = Gem::RemoteFetcher.fetcher + def fetcher.fetch_path(uri, *rest) + File.read File.join(@test_gem_dir, "a-1.gem") + end + fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem)) + fetcher.download(@a1, "http://gems.example.com") a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name assert File.exist? a1_cache_gem @@ -301,19 +282,21 @@ def test_download_platform_legacy end e1.loaded_from = File.join(@gemhome, "specifications", e1.full_name) - e1_data = nil - File.open e1_gem, "rb" do |fp| - e1_data = fp.read - end + e1_data = File.open e1_gem, "rb", &:read - fetcher = util_fuck_with_fetcher e1_data, :blow_chunks + fetcher = Gem::RemoteFetcher.fetcher + def fetcher.fetch_path(uri, *rest) + @call_count ||= 0 + @call_count += 1 + raise Gem::RemoteFetcher::FetchError.new("error", uri) if @call_count == 1 + @test_data + end + fetcher.instance_variable_set(:@test_data, e1_data) e1_cache_gem = e1.cache_file assert_equal e1_cache_gem, fetcher.download(e1, "http://gems.example.com") - assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem", - fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(e1_cache_gem) end @@ -592,6 +575,8 @@ def test_yaml_error_on_size end end + private + def assert_error(exception_class = Exception) got_exception = false @@ -603,4 +588,13 @@ def assert_error(exception_class = Exception) assert got_exception, "Expected exception conforming to #{exception_class}" end + + def fake_fetcher(url, data) + original_fetcher = Gem::RemoteFetcher.fetcher + fetcher = Gem::FakeFetcher.new + fetcher.data[url] = data + Gem::RemoteFetcher.fetcher = fetcher + ensure + Gem::RemoteFetcher.fetcher = original_fetcher + end end From 1e76b1f8b29e8824832170d0a7cedcac711f99c4 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 14 Dec 2025 17:00:20 -0500 Subject: [PATCH 1897/2435] [DOC] Remove doc/reline Reline has been moved to a bundled gem, so we don't need the docs anymore. --- doc/reline/face.md | 111 --------------------------------------------- 1 file changed, 111 deletions(-) delete mode 100644 doc/reline/face.md diff --git a/doc/reline/face.md b/doc/reline/face.md deleted file mode 100644 index 1fa916123b5363..00000000000000 --- a/doc/reline/face.md +++ /dev/null @@ -1,111 +0,0 @@ -# Face - -With the `Reline::Face` class, you can modify the text color and text decorations in your terminal emulator. -This is primarily used to customize the appearance of the method completion dialog in IRB. - -## Usage - -### ex: Change the background color of the completion dialog cyan to blue - -```ruby -Reline::Face.config(:completion_dialog) do |conf| - conf.define :default, foreground: :white, background: :blue - # ^^^^^ `:cyan` by default - conf.define :enhanced, foreground: :white, background: :magenta - conf.define :scrollbar, foreground: :white, background: :blue -end -``` - -If you provide the above code to an IRB session in some way, you can apply the configuration. -It's generally done by writing it in `.irbrc`. - -Regarding `.irbrc`, please refer to the following link: [https://docs.ruby-lang.org/en/master/IRB.html](https://docs.ruby-lang.org/en/master/IRB.html) - -## Available parameters - -`Reline::Face` internally creates SGR (Select Graphic Rendition) code according to the block parameter of `Reline::Face.config` method. - -| Key | Value | SGR Code (numeric part following "\e[")| -|:------------|:------------------|-----:| -| :foreground | :black | 30 | -| | :red | 31 | -| | :green | 32 | -| | :yellow | 33 | -| | :blue | 34 | -| | :magenta | 35 | -| | :cyan | 36 | -| | :white | 37 | -| | :bright_black | 90 | -| | :gray | 90 | -| | :bright_red | 91 | -| | :bright_green | 92 | -| | :bright_yellow | 93 | -| | :bright_blue | 94 | -| | :bright_magenta | 95 | -| | :bright_cyan | 96 | -| | :bright_white | 97 | -| :background | :black | 40 | -| | :red | 41 | -| | :green | 42 | -| | :yellow | 43 | -| | :blue | 44 | -| | :magenta | 45 | -| | :cyan | 46 | -| | :white | 47 | -| | :bright_black | 100 | -| | :gray | 100 | -| | :bright_red | 101 | -| | :bright_green | 102 | -| | :bright_yellow | 103 | -| | :bright_blue | 104 | -| | :bright_magenta | 105 | -| | :bright_cyan | 106 | -| | :bright_white | 107 | -| :style | :reset | 0 | -| | :bold | 1 | -| | :faint | 2 | -| | :italicized | 3 | -| | :underlined | 4 | -| | :slowly_blinking | 5 | -| | :blinking | 5 | -| | :rapidly_blinking | 6 | -| | :negative | 7 | -| | :concealed | 8 | -| | :crossed_out | 9 | - -- The value for `:style` can be both a Symbol and an Array - ```ruby - # Single symbol - conf.define :default, style: :bold - # Array - conf.define :default, style: [:bold, :negative] - ``` -- The availability of specific SGR codes depends on your terminal emulator -- You can specify a hex color code to `:foreground` and `:background` color like `foreground: "#FF1020"`. Its availability also depends on your terminal emulator - -## Debugging - -You can see the current Face configuration by `Reline::Face.configs` method - -Example: - -```ruby -irb(main):001:0> Reline::Face.configs -=> -{:default=> - {:default=>{:style=>:reset, :escape_sequence=>"\e[0m"}, - :enhanced=>{:style=>:reset, :escape_sequence=>"\e[0m"}, - :scrollbar=>{:style=>:reset, :escape_sequence=>"\e[0m"}}, - :completion_dialog=> - {:default=>{:foreground=>:white, :background=>:cyan, :escape_sequence=>"\e[0m\e[37;46m"}, - :enhanced=>{:foreground=>:white, :background=>:magenta, :escape_sequence=>"\e[0m\e[37;45m"}, - :scrollbar=>{:foreground=>:white, :background=>:cyan, :escape_sequence=>"\e[0m\e[37;46m"}}} -``` - -## 256-Color and TrueColor - -Reline will automatically detect if your terminal emulator supports truecolor with `ENV['COLORTERM] in 'truecolor' | '24bit'`. When this env is not set, Reline will fallback to 256-color. -If your terminal emulator supports truecolor but does not set COLORTERM env, add this line to `.irbrc`. -```ruby -Reline::Face.force_truecolor -``` From dd25fdcd4aa51d51615260662f75905d5f0896cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:02:50 +0000 Subject: [PATCH 1898/2435] Bump actions/cache from 5.0.0 to 5.0.1 Bumps [actions/cache](https://github.com/actions/cache) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/a7833574556fa59680c1b7cb190c1735db73ebf0...9255dc7a253b0ccc959486e2bca901246202afeb) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1230af953e03c0..c19d9e4aa78577 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -88,7 +88,7 @@ jobs: - name: Restore vcpkg artifact id: restore-vcpkg - uses: actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -100,7 +100,7 @@ jobs: if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - name: Save vcpkg artifact - uses: actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} From 76f30030c922f420b1c10bc4138dc6ee67005fbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:03:08 +0000 Subject: [PATCH 1899/2435] Bump msys2/setup-msys2 from 2.29.0 to 2.30.0 Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.29.0 to 2.30.0. - [Release notes](https://github.com/msys2/setup-msys2/releases) - [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md) - [Commits](https://github.com/msys2/setup-msys2/compare/fb197b72ce45fb24f17bf3f807a388985654d1f2...4f806de0a5a7294ffabaff804b38a9b435a73bda) --- updated-dependencies: - dependency-name: msys2/setup-msys2 dependency-version: 2.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 1a709d8344a290..8c3d16fbc9c120 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -81,7 +81,7 @@ jobs: )}} steps: - - uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2 # v2.29.0 + - uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0 id: msys2 with: msystem: ${{ matrix.msystem }} From a2dc4d7faa61a80837f5ea80cfe02f82a489ca89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:03:01 +0000 Subject: [PATCH 1900/2435] Bump actions/upload-artifact from 5.0.0 to 6.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check_misc.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/wasm.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index d7b096a96d58ec..5cc891c477982c 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -103,7 +103,7 @@ jobs: }} - name: Upload docs - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6.0.0 with: path: html name: ${{ steps.docs.outputs.htmlout }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a321d14010cd69..c60709899729a9 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index b59e1d4c931ef7..9b9ea1841029d4 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -140,7 +140,7 @@ jobs: - run: tar cfz ../install.tar.gz -C ../install . - name: Upload artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ruby-wasm-install path: ${{ github.workspace }}/install.tar.gz @@ -168,7 +168,7 @@ jobs: - name: Save Pull Request number if: ${{ github.event_name == 'pull_request' }} run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ github.event_name == 'pull_request' }} with: name: github-pr-info From 700487ce21f0991fb5d4042707d12b2966670f1e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 13 Dec 2025 17:30:21 +0900 Subject: [PATCH 1901/2435] [ruby/net-http] Refactor HTTPS tests This contains various improvements in tests for openssl integration: - Remove DHE parameters from test servers. OpenSSL is almost always compiled with ECC support nowadays and will prefer ECDHE over DHE. - Remove an outdated omission for a bug in OpenSSL 1.1.0h released in 2018. None of our CI systems use this specific OpenSSL version. - Use top-level return to skip tests if openssl is unavailable. - Refactor tests for Net::HTTP#verify_callback. https://github.com/ruby/net-http/commit/35c1745a26 --- test/net/http/test_https.rb | 66 +++++++++++-------------------- test/net/http/test_https_proxy.rb | 16 ++------ test/net/http/utils.rb | 3 +- 3 files changed, 26 insertions(+), 59 deletions(-) diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index e860c8745ed1f7..55b9eb317016aa 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -7,6 +7,8 @@ # should skip this test end +return unless defined?(OpenSSL::SSL) + class TestNetHTTPS < Test::Unit::TestCase include TestNetHTTPUtils @@ -19,7 +21,6 @@ def self.read_fixture(key) CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) - DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem")) TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } CONFIG = { @@ -29,25 +30,16 @@ def self.read_fixture(key) 'ssl_enable' => true, 'ssl_certificate' => SERVER_CERT, 'ssl_private_key' => SERVER_KEY, - 'ssl_tmp_dh_callback' => proc { DHPARAMS }, } def test_get http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE - certs = [] - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - certs << store_ctx.current_cert - preverify_ok - end http.request_get("/") {|res| assert_equal($test_net_http_data, res.body) + assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der) } - # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility - certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected| - assert_equal(expected.to_der, actual.to_der) - end end def test_get_SNI @@ -55,18 +47,10 @@ def test_get_SNI http.ipaddr = config('host') http.use_ssl = true http.cert_store = TEST_STORE - certs = [] - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - certs << store_ctx.current_cert - preverify_ok - end http.request_get("/") {|res| assert_equal($test_net_http_data, res.body) + assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der) } - # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility - certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected| - assert_equal(expected.to_der, actual.to_der) - end end def test_get_SNI_proxy @@ -78,11 +62,6 @@ def test_get_SNI_proxy http.ipaddr = "192.0.2.1" http.use_ssl = true http.cert_store = TEST_STORE - certs = [] - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - certs << store_ctx.current_cert - preverify_ok - end begin http.start rescue EOFError @@ -114,11 +93,6 @@ def test_get_SNI_failure http.ipaddr = config('host') http.use_ssl = true http.cert_store = TEST_STORE - certs = [] - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - certs << store_ctx.current_cert - preverify_ok - end @log_tester = lambda {|_| } assert_raise(OpenSSL::SSL::SSLError){ http.start } end @@ -135,10 +109,6 @@ def test_post end def test_session_reuse - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - # See https://github.com/openssl/openssl/pull/5967 for details. - omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h') - http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE @@ -165,9 +135,6 @@ def test_session_reuse end def test_session_reuse_but_expire - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h') - http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE @@ -240,6 +207,21 @@ def test_certificate_verify_failure assert_match(/certificate verify failed/, ex.message) end + def test_verify_callback + http = Net::HTTP.new(HOST, config("port")) + http.use_ssl = true + http.cert_store = TEST_STORE + certs = [] + http.verify_callback = Proc.new {|preverify_ok, store_ctx| + certs << store_ctx.current_cert + preverify_ok + } + http.request_get("/") {|res| + assert_equal($test_net_http_data, res.body) + } + assert_equal(SERVER_CERT.to_der, certs.last.to_der) + end + def test_timeout_during_SSL_handshake bug4246 = "expected the SSL connection to have timed out but have not. [ruby-core:34203]" @@ -275,9 +257,7 @@ def test_max_version http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.max_version = :SSL2 - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - true - end + http.cert_store = TEST_STORE @log_tester = lambda {|_| } ex = assert_raise(OpenSSL::SSL::SSLError){ http.request_get("/") {|res| } @@ -286,7 +266,7 @@ def test_max_version assert_match(re_msg, ex.message) end -end if defined?(OpenSSL::SSL) +end class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase include TestNetHTTPUtils @@ -300,7 +280,6 @@ def self.read_fixture(key) CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) - DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem")) TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } CONFIG = { @@ -310,7 +289,6 @@ def self.read_fixture(key) 'ssl_enable' => true, 'ssl_certificate' => SERVER_CERT, 'ssl_private_key' => SERVER_KEY, - 'ssl_tmp_dh_callback' => proc { DHPARAMS }, } def test_identity_verify_failure @@ -326,4 +304,4 @@ def test_identity_verify_failure re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/ assert_match(re_msg, ex.message) end -end if defined?(OpenSSL::SSL) +end diff --git a/test/net/http/test_https_proxy.rb b/test/net/http/test_https_proxy.rb index f4c6aa0b6a015d..237c16e64de295 100644 --- a/test/net/http/test_https_proxy.rb +++ b/test/net/http/test_https_proxy.rb @@ -5,14 +5,10 @@ end require 'test/unit' +return unless defined?(OpenSSL::SSL) + class HTTPSProxyTest < Test::Unit::TestCase def test_https_proxy_authentication - begin - OpenSSL - rescue LoadError - omit 'autoload problem. see [ruby-dev:45021][Bug #5786]' - end - TCPServer.open("127.0.0.1", 0) {|serv| _, port, _, _ = serv.addr client_thread = Thread.new { @@ -50,12 +46,6 @@ def read_fixture(key) end def test_https_proxy_ssl_connection - begin - OpenSSL - rescue LoadError - omit 'autoload problem. see [ruby-dev:45021][Bug #5786]' - end - TCPServer.open("127.0.0.1", 0) {|tcpserver| ctx = OpenSSL::SSL::SSLContext.new ctx.key = OpenSSL::PKey.read(read_fixture("server.key")) @@ -91,4 +81,4 @@ def test_https_proxy_ssl_connection assert_join_threads([client_thread, server_thread]) } end -end if defined?(OpenSSL) +end diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb index 067cca02e31bff..0b9e440e7cbcbb 100644 --- a/test/net/http/utils.rb +++ b/test/net/http/utils.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'socket' -require 'openssl' module TestNetHTTPUtils @@ -14,10 +13,10 @@ def initialize(config, &block) @procs = {} if @config['ssl_enable'] + require 'openssl' context = OpenSSL::SSL::SSLContext.new context.cert = @config['ssl_certificate'] context.key = @config['ssl_private_key'] - context.tmp_dh_callback = @config['ssl_tmp_dh_callback'] @ssl_server = OpenSSL::SSL::SSLServer.new(@server, context) end From 5a4faaaeb198b8cede59f323c7b6ed47e029c6db Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Dec 2025 22:40:45 +0900 Subject: [PATCH 1902/2435] Merge `root_box_data` into `root_box` * Make invariant `root_box` an array consist of only `root_box_data`. * Remove the unnecessary initializer list that is just overwritten in `initialize_root_box()` and missing `classext_cow_classes`. * Shrink the scope using another local `root_box`. * Make the data type constants static. --- box.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/box.c b/box.c index 7907e0ff632a1e..150a75700c9313 100644 --- a/box.c +++ b/box.c @@ -31,16 +31,8 @@ VALUE rb_cBox = 0; VALUE rb_cBoxEntry = 0; VALUE rb_mBoxLoader = 0; -static rb_box_t root_box_data = { - /* Initialize values lazily in Init_Box() */ - (VALUE)NULL, 0, - (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, (VALUE)NULL, - (struct st_table *)NULL, (struct st_table *)NULL, (VALUE)NULL, (VALUE)NULL, - false, false -}; - -static rb_box_t * root_box = &root_box_data; -static rb_box_t * main_box = 0; +static rb_box_t root_box[1]; /* Initialize in initialize_root_box() */ +static rb_box_t *main_box; static char *tmp_dir; static bool tmp_dir_has_dirsep; @@ -290,7 +282,7 @@ box_entry_memsize(const void *ptr) rb_st_memsize(box->loading_table); } -const rb_data_type_t rb_box_data_type = { +static const rb_data_type_t rb_box_data_type = { "Ruby::Box::Entry", { rb_box_entry_mark, @@ -301,7 +293,7 @@ const rb_data_type_t rb_box_data_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY // TODO: enable RUBY_TYPED_WB_PROTECTED when inserting write barriers }; -const rb_data_type_t rb_root_box_data_type = { +static const rb_data_type_t rb_root_box_data_type = { "Ruby::Box::Root", { rb_box_entry_mark, @@ -838,8 +830,6 @@ rb_box_require_relative(VALUE box, VALUE fname) static void initialize_root_box(void) { - VALUE root_box, entry; - ID id_box_entry; rb_vm_t *vm = GET_VM(); rb_box_t *root = (rb_box_t *)rb_root_box(); @@ -864,6 +854,8 @@ initialize_root_box(void) vm->root_box = root; if (rb_box_available()) { + VALUE root_box, entry; + ID id_box_entry; CONST_ID(id_box_entry, "__box_entry__"); root_box = rb_obj_alloc(rb_cBox); From ee6ba41bfdc82110468598467cd10ce850b44f0c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 14 Dec 2025 19:55:28 +0900 Subject: [PATCH 1903/2435] [ruby/openssl] Freeze more constants for Ractor compatibility https://github.com/ruby/openssl/commit/695126f582 --- ext/openssl/ossl.c | 9 +++++++-- ext/openssl/ossl_asn1.c | 18 ++++++++++-------- ext/openssl/ossl_x509.c | 3 ++- ext/openssl/ossl_x509name.c | 1 + 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 9c63d9451aa59d..b2edafd876812c 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1010,12 +1010,17 @@ Init_openssl(void) /* * Version of OpenSSL the ruby OpenSSL extension was built with */ - rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); + rb_define_const(mOSSL, "OPENSSL_VERSION", + rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); /* * Version of OpenSSL the ruby OpenSSL extension is running with */ - rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION))); + rb_define_const( + mOSSL, + "OPENSSL_LIBRARY_VERSION", + rb_obj_freeze(rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION))) + ); /* * Version number of OpenSSL the ruby OpenSSL extension was built with diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 628140a75e3ea4..71a87f04636c98 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -1397,8 +1397,6 @@ void Init_ossl_asn1(void) { #undef rb_intern - VALUE ary; - int i; sym_UNIVERSAL = ID2SYM(rb_intern_const("UNIVERSAL")); sym_CONTEXT_SPECIFIC = ID2SYM(rb_intern_const("CONTEXT_SPECIFIC")); @@ -1548,17 +1546,20 @@ Init_ossl_asn1(void) rb_define_module_function(mASN1, "traverse", ossl_asn1_traverse, 1); rb_define_module_function(mASN1, "decode", ossl_asn1_decode, 1); rb_define_module_function(mASN1, "decode_all", ossl_asn1_decode_all, 1); - ary = rb_ary_new(); + VALUE ary = rb_ary_new_capa(ossl_asn1_info_size); + for (int i = 0; i < ossl_asn1_info_size; i++) { + const char *name = ossl_asn1_info[i].name; + if (name[0] == '[') + continue; + rb_define_const(mASN1, name, INT2NUM(i)); + rb_ary_store(ary, i, rb_obj_freeze(rb_str_new_cstr(name))); + } + rb_obj_freeze(ary); /* * Array storing tag names at the tag's index. */ rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary); - for(i = 0; i < ossl_asn1_info_size; i++){ - if(ossl_asn1_info[i].name[0] == '[') continue; - rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i)); - rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name)); - } /* Document-class: OpenSSL::ASN1::ASN1Data * @@ -1880,6 +1881,7 @@ do{\ rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING)); rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING)); rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING)); + rb_obj_freeze(class_tag_map); id_each = rb_intern_const("each"); } diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c index e341ca1fbb4c1c..bc3914fda20d39 100644 --- a/ext/openssl/ossl_x509.c +++ b/ext/openssl/ossl_x509.c @@ -13,7 +13,8 @@ VALUE mX509; #define DefX509Const(x) rb_define_const(mX509, #x, INT2NUM(X509_##x)) #define DefX509Default(x,i) \ - rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i())) + rb_define_const(mX509, "DEFAULT_" #x, \ + rb_obj_freeze(rb_str_new_cstr(X509_get_default_##i()))) ASN1_TIME * ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index b91c92c1ff9670..5b3c3f7261dc26 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -534,6 +534,7 @@ Init_ossl_x509name(void) rb_hash_aset(hash, rb_str_new2("DC"), ia5str); rb_hash_aset(hash, rb_str_new2("domainComponent"), ia5str); rb_hash_aset(hash, rb_str_new2("emailAddress"), ia5str); + rb_obj_freeze(hash); /* * The default object type template for name entries. From f06eb75646e7a8d17d9c41988207a2a29a3b006c Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 9 Apr 2025 04:01:58 +0900 Subject: [PATCH 1904/2435] [ruby/openssl] ossl.c: improve docs for constants and methods under ::OpenSSL https://github.com/ruby/openssl/commit/b0de8ba9bd --- ext/openssl/lib/openssl.rb | 8 ++- ext/openssl/lib/openssl/digest.rb | 2 +- ext/openssl/lib/openssl/version.rb | 1 + ext/openssl/ossl.c | 103 ++++++++++++++++++----------- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb index 41927f32ae8238..98fa8d39f2aad6 100644 --- a/ext/openssl/lib/openssl.rb +++ b/ext/openssl/lib/openssl.rb @@ -23,12 +23,16 @@ require_relative 'openssl/x509' module OpenSSL - # call-seq: - # OpenSSL.secure_compare(string, string) -> boolean + # :call-seq: + # OpenSSL.secure_compare(string, string) -> true or false # # Constant time memory comparison. Inputs are hashed using SHA-256 to mask # the length of the secret. Returns +true+ if the strings are identical, # +false+ otherwise. + # + # This method is expensive due to the SHA-256 hashing. In most cases, where + # the input lengths are known to be equal or are not sensitive, + # OpenSSL.fixed_length_secure_compare should be used instead. def self.secure_compare(a, b) hashed_a = OpenSSL::Digest.digest('SHA256', a) hashed_b = OpenSSL::Digest.digest('SHA256', b) diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index 5cda1e931c8b44..46ddfd6021b7ea 100644 --- a/ext/openssl/lib/openssl/digest.rb +++ b/ext/openssl/lib/openssl/digest.rb @@ -57,7 +57,7 @@ class Digest < Digest; end # :nodoc: # OpenSSL::Digest("MD5") # # => OpenSSL::Digest::MD5 # - # Digest("Foo") + # OpenSSL::Digest("Foo") # # => NameError: wrong constant name Foo def Digest(name) diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 784f8885066091..6ca62f428352a5 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true module OpenSSL + # The version string of Ruby/OpenSSL. VERSION = "4.0.0.pre" end diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index b2edafd876812c..98127fcba02314 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -388,10 +388,14 @@ osslerror_detailed_message(int argc, VALUE *argv, VALUE self) * call-seq: * OpenSSL.errors -> [String...] * - * See any remaining errors held in queue. + * Returns any remaining errors held in the \OpenSSL thread-local error queue + * and clears the queue. This should normally return an empty array. * - * Any errors you see here are probably due to a bug in Ruby's OpenSSL - * implementation. + * This is intended for debugging Ruby/OpenSSL. If you see any errors here, + * it likely indicates a bug in the extension. Please file an issue at + * https://github.com/ruby/openssl. + * + * For debugging your program, OpenSSL.debug= may be useful. */ static VALUE ossl_get_errors(VALUE _) @@ -415,6 +419,8 @@ VALUE dOSSL; /* * call-seq: * OpenSSL.debug -> true | false + * + * Returns whether Ruby/OpenSSL's debug mode is currently enabled. */ static VALUE ossl_debug_get(VALUE self) @@ -424,9 +430,9 @@ ossl_debug_get(VALUE self) /* * call-seq: - * OpenSSL.debug = boolean -> boolean + * OpenSSL.debug = boolean * - * Turns on or off debug mode. With debug mode, all errors added to the OpenSSL + * Turns on or off debug mode. With debug mode, all errors added to the \OpenSSL * error queue will be printed to stderr. */ static VALUE @@ -440,6 +446,8 @@ ossl_debug_set(VALUE self, VALUE val) /* * call-seq: * OpenSSL.fips_mode -> true | false + * + * Returns whether the FIPS mode is currently enabled. */ static VALUE ossl_fips_mode_get(VALUE self) @@ -460,10 +468,10 @@ ossl_fips_mode_get(VALUE self) /* * call-seq: - * OpenSSL.fips_mode = boolean -> boolean + * OpenSSL.fips_mode = boolean * * Turns FIPS mode on or off. Turning on FIPS mode will obviously only have an - * effect for FIPS-capable installations of the OpenSSL library. Trying to do + * effect for FIPS-capable installations of the \OpenSSL library. Trying to do * so otherwise will result in an error. * * === Examples @@ -503,13 +511,13 @@ ossl_fips_mode_set(VALUE self, VALUE enabled) /* * call-seq: - * OpenSSL.fixed_length_secure_compare(string, string) -> boolean + * OpenSSL.fixed_length_secure_compare(string, string) -> true or false * * Constant time memory comparison for fixed length strings, such as results - * of HMAC calculations. + * of \HMAC calculations. * * Returns +true+ if the strings are identical, +false+ if they are of the same - * length but not identical. If the length is different, +ArgumentError+ is + * length but not identical. If the length is different, ArgumentError is * raised. */ static VALUE @@ -531,7 +539,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) } /* - * OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the + * OpenSSL provides \SSL, TLS and general purpose cryptography. It wraps the * OpenSSL[https://www.openssl.org/] library. * * = Examples @@ -586,7 +594,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * === Loading an Encrypted Key * - * OpenSSL will prompt you for your password when loading an encrypted key. + * \OpenSSL will prompt you for your password when loading an encrypted key. * If you will not be able to type in the password you may provide it when * loading the key: * @@ -649,7 +657,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * == PBKDF2 Password-based Encryption * - * If supported by the underlying OpenSSL version used, Password-based + * If supported by the underlying \OpenSSL version used, Password-based * Encryption should use the features of PKCS5. If not supported or if * required by legacy applications, the older, less secure methods specified * in RFC 2898 are also supported (see below). @@ -708,7 +716,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * decrypted = cipher.update encrypted * decrypted << cipher.final * - * == X509 Certificates + * == \X509 Certificates * * === Creating a Certificate * @@ -745,7 +753,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * extension_factory.create_extension('subjectKeyIdentifier', 'hash') * * The list of supported extensions (and in some cases their possible values) - * can be derived from the "objects.h" file in the OpenSSL source code. + * can be derived from the "objects.h" file in the \OpenSSL source code. * * === Signing a Certificate * @@ -899,23 +907,23 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * io.write csr_cert.to_pem * end * - * == SSL and TLS Connections + * == \SSL and TLS Connections * - * Using our created key and certificate we can create an SSL or TLS connection. - * An SSLContext is used to set up an SSL session. + * Using our created key and certificate we can create an \SSL or TLS + * connection. An OpenSSL::SSL::SSLContext is used to set up an \SSL session. * * context = OpenSSL::SSL::SSLContext.new * - * === SSL Server + * === \SSL Server * - * An SSL server requires the certificate and private key to communicate + * An \SSL server requires the certificate and private key to communicate * securely with its clients: * * context.cert = cert * context.key = key * - * Then create an SSLServer with a TCP server socket and the context. Use the - * SSLServer like an ordinary TCP server. + * Then create an OpenSSL::SSL::SSLServer with a TCP server socket and the + * context. Use the SSLServer like an ordinary TCP server. * * require 'socket' * @@ -934,14 +942,15 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * ssl_connection.close * end * - * === SSL client + * === \SSL client * - * An SSL client is created with a TCP socket and the context. - * SSLSocket#connect must be called to initiate the SSL handshake and start - * encryption. A key and certificate are not required for the client socket. + * An \SSL client is created with a TCP socket and the context. + * OpenSSL::SSL::SSLSocket#connect must be called to initiate the \SSL handshake + * and start encryption. A key and certificate are not required for the client + * socket. * - * Note that SSLSocket#close doesn't close the underlying socket by default. Set - * SSLSocket#sync_close to true if you want. + * Note that OpenSSL::SSL::SSLSocket#close doesn't close the underlying socket + * by default. Set OpenSSL::SSL::SSLSocket#sync_close to true if you want. * * require 'socket' * @@ -957,7 +966,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * * === Peer Verification * - * An unverified SSL connection does not provide much security. For enhanced + * An unverified \SSL connection does not provide much security. For enhanced * security the client or server can verify the certificate of its peer. * * The client can be modified to verify the server's certificate against the @@ -1008,13 +1017,14 @@ Init_openssl(void) rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* - * Version of OpenSSL the ruby OpenSSL extension was built with + * \OpenSSL library version string used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. */ rb_define_const(mOSSL, "OPENSSL_VERSION", rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); /* - * Version of OpenSSL the ruby OpenSSL extension is running with + * \OpenSSL library version string currently used at runtime. */ rb_define_const( mOSSL, @@ -1023,12 +1033,18 @@ Init_openssl(void) ); /* - * Version number of OpenSSL the ruby OpenSSL extension was built with - * (base 16). The formats are below. + * \OpenSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. * - * [OpenSSL 3] 0xMNN00PP0 (major minor 00 patch 0) - * [OpenSSL before 3] 0xMNNFFPPS (major minor fix patch status) - * [LibreSSL] 0x20000000 (fixed value) + * The version number is encoded into a single integer value. The number + * follows the format: + * + * [\OpenSSL 3.0.0 or later] + * 0xMNN00PP0 (major minor 00 patch 0) + * [\OpenSSL 1.1.1 or earlier] + * 0xMNNFFPPS (major minor fix patch status) + * [LibreSSL] + * 0x20000000 (a fixed value) * * See also the man page OPENSSL_VERSION_NUMBER(3). */ @@ -1036,9 +1052,12 @@ Init_openssl(void) #if defined(LIBRESSL_VERSION_NUMBER) /* - * Version number of LibreSSL the ruby OpenSSL extension was built with - * (base 16). The format is 0xMNNFF00f (major minor fix 00 - * status). This constant is only defined in LibreSSL cases. + * LibreSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. + * + * This constant is only defined if the extension was compiled against + * LibreSSL. The number follows the format: + * 0xMNNFF00f (major minor fix 00 status). * * See also the man page LIBRESSL_VERSION_NUMBER(3). */ @@ -1046,7 +1065,11 @@ Init_openssl(void) #endif /* - * Boolean indicating whether OpenSSL is FIPS-capable or not + * Boolean indicating whether the \OpenSSL library is FIPS-capable or not. + * Always true for \OpenSSL 3.0 and later. + * + * This is obsolete and will be removed in the future. + * See also OpenSSL.fips_mode. */ rb_define_const(mOSSL, "OPENSSL_FIPS", /* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ From f0793731853c0e130f798e9dc5c736b2fa1b72b7 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 14 Dec 2025 19:10:04 +0900 Subject: [PATCH 1905/2435] [ruby/openssl] Ruby/OpenSSL 4.0.0 https://github.com/ruby/openssl/commit/5af1edab18 --- ext/openssl/History.md | 85 ++++++++++++++++++++++++++++++ ext/openssl/lib/openssl/version.rb | 2 +- ext/openssl/openssl.gemspec | 2 +- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/ext/openssl/History.md b/ext/openssl/History.md index 32a2c0b2fb6ea7..419237ff167f9a 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,88 @@ +Version 4.0.0 +============= + +Compatibility +------------- + +* Ruby >= 2.7 +* OpenSSL >= 1.1.1, LibreSSL >= 3.9, and AWS-LC 1.66.0 + - Removed support for OpenSSL 1.0.2-1.1.0 and LibreSSL 3.1-3.8. + [[GitHub #835]](https://github.com/ruby/openssl/issues/835) + - Added support for AWS-LC. + [[GitHub #833]](https://github.com/ruby/openssl/issues/833) + + +Notable changes +--------------- + +* `OpenSSL::SSL` + - Reduce overhead when writing to `OpenSSL::SSL::SSLSocket`. `#syswrite` no + longer creates a temporary String object. + [[GitHub #831]](https://github.com/ruby/openssl/pull/831) + - Make `OpenSSL::SSL::SSLContext#min_version=` and `#max_version=` wrap the + corresponding OpenSSL APIs directly, and remove the fallback to SSL options. + [[GitHub #849]](https://github.com/ruby/openssl/pull/849) + - Add `OpenSSL::SSL::SSLContext#sigalgs=` and `#client_sigalgs=` for + specifying signature algorithms to use for connections. + [[GitHub #895]](https://github.com/ruby/openssl/pull/895) + - Rename `OpenSSL::SSL::SSLContext#ecdh_curves=` to `#groups=` following + the underlying OpenSSL API rename. This method is no longer specific to + ECDHE. The old method remains as an alias. + [[GitHub #900]](https://github.com/ruby/openssl/pull/900) + - Add `OpenSSL::SSL::SSLSocket#sigalg`, `#peer_sigalg`, and `#group` for + getting the signature algorithm and the key agreement group used in the + current connection. + [[GitHub #908]](https://github.com/ruby/openssl/pull/908) + - Enable `SSL_CTX_set_dh_auto()` for servers by default. + [[GitHub #924]](https://github.com/ruby/openssl/pull/924) + - Improve Ractor compatibility. Note that the internal-use constant + `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS` is now frozen. + [[GitHub #925]](https://github.com/ruby/openssl/pull/925) +* `OpenSSL::PKey` + - Remove `OpenSSL::PKey::EC::Point#mul` support with array arguments. The + underlying OpenSSL API has been removed, and the method has been deprecated + since ruby/openssl v3.0.0. + [[GitHub #843]](https://github.com/ruby/openssl/pull/843) + - `OpenSSL::PKey::{RSA,DSA,DH}#params` uses `nil` to indicate missing fields + instead of the number `0`. + [[GitHub #774]](https://github.com/ruby/openssl/pull/774) + - Unify `OpenSSL::PKey::PKeyError` classes. The former subclasses + `OpenSSL::PKey::DHError`, `OpenSSL::PKey::DSAError`, + `OpenSSL::PKey::ECError`, and `OpenSSL::PKey::RSAError` have been merged + into a single class. + [[GitHub #929]](https://github.com/ruby/openssl/pull/929) +* `OpenSSL::Cipher` + - `OpenSSL::Cipher#encrypt` and `#decrypt` no longer accept arguments. + Passing passwords has been deprecated since Ruby 1.8.2 (released in 2004). + [[GitHub #887]](https://github.com/ruby/openssl/pull/887) + - `OpenSSL::Cipher#final` raises `OpenSSL::Cipher::AuthTagError` when the + integrity check fails for AEAD ciphers. `OpenSSL::Cipher::AuthTagError` is a + new subclass of `OpenSSL::Cipher::CipherError`, which was previously raised. + [[GitHub #939]](https://github.com/ruby/openssl/pull/939) + - `OpenSSL::Cipher.new` now raises `OpenSSL::Cipher::CipherError` instead of + `RuntimeError` when OpenSSL does not recognize the algorithm. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) + - Add support for "fetched" cipher algorithms with OpenSSL 3.0 or later. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) +* `OpenSSL::Digest` + - `OpenSSL::Digest.new` now raises `OpenSSL::Digest::DigestError` instead of + `RuntimeError` when OpenSSL does not recognize the algorithm. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) + - Add support for "fetched" digest algorithms with OpenSSL 3.0 or later. + [[GitHub #958]](https://github.com/ruby/openssl/pull/958) +* `OpenSSL::ASN1.decode` now assumes a 1950-2049 year range for `UTCTime` + according to RFC 5280. It previously used a 1969-2068 range. The encoder + has always used the 1950-2049 range. + [[GitHub #909]](https://github.com/ruby/openssl/pull/909) +* `OpenSSL::OpenSSLError`, the base class for all ruby/openssl errors, carry + an additional attribute `#errors` to keep the content of OpenSSL's error + queue. Also, add `#detailed_message` for Ruby 3.2 or later. + [[GitHub #976]](https://github.com/ruby/openssl/pull/976) +* `OpenSSL::PKCS7.new` raises `OpenSSL::PKCS7::PKCS7Error` instead of + `ArgumentError` on error to be consistent with other constructors. + [[GitHub #983]](https://github.com/ruby/openssl/pull/983) + + Version 3.3.2 ============= diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 6ca62f428352a5..88570562e255c3 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -2,5 +2,5 @@ module OpenSSL # The version string of Ruby/OpenSSL. - VERSION = "4.0.0.pre" + VERSION = "4.0.0" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 061a9c5a6ab626..7072d599d86d69 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "4.0.0.pre" + spec.version = "4.0.0" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} From 35209caef2b46de3a0625c2c40330ab3c6ebb5a3 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 15 Dec 2025 09:51:22 +0000 Subject: [PATCH 1906/2435] Update default gems list at f0793731853c0e130f798e9dc5c736 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 947d3d5eeee93f..1389c892f435d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -286,7 +286,7 @@ The following default gems are updated. * ipaddr 1.2.8 * json 2.18.0 * net-http 0.8.0 -* openssl 4.0.0.pre +* openssl 4.0.0 * optparse 0.8.1 * pp 0.6.3 * prism 1.6.0 From bbc10ed0cdcd2fe9d7d09a9dcc5f036bc4425aef Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 15 Dec 2025 12:10:49 +0100 Subject: [PATCH 1907/2435] Add NEWS entry for Array#rfind and Array#find --- NEWS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1389c892f435d0..cd038f08d0d563 100644 --- a/NEWS.md +++ b/NEWS.md @@ -82,6 +82,11 @@ Note: We're only listing outstanding class updates. * A deprecated behavior, process creation by `Kernel#open` with a leading `|`, was removed. [[Feature #19630]] +* Array + + * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] + * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] + * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -478,4 +483,5 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 +[Feature #21678]: https://bugs.ruby-lang.org/issues/21678 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 From eceab2f44c631ceda13656728ae23b0750941001 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 14 Dec 2025 20:39:10 -0500 Subject: [PATCH 1908/2435] [ruby/prism] Escape error location is incorrect for some regex When you have a regular expression that has a named capture that has an escape sequence in the named capture, and that escape sequence is a unicode escape sequence with an invalid surrogate pair, the error was attached to the owned string as opposed to a location on the shared source. https://github.com/ruby/prism/commit/793a7a6a0a --- prism/prism.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 677e65056f8f74..06e692c95ea696 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8613,7 +8613,7 @@ escape_hexadecimal_digit(const uint8_t value) { * validated. */ static inline uint32_t -escape_unicode(pm_parser_t *parser, const uint8_t *string, size_t length) { +escape_unicode(pm_parser_t *parser, const uint8_t *string, size_t length, const pm_location_t *error_location) { uint32_t value = 0; for (size_t index = 0; index < length; index++) { if (index != 0) value <<= 4; @@ -8623,7 +8623,11 @@ escape_unicode(pm_parser_t *parser, const uint8_t *string, size_t length) { // Here we're going to verify that the value is actually a valid Unicode // codepoint and not a surrogate pair. if (value >= 0xD800 && value <= 0xDFFF) { - pm_parser_err(parser, string, string + length, PM_ERR_ESCAPE_INVALID_UNICODE); + if (error_location != NULL) { + pm_parser_err(parser, error_location->start, error_location->end, PM_ERR_ESCAPE_INVALID_UNICODE); + } else { + pm_parser_err(parser, string, string + length, PM_ERR_ESCAPE_INVALID_UNICODE); + } return 0xFFFD; } @@ -8923,7 +8927,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre extra_codepoints_start = unicode_start; } - uint32_t value = escape_unicode(parser, unicode_start, hexadecimal_length); + uint32_t value = escape_unicode(parser, unicode_start, hexadecimal_length, NULL); escape_write_unicode(parser, buffer, flags, unicode_start, parser->current.end, value); parser->current.end += pm_strspn_inline_whitespace(parser->current.end, parser->end - parser->current.end); @@ -8964,7 +8968,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre PM_PARSER_ERR_FORMAT(parser, start, parser->current.end, PM_ERR_ESCAPE_INVALID_UNICODE_SHORT, 2, start); } } else if (length == 4) { - uint32_t value = escape_unicode(parser, parser->current.end, 4); + uint32_t value = escape_unicode(parser, parser->current.end, 4, NULL); if (flags & PM_ESCAPE_FLAG_REGEXP) { pm_buffer_append_bytes(regular_expression_buffer, start, (size_t) (parser->current.end + 4 - start)); @@ -20368,7 +20372,7 @@ pm_named_capture_escape_octal(pm_buffer_t *unescaped, const uint8_t *cursor, con } static inline const uint8_t * -pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8_t *cursor, const uint8_t *end) { +pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8_t *cursor, const uint8_t *end, const pm_location_t *error_location) { const uint8_t *start = cursor - 1; cursor++; @@ -20379,7 +20383,7 @@ pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, con if (*cursor != '{') { size_t length = pm_strspn_hexadecimal_digit(cursor, MIN(end - cursor, 4)); - uint32_t value = escape_unicode(parser, cursor, length); + uint32_t value = escape_unicode(parser, cursor, length, error_location); if (!pm_buffer_append_unicode_codepoint(unescaped, value)) { pm_buffer_append_string(unescaped, (const char *) start, (size_t) ((cursor + length) - start)); @@ -20402,7 +20406,7 @@ pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, con if (length == 0) { break; } - uint32_t value = escape_unicode(parser, cursor, length); + uint32_t value = escape_unicode(parser, cursor, length, error_location); (void) pm_buffer_append_unicode_codepoint(unescaped, value); cursor += length; @@ -20412,7 +20416,7 @@ pm_named_capture_escape_unicode(pm_parser_t *parser, pm_buffer_t *unescaped, con } static void -pm_named_capture_escape(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8_t *source, const size_t length, const uint8_t *cursor) { +pm_named_capture_escape(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8_t *source, const size_t length, const uint8_t *cursor, const pm_location_t *error_location) { const uint8_t *end = source + length; pm_buffer_append_string(unescaped, (const char *) source, (size_t) (cursor - source)); @@ -20430,7 +20434,7 @@ pm_named_capture_escape(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8 cursor = pm_named_capture_escape_octal(unescaped, cursor, end); break; case 'u': - cursor = pm_named_capture_escape_unicode(parser, unescaped, cursor, end); + cursor = pm_named_capture_escape_unicode(parser, unescaped, cursor, end, error_location); break; default: pm_buffer_append_byte(unescaped, '\\'); @@ -20473,7 +20477,7 @@ parse_regular_expression_named_capture(const pm_string_t *capture, void *data) { // unescaped, which is what we need. const uint8_t *cursor = pm_memchr(source, '\\', length, parser->encoding_changed, parser->encoding); if (PRISM_UNLIKELY(cursor != NULL)) { - pm_named_capture_escape(parser, &unescaped, source, length, cursor); + pm_named_capture_escape(parser, &unescaped, source, length, cursor, callback_data->shared ? NULL : &call->receiver->location); source = (const uint8_t *) pm_buffer_value(&unescaped); length = pm_buffer_length(&unescaped); } From ac946e076c0136b070c84f66c8b018f8ab6b2223 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 15 Dec 2025 08:43:17 -0500 Subject: [PATCH 1909/2435] [ruby/prism] Unreference before destroying in call node in pattern https://github.com/ruby/prism/commit/609c80c91e --- prism/prism.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index 06e692c95ea696..f98032cd73b0b1 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -16690,6 +16690,8 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm if (PM_NODE_TYPE(node) == PM_CALL_NODE) { pm_parser_err_node(parser, node, diag_id); pm_missing_node_t *missing_node = pm_missing_node_create(parser, node->location.start, node->location.end); + + pm_node_unreference(parser, node); pm_node_destroy(parser, node); return UP(missing_node); } From bb0e42c5e706f238dce40abb9cf5322e4a8a73cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 15 Dec 2025 14:36:53 +0100 Subject: [PATCH 1910/2435] Define Array#detect as an alias for Array#find Otherwise Array#detect is Enumerable#detect while Array#find uses a different more performant implementation. [Feature #21678] --- array.c | 1 + 1 file changed, 1 insertion(+) diff --git a/array.c b/array.c index 1352b5c046ae1d..b9d91d0a9b296f 100644 --- a/array.c +++ b/array.c @@ -8910,6 +8910,7 @@ Init_Array(void) rb_define_method(rb_cArray, "size", rb_ary_length, 0); rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0); rb_define_method(rb_cArray, "find", rb_ary_find, -1); + rb_define_method(rb_cArray, "detect", rb_ary_find, -1); rb_define_method(rb_cArray, "rfind", rb_ary_rfind, -1); rb_define_method(rb_cArray, "find_index", rb_ary_index, -1); rb_define_method(rb_cArray, "index", rb_ary_index, -1); From 6b63b0cbeb33c8606f147b0293e9b8ed8c34a0e5 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 15 Dec 2025 07:07:54 -0800 Subject: [PATCH 1911/2435] [DOC] Update Set#inspect description in NEWS --- NEWS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index cd038f08d0d563..b778c8f24c059f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -205,10 +205,8 @@ Note: We're only listing outstanding class updates. * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] - * `Set#inspect` now returns a string suitable for `eval`, using the - `Set[]` syntax (e.g., `Set[1, 2, 3]` instead of - `#`). This makes it consistent with other core - collection classes like Array and Hash. [[Feature #21389]] + * `Set#inspect` now uses a simpler displays, similar to literal arrays. + (e.g., `Set[1, 2, 3]` instead of `#`). [[Feature #21389]] * Passing arguments to `Set#to_set` and `Enumerable#to_set` is now deprecated. [[Feature #21390]] From 3038286a4bf7832f1c42c8cc9774ee6ff19876fc Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 15 Dec 2025 11:48:34 -0500 Subject: [PATCH 1912/2435] Fix Socket.tcp cleanup after Thread#kill (#15131) Socket.tcp launches ruby threads to resolve hostnames, and those threads communicate through a queue implemented with `IO.pipe`. When the thread that called `Socket.tcp` is killed, the resolver threads still try to communicate through the pipe even though it may be closed. The method `Socket.tcp_with_fast_fallback` tries to deal with this by killing the threads in an ensure block, and then closing the pipe. However, calling `Thread#kill` is not a blocking operation, it only sets a flag on the thread telling it to raise during the next interrupt. The thread needs to be joined to ensure it is terminated. The following script demonstrates the issue: ```ruby require "socket" ts = [] 5.times do ts << Thread.new do loop do 1_000.times do |i| puts "#{i}" t = Thread.new do Socket.tcp("ruby-lang.org", 80) end sleep 0.001 t.kill end end end end ts.each(&:join) ``` output: ``` /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#write': closed stream (IOError) from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#putc' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'block in Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Thread::Mutex#synchronize' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:980:in 'Socket.resolve_hostname' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:719:in 'block (2 levels) in Socket.tcp_with_fast_fallback' /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#write': closed stream (IOError) from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#putc' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'block in Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Thread::Mutex#synchronize' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:978:in 'Socket.resolve_hostname' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:719:in 'block (2 levels) in Socket.tcp_with_fast_fallback' ``` --- ext/socket/lib/socket.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 9862c92c0b6591..dae1c16760a550 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -917,7 +917,8 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end ensure hostname_resolution_threads.each do |thread| - thread.exit + thread.kill + thread.join end hostname_resolution_result&.close From 9581d6c8988f9b54637b9872c42c79d16ff9fcb3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 15 Dec 2025 11:52:35 -0500 Subject: [PATCH 1913/2435] ZJIT: Add iongraph-generating Ruby script (#15466) Run like so: $ ../tool/zjit_iongraph.rb ../build-dev/miniruby --zjit-call-threshold=2 tmp/ghbug.rb false false tmp/ghbug.rb:3:in 'Object#doit': this shouldnt ever be nil (RuntimeError) from tmp/ghbug.rb:10:in '
' W, [2025-12-09T11:00:32.070382 #67400] WARN -- : Command failed with exit status 1 zjit_iongraph_67405.html $ Then open zjit_iongraph_67405.html with your browser. --- tool/zjit_iongraph.html | 546 ++++++++++++++++++++++++++++++++++++++++ tool/zjit_iongraph.rb | 38 +++ 2 files changed, 584 insertions(+) create mode 100644 tool/zjit_iongraph.html create mode 100755 tool/zjit_iongraph.rb diff --git a/tool/zjit_iongraph.html b/tool/zjit_iongraph.html new file mode 100644 index 00000000000000..e97ac74e4ac411 --- /dev/null +++ b/tool/zjit_iongraph.html @@ -0,0 +1,546 @@ + + + + + + iongraph + + + + + +
+ + + + diff --git a/tool/zjit_iongraph.rb b/tool/zjit_iongraph.rb new file mode 100755 index 00000000000000..0cb770161494f1 --- /dev/null +++ b/tool/zjit_iongraph.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +require 'json' +require 'logger' + +LOGGER = Logger.new($stderr) + +def run_ruby *cmd + # Find the first --zjit* option and add --zjit-dump-hir-iongraph after it + zjit_index = cmd.find_index { |arg| arg.start_with?("--zjit") } + raise "No --zjit option found in command" unless zjit_index + cmd.insert(zjit_index + 1, "--zjit-dump-hir-iongraph") + pid = Process.spawn(*cmd) + _, status = Process.wait2(pid) + if status.exitstatus != 0 + LOGGER.warn("Command failed with exit status #{status.exitstatus}") + end + pid +end + +usage = "Usage: zjit_iongraph.rb " +RUBY = ARGV[0] || raise(usage) +OPTIONS = ARGV[1..] +pid = run_ruby(RUBY, *OPTIONS) +functions = Dir["/tmp/zjit-iongraph-#{pid}/fun*.json"].map do |path| + JSON.parse(File.read(path)) +end + +if functions.empty? + LOGGER.warn("No iongraph functions found for PID #{pid}") +end + +json = JSON.dump({version: 1, functions: functions}) +# Get zjit_iongraph.html from the sibling file next to this script +html = File.read(File.join(File.dirname(__FILE__), "zjit_iongraph.html")) +html.sub!("{{ IONJSON }}", json) +output_path = "zjit_iongraph_#{pid}.html" +File.write(output_path, html) +puts "Wrote iongraph to #{output_path}" From fdd8bdea8174835a5d83f561add9212e64f74cc0 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Tue, 16 Dec 2025 02:14:06 +0900 Subject: [PATCH 1914/2435] [ruby/erb] Freeze ERB::Compiler::TrimScanner::ERB_STAG (https://github.com/ruby/erb/pull/100) For Ractor compatibility. https://github.com/ruby/erb/commit/43f0876595 --- lib/erb/compiler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb index 8a4d85c6ea5d54..6d70288b4f55c1 100644 --- a/lib/erb/compiler.rb +++ b/lib/erb/compiler.rb @@ -225,7 +225,7 @@ def explicit_trim_line(line) end end - ERB_STAG = %w(<%= <%# <%) + ERB_STAG = %w(<%= <%# <%).freeze def is_erb_stag?(s) ERB_STAG.member?(s) end From adf676c530b5da2822aa3b03ee43e5507111a0b2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 15 Dec 2025 09:14:54 -0800 Subject: [PATCH 1915/2435] [ruby/erb] Version 6.0.1 https://github.com/ruby/erb/commit/bbde68fcd5 --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 3794e8288b1bb6..e367a1b5c7ef4b 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB # The string \ERB version. - VERSION = '6.0.0' + VERSION = '6.0.1' end From 74b24e09d4f0ba0b930da2fe908002b81e8571a6 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 15 Dec 2025 17:16:21 +0000 Subject: [PATCH 1916/2435] Update default gems list at adf676c530b5da2822aa3b03ee43e5 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index b778c8f24c059f..dbf44b63642720 100644 --- a/NEWS.md +++ b/NEWS.md @@ -278,7 +278,7 @@ The following default gems are updated. * date 3.5.1 * digest 3.2.1 * english 0.8.1 -* erb 6.0.0 +* erb 6.0.1 * etc 1.4.6 * fcntl 1.3.0 * fileutils 1.8.0 From 98ab418fed61079f8ef7e229ea1b41957ec5722b Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 15 Dec 2025 17:07:15 -0500 Subject: [PATCH 1917/2435] Revert "Fix Socket.tcp cleanup after Thread#kill (#15131)" (#15565) This reverts commit 3038286a4bf7832f1c42c8cc9774ee6ff19876fc. The following CI failure scared me: https://github.com/ruby/ruby/actions/runs/20241051861/job/58108997049 ``` 1) Timeout: TestResolvDNS#test_multiple_servers_with_timeout_and_truncated_tcp_fallback ``` Since it could be related, I'm reverting this for now. --- ext/socket/lib/socket.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index dae1c16760a550..9862c92c0b6591 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -917,8 +917,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end ensure hostname_resolution_threads.each do |thread| - thread.kill - thread.join + thread.exit end hostname_resolution_result&.close From 7fbf321d23f3cc20988c80fae25ce131a5d1b231 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 15 Dec 2025 20:20:25 +0000 Subject: [PATCH 1918/2435] [DOC] Harmonize #** methods --- complex.c | 4 ++-- numeric.c | 11 +++++------ rational.c | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/complex.c b/complex.c index 12dec4d23b04c8..c34c798a158457 100644 --- a/complex.c +++ b/complex.c @@ -1122,9 +1122,9 @@ complex_pow_for_special_angle(VALUE self, VALUE other) /* * call-seq: - * complex ** numeric -> new_complex + * self ** exponent -> complex * - * Returns +self+ raised to power +numeric+: + * Returns +self+ raised to the power +exponent+: * * Complex.rect(0, 1) ** 2 # => (-1+0i) * Complex.rect(-8) ** Rational(1, 3) # => (1.0000000000000002+1.7320508075688772i) diff --git a/numeric.c b/numeric.c index f96535cd2461d2..cb3ca129047669 100644 --- a/numeric.c +++ b/numeric.c @@ -1390,9 +1390,9 @@ flo_divmod(VALUE x, VALUE y) /* * call-seq: - * self ** other -> numeric + * self ** exponent -> numeric * - * Raises +self+ to the power of +other+: + * Returns +self+ raised to the power +exponent+: * * f = 3.14 * f ** 2 # => 9.8596 @@ -4623,9 +4623,9 @@ rb_int_divmod(VALUE x, VALUE y) /* * call-seq: - * self ** numeric -> numeric_result + * self ** exponent -> numeric * - * Raises +self+ to the power of +numeric+: + * Returns +self+ raised to the power +exponent+: * * 2 ** 3 # => 8 * 2 ** -3 # => (1/8) @@ -4748,8 +4748,7 @@ fix_pow(VALUE x, VALUE y) * call-seq: * self ** exponent -> numeric * - * Returns the value of base +self+ raised to the power +exponent+; - * see {Exponentiation}[https://en.wikipedia.org/wiki/Exponentiation]: + * Returns +self+ raised to the power +exponent+: * * # Result for non-negative Integer exponent is Integer. * 2 ** 0 # => 1 diff --git a/rational.c b/rational.c index 7c2bd0b1eb7f00..8c9b80cb62fe21 100644 --- a/rational.c +++ b/rational.c @@ -984,9 +984,9 @@ nurat_fdiv(VALUE self, VALUE other) /* * call-seq: - * rat ** numeric -> numeric + * self ** exponent -> numeric * - * Performs exponentiation. + * Returns +self+ raised to the power +exponent+: * * Rational(2) ** Rational(3) #=> (8/1) * Rational(10) ** -2 #=> (1/100) From acbf55f4e6658f94e49f28b4df21ab0c29683c4b Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 15 Dec 2025 20:35:27 +0000 Subject: [PATCH 1919/2435] [DOC] Harmonize #- methods --- complex.c | 4 ++-- numeric.c | 6 +++--- rational.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/complex.c b/complex.c index c34c798a158457..2a92f4c7028b37 100644 --- a/complex.c +++ b/complex.c @@ -861,9 +861,9 @@ rb_complex_plus(VALUE self, VALUE other) /* * call-seq: - * complex - numeric -> new_complex + * self - other -> complex * - * Returns the difference of +self+ and +numeric+: + * Returns the difference of +self+ and +other+: * * Complex.rect(2, 3) - Complex.rect(2, 3) # => (0+0i) * Complex.rect(900) - Complex.rect(1) # => (899+0i) diff --git a/numeric.c b/numeric.c index cb3ca129047669..5f2f4ea79743a4 100644 --- a/numeric.c +++ b/numeric.c @@ -1089,7 +1089,7 @@ rb_float_plus(VALUE x, VALUE y) * call-seq: * self - other -> numeric * - * Returns a new \Float which is the difference of +self+ and +other+: + * Returns the difference of +self+ and +other+: * * f = 3.14 * f - 1 # => 2.14 @@ -4197,9 +4197,9 @@ fix_minus(VALUE x, VALUE y) /* * call-seq: - * self - numeric -> numeric_result + * self - other -> numeric * - * Performs subtraction: + * Returns the difference of +self+ and +other+: * * 4 - 2 # => 2 * -4 - 2 # => -6 diff --git a/rational.c b/rational.c index 8c9b80cb62fe21..f03a98a9ee2cd9 100644 --- a/rational.c +++ b/rational.c @@ -768,9 +768,9 @@ rb_rational_plus(VALUE self, VALUE other) /* * call-seq: - * rat - numeric -> numeric + * self - other -> numeric * - * Performs subtraction. + * Returns the difference of +self+ and +other+: * * Rational(2, 3) - Rational(2, 3) #=> (0/1) * Rational(900) - Rational(1) #=> (899/1) From cfd41cbf0338a28638ed54edb0de13539b159dfb Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 15 Dec 2025 21:28:41 +0000 Subject: [PATCH 1920/2435] [DOC] Harmonize #-@ methods --- complex.c | 4 ++-- numeric.c | 2 +- numeric.rb | 17 +++++++++++++---- rational.c | 8 ++++++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/complex.c b/complex.c index 2a92f4c7028b37..eab6bde85ab82b 100644 --- a/complex.c +++ b/complex.c @@ -799,9 +799,9 @@ rb_complex_imag(VALUE self) /* * call-seq: - * -complex -> new_complex + * -self -> complex * - * Returns the negation of +self+, which is the negation of each of its parts: + * Returns +self+, negated, which is the negation of each of its parts: * * -Complex.rect(1, 2) # => (-1-2i) * -Complex.rect(-1, -2) # => (1+2i) diff --git a/numeric.c b/numeric.c index 5f2f4ea79743a4..4e105baa81532b 100644 --- a/numeric.c +++ b/numeric.c @@ -576,7 +576,7 @@ num_imaginary(VALUE num) * call-seq: * -self -> numeric * - * Unary Minus---Returns the receiver, negated. + * Returns +self+, negated. */ static VALUE diff --git a/numeric.rb b/numeric.rb index 552a3dd687aedc..306561943d5029 100644 --- a/numeric.rb +++ b/numeric.rb @@ -93,9 +93,14 @@ def +@ class Integer # call-seq: - # -int -> integer + # -self -> integer + # + # Returns +self+, negated: + # + # -1 # => -1 + # -(-1) # => 1 + # -0 # => 0 # - # Returns +self+, negated. def -@ Primitive.attr! :leaf Primitive.cexpr! 'rb_int_uminus(self)' @@ -373,9 +378,13 @@ def abs alias magnitude abs # call-seq: - # -float -> float + # -self -> float + # + # Returns +self+, negated: # - # Returns +self+, negated. + # -3.14 # => -3.14 + # -(-3.14) # => 3.14 + # -0.0 # => -0.0 # def -@ Primitive.attr! :leaf diff --git a/rational.c b/rational.c index f03a98a9ee2cd9..05dc3540c09442 100644 --- a/rational.c +++ b/rational.c @@ -609,9 +609,13 @@ nurat_denominator(VALUE self) /* * call-seq: - * -rat -> rational + * -self -> rational + * + * Returns +self+, negated: + * + * -(1/3r) # => (-1/3) + * -(-1/3r) # => (1/3) * - * Negates +rat+. */ VALUE rb_rational_uminus(VALUE self) From b3f0fb56187dd9711fac0baae81a3f1ed0b5714a Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 11 Dec 2025 15:25:59 -0500 Subject: [PATCH 1921/2435] [ruby/psych] Replace C extension with Data#initialize bind_call https://github.com/ruby/psych/commit/6a826693ba --- ext/psych/lib/psych/visitors/to_ruby.rb | 7 +++++-- ext/psych/psych_to_ruby.c | 7 ------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index e62311ae12d177..475444e589d1ca 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -12,6 +12,10 @@ module Visitors ### # This class walks a YAML AST, converting each node to Ruby class ToRuby < Psych::Visitors::Visitor + unless RUBY_VERSION < "3.2" + DATA_INITIALIZE = Data.instance_method(:initialize) + end + def self.create(symbolize_names: false, freeze: false, strict_integer: false, parse_symbols: true) class_loader = ClassLoader.new scanner = ScalarScanner.new class_loader, strict_integer: strict_integer, parse_symbols: parse_symbols @@ -219,8 +223,7 @@ def visit_Psych_Nodes_Mapping o revive_data_members(members, o) end data ||= allocate_anon_data(o, members) - values = data.members.map { |m| members[m] } - init_data(data, values) + DATA_INITIALIZE.bind_call(data, **members) data.freeze data diff --git a/ext/psych/psych_to_ruby.c b/ext/psych/psych_to_ruby.c index 0132b2c94e28c7..3ab0138b52417c 100644 --- a/ext/psych/psych_to_ruby.c +++ b/ext/psych/psych_to_ruby.c @@ -28,12 +28,6 @@ static VALUE path2class(VALUE self, VALUE path) return rb_path_to_class(path); } -static VALUE init_data(VALUE self, VALUE data, VALUE values) -{ - rb_struct_initialize(data, values); - return data; -} - void Init_psych_to_ruby(void) { VALUE psych = rb_define_module("Psych"); @@ -43,7 +37,6 @@ void Init_psych_to_ruby(void) VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); - rb_define_private_method(cPsychVisitorsToRuby, "init_data", init_data, 2); rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); rb_define_private_method(class_loader, "path2class", path2class, 1); } From abefd3e8ff1853800d4df5b28388191b51d9ec37 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 10 Dec 2025 21:11:05 +0100 Subject: [PATCH 1922/2435] [ruby/psych] Check that Data members match exactly * Fixes https://github.com/ruby/psych/issues/760 https://github.com/ruby/psych/commit/952008c898 --- test/psych/test_data.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/psych/test_data.rb b/test/psych/test_data.rb index a67a037b9e4af5..57c3478193a526 100644 --- a/test/psych/test_data.rb +++ b/test/psych/test_data.rb @@ -64,6 +64,31 @@ def test_load assert_equal "hello", obj.bar assert_equal "bar", obj.foo end + + def test_members_must_be_identical + TestData.const_set :D, Data.define(:a, :b) + d = Psych.dump(TestData::D.new(1, 2)) + + # more members + TestData.send :remove_const, :D + TestData.const_set :D, Data.define(:a, :b, :c) + e = assert_raise(ArgumentError) { Psych.unsafe_load d } + assert_equal 'missing keyword: :c', e.message + + # less members + TestData.send :remove_const, :D + TestData.const_set :D, Data.define(:a) + e = assert_raise(ArgumentError) { Psych.unsafe_load d } + assert_equal 'unknown keyword: :b', e.message + + # completely different members + TestData.send :remove_const, :D + TestData.const_set :D, Data.define(:foo, :bar) + e = assert_raise(ArgumentError) { Psych.unsafe_load d } + assert_equal 'unknown keywords: :a, :b', e.message + ensure + TestData.send :remove_const, :D + end end end From 98cac1a75d9d9b7ff4cfd5b5d0fd99c1b5f17631 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 15 Dec 2025 19:10:04 -0500 Subject: [PATCH 1923/2435] Point people to redmine on ZJIT docs (#15499) Fix https://github.com/Shopify/ruby/issues/900 --- doc/jit/zjit.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index c66235269bd265..b5a2f05604983f 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -11,6 +11,38 @@ This project is open source and falls under the same license as CRuby. ZJIT may not be suitable for certain applications. It currently only supports macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs. ZJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state information. You can change how much executable memory is allocated using [ZJIT's command-line options](rdoc-ref:@Command-Line+Options). +## Contributing + +We welcome open source contributions. Feel free to open new issues to report +bugs or just to ask questions. Suggestions on how to make this document more +helpful for new contributors are most welcome. + +Bug fixes and bug reports are very valuable to us. If you find a bug in ZJIT, +it's very possible that nobody has reported it before, or that we don't have +a good reproduction for it, so please open a ticket on [the official Ruby bug +tracker][rubybugs] (or, if you don't want to make an account, [on +Shopify/ruby][shopifyruby]) and provide as much information as you can about +your configuration and a description of how you encountered the problem. List +the commands you used to run ZJIT so that we can easily reproduce the issue on +our end and investigate it. If you are able to produce a small program +reproducing the error to help us track it down, that is very much appreciated +as well. + +[rubybugs]: https://bugs.ruby-lang.org/projects/ruby-master +[shopifyruby]: https://github.com/Shopify/ruby/issues + +If you would like to contribute a large patch to ZJIT, we suggest [chatting on +Zulip][zulip] for a casual chat and then opening an issue on the [Shopify/ruby +repository][shopifyruby] so that we can have a technical discussion. A common +problem is that sometimes people submit large pull requests to open source +projects without prior communication, and we have to reject them because the +work they implemented does not fit within the design of the project. We want to +save you time and frustration, so please reach out so we can have a productive +discussion as to how you can contribute patches we will want to merge into +ZJIT. + +[zulip]: https://zjit.zulipchat.com/ + ## Build Instructions Refer to [Building Ruby](rdoc-ref:contributing/building_ruby.md) for general build prerequists. From f88e7970901d3d8cfb6efeb536d983bf2e05b04a Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 15 Dec 2025 15:56:41 +0100 Subject: [PATCH 1924/2435] [ruby/rubygems] Allow bundle pristine to work for git gems in the same repo: - Fix https://github.com/ruby/rubygems/pull/9186 - ### Problem Running `bundle pristine` in a Gemfile where there is many git gem pointing to the same repository will result in a error "Another git process seems to be running in this repository". ### Context This error is a regression since https://github.com/ruby/rubygems/commit/a555fd6ccd17 where `bundle pristine` now runs in parallel which could lead to running simultaneous git operations in the same repository. ### Solution When Bundler pristine a git gem it does a `git reset --hard` without specifying a path. This means the whole repository will be reset. In this case, we can leverage that by just pristining one gem per unique git sources. This is also more efficient. https://github.com/ruby/rubygems/commit/710ba514a8 --- lib/bundler/cli/pristine.rb | 4 ++ spec/bundler/commands/pristine_spec.rb | 60 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb index cfd4a995a31faa..b8545fe4c9c3eb 100644 --- a/lib/bundler/cli/pristine.rb +++ b/lib/bundler/cli/pristine.rb @@ -11,6 +11,7 @@ def run definition = Bundler.definition definition.validate_runtime! installer = Bundler::Installer.new(Bundler.root, definition) + git_sources = [] ProcessLock.lock do installed_specs = definition.specs.reject do |spec| @@ -41,6 +42,9 @@ def run end FileUtils.rm_rf spec.extension_dir FileUtils.rm_rf spec.full_gem_path + + next if git_sources.include?(source) + git_sources << source else Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.") next diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index da61dc819920fb..daeafea43bf87e 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -89,6 +89,66 @@ expect(changes_txt).to be_file expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is locally overridden.") end + + it "doesn't run multiple git processes for the same repository" do + nested_gems = [ + "actioncable", + "actionmailer", + "actionpack", + "actionview", + "activejob", + "activemodel", + "activerecord", + "activestorage", + "activesupport", + "railties", + ] + + build_repo2 do + nested_gems.each do |gem| + build_lib gem, path: lib_path("rails/#{gem}") + end + + build_git "rails", path: lib_path("rails") do |s| + nested_gems.each do |gem| + s.add_dependency gem + end + end + end + + install_gemfile <<-G + source 'https://rubygems.org' + + git "#{lib_path("rails")}" do + gem "rails" + gem "actioncable" + gem "actionmailer" + gem "actionpack" + gem "actionview" + gem "activejob" + gem "activemodel" + gem "activerecord" + gem "activestorage" + gem "activesupport" + gem "railties" + end + G + + changed_files = [] + diff = "#Pristine spec changes" + + nested_gems.each do |gem| + spec = find_spec(gem) + changed_files << Pathname.new(spec.full_gem_path).join("lib/#{gem}.rb") + File.open(changed_files.last, "a") {|f| f.puts diff } + end + + bundle "pristine" + + changed_files.each do |changed_file| + expect(File.read(changed_file)).to_not include(diff) + end + end end context "when sourced from gemspec" do From ff1b8ffa61922a46eae217c06aa16b24f26ae723 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 15 Dec 2025 11:45:23 +0100 Subject: [PATCH 1925/2435] [ruby/rubygems] Tweak the Bundler's "X gems now installed message": - Fix https://github.com/ruby/rubygems/pull/9188 - This message is a bit misleading because it always outputs one extra specs, which is Bundler itself. This is now fixed when the message is about to be output. https://github.com/ruby/rubygems/commit/70b4e19506 --- lib/bundler/cli/install.rb | 2 +- spec/bundler/commands/post_bundle_message_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index ba1cef8434e63c..67feba84bdb06e 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -90,7 +90,7 @@ def dependencies_count_for(definition) end def gems_installed_for(definition) - count = definition.specs.count + count = definition.specs.count {|spec| spec.name != "bundler" } "#{count} #{count == 1 ? "gem" : "gems"} now installed" end diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 257443526089ad..9eecff593f7e33 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -18,7 +18,7 @@ let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" } let(:bundle_complete_message) { "Bundle complete!" } let(:bundle_updated_message) { "Bundle updated!" } - let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." } + let(:installed_gems_stats) { "4 Gemfile dependencies, 4 gems now installed." } describe "when installing to system gems" do before do @@ -44,14 +44,14 @@ expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") bundle "config set --local without emo obama test" bundle :install expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 1 gem now installed.") end describe "for second bundle install run" do From b6d4562e6ae9cbf9864c3f35c361db8087e595e7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 14 Dec 2025 17:34:34 -0500 Subject: [PATCH 1926/2435] [DOC] Remove copyright from Set class docs --- set.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/set.c b/set.c index 0c657cf66d8118..6d200b5dfa1495 100644 --- a/set.c +++ b/set.c @@ -1988,13 +1988,6 @@ rb_set_size(VALUE set) /* * Document-class: Set * - * Copyright (c) 2002-2024 Akinori MUSHA - * - * Documentation by Akinori MUSHA and Gavin Sinclair. - * - * All rights reserved. You can redistribute and/or modify it under the same - * terms as Ruby. - * * The Set class implements a collection of unordered values with no * duplicates. It is a hybrid of Array's intuitive inter-operation * facilities and Hash's fast lookup. From 38d67986b044ec32681aabc3d9017c5bdb4e7289 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:36:24 +0000 Subject: [PATCH 1927/2435] Bump actions/cache in /.github/actions/setup/directories Bumps [actions/cache](https://github.com/actions/cache) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/a7833574556fa59680c1b7cb190c1735db73ebf0...9255dc7a253b0ccc959486e2bca901246202afeb) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 21cc817cba81cb..1f0463c9ca021f 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -100,7 +100,7 @@ runs: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 + - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache From 060199910afeccd4c81f90beebbd406799c05c46 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Dec 2025 11:09:34 +0900 Subject: [PATCH 1928/2435] [ruby/rubygems] Allow to show cli_help with bundler executable https://github.com/ruby/rubygems/commit/a091e3fd10 --- lib/bundler/cli.rb | 2 +- spec/bundler/bundler/cli_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 17d8c42e6ecdc5..1f6a65ca57aedd 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -104,7 +104,7 @@ def cli_help primary_commands = ["install", "update", "cache", "exec", "config", "help"] list = self.class.printable_commands(true) - by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] } + by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] } utilities = by_name.keys.sort - primary_commands primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } utilities.map! {|name| by_name[name].first } diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 7bc8fd3d36adaf..e2c64b93940157 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -282,4 +282,15 @@ def out_with_macos_man_workaround bundler "--version" expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end + + it "shows cli_help when bundler install and no Gemfile is found" do + bundler "install", raise_on_error: false + expect(err).to include("Could not locate Gemfile") + + expect(out).to include("Bundler version #{Bundler::VERSION}"). + and include("\n\nBundler commands:\n\n"). + and include("\n\n Primary commands:\n"). + and include("\n\n Utilities:\n"). + and include("\n\nOptions:\n") + end end From 3b3ab338516c9af009300761382f01a6beff4dd0 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Dec 2025 22:18:44 -0500 Subject: [PATCH 1929/2435] ZJIT: Fix test failures from line number of `Primitive` shifting This can happen with documentation updates and we don't want those to trip on ZJIT tests. Redact the whole name since names like "_bi342" aren't that helpful anyways. --- zjit/src/hir.rs | 4 +++- zjit/src/hir/opt_tests.rs | 4 ++-- zjit/src/hir/tests.rs | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index dfb8bbaecb55b4..2f8f21225524d5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1297,9 +1297,11 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) } Insn::InvokeBuiltin { bf, args, leaf, .. } => { + let bf_name = unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap(); write!(f, "InvokeBuiltin{} {}", if *leaf { " leaf" } else { "" }, - unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?; + // e.g. Code that use `Primitive.cexpr!`. From BUILTIN_INLINE_PREFIX. + if bf_name.starts_with("_bi") { "" } else { bf_name })?; for arg in args { write!(f, ", {arg}")?; } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e3c74c86364007..166d1d4754591e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2588,7 +2588,7 @@ mod hir_opt_tests { v13:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) IncrCounter inline_iseq_optimized_send_count - v23:BasicObject = InvokeBuiltin leaf _bi285, v13 + v23:BasicObject = InvokeBuiltin leaf , v13 CheckInterrupts Return v23 "); @@ -2620,7 +2620,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) IncrCounter inline_iseq_optimized_send_count - v31:BasicObject = InvokeBuiltin leaf _bi132, v17 + v31:BasicObject = InvokeBuiltin leaf , v17 CheckInterrupts Return v31 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 49f092337e9816..f67874fad7a832 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3026,7 +3026,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v10:HeapObject = InvokeBuiltin leaf _bi20, v6 + v10:HeapObject = InvokeBuiltin leaf , v6 Jump bb3(v6, v10) bb3(v12:BasicObject, v13:HeapObject): CheckInterrupts @@ -3140,7 +3140,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v10:StringExact = InvokeBuiltin leaf _bi28, v6 + v10:StringExact = InvokeBuiltin leaf , v6 Jump bb3(v6, v10) bb3(v12:BasicObject, v13:StringExact): CheckInterrupts @@ -3162,7 +3162,7 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v10:StringExact = InvokeBuiltin leaf _bi12, v6 + v10:StringExact = InvokeBuiltin leaf , v6 Jump bb3(v6, v10) bb3(v12:BasicObject, v13:StringExact): CheckInterrupts From 3b50f4ba41ececd01dcf2e35c4071495f250d609 Mon Sep 17 00:00:00 2001 From: hituzi no sippo Date: Thu, 11 Dec 2025 19:16:17 +0900 Subject: [PATCH 1930/2435] [ruby/rubygems] Support single quotes in mise format ruby version https://github.com/ruby/rubygems/commit/a7d7ab39dd --- lib/bundler/ruby_dsl.rb | 8 ++++---- spec/bundler/bundler/ruby_dsl_spec.rb | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index db4d5521e54925..228904850a9295 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -42,16 +42,16 @@ def ruby(*ruby_version) # Loads the file relative to the dirname of the Gemfile itself. def normalize_ruby_file(filename) file_content = Bundler.read_file(gemfile.dirname.join(filename)) - # match "ruby-3.2.2", ruby = "3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment + # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment if /^ # Start of line ruby # Literal "ruby" [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) - "? # Optional opening quote + ["']? # Optional opening quote ( # Start capturing group - [^\s#"]+ # One or more chars that aren't spaces, #, or quotes + [^\s#"']+ # One or more chars that aren't spaces, #, or quotes ) # End capturing group - "? # Optional closing quote + ["']? # Optional closing quote /x.match(file_content) $1 else diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb index 0d02542fb595b3..4ef02966953dc2 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -178,11 +178,21 @@ class MockDSL let(:file_content) do <<~TOML [tools] - ruby = "#{version}" + ruby = #{quote}#{version}#{quote} TOML end - it_behaves_like "it stores the ruby version" + context "with double quotes" do + let(:quote) { '"' } + + it_behaves_like "it stores the ruby version" + end + + context "with single quotes" do + let(:quote) { "'" } + + it_behaves_like "it stores the ruby version" + end end context "with a .tool-versions file format" do From f3b9509b52bbf845d95bf799d76dad41783919e5 Mon Sep 17 00:00:00 2001 From: hituzi no sippo Date: Sat, 13 Dec 2025 15:53:10 +0900 Subject: [PATCH 1931/2435] [ruby/rubygems] Fix quote handling in mise format ruby version parsing The previous regex didn't properly match quoted strings it would capture the opening quote as part of the version if quotes were mismatched. This change properly parses double-quoted, single-quoted, and unquoted version strings separately. https://github.com/ruby/rubygems/commit/81e48c8185 --- lib/bundler/ruby_dsl.rb | 25 ++++++++++++++----------- spec/bundler/bundler/ruby_dsl_spec.rb | 13 +++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index 228904850a9295..5e52f38c8fd719 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -43,17 +43,20 @@ def ruby(*ruby_version) def normalize_ruby_file(filename) file_content = Bundler.read_file(gemfile.dirname.join(filename)) # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment - if /^ # Start of line - ruby # Literal "ruby" - [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) - (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) - ["']? # Optional opening quote - ( # Start capturing group - [^\s#"']+ # One or more chars that aren't spaces, #, or quotes - ) # End capturing group - ["']? # Optional closing quote - /x.match(file_content) - $1 + version_match = /^ # Start of line + ruby # Literal "ruby" + [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) + (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) + (?: + "([^"]+)" # Double quoted version + | + '([^']+)' # Single quoted version + | + ([^\s#"']+) # Unquoted version + ) + /x.match(file_content) + if version_match + version_match[1] || version_match[2] || version_match[3] else file_content.strip end diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb index 4ef02966953dc2..45a37c5795c873 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -193,6 +193,19 @@ class MockDSL it_behaves_like "it stores the ruby version" end + + context "with mismatched quotes" do + let(:file_content) do + <<~TOML + [tools] + ruby = "#{version}' + TOML + end + + it "raises an error" do + expect { subject }.to raise_error(Bundler::InvalidArgumentError, "= is not a valid requirement on the Ruby version") + end + end end context "with a .tool-versions file format" do From 9168cad4d63a5d281d443bde4edea6be213b0b25 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Thu, 11 Dec 2025 15:56:16 -0700 Subject: [PATCH 1932/2435] YJIT: Bail out if proc would be stored above stack top Fixes [Bug #21266]. --- bootstraptest/test_yjit.rb | 10 ++++++++++ test/ruby/test_yjit.rb | 12 ++++++++++++ yjit/src/codegen.rs | 5 +++++ yjit/src/stats.rs | 1 + 4 files changed, 28 insertions(+) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index cf1605cf407ac3..be66395190b273 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -4889,6 +4889,16 @@ def tests tests } +# regression test for splat with &proc{} when the target has rest (Bug #21266) +assert_equal '[]', %q{ + def foo(args) = bar(*args, &proc { _1 }) + def bar(_, _, _, _, *rest) = yield rest + + GC.stress = true + foo([1,2,3,4]) + foo([1,2,3,4]) +} + # regression test for invalidating an empty block assert_equal '0', %q{ def foo = (* = 1).pred diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index ce9dfca3900e0e..2096585451c324 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -1772,6 +1772,18 @@ def req2kws = yield a: 1, b: 2 RUBY end + def test_proc_block_with_kwrest + # When the bug was present this required --yjit-stats to trigger. + assert_compiles(<<~RUBY, result: {extra: 5}) + def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 }) + def bar(w:, x:, y:, z:, **kwrest) = yield kwrest + + GC.stress = true + foo + foo + RUBY + end + private def code_gc_helpers diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 35d1713ed83ae9..620bdb82800a78 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7896,6 +7896,11 @@ fn gen_send_iseq( gen_counter_incr(jit, asm, Counter::send_iseq_clobbering_block_arg); return None; } + if iseq_has_rest || has_kwrest { + // The proc would be stored above the current stack top, where GC can't see it + gen_counter_incr(jit, asm, Counter::send_iseq_block_arg_gc_unsafe); + return None; + } let proc = asm.stack_pop(1); // Pop first, as argc doesn't account for the block arg let callee_specval = asm.ctx.sp_opnd(callee_specval); asm.store(callee_specval, proc); diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 4f23d97bce7360..84549fa5d34963 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -354,6 +354,7 @@ make_counters! { send_iseq_arity_error, send_iseq_block_arg_type, send_iseq_clobbering_block_arg, + send_iseq_block_arg_gc_unsafe, send_iseq_complex_discard_extras, send_iseq_leaf_builtin_block_arg_block_param, send_iseq_kw_splat_non_nil, From 9f593156b6184718a65f99905e6b07002058ebe6 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Mon, 8 Dec 2025 22:02:03 +0100 Subject: [PATCH 1933/2435] [ruby/rubygems] Pass down value of `BUNDLE_JOBS` to RubyGems before compiling: - ### Problem Since https://github.com/ruby/rubygems/pull/9131, we are now compiling make rules simultaneously. The number of jobs is equal to the number of processors. This may be problematic for some users as they want to control this value. ### Solution The number of jobs passed to `make` will now be equal to the `BUNDLE_JOBS` value. ### Side note It's also worth to note that since Bundler installs gems in parallel, we may end up running multiple `make -j` in parallel which would cause exhaust the number of processors we have. This problem can be fixed by implementing a GNU jobserver, which I plan to do. But I felt that this would be too much change in one PR. https://github.com/ruby/rubygems/commit/d51995deb9 --- lib/bundler/rubygems_gem_installer.rb | 4 ++ lib/rubygems/ext/builder.rb | 19 +++++-- lib/rubygems/ext/cargo_builder.rb | 2 +- lib/rubygems/ext/cmake_builder.rb | 2 +- lib/rubygems/ext/configure_builder.rb | 4 +- lib/rubygems/ext/ext_conf_builder.rb | 7 +-- lib/rubygems/ext/rake_builder.rb | 2 +- lib/rubygems/installer.rb | 7 ++- spec/bundler/commands/install_spec.rb | 73 +++++++++++++++++++++++++++ 9 files changed, 105 insertions(+), 15 deletions(-) diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 0da5ed236b2bdf..64ce6193d3d1f5 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -103,6 +103,10 @@ def generate_bin_script(filename, bindir) end end + def build_jobs + Bundler.settings[:jobs] || super + end + def build_extensions extension_cache_path = options[:bundler_extension_cache_path] extension_dir = spec.extension_dir diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 600a6a5ff675ac..350daf1e16d7b7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -22,7 +22,7 @@ def self.class_name end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"], - target_rbconfig: Gem.target_rbconfig) + target_rbconfig: Gem.target_rbconfig, n_jobs: nil) unless File.exist? File.join(make_dir, "Makefile") # No makefile exists, nothing to do. raise NoMakefileError, "No Makefile found in #{make_dir}" @@ -34,8 +34,18 @@ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = [ make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" make_program = shellsplit(make_program_name) + is_nmake = /\bnmake/i.match?(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. - destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + + # nmake doesn't support parallel build + unless is_nmake + have_make_arguments = make_program.size > 1 + + if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs + make_program << "-j#{n_jobs}" + end + end env = [destdir] @@ -147,11 +157,12 @@ def self.shelljoin(command) # have build arguments, saved, set +build_args+ which is an ARGV-style # array. - def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig) + def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @target_rbconfig = target_rbconfig + @build_jobs = build_jobs @ran_rake = false end @@ -208,7 +219,7 @@ def build_extension(extension, dest_path) # :nodoc: FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, - results, @build_args, lib_dir, extension_dir, @target_rbconfig) + results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs) verbose { results.join("\n") } diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 6bf3b405ad7ce4..42dca3b102546c 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -15,7 +15,7 @@ def initialize end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "tempfile" require "fileutils" diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 2915568b39d0c1..e660ed558b7e8a 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -37,7 +37,7 @@ def initialize end def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb index 76c1cd8b197548..230b214b3cb504 100644 --- a/lib/rubygems/ext/configure_builder.rb +++ b/lib/rubygems/ext/configure_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring" end @@ -19,7 +19,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, configur run cmd, results, class_name, configure_dir end - make dest_path, results, configure_dir, target_rbconfig: target_rbconfig + make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs results end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 81491eac79abbc..822454355d104d 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "fileutils" require "tempfile" @@ -40,11 +40,8 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio end ENV["DESTDIR"] = nil - unless RUBY_PLATFORM.include?("mswin") && RbConfig::CONFIG["configure_args"]&.include?("nmake") - ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}" - end - make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig + make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs full_tmp_dest = File.join(extension_dir, tmp_dest_relative) diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 0eac5a180ca3ca..d702d7f3393757 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -8,7 +8,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, - target_rbconfig = Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring" end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 4c3038770d486b..90aa25dc072888 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -635,6 +635,7 @@ def process_options # :nodoc: @build_root = options[:build_root] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @gem_home = @install_dir || user_install_dir || Gem.dir @@ -803,7 +804,7 @@ def windows_stub_script(bindir, bin_file_name) # configure scripts and rakefiles or mkrf_conf files. def build_extensions - builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig + builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs builder.build_extensions end @@ -941,6 +942,10 @@ def build_args end end + def build_jobs + @build_jobs ||= Etc.nprocessors + 1 + end + def rb_config Gem.target_rbconfig end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 41b3a865cd9dfd..ae651bf981c7f8 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -1306,6 +1306,79 @@ def run end end + describe "parallel make" do + before do + unless Gem::Installer.private_method_defined?(:build_jobs) + skip "This example is runnable when RubyGems::Installer implements `build_jobs`" + end + + @old_makeflags = ENV["MAKEFLAGS"] + @gemspec = nil + + extconf_code = <<~CODE + require "mkmf" + create_makefile("foo") + CODE + + build_repo4 do + build_gem "mypsych", "4.0.6" do |s| + @gemspec = s + extension = "ext/mypsych/extconf.rb" + s.extensions = extension + + s.write(extension, extconf_code) + end + end + end + + after do + if @old_makeflags + ENV["MAKEFLAGS"] = @old_makeflags + else + ENV.delete("MAKEFLAGS") + end + end + + it "doesn't pass down -j to make when MAKEFLAGS is set" do + ENV["MAKEFLAGS"] = "-j1" + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).not_to include("make -j8") + end + + it "pass down the BUNDLE_JOBS to RubyGems when running the compilation of an extension" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j8") + end + + it "uses nprocessors by default" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j#{Etc.nprocessors + 1}") + end + end + describe "when configured path is UTF-8 and a file inside a gem package too" do let(:app_path) do path = tmp("♥") From 080bf30c48a8212cf0ee4f769d1bdaa4a2df5900 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Wed, 10 Dec 2025 14:29:32 +0100 Subject: [PATCH 1934/2435] [ruby/rubygems] Allow to specify the number of `make` jobs when installing gems: - Added a new `-j` option to `gem install` and `gem update`. This option allows to specify the number of jobs we pass to `make` when compiling gem with native extensions. By default its the number of processors, but users may want a way to control this. You can use it like so: `gem install json -j8` https://github.com/ruby/rubygems/commit/67aad88ca6 --- lib/rubygems/dependency_installer.rb | 2 ++ lib/rubygems/install_update_options.rb | 9 ++++++ .../test_gem_commands_install_command.rb | 28 +++++++++++++++++++ .../test_gem_commands_update_command.rb | 28 +++++++++++++++++++ .../test_gem_install_update_options.rb | 12 ++++++++ 5 files changed, 79 insertions(+) diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index a6cfc3c07af76d..6a6dfa5c2031ba 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -83,6 +83,7 @@ def initialize(options = {}) @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @build_docs_in_background = options[:build_docs_in_background] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @@ -154,6 +155,7 @@ def install(dep_or_name, version = Gem::Requirement.default) options = { bin_dir: @bin_dir, build_args: @build_args, + build_jobs: @build_jobs, document: @document, env_shebang: @env_shebang, force: @force, diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 2d80e997879715..66cb5c049bcc03 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -31,6 +31,15 @@ def add_install_update_options options[:bin_dir] = File.expand_path(value) end + add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer, + "Specify the number of jobs to pass to `make` when installing", + "gems with native extensions.", + "Defaults to the number of processors.", + "This option is ignored on the mswin platform or", + "if the MAKEFLAGS environment variable is set.") do |value, options| + options[:build_jobs] = value + end + add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 92933bfb77d06d..4fb7a04fb1c6d3 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1584,6 +1584,34 @@ def test_suggest_update_if_enabled end end + def test_pass_down_the_job_option_to_make + gemspec = nil + + spec_fetcher do |fetcher| + fetcher.gem "a", 2 do |spec| + gemspec = spec + + extconf_path = "#{spec.gem_dir}/extconf.rb" + + write_file(extconf_path) do |io| + io.puts "require 'mkmf'" + io.puts "create_makefile '#{spec.name}'" + end + + spec.extensions = "extconf.rb" + end + end + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.invoke "a", "-j4" + end + end + + gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) + assert_includes(gem_make_out, "make -j4") + end + def test_execute_bindir_with_nonexistent_parent_dirs spec_fetcher do |fetcher| fetcher.gem "a", 2 do |s| diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 3b106e4581004f..9051640c0b1e9c 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -696,6 +696,34 @@ def test_fetch_remote_gems_prerelease assert_equal expected, @cmd.fetch_remote_gems(specs["a-1"]) end + def test_pass_down_the_job_option_to_make + gemspec = nil + + spec_fetcher do |fetcher| + fetcher.download "a", 3 do |spec| + gemspec = spec + + extconf_path = "#{spec.gem_dir}/extconf.rb" + + write_file(extconf_path) do |io| + io.puts "require 'mkmf'" + io.puts "create_makefile '#{spec.name}'" + end + + spec.extensions = "extconf.rb" + end + + fetcher.gem "a", 2 + end + + use_ui @ui do + @cmd.invoke("a", "-j2") + end + + gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) + assert_includes(gem_make_out, "make -j2") + end + def test_handle_options_system @cmd.handle_options %w[--system] diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index 8fd5d9c543babe..1e451dcb050f4d 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -202,4 +202,16 @@ def test_minimal_deps assert_equal true, @cmd.options[:minimal_deps] end + + def test_build_jobs_short_version + @cmd.handle_options %w[-j 4] + + assert_equal 4, @cmd.options[:build_jobs] + end + + def test_build_jobs_long_version + @cmd.handle_options %w[--build-jobs 4] + + assert_equal 4, @cmd.options[:build_jobs] + end end From e4797e93213365956a6cec142122866c8f0c9a51 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Dec 2025 13:49:16 +0900 Subject: [PATCH 1935/2435] [ruby/rubygems] Reset MAKEFLAGS option for build jobs tests https://github.com/ruby/rubygems/commit/09e6031a11 --- test/rubygems/helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 6e0be10ef53044..dc40f4ecb1f8ec 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -333,6 +333,7 @@ def setup ENV["XDG_CONFIG_HOME"] = nil ENV["XDG_DATA_HOME"] = nil ENV["XDG_STATE_HOME"] = nil + ENV["MAKEFLAGS"] = nil ENV["SOURCE_DATE_EPOCH"] = nil ENV["BUNDLER_VERSION"] = nil ENV["BUNDLE_CONFIG"] = nil From 5b0fefef417cad1733c18a0b66db07bcb1de5caf Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Dec 2025 14:50:44 +0900 Subject: [PATCH 1936/2435] [ruby/rubygems] Added assertion for Windows and nmake https://github.com/ruby/rubygems/commit/be5c4e27d9 --- test/rubygems/test_gem_commands_install_command.rb | 6 +++++- test/rubygems/test_gem_commands_update_command.rb | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 4fb7a04fb1c6d3..72ca9d8262583a 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1609,7 +1609,11 @@ def test_pass_down_the_job_option_to_make end gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) - assert_includes(gem_make_out, "make -j4") + if vc_windows? && nmake_found? + refute_includes(gem_make_out, "-j4") + else + assert_includes(gem_make_out, "make -j4") + end end def test_execute_bindir_with_nonexistent_parent_dirs diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 9051640c0b1e9c..3bb4a72c4151ad 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -721,7 +721,11 @@ def test_pass_down_the_job_option_to_make end gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) - assert_includes(gem_make_out, "make -j2") + if vc_windows? && nmake_found? + refute_includes(gem_make_out, "-j2") + else + assert_includes(gem_make_out, "make -j2") + end end def test_handle_options_system From 065c48cdf11a1c4cece84db44ed8624d294f8fd5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Dec 2025 12:51:45 +0900 Subject: [PATCH 1937/2435] Revert "[Feature #6012] Extend `source_location` for end position and columns" This reverts commit 073c4e1cc712064e626914fa4a5a8061f903a637. https://bugs.ruby-lang.org/issues/6012#note-31 > we will cancel this feature in 4.0 because of design ambiguities > such as whether to return column positions in bytes or characters as > in [#21783]. [#21783]: https://bugs.ruby-lang.org/issues/21783 --- NEWS.md | 10 ---- proc.c | 16 ++---- spec/ruby/core/method/source_location_spec.rb | 16 ++---- spec/ruby/core/proc/source_location_spec.rb | 51 +++++++------------ .../unboundmethod/source_location_spec.rb | 18 +++---- test/ruby/test_lambda.rb | 12 ++--- test/ruby/test_proc.rb | 18 +++---- 7 files changed, 48 insertions(+), 93 deletions(-) diff --git a/NEWS.md b/NEWS.md index dbf44b63642720..4b16fd50189d0b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -116,15 +116,6 @@ Note: We're only listing outstanding class updates. * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] -* Method - - * `Method#source_location`, `Proc#source_location`, and - `UnboundMethod#source_location` now return extended location - information with 5 elements: `[path, start_line, start_column, - end_line, end_column]`. The previous 2-element format `[path, - line]` can still be obtained by calling `.take(2)` on the result. - [[Feature #6012]] - * Proc * `Proc#parameters` now shows anonymous optional parameters as `[:opt]` @@ -443,7 +434,6 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * `--rjit` is removed. We will move the implementation of the third-party JIT API to the [ruby/rjit](https://github.com/ruby/rjit) repository. -[Feature #6012]: https://bugs.ruby-lang.org/issues/6012 [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 diff --git a/proc.c b/proc.c index 9cd4c5b0c9f488..cdc453f50092d2 100644 --- a/proc.c +++ b/proc.c @@ -1511,20 +1511,14 @@ proc_eq(VALUE self, VALUE other) static VALUE iseq_location(const rb_iseq_t *iseq) { - VALUE loc[5]; - int i = 0; + VALUE loc[2]; if (!iseq) return Qnil; rb_iseq_check(iseq); - loc[i++] = rb_iseq_path(iseq); - const rb_code_location_t *cl = &ISEQ_BODY(iseq)->location.code_location; - loc[i++] = RB_INT2NUM(cl->beg_pos.lineno); - loc[i++] = RB_INT2NUM(cl->beg_pos.column); - loc[i++] = RB_INT2NUM(cl->end_pos.lineno); - loc[i++] = RB_INT2NUM(cl->end_pos.column); - RUBY_ASSERT_ALWAYS(i == numberof(loc)); - - return rb_ary_new_from_values(i, loc); + loc[0] = rb_iseq_path(iseq); + loc[1] = RB_INT2NUM(ISEQ_BODY(iseq)->location.first_lineno); + + return rb_ary_new4(2, loc); } VALUE diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index 1b175ebabac044..c5b296f6e2a7b0 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -11,23 +11,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location[0] + file = @method.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location[1] + line = @method.source_location.last line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - MethodSpecs::SourceLocation.method(:redefined).source_location[1].should == 13 + MethodSpecs::SourceLocation.method(:redefined).source_location.last.should == 13 end it "returns the location of the original method even if it was aliased" do - MethodSpecs::SourceLocation.new.method(:aka).source_location[1].should == 17 + MethodSpecs::SourceLocation.new.method(:aka).source_location.last.should == 17 end it "works for methods defined with a block" do @@ -108,13 +108,7 @@ def f c = Class.new do eval('def self.m; end', nil, "foo", 100) end - location = c.method(:m).source_location - ruby_version_is(""..."4.0") do - location.should == ["foo", 100] - end - ruby_version_is("4.0") do - location.should == ["foo", 100, 0, 100, 15] - end + c.method(:m).source_location.should == ["foo", 100] end describe "for a Method generated by respond_to_missing?" do diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 69b4e2fd8273b6..a8b99287d5c380 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -17,64 +17,57 @@ end it "sets the first value to the path of the file in which the proc was defined" do - file = @proc.source_location[0] + file = @proc.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @proc_new.source_location[0] + file = @proc_new.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @lambda.source_location[0] + file = @lambda.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @method.source_location[0] + file = @method.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) end - it "sets the second value to an Integer representing the line on which the proc was defined" do - line = @proc.source_location[1] + it "sets the last value to an Integer representing the line on which the proc was defined" do + line = @proc.source_location.last line.should be_an_instance_of(Integer) line.should == 4 - line = @proc_new.source_location[1] + line = @proc_new.source_location.last line.should be_an_instance_of(Integer) line.should == 12 - line = @lambda.source_location[1] + line = @lambda.source_location.last line.should be_an_instance_of(Integer) line.should == 8 - line = @method.source_location[1] + line = @method.source_location.last line.should be_an_instance_of(Integer) line.should == 15 end it "works even if the proc was created on the same line" do - ruby_version_is(""..."4.0") do - proc { true }.source_location.should == [__FILE__, __LINE__] - Proc.new { true }.source_location.should == [__FILE__, __LINE__] - -> { true }.source_location.should == [__FILE__, __LINE__] - end - ruby_version_is("4.0") do - proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] - Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] - -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] - end + proc { true }.source_location.should == [__FILE__, __LINE__] + Proc.new { true }.source_location.should == [__FILE__, __LINE__] + -> { true }.source_location.should == [__FILE__, __LINE__] end it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do - ProcSpecs::SourceLocation.my_multiline_proc.source_location[1].should == 20 - ProcSpecs::SourceLocation.my_multiline_proc_new.source_location[1].should == 34 - ProcSpecs::SourceLocation.my_multiline_lambda.source_location[1].should == 27 + ProcSpecs::SourceLocation.my_multiline_proc.source_location.last.should == 20 + ProcSpecs::SourceLocation.my_multiline_proc_new.source_location.last.should == 34 + ProcSpecs::SourceLocation.my_multiline_lambda.source_location.last.should == 27 end it "returns the location of the proc's body; not necessarily the proc itself" do - ProcSpecs::SourceLocation.my_detached_proc.source_location[1].should == 41 - ProcSpecs::SourceLocation.my_detached_proc_new.source_location[1].should == 51 - ProcSpecs::SourceLocation.my_detached_lambda.source_location[1].should == 46 + ProcSpecs::SourceLocation.my_detached_proc.source_location.last.should == 41 + ProcSpecs::SourceLocation.my_detached_proc_new.source_location.last.should == 51 + ProcSpecs::SourceLocation.my_detached_lambda.source_location.last.should == 46 end it "returns the same value for a proc-ified method as the method reports" do @@ -93,12 +86,6 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) - location = proc.source_location - ruby_version_is(""..."4.0") do - location.should == ["foo", 100] - end - ruby_version_is("4.0") do - location.should == ["foo", 100, 2, 100, 5] - end + proc.source_location.should == ["foo", 100] end end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 85078ff34e8cd5..5c2f14362c40b4 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -7,23 +7,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location[0] + file = @method.source_location.first file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end - it "sets the second value to an Integer representing the line on which the method was defined" do - line = @method.source_location[1] + it "sets the last value to an Integer representing the line on which the method was defined" do + line = @method.source_location.last line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location[1].should == 13 + UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13 end it "returns the location of the original method even if it was aliased" do - UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location[1].should == 17 + UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17 end it "works for define_method methods" do @@ -54,12 +54,6 @@ c = Class.new do eval('def m; end', nil, "foo", 100) end - location = c.instance_method(:m).source_location - ruby_version_is(""..."4.0") do - location.should == ["foo", 100] - end - ruby_version_is("4.0") do - location.should == ["foo", 100, 0, 100, 10] - end + c.instance_method(:m).source_location.should == ["foo", 100] end end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 3cbb54306c8afa..7738034240497d 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -276,27 +276,27 @@ def test_break end def test_do_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 7] + exp_lineno = __LINE__ + 3 lmd = ->(x, y, z) do # end - file, *loc = lmd.source_location + file, lineno = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp, loc) + assert_equal(exp_lineno, lineno, "must be at the beginning of the block") end def test_brace_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 5] + exp_lineno = __LINE__ + 3 lmd = ->(x, y, z) { # } - file, *loc = lmd.source_location + file, lineno = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp, loc) + assert_equal(exp_lineno, lineno, "must be at the beginning of the block") end def test_not_orphan_return diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 92cdfc6757da22..b50875e7b0aa24 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -513,7 +513,7 @@ def test_binding_source_location file, lineno = method(:source_location_test).to_proc.binding.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test[0], lineno, 'Bug #2427') + assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') end def test_binding_error_unless_ruby_frame @@ -1499,19 +1499,15 @@ def test_to_s assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name) end - @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5] + @@line_of_source_location_test = __LINE__ + 1 def source_location_test a=1, b=2 end def test_source_location - file, *loc = method(:source_location_test).source_location + file, lineno = method(:source_location_test).source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') - - file, *loc = self.class.instance_method(:source_location_test).source_location - assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') + assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') end @@line_of_attr_reader_source_location_test = __LINE__ + 3 @@ -1544,13 +1540,13 @@ def block_source_location_test(*args, &block) end def test_block_source_location - exp_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49] - file, *loc = block_source_location_test(1, + exp_lineno = __LINE__ + 3 + file, lineno = block_source_location_test(1, 2, 3) do end assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_loc, loc) + assert_equal(exp_lineno, lineno) end def test_splat_without_respond_to From f4c48505ca50d021c3505fe422c04e4f41624bb2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Dec 2025 21:11:33 +0900 Subject: [PATCH 1938/2435] Box: move extensions from namespace to box --- ext/-test-/box/yay1/extconf.rb | 2 +- ext/-test-/box/yay2/extconf.rb | 2 +- test/-ext-/box/test_load_ext.rb | 32 ++++++++++++++++---------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ext/-test-/box/yay1/extconf.rb b/ext/-test-/box/yay1/extconf.rb index 539e99ab091b34..54387cedf13d43 100644 --- a/ext/-test-/box/yay1/extconf.rb +++ b/ext/-test-/box/yay1/extconf.rb @@ -1 +1 @@ -create_makefile('-test-/namespace/yay1') +create_makefile('-test-/box/yay1') diff --git a/ext/-test-/box/yay2/extconf.rb b/ext/-test-/box/yay2/extconf.rb index 2027a42860ba6a..850ef3edc942a7 100644 --- a/ext/-test-/box/yay2/extconf.rb +++ b/ext/-test-/box/yay2/extconf.rb @@ -1 +1 @@ -create_makefile('-test-/namespace/yay2') +create_makefile('-test-/box/yay2') diff --git a/test/-ext-/box/test_load_ext.rb b/test/-ext-/box/test_load_ext.rb index da7fe22a74f72c..cc69e07e0b757e 100644 --- a/test/-ext-/box/test_load_ext.rb +++ b/test/-ext-/box/test_load_ext.rb @@ -8,7 +8,7 @@ def test_load_extension pend assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - require '-test-/namespace/yay1' + require '-test-/box/yay1' assert_equal "1.0.0", Yay.version assert_equal "yay", Yay.yay end; @@ -18,24 +18,24 @@ def test_extension_contamination_in_global pend assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - require '-test-/namespace/yay1' + require '-test-/box/yay1' yay1 = Yay assert_equal "1.0.0", Yay.version assert_equal "yay", Yay.yay - require '-test-/namespace/yay2' + require '-test-/box/yay2' assert_equal "2.0.0", Yay.version v = Yay.yay assert(v == "yay" || v == "yaaay") # "yay" on Linux, "yaaay" on macOS, Win32 end; end - def test_load_extension_in_namespace + def test_load_extension_in_box pend assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - ns = Namespace.new - ns.require '-test-/namespace/yay1' + ns = Ruby::Box.new + ns.require '-test-/box/yay1' assert_equal "1.0.0", ns::Yay.version assert_raise(NameError) { Yay } end; @@ -45,10 +45,10 @@ def test_different_version_extensions pend assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - ns1 = Namespace.new - ns2 = Namespace.new - ns1.require('-test-/namespace/yay1') - ns2.require('-test-/namespace/yay2') + ns1 = Ruby::Box.new + ns2 = Ruby::Box.new + ns1.require('-test-/box/yay1') + ns2.require('-test-/box/yay2') assert_raise(NameError) { Yay } assert_not_nil ns1::Yay @@ -64,12 +64,12 @@ def test_loading_extensions_from_global_to_local pend assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - require '-test-/namespace/yay1' + require '-test-/box/yay1' assert_equal "1.0.0", Yay.version assert_equal "yay", Yay.yay - ns = Namespace.new - ns.require '-test-/namespace/yay2' + ns = Ruby::Box.new + ns.require '-test-/box/yay2' assert_equal "2.0.0", ns::Yay.version assert_equal "yaaay", ns::Yay.yay @@ -81,13 +81,13 @@ def test_loading_extensions_from_local_to_global pend assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - ns = Namespace.new - ns.require '-test-/namespace/yay1' + ns = Ruby::Box.new + ns.require '-test-/box/yay1' assert_equal "1.0.0", ns::Yay.version assert_equal "yay", ns::Yay.yay - require '-test-/namespace/yay2' + require '-test-/box/yay2' assert_equal "2.0.0", Yay.version assert_equal "yaaay", Yay.yay From 7780d3b6c303d00bc39d913dc91ff9f5f61ae9ed Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Dec 2025 21:12:07 +0900 Subject: [PATCH 1939/2435] Box: fix the environment variable name --- test/-ext-/box/test_load_ext.rb | 10 +++++----- test/ruby/test_box.rb | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/-ext-/box/test_load_ext.rb b/test/-ext-/box/test_load_ext.rb index cc69e07e0b757e..ea3744375ea38f 100644 --- a/test/-ext-/box/test_load_ext.rb +++ b/test/-ext-/box/test_load_ext.rb @@ -2,7 +2,7 @@ require 'test/unit' class Test_Load_Extensions < Test::Unit::TestCase - ENV_ENABLE_NAMESPACE = {'RUBY_NAMESPACE' => '1'} + ENV_ENABLE_BOX = {'RUBY_BOX' => '1'} def test_load_extension pend @@ -32,7 +32,7 @@ def test_extension_contamination_in_global def test_load_extension_in_box pend - assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}") begin; ns = Ruby::Box.new ns.require '-test-/box/yay1' @@ -43,7 +43,7 @@ def test_load_extension_in_box def test_different_version_extensions pend - assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}") begin; ns1 = Ruby::Box.new ns2 = Ruby::Box.new @@ -62,7 +62,7 @@ def test_different_version_extensions def test_loading_extensions_from_global_to_local pend - assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}") begin; require '-test-/box/yay1' assert_equal "1.0.0", Yay.version @@ -79,7 +79,7 @@ def test_loading_extensions_from_global_to_local def test_loading_extensions_from_local_to_global pend - assert_separately([ENV_ENABLE_NAMESPACE], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}") begin; ns = Ruby::Box.new ns.require '-test-/box/yay1' diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 5d06b60cd77005..4f631254817d28 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -190,7 +190,7 @@ def test_proc_defined_in_box_refers_module_in_box pend unless Ruby::Box.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; ns1 = Namespace.new ns1.require(File.join("#{here}", 'namespace/proc_callee')) @@ -212,7 +212,7 @@ def test_proc_defined_globally_refers_global_module pend unless Ruby::Box.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; require(File.join("#{here}", 'namespace/proc_callee')) def Target.foo From 8db3642ab54f303b32a02bbfe53a2ab54725b046 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Dec 2025 21:13:11 +0900 Subject: [PATCH 1940/2435] Box: fix the class name in inspect --- box.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/box.c b/box.c index 150a75700c9313..77a89de317d298 100644 --- a/box.c +++ b/box.c @@ -929,11 +929,11 @@ rb_box_inspect(VALUE obj) rb_box_t *box; VALUE r; if (obj == Qfalse) { - r = rb_str_new_cstr("#"); + r = rb_str_new_cstr("#"); return r; } box = rb_get_box_t(obj); - r = rb_str_new_cstr("#box_id), rb_intern("to_s"), 0)); if (BOX_ROOT_P(box)) { rb_str_cat_cstr(r, ",root"); From 5f09e1f046599c5466787449f5d30330d4dd4aab Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Dec 2025 21:28:17 +0900 Subject: [PATCH 1941/2435] Box: [DOC] fix the class name in rdoc Also remove a stale TODO. --- box.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/box.c b/box.c index 77a89de317d298..14f6acdd8267b5 100644 --- a/box.c +++ b/box.c @@ -348,7 +348,7 @@ rb_get_box_object(rb_box_t *box) /* * call-seq: - * Namespace.new -> new_box + * Ruby::Box.new -> new_box * * Returns a new Ruby::Box object. */ @@ -389,7 +389,7 @@ box_initialize(VALUE box_value) /* * call-seq: - * Namespace.enabled? -> true or false + * Ruby::Box.enabled? -> true or false * * Returns +true+ if Ruby::Box is enabled. */ @@ -401,7 +401,7 @@ rb_box_s_getenabled(VALUE recv) /* * call-seq: - * Namespace.current -> box, nil or false + * Ruby::Box.current -> box, nil or false * * Returns the current box. * Returns +nil+ if Ruby Box is not enabled. @@ -795,10 +795,6 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path, VALUE *cleanup) return new_path; } -// TODO: delete it just after dln_load? or delay it? -// At least for _WIN32, deleting extension files should be delayed until the namespace's destructor. -// And it requires calling dlclose before deleting it. - static VALUE rb_box_load(int argc, VALUE *argv, VALUE box) { From 85b40c5ea8f606cf34cab8a5b1277033bede2457 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Dec 2025 09:01:14 +0900 Subject: [PATCH 1942/2435] Box: fix the class name in tests --- spec/ruby/core/module/ancestors_spec.rb | 6 +- test/ruby/box/a.1_1_0.rb | 4 +- test/ruby/box/a.1_2_0.rb | 4 +- test/ruby/box/a.rb | 4 +- test/ruby/box/autoloading.rb | 6 +- test/ruby/box/box.rb | 10 + test/ruby/box/consts.rb | 1 + test/ruby/box/current.rb | 13 - test/ruby/box/global_vars.rb | 8 +- test/ruby/box/ns.rb | 10 - test/ruby/box/procs.rb | 2 +- test/ruby/test_box.rb | 578 ++++++++++++------------ 12 files changed, 321 insertions(+), 325 deletions(-) create mode 100644 test/ruby/box/box.rb delete mode 100644 test/ruby/box/current.rb delete mode 100644 test/ruby/box/ns.rb diff --git a/spec/ruby/core/module/ancestors_spec.rb b/spec/ruby/core/module/ancestors_spec.rb index 34679575b5dd95..90c26941d14a1b 100644 --- a/spec/ruby/core/module/ancestors_spec.rb +++ b/spec/ruby/core/module/ancestors_spec.rb @@ -7,11 +7,11 @@ ModuleSpecs.ancestors.should == [ModuleSpecs] ModuleSpecs::Basic.ancestors.should == [ModuleSpecs::Basic] ModuleSpecs::Super.ancestors.should == [ModuleSpecs::Super, ModuleSpecs::Basic] - if defined?(Namespace) && Namespace.enabled? + if defined?(Ruby::Box) && Ruby::Box.enabled? ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == - [ModuleSpecs::Parent, Object, Namespace::Loader, Kernel, BasicObject] + [ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == - [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Namespace::Loader, Kernel, BasicObject] + [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] else ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == [ModuleSpecs::Parent, Object, Kernel, BasicObject] diff --git a/test/ruby/box/a.1_1_0.rb b/test/ruby/box/a.1_1_0.rb index bf64dbaa62264c..032258509759be 100644 --- a/test/ruby/box/a.1_1_0.rb +++ b/test/ruby/box/a.1_1_0.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class NS_A +class BOX_A VERSION = "1.1.0" def yay @@ -8,7 +8,7 @@ def yay end end -module NS_B +module BOX_B VERSION = "1.1.0" def self.yay diff --git a/test/ruby/box/a.1_2_0.rb b/test/ruby/box/a.1_2_0.rb index 6d25c0885dc2eb..29813ea57b2a70 100644 --- a/test/ruby/box/a.1_2_0.rb +++ b/test/ruby/box/a.1_2_0.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class NS_A +class BOX_A VERSION = "1.2.0" def yay @@ -8,7 +8,7 @@ def yay end end -module NS_B +module BOX_B VERSION = "1.2.0" def self.yay diff --git a/test/ruby/box/a.rb b/test/ruby/box/a.rb index a6dcd9cd21ce25..26a622c92b5fb2 100644 --- a/test/ruby/box/a.rb +++ b/test/ruby/box/a.rb @@ -1,4 +1,4 @@ -class NS_A +class BOX_A FOO = "foo_a1" def yay @@ -6,7 +6,7 @@ def yay end end -module NS_B +module BOX_B BAR = "bar_b1" def self.yay diff --git a/test/ruby/box/autoloading.rb b/test/ruby/box/autoloading.rb index 19ec00bcd5a15d..cba57ab3776e91 100644 --- a/test/ruby/box/autoloading.rb +++ b/test/ruby/box/autoloading.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -autoload :NS_A, File.join(__dir__, 'a.1_1_0') -NS_A.new.yay +autoload :BOX_A, File.join(__dir__, 'a.1_1_0') +BOX_A.new.yay -module NS_B +module BOX_B autoload :BAR, File.join(__dir__, 'a') end diff --git a/test/ruby/box/box.rb b/test/ruby/box/box.rb new file mode 100644 index 00000000000000..3b7da14e9d7ef5 --- /dev/null +++ b/test/ruby/box/box.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +BOX1 = Ruby::Box.new +BOX1.require_relative('a.1_1_0') + +def yay + BOX1::BOX_B::yay +end + +yay diff --git a/test/ruby/box/consts.rb b/test/ruby/box/consts.rb index 44a383111b7685..e40cd5c50c7fe5 100644 --- a/test/ruby/box/consts.rb +++ b/test/ruby/box/consts.rb @@ -1,3 +1,4 @@ +$VERBOSE = nil class String STR_CONST1 = 111 STR_CONST2 = 222 diff --git a/test/ruby/box/current.rb b/test/ruby/box/current.rb deleted file mode 100644 index 4af9a92ddc5d5b..00000000000000 --- a/test/ruby/box/current.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -$ns_in_ns = ::Namespace.current - -module CurrentNamespace - def self.in_require - $ns_in_ns - end - - def self.in_method_call - ::Namespace.current - end -end diff --git a/test/ruby/box/global_vars.rb b/test/ruby/box/global_vars.rb index 3764eb0d19efc2..590363f6173fe8 100644 --- a/test/ruby/box/global_vars.rb +++ b/test/ruby/box/global_vars.rb @@ -20,18 +20,18 @@ def self.write(char) module UniqueGvar def self.read - $used_only_in_ns + $used_only_in_box end def self.write(val) - $used_only_in_ns = val + $used_only_in_box = val end def self.write_only(val) - $write_only_var_in_ns = val + $write_only_var_in_box = val end - def self.gvars_in_ns + def self.gvars_in_box global_variables end end diff --git a/test/ruby/box/ns.rb b/test/ruby/box/ns.rb deleted file mode 100644 index e947e3cdc830aa..00000000000000 --- a/test/ruby/box/ns.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -NS1 = Namespace.new -NS1.require_relative('a.1_1_0') - -def yay - NS1::NS_B::yay -end - -yay diff --git a/test/ruby/box/procs.rb b/test/ruby/box/procs.rb index a7fe58ceb6ecc6..1c39a8231bf463 100644 --- a/test/ruby/box/procs.rb +++ b/test/ruby/box/procs.rb @@ -11,7 +11,7 @@ module B end end -module ProcInNS +module ProcInBox def self.make_proc_from_block(&b) b end diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 4f631254817d28..b62ac3c544722d 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -10,16 +10,21 @@ class TestBox < Test::Unit::TestCase ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} def setup - @n = nil + @box = nil @dir = __dir__ end def teardown - @n = nil + @box = nil + end + + def setup_box + pend unless Ruby::Box.enabled? + @box = Ruby::Box.new end def test_box_availability_in_default - assert_separately([], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately(['RUBY_BOX'=>nil], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; assert_nil ENV['RUBY_BOX'] assert !Ruby::Box.enabled? @@ -43,278 +48,278 @@ def test_current_box_in_main end def test_require_rb_separately - pend unless Ruby::Box.enabled? + setup_box - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } - @n.require(File.join(__dir__, 'namespace', 'a.1_1_0')) + @box.require(File.join(__dir__, 'box', 'a.1_1_0')) - assert_not_nil @n::NS_A - assert_not_nil @n::NS_B - assert_equal "1.1.0", @n::NS_A::VERSION - assert_equal "yay 1.1.0", @n::NS_A.new.yay - assert_equal "1.1.0", @n::NS_B::VERSION - assert_equal "yay_b1", @n::NS_B.yay + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } end def test_require_relative_rb_separately - pend unless Ruby::Box.enabled? + setup_box - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } - @n.require_relative('namespace/a.1_1_0') + @box.require_relative('box/a.1_1_0') - assert_not_nil @n::NS_A - assert_not_nil @n::NS_B - assert_equal "1.1.0", @n::NS_A::VERSION - assert_equal "yay 1.1.0", @n::NS_A.new.yay - assert_equal "1.1.0", @n::NS_B::VERSION - assert_equal "yay_b1", @n::NS_B.yay + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } end def test_load_separately - pend unless Ruby::Box.enabled? + setup_box - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } - @n.load(File.join(__dir__, 'namespace', 'a.1_1_0.rb')) + @box.load(File.join(__dir__, 'box', 'a.1_1_0.rb')) - assert_not_nil @n::NS_A - assert_not_nil @n::NS_B - assert_equal "1.1.0", @n::NS_A::VERSION - assert_equal "yay 1.1.0", @n::NS_A.new.yay - assert_equal "1.1.0", @n::NS_B::VERSION - assert_equal "yay_b1", @n::NS_B.yay + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } end def test_box_in_box - pend unless Ruby::Box.enabled? + setup_box - assert_raise(NameError) { NS1 } - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX1 } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } - @n.require_relative('namespace/ns') + @box.require_relative('box/box') - assert_not_nil @n::NS1 - assert_not_nil @n::NS1::NS_A - assert_not_nil @n::NS1::NS_B - assert_equal "1.1.0", @n::NS1::NS_A::VERSION - assert_equal "yay 1.1.0", @n::NS1::NS_A.new.yay - assert_equal "1.1.0", @n::NS1::NS_B::VERSION - assert_equal "yay_b1", @n::NS1::NS_B.yay + assert_not_nil @box::BOX1 + assert_not_nil @box::BOX1::BOX_A + assert_not_nil @box::BOX1::BOX_B + assert_equal "1.1.0", @box::BOX1::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX1::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX1::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX1::BOX_B.yay - assert_raise(NameError) { NS1 } - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX1 } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } end - def test_require_rb_2versions - pend unless Ruby::Box.enabled? + def test_require_rb_2versiobox + setup_box - assert_raise(NameError) { NS_A } + assert_raise(NameError) { BOX_A } - @n.require(File.join(__dir__, 'namespace', 'a.1_2_0')) - assert_equal "1.2.0", @n::NS_A::VERSION - assert_equal "yay 1.2.0", @n::NS_A.new.yay + @box.require(File.join(__dir__, 'box', 'a.1_2_0')) + assert_equal "1.2.0", @box::BOX_A::VERSION + assert_equal "yay 1.2.0", @box::BOX_A.new.yay - n2 = Namespace.new - n2.require(File.join(__dir__, 'namespace', 'a.1_1_0')) - assert_equal "1.1.0", n2::NS_A::VERSION - assert_equal "yay 1.1.0", n2::NS_A.new.yay + n2 = Ruby::Box.new + n2.require(File.join(__dir__, 'box', 'a.1_1_0')) + assert_equal "1.1.0", n2::BOX_A::VERSION + assert_equal "yay 1.1.0", n2::BOX_A.new.yay - # recheck @n is not affected by the following require - assert_equal "1.2.0", @n::NS_A::VERSION - assert_equal "yay 1.2.0", @n::NS_A.new.yay + # recheck @box is not affected by the following require + assert_equal "1.2.0", @box::BOX_A::VERSION + assert_equal "yay 1.2.0", @box::BOX_A.new.yay - assert_raise(NameError) { NS_A } + assert_raise(NameError) { BOX_A } end def test_raising_errors_in_require - pend unless Ruby::Box.enabled? + setup_box - assert_raise(RuntimeError, "Yay!") { @n.require(File.join(__dir__, 'namespace', 'raise')) } - assert Namespace.current.inspect.include?("main") + assert_raise(RuntimeError, "Yay!") { @box.require(File.join(__dir__, 'box', 'raise')) } + assert Ruby::Box.current.inspect.include?("main") end def test_autoload_in_box - pend unless Ruby::Box.enabled? + setup_box - assert_raise(NameError) { NS_A } + assert_raise(NameError) { BOX_A } - @n.require_relative('namespace/autoloading') + @box.require_relative('box/autoloading') # autoloaded A is visible from global - assert_equal '1.1.0', @n::NS_A::VERSION + assert_equal '1.1.0', @box::BOX_A::VERSION - assert_raise(NameError) { NS_A } + assert_raise(NameError) { BOX_A } - # autoload trigger NS_B::BAR is valid even from global - assert_equal 'bar_b1', @n::NS_B::BAR + # autoload trigger BOX_B::BAR is valid even from global + assert_equal 'bar_b1', @box::BOX_B::BAR - assert_raise(NameError) { NS_A } - assert_raise(NameError) { NS_B } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } end def test_continuous_top_level_method_in_a_box - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/define_toplevel') - @n.require_relative('namespace/call_toplevel') + @box.require_relative('box/define_toplevel') + @box.require_relative('box/call_toplevel') assert_raise(NameError) { foo } end def test_top_level_methods_in_box pend # TODO: fix loading/current box detection - pend unless Ruby::Box.enabled? - @n.require_relative('box/top_level') - assert_equal "yay!", @n::Foo.foo + setup_box + @box.require_relative('box/top_level') + assert_equal "yay!", @box::Foo.foo assert_raise(NameError) { yaaay } - assert_equal "foo", @n::Bar.bar - assert_raise_with_message(RuntimeError, "boooo") { @n::Baz.baz } + assert_equal "foo", @box::Bar.bar + assert_raise_with_message(RuntimeError, "boooo") { @box::Baz.baz } end def test_proc_defined_in_box_refers_module_in_box - pend unless Ruby::Box.enabled? + setup_box # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - ns1 = Namespace.new - ns1.require(File.join("#{here}", 'namespace/proc_callee')) - proc_v = ns1::Foo.callee + box1 = Ruby::Box.new + box1.require("#{here}/box/proc_callee") + proc_v = box1::Foo.callee assert_raise(NameError) { Target } - assert ns1::Target - assert_equal "fooooo", proc_v.call # refers Target in the namespace ns1 - ns1.require(File.join("#{here}", 'namespace/proc_caller')) - assert_equal "fooooo", ns1::Bar.caller(proc_v) - - ns2 = Namespace.new - ns2.require(File.join("#{here}", 'namespace/proc_caller')) - assert_raise(NameError) { ns2::Target } - assert_equal "fooooo", ns2::Bar.caller(proc_v) # refers Target in the namespace ns1 + assert box1::Target + assert_equal "fooooo", proc_v.call # refers Target in the box box1 + box1.require("#{here}/box/proc_caller") + assert_equal "fooooo", box1::Bar.caller(proc_v) + + box2 = Ruby::Box.new + box2.require("#{here}/box/proc_caller") + assert_raise(NameError) { box2::Target } + assert_equal "fooooo", box2::Bar.caller(proc_v) # refers Target in the box box1 end; end def test_proc_defined_globally_refers_global_module - pend unless Ruby::Box.enabled? + setup_box # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - require(File.join("#{here}", 'namespace/proc_callee')) + require("#{here}/box/proc_callee") def Target.foo "yay" end proc_v = Foo.callee assert Target assert_equal "yay", proc_v.call # refers global Foo - ns1 = Namespace.new - ns1.require(File.join("#{here}", 'namespace/proc_caller')) - assert_equal "yay", ns1::Bar.caller(proc_v) - - ns2 = Namespace.new - ns2.require(File.join("#{here}", 'namespace/proc_callee')) - ns2.require(File.join("#{here}", 'namespace/proc_caller')) - assert_equal "fooooo", ns2::Foo.callee.call - assert_equal "yay", ns2::Bar.caller(proc_v) # should refer the global Target, not Foo in ns2 + box1 = Ruby::Box.new + box1.require("#{here}/box/proc_caller") + assert_equal "yay", box1::Bar.caller(proc_v) + + box2 = Ruby::Box.new + box2.require("#{here}/box/proc_callee") + box2.require("#{here}/box/proc_caller") + assert_equal "fooooo", box2::Foo.callee.call + assert_equal "yay", box2::Bar.caller(proc_v) # should refer the global Target, not Foo in box2 end; end def test_instance_variable - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/instance_variables') + @box.require_relative('box/instance_variables') assert_equal [], String.instance_variables - assert_equal [:@str_ivar1, :@str_ivar2], @n::StringDelegatorObj.instance_variables - assert_equal 111, @n::StringDelegatorObj.str_ivar1 - assert_equal 222, @n::StringDelegatorObj.str_ivar2 - assert_equal 222, @n::StringDelegatorObj.instance_variable_get(:@str_ivar2) + assert_equal [:@str_ivar1, :@str_ivar2], @box::StringDelegatorObj.instance_variables + assert_equal 111, @box::StringDelegatorObj.str_ivar1 + assert_equal 222, @box::StringDelegatorObj.str_ivar2 + assert_equal 222, @box::StringDelegatorObj.instance_variable_get(:@str_ivar2) - @n::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333) - assert_equal 333, @n::StringDelegatorObj.instance_variable_get(:@str_ivar3) - @n::StringDelegatorObj.remove_instance_variable(:@str_ivar1) - assert_nil @n::StringDelegatorObj.str_ivar1 - assert_equal [:@str_ivar2, :@str_ivar3], @n::StringDelegatorObj.instance_variables + @box::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333) + assert_equal 333, @box::StringDelegatorObj.instance_variable_get(:@str_ivar3) + @box::StringDelegatorObj.remove_instance_variable(:@str_ivar1) + assert_nil @box::StringDelegatorObj.str_ivar1 + assert_equal [:@str_ivar2, :@str_ivar3], @box::StringDelegatorObj.instance_variables assert_equal [], String.instance_variables end def test_methods_added_in_box_are_invisible_globally - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/string_ext') + @box.require_relative('box/string_ext') - assert_equal "yay", @n::Bar.yay + assert_equal "yay", @box::Bar.yay assert_raise(NoMethodError){ String.new.yay } end def test_continuous_method_definitions_in_a_box - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/string_ext') - assert_equal "yay", @n::Bar.yay + @box.require_relative('box/string_ext') + assert_equal "yay", @box::Bar.yay - @n.require_relative('namespace/string_ext_caller') - assert_equal "yay", @n::Foo.yay + @box.require_relative('box/string_ext_caller') + assert_equal "yay", @box::Foo.yay - @n.require_relative('namespace/string_ext_calling') + @box.require_relative('box/string_ext_calling') end def test_methods_added_in_box_later_than_caller_code - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/string_ext_caller') - @n.require_relative('namespace/string_ext') + @box.require_relative('box/string_ext_caller') + @box.require_relative('box/string_ext') - assert_equal "yay", @n::Bar.yay - assert_equal "yay", @n::Foo.yay + assert_equal "yay", @box::Bar.yay + assert_equal "yay", @box::Foo.yay end def test_method_added_in_box_are_available_on_eval - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/string_ext') - @n.require_relative('namespace/string_ext_eval_caller') + @box.require_relative('box/string_ext') + @box.require_relative('box/string_ext_eval_caller') - assert_equal "yay", @n::Baz.yay + assert_equal "yay", @box::Baz.yay end def test_method_added_in_box_are_available_on_eval_with_binding - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/string_ext') - @n.require_relative('namespace/string_ext_eval_caller') + @box.require_relative('box/string_ext') + @box.require_relative('box/string_ext_eval_caller') - assert_equal "yay, yay!", @n::Baz.yay_with_binding + assert_equal "yay, yay!", @box::Baz.yay_with_binding end def test_methods_and_constants_added_by_include - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/open_class_with_include') + @box.require_relative('box/open_class_with_include') - assert_equal "I'm saying foo 1", @n::OpenClassWithInclude.say - assert_equal "I'm saying foo 1", @n::OpenClassWithInclude.say_foo - assert_equal "I'm saying foo 1", @n::OpenClassWithInclude.say_with_obj("wow") + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_foo + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_with_obj("wow") assert_raise(NameError) { String::FOO } - assert_equal "foo 1", @n::OpenClassWithInclude.refer_foo + assert_equal "foo 1", @box::OpenClassWithInclude.refer_foo end end @@ -330,9 +335,9 @@ def make_proc_from_block(&b) end def test_proc_from_main_works_with_global_definitions - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/procs') + @box.require_relative('box/procs') proc_and_labels = [ [Proc.new { String.new.yay }, "Proc.new"], @@ -340,13 +345,13 @@ def test_proc_from_main_works_with_global_definitions [lambda { String.new.yay }, "lambda{}"], [->(){ String.new.yay }, "->(){}"], [make_proc_from_block { String.new.yay }, "make_proc_from_block"], - [@n::ProcInNS.make_proc_from_block { String.new.yay }, "make_proc_from_block in @n"], + [@box::ProcInBox.make_proc_from_block { String.new.yay }, "make_proc_from_block in @box"], ] proc_and_labels.each do |str_pr| pr, pr_label = str_pr assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in main") { pr.call } - assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in @n") { @n::ProcInNS.call_proc(pr) } + assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in @box") { @box::ProcInBox.call_proc(pr) } end const_and_labels = [ @@ -355,48 +360,48 @@ def test_proc_from_main_works_with_global_definitions [lambda { ProcLookupTestA::B::VALUE }, "lambda{}"], [->(){ ProcLookupTestA::B::VALUE }, "->(){}"], [make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block"], - [@n::ProcInNS.make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block in @n"], + [@box::ProcInBox.make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block in @box"], ] const_and_labels.each do |const_pr| pr, pr_label = const_pr assert_equal 111, pr.call, "111 expected, #{pr_label} called in main" - assert_equal 111, @n::ProcInNS.call_proc(pr), "111 expected, #{pr_label} called in @n" + assert_equal 111, @box::ProcInBox.call_proc(pr), "111 expected, #{pr_label} called in @box" end end def test_proc_from_box_works_with_definitions_in_box - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/procs') + @box.require_relative('box/procs') proc_types = [:proc_new, :proc_f, :lambda_f, :lambda_l, :block] proc_types.each do |proc_type| - assert_equal 222, @n::ProcInNS.make_const_proc(proc_type).call, "ProcLookupTestA::B::VALUE should be 222 in @n" - assert_equal "foo", @n::ProcInNS.make_str_const_proc(proc_type).call, "String::FOO should be \"foo\" in @n" - assert_equal "yay", @n::ProcInNS.make_str_proc(proc_type).call, "String#yay should be callable in @n" + assert_equal 222, @box::ProcInBox.make_const_proc(proc_type).call, "ProcLookupTestA::B::VALUE should be 222 in @box" + assert_equal "foo", @box::ProcInBox.make_str_const_proc(proc_type).call, "String::FOO should be \"foo\" in @box" + assert_equal "yay", @box::ProcInBox.make_str_proc(proc_type).call, "String#yay should be callable in @box" # - # TODO: method calls not-in-methods nor procs can't handle the current namespace correctly. + # TODO: method calls not-in-methods nor procs can't handle the current box correctly. # # assert_equal "yay,foo,222", - # @n::ProcInNS.const_get(('CONST_' + proc_type.to_s.upcase).to_sym).call, - # "Proc assigned to constants should refer constants correctly in @n" + # @box::ProcInBox.const_get(('CONST_' + proc_type.to_s.upcase).to_sym).call, + # "Proc assigned to constants should refer constants correctly in @box" end end def test_class_module_singleton_methods - pend unless Ruby::Box.enabled? + setup_box - @n.require_relative('namespace/singleton_methods') + @box.require_relative('box/singleton_methods') - assert_equal "Good evening!", @n::SingletonMethods.string_greeing # def self.greeting - assert_equal 42, @n::SingletonMethods.integer_answer # class << self; def answer - assert_equal([], @n::SingletonMethods.array_blank) # def self.blank w/ instance methods - assert_equal({status: 200, body: 'OK'}, @n::SingletonMethods.hash_http_200) # class << self; def ... w/ instance methods + assert_equal "Good evening!", @box::SingletonMethods.string_greeing # def self.greeting + assert_equal 42, @box::SingletonMethods.integer_answer # class << self; def answer + assert_equal([], @box::SingletonMethods.array_blank) # def self.blank w/ instance methods + assert_equal({status: 200, body: 'OK'}, @box::SingletonMethods.hash_http_200) # class << self; def ... w/ instance methods - assert_equal([4, 4], @n::SingletonMethods.array_instance_methods_return_size([1, 2, 3, 4])) - assert_equal([3, 3], @n::SingletonMethods.hash_instance_methods_return_size({a: 2, b: 4, c: 8})) + assert_equal([4, 4], @box::SingletonMethods.array_instance_methods_return_size([1, 2, 3, 4])) + assert_equal([3, 3], @box::SingletonMethods.hash_instance_methods_return_size({a: 2, b: 4, c: 8})) assert_raise(NoMethodError) { String.greeting } assert_raise(NoMethodError) { Integer.answer } @@ -405,7 +410,9 @@ def test_class_module_singleton_methods end def test_add_constants_in_box - pend unless Ruby::Box.enabled? + setup_box + + @box.require('envutil') String.const_set(:STR_CONST0, 999) assert_equal 999, String::STR_CONST0 @@ -416,37 +423,38 @@ def test_add_constants_in_box assert_raise(NameError) { String::STR_CONST3 } assert_raise(NameError) { Integer.const_get(:INT_CONST1) } - EnvUtil.suppress_warning do - @n.require_relative('namespace/consts') + EnvUtil.verbose_warning do + @box.require_relative('box/consts') end + return assert_equal 999, String::STR_CONST0 assert_raise(NameError) { String::STR_CONST1 } assert_raise(NameError) { String::STR_CONST2 } assert_raise(NameError) { Integer::INT_CONST1 } - assert_not_nil @n::ForConsts.refer_all + assert_not_nil @box::ForConsts.refer_all - assert_equal 112, @n::ForConsts.refer1 - assert_equal 112, @n::ForConsts.get1 - assert_equal 112, @n::ForConsts::CONST1 - assert_equal 222, @n::ForConsts.refer2 - assert_equal 222, @n::ForConsts.get2 - assert_equal 222, @n::ForConsts::CONST2 - assert_equal 333, @n::ForConsts.refer3 - assert_equal 333, @n::ForConsts.get3 - assert_equal 333, @n::ForConsts::CONST3 + assert_equal 112, @box::ForConsts.refer1 + assert_equal 112, @box::ForConsts.get1 + assert_equal 112, @box::ForConsts::CONST1 + assert_equal 222, @box::ForConsts.refer2 + assert_equal 222, @box::ForConsts.get2 + assert_equal 222, @box::ForConsts::CONST2 + assert_equal 333, @box::ForConsts.refer3 + assert_equal 333, @box::ForConsts.get3 + assert_equal 333, @box::ForConsts::CONST3 - EnvUtil.suppress_warning do - @n::ForConsts.const_set(:CONST3, 334) + @box::EnvUtil.suppress_warning do + @box::ForConsts.const_set(:CONST3, 334) end - assert_equal 334, @n::ForConsts::CONST3 - assert_equal 334, @n::ForConsts.refer3 - assert_equal 334, @n::ForConsts.get3 + assert_equal 334, @box::ForConsts::CONST3 + assert_equal 334, @box::ForConsts.refer3 + assert_equal 334, @box::ForConsts.get3 - assert_equal 10, @n::ForConsts.refer_top_const + assert_equal 10, @box::ForConsts.refer_top_const # use Proxy object to use usual methods instead of singleton methods - proxy = @n::ForConsts::Proxy.new + proxy = @box::ForConsts::Proxy.new assert_raise(NameError){ proxy.call_str_refer0 } assert_raise(NameError){ proxy.call_str_get0 } @@ -486,36 +494,36 @@ def test_global_variables default_l = $-0 default_f = $, - pend unless Ruby::Box.enabled? + setup_box assert_equal "\n", $-0 # equal to $/, line splitter assert_equal nil, $, # field splitter - @n.require_relative('namespace/global_vars') + @box.require_relative('box/global_vars') # read first - assert_equal "\n", @n::LineSplitter.read - @n::LineSplitter.write("\r\n") - assert_equal "\r\n", @n::LineSplitter.read + assert_equal "\n", @box::LineSplitter.read + @box::LineSplitter.write("\r\n") + assert_equal "\r\n", @box::LineSplitter.read assert_equal "\n", $-0 # write first - @n::FieldSplitter.write(",") - assert_equal ",", @n::FieldSplitter.read + @box::FieldSplitter.write(",") + assert_equal ",", @box::FieldSplitter.read assert_equal nil, $, - # used only in ns - assert !global_variables.include?(:$used_only_in_ns) - @n::UniqueGvar.write(123) - assert_equal 123, @n::UniqueGvar.read - assert_nil $used_only_in_ns + # used only in box + assert !global_variables.include?(:$used_only_in_box) + @box::UniqueGvar.write(123) + assert_equal 123, @box::UniqueGvar.read + assert_nil $used_only_in_box # Kernel#global_variables returns the sum of all gvars. global_gvars = global_variables.sort - assert_equal global_gvars, @n::UniqueGvar.gvars_in_ns.sort - @n::UniqueGvar.write_only(456) - assert_equal (global_gvars + [:$write_only_var_in_ns]).sort, @n::UniqueGvar.gvars_in_ns.sort - assert_equal (global_gvars + [:$write_only_var_in_ns]).sort, global_variables.sort + assert_equal global_gvars, @box::UniqueGvar.gvars_in_box.sort + @box::UniqueGvar.write_only(456) + assert_equal (global_gvars + [:$write_only_var_in_box]).sort, @box::UniqueGvar.gvars_in_box.sort + assert_equal (global_gvars + [:$write_only_var_in_box]).sort, global_variables.sort ensure EnvUtil.suppress_warning do $-0 = default_l @@ -524,105 +532,105 @@ def test_global_variables end def test_load_path_and_loaded_features - pend unless Ruby::Box.enabled? + setup_box assert $LOAD_PATH.respond_to?(:resolve_feature_path) - @n.require_relative('namespace/load_path') + @box.require_relative('box/load_path') - assert_not_equal $LOAD_PATH, @n::LoadPathCheck::FIRST_LOAD_PATH + assert_not_equal $LOAD_PATH, @box::LoadPathCheck::FIRST_LOAD_PATH - assert @n::LoadPathCheck::FIRST_LOAD_PATH_RESPOND_TO_RESOLVE + assert @box::LoadPathCheck::FIRST_LOAD_PATH_RESPOND_TO_RESOLVE - namespace_dir = File.join(__dir__, 'namespace') - # TODO: $LOADED_FEATURES in method calls should refer the current namespace in addition to the loading namespace. - # assert @n::LoadPathCheck.current_loaded_features.include?(File.join(namespace_dir, 'blank1.rb')) - # assert !@n::LoadPathCheck.current_loaded_features.include?(File.join(namespace_dir, 'blank2.rb')) - # assert @n::LoadPathCheck.require_blank2 - # assert @n::LoadPathCheck.current_loaded_features.include?(File.join(namespace_dir, 'blank2.rb')) + box_dir = File.join(__dir__, 'box') + # TODO: $LOADED_FEATURES in method calls should refer the current box in addition to the loading box. + # assert @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank1.rb')) + # assert !@box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) + # assert @box::LoadPathCheck.require_blank2 + # assert @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) - assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank1.rb')) - assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb')) + assert !$LOADED_FEATURES.include?(File.join(box_dir, 'blank1.rb')) + assert !$LOADED_FEATURES.include?(File.join(box_dir, 'blank2.rb')) end def test_eval_basic - pend unless Ruby::Box.enabled? + setup_box # Test basic evaluation - result = @n.eval("1 + 1") + result = @box.eval("1 + 1") assert_equal 2, result # Test string evaluation - result = @n.eval("'hello ' + 'world'") + result = @box.eval("'hello ' + 'world'") assert_equal "hello world", result end def test_eval_with_constants - pend unless Ruby::Box.enabled? + setup_box - # Define a constant in the namespace via eval - @n.eval("TEST_CONST = 42") - assert_equal 42, @n::TEST_CONST + # Define a constant in the box via eval + @box.eval("TEST_CONST = 42") + assert_equal 42, @box::TEST_CONST - # Constant should not be visible in main namespace + # Constant should not be visible in main box assert_raise(NameError) { TEST_CONST } end def test_eval_with_classes - pend unless Ruby::Box.enabled? + setup_box - # Define a class in the namespace via eval - @n.eval("class TestClass; def hello; 'from namespace'; end; end") + # Define a class in the box via eval + @box.eval("class TestClass; def hello; 'from box'; end; end") - # Class should be accessible in the namespace - instance = @n::TestClass.new - assert_equal "from namespace", instance.hello + # Class should be accessible in the box + instance = @box::TestClass.new + assert_equal "from box", instance.hello - # Class should not be visible in main namespace + # Class should not be visible in main box assert_raise(NameError) { TestClass } end def test_eval_isolation - pend unless Ruby::Box.enabled? + setup_box - # Create another namespace - n2 = Namespace.new + # Create another box + n2 = Ruby::Box.new - # Define different constants in each namespace - @n.eval("ISOLATION_TEST = 'first'") + # Define different constants in each box + @box.eval("ISOLATION_TEST = 'first'") n2.eval("ISOLATION_TEST = 'second'") - # Each namespace should have its own constant - assert_equal "first", @n::ISOLATION_TEST + # Each box should have its own constant + assert_equal "first", @box::ISOLATION_TEST assert_equal "second", n2::ISOLATION_TEST # Constants should not interfere with each other - assert_not_equal @n::ISOLATION_TEST, n2::ISOLATION_TEST + assert_not_equal @box::ISOLATION_TEST, n2::ISOLATION_TEST end def test_eval_with_variables - pend unless Ruby::Box.enabled? + setup_box # Test local variable access (should work within the eval context) - result = @n.eval("x = 10; y = 20; x + y") + result = @box.eval("x = 10; y = 20; x + y") assert_equal 30, result end def test_eval_error_handling - pend unless Ruby::Box.enabled? + setup_box # Test syntax error - assert_raise(SyntaxError) { @n.eval("1 +") } + assert_raise(SyntaxError) { @box.eval("1 +") } # Test name error - assert_raise(NameError) { @n.eval("undefined_variable") } + assert_raise(NameError) { @box.eval("undefined_variable") } - # Test that namespace is properly restored after error + # Test that box is properly restored after error begin - @n.eval("raise RuntimeError, 'test error'") + @box.eval("raise RuntimeError, 'test error'") rescue RuntimeError - # Should be able to continue using the namespace - result = @n.eval("2 + 2") + # Should be able to continue using the box + result = @box.eval("2 + 2") assert_equal 4, result end end @@ -702,24 +710,24 @@ def test_root_and_main_methods def test_basic_box_detections assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - ns = Ruby::Box.new + box = Ruby::Box.new $gvar1 = 'bar' code = <<~EOC - NS1 = Ruby::Box.current + BOX1 = Ruby::Box.current $gvar1 = 'foo' def toplevel = $gvar1 class Foo - NS2 = Ruby::Box.current - NS2_proc = ->(){ NS2 } - NS3_proc = ->(){ Ruby::Box.current } - - def ns4 = Ruby::Box.current - def self.ns5 = NS2 - def self.ns6 = Ruby::Box.current - def self.ns6_proc = ->(){ Ruby::Box.current } - def self.ns7 + BOX2 = Ruby::Box.current + BOX2_proc = ->(){ BOX2 } + BOX3_proc = ->(){ Ruby::Box.current } + + def box4 = Ruby::Box.current + def self.box5 = BOX2 + def self.box6 = Ruby::Box.current + def self.box6_proc = ->(){ Ruby::Box.current } + def self.box7 res = [] [1,2].chunk{ it.even? }.each do |bool, members| res << Ruby::Box.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") @@ -740,35 +748,35 @@ def foo_box = Ruby::Box.current module_function :foo_box end - NS_X = Foo.new.ns4 - NS_Y = foo_box + BOX_X = Foo.new.box4 + BOX_Y = foo_box EOC - ns.eval(code) + box.eval(code) outer = Ruby::Box.current - assert_equal ns, ns::NS1 # on TOP frame - assert_equal ns, ns::Foo::NS2 # on CLASS frame - assert_equal ns, ns::Foo::NS2_proc.call # proc -> a const on CLASS - assert_equal ns, ns::Foo::NS3_proc.call # proc -> the current - assert_equal ns, ns::Foo.new.ns4 # instance method -> the current - assert_equal ns, ns::Foo.ns5 # singleton method -> a const on CLASS - assert_equal ns, ns::Foo.ns6 # singleton method -> the current - assert_equal ns, ns::Foo.ns6_proc.call # method returns a proc -> the current + assert_equal box, box::BOX1 # on TOP frame + assert_equal box, box::Foo::BOX2 # on CLASS frame + assert_equal box, box::Foo::BOX2_proc.call # proc -> a const on CLASS + assert_equal box, box::Foo::BOX3_proc.call # proc -> the current + assert_equal box, box::Foo.new.box4 # instance method -> the current + assert_equal box, box::Foo.box5 # singleton method -> a const on CLASS + assert_equal box, box::Foo.box6 # singleton method -> the current + assert_equal box, box::Foo.box6_proc.call # method returns a proc -> the current # a block after CFUNC/IFUNC in a method -> the current - assert_equal ["#{ns.object_id}:false:1", "#{ns.object_id}:true:2"], ns::Foo.ns7 + assert_equal ["#{box.object_id}:false:1", "#{box.object_id}:true:2"], box::Foo.box7 - assert_equal outer, ns::Foo.yield_block{ Ruby::Box.current } # method yields - assert_equal outer, ns::Foo.call_block{ Ruby::Box.current } # method calls a block + assert_equal outer, box::Foo.yield_block{ Ruby::Box.current } # method yields + assert_equal outer, box::Foo.call_block{ Ruby::Box.current } # method calls a block - assert_equal 'foo', ns::Foo.gvar1 # method refers gvar - assert_equal 'bar', $gvar1 # gvar value out of the ns - assert_equal 'foo', ns::Foo.call_toplevel # toplevel method referring gvar + assert_equal 'foo', box::Foo.gvar1 # method refers gvar + assert_equal 'bar', $gvar1 # gvar value out of the box + assert_equal 'foo', box::Foo.call_toplevel # toplevel method referring gvar - assert_equal ns, ns::NS_X # on TOP frame, referring a class in the current - assert_equal ns, ns::NS_Y # on TOP frame, referring Kernel method defined by a CFUNC method + assert_equal box, box::BOX_X # on TOP frame, referring a class in the current + assert_equal box, box::BOX_Y # on TOP frame, referring Kernel method defined by a CFUNC method - assert_equal "Foo", ns::FOO_NAME - assert_equal "Foo", ns::Foo.name + assert_equal "Foo", box::FOO_NAME + assert_equal "Foo", box::Foo.name end; end From 28b195fc67788c03be59c2a4cbf0cad52ac3b90f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 14 Dec 2025 10:46:52 +0100 Subject: [PATCH 1943/2435] Store the fiber_serial in the EC to allow inlining MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mutexes spend a significant amount of time in `rb_fiber_serial` because it can't be inlined (except with LTO). The fiber struct is opaque the so function can't be defined as inlineable. Ideally the while fiber struct would not be opaque to the rest of Ruby core, but it's tricky to do. Instead we can store the fiber serial in the execution context itself, and make its access cheaper: ``` $ hyperfine './miniruby-baseline --yjit /tmp/mut.rb' './miniruby-inline-serial --yjit /tmp/mut.rb' Benchmark 1: ./miniruby-baseline --yjit /tmp/mut.rb Time (mean ± σ): 4.011 s ± 0.084 s [User: 3.977 s, System: 0.011 s] Range (min … max): 3.950 s … 4.245 s 10 runs Benchmark 2: ./miniruby-inline-serial --yjit /tmp/mut.rb Time (mean ± σ): 3.495 s ± 0.150 s [User: 3.448 s, System: 0.009 s] Range (min … max): 3.340 s … 3.869 s 10 runs Summary ./miniruby-inline-serial --yjit /tmp/mut.rb ran 1.15 ± 0.05 times faster than ./miniruby-baseline --yjit /tmp/mut.rb ``` ```ruby i = 10_000_000 mut = Mutex.new while i > 0 i -= 1 mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } mut.synchronize { } end ``` --- cont.c | 13 ++----------- internal/cont.h | 7 ++++++- thread.c | 8 ++++---- thread_sync.c | 49 +++++++++++++++++++++++++------------------------ vm_core.h | 1 + 5 files changed, 38 insertions(+), 40 deletions(-) diff --git a/cont.c b/cont.c index 8d5efeaac431a1..404574e110c870 100644 --- a/cont.c +++ b/cont.c @@ -268,8 +268,6 @@ struct rb_fiber_struct { unsigned int killed : 1; - rb_serial_t serial; - struct coroutine_context context; struct fiber_pool_stack stack; }; @@ -1012,13 +1010,6 @@ rb_fiber_threadptr(const rb_fiber_t *fiber) return fiber->cont.saved_ec.thread_ptr; } -rb_serial_t -rb_fiber_serial(const rb_fiber_t *fiber) -{ - VM_ASSERT(fiber->serial >= 1); - return fiber->serial; -} - static VALUE cont_thread_value(const rb_context_t *cont) { @@ -2026,10 +2017,10 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) fiber->cont.type = FIBER_CONTEXT; fiber->blocking = blocking; fiber->killed = 0; - fiber->serial = next_fiber_serial(th->ractor); cont_init(&fiber->cont, th); fiber->cont.saved_ec.fiber_ptr = fiber; + fiber->cont.saved_ec.fiber_serial = next_fiber_serial(th->ractor); rb_ec_clear_vm_stack(&fiber->cont.saved_ec); fiber->prev = NULL; @@ -2576,10 +2567,10 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) } fiber->cont.type = FIBER_CONTEXT; fiber->cont.saved_ec.fiber_ptr = fiber; + fiber->cont.saved_ec.fiber_serial = next_fiber_serial(th->ractor); fiber->cont.saved_ec.thread_ptr = th; fiber->blocking = 1; fiber->killed = 0; - fiber->serial = next_fiber_serial(th->ractor); fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ th->ec = &fiber->cont.saved_ec; cont_init_jit_cont(&fiber->cont); diff --git a/internal/cont.h b/internal/cont.h index 21a054f37c1294..bbcaf0b18824c4 100644 --- a/internal/cont.h +++ b/internal/cont.h @@ -31,6 +31,11 @@ VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber); unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber); struct rb_execution_context_struct * rb_fiberptr_get_ec(struct rb_fiber_struct *fiber); -rb_serial_t rb_fiber_serial(const struct rb_fiber_struct *fiber); +static inline rb_serial_t +rb_ec_fiber_serial(struct rb_execution_context_struct *ec) +{ + VM_ASSERT(ec->fiber_serial >= 1); + return ec->fiber_serial; +} #endif /* INTERNAL_CONT_H */ diff --git a/thread.c b/thread.c index e9d36cb1f69bc0..cea8e7fe75d362 100644 --- a/thread.c +++ b/thread.c @@ -454,7 +454,7 @@ rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th) // rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th); VM_ASSERT(mutex->fiber_serial); - const char *error_message = rb_mutex_unlock_th(mutex, th, NULL); + const char *error_message = rb_mutex_unlock_th(mutex, th, 0); if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message); } } @@ -5283,7 +5283,7 @@ rb_thread_shield_owned(VALUE self) rb_mutex_t *m = mutex_ptr(mutex); - return m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr); + return m->fiber_serial == rb_ec_fiber_serial(GET_EC()); } /* @@ -5302,7 +5302,7 @@ rb_thread_shield_wait(VALUE self) if (!mutex) return Qfalse; m = mutex_ptr(mutex); - if (m->fiber_serial == rb_fiber_serial(GET_EC()->fiber_ptr)) return Qnil; + if (m->fiber_serial == rb_ec_fiber_serial(GET_EC())) return Qnil; rb_thread_shield_waiting_inc(self); rb_mutex_lock(mutex); rb_thread_shield_waiting_dec(self); @@ -5860,7 +5860,7 @@ rb_check_deadlock(rb_ractor_t *r) } else if (th->locking_mutex) { rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); - if (mutex->fiber_serial == rb_fiber_serial(th->ec->fiber_ptr) || (!mutex->fiber_serial && !ccan_list_empty(&mutex->waitq))) { + if (mutex->fiber_serial == rb_ec_fiber_serial(th->ec) || (!mutex->fiber_serial && !ccan_list_empty(&mutex->waitq))) { found = 1; } } diff --git a/thread_sync.c b/thread_sync.c index 6af6aaddd6241d..5af86c5a3b294d 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -81,7 +81,7 @@ static void rb_mutex_abandon_all(rb_mutex_t *mutexes); static void rb_mutex_abandon_keeping_mutexes(rb_thread_t *th); static void rb_mutex_abandon_locking_mutex(rb_thread_t *th); #endif -static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber); +static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial); /* * Document-class: Thread::Mutex @@ -133,7 +133,7 @@ mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; if (mutex_locked_p(mutex)) { - const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), NULL); + const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), 0); if (err) rb_bug("%s", err); } ruby_xfree(ptr); @@ -221,26 +221,26 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) } static void -mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) { mutex->thread = th->self; - mutex->fiber_serial = rb_fiber_serial(fiber); + mutex->fiber_serial = fiber_serial; } static void -mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) { - mutex_set_owner(mutex, th, fiber); + mutex_set_owner(mutex, th, fiber_serial); thread_mutex_insert(th, mutex); } static inline bool -mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) { if (mutex->fiber_serial == 0) { RUBY_DEBUG_LOG("%p ok", mutex); - mutex_locked(mutex, th, fiber); + mutex_locked(mutex, th, fiber_serial); return true; } else { @@ -252,7 +252,7 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) static VALUE rb_mut_trylock(rb_execution_context_t *ec, VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, ec->fiber_ptr)); + return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_fiber_serial(ec))); } VALUE @@ -262,9 +262,9 @@ rb_mutex_trylock(VALUE self) } static VALUE -mutex_owned_p(rb_fiber_t *fiber, rb_mutex_t *mutex) +mutex_owned_p(rb_serial_t fiber_serial, rb_mutex_t *mutex) { - return RBOOL(mutex->fiber_serial == rb_fiber_serial(fiber)); + return RBOOL(mutex->fiber_serial == fiber_serial); } static VALUE @@ -305,6 +305,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_execution_context_t *ec = args->ec; rb_thread_t *th = ec->thread_ptr; rb_fiber_t *fiber = ec->fiber_ptr; + rb_serial_t fiber_serial = rb_ec_fiber_serial(ec); rb_mutex_t *mutex = args->mutex; rb_atomic_t saved_ints = 0; @@ -314,12 +315,12 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (!mutex_trylock(mutex, th, fiber)) { - if (mutex->fiber_serial == rb_fiber_serial(fiber)) { + if (!mutex_trylock(mutex, th, fiber_serial)) { + if (mutex->fiber_serial == fiber_serial) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } - while (mutex->fiber_serial != rb_fiber_serial(fiber)) { + while (mutex->fiber_serial != fiber_serial) { VM_ASSERT(mutex->fiber_serial != 0); VALUE scheduler = rb_fiber_scheduler_current(); @@ -335,7 +336,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter); if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber); + mutex_set_owner(mutex, th, fiber_serial); } } else { @@ -376,7 +377,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) // unlocked by another thread while sleeping if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber); + mutex_set_owner(mutex, th, fiber_serial); } rb_ractor_sleeper_threads_dec(th->ractor); @@ -389,13 +390,13 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) if (interruptible_p) { /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ - if (mutex->fiber_serial == rb_fiber_serial(fiber)) { + if (mutex->fiber_serial == fiber_serial) { mutex->thread = Qfalse; mutex->fiber_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber); + mutex_set_owner(mutex, th, fiber_serial); } } else { @@ -414,13 +415,13 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) } if (saved_ints) th->ec->interrupt_flag = saved_ints; - if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(mutex, th, fiber); + if (mutex->fiber_serial == fiber_serial) mutex_locked(mutex, th, fiber_serial); } RUBY_DEBUG_LOG("%p locked", mutex); // assertion - if (mutex_owned_p(fiber, mutex) == Qfalse) rb_bug("do_mutex_lock: mutex is not owned."); + if (mutex_owned_p(fiber_serial, mutex) == Qfalse) rb_bug("do_mutex_lock: mutex is not owned."); return self; } @@ -455,7 +456,7 @@ rb_mutex_lock(VALUE self) static VALUE rb_mut_owned_p(rb_execution_context_t *ec, VALUE self) { - return mutex_owned_p(ec->fiber_ptr, mutex_ptr(self)); + return mutex_owned_p(rb_ec_fiber_serial(ec), mutex_ptr(self)); } VALUE @@ -465,14 +466,14 @@ rb_mutex_owned_p(VALUE self) } static const char * -rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) { RUBY_DEBUG_LOG("%p", mutex); if (mutex->fiber_serial == 0) { return "Attempt to unlock a mutex which is not locked"; } - else if (fiber && mutex->fiber_serial != rb_fiber_serial(fiber)) { + else if (fiber_serial && mutex->fiber_serial != fiber_serial) { return "Attempt to unlock a mutex which is locked by another thread/fiber"; } @@ -516,7 +517,7 @@ do_mutex_unlock(struct mutex_args *args) rb_mutex_t *mutex = args->mutex; rb_thread_t *th = rb_ec_thread_ptr(args->ec); - err = rb_mutex_unlock_th(mutex, th, args->ec->fiber_ptr); + err = rb_mutex_unlock_th(mutex, th, rb_ec_fiber_serial(args->ec)); if (err) rb_raise(rb_eThreadError, "%s", err); } diff --git a/vm_core.h b/vm_core.h index d391c4258a4337..d61686f4b66bff 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1041,6 +1041,7 @@ struct rb_execution_context_struct { rb_fiber_t *fiber_ptr; struct rb_thread_struct *thread_ptr; + rb_serial_t fiber_serial; /* storage (ec (fiber) local) */ struct rb_id_table *local_storage; From e42bcd7ce76e75601ef3adf35467edf277471af2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 16 Dec 2025 00:43:41 +0100 Subject: [PATCH 1944/2435] Rename fiber_serial into ec_serial Since it now live in the EC. --- cont.c | 8 +++--- internal/cont.h | 7 ----- ractor.c | 4 +-- ractor_core.h | 2 +- thread.c | 10 +++---- thread_sync.c | 70 ++++++++++++++++++++++++------------------------- vm_core.h | 9 ++++++- 7 files changed, 55 insertions(+), 55 deletions(-) diff --git a/cont.c b/cont.c index 404574e110c870..0087994730419e 100644 --- a/cont.c +++ b/cont.c @@ -1996,9 +1996,9 @@ fiber_alloc(VALUE klass) } static rb_serial_t -next_fiber_serial(rb_ractor_t *cr) +next_ec_serial(rb_ractor_t *cr) { - return cr->next_fiber_serial++; + return cr->next_ec_serial++; } static rb_fiber_t* @@ -2020,7 +2020,7 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) cont_init(&fiber->cont, th); fiber->cont.saved_ec.fiber_ptr = fiber; - fiber->cont.saved_ec.fiber_serial = next_fiber_serial(th->ractor); + fiber->cont.saved_ec.serial = next_ec_serial(th->ractor); rb_ec_clear_vm_stack(&fiber->cont.saved_ec); fiber->prev = NULL; @@ -2567,7 +2567,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) } fiber->cont.type = FIBER_CONTEXT; fiber->cont.saved_ec.fiber_ptr = fiber; - fiber->cont.saved_ec.fiber_serial = next_fiber_serial(th->ractor); + fiber->cont.saved_ec.serial = next_ec_serial(th->ractor); fiber->cont.saved_ec.thread_ptr = th; fiber->blocking = 1; fiber->killed = 0; diff --git a/internal/cont.h b/internal/cont.h index bbcaf0b18824c4..dcf6f820a38f87 100644 --- a/internal/cont.h +++ b/internal/cont.h @@ -31,11 +31,4 @@ VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb VALUE rb_fiberptr_self(struct rb_fiber_struct *fiber); unsigned int rb_fiberptr_blocking(struct rb_fiber_struct *fiber); struct rb_execution_context_struct * rb_fiberptr_get_ec(struct rb_fiber_struct *fiber); - -static inline rb_serial_t -rb_ec_fiber_serial(struct rb_execution_context_struct *ec) -{ - VM_ASSERT(ec->fiber_serial >= 1); - return ec->fiber_serial; -} #endif /* INTERNAL_CONT_H */ diff --git a/ractor.c b/ractor.c index f65a4f79c1f8e8..5c98196c912fa2 100644 --- a/ractor.c +++ b/ractor.c @@ -422,7 +422,7 @@ ractor_alloc(VALUE klass) VALUE rv = TypedData_Make_Struct(klass, rb_ractor_t, &ractor_data_type, r); FL_SET_RAW(rv, RUBY_FL_SHAREABLE); r->pub.self = rv; - r->next_fiber_serial = 1; + r->next_ec_serial = 1; VM_ASSERT(ractor_status_p(r, ractor_created)); return rv; } @@ -440,7 +440,7 @@ rb_ractor_main_alloc(void) r->name = Qnil; r->pub.self = Qnil; r->newobj_cache = rb_gc_ractor_cache_alloc(r); - r->next_fiber_serial = 1; + r->next_ec_serial = 1; ruby_single_main_ractor = r; return r; diff --git a/ractor_core.h b/ractor_core.h index d6f9d4eda0fd4b..96d22bea29eade 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -91,7 +91,7 @@ struct rb_ractor_struct { // ractor local data - rb_serial_t next_fiber_serial; + rb_serial_t next_ec_serial; st_table *local_storage; struct rb_id_table *idkey_local_storage; diff --git a/thread.c b/thread.c index cea8e7fe75d362..3e1bb1dbe7ad17 100644 --- a/thread.c +++ b/thread.c @@ -453,7 +453,7 @@ rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th) th->keeping_mutexes = mutex->next_mutex; // rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th); - VM_ASSERT(mutex->fiber_serial); + VM_ASSERT(mutex->ec_serial); const char *error_message = rb_mutex_unlock_th(mutex, th, 0); if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message); } @@ -5283,7 +5283,7 @@ rb_thread_shield_owned(VALUE self) rb_mutex_t *m = mutex_ptr(mutex); - return m->fiber_serial == rb_ec_fiber_serial(GET_EC()); + return m->ec_serial == rb_ec_serial(GET_EC()); } /* @@ -5302,7 +5302,7 @@ rb_thread_shield_wait(VALUE self) if (!mutex) return Qfalse; m = mutex_ptr(mutex); - if (m->fiber_serial == rb_ec_fiber_serial(GET_EC())) return Qnil; + if (m->ec_serial == rb_ec_serial(GET_EC())) return Qnil; rb_thread_shield_waiting_inc(self); rb_mutex_lock(mutex); rb_thread_shield_waiting_dec(self); @@ -5820,7 +5820,7 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg) if (th->locking_mutex) { rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); rb_str_catf(msg, " mutex:%llu cond:%"PRIuSIZE, - (unsigned long long)mutex->fiber_serial, rb_mutex_num_waiting(mutex)); + (unsigned long long)mutex->ec_serial, rb_mutex_num_waiting(mutex)); } { @@ -5860,7 +5860,7 @@ rb_check_deadlock(rb_ractor_t *r) } else if (th->locking_mutex) { rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); - if (mutex->fiber_serial == rb_ec_fiber_serial(th->ec) || (!mutex->fiber_serial && !ccan_list_empty(&mutex->waitq))) { + if (mutex->ec_serial == rb_ec_serial(th->ec) || (!mutex->ec_serial && !ccan_list_empty(&mutex->waitq))) { found = 1; } } diff --git a/thread_sync.c b/thread_sync.c index 5af86c5a3b294d..6bff982f31d78b 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -7,7 +7,7 @@ static VALUE rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { - rb_serial_t fiber_serial; + rb_serial_t ec_serial; VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free struct rb_mutex_struct *next_mutex; struct ccan_list_head waitq; /* protected by GVL */ @@ -81,7 +81,7 @@ static void rb_mutex_abandon_all(rb_mutex_t *mutexes); static void rb_mutex_abandon_keeping_mutexes(rb_thread_t *th); static void rb_mutex_abandon_locking_mutex(rb_thread_t *th); #endif -static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial); +static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial); /* * Document-class: Thread::Mutex @@ -125,7 +125,7 @@ rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber); static bool mutex_locked_p(rb_mutex_t *mutex) { - return mutex->fiber_serial != 0; + return mutex->ec_serial != 0; } static void @@ -221,26 +221,26 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) } static void -mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) +mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { mutex->thread = th->self; - mutex->fiber_serial = fiber_serial; + mutex->ec_serial = ec_serial; } static void -mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) +mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { - mutex_set_owner(mutex, th, fiber_serial); + mutex_set_owner(mutex, th, ec_serial); thread_mutex_insert(th, mutex); } static inline bool -mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) +mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { - if (mutex->fiber_serial == 0) { + if (mutex->ec_serial == 0) { RUBY_DEBUG_LOG("%p ok", mutex); - mutex_locked(mutex, th, fiber_serial); + mutex_locked(mutex, th, ec_serial); return true; } else { @@ -252,7 +252,7 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) static VALUE rb_mut_trylock(rb_execution_context_t *ec, VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_fiber_serial(ec))); + return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_serial(ec))); } VALUE @@ -262,9 +262,9 @@ rb_mutex_trylock(VALUE self) } static VALUE -mutex_owned_p(rb_serial_t fiber_serial, rb_mutex_t *mutex) +mutex_owned_p(rb_serial_t ec_serial, rb_mutex_t *mutex) { - return RBOOL(mutex->fiber_serial == fiber_serial); + return RBOOL(mutex->ec_serial == ec_serial); } static VALUE @@ -305,7 +305,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_execution_context_t *ec = args->ec; rb_thread_t *th = ec->thread_ptr; rb_fiber_t *fiber = ec->fiber_ptr; - rb_serial_t fiber_serial = rb_ec_fiber_serial(ec); + rb_serial_t ec_serial = rb_ec_serial(ec); rb_mutex_t *mutex = args->mutex; rb_atomic_t saved_ints = 0; @@ -315,13 +315,13 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (!mutex_trylock(mutex, th, fiber_serial)) { - if (mutex->fiber_serial == fiber_serial) { + if (!mutex_trylock(mutex, th, ec_serial)) { + if (mutex->ec_serial == ec_serial) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } - while (mutex->fiber_serial != fiber_serial) { - VM_ASSERT(mutex->fiber_serial != 0); + while (mutex->ec_serial != ec_serial) { + VM_ASSERT(mutex->ec_serial != 0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { @@ -335,8 +335,8 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter); - if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber_serial); + if (!mutex->ec_serial) { + mutex_set_owner(mutex, th, ec_serial); } } else { @@ -376,8 +376,8 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) ccan_list_del(&sync_waiter.node); // unlocked by another thread while sleeping - if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber_serial); + if (!mutex->ec_serial) { + mutex_set_owner(mutex, th, ec_serial); } rb_ractor_sleeper_threads_dec(th->ractor); @@ -390,13 +390,13 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) if (interruptible_p) { /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ - if (mutex->fiber_serial == fiber_serial) { + if (mutex->ec_serial == ec_serial) { mutex->thread = Qfalse; - mutex->fiber_serial = 0; + mutex->ec_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ - if (!mutex->fiber_serial) { - mutex_set_owner(mutex, th, fiber_serial); + if (!mutex->ec_serial) { + mutex_set_owner(mutex, th, ec_serial); } } else { @@ -415,13 +415,13 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) } if (saved_ints) th->ec->interrupt_flag = saved_ints; - if (mutex->fiber_serial == fiber_serial) mutex_locked(mutex, th, fiber_serial); + if (mutex->ec_serial == ec_serial) mutex_locked(mutex, th, ec_serial); } RUBY_DEBUG_LOG("%p locked", mutex); // assertion - if (mutex_owned_p(fiber_serial, mutex) == Qfalse) rb_bug("do_mutex_lock: mutex is not owned."); + if (mutex_owned_p(ec_serial, mutex) == Qfalse) rb_bug("do_mutex_lock: mutex is not owned."); return self; } @@ -456,7 +456,7 @@ rb_mutex_lock(VALUE self) static VALUE rb_mut_owned_p(rb_execution_context_t *ec, VALUE self) { - return mutex_owned_p(rb_ec_fiber_serial(ec), mutex_ptr(self)); + return mutex_owned_p(rb_ec_serial(ec), mutex_ptr(self)); } VALUE @@ -466,20 +466,20 @@ rb_mutex_owned_p(VALUE self) } static const char * -rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t fiber_serial) +rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { RUBY_DEBUG_LOG("%p", mutex); - if (mutex->fiber_serial == 0) { + if (mutex->ec_serial == 0) { return "Attempt to unlock a mutex which is not locked"; } - else if (fiber_serial && mutex->fiber_serial != fiber_serial) { + else if (ec_serial && mutex->ec_serial != ec_serial) { return "Attempt to unlock a mutex which is locked by another thread/fiber"; } struct sync_waiter *cur = 0, *next; - mutex->fiber_serial = 0; + mutex->ec_serial = 0; thread_mutex_remove(th, mutex); ccan_list_for_each_safe(&mutex->waitq, cur, next, node) { @@ -517,7 +517,7 @@ do_mutex_unlock(struct mutex_args *args) rb_mutex_t *mutex = args->mutex; rb_thread_t *th = rb_ec_thread_ptr(args->ec); - err = rb_mutex_unlock_th(mutex, th, rb_ec_fiber_serial(args->ec)); + err = rb_mutex_unlock_th(mutex, th, rb_ec_serial(args->ec)); if (err) rb_raise(rb_eThreadError, "%s", err); } @@ -583,7 +583,7 @@ rb_mutex_abandon_all(rb_mutex_t *mutexes) while (mutexes) { mutex = mutexes; mutexes = mutex->next_mutex; - mutex->fiber_serial = 0; + mutex->ec_serial = 0; mutex->next_mutex = 0; ccan_list_head_init(&mutex->waitq); } diff --git a/vm_core.h b/vm_core.h index d61686f4b66bff..c716e001a5f542 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1041,7 +1041,7 @@ struct rb_execution_context_struct { rb_fiber_t *fiber_ptr; struct rb_thread_struct *thread_ptr; - rb_serial_t fiber_serial; + rb_serial_t serial; /* storage (ec (fiber) local) */ struct rb_id_table *local_storage; @@ -2037,6 +2037,13 @@ RUBY_EXTERN unsigned int ruby_vm_event_local_num; #define GET_THREAD() rb_current_thread() #define GET_EC() rb_current_execution_context(true) +static inline rb_serial_t +rb_ec_serial(struct rb_execution_context_struct *ec) +{ + VM_ASSERT(ec->serial >= 1); + return ec->serial; +} + static inline rb_thread_t * rb_ec_thread_ptr(const rb_execution_context_t *ec) { From 2b1a9afbfbb5491665bfbdd560486a310ca49755 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:52:45 +0900 Subject: [PATCH 1945/2435] Fix: Do not pass negative timeout to Addrinfo#connect_internal (#15578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change fixes a bug where, with `Socket.tcp`’s `fast_fallback option` disabled, specifying `open_timeout` could unintentionally pass a negative value to `Addrinfo#connect_internal`, `causing an ArgumentError`. ``` ❯ ruby -rsocket -e 'p Socket.tcp("localhost", 9292, open_timeout: 1, fast_fallback: false)' /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:64:in 'IO#wait_writable': time interval must not be negative (ArgumentError) sock.wait_writable(timeout) or ^^^^^^^ from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:64:in 'Addrinfo#connect_internal' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:141:in 'Addrinfo#connect' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:964:in 'block in Socket.tcp_without_fast_fallback' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:231:in 'Array#each' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:231:in 'Addrinfo.foreach' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:945:in 'Socket.tcp_without_fast_fallback' from /Users/misaki-shioi/src/install/lib/ruby/4.0.0+0/socket.rb:671:in 'Socket.tcp' from -e:1:in '
' ``` --- ext/socket/lib/socket.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 9862c92c0b6591..f47b5bc1d17a54 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -948,7 +948,14 @@ def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_t local_addr = nil end begin - timeout = open_timeout ? open_timeout - (current_clock_time - starts_at) : connect_timeout + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end + sock = local_addr ? ai.connect_from(local_addr, timeout:) : ai.connect(timeout:) From 6b35f074bd83794007d4c7b773a289bddef0dbdf Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 16 Dec 2025 22:17:08 +0900 Subject: [PATCH 1946/2435] Box: [DOC] Add RUBY_BOX in Environment --- man/ruby.1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/man/ruby.1 b/man/ruby.1 index d19cea99ca3c5b..8141a6d7efa97a 100644 --- a/man/ruby.1 +++ b/man/ruby.1 @@ -523,6 +523,13 @@ variable is not defined, Ruby refers to If set, Ruby tries to free all dynamically allocated memories. Introduced in Ruby 3.3, default: unset. .Pp +.It Ev RUBY_BOX +If set to +.Li 1 , +Ruby Box is enabled and users will be able to execute +.Li Ruby::Box.new . +Ruby Box is an experimental feature introduced in Ruby 4.0. +.Pp .It Ev RUBY_IO_BUFFER_DEFAULT_SIZE The custom default buffer size of .Li IO::Buffer . From 09a29e1312121fed5704ac196413c3b5ecb83fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 16 Dec 2025 17:06:33 +0100 Subject: [PATCH 1947/2435] Add the class variable and the class itself in Ractor::IsolationError (#15562) --- bootstraptest/test_ractor.rb | 4 ++-- variable.c | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 6b35bbb46be896..2b268e9966d076 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1022,7 +1022,7 @@ def initialize } # cvar in shareable-objects are not allowed to access from non-main Ractor -assert_equal 'can not access class variables from non-main Ractors', %q{ +assert_equal 'can not access class variables from non-main Ractors (@@cv from C)', %q{ class C @@cv = 'str' end @@ -1041,7 +1041,7 @@ class C } # also cached cvar in shareable-objects are not allowed to access from non-main Ractor -assert_equal 'can not access class variables from non-main Ractors', %q{ +assert_equal 'can not access class variables from non-main Ractors (@@cv from C)', %q{ class C @@cv = 'str' def self.cv diff --git a/variable.c b/variable.c index 47b521856676dd..a1899274725229 100644 --- a/variable.c +++ b/variable.c @@ -1195,10 +1195,13 @@ IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(ID id) } } -#define CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR() \ - if (UNLIKELY(!rb_ractor_main_p())) { \ - rb_raise(rb_eRactorIsolationError, "can not access class variables from non-main Ractors"); \ - } +static void +CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(VALUE klass, ID id) +{ + if (UNLIKELY(!rb_ractor_main_p())) { + rb_raise(rb_eRactorIsolationError, "can not access class variables from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", rb_id2str(id), klass); + } +} static inline void ivar_ractor_check(VALUE obj, ID id) @@ -4202,7 +4205,7 @@ cvar_overtaken(VALUE front, VALUE target, ID id) } #define CVAR_LOOKUP(v,r) do {\ - CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(); \ + CVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(klass, id); \ if (cvar_lookup_at(klass, id, (v))) {r;}\ CVAR_FOREACH_ANCESTORS(klass, v, r);\ } while(0) From aab4f6287da4d8035035f7486072f149abf8dcc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 16 Dec 2025 17:47:13 +0100 Subject: [PATCH 1948/2435] Add the instance variable name and the module in Ractor::IsolationError (#15563) --- bootstraptest/test_ractor.rb | 2 +- variable.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 2b268e9966d076..575b96e48d501a 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -846,7 +846,7 @@ def ractor_local_globals } # ivar in shareable-objects are not allowed to access from non-main Ractor -assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false +assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors (@iv from C)", <<~'RUBY', frozen_string_literal: false class C @iv = 'str' end diff --git a/variable.c b/variable.c index a1899274725229..7a015a51e397e0 100644 --- a/variable.c +++ b/variable.c @@ -1447,7 +1447,8 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { rb_raise(rb_eRactorIsolationError, - "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); + "can not get unshareable values from instance variables of classes/modules from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", + rb_id2str(id), obj); } return val; } From a8ba2b295b73a116d24fe5602d892d4a2042cd08 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 16 Dec 2025 09:15:46 -0800 Subject: [PATCH 1949/2435] add 21254 to the feature list --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 4b16fd50189d0b..465f8ec4b204a8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -456,6 +456,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21205]: https://bugs.ruby-lang.org/issues/21205 [Feature #21216]: https://bugs.ruby-lang.org/issues/21216 [Feature #21219]: https://bugs.ruby-lang.org/issues/21219 +[Feature #21254]: https://bugs.ruby-lang.org/issues/21254 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21275]: https://bugs.ruby-lang.org/issues/21275 From d209e6f1c0a93ad3ce1cc64dd165a6b67672614d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 26 Nov 2025 21:59:37 -0500 Subject: [PATCH 1950/2435] search_nonascii(): Replace UB pointer cast with memcpy Casting a pointer to create an unaligned one is undefined behavior in C standards. Use memcpy to express the unaligned load instead to play by the rules. Practically, this yields the same binary output in many situations while fixing the crash in [Bug #21715]. --- string.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/string.c b/string.c index 060a1467a06f55..9819a5910fa76e 100644 --- a/string.c +++ b/string.c @@ -716,7 +716,7 @@ VALUE rb_fs; static inline const char * search_nonascii(const char *p, const char *e) { - const uintptr_t *s, *t; + const char *s, *t; #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # if SIZEOF_UINTPTR_T == 8 @@ -760,17 +760,19 @@ search_nonascii(const char *p, const char *e) #define aligned_ptr(value) \ __builtin_assume_aligned((value), sizeof(uintptr_t)) #else -#define aligned_ptr(value) (uintptr_t *)(value) +#define aligned_ptr(value) (value) #endif s = aligned_ptr(p); - t = (uintptr_t *)(e - (SIZEOF_VOIDP-1)); + t = (e - (SIZEOF_VOIDP-1)); #undef aligned_ptr - for (;s < t; s++) { - if (*s & NONASCII_MASK) { + for (;s < t; s += sizeof(uintptr_t)) { + uintptr_t word; + memcpy(&word, s, sizeof(word)); + if (word & NONASCII_MASK) { #ifdef WORDS_BIGENDIAN - return (const char *)s + (nlz_intptr(*s&NONASCII_MASK)>>3); + return (const char *)s + (nlz_intptr(word&NONASCII_MASK)>>3); #else - return (const char *)s + (ntz_intptr(*s&NONASCII_MASK)>>3); + return (const char *)s + (ntz_intptr(word&NONASCII_MASK)>>3); #endif } } From 4fb537b1ee28bb37dbe551ac65c279d436c756bc Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 16 Dec 2025 14:06:55 -0500 Subject: [PATCH 1951/2435] Make tracepoints with set_trace_func or TracePoint.new ractor local (#15468) Before this change, GC'ing any Ractor object caused you to lose all enabled tracepoints across all ractors (even main). Now tracepoints are ractor-local and this doesn't happen. Internal events are still global. Fixes [Bug #19112] --- depend | 2 + imemo.c | 3 - iseq.c | 141 ++++++++++----- iseq.h | 4 +- method.h | 7 +- ractor.c | 45 +++++ ractor_core.h | 20 +++ test/ruby/test_settracefunc.rb | 206 ++++++++++++++++++++++ trace_point.rb | 8 +- vm.c | 33 ++-- vm_core.h | 24 ++- vm_insnhelper.c | 61 ++++--- vm_method.c | 26 ++- vm_trace.c | 311 +++++++++++++++++++++++---------- yjit.c | 13 +- zjit/src/cruby_bindings.inc.rs | 16 +- 16 files changed, 694 insertions(+), 226 deletions(-) diff --git a/depend b/depend index 81ab8274710947..69ce9e63bd9000 100644 --- a/depend +++ b/depend @@ -7606,6 +7606,7 @@ iseq.$(OBJEXT): {$(VPATH)}onigmo.h iseq.$(OBJEXT): {$(VPATH)}oniguruma.h iseq.$(OBJEXT): {$(VPATH)}prism_compile.h iseq.$(OBJEXT): {$(VPATH)}ractor.h +iseq.$(OBJEXT): {$(VPATH)}ractor_core.h iseq.$(OBJEXT): {$(VPATH)}ruby_assert.h iseq.$(OBJEXT): {$(VPATH)}ruby_atomic.h iseq.$(OBJEXT): {$(VPATH)}rubyparser.h @@ -19760,6 +19761,7 @@ vm_trace.$(OBJEXT): {$(VPATH)}onigmo.h vm_trace.$(OBJEXT): {$(VPATH)}oniguruma.h vm_trace.$(OBJEXT): {$(VPATH)}prism_compile.h vm_trace.$(OBJEXT): {$(VPATH)}ractor.h +vm_trace.$(OBJEXT): {$(VPATH)}ractor_core.h vm_trace.$(OBJEXT): {$(VPATH)}ruby_assert.h vm_trace.$(OBJEXT): {$(VPATH)}ruby_atomic.h vm_trace.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/imemo.c b/imemo.c index 8ec58ae4a92e52..42f6615a5e83be 100644 --- a/imemo.c +++ b/imemo.c @@ -306,9 +306,6 @@ mark_and_move_method_entry(rb_method_entry_t *ment, bool reference_updating) if (!rb_gc_checking_shareable()) { rb_gc_mark_and_move(&def->body.bmethod.proc); } - if (def->body.bmethod.hooks) { - rb_hook_list_mark_and_move(def->body.bmethod.hooks); - } break; case VM_METHOD_TYPE_ALIAS: rb_gc_mark_and_move_ptr(&def->body.alias.original_me); diff --git a/iseq.c b/iseq.c index 726501d45cd27e..90facfad78616c 100644 --- a/iseq.c +++ b/iseq.c @@ -39,6 +39,7 @@ #include "iseq.h" #include "ruby/util.h" #include "vm_core.h" +#include "ractor_core.h" #include "vm_callinfo.h" #include "yjit.h" #include "ruby/ractor.h" @@ -161,6 +162,24 @@ iseq_clear_ic_references(const rb_iseq_t *iseq) } } + +rb_hook_list_t * +rb_iseq_local_hooks(const rb_iseq_t *iseq, rb_ractor_t *r, bool create) +{ + rb_hook_list_t *hook_list = NULL; + st_data_t val; + if (st_lookup(rb_ractor_targeted_hooks(r), (st_data_t)iseq, &val)) { + hook_list = (rb_hook_list_t*)val; + RUBY_ASSERT(hook_list->type == hook_list_type_targeted_iseq); + } + else if (create) { + hook_list = RB_ZALLOC(rb_hook_list_t); + hook_list->type = hook_list_type_targeted_iseq; + st_insert(rb_ractor_targeted_hooks(r), (st_data_t)iseq, (st_data_t)hook_list); + } + return hook_list; +} + void rb_iseq_free(const rb_iseq_t *iseq) { @@ -213,10 +232,6 @@ rb_iseq_free(const rb_iseq_t *iseq) ruby_xfree(body); } - if (iseq && ISEQ_EXECUTABLE_P(iseq) && iseq->aux.exec.local_hooks) { - rb_hook_list_free(iseq->aux.exec.local_hooks); - } - RUBY_FREE_LEAVE("iseq"); } @@ -448,10 +463,6 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) else { /* executable */ VM_ASSERT(ISEQ_EXECUTABLE_P(iseq)); - - if (iseq->aux.exec.local_hooks) { - rb_hook_list_mark_and_move(iseq->aux.exec.local_hooks); - } } RUBY_MARK_LEAVE("iseq"); @@ -2438,17 +2449,22 @@ rb_iseq_event_flags(const rb_iseq_t *iseq, size_t pos) } } +static void rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, size_t pos); + // Clear tracing event flags and turn off tracing for a given instruction as needed. // This is currently used after updating a one-shot line coverage for the current instruction. void rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset) { - struct iseq_insn_info_entry *entry = (struct iseq_insn_info_entry *)get_insn_info(iseq, pos); - if (entry) { - entry->events &= ~reset; - if (!(entry->events & iseq->aux.exec.global_trace_events)) { - void rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, size_t pos); - rb_iseq_trace_flag_cleared(iseq, pos); + RB_VM_LOCKING() { + rb_vm_barrier(); + + struct iseq_insn_info_entry *entry = (struct iseq_insn_info_entry *)get_insn_info(iseq, pos); + if (entry) { + entry->events &= ~reset; + if (!(entry->events & iseq->aux.exec.global_trace_events)) { + rb_iseq_trace_flag_cleared(iseq, pos); + } } } } @@ -3930,14 +3946,15 @@ rb_vm_insn_decode(const VALUE encoded) // Turn on or off tracing for a given instruction address static inline int -encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon, bool remain_current_trace) +encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon, bool remain_traced) { + ASSERT_vm_locking(); st_data_t key = (st_data_t)*iseq_encoded_insn; st_data_t val; if (st_lookup(encoded_insn_data, key, &val)) { insn_data_t *e = (insn_data_t *)val; - if (remain_current_trace && key == (st_data_t)e->trace_encoded_insn) { + if (remain_traced && key == (st_data_t)e->trace_encoded_insn) { turnon = 1; } *iseq_encoded_insn = (VALUE) (turnon ? e->trace_encoded_insn : e->notrace_encoded_insn); @@ -3948,7 +3965,7 @@ encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon, } // Turn off tracing for an instruction at pos after tracing event flags are cleared -void +static void rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, size_t pos) { const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); @@ -3974,14 +3991,16 @@ add_bmethod_events(rb_event_flag_t events) // Note, to support call/return events for bmethods, turnon_event can have more events than tpval. static int -iseq_add_local_tracepoint(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line) +iseq_add_local_tracepoint(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line, rb_ractor_t *r) { unsigned int pc; int n = 0; const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); VALUE *iseq_encoded = (VALUE *)body->iseq_encoded; + rb_iseq_t *iseq_mut = (rb_iseq_t*)iseq; VM_ASSERT(ISEQ_EXECUTABLE_P(iseq)); + ASSERT_vm_locking_with_barrier(); for (pc=0; pciseq_size;) { const struct iseq_insn_info_entry *entry = get_insn_info(iseq, pc); @@ -4003,11 +4022,9 @@ iseq_add_local_tracepoint(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, } if (n > 0) { - if (iseq->aux.exec.local_hooks == NULL) { - ((rb_iseq_t *)iseq)->aux.exec.local_hooks = RB_ZALLOC(rb_hook_list_t); - iseq->aux.exec.local_hooks->is_local = true; - } - rb_hook_list_connect_tracepoint((VALUE)iseq, iseq->aux.exec.local_hooks, tpval, target_line); + rb_hook_list_t *hook_list = rb_iseq_local_hooks(iseq, r, true); + rb_hook_list_connect_local_tracepoint(hook_list, tpval, target_line); + iseq_mut->aux.exec.local_hooks_cnt++; } return n; @@ -4018,19 +4035,21 @@ struct trace_set_local_events_struct { VALUE tpval; unsigned int target_line; int n; + rb_ractor_t *r; }; static void iseq_add_local_tracepoint_i(const rb_iseq_t *iseq, void *p) { struct trace_set_local_events_struct *data = (struct trace_set_local_events_struct *)p; - data->n += iseq_add_local_tracepoint(iseq, data->turnon_events, data->tpval, data->target_line); + data->n += iseq_add_local_tracepoint(iseq, data->turnon_events, data->tpval, data->target_line, data->r); iseq_iterate_children(iseq, iseq_add_local_tracepoint_i, p); } int rb_iseq_add_local_tracepoint_recursively(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line, bool target_bmethod) { + ASSERT_vm_locking_with_barrier(); struct trace_set_local_events_struct data; if (target_bmethod) { turnon_events = add_bmethod_events(turnon_events); @@ -4039,35 +4058,52 @@ rb_iseq_add_local_tracepoint_recursively(const rb_iseq_t *iseq, rb_event_flag_t data.tpval = tpval; data.target_line = target_line; data.n = 0; + data.r = GET_RACTOR(); iseq_add_local_tracepoint_i(iseq, (void *)&data); - if (0) rb_funcall(Qnil, rb_intern("puts"), 1, rb_iseq_disasm(iseq)); /* for debug */ + if (0) fprintf(stderr, "Iseq disasm:\n:%s", RSTRING_PTR(rb_iseq_disasm(iseq))); /* for debug */ return data.n; } static int -iseq_remove_local_tracepoint(const rb_iseq_t *iseq, VALUE tpval) +iseq_remove_local_tracepoint(const rb_iseq_t *iseq, VALUE tpval, rb_ractor_t *r) { int n = 0; + unsigned int num_hooks_left; + unsigned int pc; + const struct rb_iseq_constant_body *body; + rb_iseq_t *iseq_mut = (rb_iseq_t*)iseq; + rb_hook_list_t *hook_list; + VALUE *iseq_encoded; + ASSERT_vm_locking_with_barrier(); - if (iseq->aux.exec.local_hooks) { - unsigned int pc; - const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); - VALUE *iseq_encoded = (VALUE *)body->iseq_encoded; + hook_list = rb_iseq_local_hooks(iseq, r, false); + + if (hook_list) { rb_event_flag_t local_events = 0; - rb_hook_list_remove_tracepoint(iseq->aux.exec.local_hooks, tpval); - local_events = iseq->aux.exec.local_hooks->events; + rb_event_flag_t prev_events = hook_list->events; + if (rb_hook_list_remove_local_tracepoint(hook_list, tpval)) { + RUBY_ASSERT(iseq->aux.exec.local_hooks_cnt > 0); + iseq_mut->aux.exec.local_hooks_cnt--; + local_events = hook_list->events; // remaining events for this ractor + num_hooks_left = rb_hook_list_count(hook_list); + if (local_events == 0 && prev_events != 0) { + st_delete(rb_ractor_targeted_hooks(r), (st_data_t*)&iseq, NULL); + rb_hook_list_free(hook_list); + } - if (local_events == 0) { - rb_hook_list_free(iseq->aux.exec.local_hooks); - ((rb_iseq_t *)iseq)->aux.exec.local_hooks = NULL; - } + if (iseq->aux.exec.local_hooks_cnt == num_hooks_left) { + body = ISEQ_BODY(iseq); + iseq_encoded = (VALUE *)body->iseq_encoded; + local_events = add_bmethod_events(local_events); + for (pc = 0; pciseq_size;) { + rb_event_flag_t pc_events = rb_iseq_event_flags(iseq, pc); + pc += encoded_iseq_trace_instrument(&iseq_encoded[pc], pc_events & (local_events | iseq->aux.exec.global_trace_events), false); + } + } - local_events = add_bmethod_events(local_events); - for (pc = 0; pciseq_size;) { - rb_event_flag_t pc_events = rb_iseq_event_flags(iseq, pc); - pc += encoded_iseq_trace_instrument(&iseq_encoded[pc], pc_events & (local_events | iseq->aux.exec.global_trace_events), false); + n++; } } return n; @@ -4076,22 +4112,25 @@ iseq_remove_local_tracepoint(const rb_iseq_t *iseq, VALUE tpval) struct trace_clear_local_events_struct { VALUE tpval; int n; + rb_ractor_t *r; }; static void iseq_remove_local_tracepoint_i(const rb_iseq_t *iseq, void *p) { struct trace_clear_local_events_struct *data = (struct trace_clear_local_events_struct *)p; - data->n += iseq_remove_local_tracepoint(iseq, data->tpval); + data->n += iseq_remove_local_tracepoint(iseq, data->tpval, data->r); iseq_iterate_children(iseq, iseq_remove_local_tracepoint_i, p); } int -rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval) +rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval, rb_ractor_t *r) { struct trace_clear_local_events_struct data; + ASSERT_vm_locking_with_barrier(); data.tpval = tpval; data.n = 0; + data.r = r; iseq_remove_local_tracepoint_i(iseq, (void *)&data); return data.n; @@ -4109,11 +4148,14 @@ rb_iseq_trace_set(const rb_iseq_t *iseq, rb_event_flag_t turnon_events) return; } else { + // NOTE: this does not need VM barrier if it's a new ISEQ unsigned int pc; const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); + VALUE *iseq_encoded = (VALUE *)body->iseq_encoded; rb_event_flag_t enabled_events; - rb_event_flag_t local_events = iseq->aux.exec.local_hooks ? iseq->aux.exec.local_hooks->events : 0; + rb_hook_list_t *local_hooks = rb_iseq_local_hooks(iseq, GET_RACTOR(), false); + rb_event_flag_t local_events = local_hooks ? local_hooks->events : 0; ((rb_iseq_t *)iseq)->aux.exec.global_trace_events = turnon_events; enabled_events = add_bmethod_events(turnon_events | local_events); @@ -4129,6 +4171,7 @@ void rb_vm_cc_general(const struct rb_callcache *cc); static bool clear_attr_cc(VALUE v) { + ASSERT_vm_locking_with_barrier(); if (imemo_type_p(v, imemo_callcache) && vm_cc_ivar_p((const struct rb_callcache *)v)) { rb_vm_cc_general((struct rb_callcache *)v); return true; @@ -4141,6 +4184,7 @@ clear_attr_cc(VALUE v) static bool clear_bf_cc(VALUE v) { + ASSERT_vm_locking_with_barrier(); if (imemo_type_p(v, imemo_callcache) && vm_cc_bf_p((const struct rb_callcache *)v)) { rb_vm_cc_general((struct rb_callcache *)v); return true; @@ -4166,7 +4210,10 @@ clear_attr_ccs_i(void *vstart, void *vend, size_t stride, void *data) void rb_clear_attr_ccs(void) { - rb_objspace_each_objects(clear_attr_ccs_i, NULL); + RB_VM_LOCKING() { + rb_vm_barrier(); + rb_objspace_each_objects(clear_attr_ccs_i, NULL); + } } static int @@ -4185,6 +4232,7 @@ clear_bf_ccs_i(void *vstart, void *vend, size_t stride, void *data) void rb_clear_bf_ccs(void) { + ASSERT_vm_locking_with_barrier(); rb_objspace_each_objects(clear_bf_ccs_i, NULL); } @@ -4214,7 +4262,10 @@ trace_set_i(void *vstart, void *vend, size_t stride, void *data) void rb_iseq_trace_set_all(rb_event_flag_t turnon_events) { - rb_objspace_each_objects(trace_set_i, &turnon_events); + RB_VM_LOCKING() { + rb_vm_barrier(); + rb_objspace_each_objects(trace_set_i, &turnon_events); + } } VALUE diff --git a/iseq.h b/iseq.h index 86063d8be28136..fbb8180a496662 100644 --- a/iseq.h +++ b/iseq.h @@ -190,10 +190,12 @@ const rb_iseq_t *rb_iseq_ibf_load_bytes(const char *cstr, size_t); VALUE rb_iseq_ibf_load_extra_data(VALUE str); void rb_iseq_init_trace(rb_iseq_t *iseq); int rb_iseq_add_local_tracepoint_recursively(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line, bool target_bmethod); -int rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval); +int rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval, rb_ractor_t *r); const rb_iseq_t *rb_iseq_load_iseq(VALUE fname); const rb_iseq_t *rb_iseq_compile_iseq(VALUE str, VALUE fname); int rb_iseq_opt_frozen_string_literal(void); +rb_hook_list_t *rb_iseq_local_hooks(const rb_iseq_t *iseq, rb_ractor_t *r, bool create); + #if VM_INSN_INFO_TABLE_IMPL == 2 unsigned int *rb_iseq_insns_info_decode_positions(const struct rb_iseq_constant_body *body); diff --git a/method.h b/method.h index b174c6fccb4d94..260344d53b58ba 100644 --- a/method.h +++ b/method.h @@ -166,8 +166,8 @@ typedef struct rb_method_refined_struct { typedef struct rb_method_bmethod_struct { VALUE proc; /* should be marked */ - struct rb_hook_list_struct *hooks; rb_serial_t defined_ractor_id; + unsigned int local_hooks_cnt; } rb_method_bmethod_t; enum method_optimized_type { @@ -208,6 +208,8 @@ struct rb_method_definition_struct { }; struct rb_id_table; +struct rb_ractor_struct; +struct rb_hook_list_struct; typedef struct rb_method_definition_struct rb_method_definition_t; STATIC_ASSERT(sizeof_method_def, offsetof(rb_method_definition_t, body) <= 8); @@ -267,5 +269,8 @@ void rb_vm_delete_cc_refinement(const struct rb_callcache *cc); void rb_clear_method_cache(VALUE klass_or_module, ID mid); void rb_clear_all_refinement_method_cache(void); void rb_invalidate_method_caches(struct rb_id_table *cm_tbl, VALUE cc_tbl); +struct rb_hook_list_struct *rb_method_def_local_hooks(rb_method_definition_t *def, struct rb_ractor_struct *cr, bool create); +void rb_method_definition_addref(rb_method_definition_t *def); +void rb_method_definition_release(rb_method_definition_t *def); #endif /* RUBY_METHOD_H */ diff --git a/ractor.c b/ractor.c index 5c98196c912fa2..a95468880708d0 100644 --- a/ractor.c +++ b/ractor.c @@ -207,6 +207,24 @@ static void ractor_sync_free(rb_ractor_t *r); static size_t ractor_sync_memsize(const rb_ractor_t *r); static void ractor_sync_init(rb_ractor_t *r); +static int +mark_targeted_hook_list(st_data_t key, st_data_t value, st_data_t _arg) +{ + rb_hook_list_t *hook_list = (rb_hook_list_t*)value; + + if (hook_list->type == hook_list_type_targeted_iseq) { + rb_gc_mark((VALUE)key); + } + else { + rb_method_definition_t *def = (rb_method_definition_t*)key; + RUBY_ASSERT(hook_list->type == hook_list_type_targeted_def); + rb_gc_mark(def->body.bmethod.proc); + } + rb_hook_list_mark(hook_list); + + return ST_CONTINUE; +} + static void ractor_mark(void *ptr) { @@ -228,6 +246,9 @@ ractor_mark(void *ptr) ractor_sync_mark(r); rb_hook_list_mark(&r->pub.hooks); + if (r->pub.targeted_hooks) { + st_foreach(r->pub.targeted_hooks, mark_targeted_hook_list, 0); + } if (r->threads.cnt > 0) { rb_thread_t *th = 0; @@ -241,17 +262,33 @@ ractor_mark(void *ptr) } } +static int +free_targeted_hook_lists(st_data_t key, st_data_t val, st_data_t _arg) +{ + rb_hook_list_t *hook_list = (rb_hook_list_t*)val; + rb_hook_list_free(hook_list); + return ST_DELETE; +} + +static void +free_targeted_hooks(st_table *hooks_tbl) +{ + st_foreach(hooks_tbl, free_targeted_hook_lists, 0); +} + static void ractor_free(void *ptr) { rb_ractor_t *r = (rb_ractor_t *)ptr; RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r)); + free_targeted_hooks(r->pub.targeted_hooks); rb_native_mutex_destroy(&r->sync.lock); #ifdef RUBY_THREAD_WIN32_H rb_native_cond_destroy(&r->sync.wakeup_cond); #endif ractor_local_storage_free(r); rb_hook_list_free(&r->pub.hooks); + st_free_table(r->pub.targeted_hooks); if (r->newobj_cache) { RUBY_ASSERT(r == ruby_single_main_ractor); @@ -489,6 +526,8 @@ static void ractor_init(rb_ractor_t *r, VALUE name, VALUE loc) { ractor_sync_init(r); + r->pub.targeted_hooks = st_init_numtable(); + r->pub.hooks.type = hook_list_type_ractor_local; // thread management rb_thread_sched_init(&r->threads.sched, false); @@ -1136,6 +1175,12 @@ rb_ractor_hooks(rb_ractor_t *cr) return &cr->pub.hooks; } +st_table * +rb_ractor_targeted_hooks(rb_ractor_t *cr) +{ + return cr->pub.targeted_hooks; +} + static void rb_obj_set_shareable_no_assert(VALUE obj) { diff --git a/ractor_core.h b/ractor_core.h index 96d22bea29eade..d112ff87244944 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -144,6 +144,7 @@ VALUE rb_ractor_require(VALUE feature, bool silent); VALUE rb_ractor_autoload_load(VALUE space, ID id); VALUE rb_ractor_ensure_shareable(VALUE obj, VALUE name); +st_table *rb_ractor_targeted_hooks(rb_ractor_t *cr); RUBY_SYMBOL_EXPORT_BEGIN void rb_ractor_finish_marking(void); @@ -250,6 +251,25 @@ rb_ractor_id(const rb_ractor_t *r) return r->pub.id; } +static inline void +rb_ractor_targeted_hooks_incr(rb_ractor_t *cr) +{ + cr->pub.targeted_hooks_cnt++; +} + +static inline void +rb_ractor_targeted_hooks_decr(rb_ractor_t *cr) +{ + RUBY_ASSERT(cr->pub.targeted_hooks_cnt > 0); + cr->pub.targeted_hooks_cnt--; +} + +static inline unsigned int +rb_ractor_targeted_hooks_cnt(rb_ractor_t *cr) +{ + return cr->pub.targeted_hooks_cnt; +} + #if RACTOR_CHECK_MODE > 0 # define RACTOR_BELONGING_ID(obj) (*(uint32_t *)(((uintptr_t)(obj)) + rb_gc_obj_slot_size(obj))) diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index ccf24521694484..776534a2b54770 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -2957,4 +2957,210 @@ def test_tracepoint_thread_end_with_exception assert_kind_of(Thread, target_thread) end + + def test_tracepoint_garbage_collected_when_disable + before_count_stat = 0 + before_count_objspace = 0 + TracePoint.stat.each do + before_count_stat += 1 + end + ObjectSpace.each_object(TracePoint) do + before_count_objspace += 1 + end + tp = TracePoint.new(:c_call, :c_return) do + end + tp.enable + Class.inspect # c_call, c_return invoked + tp.disable + tp_id = tp.object_id + tp = nil + + gc_times = 0 + gc_max_retries = 10 + EnvUtil.suppress_warning do + until (ObjectSpace._id2ref(tp_id) rescue nil).nil? + GC.start + gc_times += 1 + if gc_times == gc_max_retries + break + end + end + end + return if gc_times == gc_max_retries + + after_count_stat = 0 + TracePoint.stat.each do |v| + after_count_stat += 1 + end + assert after_count_stat <= before_count_stat + after_count_objspace = 0 + ObjectSpace.each_object(TracePoint) do + after_count_objspace += 1 + end + assert after_count_objspace <= before_count_objspace + end + + def test_tp_ractor_local_untargeted + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + r = Ractor.new do + results = [] + tp = TracePoint.new(:line) { |tp| results << tp.path } + tp.enable + Ractor.main << :continue + Ractor.receive + tp.disable + results + end + outer_results = [] + outer_tp = TracePoint.new(:line) { |tp| outer_results << tp.path } + outer_tp.enable + Ractor.receive + GC.start # so I can check path + r << :continue + inner_results = r.value + outer_tp.disable + assert_equal 1, outer_results.select { |path| path.match?(/internal:gc/) }.size + assert_equal 0, inner_results.select { |path| path.match?(/internal:gc/) }.size + end; + end + + def test_tp_targeted_ractor_local_bmethod + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + mname = :foo + prok = Ractor.shareable_proc do + end + klass = EnvUtil.labeled_class(:Klass) do + define_method(mname, &prok) + end + outer_results = 0 + _outer_tp = TracePoint.new(:call) do + outer_results += 1 + end # not enabled + rs = 10.times.map do + Ractor.new(mname, klass) do |mname, klass0| + inner_results = 0 + tp = TracePoint.new(:call) { |tp| inner_results += 1 } + target = klass0.instance_method(mname) + tp.enable(target: target) + obj = klass0.new + 10.times { obj.send(mname) } + tp.disable + inner_results + end + end + inner_results = rs.map(&:value).sum + obj = klass.new + 10.times { obj.send(mname) } + assert_equal 100, inner_results + assert_equal 0, outer_results + end; + end + + def test_tp_targeted_ractor_local_method + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def foo + end + outer_results = 0 + _outer_tp = TracePoint.new(:call) do + outer_results += 1 + end # not enabled + + rs = 10.times.map do + Ractor.new do + inner_results = 0 + tp = TracePoint.new(:call) do + inner_results += 1 + end + tp.enable(target: method(:foo)) + 10.times { foo } + tp.disable + inner_results + end + end + + inner_results = rs.map(&:value).sum + 10.times { foo } + assert_equal 100, inner_results + assert_equal 0, outer_results + end; + end + + def test_tracepoints_not_disabled_by_ractor_gc + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $-w = nil # uses ObjectSpace._id2ref + def hi = "hi" + greetings = 0 + tp_target = TracePoint.new(:call) do |tp| + greetings += 1 + end + tp_target.enable(target: method(:hi)) + + raises = 0 + tp_global = TracePoint.new(:raise) do |tp| + raises += 1 + end + tp_global.enable + + r = Ractor.new { 10 } + r.join + ractor_id = r.object_id + r = nil # allow gc for ractor + gc_max_retries = 15 + gc_times = 0 + # force GC of ractor (or try, because we have a conservative GC) + until (ObjectSpace._id2ref(ractor_id) rescue nil).nil? + GC.start + gc_times += 1 + if gc_times == gc_max_retries + break + end + end + + # tracepoints should still be enabled after GC of `r` + 5.times { + hi + } + 6.times { + raise "uh oh" rescue nil + } + tp_target.disable + tp_global.disable + assert_equal 5, greetings + if gc_times == gc_max_retries # _id2ref never raised + assert_equal 6, raises + else + assert_equal 7, raises + end + end; + end + + def test_lots_of_enabled_tracepoints_ractor_gc + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def foo; end + sum = 8.times.map do + Ractor.new do + called = 0 + TracePoint.new(:call) do |tp| + next if tp.callee_id != :foo + called += 1 + end.enable + 200.times do + TracePoint.new(:line) { + # all these allocations shouldn't GC these tracepoints while the ractor is alive. + Object.new + }.enable + end + 100.times { foo } + called + end + end.map(&:value).sum + assert_equal 800, sum + 4.times { GC.start } # Now the tracepoints can be GC'd because the ractors can be GC'd + end; + end end diff --git a/trace_point.rb b/trace_point.rb index 682398ec3fe263..7e8af21a68706f 100644 --- a/trace_point.rb +++ b/trace_point.rb @@ -26,7 +26,7 @@ # change. Instead, it is recommended to specify the types of events you # want to use. # -# To filter what is traced, you can pass any of the following as +events+: +# To filter what is traced, you can pass any number of the following as +events+: # # +:line+:: Execute an expression or statement on a new line. # +:class+:: Start a class or module definition. @@ -74,7 +74,7 @@ class TracePoint # # A block must be given; otherwise, an ArgumentError is raised. # - # If the trace method isn't included in the given events filter, a + # If the trace method isn't supported for the given event(s) filter, a # RuntimeError is raised. # # TracePoint.trace(:line) do |tp| @@ -89,7 +89,9 @@ class TracePoint # end # $tp.lineno #=> access from outside (RuntimeError) # - # Access from other threads is also forbidden. + # Access from other ractors, threads or fibers is forbidden. TracePoints are active + # per-ractor so if you enable a TracePoint in one ractor, other ractors will not be + # affected. # def self.new(*events) Primitive.attr! :use_block diff --git a/vm.c b/vm.c index 99f96ff7a0dbc2..27cb5ae25825e0 100644 --- a/vm.c +++ b/vm.c @@ -718,9 +718,10 @@ rb_current_ec_noinline(void) #endif -rb_event_flag_t ruby_vm_event_flags; -rb_event_flag_t ruby_vm_event_enabled_global_flags; -unsigned int ruby_vm_event_local_num; +rb_event_flag_t ruby_vm_event_flags = 0; +rb_event_flag_t ruby_vm_event_enabled_global_flags = 0; +unsigned int ruby_vm_c_events_enabled = 0; +unsigned int ruby_vm_iseq_events_enabled = 0; rb_serial_t ruby_vm_constant_cache_invalidations = 0; rb_serial_t ruby_vm_constant_cache_misses = 0; @@ -2579,7 +2580,11 @@ hook_before_rewind(rb_execution_context_t *ec, bool cfp_returning_with_value, in } else { const rb_iseq_t *iseq = ec->cfp->iseq; - rb_hook_list_t *local_hooks = iseq->aux.exec.local_hooks; + rb_hook_list_t *local_hooks = NULL; + unsigned int local_hooks_cnt = iseq->aux.exec.local_hooks_cnt; + if (RB_UNLIKELY(local_hooks_cnt > 0)) { + local_hooks = rb_iseq_local_hooks(iseq, rb_ec_ractor_ptr(ec), false); + } switch (VM_FRAME_TYPE(ec->cfp)) { case VM_FRAME_MAGIC_METHOD: @@ -2617,15 +2622,18 @@ hook_before_rewind(rb_execution_context_t *ec, bool cfp_returning_with_value, in bmethod_return_value); VM_ASSERT(me->def->type == VM_METHOD_TYPE_BMETHOD); - local_hooks = me->def->body.bmethod.hooks; - - if (UNLIKELY(local_hooks && local_hooks->events & RUBY_EVENT_RETURN)) { - rb_exec_event_hook_orig(ec, local_hooks, RUBY_EVENT_RETURN, ec->cfp->self, - rb_vm_frame_method_entry(ec->cfp)->def->original_id, - rb_vm_frame_method_entry(ec->cfp)->called_id, - rb_vm_frame_method_entry(ec->cfp)->owner, - bmethod_return_value, TRUE); + unsigned int local_hooks_cnt = me->def->body.bmethod.local_hooks_cnt; + if (UNLIKELY(local_hooks_cnt > 0)) { + local_hooks = rb_method_def_local_hooks(me->def, rb_ec_ractor_ptr(ec), false); + if (local_hooks && local_hooks->events & RUBY_EVENT_RETURN) { + rb_exec_event_hook_orig(ec, local_hooks, RUBY_EVENT_RETURN, ec->cfp->self, + rb_vm_frame_method_entry(ec->cfp)->def->original_id, + rb_vm_frame_method_entry(ec->cfp)->called_id, + rb_vm_frame_method_entry(ec->cfp)->owner, + bmethod_return_value, TRUE); + } } + THROW_DATA_CONSUMED_SET(err); } else { @@ -4580,6 +4588,7 @@ Init_BareVM(void) vm->overloaded_cme_table = st_init_numtable(); vm->constant_cache = rb_id_table_create(0); vm->unused_block_warning_table = set_init_numtable(); + vm->global_hooks.type = hook_list_type_global; // setup main thread th->nt = ZALLOC(struct rb_native_thread); diff --git a/vm_core.h b/vm_core.h index c716e001a5f542..839c054ab399f5 100644 --- a/vm_core.h +++ b/vm_core.h @@ -584,7 +584,7 @@ struct rb_iseq_struct { } loader; struct { - struct rb_hook_list_struct *local_hooks; + unsigned int local_hooks_cnt; rb_event_flag_t global_trace_events; } exec; } aux; @@ -650,15 +650,21 @@ void *rb_objspace_alloc(void); void rb_objspace_free(void *objspace); void rb_objspace_call_finalizer(void); +enum rb_hook_list_type { + hook_list_type_ractor_local, + hook_list_type_targeted_iseq, + hook_list_type_targeted_def, // C function + hook_list_type_global +}; + typedef struct rb_hook_list_struct { struct rb_event_hook_struct *hooks; rb_event_flag_t events; unsigned int running; + enum rb_hook_list_type type; bool need_clean; - bool is_local; } rb_hook_list_t; - // see builtin.h for definition typedef const struct rb_builtin_function *RB_BUILTIN; @@ -2029,8 +2035,9 @@ rb_execution_context_t *rb_vm_main_ractor_ec(rb_vm_t *vm); // ractor.c RUBY_EXTERN struct rb_ractor_struct *ruby_single_main_ractor; // ractor.c RUBY_EXTERN rb_vm_t *ruby_current_vm_ptr; RUBY_EXTERN rb_event_flag_t ruby_vm_event_flags; -RUBY_EXTERN rb_event_flag_t ruby_vm_event_enabled_global_flags; -RUBY_EXTERN unsigned int ruby_vm_event_local_num; +RUBY_EXTERN rb_event_flag_t ruby_vm_event_enabled_global_flags; // only ever added to +RUBY_EXTERN unsigned int ruby_vm_iseq_events_enabled; +RUBY_EXTERN unsigned int ruby_vm_c_events_enabled; #define GET_VM() rb_current_vm() #define GET_RACTOR() rb_current_ractor() @@ -2272,8 +2279,9 @@ struct rb_trace_arg_struct { void rb_hook_list_mark(rb_hook_list_t *hooks); void rb_hook_list_mark_and_move(rb_hook_list_t *hooks); void rb_hook_list_free(rb_hook_list_t *hooks); -void rb_hook_list_connect_tracepoint(VALUE target, rb_hook_list_t *list, VALUE tpval, unsigned int target_line); -void rb_hook_list_remove_tracepoint(rb_hook_list_t *list, VALUE tpval); +void rb_hook_list_connect_local_tracepoint(rb_hook_list_t *list, VALUE tpval, unsigned int target_line); +bool rb_hook_list_remove_local_tracepoint(rb_hook_list_t *list, VALUE tpval); +unsigned int rb_hook_list_count(rb_hook_list_t *list); void rb_exec_event_hooks(struct rb_trace_arg_struct *trace_arg, rb_hook_list_t *hooks, int pop_p); @@ -2312,6 +2320,8 @@ struct rb_ractor_pub { VALUE self; uint32_t id; rb_hook_list_t hooks; + st_table *targeted_hooks; // also called "local hooks". {ISEQ => hook_list, def => hook_list...} + unsigned int targeted_hooks_cnt; // ex: tp.enabled(target: method(:puts)) }; static inline rb_hook_list_t * diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 8c1992de43d495..d103146d1f601f 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -3224,8 +3224,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, VM_ASSERT(cc == calling->cc); if (vm_call_iseq_optimizable_p(ci, cc)) { - if ((iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) && - !(ruby_vm_event_flags & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN))) { + if ((iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) && ruby_vm_c_events_enabled == 0) { VM_ASSERT(iseq->body->builtin_attrs & BUILTIN_ATTR_LEAF); vm_cc_bf_set(cc, (void *)iseq->body->iseq_encoded[1]); CC_SET_FASTPATH(cc, vm_call_single_noarg_leaf_builtin, true); @@ -4809,7 +4808,7 @@ NOINLINE(static VALUE vm_call_optimized(rb_execution_context_t *ec, rb_control_f const struct rb_callinfo *ci, const struct rb_callcache *cc)); #define VM_CALL_METHOD_ATTR(var, func, nohook) \ - if (UNLIKELY(ruby_vm_event_flags & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN))) { \ + if (UNLIKELY(ruby_vm_c_events_enabled > 0)) { \ EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_CALL, calling->recv, vm_cc_cme(cc)->def->original_id, \ vm_ci_mid(ci), vm_cc_cme(cc)->owner, Qundef); \ var = func; \ @@ -7193,13 +7192,15 @@ NOINLINE(static void vm_trace(rb_execution_context_t *ec, rb_control_frame_t *re static inline void vm_trace_hook(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE *pc, rb_event_flag_t pc_events, rb_event_flag_t target_event, - rb_hook_list_t *global_hooks, rb_hook_list_t *const *local_hooks_ptr, VALUE val) + rb_hook_list_t *global_hooks, rb_hook_list_t *local_hooks, VALUE val) { rb_event_flag_t event = pc_events & target_event; VALUE self = GET_SELF(); VM_ASSERT(rb_popcount64((uint64_t)event) == 1); + if (local_hooks) local_hooks->running++; // make sure they don't get deleted while global hooks run + if (event & global_hooks->events) { /* increment PC because source line is calculated with PC-1 */ reg_cfp->pc++; @@ -7208,8 +7209,7 @@ vm_trace_hook(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VAL reg_cfp->pc--; } - // Load here since global hook above can add and free local hooks - rb_hook_list_t *local_hooks = *local_hooks_ptr; + if (local_hooks) local_hooks->running--; if (local_hooks != NULL) { if (event & local_hooks->events) { /* increment PC because source line is calculated with PC-1 */ @@ -7222,7 +7222,7 @@ vm_trace_hook(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VAL #define VM_TRACE_HOOK(target_event, val) do { \ if ((pc_events & (target_event)) & enabled_flags) { \ - vm_trace_hook(ec, reg_cfp, pc, pc_events, (target_event), global_hooks, local_hooks_ptr, (val)); \ + vm_trace_hook(ec, reg_cfp, pc, pc_events, (target_event), global_hooks, local_hooks, (val)); \ } \ } while (0) @@ -7238,22 +7238,28 @@ static void vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) { const VALUE *pc = reg_cfp->pc; - rb_event_flag_t enabled_flags = ruby_vm_event_flags & ISEQ_TRACE_EVENTS; - rb_event_flag_t global_events = enabled_flags; + rb_ractor_t *r = rb_ec_ractor_ptr(ec); + rb_event_flag_t enabled_flags = r->pub.hooks.events & ISEQ_TRACE_EVENTS; + rb_event_flag_t ractor_events = enabled_flags; - if (enabled_flags == 0 && ruby_vm_event_local_num == 0) { + if (enabled_flags == 0 && rb_ractor_targeted_hooks_cnt(r) == 0) { return; } else { const rb_iseq_t *iseq = reg_cfp->iseq; - VALUE iseq_val = (VALUE)iseq; size_t pos = pc - ISEQ_BODY(iseq)->iseq_encoded; rb_event_flag_t pc_events = rb_iseq_event_flags(iseq, pos); - rb_hook_list_t *local_hooks = iseq->aux.exec.local_hooks; - rb_hook_list_t *const *local_hooks_ptr = &iseq->aux.exec.local_hooks; + unsigned int local_hooks_cnt = iseq->aux.exec.local_hooks_cnt; + rb_hook_list_t *local_hooks = NULL; + if (RB_UNLIKELY(local_hooks_cnt > 0)) { + st_data_t val; + if (st_lookup(rb_ractor_targeted_hooks(r), (st_data_t)iseq, &val)) { + local_hooks = (rb_hook_list_t*)val; + } + } rb_event_flag_t iseq_local_events = local_hooks != NULL ? local_hooks->events : 0; + rb_hook_list_t *bmethod_local_hooks = NULL; - rb_hook_list_t **bmethod_local_hooks_ptr = NULL; rb_event_flag_t bmethod_local_events = 0; const bool bmethod_frame = VM_FRAME_BMETHOD_P(reg_cfp); enabled_flags |= iseq_local_events; @@ -7263,14 +7269,18 @@ vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) if (bmethod_frame) { const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(reg_cfp); VM_ASSERT(me->def->type == VM_METHOD_TYPE_BMETHOD); - bmethod_local_hooks = me->def->body.bmethod.hooks; - bmethod_local_hooks_ptr = &me->def->body.bmethod.hooks; - if (bmethod_local_hooks) { - bmethod_local_events = bmethod_local_hooks->events; + unsigned int bmethod_hooks_cnt = me->def->body.bmethod.local_hooks_cnt; + if (RB_UNLIKELY(bmethod_hooks_cnt > 0)) { + st_data_t val; + if (st_lookup(rb_ractor_targeted_hooks(r), (st_data_t)me->def, &val)) { + bmethod_local_hooks = (rb_hook_list_t*)val; + } + if (bmethod_local_hooks) { + bmethod_local_events = bmethod_local_hooks->events; + } } } - if ((pc_events & enabled_flags) == 0 && !bmethod_frame) { #if 0 /* disable trace */ @@ -7291,7 +7301,7 @@ vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) rb_hook_list_t *global_hooks = rb_ec_ractor_hooks(ec); /* Note, not considering iseq local events here since the same * iseq could be used in multiple bmethods. */ - rb_event_flag_t bmethod_events = global_events | bmethod_local_events; + rb_event_flag_t bmethod_events = ractor_events | bmethod_local_events; if (0) { ruby_debug_printf("vm_trace>>%4d (%4x) - %s:%d %s\n", @@ -7307,7 +7317,7 @@ vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) /* check traces */ if ((pc_events & RUBY_EVENT_B_CALL) && bmethod_frame && (bmethod_events & RUBY_EVENT_CALL)) { /* b_call instruction running as a method. Fire call event. */ - vm_trace_hook(ec, reg_cfp, pc, RUBY_EVENT_CALL, RUBY_EVENT_CALL, global_hooks, bmethod_local_hooks_ptr, Qundef); + vm_trace_hook(ec, reg_cfp, pc, RUBY_EVENT_CALL, RUBY_EVENT_CALL, global_hooks, bmethod_local_hooks, Qundef); } VM_TRACE_HOOK(RUBY_EVENT_CLASS | RUBY_EVENT_CALL | RUBY_EVENT_B_CALL, Qundef); VM_TRACE_HOOK(RUBY_EVENT_RESCUE, rescue_errinfo(ec, reg_cfp)); @@ -7317,15 +7327,8 @@ vm_trace(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp) VM_TRACE_HOOK(RUBY_EVENT_END | RUBY_EVENT_RETURN | RUBY_EVENT_B_RETURN, TOPN(0)); if ((pc_events & RUBY_EVENT_B_RETURN) && bmethod_frame && (bmethod_events & RUBY_EVENT_RETURN)) { /* b_return instruction running as a method. Fire return event. */ - vm_trace_hook(ec, reg_cfp, pc, RUBY_EVENT_RETURN, RUBY_EVENT_RETURN, global_hooks, bmethod_local_hooks_ptr, TOPN(0)); + vm_trace_hook(ec, reg_cfp, pc, RUBY_EVENT_RETURN, RUBY_EVENT_RETURN, global_hooks, bmethod_local_hooks, TOPN(0)); } - - // Pin the iseq since `local_hooks_ptr` points inside the iseq's slot on the GC heap. - // We need the pointer to stay valid in case compaction happens in a trace hook. - // - // Similar treatment is unnecessary for `bmethod_local_hooks_ptr` since - // storage for `rb_method_definition_t` is not on the GC heap. - RB_GC_GUARD(iseq_val); } } } diff --git a/vm_method.c b/vm_method.c index 17f68fc258ad16..9f569df7fa6e51 100644 --- a/vm_method.c +++ b/vm_method.c @@ -826,7 +826,7 @@ rb_add_method_optimized(VALUE klass, ID mid, enum method_optimized_type opt_type } static void -rb_method_definition_release(rb_method_definition_t *def) +method_definition_release(rb_method_definition_t *def) { if (def != NULL) { const unsigned int reference_count_was = RUBY_ATOMIC_FETCH_SUB(def->reference_count, 1); @@ -836,9 +836,6 @@ rb_method_definition_release(rb_method_definition_t *def) if (reference_count_was == 1) { if (METHOD_DEBUG) fprintf(stderr, "-%p-%s:1->0 (remove)\n", (void *)def, rb_id2name(def->original_id)); - if (def->type == VM_METHOD_TYPE_BMETHOD && def->body.bmethod.hooks) { - xfree(def->body.bmethod.hooks); - } xfree(def); } else { @@ -848,6 +845,12 @@ rb_method_definition_release(rb_method_definition_t *def) } } +void +rb_method_definition_release(rb_method_definition_t *def) +{ + method_definition_release(def); +} + static void delete_overloaded_cme(const rb_callable_method_entry_t *cme); void @@ -872,7 +875,7 @@ rb_free_method_entry(const rb_method_entry_t *me) // to remove from `Invariants` here. #endif - rb_method_definition_release(me->def); + method_definition_release(me->def); } static inline rb_method_entry_t *search_method(VALUE klass, ID id, VALUE *defined_class_ptr); @@ -939,6 +942,7 @@ setup_method_cfunc_struct(rb_method_cfunc_t *cfunc, VALUE (*func)(ANYARGS), int cfunc->invoker = call_cfunc_invoker_func(argc); } + static rb_method_definition_t * method_definition_addref(rb_method_definition_t *def, bool complemented) { @@ -952,10 +956,16 @@ method_definition_addref(rb_method_definition_t *def, bool complemented) return def; } +void +rb_method_definition_addref(rb_method_definition_t *def) +{ + method_definition_addref(def, false); +} + void rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *def, void *opts) { - rb_method_definition_release(me->def); + method_definition_release(me->def); *(rb_method_definition_t **)&me->def = method_definition_addref(def, METHOD_ENTRY_COMPLEMENTED(me)); if (!ruby_running) add_opt_method_entry(me); @@ -1060,8 +1070,6 @@ method_definition_reset(const rb_method_entry_t *me) break; case VM_METHOD_TYPE_BMETHOD: RB_OBJ_WRITTEN(me, Qundef, def->body.bmethod.proc); - /* give up to check all in a list */ - if (def->body.bmethod.hooks) rb_gc_writebarrier_remember((VALUE)me); break; case VM_METHOD_TYPE_REFINED: RB_OBJ_WRITTEN(me, Qundef, def->body.refined.orig_me); @@ -1195,7 +1203,7 @@ rb_method_entry_complement_defined_class(const rb_method_entry_t *src_me, ID cal void rb_method_entry_copy(rb_method_entry_t *dst, const rb_method_entry_t *src) { - rb_method_definition_release(dst->def); + method_definition_release(dst->def); *(rb_method_definition_t **)&dst->def = method_definition_addref(src->def, METHOD_ENTRY_COMPLEMENTED(src)); method_definition_reset(dst); dst->called_id = src->called_id; diff --git a/vm_trace.c b/vm_trace.c index b2fc436a96b210..00905412758bce 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -34,6 +34,7 @@ #include "ruby/debug.h" #include "vm_core.h" #include "ruby/ractor.h" +#include "ractor_core.h" #include "yjit.h" #include "zjit.h" @@ -103,51 +104,90 @@ rb_hook_list_free(rb_hook_list_t *hooks) void rb_clear_attr_ccs(void); void rb_clear_bf_ccs(void); -static void -update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events) +static bool iseq_trace_set_all_needed(rb_event_flag_t new_events) { rb_event_flag_t new_iseq_events = new_events & ISEQ_TRACE_EVENTS; rb_event_flag_t enabled_iseq_events = ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS; - bool first_time_iseq_events_p = new_iseq_events & ~enabled_iseq_events; - bool enable_c_call = (prev_events & RUBY_EVENT_C_CALL) == 0 && (new_events & RUBY_EVENT_C_CALL); + return new_iseq_events & ~enabled_iseq_events; + +} + +static bool clear_attr_ccs_needed(rb_event_flag_t prev_events, rb_event_flag_t new_events) +{ + bool enable_c_call = (prev_events & RUBY_EVENT_C_CALL) == 0 && (new_events & RUBY_EVENT_C_CALL); bool enable_c_return = (prev_events & RUBY_EVENT_C_RETURN) == 0 && (new_events & RUBY_EVENT_C_RETURN); - bool enable_call = (prev_events & RUBY_EVENT_CALL) == 0 && (new_events & RUBY_EVENT_CALL); - bool enable_return = (prev_events & RUBY_EVENT_RETURN) == 0 && (new_events & RUBY_EVENT_RETURN); + return enable_c_call || enable_c_return; +} + +/* If the events are internal events (e.g. gc hooks), it updates them globally for all ractors. Otherwise + * they are ractor local. You cannot listen to internal events through set_trace_func or TracePoint. + * Some ractor-local tracepoint events cause global level iseq changes, so are still called `global events`. + */ +static void +update_global_event_hooks(rb_hook_list_t *list, rb_event_flag_t prev_events, rb_event_flag_t new_events, int change_iseq_events, int change_c_events) +{ + rb_execution_context_t *ec = rb_current_execution_context(false); + unsigned int lev; + + // Can't enter VM lock during freeing of ractor hook list on MMTK, where ec == NULL. + if (ec) { + RB_VM_LOCK_ENTER_LEV(&lev); + rb_vm_barrier(); + } + + rb_event_flag_t new_iseq_events = new_events & ISEQ_TRACE_EVENTS; + rb_event_flag_t enabled_iseq_events = ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS; + bool new_iseq_events_p = iseq_trace_set_all_needed(new_events); + bool enable_call = (prev_events & RUBY_EVENT_CALL) == 0 && (new_events & RUBY_EVENT_CALL); + bool enable_return = (prev_events & RUBY_EVENT_RETURN) == 0 && (new_events & RUBY_EVENT_RETURN); + bool clear_attr_ccs_p = clear_attr_ccs_needed(prev_events, new_events); + + // FIXME: `ruby_vm_event_flags` should have the global list of event flags for internal events as well + // as for all ractors. That's not how it works right now, so we shouldn't rely on it apart from the + // internal events. Since it doesn't work like this, we have to track more state with `ruby_vm_iseq_events_enabled`, + // `ruby_vm_c_events_enabled`, etc. + rb_event_flag_t new_events_global = (ruby_vm_event_flags & ~prev_events) | new_events; + ruby_vm_event_flags = new_events_global; // Modify ISEQs or CCs to enable tracing - if (first_time_iseq_events_p) { + if (new_iseq_events_p) { // write all ISeqs only when new events are added for the first time rb_iseq_trace_set_all(new_iseq_events | enabled_iseq_events); } - // if c_call or c_return is activated - else if (enable_c_call || enable_c_return) { + else if (clear_attr_ccs_p) { // turn on C_CALL or C_RETURN ractor locally rb_clear_attr_ccs(); } - else if (enable_call || enable_return) { + else if (enable_call || enable_return) { // turn on CALL or RETURN ractor locally rb_clear_bf_ccs(); } - // FIXME: Which flags are enabled globally comes from multiple lists, one - // per-ractor and a global list. - // This incorrectly assumes the lists have mutually exclusive flags set. - // This is true for the global (objspace) events, but not for ex. multiple - // Ractors listening for the same iseq events. - rb_event_flag_t new_events_global = (ruby_vm_event_flags & ~prev_events) | new_events; - ruby_vm_event_flags = new_events_global; - ruby_vm_event_enabled_global_flags |= new_events_global; - rb_objspace_set_event_hook(new_events_global); + if (change_iseq_events < 0) { + RUBY_ASSERT(ruby_vm_iseq_events_enabled >= (unsigned int)(-change_iseq_events)); + } + ruby_vm_iseq_events_enabled += change_iseq_events; + if (change_c_events < 0) { + RUBY_ASSERT(ruby_vm_c_events_enabled >= (unsigned int)(-change_iseq_events)); + } + ruby_vm_c_events_enabled += change_c_events; + + ruby_vm_event_enabled_global_flags |= new_events; // NOTE: this is only ever added to + if (new_events_global & RUBY_INTERNAL_EVENT_MASK) { + rb_objspace_set_event_hook(new_events_global); + } // Invalidate JIT code as needed - if (first_time_iseq_events_p || enable_c_call || enable_c_return) { + if (new_iseq_events_p || clear_attr_ccs_p) { // Invalidate all code when ISEQs are modified to use trace_* insns above. // Also invalidate when enabling c_call or c_return because generated code // never fires these events. // Internal events fire inside C routines so don't need special handling. - // Do this after event flags updates so other ractors see updated vm events - // when they wake up. rb_yjit_tracing_invalidate_all(); rb_zjit_tracing_invalidate_all(); } + + if (ec) { + RB_VM_LOCK_LEAVE_LEV(&lev); + } } /* add/remove hooks */ @@ -174,25 +214,30 @@ alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, return hook; } +// Connect a hook onto a ractor, an iseq or a method definition's hook list static void -hook_list_connect(VALUE list_owner, rb_hook_list_t *list, rb_event_hook_t *hook, int global_p) +hook_list_connect(rb_hook_list_t *list, rb_event_hook_t *hook, int global_p) { rb_event_flag_t prev_events = list->events; + int change_iseq_events = 0; + int change_c_events = 0; hook->next = list->hooks; list->hooks = hook; list->events |= hook->events; if (global_p) { - /* global hooks are root objects at GC mark. */ - update_global_event_hook(prev_events, list->events); - } - else { - RB_OBJ_WRITTEN(list_owner, Qundef, hook->data); + if (hook->events & ISEQ_TRACE_EVENTS) { + change_iseq_events++; + } + if ((hook->events & RUBY_EVENT_C_CALL) || (hook->events & RUBY_EVENT_C_RETURN)) { + change_c_events++; + } + update_global_event_hooks(list, prev_events, list->events, change_iseq_events, change_c_events); } } static void -connect_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook) +connect_non_targeted_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook) { rb_hook_list_t *list; @@ -205,7 +250,7 @@ connect_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook) else { list = rb_ec_ractor_hooks(ec); } - hook_list_connect(Qundef, list, hook, TRUE); + hook_list_connect(list, hook, TRUE); } static void @@ -214,7 +259,7 @@ rb_threadptr_add_event_hook(const rb_execution_context_t *ec, rb_thread_t *th, { rb_event_hook_t *hook = alloc_event_hook(func, events, data, hook_flags); hook->filter.th = th; - connect_event_hook(ec, hook); + connect_non_targeted_event_hook(ec, hook); } void @@ -239,7 +284,35 @@ void rb_add_event_hook2(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags) { rb_event_hook_t *hook = alloc_event_hook(func, events, data, hook_flags); - connect_event_hook(GET_EC(), hook); + connect_non_targeted_event_hook(GET_EC(), hook); +} + +static bool +hook_list_targeted_p(rb_hook_list_t *list) +{ + switch (list->type) { + case hook_list_type_targeted_iseq: + case hook_list_type_targeted_def: + return true; + default: + return false; + } +} + +unsigned int +rb_hook_list_count(rb_hook_list_t *list) +{ + rb_event_hook_t *hook = list->hooks; + unsigned int count = 0; + + while (hook) { + if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED)) { + count++; + } + hook = hook->next; + } + + return count; } static void @@ -247,6 +320,8 @@ clean_hooks(rb_hook_list_t *list) { rb_event_hook_t *hook, **nextp = &list->hooks; rb_event_flag_t prev_events = list->events; + int change_iseq_events = 0; + int change_c_events = 0; VM_ASSERT(list->running == 0); VM_ASSERT(list->need_clean == true); @@ -257,6 +332,14 @@ clean_hooks(rb_hook_list_t *list) while ((hook = *nextp) != 0) { if (hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) { *nextp = hook->next; + if (!hook_list_targeted_p(list)) { + if (hook->events & ISEQ_TRACE_EVENTS) { + change_iseq_events--; + } + if ((hook->events & RUBY_EVENT_C_CALL) || (hook->events & RUBY_EVENT_C_RETURN)) { + change_c_events--; + } + } xfree(hook); } else { @@ -265,14 +348,13 @@ clean_hooks(rb_hook_list_t *list) } } - if (list->is_local) { + if (hook_list_targeted_p(list)) { if (list->events == 0) { - /* local events */ ruby_xfree(list); } } else { - update_global_event_hook(prev_events, list->events); + update_global_event_hooks(list, prev_events, list->events, change_iseq_events, change_c_events); } } @@ -299,8 +381,8 @@ remove_event_hook_from_list(rb_hook_list_t *list, const rb_thread_t *filter_th, if (hook->filter.th == filter_th || filter_th == MATCH_ANY_FILTER_TH) { if (UNDEF_P(data) || hook->data == data) { hook->hook_flags |= RUBY_EVENT_HOOK_FLAG_DELETED; - ret+=1; list->need_clean = true; + ret+=1; } } } @@ -1217,7 +1299,7 @@ tp_call_trace(VALUE tpval, rb_trace_arg_t *trace_arg) (*tp->func)(tpval, tp->data); } else { - if (tp->ractor == NULL || tp->ractor == GET_RACTOR()) { + if (tp->ractor == GET_RACTOR()) { rb_proc_call_with_block((VALUE)tp->proc, 1, &tpval, Qnil); } } @@ -1263,14 +1345,33 @@ iseq_of(VALUE target) const rb_method_definition_t *rb_method_def(VALUE method); /* proc.c */ +rb_hook_list_t * +rb_method_def_local_hooks(rb_method_definition_t *def, rb_ractor_t *cr, bool create) +{ + st_data_t val; + rb_hook_list_t *hook_list = NULL; + if (st_lookup(rb_ractor_targeted_hooks(cr), (st_data_t)def, &val)) { + hook_list = (rb_hook_list_t*)val; + RUBY_ASSERT(hook_list->type == hook_list_type_targeted_def); + } + else if (create) { + hook_list = ZALLOC(rb_hook_list_t); + hook_list->type = hook_list_type_targeted_def; + st_insert(cr->pub.targeted_hooks, (st_data_t)def, (st_data_t)hook_list); + } + return hook_list; +} + +// Enable "local" (targeted) tracepoint static VALUE rb_tracepoint_enable_for_target(VALUE tpval, VALUE target, VALUE target_line) { rb_tp_t *tp = tpptr(tpval); - const rb_iseq_t *iseq = iseq_of(target); + const rb_iseq_t *iseq = iseq_of(target); // takes Proc, Iseq, Method int n = 0; unsigned int line = 0; bool target_bmethod = false; + rb_ractor_t *cr = GET_RACTOR(); if (tp->tracing > 0) { rb_raise(rb_eArgError, "can't nest-enable a targeting TracePoint"); @@ -1288,62 +1389,80 @@ rb_tracepoint_enable_for_target(VALUE tpval, VALUE target, VALUE target_line) VM_ASSERT(tp->local_target_set == Qfalse); RB_OBJ_WRITE(tpval, &tp->local_target_set, rb_obj_hide(rb_ident_hash_new())); - /* bmethod */ - if (rb_obj_is_method(target)) { - rb_method_definition_t *def = (rb_method_definition_t *)rb_method_def(target); - if (def->type == VM_METHOD_TYPE_BMETHOD && - (tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN))) { - if (def->body.bmethod.hooks == NULL) { - def->body.bmethod.hooks = ZALLOC(rb_hook_list_t); - def->body.bmethod.hooks->is_local = true; - } - rb_hook_list_connect_tracepoint(target, def->body.bmethod.hooks, tpval, 0); - rb_hash_aset(tp->local_target_set, target, Qfalse); - target_bmethod = true; + RB_VM_LOCKING() { + // Rewriting iseq instructions across ractors is not safe unless they are stopped. + rb_vm_barrier(); - n++; + /* bmethod */ + if (rb_obj_is_method(target)) { + rb_method_definition_t *def = (rb_method_definition_t *)rb_method_def(target); + if (def->type == VM_METHOD_TYPE_BMETHOD && (tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN))) { + rb_hook_list_t *hook_list = rb_method_def_local_hooks(def, cr, true); + rb_hook_list_connect_local_tracepoint(hook_list, tpval, 0); + rb_hash_aset(tp->local_target_set, target, Qfalse); // Qfalse means not an iseq + rb_method_definition_addref(def); // in case `tp` gets GC'd and didn't disable the hook, `def` needs to stay alive + def->body.bmethod.local_hooks_cnt++; + target_bmethod = true; + n++; + } } - } - /* iseq */ - n += rb_iseq_add_local_tracepoint_recursively(iseq, tp->events, tpval, line, target_bmethod); - rb_hash_aset(tp->local_target_set, (VALUE)iseq, Qtrue); + /* iseq */ + n += rb_iseq_add_local_tracepoint_recursively(iseq, tp->events, tpval, line, target_bmethod); + if (n > 0) { + rb_hash_aset(tp->local_target_set, (VALUE)iseq, Qtrue); - if ((tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN)) && - iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) { - rb_clear_bf_ccs(); + if ((tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN)) && + iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) { + rb_clear_bf_ccs(); + } + + rb_yjit_tracing_invalidate_all(); + rb_zjit_tracing_invalidate_all(); + rb_ractor_targeted_hooks_incr(tp->ractor); + if (tp->events & ISEQ_TRACE_EVENTS) { + ruby_vm_iseq_events_enabled++; + } + if ((tp->events & RUBY_EVENT_C_CALL) || (tp->events & RUBY_EVENT_C_RETURN)) { + ruby_vm_c_events_enabled++; + } + tp->tracing = 1; + } } if (n == 0) { rb_raise(rb_eArgError, "can not enable any hooks"); } - rb_yjit_tracing_invalidate_all(); - rb_zjit_tracing_invalidate_all(); - - ruby_vm_event_local_num++; - - tp->tracing = 1; - return Qnil; } static int -disable_local_event_iseq_i(VALUE target, VALUE iseq_p, VALUE tpval) +disable_local_tracepoint_i(VALUE target, VALUE iseq_p, VALUE tpval) { + rb_tp_t *tp = tpptr(tpval); + rb_ractor_t *cr; + rb_method_definition_t *def; + rb_hook_list_t *hook_list; + ASSERT_vm_locking_with_barrier(); + if (iseq_p) { - rb_iseq_remove_local_tracepoint_recursively((rb_iseq_t *)target, tpval); + rb_iseq_remove_local_tracepoint_recursively((rb_iseq_t *)target, tpval, tp->ractor); } else { + cr = GET_RACTOR(); /* bmethod */ - rb_method_definition_t *def = (rb_method_definition_t *)rb_method_def(target); - rb_hook_list_t *hooks = def->body.bmethod.hooks; - VM_ASSERT(hooks != NULL); - rb_hook_list_remove_tracepoint(hooks, tpval); - - if (hooks->events == 0) { - rb_hook_list_free(def->body.bmethod.hooks); - def->body.bmethod.hooks = NULL; + def = (rb_method_definition_t *)rb_method_def(target); + hook_list = rb_method_def_local_hooks(def, cr, false); + RUBY_ASSERT(hook_list != NULL); + if (rb_hook_list_remove_local_tracepoint(hook_list, tpval)) { + RUBY_ASSERT(def->body.bmethod.local_hooks_cnt > 0); + def->body.bmethod.local_hooks_cnt--; + if (hook_list->events == 0) { + st_delete(rb_ractor_targeted_hooks(cr), (st_data_t*)&def, NULL); + rb_hook_list_free(hook_list); + } + rb_method_definition_release(def); } } return ST_CONTINUE; @@ -1356,10 +1475,23 @@ rb_tracepoint_disable(VALUE tpval) tp = tpptr(tpval); - if (tp->local_target_set) { - rb_hash_foreach(tp->local_target_set, disable_local_event_iseq_i, tpval); - RB_OBJ_WRITE(tpval, &tp->local_target_set, Qfalse); - ruby_vm_event_local_num--; + if (RTEST(tp->local_target_set)) { + RUBY_ASSERT(GET_RACTOR() == tp->ractor); + RB_VM_LOCKING() { + rb_vm_barrier(); + + rb_hash_foreach(tp->local_target_set, disable_local_tracepoint_i, tpval); + RB_OBJ_WRITE(tpval, &tp->local_target_set, Qfalse); + rb_ractor_targeted_hooks_decr(tp->ractor); + if (tp->events & ISEQ_TRACE_EVENTS) { + RUBY_ASSERT(ruby_vm_iseq_events_enabled > 0); + ruby_vm_iseq_events_enabled--; + } + if ((tp->events & RUBY_EVENT_C_CALL) || (tp->events & RUBY_EVENT_C_RETURN)) { + RUBY_ASSERT(ruby_vm_c_events_enabled > 0); + ruby_vm_c_events_enabled--; + } + } } else { if (tp->target_th) { @@ -1374,26 +1506,30 @@ rb_tracepoint_disable(VALUE tpval) return Qundef; } +// connect a targeted (ie: "local") tracepoint to the hook list for the method +// ex: tp.enable(target: method(:puts)) void -rb_hook_list_connect_tracepoint(VALUE target, rb_hook_list_t *list, VALUE tpval, unsigned int target_line) +rb_hook_list_connect_local_tracepoint(rb_hook_list_t *list, VALUE tpval, unsigned int target_line) { rb_tp_t *tp = tpptr(tpval); rb_event_hook_t *hook = alloc_event_hook((rb_event_hook_func_t)tp_call_trace, tp->events & ISEQ_TRACE_EVENTS, tpval, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG); hook->filter.target_line = target_line; - hook_list_connect(target, list, hook, FALSE); + hook_list_connect(list, hook, FALSE); } -void -rb_hook_list_remove_tracepoint(rb_hook_list_t *list, VALUE tpval) +bool +rb_hook_list_remove_local_tracepoint(rb_hook_list_t *list, VALUE tpval) { rb_event_hook_t *hook = list->hooks; rb_event_flag_t events = 0; + bool removed = false; while (hook) { if (hook->data == tpval) { hook->hook_flags |= RUBY_EVENT_HOOK_FLAG_DELETED; list->need_clean = true; + removed = true; } else if ((hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) == 0) { events |= hook->events; @@ -1402,6 +1538,7 @@ rb_hook_list_remove_tracepoint(rb_hook_list_t *list, VALUE tpval) } list->events = events; + return removed; } static VALUE @@ -1496,8 +1633,8 @@ tracepoint_new(VALUE klass, rb_thread_t *target_th, rb_event_flag_t events, void TypedData_Get_Struct(tpval, rb_tp_t, &tp_data_type, tp); RB_OBJ_WRITE(tpval, &tp->proc, proc); - tp->ractor = rb_ractor_shareable_p(proc) ? NULL : GET_RACTOR(); - tp->func = func; + tp->ractor = GET_RACTOR(); + tp->func = func; // for internal events tp->data = data; tp->events = events; tp->self = tpval; @@ -1512,9 +1649,6 @@ rb_tracepoint_new(VALUE target_thval, rb_event_flag_t events, void (*func)(VALUE if (RTEST(target_thval)) { target_th = rb_thread_ptr(target_thval); - /* TODO: Test it! - * Warning: This function is not tested. - */ } return tracepoint_new(rb_cTracePoint, target_th, events, func, data, Qundef); } @@ -1621,7 +1755,6 @@ tracepoint_stat_s(rb_execution_context_t *ec, VALUE self) VALUE stat = rb_hash_new(); tracepoint_stat_event_hooks(stat, vm->self, rb_ec_ractor_hooks(ec)->hooks); - /* TODO: thread local hooks */ return stat; } diff --git a/yjit.c b/yjit.c index f3c256093eda0b..6c3c9cd00161ce 100644 --- a/yjit.c +++ b/yjit.c @@ -160,18 +160,7 @@ rb_yjit_exit_locations_dict(VALUE *yjit_raw_samples, int *yjit_line_samples, int bool rb_c_method_tracing_currently_enabled(const rb_execution_context_t *ec) { - rb_event_flag_t tracing_events; - if (rb_multi_ractor_p()) { - tracing_events = ruby_vm_event_enabled_global_flags; - } - else { - // At the time of writing, events are never removed from - // ruby_vm_event_enabled_global_flags so always checking using it would - // mean we don't compile even after tracing is disabled. - tracing_events = rb_ec_ractor_hooks(ec)->events; - } - - return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN); + return ruby_vm_c_events_enabled > 0; } // The code we generate in gen_send_cfunc() doesn't fire the c_return TracePoint event diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index c436e20087ebbe..77d6aef561a716 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1214,19 +1214,10 @@ pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_1 { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_2 { - pub local_hooks: *mut rb_hook_list_struct, + pub local_hooks_cnt: ::std::os::raw::c_uint, pub global_trace_events: rb_event_flag_t, } #[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct rb_hook_list_struct { - pub hooks: *mut rb_event_hook_struct, - pub events: rb_event_flag_t, - pub running: ::std::os::raw::c_uint, - pub need_clean: bool, - pub is_local: bool, -} -#[repr(C)] pub struct rb_captured_block { pub self_: VALUE, pub ep: *const VALUE, @@ -1846,11 +1837,6 @@ pub type rb_iseq_param_keyword_struct = pub struct succ_index_table { pub _address: u8, } -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct rb_event_hook_struct { - pub _address: u8, -} unsafe extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; From f3d1557d5c04d7bc0bfa931869fbb35d14592c53 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 16 Dec 2025 14:53:31 -0500 Subject: [PATCH 1952/2435] Revert "ZJIT: Allow ccalls above 7 arguments" This reverts commit 2f151e76b5dc578026706b31f054d5caf5374b05. The SP decrement (push) before the call do not match up with the pops after the call, so registers were restored incorrectly. Code from: ./miniruby --zjit-call-threshold=1 --zjit-dump-disasm -e 'p Time.new(1992, 9, 23, 23, 0, 0, :std)' str x11, [sp, #-0x10]! str x12, [sp, #-0x10]! stur x7, [sp] # last argument mov x0, x20 mov x7, x6 mov x6, x5 mov x5, x4 mov x4, x3 mov x3, x2 mov x2, x1 ldur x1, [x29, #-0x20] mov x16, #0xccfc movk x16, #0x2e7, lsl #16 movk x16, #1, lsl #32 blr x16 ldr x12, [sp], #0x10 # supposed to match str x12, [sp, #-0x10]!, but got last argument ldr x11, [sp], #0x10 --- zjit/src/asm/arm64/opnd.rs | 2 - zjit/src/backend/arm64/mod.rs | 120 ++++++++++++++++++--------------- zjit/src/backend/lir.rs | 47 ++----------- zjit/src/backend/x86_64/mod.rs | 16 ++++- zjit/src/codegen.rs | 13 +++- 5 files changed, 99 insertions(+), 99 deletions(-) diff --git a/zjit/src/asm/arm64/opnd.rs b/zjit/src/asm/arm64/opnd.rs index d7185ac23b38ff..667533ab938e0e 100644 --- a/zjit/src/asm/arm64/opnd.rs +++ b/zjit/src/asm/arm64/opnd.rs @@ -98,8 +98,6 @@ pub const X2_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 2 }; pub const X3_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 3 }; pub const X4_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 4 }; pub const X5_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 5 }; -pub const X6_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 6 }; -pub const X7_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 7 }; // caller-save registers pub const X9_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 9 }; diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index e00ea270d52dea..55a65e3ea6161e 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -24,15 +24,13 @@ pub const EC: Opnd = Opnd::Reg(X20_REG); pub const SP: Opnd = Opnd::Reg(X21_REG); // C argument registers on this platform -pub const C_ARG_OPNDS: [Opnd; 8] = [ +pub const C_ARG_OPNDS: [Opnd; 6] = [ Opnd::Reg(X0_REG), Opnd::Reg(X1_REG), Opnd::Reg(X2_REG), Opnd::Reg(X3_REG), Opnd::Reg(X4_REG), - Opnd::Reg(X5_REG), - Opnd::Reg(X6_REG), - Opnd::Reg(X7_REG) + Opnd::Reg(X5_REG) ]; // C return value register on this platform @@ -201,8 +199,6 @@ pub const ALLOC_REGS: &[Reg] = &[ X3_REG, X4_REG, X5_REG, - X6_REG, - X7_REG, X11_REG, X12_REG, ]; @@ -235,7 +231,7 @@ impl Assembler { /// Get a list of all of the caller-saved registers pub fn get_caller_save_regs() -> Vec { - vec![X1_REG, X6_REG, X7_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG] + vec![X1_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG] } /// How many bytes a call and a [Self::frame_setup] would change native SP @@ -492,13 +488,31 @@ impl Assembler { } */ Insn::CCall { opnds, .. } => { - opnds.iter_mut().for_each(|opnd| { - *opnd = match opnd { - Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0), - Opnd::Mem(_) => split_memory_address(asm, *opnd), - _ => *opnd - }; - }); + assert!(opnds.len() <= C_ARG_OPNDS.len()); + + // Load each operand into the corresponding argument + // register. + // Note: the iteration order is reversed to avoid corrupting x0, + // which is both the return value and first argument register + if !opnds.is_empty() { + let mut args: Vec<(Opnd, Opnd)> = vec![]; + for (idx, opnd) in opnds.iter_mut().enumerate().rev() { + // If the value that we're sending is 0, then we can use + // the zero register, so in this case we'll just send + // a UImm of 0 along as the argument to the move. + let value = match opnd { + Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0), + Opnd::Mem(_) => split_memory_address(asm, *opnd), + _ => *opnd + }; + args.push((C_ARG_OPNDS[idx], value)); + } + asm.parallel_mov(args); + } + + // Now we push the CCall without any arguments so that it + // just performs the call. + *opnds = vec![]; asm.push_insn(insn); }, Insn::Cmp { left, right } => { @@ -1843,19 +1857,17 @@ mod tests { assert_disasm_snapshot!(cb.disasm(), @r" 0x0: str x1, [sp, #-0x10]! - 0x4: str x6, [sp, #-0x10]! - 0x8: str x7, [sp, #-0x10]! - 0xc: str x9, [sp, #-0x10]! - 0x10: str x10, [sp, #-0x10]! - 0x14: str x11, [sp, #-0x10]! - 0x18: str x12, [sp, #-0x10]! - 0x1c: str x13, [sp, #-0x10]! - 0x20: str x14, [sp, #-0x10]! - 0x24: str x15, [sp, #-0x10]! - 0x28: mrs x16, nzcv - 0x2c: str x16, [sp, #-0x10]! + 0x4: str x9, [sp, #-0x10]! + 0x8: str x10, [sp, #-0x10]! + 0xc: str x11, [sp, #-0x10]! + 0x10: str x12, [sp, #-0x10]! + 0x14: str x13, [sp, #-0x10]! + 0x18: str x14, [sp, #-0x10]! + 0x1c: str x15, [sp, #-0x10]! + 0x20: mrs x16, nzcv + 0x24: str x16, [sp, #-0x10]! "); - assert_snapshot!(cb.hexdump(), @"e10f1ff8e60f1ff8e70f1ff8e90f1ff8ea0f1ff8eb0f1ff8ec0f1ff8ed0f1ff8ee0f1ff8ef0f1ff810423bd5f00f1ff8"); + assert_snapshot!(cb.hexdump(), @"e10f1ff8e90f1ff8ea0f1ff8eb0f1ff8ec0f1ff8ed0f1ff8ee0f1ff8ef0f1ff810423bd5f00f1ff8"); } #[test] @@ -1875,11 +1887,9 @@ mod tests { 0x18: ldr x11, [sp], #0x10 0x1c: ldr x10, [sp], #0x10 0x20: ldr x9, [sp], #0x10 - 0x24: ldr x7, [sp], #0x10 - 0x28: ldr x6, [sp], #0x10 - 0x2c: ldr x1, [sp], #0x10 + 0x24: ldr x1, [sp], #0x10 "); - assert_snapshot!(cb.hexdump(), @"10421bd5f00741f8ef0741f8ee0741f8ed0741f8ec0741f8eb0741f8ea0741f8e90741f8e70741f8e60741f8e10741f8"); + assert_snapshot!(cb.hexdump(), @"10421bd5f00741f8ef0741f8ee0741f8ed0741f8ec0741f8eb0741f8ea0741f8e90741f8e10741f8"); } #[test] @@ -2648,14 +2658,14 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @r" - 0x0: mov x15, x1 - 0x4: mov x1, x0 - 0x8: mov x0, x15 - 0xc: mov x16, #0 - 0x10: blr x16 + assert_disasm_snapshot!(cb.disasm(), @" + 0x0: mov x15, x0 + 0x4: mov x0, x1 + 0x8: mov x1, x15 + 0xc: mov x16, #0 + 0x10: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0301aae10300aae0030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae1030faa100080d200023fd6"); } #[test] @@ -2671,17 +2681,17 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @r" - 0x0: mov x15, x1 - 0x4: mov x1, x0 - 0x8: mov x0, x15 - 0xc: mov x15, x3 - 0x10: mov x3, x2 - 0x14: mov x2, x15 - 0x18: mov x16, #0 - 0x1c: blr x16 + assert_disasm_snapshot!(cb.disasm(), @" + 0x0: mov x15, x2 + 0x4: mov x2, x3 + 0x8: mov x3, x15 + 0xc: mov x15, x0 + 0x10: mov x0, x1 + 0x14: mov x1, x15 + 0x18: mov x16, #0 + 0x1c: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0301aae10300aae0030faaef0303aae30302aae2030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0302aae20303aae3030faaef0300aae00301aae1030faa100080d200023fd6"); } #[test] @@ -2696,15 +2706,15 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @r" - 0x0: mov x15, x1 - 0x4: mov x1, x2 - 0x8: mov x2, x0 - 0xc: mov x0, x15 - 0x10: mov x16, #0 - 0x14: blr x16 + assert_disasm_snapshot!(cb.disasm(), @" + 0x0: mov x15, x0 + 0x4: mov x0, x1 + 0x8: mov x1, x2 + 0xc: mov x2, x15 + 0x10: mov x16, #0 + 0x14: blr x16 "); - assert_snapshot!(cb.hexdump(), @"ef0301aae10302aae20300aae0030faa100080d200023fd6"); + assert_snapshot!(cb.hexdump(), @"ef0300aae00301aae10302aae2030faa100080d200023fd6"); } #[test] diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index af19f056d339ef..f4ed3d7cb78ce6 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1559,8 +1559,9 @@ impl Assembler frame_setup_idxs.push(asm.insns.len()); } - let before_ccall = match &insn { - Insn::CCall { .. } if !pool.is_empty() => { + let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { + (Insn::ParallelMov { .. }, Some(Insn::CCall { .. })) | + (Insn::CCall { .. }, _) if !pool.is_empty() => { // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { @@ -1611,25 +1612,6 @@ impl Assembler if cfg!(target_arch = "x86_64") && saved_regs.len() % 2 == 1 { asm.cpush(Opnd::Reg(saved_regs.last().unwrap().0)); } - if let Insn::ParallelMov { moves } = &insn { - if moves.len() > C_ARG_OPNDS.len() { - let difference = moves.len().saturating_sub(C_ARG_OPNDS.len()); - - #[cfg(target_arch = "x86_64")] - let offset = { - // double quadword alignment - ((difference + 3) / 4) * 4 - }; - - #[cfg(target_arch = "aarch64")] - let offset = { - // quadword alignment - if difference % 2 == 0 { difference } else { difference + 1 } - }; - - asm.sub_into(NATIVE_STACK_PTR, (offset * 8).into()); - } - } } // Allocate a register for the output operand if it exists @@ -1721,23 +1703,7 @@ impl Assembler // Push instruction(s) let is_ccall = matches!(insn, Insn::CCall { .. }); match insn { - Insn::CCall { opnds, fptr, start_marker, end_marker, out } => { - let mut moves: Vec<(Opnd, Opnd)> = vec![]; - let num_reg_args = opnds.len().min(C_ARG_OPNDS.len()); - let num_stack_args = opnds.len().saturating_sub(C_ARG_OPNDS.len()); - - if num_stack_args > 0 { - for (i, opnd) in opnds.iter().skip(num_reg_args).enumerate() { - moves.push((Opnd::mem(64, NATIVE_STACK_PTR, 8 * i as i32), *opnd)); - } - } - - if num_reg_args > 0 { - for (i, opnd) in opnds.iter().take(num_reg_args).enumerate() { - moves.push((C_ARG_OPNDS[i], *opnd)); - } - } - + Insn::ParallelMov { moves } => { // For trampolines that use scratch registers, attempt to lower ParallelMov without scratch_reg. if let Some(moves) = Self::resolve_parallel_moves(&moves, None) { for (dst, src) in moves { @@ -1747,12 +1713,13 @@ impl Assembler // If it needs a scratch_reg, leave it to *_split_with_scratch_regs to handle it. asm.push_insn(Insn::ParallelMov { moves }); } - + } + Insn::CCall { opnds, fptr, start_marker, end_marker, out } => { // Split start_marker and end_marker here to avoid inserting push/pop between them. if let Some(start_marker) = start_marker { asm.push_insn(Insn::PosMarker(start_marker)); } - asm.push_insn(Insn::CCall { opnds: vec![], fptr, start_marker: None, end_marker: None, out }); + asm.push_insn(Insn::CCall { opnds, fptr, start_marker: None, end_marker: None, out }); if let Some(end_marker) = end_marker { asm.push_insn(Insn::PosMarker(end_marker)); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 4af704644910f3..9f780617cc4ac5 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -351,7 +351,21 @@ impl Assembler { }; asm.push_insn(insn); }, - Insn::CCall { .. } => { + Insn::CCall { opnds, .. } => { + assert!(opnds.len() <= C_ARG_OPNDS.len()); + + // Load each operand into the corresponding argument register. + if !opnds.is_empty() { + let mut args: Vec<(Opnd, Opnd)> = vec![]; + for (idx, opnd) in opnds.iter_mut().enumerate() { + args.push((C_ARG_OPNDS[idx], *opnd)); + } + asm.parallel_mov(args); + } + + // Now we push the CCall without any arguments so that it + // just performs the call. + *opnds = vec![]; asm.push_insn(insn); }, Insn::Lea { .. } => { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 46952dc12215a8..7c33473314b2dd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -398,12 +398,15 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Send { cd, blockiseq, state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::SendWithoutBlock { cd, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason), - // Give up SendWithoutBlockDirect for 6+ args since the callee doesn't know how to read arguments from the stack. + // Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it. Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir), Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), + // Ensure we have enough room fit ec, self, and arguments + // TODO remove this check when we have stack args (we can use Time.new to test it) + Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), Insn::InvokeBuiltin { bf, leaf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, *leaf, opnds!(args)), &Insn::EntryPoint { jit_entry_idx } => no_output!(gen_entry_point(jit, asm, jit_entry_idx)), Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))), @@ -450,6 +453,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), + // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). + // There's no test case for this because no core cfuncs have this many parameters. But C extensions could have such methods. + Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => + gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, recv, name, args, cme, state, blockiseq, .. } => gen_ccall_with_frame(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)), Insn::CCallVariadic { cfunc, recv, name, args, cme, state, blockiseq, return_type: _, elidable: _ } => { @@ -715,6 +722,10 @@ fn gen_fixnum_bit_check(asm: &mut Assembler, val: Opnd, index: u8) -> Opnd { } fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, leaf: bool, args: Vec) -> lir::Opnd { + assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, + "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", + unsafe { std::ffi::CStr::from_ptr(bf.name).to_str().unwrap() }, + bf.argc); if leaf { gen_prepare_leaf_call_with_gc(asm, state); } else { From eaa952b536c48658a5a2e3f128e3afdef03a01b6 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 16 Dec 2025 13:33:47 -0500 Subject: [PATCH 1953/2435] YJIT: Print `Rc` strong and weak count on assert failure For , the panic is looking like some sort of third party memory corruption, with YJIT taking the fall. At the point of this assert, the assembler has dropped, so there's nothing in YJIT's code other than JITState that could be holding on to these transient `PendingBranchRef`. The strong count being more than a handful or the weak count is non-zero shows that someone in the process (likely some native extension) corrupted the Rc's counts. --- yjit/src/core.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 0a14c44ae4d97e..0590135392d1cf 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -2419,7 +2419,9 @@ impl<'a> JITState<'a> { // Pending branches => actual branches outgoing: MutableBranchList(Cell::new(self.pending_outgoing.into_iter().map(|pending_out| { let pending_out = Rc::try_unwrap(pending_out) - .ok().expect("all PendingBranchRefs should be unique when ready to construct a Block"); + .unwrap_or_else(|rc| panic!( + "PendingBranchRef should be unique when ready to construct a Block. \ + strong={} weak={}", Rc::strong_count(&rc), Rc::weak_count(&rc))); pending_out.into_branch(NonNull::new(blockref as *mut Block).expect("no null from Box")) }).collect())) }); From 094418a6de89a37fc51a17077a5565f125b97f2e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 14 Dec 2025 09:50:21 +0100 Subject: [PATCH 1954/2435] gc.h: Reintroduce immediate guard in `rb_obj_written` This guard was removed in https://github.com/ruby/ruby/pull/13497 on the justification that some GC may need to be notified even for immediate. But the two currently available GCs don't, and there are plenty of assumtions GCs don't everywhere, notably in YJIT and ZJIT. This optimization is also not so micro (but not huge either). I routinely see 1-2% wasted there on micro-benchmarks. So perhaps if in the future we actually need this, it might make sense to introduce a way for GCs to declare that as an option, but in the meantime it's extra overhead with little gain. --- gc/default/default.c | 12 +++++++----- include/ruby/internal/abi.h | 2 +- include/ruby/internal/gc.h | 4 +++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 6a2035dccc9c99..b69fbeb1d9a40c 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6090,11 +6090,13 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) { rb_objspace_t *objspace = objspace_ptr; - if (RGENGC_CHECK_MODE) { - if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a); - } - - if (SPECIAL_CONST_P(b)) return; +#if RGENGC_CHECK_MODE + if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a); + if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b); +#else + ASSUME(!SPECIAL_CONST_P(a)); + ASSUME(!SPECIAL_CONST_P(b)); +#endif GC_ASSERT(!during_gc); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE); diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index e735a67564d885..e6d1fa7e8f3770 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,7 +24,7 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 0 +#define RUBY_ABI_VERSION 1 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index 19783f302342dc..5ab3bb266e242f 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -785,7 +785,9 @@ rb_obj_written( RGENGC_LOGGING_OBJ_WRITTEN(a, oldv, b, filename, line); #endif - rb_gc_writebarrier(a, b); + if (!RB_SPECIAL_CONST_P(b)) { + rb_gc_writebarrier(a, b); + } return a; } From 4d4f414a6062173743af7fe2b88835f16287a6cc Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 11:58:49 +0100 Subject: [PATCH 1955/2435] Use RBIMPL_ASSERT_OR_ASSUME instead of ASSUME for better errors when it does not hold --- gc/default/default.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index b69fbeb1d9a40c..be0be4a37335e4 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6094,8 +6094,8 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a); if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b); #else - ASSUME(!SPECIAL_CONST_P(a)); - ASSUME(!SPECIAL_CONST_P(b)); + RBIMPL_ASSERT_OR_ASSUME(!SPECIAL_CONST_P(a)); + RBIMPL_ASSERT_OR_ASSUME(!SPECIAL_CONST_P(b)); #endif GC_ASSERT(!during_gc); From 68174c31e4de2e63dbe32065a5d42c909963cdb8 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 12:10:28 +0100 Subject: [PATCH 1956/2435] ZJIT: Do not call rb_gc_writebarrier() with an immediate value in gen_write_barrier() --- zjit/src/codegen.rs | 26 +++++++++++++++++++++++--- zjit/src/hir_type/mod.rs | 4 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7c33473314b2dd..f14e4cdce4b6d7 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1130,11 +1130,31 @@ fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Op } fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { - // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written()->rb_gc_writebarrier() - if !val_type.is_immediate() { - asm_comment!(asm, "Write barrier"); + // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written(). + // rb_obj_written() does: if (!RB_SPECIAL_CONST_P(val)) { rb_gc_writebarrier(recv, val); } + if val_type.is_immediate() { + return; + } else if val_type.is_heap_object() { + asm_comment!(asm, "Write barrier with known heap object value"); let recv = asm.load(recv); asm_ccall!(asm, rb_gc_writebarrier, recv, val); + } else { + // Unknown if immediate or not, need to check because rb_gc_writebarrier() assumes not immediate + asm_comment!(asm, "Write barrier with unknown value"); + let no_wb = asm.new_label("no_write_barrier_for_immediate"); + + // Continue if special constant + asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64)); + asm.jnz(no_wb.clone()); + + // Continue if false + asm.cmp(val, Qfalse.into()); + asm.je(no_wb.clone()); + + let recv = asm.load(recv); + asm_ccall!(asm, rb_gc_writebarrier, recv, val); + + asm.write_label(no_wb); } } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index c87f1313b577d3..8ee90a8790a6f9 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -517,6 +517,10 @@ impl Type { self.is_subtype(types::Immediate) } + pub fn is_heap_object(&self) -> bool { + self.is_subtype(types::HeapBasicObject) + } + pub fn print(self, ptr_map: &PtrPrintMap) -> TypePrinter<'_> { TypePrinter { inner: self, ptr_map } } From 49cecd360fe61ec266c230084f46d5b640c03399 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 12:42:09 +0100 Subject: [PATCH 1957/2435] ZJIT: Guard other calls to rb_gc_writebarrier() with a !special_const_p() check --- zjit/src/gc.rs | 4 +++- zjit/src/profile.rs | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index 48d841a1048c39..0d2e1350a77b19 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -183,7 +183,9 @@ pub fn append_gc_offsets(iseq: IseqPtr, mut version: IseqVersionRef, offsets: &V let value_ptr = value_ptr as *const VALUE; unsafe { let object = value_ptr.read_unaligned(); - rb_gc_writebarrier(iseq.into(), object); + if !object.special_const_p() { + rb_gc_writebarrier(iseq.into(), object); + } } } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e7efbcad34a678..a4b893b2d7380d 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -124,7 +124,9 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or // drop them or something let ty = ProfiledType::new(obj); - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + if !ty.class().special_const_p() { + unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + } profile_type.observe(ty); } } @@ -138,7 +140,9 @@ fn profile_self(profiler: &mut Profiler, profile: &mut IseqProfile) { // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or // drop them or something let ty = ProfiledType::new(obj); - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + if !ty.class().special_const_p() { + unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + } types[0].observe(ty); } @@ -149,7 +153,9 @@ fn profile_block_handler(profiler: &mut Profiler, profile: &mut IseqProfile) { } let obj = profiler.peek_at_block_handler(); let ty = ProfiledType::object(obj); - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + if !ty.class().special_const_p() { + unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; + } types[0].observe(ty); } From 04edf3d9939d08f4e724cb91b9be374032cb662e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 13:02:33 +0100 Subject: [PATCH 1958/2435] ZJIT: Add a VALUE#write_barrier helper method to deduplicate logic --- zjit/src/cruby.rs | 8 ++++++++ zjit/src/gc.rs | 4 +--- zjit/src/profile.rs | 12 +++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 2bd44d21443b8d..68b6810125ea25 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -681,6 +681,14 @@ impl VALUE { let k: isize = item.wrapping_add(item.wrapping_add(1)); VALUE(k as usize) } + + /// Call the write barrier after separately writing val to self. + pub fn write_barrier(self, val: VALUE) { + // rb_gc_writebarrier() asserts it is not called with a special constant + if !val.special_const_p() { + unsafe { rb_gc_writebarrier(self, val) }; + } + } } pub type IseqParameters = rb_iseq_constant_body_rb_iseq_parameters; diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index 0d2e1350a77b19..40230ccc8db216 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -183,9 +183,7 @@ pub fn append_gc_offsets(iseq: IseqPtr, mut version: IseqVersionRef, offsets: &V let value_ptr = value_ptr as *const VALUE; unsafe { let object = value_ptr.read_unaligned(); - if !object.special_const_p() { - rb_gc_writebarrier(iseq.into(), object); - } + VALUE::from(iseq).write_barrier(object); } } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index a4b893b2d7380d..867d97641be92f 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -124,9 +124,7 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or // drop them or something let ty = ProfiledType::new(obj); - if !ty.class().special_const_p() { - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; - } + VALUE::from(profiler.iseq).write_barrier(ty.class()); profile_type.observe(ty); } } @@ -140,9 +138,7 @@ fn profile_self(profiler: &mut Profiler, profile: &mut IseqProfile) { // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or // drop them or something let ty = ProfiledType::new(obj); - if !ty.class().special_const_p() { - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; - } + VALUE::from(profiler.iseq).write_barrier(ty.class()); types[0].observe(ty); } @@ -153,9 +149,7 @@ fn profile_block_handler(profiler: &mut Profiler, profile: &mut IseqProfile) { } let obj = profiler.peek_at_block_handler(); let ty = ProfiledType::object(obj); - if !ty.class().special_const_p() { - unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; - } + VALUE::from(profiler.iseq).write_barrier(ty.class()); types[0].observe(ty); } From cc048f7571c5d054b04dad39cb61d008b30f663a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 16:23:29 +0100 Subject: [PATCH 1959/2435] Revert "ZJIT: Do not call rb_gc_writebarrier() with an immediate value in gen_write_barrier()" * This reverts commit 623559faa3dd0927b4034a752226a30ae8821604. * There is an issue with the jump in LIR, see https://github.com/ruby/ruby/pull/15542. --- zjit/src/codegen.rs | 26 +++----------------------- zjit/src/hir_type/mod.rs | 4 ---- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f14e4cdce4b6d7..7c33473314b2dd 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1130,31 +1130,11 @@ fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Op } fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { - // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written(). - // rb_obj_written() does: if (!RB_SPECIAL_CONST_P(val)) { rb_gc_writebarrier(recv, val); } - if val_type.is_immediate() { - return; - } else if val_type.is_heap_object() { - asm_comment!(asm, "Write barrier with known heap object value"); + // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written()->rb_gc_writebarrier() + if !val_type.is_immediate() { + asm_comment!(asm, "Write barrier"); let recv = asm.load(recv); asm_ccall!(asm, rb_gc_writebarrier, recv, val); - } else { - // Unknown if immediate or not, need to check because rb_gc_writebarrier() assumes not immediate - asm_comment!(asm, "Write barrier with unknown value"); - let no_wb = asm.new_label("no_write_barrier_for_immediate"); - - // Continue if special constant - asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64)); - asm.jnz(no_wb.clone()); - - // Continue if false - asm.cmp(val, Qfalse.into()); - asm.je(no_wb.clone()); - - let recv = asm.load(recv); - asm_ccall!(asm, rb_gc_writebarrier, recv, val); - - asm.write_label(no_wb); } } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 8ee90a8790a6f9..c87f1313b577d3 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -517,10 +517,6 @@ impl Type { self.is_subtype(types::Immediate) } - pub fn is_heap_object(&self) -> bool { - self.is_subtype(types::HeapBasicObject) - } - pub fn print(self, ptr_map: &PtrPrintMap) -> TypePrinter<'_> { TypePrinter { inner: self, ptr_map } } From 5e27581c3b3782f731ada9f83129c5c17a8f8c49 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 16:31:29 +0100 Subject: [PATCH 1960/2435] ZJIT: Use rb_zjit_writebarrier_check_immediate() instead of rb_gc_writebarrier() in gen_write_barrier() * To avoid calling rb_gc_writebarrier() with an immediate value in gen_write_barrier(), and avoid the LIR jump issue. --- zjit.c | 8 ++++++++ zjit/bindgen/src/main.rs | 1 + zjit/src/codegen.rs | 5 +++-- zjit/src/cruby_bindings.inc.rs | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/zjit.c b/zjit.c index cb5a01734f9721..9560d88130b03f 100644 --- a/zjit.c +++ b/zjit.c @@ -302,6 +302,14 @@ rb_zjit_class_has_default_allocator(VALUE klass) VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp); +void +rb_zjit_writebarrier_check_immediate(VALUE recv, VALUE val) +{ + if (!RB_SPECIAL_CONST_P(val)) { + rb_gc_writebarrier(recv, val); + } +} + // Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them. VALUE rb_zjit_enable(rb_execution_context_t *ec, VALUE self); VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self); diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 7b968d14bbb678..798a460c1980ac 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -144,6 +144,7 @@ fn main() { .allowlist_function("rb_gc_location") .allowlist_function("rb_gc_writebarrier") .allowlist_function("rb_gc_writebarrier_remember") + .allowlist_function("rb_zjit_writebarrier_check_immediate") // VALUE variables for Ruby class objects .allowlist_var("rb_cBasicObject") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7c33473314b2dd..e81732f3d54ff7 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1130,11 +1130,12 @@ fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Op } fn gen_write_barrier(asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { - // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written()->rb_gc_writebarrier() + // See RB_OBJ_WRITE/rb_obj_write: it's just assignment and rb_obj_written(). + // rb_obj_written() does: if (!RB_SPECIAL_CONST_P(val)) { rb_gc_writebarrier(recv, val); } if !val_type.is_immediate() { asm_comment!(asm, "Write barrier"); let recv = asm.load(recv); - asm_ccall!(asm, rb_gc_writebarrier, recv, val); + asm_ccall!(asm, rb_zjit_writebarrier_check_immediate, recv, val); } } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 77d6aef561a716..56ec724dd9b29c 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2078,6 +2078,7 @@ unsafe extern "C" { pub fn rb_zjit_class_get_alloc_func(klass: VALUE) -> rb_alloc_func_t; pub fn rb_zjit_class_has_default_allocator(klass: VALUE) -> bool; pub fn rb_vm_get_untagged_block_handler(reg_cfp: *mut rb_control_frame_t) -> VALUE; + pub fn rb_zjit_writebarrier_check_immediate(recv: VALUE, val: VALUE); pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; From 2d0140664436d85684a98a81f4fb103c186895b9 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 16 Dec 2025 21:27:09 +0000 Subject: [PATCH 1961/2435] [DOC] Harmonize rb_div methods --- complex.c | 4 ++-- numeric.c | 10 ++++++---- rational.c | 5 ++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/complex.c b/complex.c index eab6bde85ab82b..72e13b4e2ecde2 100644 --- a/complex.c +++ b/complex.c @@ -999,9 +999,9 @@ f_divide(VALUE self, VALUE other, /* * call-seq: - * complex / numeric -> new_complex + * self / other -> complex * - * Returns the quotient of +self+ and +numeric+: + * Returns the quotient of +self+ and +other+: * * Complex.rect(2, 3) / Complex.rect(2, 3) # => (1+0i) * Complex.rect(900) / Complex.rect(1) # => (900+0i) diff --git a/numeric.c b/numeric.c index 4e105baa81532b..63e10fbe9f9141 100644 --- a/numeric.c +++ b/numeric.c @@ -1175,7 +1175,7 @@ rb_flo_div_flo(VALUE x, VALUE y) * call-seq: * self / other -> numeric * - * Returns a new \Float which is the result of dividing +self+ by +other+: + * Returns the quotient of +self+ and +other+: * * f = 3.14 * f / 2 # => 1.57 @@ -4386,16 +4386,18 @@ fix_div(VALUE x, VALUE y) /* * call-seq: - * self / numeric -> numeric_result + * self / other -> numeric * - * Performs division; for integer +numeric+, truncates the result to an integer: + * Returns the quotient of +self+ and +other+. + * + * For integer +other+, truncates the result to an integer: * * 4 / 3 # => 1 * 4 / -3 # => -2 * -4 / 3 # => -2 * -4 / -3 # => 1 * - * For other +numeric+, returns non-integer result: + * For non-integer +other+, returns a non-integer result: * * 4 / 3.0 # => 1.3333333333333333 * 4 / Rational(3, 1) # => (4/3) diff --git a/rational.c b/rational.c index 05dc3540c09442..c172f06d535e0f 100644 --- a/rational.c +++ b/rational.c @@ -911,10 +911,9 @@ rb_rational_mul(VALUE self, VALUE other) /* * call-seq: - * rat / numeric -> numeric - * rat.quo(numeric) -> numeric + * self / other -> numeric * - * Performs division. + * Returns the quotient of +self+ and +other+: * * Rational(2, 3) / Rational(2, 3) #=> (1/1) * Rational(900) / Rational(1) #=> (900/1) From cbcbbb2fbe57fb17b3bd6b93244c4f37f3626c7e Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Mon, 15 Dec 2025 18:04:57 +0900 Subject: [PATCH 1962/2435] Let Ractor::IsolationError report correct constant path Before this patch, Ractor::IsolationError reported an incorrect constant path when constant was found through `rb_const_get_0()`. In this code, Ractor::IsolationError reported illegal access against `M::TOPLEVEL`, where it should be `Object::TOPLEVEL`. ```ruby TOPLEVEL = [1] module M def self.f TOPLEVEL end end Ractor.new { M.f }.value ``` This was because `rb_const_get_0()` built the "path" part referring to the module/class passed to it in the first place. When a constant was found through recursive search upwards, the module/class which the constant was found should be reported. This patch fixes this issue by modifying rb_const_search() to take a VALUE pointer to be filled with the module/class where the constant was found. [Bug #21782] --- bootstraptest/test_ractor.rb | 14 ++++++++++++++ variable.c | 23 ++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 575b96e48d501a..a1169f9d29c54f 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1089,6 +1089,20 @@ def str; STR; end end RUBY +# The correct constant path shall be reported +assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false + STR = "hello" + module M + def self.str; STR; end + end + + begin + Ractor.new{ M.str }.join + rescue Ractor::RemoteError => e + e.cause.message + end +RUBY + # Setting non-shareable objects into constants by other Ractors is not allowed assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false class C diff --git a/variable.c b/variable.c index 7a015a51e397e0..686056fdb47c15 100644 --- a/variable.c +++ b/variable.c @@ -63,7 +63,7 @@ static VALUE autoload_mutex; static void check_before_mod_set(VALUE, ID, VALUE, const char *); static void setup_const_entry(rb_const_entry_t *, VALUE, VALUE, rb_const_flag_t); -static VALUE rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility); +static VALUE rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in); static st_table *generic_fields_tbl_; typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg); @@ -473,7 +473,7 @@ rb_path_to_class(VALUE pathname) if (!id) { goto undefined_class; } - c = rb_const_search(c, id, TRUE, FALSE, FALSE); + c = rb_const_search(c, id, TRUE, FALSE, FALSE, NULL); if (UNDEF_P(c)) goto undefined_class; if (!rb_namespace_p(c)) { rb_raise(rb_eTypeError, "%"PRIsVALUE" does not refer to class/module", @@ -3352,11 +3352,12 @@ rb_const_warn_if_deprecated(const rb_const_entry_t *ce, VALUE klass, ID id) static VALUE rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) { - VALUE c = rb_const_search(klass, id, exclude, recurse, visibility); + VALUE found_in; + VALUE c = rb_const_search(klass, id, exclude, recurse, visibility, &found_in); if (!UNDEF_P(c)) { if (UNLIKELY(!rb_ractor_main_p())) { if (!rb_ractor_shareable_p(c)) { - rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%s by non-main Ractor.", rb_class_path(klass), rb_id2name(id)); + rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%s by non-main Ractor.", rb_class_path(found_in), rb_id2name(id)); } } return c; @@ -3365,7 +3366,7 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) } static VALUE -rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibility) +rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in) { VALUE value, current; bool first_iteration = true; @@ -3402,13 +3403,17 @@ rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibilit if (am == tmp) break; am = tmp; ac = autoloading_const_entry(tmp, id); - if (ac) return ac->value; + if (ac) { + if (found_in) { *found_in = tmp; } + return ac->value; + } rb_autoload_load(tmp, id); continue; } if (exclude && tmp == rb_cObject) { goto not_found; } + if (found_in) { *found_in = tmp; } return value; } if (!recurse) break; @@ -3420,17 +3425,17 @@ rb_const_search_from(VALUE klass, ID id, int exclude, int recurse, int visibilit } static VALUE -rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility) +rb_const_search(VALUE klass, ID id, int exclude, int recurse, int visibility, VALUE *found_in) { VALUE value; if (klass == rb_cObject) exclude = FALSE; - value = rb_const_search_from(klass, id, exclude, recurse, visibility); + value = rb_const_search_from(klass, id, exclude, recurse, visibility, found_in); if (!UNDEF_P(value)) return value; if (exclude) return value; if (BUILTIN_TYPE(klass) != T_MODULE) return value; /* search global const too, if klass is a module */ - return rb_const_search_from(rb_cObject, id, FALSE, recurse, visibility); + return rb_const_search_from(rb_cObject, id, FALSE, recurse, visibility, found_in); } VALUE From 0fe111caa6885407960541bdb6de7ba3cd6aad73 Mon Sep 17 00:00:00 2001 From: Daisuke Aritomo Date: Tue, 16 Dec 2025 15:15:28 +0900 Subject: [PATCH 1963/2435] Respect encoding of ID in exception messages --- variable.c | 2 +- vm_insnhelper.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/variable.c b/variable.c index 686056fdb47c15..085ba240e412ee 100644 --- a/variable.c +++ b/variable.c @@ -3357,7 +3357,7 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) if (!UNDEF_P(c)) { if (UNLIKELY(!rb_ractor_main_p())) { if (!rb_ractor_shareable_p(c)) { - rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%s by non-main Ractor.", rb_class_path(found_in), rb_id2name(id)); + rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.", rb_class_path(found_in), rb_id2str(id)); } } return c; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index d103146d1f601f..aa67e54d0ac44b 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1145,7 +1145,7 @@ vm_get_ev_const(rb_execution_context_t *ec, VALUE orig_klass, ID id, bool allow_ if (UNLIKELY(!rb_ractor_main_p())) { if (!rb_ractor_shareable_p(val)) { rb_raise(rb_eRactorIsolationError, - "can not access non-shareable objects in constant %"PRIsVALUE"::%s by non-main ractor.", rb_class_path(klass), rb_id2name(id)); + "can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main ractor.", rb_class_path(klass), rb_id2str(id)); } } return val; @@ -7575,4 +7575,3 @@ rb_vm_lvar_exposed(rb_execution_context_t *ec, int index) const rb_control_frame_t *cfp = ec->cfp; return cfp->ep[index]; } - From f483484fc610589a6faf828a77fd76f15fb4ebb3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 16 Dec 2025 17:49:55 -0500 Subject: [PATCH 1964/2435] [DOC] Fix call-seq of Method#box --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index cdc453f50092d2..aa52cd4a550793 100644 --- a/proc.c +++ b/proc.c @@ -2157,7 +2157,7 @@ method_owner(VALUE obj) } /* - * call-see: + * call-seq: * meth.box -> box or nil * * Returns the Ruby::Box where +meth+ is defined in. From 74a365310cfd150af6b45920d84920d17d6b3946 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:11:39 +0900 Subject: [PATCH 1965/2435] Fix: Recalculate the timeout duration considering `open_timeout` (#15596) This change updates the behavior so that, when there is only a single destination and `open_timeout` is specified, the remaining `open_timeout` duration is used as the connection timeout. --- ext/socket/ipsocket.c | 41 ++++++++++++++++++++++++++++++++-------- ext/socket/lib/socket.rb | 19 +++++++++++++------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index a95f5767d21263..ba1b81b3ad864d 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -606,6 +606,7 @@ init_fast_fallback_inetsock_internal(VALUE v) struct timeval user_specified_open_timeout_storage; struct timeval *user_specified_open_timeout_at = NULL; struct timespec now = current_clocktime_ts(); + VALUE starts_at = current_clocktime(); if (!NIL_P(open_timeout)) { struct timeval open_timeout_tv = rb_time_interval(open_timeout); @@ -619,7 +620,14 @@ init_fast_fallback_inetsock_internal(VALUE v) arg->getaddrinfo_shared = NULL; int family = arg->families[0]; - unsigned int t = NIL_P(resolv_timeout) ? 0 : rsock_value_timeout_to_msec(resolv_timeout); + unsigned int t; + if (!NIL_P(open_timeout)) { + t = rsock_value_timeout_to_msec(open_timeout); + } else if (!NIL_P(open_timeout)) { + t = rsock_value_timeout_to_msec(resolv_timeout); + } else { + t = 0; + } arg->remote.res = rsock_addrinfo( arg->remote.host, @@ -833,14 +841,22 @@ init_fast_fallback_inetsock_internal(VALUE v) status = connect(fd, remote_ai->ai_addr, remote_ai->ai_addrlen); last_family = remote_ai->ai_family; } else { - if (!NIL_P(connect_timeout)) { - user_specified_connect_timeout_storage = rb_time_interval(connect_timeout); - user_specified_connect_timeout_at = &user_specified_connect_timeout_storage; + VALUE timeout = Qnil; + + if (!NIL_P(open_timeout)) { + VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); + timeout = rb_funcall(open_timeout, '-', 1, elapsed); + } + if (NIL_P(timeout)) { + if (!NIL_P(connect_timeout)) { + user_specified_connect_timeout_storage = rb_time_interval(connect_timeout); + user_specified_connect_timeout_at = &user_specified_connect_timeout_storage; + } + timeout = + (user_specified_connect_timeout_at && is_infinity(*user_specified_connect_timeout_at)) ? + Qnil : tv_to_seconds(user_specified_connect_timeout_at); } - VALUE timeout = - (user_specified_connect_timeout_at && is_infinity(*user_specified_connect_timeout_at)) ? - Qnil : tv_to_seconds(user_specified_connect_timeout_at); io = arg->io = rsock_init_sock(arg->self, fd); status = rsock_connect(io, remote_ai->ai_addr, remote_ai->ai_addrlen, 0, timeout); } @@ -1305,13 +1321,22 @@ rsock_init_inetsock( * Maybe also accept a local address */ if (!NIL_P(local_host) || !NIL_P(local_serv)) { + unsigned int t; + if (!NIL_P(open_timeout)) { + t = rsock_value_timeout_to_msec(open_timeout); + } else if (!NIL_P(open_timeout)) { + t = rsock_value_timeout_to_msec(resolv_timeout); + } else { + t = 0; + } + local_res = rsock_addrinfo( local_host, local_serv, AF_UNSPEC, SOCK_STREAM, 0, - 0 + t ); struct addrinfo *tmp_p = local_res->ai; diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index f47b5bc1d17a54..49fb3fcc6d9834 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -685,7 +685,7 @@ def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: ni # :stopdoc: def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) if local_host || local_port - local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout) + local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: open_timeout || resolv_timeout) resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq else local_addrinfos = [] @@ -698,6 +698,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, is_windows_environment ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) now = current_clock_time + starts_at = now resolution_delay_expires_at = nil connection_attempt_delay_expires_at = nil user_specified_connect_timeout_at = nil @@ -707,7 +708,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if resolving_family_names.size == 1 family_name = resolving_family_names.first - addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: resolv_timeout) + addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: open_timeout || resolv_timeout) resolution_store.add_resolved(family_name, addrinfos) hostname_resolution_result = nil hostname_resolution_notifier = nil @@ -724,7 +725,6 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, thread } ) - user_specified_resolv_timeout_at = resolv_timeout ? now + resolv_timeout : Float::INFINITY end @@ -758,9 +758,16 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, socket.bind(local_addrinfo) if local_addrinfo result = socket.connect_nonblock(addrinfo, exception: false) else + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end result = socket = local_addrinfo ? - addrinfo.connect_from(local_addrinfo, timeout: connect_timeout) : - addrinfo.connect(timeout: connect_timeout) + addrinfo.connect_from(local_addrinfo, timeout:) : + addrinfo.connect(timeout:) end if result == :wait_writable @@ -934,7 +941,7 @@ def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_t local_addr_list = nil if local_host != nil || local_port != nil - local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) + local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil, timeout: open_timeout || resolv_timeout) end timeout = open_timeout ? open_timeout : resolv_timeout From 2117e612cafbd1a5ce4ce1d72cc13f8d9b715aa9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 10:38:28 +0900 Subject: [PATCH 1966/2435] Disabled gem sync for Ruby 4.0 release --- .github/workflows/sync_default_gems.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 907dc0b4f025fe..a108bf420e16cc 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -1,4 +1,8 @@ name: Sync default gems + +env: + DEFAULT_GEM_SYNC_ENABLED: false + on: workflow_dispatch: inputs: @@ -58,7 +62,7 @@ jobs: run: | git pull --rebase origin ${GITHUB_REF#refs/heads/} git push origin ${GITHUB_REF#refs/heads/} - if: ${{ steps.sync.outputs.update }} + if: ${{ steps.sync.outputs.update && env.DEFAULT_GEM_SYNC_ENABLED == 'true' }} env: EMAIL: svn-admin@ruby-lang.org GIT_AUTHOR_NAME: git From 3b66efda523fc33070aee6097898dbc5b1af6f4b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 11:29:29 +0900 Subject: [PATCH 1967/2435] Bundle RubyGems 4.0.2 and Bundler 4.0.2 --- lib/bundler/shared_helpers.rb | 3 +- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 13 +- lib/rubygems/util/atomic_file_writer.rb | 67 --------- .../realworld/fixtures/tapioca/Gemfile.lock | 2 +- .../realworld/fixtures/warbler/Gemfile.lock | 2 +- test/rubygems/test_gem_installer.rb | 34 ++--- test/rubygems/test_gem_remote_fetcher.rb | 134 +++++++++--------- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 12 files changed, 99 insertions(+), 166 deletions(-) delete mode 100644 lib/rubygems/util/atomic_file_writer.rb diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 2aa8abe0a078b0..6419e4299760b7 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -105,8 +105,7 @@ def set_bundle_environment def filesystem_access(path, action = :write, &block) yield(path.dup) rescue Errno::EACCES => e - path_basename = File.basename(path.to_s) - raise unless e.message.include?(path_basename) || action == :create + raise unless e.message.include?(path.to_s) || action == :create raise PermissionError.new(path, action) rescue Errno::EAGAIN diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 411829b28a3d35..ccb6ecab387818 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.1".freeze + VERSION = "4.0.2".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index a66e5378436600..a5d6c7fb66a52c 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.1" + VERSION = "4.0.2" end require_relative "rubygems/defaults" @@ -17,7 +17,6 @@ module Gem require_relative "rubygems/errors" require_relative "rubygems/target_rbconfig" require_relative "rubygems/win_platform" -require_relative "rubygems/util/atomic_file_writer" ## # RubyGems is the Ruby standard for publishing and managing third party @@ -834,12 +833,14 @@ def self.read_binary(path) end ## - # Atomically write a file in binary mode on all platforms. + # Safely write a file in binary mode on all platforms. def self.write_binary(path, data) - Gem::AtomicFileWriter.open(path) do |file| - file.write(data) - end + File.binwrite(path, data) + rescue Errno::ENOSPC + # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. + File.delete(path) if File.exist?(path) + raise end ## diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb deleted file mode 100644 index 7d1d6a74168f95..00000000000000 --- a/lib/rubygems/util/atomic_file_writer.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -# Based on ActiveSupport's AtomicFile implementation -# Copyright (c) David Heinemeier Hansson -# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb -# Licensed under the MIT License - -module Gem - class AtomicFileWriter - ## - # Write to a file atomically. Useful for situations where you don't - # want other processes or threads to see half-written files. - - def self.open(file_name) - temp_dir = File.dirname(file_name) - require "tempfile" unless defined?(Tempfile) - - Tempfile.create(".#{File.basename(file_name)}", temp_dir) do |temp_file| - temp_file.binmode - return_value = yield temp_file - temp_file.close - - original_permissions = if File.exist?(file_name) - File.stat(file_name) - else - # If not possible, probe which are the default permissions in the - # destination directory. - probe_permissions_in(File.dirname(file_name)) - end - - # Set correct permissions on new file - if original_permissions - begin - File.chown(original_permissions.uid, original_permissions.gid, temp_file.path) - File.chmod(original_permissions.mode, temp_file.path) - rescue Errno::EPERM, Errno::EACCES - # Changing file ownership failed, moving on. - end - end - - # Overwrite original file with temp file - File.rename(temp_file.path, file_name) - return_value - end - end - - def self.probe_permissions_in(dir) # :nodoc: - basename = [ - ".permissions_check", - Thread.current.object_id, - Process.pid, - rand(1_000_000), - ].join(".") - - file_name = File.join(dir, basename) - File.open(file_name, "w") {} - File.stat(file_name) - rescue Errno::ENOENT - nil - ensure - begin - File.unlink(file_name) if File.exist?(file_name) - rescue SystemCallError - end - end - end -end diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 866a77125bcaed..d6734a6569491d 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.1 + 4.0.2 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 67c887c84c2554..012f3fee972811 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.1 + 4.0.2 diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 0220a41f88a4f2..293fe1e823dd1a 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -2385,31 +2385,25 @@ def test_leaves_no_empty_cached_spec_when_no_more_disk_space installer = Gem::Installer.for_spec @spec installer.gem_home = @gemhome - assert_raise(Errno::ENOSPC) do - Gem::AtomicFileWriter.open(@spec.spec_file) do + File.singleton_class.class_eval do + alias_method :original_binwrite, :binwrite + + def binwrite(path, data) raise Errno::ENOSPC end end - assert_path_not_exist @spec.spec_file - end - - def test_write_default_spec - @spec = setup_base_spec - @spec.files = %w[a.rb b.rb c.rb] - - installer = Gem::Installer.for_spec @spec - installer.gem_home = @gemhome - - installer.write_default_spec - - assert_path_exist installer.default_spec_file - - loaded = Gem::Specification.load installer.default_spec_file + assert_raise Errno::ENOSPC do + installer.write_spec + end - assert_equal @spec.files, loaded.files - assert_equal @spec.name, loaded.name - assert_equal @spec.version, loaded.version + assert_path_not_exist @spec.spec_file + ensure + File.singleton_class.class_eval do + remove_method :binwrite + alias_method :binwrite, :original_binwrite + remove_method :original_binwrite + end end def test_dir diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 9badd75b427169..5c1d89fad6934a 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -60,7 +60,7 @@ def test_cache_update_path uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = fake_fetcher(uri.to_s, "hello") + fetcher = util_fuck_with_fetcher "hello" data = fetcher.cache_update_path uri, path @@ -75,7 +75,7 @@ def test_cache_update_path_with_utf8_internal_encoding path = File.join @tempdir, "file" data = String.new("\xC8").force_encoding(Encoding::BINARY) - fetcher = fake_fetcher(uri.to_s, data) + fetcher = util_fuck_with_fetcher data written_data = fetcher.cache_update_path uri, path @@ -88,7 +88,7 @@ def test_cache_update_path_no_update uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = fake_fetcher(uri.to_s, "hello") + fetcher = util_fuck_with_fetcher "hello" data = fetcher.cache_update_path uri, path, false @@ -97,79 +97,103 @@ def test_cache_update_path_no_update assert_path_not_exist path end - def test_cache_update_path_overwrites_existing_file - uri = Gem::URI "http://example/file" - path = File.join @tempdir, "file" - - # Create existing file with old content - File.write(path, "old content") - assert_equal "old content", File.read(path) - - fetcher = fake_fetcher(uri.to_s, "new content") + def util_fuck_with_fetcher(data, blow = false) + fetcher = Gem::RemoteFetcher.fetcher + fetcher.instance_variable_set :@test_data, data + + if blow + def fetcher.fetch_path(arg, *rest) + # OMG I'm such an ass + class << self; remove_method :fetch_path; end + def self.fetch_path(arg, *rest) + @test_arg = arg + @test_data + end - data = fetcher.cache_update_path uri, path + raise Gem::RemoteFetcher::FetchError.new("haha!", "") + end + else + def fetcher.fetch_path(arg, *rest) + @test_arg = arg + @test_data + end + end - assert_equal "new content", data - assert_equal "new content", File.read(path) + fetcher end def test_download - a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://gems.example.com/gems/a-1.gem" + a1_data = nil + File.open @a1_gem, "rb" do |fp| + a1_data = fp.read + end - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com") - assert_equal a1_url, fetcher.paths.last + assert_equal("http://gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end def test_download_with_auth - a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://user:password@gems.example.com/gems/a-1.gem" + a1_data = nil + File.open @a1_gem, "rb" do |fp| + a1_data = fp.read + end - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:password@gems.example.com") - assert_equal a1_url, fetcher.paths.last + assert_equal("http://user:password@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end def test_download_with_token - a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://token@gems.example.com/gems/a-1.gem" + a1_data = nil + File.open @a1_gem, "rb" do |fp| + a1_data = fp.read + end - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token@gems.example.com") - assert_equal a1_url, fetcher.paths.last + assert_equal("http://token@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end def test_download_with_x_oauth_basic - a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://token:x-oauth-basic@gems.example.com/gems/a-1.gem" + a1_data = nil + File.open @a1_gem, "rb" do |fp| + a1_data = fp.read + end - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token:x-oauth-basic@gems.example.com") - assert_equal a1_url, fetcher.paths.last + assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end def test_download_with_encoded_auth - a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://user:%25pas%25sword@gems.example.com/gems/a-1.gem" + a1_data = nil + File.open @a1_gem, "rb" do |fp| + a1_data = fp.read + end - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:%25pas%25sword@gems.example.com") - assert_equal a1_url, fetcher.paths.last + assert_equal("http://user:%25pas%25sword@gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end @@ -211,9 +235,8 @@ def test_download_local_space def test_download_install_dir a1_data = File.open @a1_gem, "rb", &:read - a1_url = "http://gems.example.com/gems/a-1.gem" - fetcher = fake_fetcher(a1_url, a1_data) + fetcher = util_fuck_with_fetcher a1_data install_dir = File.join @tempdir, "more_gems" @@ -222,7 +245,8 @@ def test_download_install_dir actual = fetcher.download(@a1, "http://gems.example.com", install_dir) assert_equal a1_cache_gem, actual - assert_equal a1_url, fetcher.paths.last + assert_equal("http://gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(a1_cache_gem) end @@ -258,12 +282,7 @@ def test_download_read_only FileUtils.chmod 0o555, @a1.cache_dir FileUtils.chmod 0o555, @gemhome - fetcher = Gem::RemoteFetcher.fetcher - def fetcher.fetch_path(uri, *rest) - File.read File.join(@test_gem_dir, "a-1.gem") - end - fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem)) - + fetcher = util_fuck_with_fetcher File.read(@a1_gem) fetcher.download(@a1, "http://gems.example.com") a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name assert File.exist? a1_cache_gem @@ -282,21 +301,19 @@ def test_download_platform_legacy end e1.loaded_from = File.join(@gemhome, "specifications", e1.full_name) - e1_data = File.open e1_gem, "rb", &:read - - fetcher = Gem::RemoteFetcher.fetcher - def fetcher.fetch_path(uri, *rest) - @call_count ||= 0 - @call_count += 1 - raise Gem::RemoteFetcher::FetchError.new("error", uri) if @call_count == 1 - @test_data + e1_data = nil + File.open e1_gem, "rb" do |fp| + e1_data = fp.read end - fetcher.instance_variable_set(:@test_data, e1_data) + + fetcher = util_fuck_with_fetcher e1_data, :blow_chunks e1_cache_gem = e1.cache_file assert_equal e1_cache_gem, fetcher.download(e1, "http://gems.example.com") + assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem", + fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(e1_cache_gem) end @@ -575,8 +592,6 @@ def test_yaml_error_on_size end end - private - def assert_error(exception_class = Exception) got_exception = false @@ -588,13 +603,4 @@ def assert_error(exception_class = Exception) assert got_exception, "Expected exception conforming to #{exception_class}" end - - def fake_fetcher(url, data) - original_fetcher = Gem::RemoteFetcher.fetcher - fetcher = Gem::FakeFetcher.new - fetcher.data[url] = data - Gem::RemoteFetcher.fetcher = fetcher - ensure - Gem::RemoteFetcher.fetcher = original_fetcher - end end diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 45062e42253876..30e29782705f8e 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.1 + 4.0.2 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index b45717d4e3eaca..ca3f816b7f8a4c 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.1 + 4.0.2 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 9fff0eb0fcdc89..1e31691b7f734a 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.1 + 4.0.2 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index ac581de02e7fd5..a257f8f8bc5b40 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.1 + 4.0.2 From 87274c7203bbf5c1c834b4ecd1c20d46ec88d7ac Mon Sep 17 00:00:00 2001 From: git Date: Wed, 17 Dec 2025 03:13:25 +0000 Subject: [PATCH 1968/2435] Update default gems list at 3b66efda523fc33070aee6097898db [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 465f8ec4b204a8..324c47909f4e20 100644 --- a/NEWS.md +++ b/NEWS.md @@ -264,8 +264,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.1 -* bundler 4.0.1 +* RubyGems 4.0.2 +* bundler 4.0.2 * date 3.5.1 * digest 3.2.1 * english 0.8.1 From f430fbbfacea5690d790dd9060ca4118431fc2fb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 22 Nov 2025 23:40:35 +0900 Subject: [PATCH 1969/2435] IO::Buffer: Fill the test for `IO::Buffer#clear` --- test/ruby/test_io_buffer.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index cc7998842313e8..62766130ced3fb 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -489,7 +489,21 @@ def test_zero_length_each_byte def test_clear buffer = IO::Buffer.new(16) - buffer.set_string("Hello World!") + assert_equal "\0" * 16, buffer.get_string + buffer.clear(1) + assert_equal "\1" * 16, buffer.get_string + buffer.clear(2, 1, 2) + assert_equal "\1" + "\2"*2 + "\1"*13, buffer.get_string + buffer.clear(2, 1) + assert_equal "\1" + "\2"*15, buffer.get_string + buffer.clear(260) + assert_equal "\4" * 16, buffer.get_string + assert_raise(TypeError) {buffer.clear("x")} + + assert_raise(ArgumentError) {buffer.clear(0, 20)} + assert_raise(ArgumentError) {buffer.clear(0, 0, 20)} + assert_raise(ArgumentError) {buffer.clear(0, 10, 10)} + assert_raise(ArgumentError) {buffer.clear(0, (1<<64)-8, 10)} end def test_invalidation From 9519d16381c8a8ddf7e1128a08fd80dfac8ed327 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 22 Nov 2025 23:41:23 +0900 Subject: [PATCH 1970/2435] IO::Buffer: Guard arguments from GC At least, `string` in `io_buffer_set_string` can be different from `argv[0]` after `rb_str_to_str` call. The other cases may not be necessary. --- io_buffer.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 55f1933194ef7b..c2e1c0ca5fe7fe 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -2569,7 +2569,9 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source) io_buffer_initialize(self, buffer, NULL, source_size, io_flags_for_size(source_size), Qnil); - return io_buffer_copy_from(buffer, source_base, source_size, 0, NULL); + VALUE result = io_buffer_copy_from(buffer, source_base, source_size, 0, NULL); + RB_GC_GUARD(source); + return result; } /* @@ -2654,7 +2656,9 @@ io_buffer_copy(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); - return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); + VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); + RB_GC_GUARD(source); + return result; } /* @@ -2732,7 +2736,9 @@ io_buffer_set_string(int argc, VALUE *argv, VALUE self) const void *source_base = RSTRING_PTR(string); size_t source_size = RSTRING_LEN(string); - return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); + VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); + RB_GC_GUARD(string); + return result; } void From c353b625297162024b5a80480664e599dd49a294 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 22 Nov 2025 23:43:19 +0900 Subject: [PATCH 1971/2435] [Bug #21787] IO::Buffer: Check addition overflows https://hackerone.com/reports/3437743 --- io_buffer.c | 20 +++++++++++++------- test/ruby/test_io_buffer.rb | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index c2e1c0ca5fe7fe..989d2905e4fb93 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1526,13 +1526,19 @@ VALUE rb_io_buffer_free_locked(VALUE self) return self; } +static bool +size_sum_is_bigger_than(size_t a, size_t b, size_t x) +{ + struct rbimpl_size_mul_overflow_tag size = rbimpl_size_add_overflow(a, b); + return size.left || size.right > x; +} + // Validate that access to the buffer is within bounds, assuming you want to // access length bytes from the specified offset. static inline void io_buffer_validate_range(struct rb_io_buffer *buffer, size_t offset, size_t length) { - // We assume here that offset + length won't overflow: - if (offset + length > buffer->size) { + if (size_sum_is_bigger_than(offset, length, buffer->size)) { rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } } @@ -1842,9 +1848,9 @@ rb_io_buffer_compare(VALUE self, VALUE other) } static void -io_buffer_validate_type(size_t size, size_t offset) +io_buffer_validate_type(size_t size, size_t offset, size_t extend) { - if (offset > size) { + if (size_sum_is_bigger_than(offset, extend, size)) { rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size); } } @@ -1944,7 +1950,7 @@ static ID RB_IO_BUFFER_DATA_TYPE_##name; \ static VALUE \ io_buffer_read_##name(const void* base, size_t size, size_t *offset) \ { \ - io_buffer_validate_type(size, *offset + sizeof(type)); \ + io_buffer_validate_type(size, *offset, sizeof(type)); \ type value; \ memcpy(&value, (char*)base + *offset, sizeof(type)); \ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \ @@ -1955,7 +1961,7 @@ io_buffer_read_##name(const void* base, size_t size, size_t *offset) \ static void \ io_buffer_write_##name(const void* base, size_t size, size_t *offset, VALUE _value) \ { \ - io_buffer_validate_type(size, *offset + sizeof(type)); \ + io_buffer_validate_type(size, *offset, sizeof(type)); \ type value = unwrap(_value); \ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \ memcpy((char*)base + *offset, &value, sizeof(type)); \ @@ -2483,7 +2489,7 @@ io_buffer_memmove(struct rb_io_buffer *buffer, size_t offset, const void *source io_buffer_validate_range(buffer, offset, length); - if (source_offset + length > source_size) { + if (size_sum_is_bigger_than(source_offset, length, source_size)) { rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!"); } diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 62766130ced3fb..e996fc39b88eb2 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require 'tempfile' +require 'rbconfig/sizeof' class TestIOBuffer < Test::Unit::TestCase experimental = Warning[:experimental] @@ -414,6 +415,8 @@ def test_zero_length_get_string :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0], } + SIZE_MAX = RbConfig::LIMITS["SIZE_MAX"] + def test_get_set_value buffer = IO::Buffer.new(128) @@ -422,6 +425,16 @@ def test_get_set_value buffer.set_value(data_type, 0, value) assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}." end + assert_raise(ArgumentError) {buffer.get_value(data_type, 128)} + assert_raise(ArgumentError) {buffer.set_value(data_type, 128, 0)} + case data_type + when :U8, :S8 + else + assert_raise(ArgumentError) {buffer.get_value(data_type, 127)} + assert_raise(ArgumentError) {buffer.set_value(data_type, 127, 0)} + assert_raise(ArgumentError) {buffer.get_value(data_type, SIZE_MAX)} + assert_raise(ArgumentError) {buffer.set_value(data_type, SIZE_MAX, 0)} + end end end @@ -503,7 +516,7 @@ def test_clear assert_raise(ArgumentError) {buffer.clear(0, 20)} assert_raise(ArgumentError) {buffer.clear(0, 0, 20)} assert_raise(ArgumentError) {buffer.clear(0, 10, 10)} - assert_raise(ArgumentError) {buffer.clear(0, (1<<64)-8, 10)} + assert_raise(ArgumentError) {buffer.clear(0, SIZE_MAX-7, 10)} end def test_invalidation From 7c402d2c2757b38df8b9406b372d2e1f406296ae Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Dec 2025 15:27:05 +0900 Subject: [PATCH 1972/2435] IO::Buffer: Warn as experimental at allocation Previously, warned only in `new` and `map`, but not `for` and `string`. --- io_buffer.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 989d2905e4fb93..1ee2bc9324c6d2 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -479,6 +479,8 @@ io_buffer_extract_offset_length(VALUE self, int argc, VALUE argv[], size_t *offs VALUE rb_io_buffer_type_allocate(VALUE self) { + io_buffer_experimental(); + struct rb_io_buffer *buffer = NULL; VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); @@ -649,8 +651,6 @@ rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags) VALUE rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { - io_buffer_experimental(); - VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer); struct rb_io_buffer *buffer = NULL; @@ -805,8 +805,6 @@ io_flags_for_size(size_t size) VALUE rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) { - io_buffer_experimental(); - rb_check_arity(argc, 0, 2); struct rb_io_buffer *buffer = NULL; From e354e9ba1007c10df3ee843bb9daabd89b47a708 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 17 Dec 2025 11:57:57 +0900 Subject: [PATCH 1973/2435] refactor: utilize a predefined macro --- iseq.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/iseq.c b/iseq.c index 90facfad78616c..7457118e07ce96 100644 --- a/iseq.c +++ b/iseq.c @@ -3711,11 +3711,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) if (is_proc) { for (i = 0; i < body->param.lead_num; i++) { - PARAM_TYPE(opt); - if (PARAM_ID(i) != idItImplicit && rb_id2str(PARAM_ID(i))) { - rb_ary_push(a, ID2SYM(PARAM_ID(i))); - } - rb_ary_push(args, a); + rb_ary_push(args, PARAM(i, opt)); } } else { @@ -3725,11 +3721,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) } r = body->param.lead_num + body->param.opt_num; for (; i < r; i++) { - PARAM_TYPE(opt); - if (rb_id2str(PARAM_ID(i))) { - rb_ary_push(a, ID2SYM(PARAM_ID(i))); - } - rb_ary_push(args, a); + rb_ary_push(args, PARAM(i, opt)); } if (body->param.flags.has_rest) { CONST_ID(rest, "rest"); @@ -3738,11 +3730,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) r = body->param.post_start + body->param.post_num; if (is_proc) { for (i = body->param.post_start; i < r; i++) { - PARAM_TYPE(opt); - if (rb_id2str(PARAM_ID(i))) { - rb_ary_push(a, ID2SYM(PARAM_ID(i))); - } - rb_ary_push(args, a); + rb_ary_push(args, PARAM(i, opt)); } } else { From 0e2962f917db1b20a6d34b6105b3768af8e692b8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Dec 2025 14:14:36 +0900 Subject: [PATCH 1974/2435] [ruby/io-wait] bump up to 0.4.0 https://github.com/ruby/io-wait/commit/ae676c9d6d --- ext/io/wait/io-wait.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index ef46fbca0f6930..c1c6172589efc5 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.4.0.dev" +_VERSION = "0.4.0" Gem::Specification.new do |spec| spec.name = "io-wait" From 4c38419eb81bc388eab0d50f8b2fe6e5edd1e4c7 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 17 Dec 2025 05:20:00 +0000 Subject: [PATCH 1975/2435] Update default gems list at 0e2962f917db1b20a6d34b6105b376 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 324c47909f4e20..479a49145ccfe0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -276,7 +276,7 @@ The following default gems are updated. * forwardable 1.4.0 * io-console 0.8.2 * io-nonblock 0.3.2 -* io-wait 0.4.0.dev +* io-wait 0.4.0 * ipaddr 1.2.8 * json 2.18.0 * net-http 0.8.0 From 7b5691c3b000c763faa282e1f73db96afa2ecae1 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:02:26 +0900 Subject: [PATCH 1976/2435] `Socket.tcp` and `TCPSocket.new` raises `IO::TiemoutError` with user specified timeout (#15602) * `Socket.tcp` and `TCPSocket.new` raises `IO::TiemoutError` with user specified timeout In https://github.com/ruby/ruby/pull/11880, `rsock_connect()` was changed to raise `IO::TimeoutError` when a user-specified timeout occurs. However, when `TCPSocket.new` attempts to connect to multiple destinations, it does not use `rsock_connect()`, and instead raises `Errno::ETIMEDOUT` on timeout. As a result, the exception class raised on timeout could differ depending on whether there were multiple destinations or not. To align this behavior with the implementation of `rsock_connect()`, this change makes `TCPSocket.new` raise `IO::TimeoutError` when a user-specified timeout occurs. Similarly, `Socket.tcp` is updated to raise `IO::TimeoutError` when a timeout occurs within the method. (Note that the existing behavior of `Addrinfo#connect_internal`, which Socket.tcp depends on internally and which raises `Errno::ETIMEDOUT` on timeout, is not changed.) * [ruby/net-http] Raise `Net::OpenTimeout` when `TCPSocket.open` raises `IO::TimeoutError`. With the changes in https://github.com/ruby/ruby/pull/15602, `TCPSocket.open` now raises `IO::TimeoutError` when a user-specified timeout occurs. This change updates #connect to handle this case accordingly. https://github.com/ruby/net-http/commit/f64109e1cf --- ext/socket/ipsocket.c | 4 +--- ext/socket/lib/socket.rb | 4 ++-- lib/net/http.rb | 4 +++- test/socket/test_socket.rb | 6 +++--- test/socket/test_tcp.rb | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index ba1b81b3ad864d..20d9101c11510d 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -29,9 +29,7 @@ struct inetsock_arg void rsock_raise_user_specified_timeout(void) { - VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno")); - VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT")); - rb_raise(etimedout_error, "user specified timeout"); + rb_raise(rb_eIOTimeoutError, "user specified timeout"); } static VALUE diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 49fb3fcc6d9834..6d04ca2483a385 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -905,7 +905,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end end - raise(Errno::ETIMEDOUT, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + raise(IO::TimeoutError, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? @@ -918,7 +918,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) && (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?) - raise Errno::ETIMEDOUT, 'user specified timeout' + raise IO::TimeoutError, 'user specified timeout' end end end diff --git a/lib/net/http.rb b/lib/net/http.rb index c63bdddcad621e..994ddbc0693820 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1676,7 +1676,9 @@ def connect begin s = timeouted_connect(conn_addr, conn_port) rescue => e - e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Net::OpenTimeout.new(e) + end raise e, "Failed to open TCP connection to " + "#{conn_addr}:#{conn_port} (#{e.message})" end diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index e746aca101f808..686114f05c1418 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -909,7 +909,7 @@ def test_tcp_socket_resolv_timeout Addrinfo.define_singleton_method(:getaddrinfo) { |*_| sleep } - assert_raise(Errno::ETIMEDOUT) do + assert_raise(IO::TimeoutError) do Socket.tcp("localhost", port, resolv_timeout: 0.01) end ensure @@ -934,7 +934,7 @@ def test_tcp_socket_resolv_timeout_with_connection_failure server.close - assert_raise(Errno::ETIMEDOUT) do + assert_raise(IO::TimeoutError) do Socket.tcp("localhost", port, resolv_timeout: 0.01) end RUBY @@ -951,7 +951,7 @@ def test_tcp_socket_open_timeout end end - assert_raise(Errno::ETIMEDOUT) do + assert_raise(IO::TimeoutError) do Socket.tcp("localhost", 12345, open_timeout: 0.01) end RUBY diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index 58fe44a279bcb4..d689ab23765c81 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -80,7 +80,7 @@ def test_tcp_initialize_open_timeout port = server.connect_address.ip_port server.close - assert_raise(Errno::ETIMEDOUT) do + assert_raise(IO::TimeoutError) do TCPSocket.new( "localhost", port, @@ -321,7 +321,7 @@ def test_initialize_resolv_timeout_with_connection_failure port = server.connect_address.ip_port server.close - assert_raise(Errno::ETIMEDOUT) do + assert_raise(IO::TimeoutError) do TCPSocket.new( "localhost", port, From 8850807eb1d8e6376a4f0dd99cb2f5e3e2988595 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 13:34:12 +0900 Subject: [PATCH 1977/2435] [ruby/psych] v5.3.1 https://github.com/ruby/psych/commit/8345af9ffb --- ext/psych/lib/psych/versions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 942495db9e894e..4c7a80d5c84274 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,7 +2,7 @@ module Psych # The version of Psych you are using - VERSION = '5.3.0' + VERSION = '5.3.1' if RUBY_ENGINE == 'jruby' DEFAULT_SNAKEYAML_VERSION = '2.10'.freeze From 01624492ecb14d85e26023169bf8b36c6015c62d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 13:32:45 +0900 Subject: [PATCH 1978/2435] [ruby/time] v0.4.2 https://github.com/ruby/time/commit/387292f5d2 --- lib/time.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/time.rb b/lib/time.rb index 14bfe4c8fbc43b..e6aab3fa5d444b 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -27,7 +27,7 @@ # # class Time - VERSION = "0.4.1" # :nodoc: + VERSION = "0.4.2" # :nodoc: class << Time From 8e258121942e7c879bc11d221425ac39f73afe8c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 13:23:04 +0900 Subject: [PATCH 1979/2435] [ruby/timeout] v0.6.0 https://github.com/ruby/timeout/commit/ab79dfff47 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 9969fa2e57b5d7..5d1f61d8af2421 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.5.0" + VERSION = "0.6.0" # Internal exception raised to when a timeout is triggered. class ExitException < Exception From d5257bea4811e54c006c4f2f56344d0d155638c9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 14:38:05 +0900 Subject: [PATCH 1980/2435] Bundle stringio-3.2.0 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 56fb044a3a299f..05bae94529b9db 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -13,7 +13,7 @@ **********************************************************************/ static const char *const -STRINGIO_VERSION = "3.1.9.dev"; +STRINGIO_VERSION = "3.2.0"; #include From df18f3baa658174ef89c8c2c11be4fea9bda4fc7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 14:38:55 +0900 Subject: [PATCH 1981/2435] Bundle strscan-3.1.6 --- ext/strscan/strscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index d63897dc61f35d..11e3d309a81757 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.6.dev" +#define STRSCAN_VERSION "3.1.6" #ifdef HAVE_RB_DEPRECATE_CONSTANT From fedafec78b2e6aa17a4246192a40192d7a0cf69c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 13:28:33 +0900 Subject: [PATCH 1982/2435] [ruby/net-http] v0.9.0 https://github.com/ruby/net-http/commit/3ccf0c8e6a --- lib/net/http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 994ddbc0693820..4ce8a2c874fad8 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.8.0" + VERSION = "0.9.0" HTTPVersion = '1.1' begin require 'zlib' From b80fc8bd84d194fdab60d0aee14ce0850a366500 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 13 Dec 2025 17:30:21 +0900 Subject: [PATCH 1983/2435] [ruby/net-http] Freeze more constants for Ractor compatibility Freeze Net::HTTP::SSL_ATTRIBUTES and IDEMPOTENT_METHODS_. Both constants have been marked as :nodoc:. Together with https://github.com/ruby/openssl/issues/521, this enables HTTPS clients in non-main Ractors on Ruby 4.0. https://github.com/ruby/net-http/commit/f24b3b358b --- lib/net/http.rb | 4 ++-- test/net/http/test_http.rb | 25 +++++++++++++++++++++++++ test/net/http/test_https.rb | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 4ce8a2c874fad8..3bed9dc3a994e5 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1531,7 +1531,7 @@ def use_ssl=(flag) :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: @@ -2430,7 +2430,7 @@ def send_entity(path, data, initheader, dest, type, &block) # :stopdoc: - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 366b4cd12c8de5..4e7fa22756cd31 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -1400,3 +1400,28 @@ def test_partial_response assert_raise(EOFError) {http.get('/')} end end + +class TestNetHTTPInRactor < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def test_get + assert_ractor(<<~RUBY, require: 'net/http') + expected = #{$test_net_http_data.dump}.b + ret = Ractor.new { + host = #{config('host').dump} + port = #{config('port')} + Net::HTTP.start(host, port) { |http| + res = http.get('/') + res.body + } + }.value + assert_equal expected, ret + RUBY + end +end if defined?(Ractor) && Ractor.method_defined?(:value) diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index 55b9eb317016aa..f5b21b901ff1af 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -266,6 +266,24 @@ def test_max_version assert_match(re_msg, ex.message) end + def test_ractor + assert_ractor(<<~RUBY, require: 'net/https') + expected = #{$test_net_http_data.dump}.b + ret = Ractor.new { + host = #{HOST.dump} + port = #{config('port')} + ca_cert_pem = #{CA_CERT.to_pem.dump} + cert_store = OpenSSL::X509::Store.new.tap { |s| + s.add_cert(OpenSSL::X509::Certificate.new(ca_cert_pem)) + } + Net::HTTP.start(host, port, use_ssl: true, cert_store: cert_store) { |http| + res = http.get('/') + res.body + } + }.value + assert_equal expected, ret + RUBY + end if defined?(Ractor) && Ractor.method_defined?(:value) end class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase From 26447b3597ab95af7cc220c641a1bd58b235fec9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 15:03:06 +0900 Subject: [PATCH 1984/2435] [ruby/net-http] v0.9.1 https://github.com/ruby/net-http/commit/8cee86e939 --- lib/net/http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 3bed9dc3a994e5..98d6793aee033d 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.9.0" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' From 38310fe79dae8373ceaab1f2c2b54c870e1f966c Mon Sep 17 00:00:00 2001 From: git Date: Wed, 17 Dec 2025 06:48:34 +0000 Subject: [PATCH 1985/2435] Update default gems list at 26447b3597ab95af7cc220c641a1bd [ci skip] --- NEWS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 479a49145ccfe0..e55df0f097ddb8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -279,16 +279,17 @@ The following default gems are updated. * io-wait 0.4.0 * ipaddr 1.2.8 * json 2.18.0 -* net-http 0.8.0 +* net-http 0.9.1 * openssl 4.0.0 * optparse 0.8.1 * pp 0.6.3 * prism 1.6.0 -* psych 5.3.0 +* psych 5.3.1 * resolv 0.7.0 -* stringio 3.1.9.dev -* strscan 3.1.6.dev -* timeout 0.5.0 +* stringio 3.2.0 +* strscan 3.1.6 +* time 0.4.2 +* timeout 0.6.0 * uri 1.1.1 * weakref 0.1.4 * zlib 3.2.2 From 1506c489eef67f268a54484662011a60897bdd48 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 17 Dec 2025 13:04:06 +0900 Subject: [PATCH 1986/2435] Update NEWS.md for improvements of error backtrace --- NEWS.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/NEWS.md b/NEWS.md index e55df0f097ddb8..ae87adecd9e0a9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -98,6 +98,24 @@ Note: We're only listing outstanding class updates. `Binding#implicit_parameter_defined?` have been added to access numbered parameters and "it" parameter. [[Bug #21049]] +* ErrorHighlight + + * When an ArgumentError is raised, it now displays code snippets for + both the method call (caller) and the method definition (callee). + [[Feature #21543]] + + ``` + test.rb:1:in 'Object#add': wrong number of arguments (given 1, expected 2) (ArgumentError) + + caller: test.rb:3 + | add(1) + ^^^ + callee: test.rb:1 + | def add(x, y) = x + y + ^^^ + from test.rb:3:in '
' + ``` + * File * `File::Stat#birthtime` is now available on Linux via the statx @@ -347,6 +365,31 @@ The following bundled gems are updated. and was already deprecated,. [[Feature #20971]] +* A backtrace for `ArgumentError` of "wrong number of arguments" now + include the receiver's class or module name (e.g., in `Foo#bar` + instead of in `bar`). [[Bug #21698]] + +* Backtraces no longer display `internal` frames. + These methods now appear as if it is in the Ruby source file, + consistent with other C-implemented methods. [[Bug #20968]] + + Before: + ``` + ruby -e '[1].fetch_values(42)' + :211:in 'Array#fetch': index 42 outside of array bounds: -1...1 (IndexError) + from :211:in 'block in Array#fetch_values' + from :211:in 'Array#map!' + from :211:in 'Array#fetch_values' + from -e:1:in '
' + ``` + + After: + ``` + $ ruby -e '[1].fetch_values(42)' + -e:1:in 'Array#fetch_values': index 42 outside of array bounds: -1...1 (IndexError) + from -e:1:in '
' + ``` + ## Stdlib compatibility issues * CGI library is removed from the default gems. Now we only provide `cgi/escape` for @@ -446,6 +489,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #20750]: https://bugs.ruby-lang.org/issues/20750 [Feature #20884]: https://bugs.ruby-lang.org/issues/20884 [Feature #20925]: https://bugs.ruby-lang.org/issues/20925 +[Bug #20968]: https://bugs.ruby-lang.org/issues/20968 [Feature #20971]: https://bugs.ruby-lang.org/issues/20971 [Bug #20974]: https://bugs.ruby-lang.org/issues/20974 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 @@ -470,8 +514,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21459]: https://bugs.ruby-lang.org/issues/21459 [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 +[Feature #21543]: https://bugs.ruby-lang.org/issues/21543 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 +[Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 From 54d3945ee53fa29186c6aa44f673a3a5ec3b38d9 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:16:32 +0900 Subject: [PATCH 1987/2435] Add host information to timeout error messages in `TCPSocket.new` and `Socket.tcp` (#15582) This change adds host information to the error messages shown when a timeout occurs while passing timeout options to `TCPSocket.new` or `Socket.tcp`, for improved usability. (When the `fast_fallback option` is enabled, there may be multiple possible destinations, so the host name is shown instead of an IP address.) As part of this change, the error messages in `Addrinfo.getaddrinfo` and `Addrinfo#connect_internal`, both of which are used by `Socket.tcp`, have also been improved in the same way. --- ext/socket/init.c | 9 ++++++--- ext/socket/ipsocket.c | 28 +++++++++++++++++++++++----- ext/socket/lib/socket.rb | 8 +++++--- ext/socket/raddrinfo.c | 10 +++++++++- ext/socket/rubysocket.h | 2 +- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/ext/socket/init.c b/ext/socket/init.c index 8e405609ba0aa4..b761d601c39d57 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -473,7 +473,7 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(VALUE self, VALUE timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { int sockerr; socklen_t sockerrlen; @@ -514,7 +514,10 @@ wait_connectable(VALUE self, VALUE timeout) VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); if (result == Qfalse) { - rb_raise(rb_eIOTimeoutError, "Connect timed out!"); + VALUE rai = rsock_addrinfo_new((struct sockaddr *)sockaddr, len, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + VALUE message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); } int revents = RB_NUM2INT(result); @@ -603,7 +606,7 @@ rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, V #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(self, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 20d9101c11510d..88225f76e5c882 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -27,9 +27,19 @@ struct inetsock_arg }; void -rsock_raise_user_specified_timeout(void) +rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port) { - rb_raise(rb_eIOTimeoutError, "user specified timeout"); + VALUE message; + + if (ai && ai->ai_addr) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)ai->ai_addr, (socklen_t)ai->ai_addrlen, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + } else { + message = rb_sprintf("user specified timeout for %" PRIsVALUE " port %" PRIsVALUE, host, port); + } + + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); } static VALUE @@ -149,7 +159,9 @@ init_inetsock_internal(VALUE v) } else { VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); timeout = rb_funcall(open_timeout, '-', 1, elapsed); - if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) rsock_raise_user_specified_timeout(); + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(res, arg->remote.host, arg->remote.serv); + } } if (status >= 0) { @@ -844,6 +856,10 @@ init_fast_fallback_inetsock_internal(VALUE v) if (!NIL_P(open_timeout)) { VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); timeout = rb_funcall(open_timeout, '-', 1, elapsed); + + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } } if (NIL_P(timeout)) { if (!NIL_P(connect_timeout)) { @@ -1180,7 +1196,9 @@ init_fast_fallback_inetsock_internal(VALUE v) } } - if (is_timeout_tv(user_specified_open_timeout_at, now)) rsock_raise_user_specified_timeout(); + if (is_timeout_tv(user_specified_open_timeout_at, now)) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } if (!any_addrinfos(&resolution_store)) { if (!in_progress_fds(arg->connection_attempt_fds_size) && @@ -1203,7 +1221,7 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.is_all_finished) && (is_timeout_tv(user_specified_connect_timeout_at, now) || !in_progress_fds(arg->connection_attempt_fds_size))) { - rsock_raise_user_specified_timeout(); + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); } } } diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 6d04ca2483a385..e74eaec43a3318 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -62,7 +62,7 @@ def connect_internal(local_addrinfo, timeout=nil) # :yields: socket break when :wait_writable sock.wait_writable(timeout) or - raise Errno::ETIMEDOUT, 'user specified timeout' + raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}" end while true else sock.connect(self) @@ -905,7 +905,9 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end end - raise(IO::TimeoutError, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + if expired?(now, user_specified_open_timeout_at) + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") + end if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? @@ -918,7 +920,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) && (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?) - raise IO::TimeoutError, 'user specified timeout' + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") end end end diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index e4e40e591dc880..3dcbe7717a0ca2 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -597,7 +597,15 @@ rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hint if (need_free) free_getaddrinfo_arg(arg); - if (timedout) rsock_raise_user_specified_timeout(); + if (timedout) { + if (arg->ai) { + rsock_raise_user_specified_timeout(arg->ai, Qnil, Qnil); + } else { + VALUE host = rb_str_new_cstr(hostp); + VALUE port = rb_str_new_cstr(portp); + rsock_raise_user_specified_timeout(NULL, host, port); + } + } // If the current thread is interrupted by asynchronous exception, the following raises the exception. // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 1adccee4276da0..638b7ede6ec72e 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -454,7 +454,7 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar #endif unsigned int rsock_value_timeout_to_msec(VALUE); -NORETURN(void rsock_raise_user_specified_timeout(void)); +NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); void rsock_init_basicsocket(void); void rsock_init_ipsocket(void); From 79f36c544a0431d9b76c3c11a5f622383eaca7bd Mon Sep 17 00:00:00 2001 From: Akinori Musha Date: Wed, 17 Dec 2025 17:30:24 +0900 Subject: [PATCH 1988/2435] Revert the override of Enumerator#to_set that performed size checks [Bug #21780] --- NEWS.md | 4 ++-- enumerator.c | 19 ------------------- test/ruby/test_enumerator.rb | 9 --------- test/ruby/test_set.rb | 6 ++++++ 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/NEWS.md b/NEWS.md index ae87adecd9e0a9..869f9b2f855d95 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,8 +188,8 @@ Note: We're only listing outstanding class updates. * Range - * `Range#to_set` and `Enumerator#to_set` now perform size checks to prevent - issues with endless ranges. [[Bug #21654]] + * `Range#to_set` now performs size checks to prevent issues with + endless ranges. [[Bug #21654]] * `Range#overlap?` now correctly handles infinite (unbounded) ranges. [[Bug #21185]] diff --git a/enumerator.c b/enumerator.c index c2b2bfa9a032dd..93671dfe712073 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3366,24 +3366,6 @@ enumerator_plus(VALUE obj, VALUE eobj) return new_enum_chain(rb_ary_new_from_args(2, obj, eobj)); } -/* - * call-seq: - * e.to_set -> set - * - * Returns a set generated from this enumerator. - * - * e = Enumerator.new { |y| y << 1 << 1 << 2 << 3 << 5 } - * e.to_set #=> # - */ -static VALUE enumerator_to_set(int argc, VALUE *argv, VALUE obj) -{ - VALUE size = rb_funcall(obj, id_size, 0); - if (RB_TYPE_P(size, T_FLOAT) && RFLOAT_VALUE(size) == INFINITY) { - rb_raise(rb_eArgError, "cannot convert an infinite enumerator to a set"); - } - return rb_call_super(argc, argv); -} - /* * Document-class: Enumerator::Product * @@ -4540,7 +4522,6 @@ InitVM_Enumerator(void) rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0); rb_define_method(rb_cEnumerator, "inspect", enumerator_inspect, 0); rb_define_method(rb_cEnumerator, "size", enumerator_size, 0); - rb_define_method(rb_cEnumerator, "to_set", enumerator_to_set, -1); rb_define_method(rb_cEnumerator, "+", enumerator_plus, 1); rb_define_method(rb_mEnumerable, "chain", enum_chain, -1); diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 5fabea645d2b8d..177d7c04fab4cf 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -1070,13 +1070,4 @@ def initialize(val) enum = ary.each assert_equal(35.0, enum.sum) end - - def test_to_set - e = Enumerator.new { it << 1 << 1 << 2 << 3 << 5 } - set = e.to_set - assert_equal(Set[1, 2, 3, 5], set) - - ei = Enumerator.new(Float::INFINITY) { it << 1 << 1 << 2 << 3 << 5 } - assert_raise(ArgumentError) { ei.to_set } - end end diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index e8ac3e329e6678..70a61aa3b5d5fe 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -1003,6 +1003,12 @@ def test_to_set_not_calling_size set = assert_nothing_raised { enum.to_set } assert(set.is_a?(Set)) assert_equal(Set[1,2,3], set) + + enumerator = enum.to_enum + + set = assert_nothing_raised { enumerator.to_set } + assert(set.is_a?(Set)) + assert_equal(Set[1,2,3], set) end end From 61bab1889048f758396acf671c9797d6bc52504b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Dec 2025 14:28:03 +0900 Subject: [PATCH 1989/2435] Rename to `struct rbimpl_size_overflow_tag` This struct is used for addition not only for multiplication, so remove the word `mul`, and make the member names more descriptive. --- gc.c | 42 +++++++++---------- include/ruby/internal/memory.h | 73 +++++++++++++++++----------------- io_buffer.c | 4 +- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/gc.c b/gc.c index 8b6142b139650e..5a14cb3675032d 100644 --- a/gc.c +++ b/gc.c @@ -484,21 +484,21 @@ rb_malloc_grow_capa(size_t current, size_t type_size) return new_capacity; } -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag size_mul_add_overflow(size_t x, size_t y, size_t z) /* x * y + z */ { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - struct rbimpl_size_mul_overflow_tag u = rbimpl_size_add_overflow(t.right, z); - return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left, u.right }; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + struct rbimpl_size_overflow_tag u = rbimpl_size_add_overflow(t.result, z); + return (struct rbimpl_size_overflow_tag) { t.overflowed || u.overflowed, u.result }; } -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag size_mul_add_mul_overflow(size_t x, size_t y, size_t z, size_t w) /* x * y + z * w */ { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - struct rbimpl_size_mul_overflow_tag u = rbimpl_size_mul_overflow(z, w); - struct rbimpl_size_mul_overflow_tag v = rbimpl_size_add_overflow(t.right, u.right); - return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left || v.left, v.right }; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + struct rbimpl_size_overflow_tag u = rbimpl_size_mul_overflow(z, w); + struct rbimpl_size_overflow_tag v = rbimpl_size_add_overflow(t.result, u.result); + return (struct rbimpl_size_overflow_tag) { t.overflowed || u.overflowed || v.overflowed, v.result }; } PRINTF_ARGS(NORETURN(static void gc_raise(VALUE, const char*, ...)), 2, 3); @@ -506,9 +506,9 @@ PRINTF_ARGS(NORETURN(static void gc_raise(VALUE, const char*, ...)), 2, 3); static inline size_t size_mul_or_raise(size_t x, size_t y, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -532,9 +532,9 @@ rb_size_mul_or_raise(size_t x, size_t y, VALUE exc) static inline size_t size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = size_mul_add_overflow(x, y, z); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = size_mul_add_overflow(x, y, z); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -559,9 +559,9 @@ rb_size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc) static inline size_t size_mul_add_mul_or_raise(size_t x, size_t y, size_t z, size_t w, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = size_mul_add_mul_overflow(x, y, z, w); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = size_mul_add_mul_overflow(x, y, z, w); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -5372,11 +5372,11 @@ ruby_mimcalloc(size_t num, size_t size) { void *mem; #if CALC_EXACT_MALLOC_SIZE - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(num, size); - if (UNLIKELY(t.left)) { + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(num, size); + if (UNLIKELY(t.overflowed)) { return NULL; } - size = t.right + sizeof(struct malloc_obj_info); + size = t.result + sizeof(struct malloc_obj_info); mem = calloc1(size); if (!mem) { return NULL; diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index 9fc6c39efca70b..cd099f85db9b31 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -408,7 +408,8 @@ typedef uint128_t DSIZE_T; /** * @private * - * This is an implementation detail of rbimpl_size_mul_overflow(). + * This is an implementation detail of rbimpl_size_mul_overflow() and + * rbimpl_size_add_overflow(). * * @internal * @@ -416,9 +417,9 @@ typedef uint128_t DSIZE_T; * nothing more than std::variant if we could use recent C++, but * reality is we cannot. */ -struct rbimpl_size_mul_overflow_tag { - bool left; /**< Whether overflow happened or not. */ - size_t right; /**< Multiplication result. */ +struct rbimpl_size_overflow_tag { + bool overflowed; /**< Whether overflow happened or not. */ + size_t result; /**< Calculation result. */ }; RBIMPL_SYMBOL_EXPORT_BEGIN() @@ -572,46 +573,46 @@ RBIMPL_ATTR_CONST() * * @param[in] x Arbitrary value. * @param[in] y Arbitrary value. - * @return `{ left, right }`, where `left` is whether there is an integer - * overflow or not, and `right` is a (possibly overflowed) result - * of `x` * `y`. + * @return `{ overflowed, result }`, where `overflowed` is whether there is + * an integer overflow or not, and `result` is a (possibly + * overflowed) result of `x` * `y`. * * @internal * * This is in fact also an implementation detail of ruby_xmalloc2() etc. */ -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag rbimpl_size_mul_overflow(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag ret = { false, 0, }; + struct rbimpl_size_overflow_tag ret = { false, 0, }; #if defined(ckd_mul) - ret.left = ckd_mul(&ret.right, x, y); + ret.overflowed = ckd_mul(&ret.result, x, y); #elif RBIMPL_HAS_BUILTIN(__builtin_mul_overflow) - ret.left = __builtin_mul_overflow(x, y, &ret.right); + ret.overflowed = __builtin_mul_overflow(x, y, &ret.result); #elif defined(DSIZE_T) RB_GNUC_EXTENSION DSIZE_T dx = x; RB_GNUC_EXTENSION DSIZE_T dy = y; RB_GNUC_EXTENSION DSIZE_T dz = dx * dy; - ret.left = dz > SIZE_MAX; - ret.right = RBIMPL_CAST((size_t)dz); + ret.overflowed = dz > SIZE_MAX; + ret.result = RBIMPL_CAST((size_t)dz); #elif defined(_MSC_VER) && defined(_M_AMD64) unsigned __int64 dp = 0; unsigned __int64 dz = _umul128(x, y, &dp); - ret.left = RBIMPL_CAST((bool)dp); - ret.right = RBIMPL_CAST((size_t)dz); + ret.overflowed = RBIMPL_CAST((bool)dp); + ret.result = RBIMPL_CAST((size_t)dz); #elif defined(_MSC_VER) && defined(_M_ARM64) - ret.left = __umulh(x, y) != 0; - ret.right = x * y; + ret.overflowed = __umulh(x, y) != 0; + ret.result = x * y; #else /* https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap */ - ret.left = (y != 0) && (x > SIZE_MAX / y); - ret.right = x * y; + ret.overflowed = (y != 0) && (x > SIZE_MAX / y); + ret.result = x * y; #endif return ret; @@ -635,11 +636,11 @@ rbimpl_size_mul_overflow(size_t x, size_t y) static inline size_t rbimpl_size_mul_or_raise(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag size = + struct rbimpl_size_overflow_tag size = rbimpl_size_mul_overflow(x, y); - if (RB_LIKELY(! size.left)) { - return size.right; + if (RB_LIKELY(! size.overflowed)) { + return size.result; } else { ruby_malloc_size_overflow(x, y); @@ -662,33 +663,33 @@ RBIMPL_ATTR_CONST() * * @param[in] x Arbitrary value. * @param[in] y Arbitrary value. - * @return `{ left, right }`, where `left` is whether there is an integer - * overflow or not, and `right` is a (possibly overflowed) result - * of `x` + `y`. + * @return `{ overflowed, result }`, where `overflowed` is whether there is + * an integer overflow or not, and `result` is a (possibly + * overflowed) result of `x` + `y`. * * @internal */ -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag rbimpl_size_add_overflow(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag ret = { false, 0, }; + struct rbimpl_size_overflow_tag ret = { false, 0, }; #if defined(ckd_add) - ret.left = ckd_add(&ret.right, x, y); + ret.overflowed = ckd_add(&ret.result, x, y); #elif RBIMPL_HAS_BUILTIN(__builtin_add_overflow) - ret.left = __builtin_add_overflow(x, y, &ret.right); + ret.overflowed = __builtin_add_overflow(x, y, &ret.result); #elif defined(DSIZE_T) RB_GNUC_EXTENSION DSIZE_T dx = x; RB_GNUC_EXTENSION DSIZE_T dy = y; RB_GNUC_EXTENSION DSIZE_T dz = dx + dy; - ret.left = dz > SIZE_MAX; - ret.right = (size_t)dz; + ret.overflowed = dz > SIZE_MAX; + ret.result = (size_t)dz; #else - ret.right = x + y; - ret.left = ret.right < y; + ret.result = x + y; + ret.overflowed = ret.result < y; #endif @@ -710,11 +711,11 @@ rbimpl_size_add_overflow(size_t x, size_t y) static inline size_t rbimpl_size_add_or_raise(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag size = + struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(x, y); - if (RB_LIKELY(!size.left)) { - return size.right; + if (RB_LIKELY(!size.overflowed)) { + return size.result; } else { ruby_malloc_add_size_overflow(x, y); diff --git a/io_buffer.c b/io_buffer.c index 1ee2bc9324c6d2..85061076cd9476 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1527,8 +1527,8 @@ VALUE rb_io_buffer_free_locked(VALUE self) static bool size_sum_is_bigger_than(size_t a, size_t b, size_t x) { - struct rbimpl_size_mul_overflow_tag size = rbimpl_size_add_overflow(a, b); - return size.left || size.right > x; + struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(a, b); + return size.overflowed || size.result > x; } // Validate that access to the buffer is within bounds, assuming you want to From f286e7001930dc1e13c9235b83af32216fde8d5c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Oct 2024 12:09:15 +0900 Subject: [PATCH 1990/2435] win32: Set the source code charset to UTF-8 --- win32/setup.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/setup.mak b/win32/setup.mak index 15fa41b5622651..6eeaa325aea45e 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -22,7 +22,7 @@ MAKE = $(MAKE) -f $(MAKEFILE) MAKEFILE = Makefile !endif CPU = PROCESSOR_LEVEL -CC = $(CC) -nologo +CC = $(CC) -nologo -source-charset:utf-8 CPP = $(CC) -EP all: -prologue- -generic- -epilogue- From bd4353ba7aeb464c9736fe49ac017899b2295d60 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Dec 2025 18:10:51 +0900 Subject: [PATCH 1991/2435] CI: Assume all C source files are UTF-8 now --- .github/workflows/check_misc.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 5cc891c477982c..e4d2b2c68961e5 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -38,10 +38,6 @@ jobs: # Skip 'push' events because post_push.yml fixes them on push if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - - name: Check if C-sources are US-ASCII - run: | - grep -r -n --exclude-dir=vendor --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : - - name: Check for bash specific substitution in configure.ac run: | git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || : From f45b1e3a1bfff6546a63d741c404fc404fa91cc6 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:53:34 +0900 Subject: [PATCH 1992/2435] Update NEWS.md for Socket (#15608) --- NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS.md b/NEWS.md index 869f9b2f855d95..f039f7af1e7aef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -224,6 +224,11 @@ Note: We're only listing outstanding class updates. * `Socket.tcp` & `TCPSocket.new` accepts an `open_timeout` keyword argument to specify the timeout for the initial connection. [[Feature #21347]] + * When a user-specified timeout occurred in `TCPSocket.new`, either `Errno::ETIMEDOUT` + or `IO::TimeoutError` could previously be raised depending on the situation. + This behavior has been unified so that `IO::TimeoutError` is now consistently raised. + (Please note that, in `Socket.tcp`, there are still cases where `Errno::ETIMEDOUT` may + be raised in similar situations.) * String From c99670d6683fec770271d35c2ae082514b1abce3 Mon Sep 17 00:00:00 2001 From: Akinori Musha Date: Wed, 17 Dec 2025 18:36:10 +0900 Subject: [PATCH 1993/2435] Revert the default size of Enumerator::Producer to infinity [Bug #21780] --- NEWS.md | 4 ++-- enumerator.c | 5 +++-- test/ruby/test_enumerator.rb | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index f039f7af1e7aef..4734a93f4cf75d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -38,8 +38,8 @@ Note: We're only listing outstanding class updates. * `Enumerator.produce` now accepts an optional `size` keyword argument to specify the size of the enumerator. It can be an integer, `Float::INFINITY`, a callable object (such as a lambda), or `nil` to - indicate unknown size. When not specified, the size is unknown (`nil`). - Previously, the size was always `Float::INFINITY` and not specifiable. + indicate unknown size. When not specified, the size defaults to + `Float::INFINITY`. ```ruby # Infinite enumerator diff --git a/enumerator.c b/enumerator.c index 93671dfe712073..5514d76dace3d4 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3037,7 +3037,8 @@ producer_size(VALUE obj, VALUE args, VALUE eobj) * The optional +size+ keyword argument specifies the size of the enumerator, * which can be retrieved by Enumerator#size. It can be an integer, * +Float::INFINITY+, a callable object (such as a lambda), or +nil+ to - * indicate unknown size. When not specified, the size is unknown (+nil+). + * indicate unknown size. When not specified, the size defaults to + * +Float::INFINITY+. * * # Infinite enumerator * enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) @@ -3063,7 +3064,7 @@ enumerator_s_produce(int argc, VALUE *argv, VALUE klass) rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "01:", &init, &opts); rb_get_kwargs(opts, keyword_ids, 0, 1, &size); - size = UNDEF_P(size) ? Qnil : convert_to_feasible_size_value(size); + size = UNDEF_P(size) ? DBL2NUM(HUGE_VAL) : convert_to_feasible_size_value(size); if (argc == 0 || (argc == 1 && !NIL_P(opts))) { init = Qundef; diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 177d7c04fab4cf..9b972d7b22e1aa 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -892,7 +892,7 @@ def test_produce passed_args = [] enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ } assert_instance_of(Enumerator, enum) - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [nil, 1, 2], passed_args @@ -900,14 +900,14 @@ def test_produce passed_args = [] enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ } assert_instance_of(Enumerator, enum) - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [1, 2], passed_args # Raising StopIteration words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/) enum = Enumerator.produce { words.shift or raise StopIteration } - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_instance_of(Enumerator, enum) assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a @@ -917,7 +917,7 @@ def test_produce obj.respond_to?(:first) or raise StopIteration obj.first } - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_instance_of(Enumerator, enum) assert_nothing_raised { assert_equal [ From aee4b24829cfd26c9089f539179bd7770d283bfa Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 17 Dec 2025 13:26:55 +0900 Subject: [PATCH 1994/2435] [ruby/error_highlight] Show no message when failing to get caller/callee snippets Even with Ruby 4.0, snippets is not always available, such as in irb by default. It would be better to just say nothing than to show a confusing message. https://github.com/ruby/error_highlight/commit/ef80ce73a1 --- lib/error_highlight/core_ext.rb | 2 +- test/error_highlight/test_error_highlight.rb | 142 +++++++------------ 2 files changed, 53 insertions(+), 91 deletions(-) diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index 1cfc331582748e..c3354f46cdf5dc 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -24,7 +24,7 @@ module CoreExt _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines out += "\n | #{ snippet } #{ highlight }" else - out += "\n (cannot highlight method definition; try Ruby 4.0 or later)" + # do nothing end end ret << "\n" + out if out diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index d3ca99021b9ad2..ec39b1c4db6fc7 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1450,26 +1450,28 @@ def exc.backtrace_locations = [] RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(exc.backtrace_locations.first) end - WRONG_NUMBER_OF_ARGUMENTS_LIENO = __LINE__ + 1 + def process_callee_snippet(str) + return str if MethodDefLocationSupported + + str.sub(/\n +\|.*\n +\^+\n\z/, "") + end + + WRONG_NUMBER_OF_ARGUMENTS_LINENO = __LINE__ + 1 def wrong_number_of_arguments_test(x, y) x + y end def test_wrong_number_of_arguments_for_method lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 2) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | wrong_number_of_arguments_test(1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LIENO } - #{ - MethodDefLocationSupported ? - "| def wrong_number_of_arguments_test(x, y) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LINENO } + | def wrong_number_of_arguments_test(x, y) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END wrong_number_of_arguments_test(1) @@ -1483,19 +1485,15 @@ def keyword_test(kw1:, kw2:, kw3:) def test_missing_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keyword: :kw3 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2) @@ -1504,19 +1502,15 @@ def test_missing_keyword def test_missing_keywords # multiple missing keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keywords: :kw2, :kw3 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1) @@ -1525,19 +1519,15 @@ def test_missing_keywords # multiple missing keywords def test_unknown_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do unknown keyword: :kw4 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) @@ -1546,19 +1536,15 @@ def test_unknown_keyword def test_unknown_keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do unknown keywords: :kw4, :kw5 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) @@ -1576,19 +1562,15 @@ def wrong_number_of_arguments_test2( def test_wrong_number_of_arguments_for_method2 lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 3) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | wrong_number_of_arguments_test2(1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO } - #{ - MethodDefLocationSupported ? - "| def wrong_number_of_arguments_test2( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def wrong_number_of_arguments_test2( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END wrong_number_of_arguments_test2(1) @@ -1598,19 +1580,15 @@ def test_wrong_number_of_arguments_for_method2 def test_wrong_number_of_arguments_for_lambda_literal v = -> {} lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 0) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | v.call(1) ^^^^^ callee: #{ __FILE__ }:#{ lineno - 1 } - #{ - MethodDefLocationSupported ? - "| v = -> {} - ^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | v = -> {} + ^^ END v.call(1) @@ -1620,19 +1598,15 @@ def test_wrong_number_of_arguments_for_lambda_literal def test_wrong_number_of_arguments_for_lambda_method v = lambda { } lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 0) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | v.call(1) ^^^^^ callee: #{ __FILE__ }:#{ lineno - 1 } - #{ - MethodDefLocationSupported ? - "| v = lambda { } - ^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | v = lambda { } + ^ END v.call(1) @@ -1646,19 +1620,15 @@ def test_wrong_number_of_arguments_for_lambda_method def test_wrong_number_of_arguments_for_define_method lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 2) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | define_method_test(1) ^^^^^^^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ DEFINE_METHOD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| define_method :define_method_test do |x, y| - ^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | define_method :define_method_test do |x, y| + ^^ END define_method_test(1) @@ -1742,19 +1712,15 @@ def self . baz(x:) def test_singleton_method_with_spacing_missing_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keyword: :x (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | SingletonMethodWithSpacing.baz ^^^^ callee: #{ __FILE__ }:#{ SingletonMethodWithSpacing::LINENO } - #{ - MethodDefLocationSupported ? - "| def self . baz(x:) - ^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def self . baz(x:) + ^^^^^ END SingletonMethodWithSpacing.baz @@ -1770,19 +1736,15 @@ def self.run(shop_id:, param1:) def test_singleton_method_multiple_missing_keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keywords: :shop_id, :param1 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | SingletonMethodMultipleKwargs.run ^^^^ callee: #{ __FILE__ }:#{ SingletonMethodMultipleKwargs::LINENO } - #{ - MethodDefLocationSupported ? - "| def self.run(shop_id:, param1:) - ^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def self.run(shop_id:, param1:) + ^^^^ END SingletonMethodMultipleKwargs.run From 41e24aeb1a432c13ff484a055077d00d93668201 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:06:53 +0900 Subject: [PATCH 1995/2435] Improve NEWS.md for Socket (#15610) --- NEWS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4734a93f4cf75d..6b18ae4c725ba5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -227,8 +227,9 @@ Note: We're only listing outstanding class updates. * When a user-specified timeout occurred in `TCPSocket.new`, either `Errno::ETIMEDOUT` or `IO::TimeoutError` could previously be raised depending on the situation. This behavior has been unified so that `IO::TimeoutError` is now consistently raised. - (Please note that, in `Socket.tcp`, there are still cases where `Errno::ETIMEDOUT` may - be raised in similar situations.) + (Please note that, in `Socket.tcp`, there are still cases where `Errno::ETIMEDOUT` + may be raised in similar situations, and that in both cases `Errno::ETIMEDOUT` may be + raised when the timeout occurs at the OS level.) * String From 56b67f1684bf1955cf69fc06701e2a710898bd9b Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Dec 2025 12:17:30 -0500 Subject: [PATCH 1996/2435] ObjectSpace.{dump,dump_all,dump_shapes} needs vm barrier. (#15569) This allows these methods to be called from ractors. Add new exported function `rb_vm_lock_with_barrier()` that requires users to include "vm_sync.h" --- ext/objspace/objspace_dump.c | 56 +++++++++++++++++++++++++++++----- test/objspace/test_objspace.rb | 21 +++++++++++++ vm_sync.c | 11 +++++++ vm_sync.h | 4 +++ 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 30829d5ee10058..da64698346c495 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -29,7 +29,7 @@ #include "ruby/util.h" #include "ruby/io.h" #include "vm_callinfo.h" -#include "vm_core.h" +#include "vm_sync.h" RUBY_EXTERN const char ruby_hexdigits[]; @@ -771,15 +771,16 @@ dump_result(struct dump_config *dc) return dc->given_output; } -/* :nodoc: */ static VALUE -objspace_dump(VALUE os, VALUE obj, VALUE output) +dump_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE obj = ((VALUE*)args_p)[0]; + VALUE output = ((VALUE*)args_p)[1]; + if (!RB_SPECIAL_CONST_P(obj)) { dc.cur_page_slot_size = rb_gc_obj_slot_size(obj); } - dump_output(&dc, output, Qnil, Qnil, Qnil); dump_object(obj, &dc); @@ -787,6 +788,16 @@ objspace_dump(VALUE os, VALUE obj, VALUE output) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump(VALUE os, VALUE obj, VALUE output) +{ + VALUE args[2]; + args[0] = obj; + args[1] = output; + return rb_vm_lock_with_barrier(dump_locked, (void*)args); +} + static void shape_id_i(shape_id_t shape_id, void *data) { @@ -835,11 +846,15 @@ shape_id_i(shape_id_t shape_id, void *data) dump_append(dc, "}\n"); } -/* :nodoc: */ static VALUE -objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +dump_all_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE full = ((VALUE*)args_p)[1]; + VALUE since = ((VALUE*)args_p)[2]; + VALUE shapes = ((VALUE*)args_p)[3]; + dump_output(&dc, output, full, since, shapes); if (!dc.partial_dump || dc.since == 0) { @@ -860,9 +875,23 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) /* :nodoc: */ static VALUE -objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +{ + VALUE args[4]; + args[0] = output; + args[1] = full; + args[2] = since; + args[3] = shapes; + return rb_vm_lock_with_barrier(dump_all_locked, (void*)args); +} + +static VALUE +dump_shapes_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE shapes = ((VALUE*)args_p)[1]; + dump_output(&dc, output, Qfalse, Qnil, shapes); if (RTEST(shapes)) { @@ -871,6 +900,16 @@ objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +{ + VALUE args[2]; + args[0] = output; + args[1] = shapes; + return rb_vm_lock_with_barrier(dump_shapes_locked, (void*)args); +} + void Init_objspace_dump(VALUE rb_mObjSpace) { @@ -878,6 +917,9 @@ Init_objspace_dump(VALUE rb_mObjSpace) #if 0 rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ #endif +#ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); +#endif rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 4); diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index ea3c6aa7192146..3d5b2a4d2ae559 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -827,6 +827,27 @@ def dump_my_heap_please end end + def test_dump_all_with_ractors + assert_ractor("#{<<-"begin;"}#{<<-'end;'}") + begin; + require "objspace" + require "tempfile" + require "json" + rs = 4.times.map do + Ractor.new do + Tempfile.create do |f| + ObjectSpace.dump_all(output: f) + f.close + File.readlines(f.path).each do |line| + JSON.parse(line) + end + end + end + end + rs.each(&:join) + end; + end + def test_dump_uninitialized_file assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)| puts ObjectSpace.dump(File.allocate) diff --git a/vm_sync.c b/vm_sync.c index 6d93720701bad9..aca83dde5a73aa 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -297,3 +297,14 @@ rb_ec_vm_lock_rec_release(const rb_execution_context_t *ec, VM_ASSERT(recorded_lock_rec == rb_ec_vm_lock_rec(ec)); } + +VALUE +rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args) +{ + VALUE result = 0; + RB_VM_LOCKING() { + rb_vm_barrier(); + result = func(args); + } + return result; +} diff --git a/vm_sync.h b/vm_sync.h index 15dfff4d0b9fe1..314a2238a96581 100644 --- a/vm_sync.h +++ b/vm_sync.h @@ -28,6 +28,10 @@ void rb_vm_lock_leave_body_nb(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_lock_leave_body(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_barrier(void); +RUBY_SYMBOL_EXPORT_BEGIN +VALUE rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args); +RUBY_SYMBOL_EXPORT_END + #if RUBY_DEBUG // GET_VM() #include "vm_core.h" From 7e13fbc0ed013d03444158cda7e800e9c0eb4ddf Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 18 Dec 2025 03:04:49 +0900 Subject: [PATCH 1997/2435] Update bundled bigdecimal and rbs (#15611) * Bundle rbs-3.10.0.pre.1 * Update rbs gem entry with commit hash Updated rbs entry to include commit hash. * Fix rbs entry in bundled_gems * Update rbs gem to version 3.10.0.pre.2 Updated rbs gem version from 3.10.0.pre.1 to 3.10.0.pre.2. * Update bundled bigdecimal to v4.0.1 --------- Co-authored-by: Soutaro Matsumoto --- NEWS.md | 2 +- gems/bundled_gems | 4 +- .../library/bigdecimal/BigDecimal_spec.rb | 40 +----------- spec/ruby/library/bigdecimal/divmod_spec.rb | 61 +++++++++++++------ spec/ruby/library/bigdecimal/precs_spec.rb | 55 ----------------- tool/rbs_skip_tests | 53 +--------------- 6 files changed, 51 insertions(+), 164 deletions(-) delete mode 100644 spec/ruby/library/bigdecimal/precs_spec.rb diff --git a/NEWS.md b/NEWS.md index 6b18ae4c725ba5..a485c70ec53e3c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -337,7 +337,7 @@ The following bundled gems are updated. * typeprof 0.31.0 * debug 1.11.0 * base64 0.3.0 -* bigdecimal 3.3.1 +* bigdecimal 4.0.1 * drb 2.2.3 * syslog 0.3.0 * csv 3.3.5 diff --git a/gems/bundled_gems b/gems/bundled_gems index d71137d3e11fab..f393f19f1cd3d8 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -18,14 +18,14 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.9.5 https://github.com/ruby/rbs 22451ecbe262326176eb3915b64366712930685c # waiting for https://github.com/ruby/rbs/pull/2706 +rbs 3.10.0.pre.2 https://github.com/ruby/rbs typeprof 0.31.0 https://github.com/ruby/typeprof debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong base64 0.3.0 https://github.com/ruby/base64 -bigdecimal 3.3.1 https://github.com/ruby/bigdecimal +bigdecimal 4.0.1 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.1.1 https://github.com/ruby/resolv-replace diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb index 01772bf9af7d8e..8596356abdb444 100644 --- a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb +++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb @@ -31,16 +31,12 @@ end it "accepts significant digits >= given precision" do - suppress_warning do - BigDecimal("3.1415923", 10).precs[1].should >= 10 - end + BigDecimal("3.1415923", 10).should == BigDecimal("3.1415923") end it "determines precision from initial value" do pi_string = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" - suppress_warning { - BigDecimal(pi_string).precs[1] - }.should >= pi_string.size-1 + BigDecimal(pi_string).precision.should == pi_string.size-1 end it "ignores leading and trailing whitespace" do @@ -208,14 +204,6 @@ def to_s; "cheese"; end Float(@b).to_s.should == "166.66666666666666" end - it "has the expected precision on the LHS" do - suppress_warning { @a.precs[0] }.should == 18 - end - - it "has the expected maximum precision on the LHS" do - suppress_warning { @a.precs[1] }.should == 27 - end - it "produces the expected result when done via Float" do (Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10" end @@ -226,34 +214,10 @@ def to_s; "cheese"; end # Check underlying methods work as we understand - it "BigDecimal precision is the number of digits rounded up to a multiple of nine" do - 1.upto(100) do |n| - b = BigDecimal('4' * n) - precs, _ = suppress_warning { b.precs } - (precs >= 9).should be_true - (precs >= n).should be_true - (precs % 9).should == 0 - end - suppress_warning { BigDecimal('NaN').precs[0] }.should == 9 - end - - it "BigDecimal maximum precision is nine more than precision except for abnormals" do - 1.upto(100) do |n| - b = BigDecimal('4' * n) - precs, max = suppress_warning { b.precs } - max.should == precs + 9 - end - suppress_warning { BigDecimal('NaN').precs[1] }.should == 9 - end - it "BigDecimal(Rational, 18) produces the result we expect" do BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3" end - it "BigDecimal(Rational, BigDecimal.precs[0]) produces the result we expect" do - BigDecimal(@b, suppress_warning { @a.precs[0] }).to_s.should == "0.166666666666666667e3" - end - # Check the top-level expression works as we expect it "produces a BigDecimal" do diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb index c519783d145a00..85c014bb8c0c37 100644 --- a/spec/ruby/library/bigdecimal/divmod_spec.rb +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -33,14 +33,16 @@ class BigDecimal end end - it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod + version_is BigDecimal::VERSION, ""..."4.0.0" do + it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod + end it "raises ZeroDivisionError if other is zero" do bd5667 = BigDecimal("5667.19") - + zero = BigDecimal("0") -> { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError) -> { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError) - -> { @zero.mod_part_of_divmod(@zero) }.should raise_error(ZeroDivisionError) + -> { zero.mod_part_of_divmod(zero) }.should raise_error(ZeroDivisionError) end end @@ -73,14 +75,25 @@ class BigDecimal @zeroes = [@zero, @zero_pos, @zero_neg] end - it "divides value, returns an array" do - res = @a.divmod(5) - res.kind_of?(Array).should == true + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "divides value, returns [BigDecimal, BigDecimal]" do + res = @a.divmod(5) + res.kind_of?(Array).should == true + DivmodSpecs.check_both_bigdecimal(res) + end + end + + version_is BigDecimal::VERSION, "4.0.0" do + it "divides value, returns [Integer, BigDecimal]" do + res = @a.divmod(5) + res.kind_of?(Array).should == true + res[0].kind_of?(Integer).should == true + res[1].kind_of?(BigDecimal).should == true + end end it "array contains quotient and modulus as BigDecimal" do res = @a.divmod(5) - DivmodSpecs.check_both_bigdecimal(res) res[0].should == BigDecimal('0.8E1') res[1].should == BigDecimal('2.00000000000000000001') @@ -123,17 +136,27 @@ class BigDecimal values_and_zeroes.each do |val1| values.each do |val2| res = val1.divmod(val2) - DivmodSpecs.check_both_bigdecimal(res) res[0].should == ((val1/val2).floor) res[1].should == (val1 - res[0] * val2) end end end - it "returns an array of two NaNs if NaN is involved" do - (@special_vals + @regular_vals + @zeroes).each do |val| - DivmodSpecs.check_both_nan(val.divmod(@nan)) - DivmodSpecs.check_both_nan(@nan.divmod(val)) + version_is BigDecimal::VERSION, "4.0.0" do + it "raise FloatDomainError error if NaN is involved" do + (@special_vals + @regular_vals + @zeroes).each do |val| + -> { val.divmod(@nan) }.should raise_error(FloatDomainError) + -> { @nan.divmod(val) }.should raise_error(FloatDomainError) + end + end + end + + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "returns an array of two NaNs if NaN is involved" do + (@special_vals + @regular_vals + @zeroes).each do |val| + DivmodSpecs.check_both_nan(val.divmod(@nan)) + DivmodSpecs.check_both_nan(@nan.divmod(val)) + end end end @@ -145,12 +168,14 @@ class BigDecimal end end - it "returns an array of Infinity and NaN if the dividend is Infinity" do - @regular_vals.each do |val| - array = @infinity.divmod(val) - array.length.should == 2 - array[0].infinite?.should == (val > 0 ? 1 : -1) - array[1].should.nan? + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "returns an array of Infinity and NaN if the dividend is Infinity" do + @regular_vals.each do |val| + array = @infinity.divmod(val) + array.length.should == 2 + array[0].infinite?.should == (val > 0 ? 1 : -1) + array[1].should.nan? + end end end diff --git a/spec/ruby/library/bigdecimal/precs_spec.rb b/spec/ruby/library/bigdecimal/precs_spec.rb deleted file mode 100644 index 5fda8d30879a56..00000000000000 --- a/spec/ruby/library/bigdecimal/precs_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require_relative '../../spec_helper' -require 'bigdecimal' - -describe "BigDecimal#precs" do - before :each do - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero = BigDecimal("0") - @zero_neg = BigDecimal("-0") - - @arr = [BigDecimal("2E40001"), BigDecimal("3E-20001"),\ - @infinity, @infinity_neg, @nan, @zero, @zero_neg] - @precision = BigDecimal::BASE.to_s.length - 1 - end - - it "returns array of two values" do - suppress_warning do - @arr.each do |x| - x.precs.kind_of?(Array).should == true - x.precs.size.should == 2 - end - end - end - - it "returns Integers as array values" do - suppress_warning do - @arr.each do |x| - x.precs[0].kind_of?(Integer).should == true - x.precs[1].kind_of?(Integer).should == true - end - end - end - - it "returns the current value of significant digits as the first value" do - suppress_warning do - BigDecimal("3.14159").precs[0].should >= 6 - BigDecimal('1').precs[0].should == BigDecimal('1' + '0' * 100).precs[0] - [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| - value.precs[0].should <= @precision - end - end - end - - it "returns the maximum number of significant digits as the second value" do - suppress_warning do - BigDecimal("3.14159").precs[1].should >= 6 - BigDecimal('1').precs[1].should >= 1 - BigDecimal('1' + '0' * 100).precs[1].should >= 101 - [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| - value.precs[1].should >= 1 - end - end - end -end diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index 4a5307a03ee4bb..05ffb4da2771a5 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -46,53 +46,6 @@ test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby -# https://github.com/ruby/openssl/pull/774 -test_params(OpenSSLDHTest) - -# RBS isn't compatible with RDoc 6.13 -RDocPluginParserTest - -# https://github.com/ruby/json/pull/773 -test_load(JSONInstanceTest) -test_load(JSONSingletonTest) - -# https://github.com/ruby/json/pull/775 -test_fast_unparse(JSONInstanceTest) -test_pretty_unparse(JSONInstanceTest) -test_restore(JSONInstanceTest) -test_unparse(JSONInstanceTest) -test_fast_unparse(JSONSingletonTest) -test_pretty_unparse(JSONSingletonTest) -test_restore(JSONSingletonTest) -test_unparse(JSONSingletonTest) - -# https://github.com/ruby/json/pull/779 -test_iconv(JSONSingletonTest) - -# https://github.com/ruby/json/pull/774 -test_recurse_proc(JSONInstanceTest) -test_recurse_proc(JSONSingletonTest) - -test_deep_const_get(JSONSingletonTest) - -CGITest CGI is retired -CGISingletonTest CGI is retired - -RactorSingletonTest Ractor API was changed https://bugs.ruby-lang.org/issues/21262 -RactorInstanceTest Ractor API was changed https://bugs.ruby-lang.org/issues/21262 - -# https://github.com/ruby/fileutils/pull/139 -# https://github.com/ruby/actions/actions/runs/16425309325/job/46414287784 -test_ln_sr(FileUtilsSingletonTest) - -# https://github.com/ruby/ruby/pull/14303 -# NoMethodError: undefined method 'respond_to?' for an instance of RBS::UnitTest::Convertibles::ToStr -test_join(PathnameInstanceTest) -test_plus(PathnameInstanceTest) -test_relative_path_from(PathnameInstanceTest) -test_slash(PathnameInstanceTest) -test_Pathname(PathnameKernelTest) -test_initialize(PathnameSingletonTest) - -# [Feature #20925] `size` of Enumerator created by `produce` returns `nil` instead of `Float::INFINITY`, unless `size:` is specified. -test_produce(EnumeratorSingletonTest) +# BigDecimal v4.0.0 changed behavior +test_divmod(BigDecimalTest) +test_precs(BigDecimalTest) From a9526ab50ca693755cd841da1c73855448453dfb Mon Sep 17 00:00:00 2001 From: git Date: Wed, 17 Dec 2025 18:05:21 +0000 Subject: [PATCH 1998/2435] Update bundled gems list as of 2025-12-17 --- NEWS.md | 6 +++--- gems/bundled_gems | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index a485c70ec53e3c..70ef9c31eab2dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -271,7 +271,7 @@ The following bundled gems are promoted from default gems. * logger 1.7.0 * rdoc 6.17.0 * win32ole 1.9.2 -* irb 1.15.3 +* irb 1.16.0 * reline 0.6.3 * readline 0.0.4 * fiddle 1.1.8 @@ -329,11 +329,11 @@ The following bundled gems are updated. * test-unit 3.7.3 * rexml 3.4.4 * net-ftp 0.3.9 -* net-imap 0.5.12 +* net-imap 0.6.1 * net-smtp 0.5.1 * matrix 0.4.3 * prime 0.1.4 -* rbs 3.9.5 +* rbs 3.10.0.pre.2 * typeprof 0.31.0 * debug 1.11.0 * base64 0.3.0 diff --git a/gems/bundled_gems b/gems/bundled_gems index f393f19f1cd3d8..559b7126db1bfe 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,7 +13,7 @@ test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp -net-imap 0.5.12 https://github.com/ruby/net-imap +net-imap 0.6.1 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix @@ -41,7 +41,7 @@ benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger rdoc 6.17.0 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole -irb 1.15.3 https://github.com/ruby/irb +irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline readline 0.0.4 https://github.com/ruby/readline fiddle 1.1.8 https://github.com/ruby/fiddle From dc2a62e14c4691808f04ea0cb2de30c681e87923 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 17 Dec 2025 10:18:40 -0800 Subject: [PATCH 1999/2435] test_commit_email.rb: Ensure #teardown doesn't fail if test is omitted. Follow up c4e090def134f9b109991b74c027648564963763 --- tool/test/test_commit_email.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index c7031f6c6ef818..db441584fdaefe 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -36,9 +36,14 @@ def setup end def teardown - File.unlink(@sendmail) - Dir.rmdir(File.dirname(@sendmail)) - FileUtils.rm_rf(@ruby) + # Clean up temporary files if #setup was not omitted + if @sendmail + File.unlink(@sendmail) + Dir.rmdir(File.dirname(@sendmail)) + end + if @ruby + FileUtils.rm_rf(@ruby) + end end def test_sendmail_encoding From f7120860b0a76fd776ef4bf040e42a46c8aeff63 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 17 Dec 2025 10:44:28 -0800 Subject: [PATCH 2000/2435] test_sync_default_gems.rb: Omit if git is v2.43 or older --- tool/test/test_sync_default_gems.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index 4a2688850be12a..f9ab0aae4def92 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -2,6 +2,7 @@ require 'test/unit' require 'stringio' require 'tmpdir' +require 'rubygems/version' require_relative '../sync_default_gems' module Test_SyncDefaultGems @@ -319,9 +320,13 @@ def test_delete_after_conflict end def test_squash_merge - if RUBY_PLATFORM =~ /s390x/ - omit("git 2.43.0 bug on s390x ubuntu 24.04: BUG: log-tree.c:1058: did a remerge diff without remerge_objdir?!?") - end + # This test is known to fail with git 2.43.0, which is used by Ubuntu 24.04. + # We don't know which exact version fixed it, but we know git 2.52.0 works. + stdout, status = Open3.capture2('git', '--version', err: File::NULL) + omit 'git version check failed' unless status.success? + git_version = stdout.rstrip.delete_prefix('git version ') + omit "git #{git_version} is too old" if Gem::Version.new(git_version) < Gem::Version.new('2.44.0') + # 2---. <- branch # / \ # 1---3---3'<- merge commit with conflict resolution From 601ac78caf1523170b2f88767d67ffa5e8c5b1bc Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Dec 2025 14:00:58 -0500 Subject: [PATCH 2001/2435] [DOC] Small changes to docs for ObjectSpace#each_object (#15564) Change example to use user-defined class instead of `Numeric`. --- gc.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/gc.c b/gc.c index 5a14cb3675032d..d229c1f5aba254 100644 --- a/gc.c +++ b/gc.c @@ -1537,34 +1537,26 @@ os_obj_of(VALUE of) * Ruby process. If module is specified, calls the block * for only those classes or modules that match (or are a subclass of) * module. Returns the number of objects found. Immediate - * objects (Fixnums, Symbols - * true, false, and nil) are - * never returned. In the example below, #each_object returns both - * the numbers we defined and several constants defined in the Math - * module. + * objects (such as Fixnums, static Symbols + * true, false and nil) are + * never returned. * * If no block is given, an enumerator is returned instead. * - * a = 102.7 - * b = 95 # Won't be returned - * c = 12345678987654321 - * count = ObjectSpace.each_object(Numeric) {|x| p x } + * Job = Class.new + * jobs = [Job.new, Job.new] + * count = ObjectSpace.each_object(Job) {|x| p x } * puts "Total count: #{count}" * * produces: * - * 12345678987654321 - * 102.7 - * 2.71828182845905 - * 3.14159265358979 - * 2.22044604925031e-16 - * 1.7976931348623157e+308 - * 2.2250738585072e-308 - * Total count: 7 + * # + * # + * Total count: 2 * - * Due to a current known Ractor implementation issue, this method will not yield - * Ractor-unshareable objects in multi-Ractor mode (when - * Ractor.new has been called within the process at least once). + * Due to a current Ractor implementation issue, this method does not yield + * Ractor-unshareable objects when the process is in multi-Ractor mode. Multi-ractor + * mode is enabled when Ractor.new has been called for the first time. * See https://bugs.ruby-lang.org/issues/19387 for more information. * * a = 12345678987654321 # shareable From 839410f0734cb3ed911d348855f69ff6d856f62b Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Dec 2025 13:18:58 -0500 Subject: [PATCH 2002/2435] Fix heap dump with ractor barrier When a ractor was being initialized and it would join the heap dump barrier when allocating its queue or its ports, the heap dump code calls `rb_obj_memsize` on the ractor and this function assumed `ports` was never NULL. We need to check for the NULL case in case the ractor is still being initialized. Hopefully other T_DATA objects don't suffer from the same issue, otherwise we could revert the ractor barrier during heap dump or not use `rb_obj_memsize` on T_DATA during the heap dump. --- ractor_sync.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ractor_sync.c b/ractor_sync.c index 57ae13e88de50f..8c7c144c3fda97 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -699,7 +699,12 @@ ractor_sync_free(rb_ractor_t *r) static size_t ractor_sync_memsize(const rb_ractor_t *r) { - return st_table_size(r->sync.ports); + if (r->sync.ports) { + return st_table_size(r->sync.ports); + } + else { + return 0; + } } static void From ef3ac3e68c20276eed3de756c1260f43d94746d3 Mon Sep 17 00:00:00 2001 From: Victor Shepelev Date: Wed, 17 Dec 2025 21:22:55 +0200 Subject: [PATCH 2003/2435] Adjust Set documentation (#15547) --- .rdoc_options | 1 + set.c | 137 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/.rdoc_options b/.rdoc_options index 38dca221112c53..591ddf58897595 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -10,6 +10,7 @@ rdoc_include: exclude: - \.gemspec\z +- lib/set/subclass_compatible.rb generator_name: aliki diff --git a/set.c b/set.c index 6d200b5dfa1495..8bd8ae56327073 100644 --- a/set.c +++ b/set.c @@ -495,11 +495,11 @@ set_initialize_with_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set)) * If a block is given, the elements of enum are preprocessed by the * given block. * - * Set.new([1, 2]) #=> # - * Set.new([1, 2, 1]) #=> # - * Set.new([1, 'c', :s]) #=> # - * Set.new(1..5) #=> # - * Set.new([1, 2, 3]) { |x| x * x } #=> # + * Set.new([1, 2]) #=> Set[1, 2] + * Set.new([1, 2, 1]) #=> Set[1, 2] + * Set.new([1, 'c', :s]) #=> Set[1, "c", :s] + * Set.new(1..5) #=> Set[1, 2, 3, 4, 5] + * Set.new([1, 2, 3]) { |x| x * x } #=> Set[1, 4, 9] */ static VALUE set_i_initialize(int argc, VALUE *argv, VALUE set) @@ -595,11 +595,11 @@ set_inspect(VALUE set, VALUE dummy, int recur) * Returns a new string containing the set entries: * * s = Set.new - * s.inspect # => "#" + * s.inspect # => "Set[]" * s.add(1) - * s.inspect # => "#" + * s.inspect # => "Set[1]" * s.add(2) - * s.inspect # => "#" + * s.inspect # => "Set[1, 2]" * * Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting]. */ @@ -650,11 +650,11 @@ set_i_to_a(VALUE set) * call-seq: * to_set(klass = Set, *args, &block) -> self or new_set * - * Returns self if receiver is an instance of +Set+ and no arguments or - * block are given. Otherwise, converts the set to another with - * klass.new(self, *args, &block). + * Without arguments, returns +self+ (for duck-typing in methods that + * accept "set, or set-convertible" arguments). * - * In subclasses, returns `klass.new(self, *args, &block)` unless overridden. + * A form with arguments is _deprecated_. It converts the set to another + * with klass.new(self, *args, &block). */ static VALUE set_i_to_set(int argc, VALUE *argv, VALUE set) @@ -700,9 +700,9 @@ set_i_join(int argc, VALUE *argv, VALUE set) * Adds the given object to the set and returns self. Use `merge` to * add many elements at once. * - * Set[1, 2].add(3) #=> # - * Set[1, 2].add([3, 4]) #=> # - * Set[1, 2].add(2) #=> # + * Set[1, 2].add(3) #=> Set[1, 2, 3] + * Set[1, 2].add([3, 4]) #=> Set[1, 2, [3, 4]] + * Set[1, 2].add(2) #=> Set[1, 2] */ static VALUE set_i_add(VALUE set, VALUE item) @@ -726,8 +726,8 @@ set_i_add(VALUE set, VALUE item) * Adds the given object to the set and returns self. If the object is * already in the set, returns nil. * - * Set[1, 2].add?(3) #=> # - * Set[1, 2].add?([3, 4]) #=> # + * Set[1, 2].add?(3) #=> Set[1, 2, 3] + * Set[1, 2].add?([3, 4]) #=> Set[1, 2, [3, 4]] * Set[1, 2].add?(2) #=> nil */ static VALUE @@ -856,9 +856,9 @@ set_classify_i(st_data_t key, st_data_t tmp) * * files = Set.new(Dir.glob("*.rb")) * hash = files.classify { |f| File.mtime(f).year } - * hash #=> {2000 => #, - * # 2001 => #, - * # 2002 => #} + * hash #=> {2000 => Set["a.rb", "b.rb"], + * # 2001 => Set["c.rb", "d.rb", "e.rb"], + * # 2002 => Set["f.rb"]} * * Returns an enumerator if no block is given. */ @@ -959,10 +959,10 @@ static void set_merge_enum_into(VALUE set, VALUE arg); * * numbers = Set[1, 3, 4, 6, 9, 10, 11] * set = numbers.divide { |i,j| (i - j).abs == 1 } - * set #=> #, - * # #, - * # #}> - * # #, + * set #=> Set[Set[1], + * # Set[3, 4], + * # Set[6], + * # Set[9, 10, 11]] * * Returns an enumerator if no block is given. */ @@ -993,9 +993,9 @@ set_clear_i(st_data_t key, st_data_t dummy) * * Removes all elements and returns self. * - * set = Set[1, 'c', :s] #=> # - * set.clear #=> # - * set #=> # + * set = Set[1, 'c', :s] #=> Set[1, "c", :s] + * set.clear #=> Set[] + * set #=> Set[] */ static VALUE set_i_clear(VALUE set) @@ -1043,8 +1043,8 @@ set_intersection_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, data)) * Returns a new set containing elements common to the set and the given * enumerable object. * - * Set[1, 3, 5] & Set[3, 2, 1] #=> # - * Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> # + * Set[1, 3, 5] & Set[3, 2, 1] #=> Set[3, 1] + * Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> Set["a", "b"] */ static VALUE set_i_intersection(VALUE set, VALUE other) @@ -1285,8 +1285,8 @@ set_xor_i(st_data_t key, st_data_t data) * given enumerable object. (set ^ enum) is equivalent to * ((set | enum) - (set & enum)). * - * Set[1, 2] ^ Set[2, 3] #=> # - * Set[1, 'b', 'c'] ^ ['b', 'd'] #=> # + * Set[1, 2] ^ Set[2, 3] #=> Set[3, 1] + * Set[1, 'b', 'c'] ^ ['b', 'd'] #=> Set["d", 1, "c"] */ static VALUE set_i_xor(VALUE set, VALUE other) @@ -1312,8 +1312,8 @@ set_i_xor(VALUE set, VALUE other) * Returns a new set built by merging the set and the elements of the * given enumerable object. * - * Set[1, 2, 3] | Set[2, 4, 5] #=> # - * Set[1, 5, 'z'] | (1..6) #=> # + * Set[1, 2, 3] | Set[2, 4, 5] #=> Set[1, 2, 3, 4, 5] + * Set[1, 5, 'z'] | (1..6) #=> Set[1, 5, "z", 2, 3, 4, 6] */ static VALUE set_i_union(VALUE set, VALUE other) @@ -1371,8 +1371,8 @@ set_i_subtract(VALUE set, VALUE other) * Returns a new set built by duplicating the set, removing every * element that appears in the given enumerable object. * - * Set[1, 3, 5] - Set[1, 5] #=> # - * Set['a', 'b', 'z'] - ['a', 'c'] #=> # + * Set[1, 3, 5] - Set[1, 5] #=> Set[3] + * Set['a', 'b', 'z'] - ['a', 'c'] #=> Set["b", "z"] */ static VALUE set_i_difference(VALUE set, VALUE other) @@ -1488,9 +1488,9 @@ set_i_select(VALUE set) * Replaces the contents of the set with the contents of the given * enumerable object and returns self. * - * set = Set[1, 'c', :s] #=> # - * set.replace([1, 2]) #=> # - * set #=> # + * set = Set[1, 'c', :s] #=> Set[1, "c", :s] + * set.replace([1, 2]) #=> Set[1, 2] + * set #=> Set[1, 2] */ static VALUE set_i_replace(VALUE set, VALUE other) @@ -1992,10 +1992,10 @@ rb_set_size(VALUE set) * duplicates. It is a hybrid of Array's intuitive inter-operation * facilities and Hash's fast lookup. * - * Set is easy to use with Enumerable objects (implementing `each`). + * Set is easy to use with Enumerable objects (implementing #each). * Most of the initializer methods and binary operators accept generic * Enumerable objects besides sets and arrays. An Enumerable object - * can be converted to Set using the `to_set` method. + * can be converted to Set using the +to_set+ method. * * Set uses a data structure similar to Hash for storage, except that * it only has keys and no values. @@ -2019,11 +2019,11 @@ rb_set_size(VALUE set) * * == Example * - * s1 = Set[1, 2] #=> # - * s2 = [1, 2].to_set #=> # + * s1 = Set[1, 2] #=> Set[1, 2] + * s2 = [1, 2].to_set #=> Set[1, 2] * s1 == s2 #=> true - * s1.add("foo") #=> # - * s1.merge([2, 6]) #=> # + * s1.add("foo") #=> Set[1, 2, "foo"] + * s1.merge([2, 6]) #=> Set[1, 2, "foo", 6] * s1.subset?(s2) #=> false * s2.subset?(s1) #=> true * @@ -2031,9 +2031,39 @@ rb_set_size(VALUE set) * * - Akinori MUSHA (current maintainer) * - * == What's Here + * == Inheriting from \Set * - * First, what's elsewhere. \Class \Set: + * Before Ruby 4.0 (released December 2025), \Set had a different, less + * efficient implementation. It was reimplemented in C, and the behavior + * of some of the core methods were adjusted. + * + * To keep backward compatibility, when a class is inherited from \Set, + * additional module +Set::SubclassCompatible+ is included, which makes + * the inherited class behavior, as well as internal method names, + * closer to what it was before Ruby 4.0. + * + * It can be easily seen, for example, in the #inspect method behavior: + * + * p Set[1, 2, 3] + * # prints "Set[1, 2, 3]" + * + * class MySet < Set + * end + * p MySet[1, 2, 3] + * # prints "#", like it was in Ruby 3.4 + * + * For new code, if backward compatibility is not necessary, + * it is recommended to instead inherit from +Set::CoreSet+, which + * avoids including the "compatibility" layer: + * + * class MyCoreSet < Set::CoreSet + * end + * p MyCoreSet[1, 2, 3] + * # prints "MyCoreSet[1, 2, 3]" + * + * == Set's methods + * + * First, what's elsewhere. \Class \Set: * * - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. * - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here], @@ -2045,16 +2075,15 @@ rb_set_size(VALUE set) * * Here, class \Set provides methods that are useful for: * - * - {Creating an Array}[rdoc-ref:Array@Methods+for+Creating+an+Array] * - {Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set] * - {Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations] - * - {Comparing}[rdoc-ref:Array@Methods+for+Comparing] - * - {Querying}[rdoc-ref:Array@Methods+for+Querying] - * - {Assigning}[rdoc-ref:Array@Methods+for+Assigning] - * - {Deleting}[rdoc-ref:Array@Methods+for+Deleting] - * - {Converting}[rdoc-ref:Array@Methods+for+Converting] - * - {Iterating}[rdoc-ref:Array@Methods+for+Iterating] - * - {And more....}[rdoc-ref:Array@Other+Methods] + * - {Comparing}[rdoc-ref:Set@Methods+for+Comparing] + * - {Querying}[rdoc-ref:Set@Methods+for+Querying] + * - {Assigning}[rdoc-ref:Set@Methods+for+Assigning] + * - {Deleting}[rdoc-ref:Set@Methods+for+Deleting] + * - {Converting}[rdoc-ref:Set@Methods+for+Converting] + * - {Iterating}[rdoc-ref:Set@Methods+for+Iterating] + * - {And more....}[rdoc-ref:Set@Other+Methods] * * === Methods for Creating a \Set * From 656de67d5c8cd894eeda0baa618663098dce17f2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 17 Dec 2025 13:51:12 -0500 Subject: [PATCH 2004/2435] JITs: Pass down GNU make jobserver resources when appropriate To fix warnings from rustc on e.g. Make 4.3, which is in Ubuntu 24.04: > warning: failed to connect to jobserver from environment variable --- defs/jit.mk | 11 ++++++++--- yjit/yjit.mk | 4 ++-- zjit/zjit.mk | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/defs/jit.mk b/defs/jit.mk index e893098ca26486..42b56c4cd928b6 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -1,4 +1,9 @@ # Make recipes that deal with the rust code of YJIT and ZJIT. +# +# $(gnumake_recursive) adds the '+' prefix to pass down GNU make's +# jobserver resources to cargo/rustc as rust-lang.org recommends. +# Without it, certain make version trigger a warning. It does not +# add the prefix when `make --dry-run` so dry runs are indeed dry. ifneq ($(JIT_CARGO_SUPPORT),no) @@ -25,7 +30,7 @@ $(RUST_LIB): $(srcdir)/ruby.rs elif [ '$(YJIT_SUPPORT)' != no ]; then \ echo 'building YJIT ($(JIT_CARGO_SUPPORT) mode)'; \ fi - +$(Q)CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \ + $(gnumake_recursive)$(Q)CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \ CARGO_TERM_PROGRESS_WHEN='never' \ MACOSX_DEPLOYMENT_TARGET=11.0 \ $(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS) @@ -34,7 +39,7 @@ else ifneq ($(strip $(RLIB_DIR)),) # combo build $(RUST_LIB): $(srcdir)/ruby.rs $(ECHO) 'building $(@F)' - $(Q) $(RUSTC) --edition=2024 \ + $(gnumake_recursive)$(Q) $(RUSTC) --edition=2024 \ '-L$(@D)' \ --extern=yjit \ --extern=zjit \ @@ -50,7 +55,7 @@ $(YJIT_RLIB): $(JIT_RLIB) $(ZJIT_RLIB): $(JIT_RLIB) $(JIT_RLIB): $(ECHO) 'building $(@F)' - $(Q) $(RUSTC) --crate-name=jit \ + $(gnumake_recursive)$(Q) $(RUSTC) --crate-name=jit \ --edition=2024 \ $(JIT_RUST_FLAGS) \ '--out-dir=$(@D)' \ diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 22d256fee78c14..21fd96514bdca9 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -18,14 +18,14 @@ ifneq ($(strip $(YJIT_LIBS)),) yjit-libs: $(BUILD_YJIT_LIBS) $(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' - $(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) + $(gnumake_recursive)$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) else ifneq ($(strip $(RLIB_DIR)),) # combo build # Absolute path to avoid VPATH ambiguity YJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libyjit.rlib $(YJIT_RLIB): $(YJIT_SRC_FILES) $(ECHO) 'building $(@F)' - $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(YJIT_RUSTC_ARGS) + $(gnumake_recursive)$(Q) $(RUSTC) '-L$(@D)' --extern=jit $(YJIT_RUSTC_ARGS) $(RUST_LIB): $(YJIT_RLIB) endif # ifneq ($(strip $(YJIT_LIBS)),) diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 2116775a91a182..58b45d87872d94 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -22,14 +22,14 @@ BUILD_ZJIT_LIBS = $(TOP_BUILD_DIR)/$(ZJIT_LIBS) ifneq ($(strip $(ZJIT_LIBS)),) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ECHO) 'building Rust ZJIT (release mode)' - $(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) + $(gnumake_recursive)$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) else ifneq ($(strip $(RLIB_DIR)),) # combo build # Absolute path to avoid VPATH ambiguity ZJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libzjit.rlib $(ZJIT_RLIB): $(ZJIT_SRC_FILES) $(ECHO) 'building $(@F)' - $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(ZJIT_RUSTC_ARGS) + $(gnumake_recursive)$(Q) $(RUSTC) '-L$(@D)' --extern=jit $(ZJIT_RUSTC_ARGS) $(RUST_LIB): $(ZJIT_RLIB) endif # ifneq ($(strip $(ZJIT_LIBS)),) From cfa3e7cf75531a8c90b39bbd9b977f30315b12e7 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Thu, 18 Dec 2025 01:34:32 +0100 Subject: [PATCH 2005/2435] [DOC] Fix double-word typos in comments Found via `grep` for repeated words. * set.c: Fix "or or" * include/ruby/internal/symbol.h: Fix "is is" * include/ruby/internal/ctype.h: Fix "in in" --- include/ruby/internal/ctype.h | 2 +- include/ruby/internal/symbol.h | 2 +- set.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ruby/internal/ctype.h b/include/ruby/internal/ctype.h index 0f7ca6c51635d0..93e92801fcc5a3 100644 --- a/include/ruby/internal/ctype.h +++ b/include/ruby/internal/ctype.h @@ -498,7 +498,7 @@ RBIMPL_ATTR_ARTIFICIAL() * Our own locale-insensitive version of `tolower(3)`. * * @param[in] c Byte in question to convert. - * @retval c The byte is not listed in in IEEE 1003.1 section + * @retval c The byte is not listed in IEEE 1003.1 section * 7.3.1.1 "upper". * @retval otherwise Byte converted using the map defined in IEEE 1003.1 * section 7.3.1 "tolower". diff --git a/include/ruby/internal/symbol.h b/include/ruby/internal/symbol.h index 569bf215a2df85..858c5e290f6463 100644 --- a/include/ruby/internal/symbol.h +++ b/include/ruby/internal/symbol.h @@ -187,7 +187,7 @@ ID rb_check_id(volatile VALUE *namep); * * :FIXME: Can anyone tell us what is the difference between this one and * rb_intern_str()? As far as @shyouhei reads the implementation it seems what - * rb_to_id() does is is just waste some CPU time, then call rb_intern_str(). + * rb_to_id() does is just waste some CPU time, then call rb_intern_str(). * He hopes he is wrong. */ ID rb_to_id(VALUE str); diff --git a/set.c b/set.c index 8bd8ae56327073..734a6ecaea2107 100644 --- a/set.c +++ b/set.c @@ -1766,7 +1766,7 @@ set_i_disjoint(VALUE set, VALUE other) * set <=> other -> -1, 0, 1, or nil * * Returns 0 if the set are equal, -1 / 1 if the set is a - * proper subset / superset of the given set, or or nil if + * proper subset / superset of the given set, or nil if * they both have unique elements. */ static VALUE From f2d2a757d1b3a11b68e60ee08162e58f59837bb3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 18 Dec 2025 10:14:40 +0900 Subject: [PATCH 2006/2435] [DOC] Re-fill the paragraph --- include/ruby/internal/ctype.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ruby/internal/ctype.h b/include/ruby/internal/ctype.h index 93e92801fcc5a3..8b24026311e6a5 100644 --- a/include/ruby/internal/ctype.h +++ b/include/ruby/internal/ctype.h @@ -498,8 +498,8 @@ RBIMPL_ATTR_ARTIFICIAL() * Our own locale-insensitive version of `tolower(3)`. * * @param[in] c Byte in question to convert. - * @retval c The byte is not listed in IEEE 1003.1 section - * 7.3.1.1 "upper". + * @retval c The byte is not listed in IEEE 1003.1 section 7.3.1.1 + * "upper". * @retval otherwise Byte converted using the map defined in IEEE 1003.1 * section 7.3.1 "tolower". * @note Not only does this function works under the POSIX Locale, but From b816f7bac5f2dfb10c7a468c14cfcdedaab2f7ae Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 18 Dec 2025 11:27:05 +0900 Subject: [PATCH 2007/2435] [DOC] Fix documents of `rb_intern_str` and so on * `rb_intern_str`: the argument must be `T_STRING`, no conversion. * `rb_intern_str`, `rb_check_id`, `rb_to_id`, `rb_check_symbol`: raise `EncodingError` unless the "name" argument is a valid string in its encoding. --- include/ruby/internal/symbol.h | 43 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/include/ruby/internal/symbol.h b/include/ruby/internal/symbol.h index 858c5e290f6463..8bfd686fbe6487 100644 --- a/include/ruby/internal/symbol.h +++ b/include/ruby/internal/symbol.h @@ -101,12 +101,11 @@ ID rb_intern(const char *name); ID rb_intern2(const char *name, long len); /** - * Identical to rb_intern(), except it takes an instance of ::rb_cString. + * Identical to rb_intern(), except it takes a `T_STRING` object. * * @param[in] str The name of the id. - * @pre `str` must either be an instance of ::rb_cSymbol, or an instance - * of ::rb_cString, or responds to `#to_str` method. - * @exception rb_eTypeError Can't convert `str` into ::rb_cString. + * @pre `rb_type(str)` must be `T_STRING`. + * @exception rb_eEncodingError `str` contains invalid character(s). * @exception rb_eRuntimeError Too many symbols. * @return A (possibly new) id whose value is the given str. * @note These days Ruby internally has two kinds of symbols @@ -166,29 +165,34 @@ RBIMPL_ATTR_NONNULL(()) * of ::rb_cSymbol, or an instance of ::rb_cString, or responds * to `#to_str` method. * @exception rb_eTypeError Can't convert `*namep` into ::rb_cString. - * @exception rb_eEncodingError Given string is non-ASCII. + * @exception rb_eEncodingError Given string contains invalid character(s). * @retval 0 No such id ever existed in the history. * @retval otherwise The id that represents the given name. * @post The object that `*namep` points to is a converted result * object, which is always an instance of either ::rb_cSymbol * or ::rb_cString. + * @see rb_str_to_str * @see https://bugs.ruby-lang.org/issues/5072 - * - * @internal - * - * @shyouhei doesn't know why this has to raise rb_eEncodingError. */ ID rb_check_id(volatile VALUE *namep); /** - * @copydoc rb_intern_str() + * Identical to rb_intern_str(), except it tries to convert the parameter object + * to an instance of ::rb_cString or its subclasses. * - * @internal - * - * :FIXME: Can anyone tell us what is the difference between this one and - * rb_intern_str()? As far as @shyouhei reads the implementation it seems what - * rb_to_id() does is just waste some CPU time, then call rb_intern_str(). - * He hopes he is wrong. + * @param[in] str The name of the id. + * @pre `str` must either be an instance of ::rb_cSymbol, or an instance + * of ::rb_cString, or responds to `#to_str` method. + * @exception rb_eTypeError Can't convert `str` into ::rb_cString. + * @exception rb_eEncodingError Given string contains invalid character(s). + * @exception rb_eRuntimeError Too many symbols. + * @return A (possibly new) id whose value is the given str. + * @note These days Ruby internally has two kinds of symbols + * (static/dynamic). Symbols created using this function would + * become static ones; i.e. would never be garbage collected. It + * is up to you to avoid memory leaks. Think twice before using + * it. + * @see rb_str_to_str */ ID rb_to_id(VALUE str); @@ -245,17 +249,14 @@ RBIMPL_ATTR_NONNULL(()) * of ::rb_cSymbol, or an instance of ::rb_cString, or responds * to `#to_str` method. * @exception rb_eTypeError Can't convert `*namep` into ::rb_cString. - * @exception rb_eEncodingError Given string is non-ASCII. + * @exception rb_eEncodingError Given string contains invalid character(s). * @retval RUBY_Qnil No such id ever existed in the history. * @retval otherwise The id that represents the given name. * @post The object that `*namep` points to is a converted result * object, which is always an instance of either ::rb_cSymbol * or ::rb_cString. * @see https://bugs.ruby-lang.org/issues/5072 - * - * @internal - * - * @shyouhei doesn't know why this has to raise rb_eEncodingError. + * @see rb_str_to_str */ VALUE rb_check_symbol(volatile VALUE *namep); RBIMPL_SYMBOL_EXPORT_END() From 769c6a1c54e68d0100054e51fc25e1a6355645c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 15 Dec 2025 14:47:54 +0100 Subject: [PATCH 2008/2435] [DOC] Use Arrays in examples for Array#find --- array.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/array.c b/array.c index b9d91d0a9b296f..27b84249bf44fe 100644 --- a/array.c +++ b/array.c @@ -2098,13 +2098,13 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * With a block given, calls the block with successive elements of the array; * returns the first element for which the block returns a truthy value: * - * (0..9).find {|element| element > 2} # => 3 + * [1, 3, 5].find {|element| element > 2} # => 3 * * If no such element is found, calls +if_none_proc+ and returns its return value. * - * (0..9).find(proc {false}) {|element| element > 12} # => false - * {foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1] - * {foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * [1, 3, 5].find(proc {false}) {|element| element > 12} # => false + * [[:foo, 0], [:bar, 1], [:baz, 2]].find {|key, value| key.start_with?('b') } # => [:bar, 1] + * [[:foo, 0], [:bar, 1], [:baz, 2]].find(proc {[]}) {|key, value| key.start_with?('c') } # => [] * * With no block given, returns an Enumerator. * From 74b18b538247f8c711bc76f881cfe6954b288bb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 02:21:18 +0000 Subject: [PATCH 2009/2435] Bump github.com/microsoft/vcpkg from master to 2025.12.12 Bumps [github.com/microsoft/vcpkg](https://github.com/microsoft/vcpkg) from master to 2025.12.12. This release includes the previously tagged commit. - [Release notes](https://github.com/microsoft/vcpkg/releases) - [Commits](https://github.com/microsoft/vcpkg/compare/74e6536215718009aae747d86d84b78376bf9e09...84bab45d415d22042bd0b9081aea57f362da3f35) --- updated-dependencies: - dependency-name: github.com/microsoft/vcpkg dependency-version: 2025.12.12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 1e4b65d89c9632..b31cf27ba86414 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,5 +7,5 @@ "openssl", "zlib" ], - "builtin-baseline": "74e6536215718009aae747d86d84b78376bf9e09" + "builtin-baseline": "84bab45d415d22042bd0b9081aea57f362da3f35" } \ No newline at end of file From 85ff21c9e5986c52814b7e5ed3402e2a964c542e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 18 Dec 2025 15:42:36 +0900 Subject: [PATCH 2010/2435] RBOOL is unnecessary in C boolean context Fix a `-Wint-in-bool-context` warning. ``` proc.c:688:33: warning: '?:' using integer constants in boolean context [-Wint-in-bool-context] 688 | if (RBOOL(get_local_variable_ptr(&env, idItImplicit, FALSE))) { ``` --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index aa52cd4a550793..8a7a6ba8630822 100644 --- a/proc.c +++ b/proc.c @@ -685,7 +685,7 @@ bind_implicit_parameters(VALUE bindval) GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); - if (RBOOL(get_local_variable_ptr(&env, idItImplicit, FALSE))) { + if (get_local_variable_ptr(&env, idItImplicit, FALSE)) { return rb_ary_new_from_args(1, ID2SYM(idIt)); } From 01e9f95cc3d796bc7bf4f809016712c52f330d74 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Fri, 12 Dec 2025 13:54:42 -0500 Subject: [PATCH 2011/2435] [ruby/delegate] Allow use of DelegateClass in ractors Tempfile uses DelegateClass and Tempfile should be able to be used by different ractors. https://github.com/ruby/delegate/commit/cad194260b --- lib/delegate.rb | 10 ++++++++-- test/test_delegate.rb | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 838de675f1f5e3..b5224ea15f9f11 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -345,10 +345,16 @@ def __setobj__(obj) end def Delegator.delegating_block(mid) # :nodoc: - lambda do |*args, &block| + prok = lambda do |*args, &block| target = self.__getobj__ target.__send__(mid, *args, &block) - end.ruby2_keywords + end + prok.ruby2_keywords + if defined?(Ractor.shareable_proc) + Ractor.shareable_proc(&prok) + else + prok + end end # diff --git a/test/test_delegate.rb b/test/test_delegate.rb index ff7998ee43b372..ca170e392f8c35 100644 --- a/test/test_delegate.rb +++ b/test/test_delegate.rb @@ -390,4 +390,20 @@ def test(a, k:) [a, k]; end a = DelegateClass(k).new(k.new) assert_equal([1, 0], a.test(1, k: 0)) end + + def test_delegate_class_can_be_used_in_ractors + omit "no Ractor#value" unless defined?(Ractor) && Ractor.method_defined?(:value) + require_path = File.expand_path(File.join(__dir__, "..", "lib", "delegate.rb")) + raise "file doesn't exist: #{require_path}" unless File.exist?(require_path) + assert_ractor <<-RUBY + require "#{require_path}" + class MyClass < DelegateClass(Array);end + values = 2.times.map do + Ractor.new do + MyClass.new([1,2,3]).at(0) + end + end.map(&:value) + assert_equal [1,1], values + RUBY + end end From bdf99bf0dc3adf15f653dd8c02fce9d6ba946af5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 12:59:04 +0900 Subject: [PATCH 2012/2435] [ruby/delegate] v0.5.0 https://github.com/ruby/delegate/commit/fa35b20eca --- lib/delegate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index b5224ea15f9f11..7c403b2747d403 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -40,7 +40,7 @@ # class Delegator < BasicObject # The version string - VERSION = "0.4.0" + VERSION = "0.5.0" kernel = ::Kernel.dup kernel.class_eval do From a21fe2adfe3b4e4496f89e35d5a5a8cf11bf1ca0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 12:56:19 +0900 Subject: [PATCH 2013/2435] [ruby/delegate] Reapply "Merge pull request #46 from byroot/use-forward-send" This reverts commit https://github.com/ruby/delegate/commit/fc2bd0498af0. https://github.com/ruby/delegate/commit/7d5c1e0842 Co-authored-by: Jean Boussier --- lib/delegate.rb | 24 +++++++++++++++++++----- test/test_delegate.rb | 19 ++++++++++++++++--- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 7c403b2747d403..5ba94da0605a44 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -405,6 +405,17 @@ def DelegateClass(superclass, &block) protected_instance_methods -= ignores public_instance_methods = superclass.public_instance_methods public_instance_methods -= ignores + + normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } + + source = normal.map do |method| + "def #{method}(...); __getobj__.#{method}(...); end" + end + + protected_instance_methods.each do |method| + source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end" + end + klass.module_eval do def __getobj__ # :nodoc: unless defined?(@delegate_dc_obj) @@ -413,18 +424,21 @@ def __getobj__ # :nodoc: end @delegate_dc_obj end + def __setobj__(obj) # :nodoc: __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_dc_obj = obj end - protected_instance_methods.each do |method| - define_method(method, Delegator.delegating_block(method)) - protected method - end - public_instance_methods.each do |method| + + class_eval(source.join(";"), __FILE__, __LINE__) + + special.each do |method| define_method(method, Delegator.delegating_block(method)) end + + protected(*protected_instance_methods) end + klass.define_singleton_method :public_instance_methods do |all=true| super(all) | superclass.public_instance_methods end diff --git a/test/test_delegate.rb b/test/test_delegate.rb index ca170e392f8c35..903265a8f7c93f 100644 --- a/test/test_delegate.rb +++ b/test/test_delegate.rb @@ -93,15 +93,21 @@ def test_override end class Parent - def parent_public; end + def parent_public + :public + end protected - def parent_protected; end + def parent_protected + :protected + end private - def parent_private; end + def parent_private + :private + end end class Child < DelegateClass(Parent) @@ -157,6 +163,13 @@ def test_DelegateClass_public_instance_method assert_instance_of UnboundMethod, Child.public_instance_method(:to_s) end + def test_call_visibiltiy + obj = Child.new(Parent.new) + assert_equal :public, obj.parent_public + assert_equal :protected, obj.__send__(:parent_protected) + assert_raise(NoMethodError) { obj.__send__(:parent_private) } + end + class IV < DelegateClass(Integer) attr_accessor :var From e6ca8908c1c2a53e5d73c1628f42c84d87b2a5d0 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Dec 2025 16:34:16 -0500 Subject: [PATCH 2014/2435] Allow use of DelegateClass in ractor Use `eval` instead of `define_method` when defining delegate methods for `DelegateClass`. --- lib/delegate.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 5ba94da0605a44..8472bb2bd247d4 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -344,17 +344,15 @@ def __setobj__(obj) end end -def Delegator.delegating_block(mid) # :nodoc: - prok = lambda do |*args, &block| +def Delegator.delegating_code(mid) # :nodoc: + line = __LINE__+2 + src = <<~RUBY + ruby2_keywords def #{mid}(*args, &block) target = self.__getobj__ - target.__send__(mid, *args, &block) - end - prok.ruby2_keywords - if defined?(Ractor.shareable_proc) - Ractor.shareable_proc(&prok) - else - prok + target.__send__(:'#{mid}', *args, &block) end + RUBY + [src, __FILE__, line] end # @@ -433,7 +431,7 @@ def __setobj__(obj) # :nodoc: class_eval(source.join(";"), __FILE__, __LINE__) special.each do |method| - define_method(method, Delegator.delegating_block(method)) + module_eval(*Delegator.delegating_code(method)) end protected(*protected_instance_methods) From 1e69d688e59e6fb82efff41f966be3b55561b349 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Dec 2025 13:58:04 +0900 Subject: [PATCH 2015/2435] [ruby/delegate] v0.6.0 https://github.com/ruby/delegate/commit/aef34e8c8b --- lib/delegate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 8472bb2bd247d4..358c4fc876f4e1 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -40,7 +40,7 @@ # class Delegator < BasicObject # The version string - VERSION = "0.5.0" + VERSION = "0.6.0" kernel = ::Kernel.dup kernel.class_eval do From 2fcad967aab5aae921b2288c8080096d79ddb8dd Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Dec 2025 14:16:49 +0900 Subject: [PATCH 2016/2435] Revert "Allow use of DelegateClass in ractor" This reverts commit 6e0f2b31f0f4a2a942f3c1daad1bb64852fe6815. --- lib/delegate.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index 358c4fc876f4e1..c2fe8278001daf 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -344,15 +344,17 @@ def __setobj__(obj) end end -def Delegator.delegating_code(mid) # :nodoc: - line = __LINE__+2 - src = <<~RUBY - ruby2_keywords def #{mid}(*args, &block) +def Delegator.delegating_block(mid) # :nodoc: + prok = lambda do |*args, &block| target = self.__getobj__ - target.__send__(:'#{mid}', *args, &block) + target.__send__(mid, *args, &block) + end + prok.ruby2_keywords + if defined?(Ractor.shareable_proc) + Ractor.shareable_proc(&prok) + else + prok end - RUBY - [src, __FILE__, line] end # @@ -431,7 +433,7 @@ def __setobj__(obj) # :nodoc: class_eval(source.join(";"), __FILE__, __LINE__) special.each do |method| - module_eval(*Delegator.delegating_code(method)) + define_method(method, Delegator.delegating_block(method)) end protected(*protected_instance_methods) From 000659140786f1c74075b1b91ce03897cf525181 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Dec 2025 14:17:23 +0900 Subject: [PATCH 2017/2435] Reapply "Merge pull request #52 from ruby/revert-49" This reverts commit 02e4b58b615d0dd83a6af5cd7c2b8861724011ee. --- lib/delegate.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index c2fe8278001daf..ba53cc9bb79f98 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -345,16 +345,10 @@ def __setobj__(obj) end def Delegator.delegating_block(mid) # :nodoc: - prok = lambda do |*args, &block| + lambda do |*args, &block| target = self.__getobj__ target.__send__(mid, *args, &block) - end - prok.ruby2_keywords - if defined?(Ractor.shareable_proc) - Ractor.shareable_proc(&prok) - else - prok - end + end.ruby2_keywords end # From 0e85881e0cded6ec82ef105b5ff2d0c6f7fdcbc8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 18 Dec 2025 15:20:19 +0900 Subject: [PATCH 2018/2435] [ruby/delegate] v0.6.1 https://github.com/ruby/delegate/commit/90ffceb6d6 --- lib/delegate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/delegate.rb b/lib/delegate.rb index ba53cc9bb79f98..0cc3ddb1b0696c 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -40,7 +40,7 @@ # class Delegator < BasicObject # The version string - VERSION = "0.6.0" + VERSION = "0.6.1" kernel = ::Kernel.dup kernel.class_eval do From 9f266ae6740ee843b073ee341220a866e8e6b67e Mon Sep 17 00:00:00 2001 From: git Date: Thu, 18 Dec 2025 07:47:43 +0000 Subject: [PATCH 2019/2435] Update default gems list at 0e85881e0cded6ec82ef105b5ff2d0 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 70ef9c31eab2dc..ebba5de74dad6c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -291,6 +291,7 @@ The following default gems are updated. * RubyGems 4.0.2 * bundler 4.0.2 * date 3.5.1 +* delegate 0.6.1 * digest 3.2.1 * english 0.8.1 * erb 6.0.1 From f133ebb2db664801f87efa98aa91d610d194b700 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 18 Dec 2025 16:12:47 +0000 Subject: [PATCH 2020/2435] Bump RDoc to 7.0.1 (#15628) This improves several enhancements to the Aliki theme. And since Aliki also became the default theme, we don't need to specify the generator name anymore. --- .rdoc_options | 2 -- NEWS.md | 2 +- gems/bundled_gems | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.rdoc_options b/.rdoc_options index 591ddf58897595..89265cafd4d0f4 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -12,8 +12,6 @@ exclude: - \.gemspec\z - lib/set/subclass_compatible.rb -generator_name: aliki - autolink_excluded_words: - Box - Class diff --git a/NEWS.md b/NEWS.md index ebba5de74dad6c..a7a94d94287f2c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -269,7 +269,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.17.0 +* rdoc 7.0.1 * win32ole 1.9.2 * irb 1.16.0 * reline 0.6.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index 559b7126db1bfe..6717d7e1924180 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.17.0 https://github.com/ruby/rdoc +rdoc 7.0.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From bfd28d581c524c7a7df877f2425de9fdd8de161a Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 12:37:27 -0500 Subject: [PATCH 2021/2435] make rb_singleton_class ractor safe (#15591) Since singleton classes are created lazily, we need to make sure that we lock around their creation. Unfortunately, that means we need to lock around every shareable object's call to `singleton_class`, including classes and modules. --- class.c | 37 +++++++++++++++++++++++-------------- depend | 1 + test/ruby/test_class.rb | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/class.c b/class.c index 9716ba07dae2e2..840bdeb0c2f9e6 100644 --- a/class.c +++ b/class.c @@ -30,6 +30,7 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "ruby/ractor.h" #include "yjit.h" #include "zjit.h" @@ -2823,7 +2824,7 @@ rb_special_singleton_class(VALUE obj) * consistency of the metaclass hierarchy. */ static VALUE -singleton_class_of(VALUE obj) +singleton_class_of(VALUE obj, bool ensure_eigenclass) { VALUE klass; @@ -2851,13 +2852,26 @@ singleton_class_of(VALUE obj) } } - klass = METACLASS_OF(obj); - if (!(RCLASS_SINGLETON_P(klass) && - RCLASS_ATTACHED_OBJECT(klass) == obj)) { - klass = rb_make_metaclass(obj, klass); + bool needs_lock = rb_multi_ractor_p() && rb_ractor_shareable_p(obj); + unsigned int lev; + if (needs_lock) { + RB_VM_LOCK_ENTER_LEV(&lev); + } + { + klass = METACLASS_OF(obj); + if (!(RCLASS_SINGLETON_P(klass) && + RCLASS_ATTACHED_OBJECT(klass) == obj)) { + klass = rb_make_metaclass(obj, klass); + } + RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); + if (ensure_eigenclass && RB_TYPE_P(obj, T_CLASS)) { + /* ensures an exposed class belongs to its own eigenclass */ + (void)ENSURE_EIGENCLASS(klass); + } + } + if (needs_lock) { + RB_VM_LOCK_LEAVE_LEV(&lev); } - - RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); return klass; } @@ -2900,12 +2914,7 @@ rb_singleton_class_get(VALUE obj) VALUE rb_singleton_class(VALUE obj) { - VALUE klass = singleton_class_of(obj); - - /* ensures an exposed class belongs to its own eigenclass */ - if (RB_TYPE_P(obj, T_CLASS)) (void)ENSURE_EIGENCLASS(klass); - - return klass; + return singleton_class_of(obj, true); } /*! @@ -2923,7 +2932,7 @@ rb_singleton_class(VALUE obj) void rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc) { - rb_define_method(singleton_class_of(obj), name, func, argc); + rb_define_method(singleton_class_of(obj, false), name, func, argc); } #ifdef rb_define_module_function diff --git a/depend b/depend index 69ce9e63bd9000..8834814227b0e8 100644 --- a/depend +++ b/depend @@ -1403,6 +1403,7 @@ class.$(OBJEXT): {$(VPATH)}missing.h class.$(OBJEXT): {$(VPATH)}node.h class.$(OBJEXT): {$(VPATH)}onigmo.h class.$(OBJEXT): {$(VPATH)}oniguruma.h +class.$(OBJEXT): {$(VPATH)}ractor.h class.$(OBJEXT): {$(VPATH)}ruby_assert.h class.$(OBJEXT): {$(VPATH)}ruby_atomic.h class.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 22078514ad5cd3..8f12e06685bb1f 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -930,4 +930,19 @@ def test_safe_multi_ractor_subclasses_list_mutation end.each(&:join) end; end + + def test_safe_multi_ractor_singleton_class_access + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class A; end + 4.times.map do + Ractor.new do + a = A + 100.times do + a = a.singleton_class + end + end + end.each(&:join) + end; + end end From 0e719239c2fa39804cdbf1f920a18fcec919e2ce Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 18 Dec 2025 09:47:26 +0100 Subject: [PATCH 2022/2435] thread_sync: Mutex keep `rb_thread_t *` instead of `VALUE` We never need the actual thread object and this avoid any issue if the thread object is ever moved. --- thread_sync.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 6bff982f31d78b..0cf7c1faef97c1 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -8,7 +8,7 @@ static VALUE rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { rb_serial_t ec_serial; - VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free + rb_thread_t *th; // even if the fiber is collected, we might need access to the thread in mutex_free struct rb_mutex_struct *next_mutex; struct ccan_list_head waitq; /* protected by GVL */ } rb_mutex_t; @@ -133,7 +133,7 @@ mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; if (mutex_locked_p(mutex)) { - const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), 0); + const char *err = rb_mutex_unlock_th(mutex, mutex->th, 0); if (err) rb_bug("%s", err); } ruby_xfree(ptr); @@ -223,7 +223,7 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) static void mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { - mutex->thread = th->self; + mutex->th = th; mutex->ec_serial = ec_serial; } @@ -340,7 +340,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) } } else { - if (!th->vm->thread_ignore_deadlock && rb_thread_ptr(mutex->thread) == th) { + if (!th->vm->thread_ignore_deadlock && mutex->th == th) { rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread"); } @@ -391,7 +391,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ if (mutex->ec_serial == ec_serial) { - mutex->thread = Qfalse; + mutex->th = NULL; mutex->ec_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ From 74bfb1606d09672a37105c138edf8e0d8acbb5c1 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 13:51:33 -0500 Subject: [PATCH 2023/2435] Remove assertion in encoded_iseq_trace_instrument (#15616) `encoded_iseq_trace_instrument` is safe to call in a ractor if the iseq is new. In that case, the VM lock is not taken. This assertion was added in 4fb537b1ee28bb37dbe551ac65c279d436c756bc. --- iseq.c | 1 - 1 file changed, 1 deletion(-) diff --git a/iseq.c b/iseq.c index 7457118e07ce96..2e13928e920ed0 100644 --- a/iseq.c +++ b/iseq.c @@ -3936,7 +3936,6 @@ rb_vm_insn_decode(const VALUE encoded) static inline int encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon, bool remain_traced) { - ASSERT_vm_locking(); st_data_t key = (st_data_t)*iseq_encoded_insn; st_data_t val; From 28c2a5b2a4db5ed6faa7cf3d71baf2765a277184 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 17 Dec 2025 20:21:17 -0800 Subject: [PATCH 2024/2435] Fix env debug assertion failure w/ Ractors+JITs Previously when using a JIT and Ractors at the same time with debug assertions turned on this could rarely fail with: vm_core.h:1448: Assertion Failed: VM_ENV_FLAGS:FIXNUM_P(flags) When using Ractors, any time the VM lock is acquired, that may join a barrier as another Ractor initiates GC. This could be made to happen reliably by replacing the invalidation with a call to rb_gc(). This assertion failure happens because VM_STACK_ENV_WRITE(ep, 0, (VALUE)env); Is setting VM_ENV_DATA_INDEX_FLAGS to the environment, which is not a valid set of flags (it should be a fixnum). Although we update cfp->ep, rb_execution_context_mark will also mark the PREV_EP, and until the recursive calls to vm_make_env_each all finish the "next" ep may still be pointing to the stack env we've just escaped. I'm not completely sure why we need to store this on the stack - why is setting cfp->ep not enough? I'm also not sure why rb_execution_context_mark needs to mark the prev_ep. --- vm.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vm.c b/vm.c index 27cb5ae25825e0..15cf64c7b38094 100644 --- a/vm.c +++ b/vm.c @@ -1122,6 +1122,14 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co local_size += VM_ENV_DATA_SIZE; } + // Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp). + // This is done before creating the imemo_env because VM_STACK_ENV_WRITE + // below leaves the on-stack ep in a state that is unsafe to GC. + if (VM_FRAME_RUBYFRAME_P(cfp)) { + rb_yjit_invalidate_ep_is_bp(cfp->iseq); + rb_zjit_invalidate_no_ep_escape(cfp->iseq); + } + /* * # local variables on a stack frame (N == local_size) * [lvar1, lvar2, ..., lvarN, SPECVAL] @@ -1165,12 +1173,6 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co } #endif - // Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp). - if (env->iseq) { - rb_yjit_invalidate_ep_is_bp(env->iseq); - rb_zjit_invalidate_no_ep_escape(env->iseq); - } - return (VALUE)env; } From 57c4cd9a474ccd32caccee28a850ff8a041babb8 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 17 Dec 2025 09:22:54 +0100 Subject: [PATCH 2025/2435] thread_sync.c: eliminate GET_EC() from queue_do_pop We receive the ec as argument, it's much cheaper to pass it around that to look it up again. --- thread_sync.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 0cf7c1faef97c1..30f3315b0cc098 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -1083,12 +1083,12 @@ szqueue_sleep_done(VALUE p) return Qfalse; } -static VALUE -queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) +static inline VALUE +queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE non_block, VALUE timeout) { check_array(self, q->que); if (RARRAY_LEN(q->que) == 0) { - if (!should_block) { + if (RTEST(non_block)) { rb_raise(rb_eThreadError, "queue empty"); } @@ -1103,8 +1103,6 @@ queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) return queue_closed_result(self, q); } else { - rb_execution_context_t *ec = GET_EC(); - RUBY_ASSERT(RARRAY_LEN(q->que) == 0); RUBY_ASSERT(queue_closed_p(self) == 0); @@ -1136,7 +1134,7 @@ queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) static VALUE rb_queue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) { - return queue_do_pop(self, queue_ptr(self), !RTEST(non_block), timeout); + return queue_do_pop(ec, self, queue_ptr(self), non_block, timeout); } /* @@ -1330,7 +1328,6 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ raise_closed_queue_error(self); } else { - rb_execution_context_t *ec = GET_EC(); struct queue_waiter queue_waiter = { .w = {.self = self, .th = ec->thread_ptr, .fiber = nonblocking_fiber(ec->fiber_ptr)}, .as = {.sq = sq} @@ -1357,10 +1354,10 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ } static VALUE -szqueue_do_pop(VALUE self, int should_block, VALUE timeout) +rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) { struct rb_szqueue *sq = szqueue_ptr(self); - VALUE retval = queue_do_pop(self, &sq->q, should_block, timeout); + VALUE retval = queue_do_pop(ec, self, &sq->q, non_block, timeout); if (queue_length(self, &sq->q) < sq->max) { wakeup_one(szqueue_pushq(sq)); @@ -1368,11 +1365,6 @@ szqueue_do_pop(VALUE self, int should_block, VALUE timeout) return retval; } -static VALUE -rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) -{ - return szqueue_do_pop(self, !RTEST(non_block), timeout); -} /* * Document-method: Thread::SizedQueue#clear From bbc684d8300fc3fc02020a9a644b857d090f2215 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 18 Dec 2025 09:11:53 +0100 Subject: [PATCH 2026/2435] thread_sync.c: simplify `check_array` If the queue was allocated without calling initialize, `ary` will be `0`. --- thread_sync.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 30f3315b0cc098..962ca13d2ab940 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -839,13 +839,13 @@ ary_buf_new(void) return rb_ary_hidden_new(1); } -static VALUE +static inline VALUE check_array(VALUE obj, VALUE ary) { - if (!RB_TYPE_P(ary, T_ARRAY)) { - rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); + if (RB_LIKELY(ary)) { + return ary; } - return ary; + rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); } static long From 8cf4f373ff596aaef7aaec993c355b242d4fe2c1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 18 Dec 2025 09:35:44 +0100 Subject: [PATCH 2027/2435] thread_sync.c: declare queue_data_type as parent of szqueue_data_type. Allows to remove some duplicated code like szqueue_length, etc. --- include/ruby/internal/core/rtypeddata.h | 19 ++++++-- thread_sync.c | 58 ++++++++----------------- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index aed4bd89b893f6..72044562df8e3b 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -615,13 +615,24 @@ RBIMPL_ATTR_ARTIFICIAL() * directly. */ static inline void * -rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *type) +rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) { - if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == type)) { - return RTYPEDDATA_GET_DATA(obj); + if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj))) { + const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); + void *data = RTYPEDDATA_GET_DATA(obj); + if (RB_LIKELY(actual_type == expected_type)) { + return data; + } + + while (actual_type) { + actual_type = actual_type->parent; + if (actual_type == expected_type) { + return data; + } + } } - return rb_check_typeddata(obj, type); + return rb_check_typeddata(obj, expected_type); } diff --git a/thread_sync.c b/thread_sync.c index 962ca13d2ab940..d0e356b161d5b2 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -728,9 +728,14 @@ queue_memsize(const void *ptr) } static const rb_data_type_t queue_data_type = { - "queue", - {queue_mark_and_move, RUBY_TYPED_DEFAULT_FREE, queue_memsize, queue_mark_and_move}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "Thread::Queue", + .function = { + .dmark = queue_mark_and_move, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = queue_memsize, + .dcompact = queue_mark_and_move, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -803,9 +808,15 @@ szqueue_memsize(const void *ptr) } static const rb_data_type_t szqueue_data_type = { - "sized_queue", - {szqueue_mark_and_move, RUBY_TYPED_DEFAULT_FREE, szqueue_memsize, szqueue_mark_and_move}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "Thread::SizedQueue", + .function = { + .dmark = szqueue_mark_and_move, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = szqueue_memsize, + .dcompact = szqueue_mark_and_move, + }, + .parent = &queue_data_type, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -1382,23 +1393,6 @@ rb_szqueue_clear(VALUE self) return self; } -/* - * Document-method: Thread::SizedQueue#length - * call-seq: - * length - * size - * - * Returns the length of the queue. - */ - -static VALUE -rb_szqueue_length(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - - return LONG2NUM(queue_length(self, &sq->q)); -} - /* * Document-method: Thread::SizedQueue#num_waiting * @@ -1413,21 +1407,6 @@ rb_szqueue_num_waiting(VALUE self) return INT2NUM(sq->q.num_waiting + sq->num_waiting_push); } -/* - * Document-method: Thread::SizedQueue#empty? - * call-seq: empty? - * - * Returns +true+ if the queue is empty. - */ - -static VALUE -rb_szqueue_empty_p(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - - return RBOOL(queue_length(self, &sq->q) == 0); -} - /* ConditionalVariable */ struct rb_condvar { @@ -1678,11 +1657,8 @@ Init_thread_sync(void) rb_define_method(rb_cSizedQueue, "close", rb_szqueue_close, 0); rb_define_method(rb_cSizedQueue, "max", rb_szqueue_max_get, 0); rb_define_method(rb_cSizedQueue, "max=", rb_szqueue_max_set, 1); - rb_define_method(rb_cSizedQueue, "empty?", rb_szqueue_empty_p, 0); rb_define_method(rb_cSizedQueue, "clear", rb_szqueue_clear, 0); - rb_define_method(rb_cSizedQueue, "length", rb_szqueue_length, 0); rb_define_method(rb_cSizedQueue, "num_waiting", rb_szqueue_num_waiting, 0); - rb_define_alias(rb_cSizedQueue, "size", "length"); /* CVar */ DEFINE_CLASS(ConditionVariable, Object); From fb1dd92d30a8df93f6fe2746aacc097f4c3ea62b Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 18 Dec 2025 20:17:45 +0100 Subject: [PATCH 2028/2435] thread_sync.c: rename mutex_trylock internal function [Bug #21793] To fix a naming conflict on solaris. --- thread_sync.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index d0e356b161d5b2..a93888fad02ae6 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -235,7 +235,7 @@ mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) } static inline bool -mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) +do_mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { if (mutex->ec_serial == 0) { RUBY_DEBUG_LOG("%p ok", mutex); @@ -252,7 +252,7 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) static VALUE rb_mut_trylock(rb_execution_context_t *ec, VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_serial(ec))); + return RBOOL(do_mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_serial(ec))); } VALUE @@ -315,7 +315,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (!mutex_trylock(mutex, th, ec_serial)) { + if (!do_mutex_trylock(mutex, th, ec_serial)) { if (mutex->ec_serial == ec_serial) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } From a7eb1879ad35a0b5d9d32fcdbf2f840bd2c8858c Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 15:08:36 -0500 Subject: [PATCH 2029/2435] [DOC] small improvements to ractor class docs (#15584) * Ractor.yield no longer exists * Ractor.shareable_proc returns a copy of the given proc * Improve wording for monitoring/unmonitoring ports --- ractor.rb | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ractor.rb b/ractor.rb index 70ce1a9fffecb9..e380d04f873aad 100644 --- a/ractor.rb +++ b/ractor.rb @@ -47,7 +47,7 @@ # frozen objects can be unshareable if they contain (through their instance variables) unfrozen # objects. # -# Shareable objects are those which can be used by several threads without compromising +# Shareable objects are those which can be used by several ractors at once without compromising # thread-safety, for example numbers, +true+ and +false+. Ractor.shareable? allows you to check this, # and Ractor.make_shareable tries to make the object shareable if it's not already, and gives an error # if it can't do it. @@ -65,12 +65,12 @@ # ary[0].frozen? #=> true # ary[1].frozen? #=> true # -# When a shareable object is sent (via #send or Ractor.yield), no additional processing occurs -# on it. It just becomes usable by both ractors. When an unshareable object is sent, it can be +# When a shareable object is sent via #send, no additional processing occurs +# on it and it becomes usable by both ractors. When an unshareable object is sent, it can be # either _copied_ or _moved_. The first is the default, and it copies the object fully by # deep cloning (Object#clone) the non-shareable parts of its structure. # -# data = ['foo', 'bar'.freeze] +# data = ['foo'.dup, 'bar'.freeze] # r = Ractor.new do # data2 = Ractor.receive # puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}" @@ -81,8 +81,8 @@ # # This will output something like: # -# In ractor: 340, 360, 320 -# Outside : 380, 400, 320 +# In ractor: 8, 16, 24 +# Outside : 32, 40, 24 # # Note that the object ids of the array and the non-frozen string inside the array have changed in # the ractor because they are different objects. The second array's element, which is a @@ -460,9 +460,9 @@ def self.[]=(sym, val) # call-seq: # Ractor.store_if_absent(key){ init_block } # - # If the corresponding value is not set, yield a value with - # init_block and store the value in thread-safe manner. - # This method returns corresponding stored value. + # If the corresponding ractor-local value is not set, yield a value with + # init_block and store the value in a thread-safe manner. + # This method returns the stored value. # # (1..10).map{ # Thread.new(it){|i| @@ -578,10 +578,10 @@ def value # call-seq: # ractor.monitor(port) -> self # - # Register port as a monitoring port. If the ractor terminated, - # the port received a Symbol object. + # Add another ractor's port to the monitored list of the receiver. If +self+ terminates, + # the port is sent a Symbol object. # :exited will be sent if the ractor terminated without an exception. - # :aborted will be sent if the ractor terminated with a exception. + # :aborted will be sent if the ractor terminated with an exception. # # r = Ractor.new{ some_task() } # r.monitor(port = Ractor::Port.new) @@ -599,7 +599,7 @@ def monitor port # call-seq: # ractor.unmonitor(port) -> self # - # Unregister port from the monitoring ports. + # Remove the given port from the ractor's monitored list. # def unmonitor port __builtin_ractor_unmonitor(port) @@ -609,11 +609,11 @@ def unmonitor port # call-seq: # Ractor.shareable_proc(self: nil){} -> shareable proc # - # It returns shareable Proc object. The Proc object is - # shareable and the self in a block will be replaced with - # the value passed via `self:` keyword. + # Returns a shareable copy of the given block's Proc. The value of +self+ + # in the Proc will be replaced with the value passed via the `self:` keyword, + # or +nil+ if not given. # - # In a shareable Proc, you can not access to the outer variables. + # In a shareable Proc, you can not access any outer variables. # # a = 42 # Ractor.shareable_proc{ p a } @@ -636,7 +636,7 @@ def self.shareable_proc self: nil # call-seq: # Ractor.shareable_proc{} -> shareable proc # - # Same as Ractor.shareable_proc, but returns lambda proc. + # Same as Ractor.shareable_proc, but returns a lambda. # def self.shareable_lambda self: nil Primitive.attr! :use_block @@ -652,7 +652,7 @@ class Port # call-seq: # port.receive -> msg # - # Receive a message to the port (which was sent there by Port#send). + # Receive a message from the port (which was sent there by Port#send). # # port = Ractor::Port.new # r = Ractor.new port do |port| @@ -662,7 +662,7 @@ class Port # v1 = port.receive # puts "Received: #{v1}" # r.join - # # Here will be printed: "Received: message1" + # # This will print: "Received: message1" # # The method blocks if the message queue is empty. # From aace29d485559e38ca06923a6af335dbb5fb28f1 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 15:42:49 -0500 Subject: [PATCH 2030/2435] Check for NULL fields in TYPEDDATA memsize functions (#15633) Some TYPEDDATA objects allocate struct fields using the GC right after they get created, and in that case the VM can try to perform a GC and join a barrier if another ractor started one. If we're dumping the heap in another ractor, this acquires a barrier and it will call the `rb_obj_memsize` function on this object. We can't assume these struct fields are non-null. This also goes for C extensions, which may cause problems with heap dumping from a ractor if their memsize functions aren't coded correctly to check for NULL fields. Because dumping the heap from a ractor is likely a rare scenario and it has only recently been introduced, we'll have to see how this works in practice and if it causes bugs. --- ast.c | 8 ++++++-- box.c | 11 ++++++++--- id_table.c | 2 +- ractor_sync.c | 6 +++++- st.c | 1 + weakmap.c | 16 ++++++++++------ 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/ast.c b/ast.c index 449b21aa2d1023..5357aa38a5ae09 100644 --- a/ast.c +++ b/ast.c @@ -32,9 +32,13 @@ static size_t node_memsize(const void *ptr) { struct ASTNodeData *data = (struct ASTNodeData *)ptr; - rb_ast_t *ast = rb_ruby_ast_data_get(data->ast_value); + size_t size = sizeof(struct ASTNodeData); + if (data->ast_value) { + rb_ast_t *ast = rb_ruby_ast_data_get(data->ast_value); + size += rb_ast_memsize(ast); + } - return sizeof(struct ASTNodeData) + rb_ast_memsize(ast); + return size; } static const rb_data_type_t rb_node_type = { diff --git a/box.c b/box.c index 14f6acdd8267b5..3182fb4eb20758 100644 --- a/box.c +++ b/box.c @@ -276,10 +276,15 @@ box_entry_free(void *ptr) static size_t box_entry_memsize(const void *ptr) { + size_t size = sizeof(rb_box_t); const rb_box_t *box = (const rb_box_t *)ptr; - return sizeof(rb_box_t) + \ - rb_st_memsize(box->loaded_features_index) + \ - rb_st_memsize(box->loading_table); + if (box->loaded_features_index) { + size += rb_st_memsize(box->loaded_features_index); + } + if (box->loading_table) { + size += rb_st_memsize(box->loading_table); + } + return size; } static const rb_data_type_t rb_box_data_type = { diff --git a/id_table.c b/id_table.c index 4629af553505b7..cece14c3891153 100644 --- a/id_table.c +++ b/id_table.c @@ -374,7 +374,7 @@ rb_managed_id_table_create(const rb_data_type_t *type, size_t capa) struct rb_id_table *tbl; VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, type, tbl); RB_OBJ_SET_SHAREABLE(obj); - rb_id_table_init(tbl, capa); + rb_id_table_init(tbl, capa); // NOTE: this can cause GC, so dmark and dsize need to check tbl->items return obj; } diff --git a/ractor_sync.c b/ractor_sync.c index 8c7c144c3fda97..a63df7c407b194 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -1267,7 +1267,11 @@ static size_t ractor_selector_memsize(const void *ptr) { const struct ractor_selector *s = ptr; - return sizeof(struct ractor_selector) + st_memsize(s->ports); + size_t size = sizeof(struct ractor_selector); + if (s->ports) { + size += st_memsize(s->ports); + } + return size; } static const rb_data_type_t ractor_selector_data_type = { diff --git a/st.c b/st.c index ef9ffbec5e1ea3..8937f7935f6b22 100644 --- a/st.c +++ b/st.c @@ -674,6 +674,7 @@ st_free_table(st_table *tab) size_t st_memsize(const st_table *tab) { + RUBY_ASSERT(tab != NULL); return(sizeof(st_table) + (tab->bins == NULL ? 0 : bins_size(tab)) + get_allocated_entries(tab) * sizeof(st_table_entry)); diff --git a/weakmap.c b/weakmap.c index 2ebf7d204f7195..ecdd219f62a21e 100644 --- a/weakmap.c +++ b/weakmap.c @@ -139,9 +139,11 @@ wmap_memsize(const void *ptr) const struct weakmap *w = ptr; size_t size = 0; - size += st_memsize(w->table); - /* The key and value of the table each take sizeof(VALUE) in size. */ - size += st_table_size(w->table) * (2 * sizeof(VALUE)); + if (w->table) { + size += st_memsize(w->table); + /* The key and value of the table each take sizeof(VALUE) in size. */ + size += st_table_size(w->table) * (2 * sizeof(VALUE)); + } return size; } @@ -689,9 +691,11 @@ wkmap_memsize(const void *ptr) const struct weakkeymap *w = ptr; size_t size = 0; - size += st_memsize(w->table); - /* Each key of the table takes sizeof(VALUE) in size. */ - size += st_table_size(w->table) * sizeof(VALUE); + if (w->table) { + size += st_memsize(w->table); + /* Each key of the table takes sizeof(VALUE) in size. */ + size += st_table_size(w->table) * sizeof(VALUE); + } return size; } From 63b082cf0e87942dcea28cbdeb1c8a9e616e903a Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 17 Dec 2025 12:12:17 -0800 Subject: [PATCH 2031/2435] Store ractor_id directly on EC This is easier to access as ec->ractor_id instead of pointer-chasing through ec->thread->ractor->ractor_id Co-authored-by: Luke Gruber --- thread.c | 1 + vm.c | 1 + vm_core.h | 8 ++++++++ vm_insnhelper.c | 4 ++-- vm_method.c | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/thread.c b/thread.c index 3e1bb1dbe7ad17..788a0e9ad791a2 100644 --- a/thread.c +++ b/thread.c @@ -860,6 +860,7 @@ thread_create_core(VALUE thval, struct thread_create_params *params) #endif th->invoke_type = thread_invoke_type_ractor_proc; th->ractor = params->g; + th->ec->ractor_id = rb_ractor_id(th->ractor); th->ractor->threads.main = th; th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil); th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args)); diff --git a/vm.c b/vm.c index 15cf64c7b38094..f78a779c3f3655 100644 --- a/vm.c +++ b/vm.c @@ -3955,6 +3955,7 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->ec->local_storage_recursive_hash_for_trace = Qnil; th->ec->storage = Qnil; + th->ec->ractor_id = rb_ractor_id(th->ractor); #if OPT_CALL_THREADED_CODE th->retval = Qundef; diff --git a/vm_core.h b/vm_core.h index 839c054ab399f5..999f06d403bbe5 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1048,6 +1048,7 @@ struct rb_execution_context_struct { rb_fiber_t *fiber_ptr; struct rb_thread_struct *thread_ptr; rb_serial_t serial; + rb_serial_t ractor_id; /* storage (ec (fiber) local) */ struct rb_id_table *local_storage; @@ -2070,6 +2071,13 @@ rb_ec_ractor_ptr(const rb_execution_context_t *ec) } } +static inline rb_serial_t +rb_ec_ractor_id(const rb_execution_context_t *ec) +{ + VM_ASSERT(ec->ractor_id == rb_ractor_id(rb_ec_ractor_ptr(ec))); + return ec->ractor_id; +} + static inline rb_vm_t * rb_ec_vm_ptr(const rb_execution_context_t *ec) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index aa67e54d0ac44b..2ad67461bb7694 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4120,7 +4120,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ec_ractor_id(ec)) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } @@ -4143,7 +4143,7 @@ vm_call_iseq_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ec_ractor_id(ec)) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } diff --git a/vm_method.c b/vm_method.c index 9f569df7fa6e51..2a6323e59300b5 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1030,7 +1030,7 @@ rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *de } case VM_METHOD_TYPE_BMETHOD: RB_OBJ_WRITE(me, &def->body.bmethod.proc, (VALUE)opts); - def->body.bmethod.defined_ractor_id = rb_ractor_id(rb_ec_ractor_ptr(GET_EC())); + def->body.bmethod.defined_ractor_id = rb_ec_ractor_id(GET_EC()); return; case VM_METHOD_TYPE_NOTIMPLEMENTED: setup_method_cfunc_struct(UNALIGNED_MEMBER_PTR(def, body.cfunc), (VALUE(*)(ANYARGS))rb_f_notimplement_internal, -1); From 345ea0c8e18d3ce8fed332137d225769df619f2b Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 17 Dec 2025 12:08:43 -0800 Subject: [PATCH 2032/2435] YJIT: Support calling bmethods in Ractors Co-authored-by: Luke Gruber --- vm_core.h | 5 +++-- yjit.c | 6 ++++++ yjit/bindgen/src/main.rs | 1 + yjit/src/codegen.rs | 11 ++++++----- yjit/src/cruby.rs | 6 ++++++ yjit/src/cruby_bindings.inc.rs | 1 + 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/vm_core.h b/vm_core.h index 999f06d403bbe5..68adc5eac16f32 100644 --- a/vm_core.h +++ b/vm_core.h @@ -2074,8 +2074,9 @@ rb_ec_ractor_ptr(const rb_execution_context_t *ec) static inline rb_serial_t rb_ec_ractor_id(const rb_execution_context_t *ec) { - VM_ASSERT(ec->ractor_id == rb_ractor_id(rb_ec_ractor_ptr(ec))); - return ec->ractor_id; + rb_serial_t ractor_id = ec->ractor_id; + RUBY_ASSERT(ractor_id); + return ractor_id; } static inline rb_vm_t * diff --git a/yjit.c b/yjit.c index 6c3c9cd00161ce..1cd934c42eee63 100644 --- a/yjit.c +++ b/yjit.c @@ -473,6 +473,12 @@ rb_yjit_invokeblock_sp_pops(const struct rb_callinfo *ci) return 1 - sp_inc_of_invokeblock(ci); // + 1 to ignore return value push } +rb_serial_t +rb_yjit_cme_ractor_serial(const rb_callable_method_entry_t *cme) +{ + return cme->def->body.bmethod.defined_ractor_id; +} + // Setup jit_return to avoid returning a non-Qundef value on a non-FINISH frame. // See [jit_compile_exception] for details. void diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 06c475f3c8b493..fd99d529041077 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -272,6 +272,7 @@ fn main() { .allowlist_function("rb_optimized_call") .allowlist_function("rb_yjit_sendish_sp_pops") .allowlist_function("rb_yjit_invokeblock_sp_pops") + .allowlist_function("rb_yjit_cme_ractor_serial") .allowlist_function("rb_yjit_set_exception_return") .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_type("rstring_offsets") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 620bdb82800a78..606fe6ed702842 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7396,11 +7396,12 @@ fn gen_send_bmethod( let capture = unsafe { proc_block.as_.captured.as_ref() }; let iseq = unsafe { *capture.code.iseq.as_ref() }; - // Optimize for single ractor mode and avoid runtime check for - // "defined with an un-shareable Proc in a different Ractor" - if !assume_single_ractor_mode(jit, asm) { - gen_counter_incr(jit, asm, Counter::send_bmethod_ractor); - return None; + if !procv.shareable_p() { + let ractor_serial = unsafe { rb_yjit_cme_ractor_serial(cme) }; + asm_comment!(asm, "guard current ractor == {}", ractor_serial); + let current_ractor_serial = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_RACTOR_ID)); + asm.cmp(current_ractor_serial, Opnd::UImm(ractor_serial)); + asm.jne(Target::side_exit(Counter::send_bmethod_ractor)); } // Passing a block to a block needs logic different from passing diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 6e6a1810c67dda..d8497e41e39321 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -361,6 +361,11 @@ impl VALUE { !self.special_const_p() } + /// Shareability between ractors. `RB_OBJ_SHAREABLE_P()`. + pub fn shareable_p(self) -> bool { + (self.builtin_flags() & RUBY_FL_SHAREABLE as usize) != 0 + } + /// Return true if the value is a Ruby Fixnum (immediate-size integer) pub fn fixnum_p(self) -> bool { let VALUE(cval) = self; @@ -772,6 +777,7 @@ mod manual_defs { pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32) pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32) pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48; + pub const RUBY_OFFSET_EC_RACTOR_ID: i32 = 64; // Constants from rb_thread_t in vm_core.h pub const RUBY_OFFSET_THREAD_SELF: i32 = 16; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index d2347671bbacb8..0cab97ebf4a603 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1198,6 +1198,7 @@ extern "C" { pub fn rb_yjit_shape_index(shape_id: shape_id_t) -> attr_index_t; pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize; pub fn rb_yjit_invokeblock_sp_pops(ci: *const rb_callinfo) -> usize; + pub fn rb_yjit_cme_ractor_serial(cme: *const rb_callable_method_entry_t) -> rb_serial_t; pub fn rb_yjit_set_exception_return( cfp: *mut rb_control_frame_t, leave_exit: *mut ::std::os::raw::c_void, From b1c3060beda4bd659d21a2b2ea598bf9ee819d70 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 18 Dec 2025 12:31:07 -0800 Subject: [PATCH 2033/2435] Co-authored-by: Luke Gruber Co-authored-by: Alan Wu YJIT: Support calling bmethods in Ractors Co-authored-by: Luke Gruber Suggestion from Alan --- yjit/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 606fe6ed702842..11f961f8c781e2 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7400,7 +7400,7 @@ fn gen_send_bmethod( let ractor_serial = unsafe { rb_yjit_cme_ractor_serial(cme) }; asm_comment!(asm, "guard current ractor == {}", ractor_serial); let current_ractor_serial = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_RACTOR_ID)); - asm.cmp(current_ractor_serial, Opnd::UImm(ractor_serial)); + asm.cmp(current_ractor_serial, ractor_serial.into()); asm.jne(Target::side_exit(Counter::send_bmethod_ractor)); } From 73e930f9f911cf71ecb416c3112a7818bae41cd6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 18 Dec 2025 12:31:34 -0800 Subject: [PATCH 2034/2435] JIT: Move EC offsets to jit_bindgen_constants Co-authored-by: Alan Wu --- jit.c | 9 ++++++++- yjit/src/backend/tests.rs | 4 ++-- yjit/src/codegen.rs | 14 +++++++------- yjit/src/cruby.rs | 7 ------- yjit/src/cruby_bindings.inc.rs | 5 +++++ zjit/src/backend/tests.rs | 4 ++-- zjit/src/codegen.rs | 14 +++++++------- zjit/src/cruby.rs | 6 ------ zjit/src/cruby_bindings.inc.rs | 5 +++++ 9 files changed, 36 insertions(+), 32 deletions(-) diff --git a/jit.c b/jit.c index ff44ac5b2e565d..fb7a5bd47d4919 100644 --- a/jit.c +++ b/jit.c @@ -23,7 +23,14 @@ enum jit_bindgen_constants { ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary), // Field offsets for the RString struct - RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len) + RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len), + + // Field offsets for rb_execution_context_t + RUBY_OFFSET_EC_CFP = offsetof(rb_execution_context_t, cfp), + RUBY_OFFSET_EC_INTERRUPT_FLAG = offsetof(rb_execution_context_t, interrupt_flag), + RUBY_OFFSET_EC_INTERRUPT_MASK = offsetof(rb_execution_context_t, interrupt_mask), + RUBY_OFFSET_EC_THREAD_PTR = offsetof(rb_execution_context_t, thread_ptr), + RUBY_OFFSET_EC_RACTOR_ID = offsetof(rb_execution_context_t, ractor_id), }; // Manually bound in rust since this is out-of-range of `int`, diff --git a/yjit/src/backend/tests.rs b/yjit/src/backend/tests.rs index ac2f35b3d9c9e4..bfeea5163a2654 100644 --- a/yjit/src/backend/tests.rs +++ b/yjit/src/backend/tests.rs @@ -232,9 +232,9 @@ fn test_jcc_ptr() let (mut asm, mut cb) = setup_asm(); let side_exit = Target::CodePtr(cb.get_write_ptr().add_bytes(4)); - let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK)); + let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK as i32)); asm.test( - Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG), + Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32), not_mask, ); asm.jnz(side_exit); diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 11f961f8c781e2..61de6a32dabe8a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1208,7 +1208,7 @@ fn gen_check_ints( // Not checking interrupt_mask since it's zero outside finalize_deferred_heap_pages, // signal_exec, or rb_postponed_job_flush. - let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG)); + let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32)); asm.test(interrupt_flag, interrupt_flag); asm.jnz(Target::side_exit(counter)); @@ -6659,7 +6659,7 @@ fn jit_thread_s_current( asm.stack_pop(1); // ec->thread_ptr - let ec_thread_opnd = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_THREAD_PTR)); + let ec_thread_opnd = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_THREAD_PTR as i32)); // thread->self let thread_self = Opnd::mem(64, ec_thread_opnd, RUBY_OFFSET_THREAD_SELF); @@ -7124,7 +7124,7 @@ fn gen_send_cfunc( asm_comment!(asm, "set ec->cfp"); let new_cfp = asm.lea(Opnd::mem(64, CFP, -(RUBY_SIZEOF_CONTROL_FRAME as i32))); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), new_cfp); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), new_cfp); if !kw_arg.is_null() { // Build a hash from all kwargs passed @@ -7220,7 +7220,7 @@ fn gen_send_cfunc( // Pop the stack frame (ec->cfp++) // Instead of recalculating, we can reuse the previous CFP, which is stored in a callee-saved // register - let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP); + let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32); asm.store(ec_cfp_opnd, CFP); // cfunc calls may corrupt types @@ -7399,7 +7399,7 @@ fn gen_send_bmethod( if !procv.shareable_p() { let ractor_serial = unsafe { rb_yjit_cme_ractor_serial(cme) }; asm_comment!(asm, "guard current ractor == {}", ractor_serial); - let current_ractor_serial = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_RACTOR_ID)); + let current_ractor_serial = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_RACTOR_ID as i32)); asm.cmp(current_ractor_serial, ractor_serial.into()); asm.jne(Target::side_exit(Counter::send_bmethod_ractor)); } @@ -8359,7 +8359,7 @@ fn gen_send_iseq( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Directly jump to the entry point of the callee gen_direct_jump( @@ -9937,7 +9937,7 @@ fn gen_leave( asm_comment!(asm, "pop stack frame"); let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, incr_cfp); - asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Load the return value let retval_opnd = asm.stack_pop(1); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index d8497e41e39321..d34b049a453625 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -772,13 +772,6 @@ mod manual_defs { pub const RUBY_OFFSET_CFP_JIT_RETURN: i32 = 48; pub const RUBY_SIZEOF_CONTROL_FRAME: usize = 56; - // Constants from rb_execution_context_t vm_core.h - pub const RUBY_OFFSET_EC_CFP: i32 = 16; - pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48; - pub const RUBY_OFFSET_EC_RACTOR_ID: i32 = 64; - // Constants from rb_thread_t in vm_core.h pub const RUBY_OFFSET_THREAD_SELF: i32 = 16; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0cab97ebf4a603..952cf88c205115 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -978,6 +978,11 @@ pub type rb_seq_param_keyword_struct = pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; +pub const RUBY_OFFSET_EC_INTERRUPT_MASK: jit_bindgen_constants = 36; +pub const RUBY_OFFSET_EC_THREAD_PTR: jit_bindgen_constants = 48; +pub const RUBY_OFFSET_EC_RACTOR_ID: jit_bindgen_constants = 64; pub type jit_bindgen_constants = u32; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 6e62b3068d5a29..ece6f8605f1540 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -229,9 +229,9 @@ fn test_jcc_ptr() let (mut asm, mut cb) = setup_asm(); let side_exit = Target::CodePtr(cb.get_write_ptr().add_bytes(4)); - let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK)); + let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK as i32)); asm.test( - Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG), + Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32), not_mask, ); asm.jnz(side_exit); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e81732f3d54ff7..b05c0110909e62 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -841,7 +841,7 @@ fn gen_ccall_with_frame( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); let mut cfunc_args = vec![recv]; cfunc_args.extend(args); @@ -851,7 +851,7 @@ fn gen_ccall_with_frame( asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); asm_comment!(asm, "restore SP register for the caller"); let new_sp = asm.sub(SP, sp_offset.into()); @@ -926,7 +926,7 @@ fn gen_ccall_variadic( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); let argv_ptr = gen_push_opnds(asm, &args); asm.count_call_to(&name.contents_lossy()); @@ -936,7 +936,7 @@ fn gen_ccall_variadic( asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); asm_comment!(asm, "restore SP register for the caller"); let new_sp = asm.sub(SP, sp_offset.into()); @@ -1051,7 +1051,7 @@ fn gen_check_interrupts(jit: &mut JITState, asm: &mut Assembler, state: &FrameSt asm_comment!(asm, "RUBY_VM_CHECK_INTS(ec)"); // Not checking interrupt_mask since it's zero outside finalize_deferred_heap_pages, // signal_exec, or rb_postponed_job_flush. - let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG)); + let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32)); asm.test(interrupt_flag, interrupt_flag); asm.jnz(side_exit(jit, state, SideExitReason::Interrupt)); } @@ -1382,7 +1382,7 @@ fn gen_send_without_block_direct( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Set up arguments let mut c_args = vec![recv]; @@ -1741,7 +1741,7 @@ fn gen_return(asm: &mut Assembler, val: lir::Opnd) { asm_comment!(asm, "pop stack frame"); let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, incr_cfp); - asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Order here is important. Because we're about to tear down the frame, // we need to load the return value, which might be part of the frame. diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 68b6810125ea25..57a3bee7e01d8c 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1073,12 +1073,6 @@ mod manual_defs { pub const RUBY_OFFSET_CFP_JIT_RETURN: i32 = 48; pub const RUBY_SIZEOF_CONTROL_FRAME: usize = 56; - // Constants from rb_execution_context_t vm_core.h - pub const RUBY_OFFSET_EC_CFP: i32 = 16; - pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48; - // Constants from rb_thread_t in vm_core.h pub const RUBY_OFFSET_THREAD_SELF: i32 = 16; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 56ec724dd9b29c..2689ec30cf8b2d 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1828,6 +1828,11 @@ pub type zjit_struct_offsets = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; +pub const RUBY_OFFSET_EC_INTERRUPT_MASK: jit_bindgen_constants = 36; +pub const RUBY_OFFSET_EC_THREAD_PTR: jit_bindgen_constants = 48; +pub const RUBY_OFFSET_EC_RACTOR_ID: jit_bindgen_constants = 64; pub type jit_bindgen_constants = u32; pub const rb_invalid_shape_id: shape_id_t = 4294967295; pub type rb_iseq_param_keyword_struct = From d0b72429a93e54f1f956b4aedfc25c57dc7001aa Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 16 Dec 2025 09:10:45 -0800 Subject: [PATCH 2035/2435] Add support for signed and unsigned LEB128 to pack/unpack. This commit adds a new pack format command `R` and `r` for unsigned and signed LEB128 encoding. The "r" mnemonic is because this is a "vaRiable" length encoding scheme. LEB128 is used in various formats including DWARF, WebAssembly, MQTT, and Protobuf. [Feature #21785] --- doc/language/packed_data.rdoc | 2 + pack.c | 83 ++++++++++++++ spec/ruby/core/array/pack/r_spec.rb | 23 ++++ spec/ruby/core/array/pack/shared/basic.rb | 4 +- spec/ruby/core/string/unpack/shared/basic.rb | 2 +- test/ruby/test_pack.rb | 112 +++++++++++++++++++ 6 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 spec/ruby/core/array/pack/r_spec.rb diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index 3a762c03829a74..dcb4d557352d88 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -53,6 +53,8 @@ These tables summarize the directives for packing and unpacking. U | UTF-8 character w | BER-compressed integer + R | LEB128 encoded unsigned integer + r | LEB128 encoded signed integer === For Floats diff --git a/pack.c b/pack.c index 3a5c1bfb9677cf..6f68b13ccac6c4 100644 --- a/pack.c +++ b/pack.c @@ -667,6 +667,56 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) } break; + case 'r': /* r for SLEB128 encoding (signed) */ + case 'R': /* R for ULEB128 encoding (unsigned) */ + { + int pack_flags = INTEGER_PACK_LITTLE_ENDIAN; + + if (type == 'r') { + pack_flags |= INTEGER_PACK_2COMP; + } + + while (len-- > 0) { + size_t numbytes; + int sign; + char *cp; + + from = NEXTFROM; + from = rb_to_int(from); + numbytes = rb_absint_numwords(from, 7, NULL); + if (numbytes == 0) + numbytes = 1; + VALUE buf = rb_str_new(NULL, numbytes); + + sign = rb_integer_pack(from, RSTRING_PTR(buf), RSTRING_LEN(buf), 1, 1, pack_flags); + + if (sign < 0 && type == 'R') { + rb_raise(rb_eArgError, "can't encode negative numbers in ULEB128"); + } + + if (type == 'r') { + /* Check if we need an extra byte for sign extension */ + unsigned char last_byte = (unsigned char)RSTRING_PTR(buf)[numbytes - 1]; + if ((sign >= 0 && (last_byte & 0x40)) || /* positive but sign bit set */ + (sign < 0 && !(last_byte & 0x40))) { /* negative but sign bit clear */ + /* Need an extra byte */ + rb_str_resize(buf, numbytes + 1); + RSTRING_PTR(buf)[numbytes] = sign < 0 ? 0x7f : 0x00; + numbytes++; + } + } + + cp = RSTRING_PTR(buf); + while (1 < numbytes) { + *cp |= 0x80; + cp++; + numbytes--; + } + + rb_str_buf_cat(res, RSTRING_PTR(buf), RSTRING_LEN(buf)); + } + } + break; case 'u': /* uuencoded string */ case 'm': /* base64 encoded string */ from = NEXTFROM; @@ -1558,6 +1608,39 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset) } break; + case 'r': + case 'R': + { + int pack_flags = INTEGER_PACK_LITTLE_ENDIAN; + + if (type == 'r') { + pack_flags |= INTEGER_PACK_2COMP; + } + char *s0 = s; + while (len > 0 && s < send) { + if (*s & 0x80) { + s++; + } + else { + s++; + UNPACK_PUSH(rb_integer_unpack(s0, s-s0, 1, 1, pack_flags)); + len--; + s0 = s; + } + } + /* Handle incomplete value and remaining expected values with nil (only if not using *) */ + if (!star) { + if (s0 != s && len > 0) { + UNPACK_PUSH(Qnil); + len--; + } + while (len-- > 0) { + UNPACK_PUSH(Qnil); + } + } + } + break; + case 'w': { char *s0 = s; diff --git a/spec/ruby/core/array/pack/r_spec.rb b/spec/ruby/core/array/pack/r_spec.rb new file mode 100644 index 00000000000000..22be6fa6400cfa --- /dev/null +++ b/spec/ruby/core/array/pack/r_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +ruby_version_is "4.0" do + describe "Array#pack with format 'R'" do + it_behaves_like :array_pack_basic, 'R' + it_behaves_like :array_pack_basic_non_float, 'R' + it_behaves_like :array_pack_arguments, 'R' + it_behaves_like :array_pack_numeric_basic, 'R' + it_behaves_like :array_pack_integer, 'R' + end + + describe "Array#pack with format 'r'" do + it_behaves_like :array_pack_basic, 'r' + it_behaves_like :array_pack_basic_non_float, 'r' + it_behaves_like :array_pack_arguments, 'r' + it_behaves_like :array_pack_numeric_basic, 'r' + it_behaves_like :array_pack_integer, 'r' + end +end diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb index ebd9f75d9dc66a..77d7f2f71c0873 100644 --- a/spec/ruby/core/array/pack/shared/basic.rb +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -37,7 +37,7 @@ # NOTE: it's just a plan of the Ruby core team it "warns that a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/) + -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K'/) -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/) -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/) end @@ -48,7 +48,7 @@ # NOTE: Added this case just to not forget about the decision in the ticket it "raise ArgumentError when a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) + -> { [@obj, @obj].pack("a K" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'K'/) -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) end diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb index b37a447683a95c..0ac2a951ed7220 100644 --- a/spec/ruby/core/string/unpack/shared/basic.rb +++ b/spec/ruby/core/string/unpack/shared/basic.rb @@ -12,7 +12,7 @@ ruby_version_is "3.3" do # https://bugs.ruby-lang.org/issues/19150 it 'raise ArgumentError when a directive is unknown' do - -> { "abcdefgh".unpack("a R" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive 'R'/) + -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive 'K'/) -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive '0'/) -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive ':'/) end diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb index ca089f09c3dc4b..9c40cfaa204f86 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -936,4 +936,116 @@ class Array assert_equal "oh no", v end; end + + def test_unpack_broken_R + assert_equal([nil], "\xFF".unpack("R")) + assert_nil("\xFF".unpack1("R")) + assert_equal([nil], "\xFF".unpack("r")) + assert_nil("\xFF".unpack1("r")) + + bytes = [256].pack("r") + assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("rrrr")) + + bytes = [256].pack("R") + assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("RRRR")) + + assert_equal([], "\xFF".unpack("R*")) + assert_equal([], "\xFF".unpack("r*")) + end + + def test_pack_unpack_R + # ULEB128 encoding (unsigned) + assert_equal("\x00", [0].pack("R")) + assert_equal("\x01", [1].pack("R")) + assert_equal("\x7f", [127].pack("R")) + assert_equal("\x80\x01", [128].pack("R")) + assert_equal("\xff\x7f", [0x3fff].pack("R")) + assert_equal("\x80\x80\x01", [0x4000].pack("R")) + assert_equal("\xff\xff\xff\xff\x0f", [0xffffffff].pack("R")) + assert_equal("\x80\x80\x80\x80\x10", [0x100000000].pack("R")) + assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01", [0xffff_ffff_ffff_ffff].pack("R")) + assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("R")) + + # Multiple values + assert_equal("\x01\x02", [1, 2].pack("R*")) + assert_equal("\x7f\x80\x01", [127, 128].pack("R*")) + + # Negative numbers should raise an error + assert_raise(ArgumentError) { [-1].pack("R") } + assert_raise(ArgumentError) { [-100].pack("R") } + + # Unpack tests + assert_equal([0], "\x00".unpack("R")) + assert_equal([1], "\x01".unpack("R")) + assert_equal([127], "\x7f".unpack("R")) + assert_equal([128], "\x80\x01".unpack("R")) + assert_equal([0x3fff], "\xff\x7f".unpack("R")) + assert_equal([0x4000], "\x80\x80\x01".unpack("R")) + assert_equal([0xffffffff], "\xff\xff\xff\xff\x0f".unpack("R")) + assert_equal([0x100000000], "\x80\x80\x80\x80\x10".unpack("R")) + assert_equal([0xffff_ffff_ffff_ffff], "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01".unpack("R")) + assert_equal([0xffff_ffff_ffff_ffff_ffff_ffff].pack("R"), "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f") + + # Multiple values + assert_equal([1, 2], "\x01\x02".unpack("R*")) + assert_equal([127, 128], "\x7f\x80\x01".unpack("R*")) + + # Round-trip test + values = [0, 1, 127, 128, 0x3fff, 0x4000, 0xffffffff, 0x100000000] + assert_equal(values, values.pack("R*").unpack("R*")) + end + + def test_pack_unpack_r + # SLEB128 encoding (signed) + assert_equal("\x00", [0].pack("r")) + assert_equal("\x01", [1].pack("r")) + assert_equal("\x7f", [-1].pack("r")) + assert_equal("\x7e", [-2].pack("r")) + assert_equal("\xff\x00", [127].pack("r")) + assert_equal("\x80\x01", [128].pack("r")) + assert_equal("\x81\x7f", [-127].pack("r")) + assert_equal("\x80\x7f", [-128].pack("r")) + + # Larger positive numbers + assert_equal("\xff\xff\x00", [0x3fff].pack("r")) + assert_equal("\x80\x80\x01", [0x4000].pack("r")) + + # Larger negative numbers + assert_equal("\x81\x80\x7f", [-0x3fff].pack("r")) + assert_equal("\x80\x80\x7f", [-0x4000].pack("r")) + + # Very large numbers + assert_equal("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1F", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) + assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) + + # Multiple values + assert_equal("\x00\x01\x7f", [0, 1, -1].pack("r*")) + + # Unpack tests + assert_equal([0], "\x00".unpack("r")) + assert_equal([1], "\x01".unpack("r")) + assert_equal([-1], "\x7f".unpack("r")) + assert_equal([-2], "\x7e".unpack("r")) + assert_equal([127], "\xff\x00".unpack("r")) + assert_equal([128], "\x80\x01".unpack("r")) + assert_equal([-127], "\x81\x7f".unpack("r")) + assert_equal([-128], "\x80\x7f".unpack("r")) + + # Larger numbers + assert_equal([0x3fff], "\xff\xff\x00".unpack("r")) + assert_equal([0x4000], "\x80\x80\x01".unpack("r")) + assert_equal([-0x3fff], "\x81\x80\x7f".unpack("r")) + assert_equal([-0x4000], "\x80\x80\x7f".unpack("r")) + + # Very large numbers + assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) + assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) + + # Multiple values + assert_equal([0, 1, -1], "\x00\x01\x7f".unpack("r*")) + + # Round-trip test + values = [0, 1, -1, 127, -127, 128, -128, 0x3fff, -0x3fff, 0x4000, -0x4000] + assert_equal(values, values.pack("r*").unpack("r*")) + end end From 99b915944f1d47c1a47b1a3e894013869c7c27a7 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 16 Dec 2025 23:21:14 +0000 Subject: [PATCH 2036/2435] [DOC] Russian strings should look Russian --- doc/string/aset.rdoc | 6 +++--- doc/string/bytes.rdoc | 4 ++-- doc/string/bytesize.rdoc | 6 +++--- doc/string/center.rdoc | 2 +- doc/string/chars.rdoc | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/string/aset.rdoc b/doc/string/aset.rdoc index cac3b67ef580dd..db9079ebfb8188 100644 --- a/doc/string/aset.rdoc +++ b/doc/string/aset.rdoc @@ -170,9 +170,9 @@ With string argument +substring+ given: s['ll'] = 'foo' # => "foo" s # => "hefooo" - s = 'тест' - s['ес'] = 'foo' # => "foo" - s # => "тfooт" + s = 'Привет' + s['ив'] = 'foo' # => "foo" + s # => "Прfooет" s = 'こんにちは' s['んにち'] = 'foo' # => "foo" diff --git a/doc/string/bytes.rdoc b/doc/string/bytes.rdoc index f4b071f6306394..3815f13276fa11 100644 --- a/doc/string/bytes.rdoc +++ b/doc/string/bytes.rdoc @@ -1,7 +1,7 @@ Returns an array of the bytes in +self+: - 'hello'.bytes # => [104, 101, 108, 108, 111] - 'тест'.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + 'hello'.bytes # => [104, 101, 108, 108, 111] + 'Привет'.bytes # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] 'こんにちは'.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] diff --git a/doc/string/bytesize.rdoc b/doc/string/bytesize.rdoc index 5166dd7dc614ac..cbb7f439fcb448 100644 --- a/doc/string/bytesize.rdoc +++ b/doc/string/bytesize.rdoc @@ -5,9 +5,9 @@ Note that the byte count may be different from the character count (returned by s = 'foo' s.bytesize # => 3 s.size # => 3 - s = 'тест' - s.bytesize # => 8 - s.size # => 4 + s = 'Привет' + s.bytesize # => 12 + s.size # => 6 s = 'こんにちは' s.bytesize # => 15 s.size # => 5 diff --git a/doc/string/center.rdoc b/doc/string/center.rdoc index 343f6ba263acae..3116d211174286 100644 --- a/doc/string/center.rdoc +++ b/doc/string/center.rdoc @@ -9,7 +9,7 @@ centered and padded on one or both ends with +pad_string+: 'hello'.center(20, '-|') # => "-|-|-|-hello-|-|-|-|" # Some padding repeated. 'hello'.center(10, 'abcdefg') # => "abhelloabc" # Some padding not used. ' hello '.center(13) # => " hello " - 'тест'.center(10) # => " тест " + 'Привет'.center(10) # => " Привет " 'こんにちは'.center(10) # => " こんにちは " # Multi-byte characters. If +size+ is less than or equal to the size of +self+, returns an unpadded copy of +self+: diff --git a/doc/string/chars.rdoc b/doc/string/chars.rdoc index 094384271b1a77..97ea07331f4c46 100644 --- a/doc/string/chars.rdoc +++ b/doc/string/chars.rdoc @@ -1,7 +1,7 @@ Returns an array of the characters in +self+: 'hello'.chars # => ["h", "e", "l", "l", "o"] - 'тест'.chars # => ["т", "е", "с", "т"] + 'Привет'.chars # => ["П", "р", "и", "в", "е", "т"] 'こんにちは'.chars # => ["こ", "ん", "に", "ち", "は"] ''.chars # => [] From 3c6a6afa1c166b9fd24761c65fbd1268cb020341 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 18 Dec 2025 15:33:34 -0800 Subject: [PATCH 2037/2435] [DOC] Update ractor.c docs --- ractor.c | 42 +++++++++++------------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/ractor.c b/ractor.c index a95468880708d0..f4c2e243be02e3 100644 --- a/ractor.c +++ b/ractor.c @@ -953,36 +953,16 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) * * Raised when an attempt is made to send a message to a closed port, * or to retrieve a message from a closed and empty port. - * Ports may be closed explicitly with Ractor#close_outgoing/close_incoming + * Ports may be closed explicitly with Ractor::Port#close * and are closed implicitly when a Ractor terminates. * - * r = Ractor.new { sleep(500) } - * r.close_outgoing - * r.take # Ractor::ClosedError + * port = Ractor::Port.new + * port.close + * port << "test" # Ractor::ClosedError + * port.receive # Ractor::ClosedError * - * ClosedError is a descendant of StopIteration, so the closing of the ractor will break - * the loops without propagating the error: - * - * r = Ractor.new do - * loop do - * msg = receive # raises ClosedError and loop traps it - * puts "Received: #{msg}" - * end - * puts "loop exited" - * end - * - * 3.times{|i| r << i} - * r.close_incoming - * r.take - * puts "Continue successfully" - * - * This will print: - * - * Received: 0 - * Received: 1 - * Received: 2 - * loop exited - * Continue successfully + * ClosedError is a descendant of StopIteration, so the closing of a port will break + * out of loops without propagating the error. */ /* @@ -995,14 +975,14 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) /* * Document-class: Ractor::RemoteError * - * Raised on attempt to Ractor#take if there was an uncaught exception in the Ractor. + * Raised on Ractor#join or Ractor#value if there was an uncaught exception in the Ractor. * Its +cause+ will contain the original exception, and +ractor+ is the original ractor * it was raised in. * * r = Ractor.new { raise "Something weird happened" } * * begin - * r.take + * r.value * rescue => e * p e # => # * p e.ractor == r # => true @@ -1014,7 +994,7 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) /* * Document-class: Ractor::MovedError * - * Raised on an attempt to access an object which was moved in Ractor#send or Ractor.yield. + * Raised on an attempt to access an object which was moved in Ractor#send or Ractor::Port#send. * * r = Ractor.new { sleep } * @@ -1029,7 +1009,7 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) * Document-class: Ractor::MovedObject * * A special object which replaces any value that was moved to another ractor in Ractor#send - * or Ractor.yield. Any attempt to access the object results in Ractor::MovedError. + * or Ractor::Port#send. Any attempt to access the object results in Ractor::MovedError. * * r = Ractor.new { receive } * From 535233c68c30546c81d22694fa3f5911d74c14b6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 18 Dec 2025 15:48:29 -0800 Subject: [PATCH 2038/2435] [DOC] Update ractor.rb docs --- ractor.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ractor.rb b/ractor.rb index e380d04f873aad..136445880251c3 100644 --- a/ractor.rb +++ b/ractor.rb @@ -371,7 +371,7 @@ def close # # Checks if the object is shareable by ractors. # - # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are frozen + # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are shareable # Ractor.shareable?('foo') #=> false, unless the string is frozen due to # frozen_string_literal: true # Ractor.shareable?('foo'.freeze) #=> true # @@ -589,7 +589,7 @@ def value # # r = Ractor.new{ raise "foo" } # r.monitor(port = Ractor::Port.new) - # port.receive #=> :terminated and r is terminated with an exception "foo" + # port.receive #=> :aborted and r is terminated with an exception "foo" # def monitor port __builtin_ractor_monitor(port) @@ -634,7 +634,7 @@ def self.shareable_proc self: nil # # call-seq: - # Ractor.shareable_proc{} -> shareable proc + # Ractor.shareable_lambda{} -> shareable lambda # # Same as Ractor.shareable_proc, but returns a lambda. # @@ -690,7 +690,7 @@ class Port # Still received only one # Received: message2 # - # If close_incoming was called on the ractor, the method raises Ractor::ClosedError + # If the port is closed, the method raises Ractor::ClosedError # if there are no more messages in the message queue: # # port = Ractor::Port.new @@ -710,8 +710,8 @@ def receive # Send a message to a port to be accepted by port.receive. # # port = Ractor::Port.new - # r = Ractor.new do - # r.send 'message' + # r = Ractor.new(port) do |port| + # port.send 'message' # end # value = port.receive # puts "Received #{value}" @@ -722,7 +722,7 @@ def receive # # port = Ractor::Port.new # r = Ractor.new(port) do |port| - # port.send 'test'} + # port.send 'test' # puts "Sent successfully" # # Prints: "Sent successfully" immediately # end @@ -752,7 +752,7 @@ def send obj, move: false # call-seq: # port.close # - # Close the port. On the closed port, sending is not prohibited. + # Close the port. On the closed port, sending is prohibited. # Receiving is also not allowed if there is no sent messages arrived before closing. # # port = Ractor::Port.new From 805f53a9b12d82830db2f523d975eff2bd71bfa5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 16 Dec 2025 16:13:44 -0800 Subject: [PATCH 2039/2435] [DOC] Various improvements to NEWS --- NEWS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index a7a94d94287f2c..f51b49416fc745 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ Note that each entry is kept to a minimum, see links for details. * Logical binary operators (`||`, `&&`, `and` and `or`) at the beginning of a line continue the previous line, like fluent dot. - The following two code are equal: + The following two code examples are equal: ```ruby if condition1 @@ -165,7 +165,7 @@ Note: We're only listing outstanding class updates. * `Ractor::Port#close` * `Ractor::Port#closed?` - As result, `Ractor.yield` and `Ractor#take` were removed. + As a result, `Ractor.yield` and `Ractor#take` were removed. * `Ractor#join` and `Ractor#value` were added to wait for the termination of a Ractor. These are similar to `Thread#join` @@ -182,7 +182,7 @@ Note: We're only listing outstanding class updates. * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. - * `Ractor.shareable_proc` and `Ractor.shareable_lambda` is introduced + * `Ractor.shareable_proc` and `Ractor.shareable_lambda` are introduced to make shareable Proc or lambda. [[Feature #21550]], [[Feature #21557]] @@ -214,7 +214,7 @@ Note: We're only listing outstanding class updates. * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] - * `Set#inspect` now uses a simpler displays, similar to literal arrays. + * `Set#inspect` now uses a simpler display, similar to literal arrays. (e.g., `Set[1, 2, 3]` instead of `#`). [[Feature #21389]] * Passing arguments to `Set#to_set` and `Enumerable#to_set` is now deprecated. @@ -369,7 +369,7 @@ The following bundled gems are updated. * `rb_path_check` has been removed. This function was used for `$SAFE` path checking which was removed in Ruby 2.7, - and was already deprecated,. + and was already deprecated. [[Feature #20971]] * A backtrace for `ArgumentError` of "wrong number of arguments" now @@ -523,6 +523,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 [Feature #21543]: https://bugs.ruby-lang.org/issues/21543 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 +[Feature #21552]: https://bugs.ruby-lang.org/issues/21552 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 From 084b916a5b9da0367b077add3203b924e868970b Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 16 Dec 2025 16:10:11 -0800 Subject: [PATCH 2040/2435] [DOC] Update NEWS for implementation improvements --- NEWS.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index f51b49416fc745..3d1807beb6100a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -358,7 +358,7 @@ The following bundled gems are updated. * `Ractor.yield` * `Ractor#take` * `Ractor#close_incoming` - * `Ractor#close_outgoging` + * `Ractor#close_outgoing` [[Feature #21262]] @@ -449,15 +449,27 @@ The following bundled gems are updated. ## Implementation improvements +* `Class#new` (ex. `Object.new`) is faster in all cases, but especially when passing keyword arguments. This has also been integrated into YJIT and ZJIT. [[Feature #21254]] +* GC heaps of different size pools now grow independently, reducing memory usage when only some pools contain long-lived objects +* GC sweeping is faster on pages of large objects +* "Generic ivar" objects (String, Array, `TypedData`, etc.) now use a new internal "fields" object for faster instance variable access +* The GC avoids maintaining an internal `id2ref` table until it is first used, making `object_id` allocation and GC sweeping faster +* `object_id` and `hash` are faster on Class and Module objects +* Larger bignum Integers can remain embedded using variable width allocation +* `Random`, `Enumerator::Product`, `Enumerator::Chain`, `Addrinfo`, + `StringScanner`, and some internal objects are now write-barrier protected, + which reduces GC overhead. + ### Ractor -A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractors implementation closer to leaving experimental status. +A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractor implementation closer to leaving experimental status. * Performance improvements - * Frozen strings and the symbol table internally use a lock-free hash set + * Frozen strings and the symbol table internally use a lock-free hash set [[Feature #21268]] * Method cache lookups avoid locking in most cases - * Class (and geniv) instance variable access is faster and avoids locking - * Cache contention is avoided during object allocation + * Class (and generic ivar) instance variable access is faster and avoids locking + * CPU cache contention is avoided in object allocation by using a per-ractor counter + * CPU cache contention is avoided in xmalloc/xfree by using a thread-local counter * `object_id` avoids locking in most cases * Bug fixes and stability * Fixed possible deadlocks when combining Ractors and Threads @@ -465,6 +477,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * Fixed encoding/transcoding issues across Ractors * Fixed race conditions in GC operations and method invalidation * Fixed issues with processes forking after starting a Ractor + * GC allocation counts are now accurate under Ractors + * Fixed TracePoints not working after GC [[Bug #19112]] ## JIT @@ -488,6 +502,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 +[Bug #19112]: https://bugs.ruby-lang.org/issues/19112 [Feature #19630]: https://bugs.ruby-lang.org/issues/19630 [Bug #19868]: https://bugs.ruby-lang.org/issues/19868 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 @@ -510,6 +525,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21219]: https://bugs.ruby-lang.org/issues/21219 [Feature #21254]: https://bugs.ruby-lang.org/issues/21254 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 +[Feature #21268]: https://bugs.ruby-lang.org/issues/21268 [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21275]: https://bugs.ruby-lang.org/issues/21275 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 From b14f2f0116e6eccbfff57edae1283b3c53247752 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 18 Dec 2025 18:21:00 -0600 Subject: [PATCH 2041/2435] [DOC] Harmonize lt methods --- compar.c | 10 +++++++--- hash.c | 5 ++--- numeric.c | 9 ++++----- object.c | 15 ++++++++------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/compar.c b/compar.c index f5da6178cf4358..c577e4741ffa13 100644 --- a/compar.c +++ b/compar.c @@ -123,10 +123,14 @@ cmp_ge(VALUE x, VALUE y) /* * call-seq: - * obj < other -> true or false + * self < other -> true or false + * + * Returns whether +self+ is "less than" +other+; + * equivalent to (self <=> other) < 0: + * + * 'foo' < 'foo' # => false + * 'foo' < 'food' # => true * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value less than 0. */ static VALUE diff --git a/hash.c b/hash.c index ac9a71794c8430..9e1555518ec037 100644 --- a/hash.c +++ b/hash.c @@ -4915,10 +4915,9 @@ rb_hash_le(VALUE hash, VALUE other) /* * call-seq: - * self < other_hash -> true or false + * self < other -> true or false * - * Returns +true+ if the entries of +self+ are a proper subset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a proper subset of the entries of +other+: * * h = {foo: 0, bar: 1} * h < {foo: 0, bar: 1, baz: 2} # => true # Proper subset. diff --git a/numeric.c b/numeric.c index 63e10fbe9f9141..3e770ceed3bc88 100644 --- a/numeric.c +++ b/numeric.c @@ -1702,7 +1702,8 @@ flo_ge(VALUE x, VALUE y) * call-seq: * self < other -> true or false * - * Returns +true+ if +self+ is numerically less than +other+: + * Returns whether the value of +self+ is less than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 < 3 # => true * 2.0 < 3.0 # => true @@ -1710,7 +1711,6 @@ flo_ge(VALUE x, VALUE y) * 2.0 < 2.0 # => false * * Float::NAN < Float::NAN returns an implementation-dependent value. - * */ static VALUE @@ -5038,7 +5038,8 @@ fix_lt(VALUE x, VALUE y) * call-seq: * self < other -> true or false * - * Returns +true+ if the value of +self+ is less than that of +other+: + * Returns whether the value of +self+ is less than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 < 0 # => false * 1 < 1 # => false @@ -5046,8 +5047,6 @@ fix_lt(VALUE x, VALUE y) * 1 < 0.5 # => false * 1 < Rational(1, 2) # => false * - * Raises an exception if the comparison cannot be made. - * */ static VALUE diff --git a/object.c b/object.c index 158bb0b219256c..b2b7a9d96038f0 100644 --- a/object.c +++ b/object.c @@ -1948,14 +1948,15 @@ rb_class_inherited_p(VALUE mod, VALUE arg) /* * call-seq: - * mod < other -> true, false, or nil + * self < other -> true, false, or nil * - * Returns true if mod is a subclass of other. Returns - * false if mod is the same as other - * or mod is an ancestor of other. - * Returns nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "A < B".) + * Returns whether +self+ is a subclass of +other+, + * or +nil+ if there is no relationship between the two: + * + * Float < Numeric # => true + * Numeric < Float # => false + * Float < Float # => false + * Float < Hash # => nil * */ From 0c4fcdff3252cca0d50986fc5b54a41bff809c85 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 18 Dec 2025 15:48:06 -0800 Subject: [PATCH 2042/2435] Update ArgumentError message for Ractor.select --- bootstraptest/test_ractor.rb | 2 +- ractor.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index a1169f9d29c54f..e2a3e8dd5beff1 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -316,7 +316,7 @@ def test n } unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878 # Exception for empty select -assert_match /specify at least one ractor/, %q{ +assert_match /specify at least one Ractor::Port or Ractor/, %q{ begin Ractor.select rescue ArgumentError => e diff --git a/ractor.rb b/ractor.rb index 136445880251c3..0002eece2ce700 100644 --- a/ractor.rb +++ b/ractor.rb @@ -267,7 +267,7 @@ def self.count # # TBD def self.select(*ports) - raise ArgumentError, 'specify at least one ractor or `yield_value`' if ports.empty? + raise ArgumentError, 'specify at least one Ractor::Port or Ractor' if ports.empty? monitors = {} # Ractor::Port => Ractor From d9b03c9369001a835b186ee7fd637e7f94d3d64f Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 15:39:00 +0100 Subject: [PATCH 2043/2435] [ruby/prism] Fix assertions in location_test.rb * assert_raise's 2nd argument is the failure message, shown when the expected exception is not raised. It's not the expected message. See https://github.com/test-unit/test-unit/issues/347 https://github.com/ruby/prism/commit/e3df994d47 --- test/prism/ruby/location_test.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/prism/ruby/location_test.rb b/test/prism/ruby/location_test.rb index 33f844243c0547..5e2ab63802b1f5 100644 --- a/test/prism/ruby/location_test.rb +++ b/test/prism/ruby/location_test.rb @@ -13,19 +13,22 @@ def test_join assert_equal 0, joined.start_offset assert_equal 10, joined.length - assert_raise(RuntimeError, "Incompatible locations") do + e = assert_raise(RuntimeError) do argument.location.join(receiver.location) end + assert_equal "Incompatible locations", e.message other_argument = Prism.parse_statement("1234 + 567").arguments.arguments.first - assert_raise(RuntimeError, "Incompatible sources") do + e = assert_raise(RuntimeError) do other_argument.location.join(receiver.location) end + assert_equal "Incompatible sources", e.message - assert_raise(RuntimeError, "Incompatible sources") do + e = assert_raise(RuntimeError) do receiver.location.join(other_argument.location) end + assert_equal "Incompatible sources", e.message end def test_character_offsets From 76248400b75d42288a5941aa03e2d2e6d4fac057 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:47:43 +0100 Subject: [PATCH 2044/2435] [ruby/prism] Add Ruby 4.1 as a version specifier https://github.com/ruby/prism/commit/138db9ccc4 --- lib/prism/ffi.rb | 2 ++ lib/prism/prism.gemspec | 2 ++ lib/prism/translation.rb | 1 + lib/prism/translation/parser.rb | 4 +++- lib/prism/translation/parser41.rb | 13 +++++++++++++ lib/prism/translation/parser_current.rb | 2 ++ prism/options.c | 10 ++++++++++ prism/options.h | 5 ++++- test/prism/api/parse_test.rb | 3 +++ test/prism/test_helper.rb | 2 +- 10 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 lib/prism/translation/parser41.rb diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 7e6103fde775bf..d4c9d60c9aa2a4 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -434,6 +434,8 @@ def dump_options_version(version) 2 when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ 3 + when /\A4\.1(\.\d+)?\z/ + 4 else if current raise CurrentVersionError, RUBY_VERSION diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 10c2eaad209ed4..d9872f88acffd9 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -102,6 +102,7 @@ Gem::Specification.new do |spec| "lib/prism/translation/parser34.rb", "lib/prism/translation/parser35.rb", "lib/prism/translation/parser40.rb", + "lib/prism/translation/parser41.rb", "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", @@ -125,6 +126,7 @@ Gem::Specification.new do |spec| "rbi/prism/translation/parser34.rbi", "rbi/prism/translation/parser35.rbi", "rbi/prism/translation/parser40.rbi", + "rbi/prism/translation/parser41.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 7933b4a7222e93..89c70ee420130f 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -11,6 +11,7 @@ module Translation # steep:ignore autoload :Parser34, "prism/translation/parser34" autoload :Parser35, "prism/translation/parser35" autoload :Parser40, "prism/translation/parser40" + autoload :Parser41, "prism/translation/parser41" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 23245dc383e9e9..fed4ac4cd1f0fd 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -84,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 40 + 41 end # The default encoding for Ruby files is UTF-8. @@ -358,6 +358,8 @@ def convert_for_prism(version) "3.4.0" when 35, 40 "4.0.0" + when 41 + "4.1.0" else "latest" end diff --git a/lib/prism/translation/parser41.rb b/lib/prism/translation/parser41.rb new file mode 100644 index 00000000000000..ed819064004606 --- /dev/null +++ b/lib/prism/translation/parser41.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 4.1 of `Prism::Translation::Parser`. + class Parser41 < Parser + def version # :nodoc: + 41 + end + end + end +end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index 76d71e940926cc..ac6daf7082e416 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -12,6 +12,8 @@ module Translation ParserCurrent = Parser34 when /^3\.5\./, /^4\.0\./ ParserCurrent = Parser40 + when /^4\.1\./ + ParserCurrent = Parser41 else # Keep this in sync with released Ruby. parser = Parser34 diff --git a/prism/options.c b/prism/options.c index 4a8953da7d2aac..09d2a65a6cbcf6 100644 --- a/prism/options.c +++ b/prism/options.c @@ -93,6 +93,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return true; } + if (strncmp(version, "4.1", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } + return false; } @@ -111,6 +116,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } + + if (strncmp(version, "4.1.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } } if (length >= 6) { diff --git a/prism/options.h b/prism/options.h index a663c9767e994e..c00c7bf7553a4f 100644 --- a/prism/options.h +++ b/prism/options.h @@ -97,8 +97,11 @@ typedef enum { /** The vendored version of prism in CRuby 4.0.x. */ PM_OPTIONS_VERSION_CRUBY_4_0 = 3, + /** The vendored version of prism in CRuby 4.1.x. */ + PM_OPTIONS_VERSION_CRUBY_4_1 = 4, + /** The current version of prism. */ - PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_0 + PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_1 } pm_options_version_t; /** diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index bb1761109fadbb..bbf28201ffe2b4 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -122,6 +122,9 @@ def test_version assert Prism.parse_success?("1 + 1", version: "4.0") assert Prism.parse_success?("1 + 1", version: "4.0.0") + assert Prism.parse_success?("1 + 1", version: "4.1") + assert Prism.parse_success?("1 + 1", version: "4.1.0") + assert Prism.parse_success?("1 + 1", version: "latest") # Test edge case diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index f78e68e87c107a..43771110b4284f 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -241,7 +241,7 @@ def self.windows? end # All versions that prism can parse - SYNTAX_VERSIONS = %w[3.3 3.4 4.0] + SYNTAX_VERSIONS = %w[3.3 3.4 4.0 4.1] # `RUBY_VERSION` with the patch version excluded CURRENT_MAJOR_MINOR = RUBY_VERSION.split(".")[0, 2].join(".") From e2c886dd669dd640dae75e3ac6d7206b74870d1b Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:13:25 +0100 Subject: [PATCH 2045/2435] [ruby/prism] Reject `p(p a, &block => value)` and similar Redo of https://github.com/ruby/prism/pull/3669 with more tests https://github.com/ruby/prism/commit/48b403ea79 --- prism/prism.c | 26 ++++++++++- test/prism/errors/command_calls_35.txt | 46 +++++++++++++++++++ test/prism/fixtures/command_method_call_3.txt | 19 ++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 test/prism/errors/command_calls_35.txt create mode 100644 test/prism/fixtures/command_method_call_3.txt diff --git a/prism/prism.c b/prism/prism.c index f98032cd73b0b1..4c8ab91f0ef4e6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13412,6 +13412,30 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod return contains_keyword_splat; } +static inline bool +argument_allowed_for_bare_hash(pm_parser_t *parser, pm_node_t *argument) { + if (pm_symbol_node_label_p(argument)) { + return true; + } + + switch (PM_NODE_TYPE(argument)) { + case PM_CALL_NODE: { + pm_call_node_t *cast = (pm_call_node_t *) argument; + if (cast->opening_loc.start == NULL && cast->arguments != NULL) { + if (PM_NODE_FLAG_P(cast->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS | PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) { + return false; + } + if (cast->block != NULL) { + return false; + } + } + break; + } + default: break; + } + return accept1(parser, PM_TOKEN_EQUAL_GREATER); +} + /** * Append an argument to a list of arguments. */ @@ -13569,7 +13593,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for bool contains_keywords = false; bool contains_keyword_splat = false; - if (pm_symbol_node_label_p(argument) || accept1(parser, PM_TOKEN_EQUAL_GREATER)) { + if (argument_allowed_for_bare_hash(parser, argument)){ if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_BARE_HASH); } diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt new file mode 100644 index 00000000000000..45f569b117c6c5 --- /dev/null +++ b/test/prism/errors/command_calls_35.txt @@ -0,0 +1,46 @@ +p(p a, x: b => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, x: => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, &block => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a do end => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, *args => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, **kwargs => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p p 1, &block => 2, &block + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + ^ unexpected '&', ignoring it + +p p p 1 => 2 => 3 => 4 + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + +p[p a, x: b => value] + ^ expected a matching `]` + ^ unexpected ']', expecting end-of-input + ^ unexpected ']', ignoring it + diff --git a/test/prism/fixtures/command_method_call_3.txt b/test/prism/fixtures/command_method_call_3.txt new file mode 100644 index 00000000000000..6de0446aa9be74 --- /dev/null +++ b/test/prism/fixtures/command_method_call_3.txt @@ -0,0 +1,19 @@ +foo(bar 1, key => '2') + +foo(bar 1, KEY => '2') + +foo(bar 1, :key => '2') + +foo(bar 1, { baz: :bat } => '2') + +foo bar - %i[baz] => '2' + +foo(bar {} => '2') + +foo(bar baz {} => '2') + +foo(bar do end => '2') + +foo(1, bar {} => '2') + +foo(1, bar do end => '2') From 5c0c0dd8737c8225f0ebcf0eaf3fb8b71917ee4d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 18 Dec 2025 10:10:13 -0500 Subject: [PATCH 2046/2435] [ruby/prism] Bump to v1.7.0 https://github.com/ruby/prism/commit/21c499d6e4 --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index d9872f88acffd9..2fb5d1d0b308e4 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.6.0" + spec.version = "1.7.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index 70017a4ae39ca2..6c3de31adb94d6 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.6.0" +#define EXPECTED_PRISM_VERSION "1.7.0" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 526be67431f0f0..63f97dddd790b1 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 6 + MINOR_VERSION = 7 # The patch version of prism that we are expecting to find in the serialized # strings. diff --git a/prism/version.h b/prism/version.h index f202b0f4d72c3a..0b64a70dfff5c0 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 6 +#define PRISM_VERSION_MINOR 7 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.6.0" +#define PRISM_VERSION "1.7.0" #endif From fec5363ba6fb541bfe4d9c6101f4faf8ffe90f3b Mon Sep 17 00:00:00 2001 From: git Date: Fri, 19 Dec 2025 00:59:55 +0000 Subject: [PATCH 2047/2435] Update default gems list at 5c0c0dd8737c8225f0ebcf0eaf3fb8 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 3d1807beb6100a..3379433f2a72ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -308,7 +308,7 @@ The following default gems are updated. * openssl 4.0.0 * optparse 0.8.1 * pp 0.6.3 -* prism 1.6.0 +* prism 1.7.0 * psych 5.3.1 * resolv 0.7.0 * stringio 3.2.0 From 68a900e30b4ca1537d7975c3a619fd94fca7b084 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 18 Dec 2025 17:03:15 -0800 Subject: [PATCH 2048/2435] add news for pack / unpack directives --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index 3379433f2a72ca..a5f657fb18f7a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -87,6 +87,9 @@ Note: We're only listing outstanding class updates. * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] + * `Array#pack` accepts a new format `R` and `r` for unpacking unsigned + and signed LEB128 encoded integers. [[Feature #21785]] + * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -239,6 +242,9 @@ Note: We're only listing outstanding class updates. * `String#strip`, `strip!`, `lstrip`, `lstrip!`, `rstrip`, and `rstrip!` are extended to accept `*selectors` arguments. [[Feature #21552]] + * `String#unpack` accepts a new format `R` and `r` for unpacking unsigned + and signed LEB128 encoded integers. [[Feature #21785]] + * Thread * Introduce support for `Thread#raise(cause:)` argument similar to @@ -545,3 +551,4 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 [Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 +[Feature #21785]: https://bugs.ruby-lang.org/issues/21785 From 81ad407475149ea47a78875444e0aef5ee4f6635 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 19 Dec 2025 11:43:58 +0900 Subject: [PATCH 2049/2435] Fix rbs test failure caused by minitest-6 (#15643) * Fix rbs test failure caused by minitest6 * Bundle minitest-6.0.0 --- common.mk | 2 +- gems/bundled_gems | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common.mk b/common.mk index 50abfeab8a4df6..c0d663f68e5658 100644 --- a/common.mk +++ b/common.mk @@ -1566,7 +1566,7 @@ yes-install-for-test-bundled-gems: yes-update-default-gemspecs $(XRUBY) -C "$(srcdir)" -r./tool/lib/gem_env.rb bin/gem \ install --no-document --conservative \ "hoe" "json-schema:5.1.0" "test-unit-rr" "simplecov" "simplecov-html" "simplecov-json" "rspec" "zeitwerk" \ - "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" + "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" "minitest-mock" test-bundled-gems-fetch: yes-test-bundled-gems-fetch yes-test-bundled-gems-fetch: clone-bundled-gems-src diff --git a/gems/bundled_gems b/gems/bundled_gems index 6717d7e1924180..3a2d410f269d7b 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.27.0 https://github.com/minitest/minitest +minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.3 https://github.com/test-unit/test-unit @@ -18,7 +18,7 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.10.0.pre.2 https://github.com/ruby/rbs +rbs 3.10.0.pre.2 https://github.com/ruby/rbs badcb9165b52c1b7ccaa6251e4d5bbd78329c5a7 typeprof 0.31.0 https://github.com/ruby/typeprof debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc From b90d3a6beef7e960dca737efcbff18f8fffa31c0 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 19 Dec 2025 02:44:57 +0000 Subject: [PATCH 2050/2435] Update bundled gems list as of 2025-12-19 --- NEWS.md | 6 +++--- gems/bundled_gems | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index a5f657fb18f7a3..b10270c17eae25 100644 --- a/NEWS.md +++ b/NEWS.md @@ -330,19 +330,19 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.27.0 +* minitest 6.0.0 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.3 * rexml 3.4.4 * net-ftp 0.3.9 -* net-imap 0.6.1 +* net-imap 0.6.2 * net-smtp 0.5.1 * matrix 0.4.3 * prime 0.1.4 * rbs 3.10.0.pre.2 * typeprof 0.31.0 -* debug 1.11.0 +* debug 1.11.1 * base64 0.3.0 * bigdecimal 4.0.1 * drb 2.2.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index 3a2d410f269d7b..a305ca7091d775 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,14 +13,14 @@ test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp -net-imap 0.6.1 https://github.com/ruby/net-imap +net-imap 0.6.2 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime rbs 3.10.0.pre.2 https://github.com/ruby/rbs badcb9165b52c1b7ccaa6251e4d5bbd78329c5a7 typeprof 0.31.0 https://github.com/ruby/typeprof -debug 1.11.0 https://github.com/ruby/debug +debug 1.11.1 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong From 305f04210545d6801b2a4821a91858f0aed84f01 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 19 Dec 2025 10:26:59 +0900 Subject: [PATCH 2051/2435] NEWS.md: Sort items in alphabetical order --- NEWS.md | 121 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/NEWS.md b/NEWS.md index b10270c17eae25..af3e78ddaaba3f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -33,6 +33,24 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Array + + * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] + * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] + * `Array#pack` accepts a new format `R` and `r` for unpacking unsigned + and signed LEB128 encoded integers. [[Feature #21785]] + +* Binding + + * `Binding#local_variables` does no longer include numbered parameters. + Also, `Binding#local_variable_get`, `Binding#local_variable_set`, and + `Binding#local_variable_defined?` reject to handle numbered parameters. + [[Bug #21049]] + + * `Binding#implicit_parameters`, `Binding#implicit_parameter_get`, and + `Binding#implicit_parameter_defined?` have been added to access + numbered parameters and "it" parameter. [[Bug #21049]] + * Enumerator * `Enumerator.produce` now accepts an optional `size` keyword argument @@ -57,50 +75,6 @@ Note: We're only listing outstanding class updates. [[Feature #21701]] -* Kernel - - * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, - allowing control over which instance variables are displayed in the `#inspect` string: - - ```ruby - class DatabaseConfig - def initialize(host, user, password) - @host = host - @user = user - @password = password - end - - private def instance_variables_to_inspect = [:@host, :@user] - end - - conf = DatabaseConfig.new("localhost", "root", "hunter2") - conf.inspect #=> # - ``` - - [[Feature #21219]] - - * A deprecated behavior, process creation by `Kernel#open` with a - leading `|`, was removed. [[Feature #19630]] - -* Array - - * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] - * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] - - * `Array#pack` accepts a new format `R` and `r` for unpacking unsigned - and signed LEB128 encoded integers. [[Feature #21785]] - -* Binding - - * `Binding#local_variables` does no longer include numbered parameters. - Also, `Binding#local_variable_get`, `Binding#local_variable_set`, and - `Binding#local_variable_defined?` reject to handle numbered parameters. - [[Bug #21049]] - - * `Binding#implicit_parameters`, `Binding#implicit_parameter_get`, and - `Binding#implicit_parameter_defined?` have been added to access - numbered parameters and "it" parameter. [[Bug #21049]] - * ErrorHighlight * When an ArgumentError is raised, it now displays code snippets for @@ -119,6 +93,18 @@ Note: We're only listing outstanding class updates. from test.rb:3:in '
' ``` +* Fiber + + * Introduce support for `Fiber#raise(cause:)` argument similar to + `Kernel#raise`. [[Feature #21360]] + +* Fiber::Scheduler + + * Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a + given exception. The initial use case is to interrupt a fiber that is + waiting on a blocking IO operation when the IO operation is closed. + [[Feature #21166]] + * File * `File::Stat#birthtime` is now available on Linux via the statx @@ -133,10 +119,40 @@ Note: We're only listing outstanding class updates. * A deprecated behavior, process creation by `IO` class methods with a leading `|`, was removed. [[Feature #19630]] +* Kernel + + * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, + allowing control over which instance variables are displayed in the `#inspect` string: + + ```ruby + class DatabaseConfig + def initialize(host, user, password) + @host = host + @user = user + @password = password + end + + private def instance_variables_to_inspect = [:@host, :@user] + end + + conf = DatabaseConfig.new("localhost", "root", "hunter2") + conf.inspect #=> # + ``` + + [[Feature #21219]] + + * A deprecated behavior, process creation by `Kernel#open` with a + leading `|`, was removed. [[Feature #19630]] + * Math * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] +* Pathname + + * Pathname has been promoted from a default gem to a core class of Ruby. + [[Feature #17473]] + * Proc * `Proc#parameters` now shows anonymous optional parameters as `[:opt]` @@ -250,23 +266,6 @@ Note: We're only listing outstanding class updates. * Introduce support for `Thread#raise(cause:)` argument similar to `Kernel#raise`. [[Feature #21360]] -* Fiber - - * Introduce support for `Fiber#raise(cause:)` argument similar to - `Kernel#raise`. [[Feature #21360]] - -* Fiber::Scheduler - - * Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a - given exception. The initial use case is to interrupt a fiber that is - waiting on a blocking IO operation when the IO operation is closed. - [[Feature #21166]] - -* Pathname - - * Pathname has been promoted from a default gem to a core class of Ruby. - [[Feature #17473]] - ## Stdlib updates The following bundled gems are promoted from default gems. From 42d66b894cdfa356ed67af3000f79f7b2e9185fe Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:12:12 +0900 Subject: [PATCH 2052/2435] Fix: Do not check open_timeout twice (#15626) --- ext/socket/ipsocket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 88225f76e5c882..c9fcfb64fb4eb0 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -633,7 +633,7 @@ init_fast_fallback_inetsock_internal(VALUE v) unsigned int t; if (!NIL_P(open_timeout)) { t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(open_timeout)) { + } else if (!NIL_P(resolv_timeout)) { t = rsock_value_timeout_to_msec(resolv_timeout); } else { t = 0; @@ -1340,7 +1340,7 @@ rsock_init_inetsock( unsigned int t; if (!NIL_P(open_timeout)) { t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(open_timeout)) { + } else if (!NIL_P(resolv_timeout)) { t = rsock_value_timeout_to_msec(resolv_timeout); } else { t = 0; From bfba65d8c1fcdc75ea3fc0f78d8bc7514e7afabd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 09:26:23 +0900 Subject: [PATCH 2053/2435] Extract `Test::JobServer` module A placeholder to handle GNU make jobserver option. spec/default.mspec didn't handle the jobserver using a FIFO. --- bootstraptest/runner.rb | 31 ++----------------------- spec/default.mspec | 33 ++++---------------------- tool/lib/test/jobserver.rb | 47 ++++++++++++++++++++++++++++++++++++++ tool/lib/test/unit.rb | 24 +++---------------- 4 files changed, 57 insertions(+), 78 deletions(-) create mode 100644 tool/lib/test/jobserver.rb diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 8988ac20ce41f6..04de0c93b90cb0 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -16,6 +16,7 @@ $:.unshift File.join(File.dirname(__FILE__), '../lib') retry end +require_relative '../tool/lib/test/jobserver' if !Dir.respond_to?(:mktmpdir) # copied from lib/tmpdir.rb @@ -110,35 +111,7 @@ def putc(c) def wn=(wn) unless wn == 1 - if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS") - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - else - r.close_on_exec = true - w.close_on_exec = true - tokens = r.read_nonblock(wn > 0 ? wn : 1024, exception: false) - r.close - if String === tokens - tokens.freeze - auth = w - w = nil - at_exit {auth << tokens; auth.close} - wn = tokens.size + 1 - else - w.close - wn = 1 - end - end - end + wn = Test::JobServer.max_jobs(wn > 0 ? wn : 1024, ENV.delete("MAKEFLAGS")) || wn if wn <= 0 require 'etc' wn = [Etc.nprocessors / 2, 1].max diff --git a/spec/default.mspec b/spec/default.mspec index 058835cd10d91c..d756dc31ffd566 100644 --- a/spec/default.mspec +++ b/spec/default.mspec @@ -9,6 +9,7 @@ ENV["CHECK_CONSTANT_LEAKS"] ||= "true" require "./rbconfig" unless defined?(RbConfig) require_relative "../tool/test-coverage" if ENV.key?("COVERAGE") +require_relative "../tool/lib/test/jobserver" load File.dirname(__FILE__) + '/ruby/default.mspec' OBJDIR = File.expand_path("spec/ruby/optional/capi/ext") unless defined?(OBJDIR) class MSpecScript @@ -50,34 +51,10 @@ end module MSpecScript::JobServer def cores(max = 1) - if max > 1 and /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ ENV["MAKEFLAGS"] - cores = 1 - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - jobtokens = r.read_nonblock(max - 1) - cores = jobtokens.size - if cores > 0 - cores += 1 - jobserver = w - w = nil - at_exit { - jobserver.print(jobtokens) - jobserver.close - } - MSpecScript::JobServer.module_eval do - remove_method :cores - define_method(:cores) do - cores - end - end - return cores - end - rescue Errno::EBADF - ensure - r&.close - w&.close - end + MSpecScript::JobServer.remove_method :cores + if cores = Test::JobServer.max_jobs(max) + MSpecScript::JobServer.define_method(:cores) { cores } + return cores end super end diff --git a/tool/lib/test/jobserver.rb b/tool/lib/test/jobserver.rb new file mode 100644 index 00000000000000..7b889163b02aa2 --- /dev/null +++ b/tool/lib/test/jobserver.rb @@ -0,0 +1,47 @@ +module Test + module JobServer + end +end + +class << Test::JobServer + def connect(makeflags = ENV["MAKEFLAGS"]) + return unless /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags + begin + if fifo = $3 + fifo.gsub!(/\\(?=.)/, '') + r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) + w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) + else + r = IO.for_fd($1.to_i(10), "rb", autoclose: false) + w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + end + rescue + r&.close + nil + else + return r, w + end + end + + def acquire_possible(r, w, max) + return unless tokens = r.read_nonblock(max - 1, exception: false) + if (jobs = tokens.size) > 0 + jobserver, w = w, nil + at_exit do + jobserver.print(tokens) + jobserver.close + end + end + return jobs + 1 + rescue Errno::EBADF + ensure + r&.close + w&.close + end + + def max_jobs(max = 2, makeflags = ENV["MAKEFLAGS"]) + if max > 1 and (r, w = connect(makeflags)) + acquire_possible(r, w, max) + end + end +end diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index 7d43e825e179eb..2663b7b76a10bf 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -19,6 +19,7 @@ def warn(message, category: nil, **kwargs) require_relative '../colorize' require_relative '../leakchecker' require_relative '../test/unit/testcase' +require_relative '../test/jobserver' require 'optparse' # See Test::Unit @@ -262,27 +263,8 @@ def process_args(args = []) def non_options(files, options) @jobserver = nil - makeflags = ENV.delete("MAKEFLAGS") - if !options[:parallel] and - /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - nil - else - r.close_on_exec = true - w.close_on_exec = true - @jobserver = [r, w] - options[:parallel] ||= 256 # number of tokens to acquire first - end + if !options[:parallel] and @jobserver = Test::JobServer.connect(ENV.delete("MAKEFLAGS")) + options[:parallel] ||= 256 # number of tokens to acquire first end @worker_timeout = EnvUtil.apply_timeout_scale(options[:worker_timeout] || 1200) super From 6f6ea70dcea1bbcb00774c1da58735a78c8924c2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 09:47:22 +0900 Subject: [PATCH 2054/2435] Just passing FDs does not need to create IO objects --- spec/ruby/optional/capi/spec_helper.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb index 1076b206ecbdb6..e7abf46e6ccf65 100644 --- a/spec/ruby/optional/capi/spec_helper.rb +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -122,13 +122,9 @@ def setup_make opts = {} if /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ make_flags - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - rescue Errno::EBADF - else - opts[r] = r - opts[w] = w + [$1, $2].each do |fd| + fd = fd.to_i(10) + opts[fd] = fd end end From f81c62be3df7b29c4c3b6adaa16599b38d0c0c46 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Fri, 19 Dec 2025 11:46:58 +0900 Subject: [PATCH 2055/2435] Terminate `args_tail_basic` rule with a semicolon Semicolon is optional however it clarifies the end of the rule. --- parse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/parse.y b/parse.y index 4754435fab0d21..067f45e6d6044a 100644 --- a/parse.y +++ b/parse.y @@ -2943,6 +2943,7 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) $$ = new_args_tail(p, 0, 0, $1, &@1); /*% ripper: [Qnil, Qnil, $:1] %*/ } + ; %rule def_endless_method(bodystmt) : defn_head[head] f_opt_paren_args[args] '=' bodystmt From 47244b0f306161f175b21f74d801882e950e18be Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:44:35 +0900 Subject: [PATCH 2056/2435] Fix: Specifying 0 should cause an immediate timeout (#15641) This change fixes a bug in which specifying 0 for timeout-related options (such as the `timeout` option of `Addrinfo.getaddrinfo`) incorrectly results in an infinite wait. (This change overwrites https://github.com/ruby/ruby/pull/15626 .) --- ext/socket/ipsocket.c | 27 +++++---------------------- ext/socket/raddrinfo.c | 29 ++++++++++++++++------------- ext/socket/rubysocket.h | 5 ++--- ext/socket/socket.c | 8 ++++---- ext/socket/tcpsocket.c | 2 +- ext/socket/udpsocket.c | 6 +++--- test/socket/test_socket.rb | 2 +- 7 files changed, 32 insertions(+), 47 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index c9fcfb64fb4eb0..e952b7871b3f6f 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -83,15 +83,13 @@ init_inetsock_internal(VALUE v) VALUE open_timeout = arg->open_timeout; VALUE timeout; VALUE starts_at; - unsigned int timeout_msec; timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout; - timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); starts_at = current_clocktime(); arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - (type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec); + (type == INET_SERVER) ? AI_PASSIVE : 0, timeout); /* * Maybe also accept a local address @@ -99,7 +97,7 @@ init_inetsock_internal(VALUE v) if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) { arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv, - family, SOCK_STREAM, 0, 0); + family, SOCK_STREAM, 0, timeout); } VALUE io = Qnil; @@ -630,14 +628,7 @@ init_fast_fallback_inetsock_internal(VALUE v) arg->getaddrinfo_shared = NULL; int family = arg->families[0]; - unsigned int t; - if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(resolv_timeout)) { - t = rsock_value_timeout_to_msec(resolv_timeout); - } else { - t = 0; - } + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; arg->remote.res = rsock_addrinfo( arg->remote.host, @@ -1337,15 +1328,7 @@ rsock_init_inetsock( * Maybe also accept a local address */ if (!NIL_P(local_host) || !NIL_P(local_serv)) { - unsigned int t; - if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(resolv_timeout)) { - t = rsock_value_timeout_to_msec(resolv_timeout); - } else { - t = 0; - } - + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; local_res = rsock_addrinfo( local_host, local_serv, @@ -1609,7 +1592,7 @@ static VALUE ip_s_getaddress(VALUE obj, VALUE host) { union_sockaddr addr; - struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, Qnil); socklen_t len = res->ai->ai_addrlen; /* just take the first one */ diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 3dcbe7717a0ca2..6cdf5c6abc40e7 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -293,7 +293,7 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } -unsigned int +static int rsock_value_timeout_to_msec(VALUE timeout) { double seconds = NUM2DBL(timeout); @@ -308,7 +308,7 @@ rsock_value_timeout_to_msec(VALUE timeout) #if GETADDRINFO_IMPL == 0 static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { return getaddrinfo(hostp, portp, hints, ai); } @@ -346,7 +346,7 @@ fork_safe_getaddrinfo(void *arg) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { struct getaddrinfo_arg arg; MEMZERO(&arg, struct getaddrinfo_arg, 1); @@ -367,11 +367,11 @@ struct getaddrinfo_arg int err, gai_errno, refcount, done, cancelled, timedout; rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; - unsigned int timeout; + int timeout; }; static struct getaddrinfo_arg * -allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, unsigned int timeout) +allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, int timeout) { size_t hostp_offset = sizeof(struct getaddrinfo_arg); size_t portp_offset = hostp_offset + (hostp ? strlen(hostp) + 1 : 0); @@ -465,8 +465,11 @@ wait_getaddrinfo(void *ptr) struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; rb_nativethread_lock_lock(&arg->lock); while (!arg->done && !arg->cancelled) { - unsigned long msec = arg->timeout; - if (msec > 0) { + long msec = arg->timeout; + if (msec == 0) { + arg->cancelled = 1; + arg->timedout = 1; + } else if (msec > 0) { rb_native_cond_timedwait(&arg->cond, &arg->lock, msec); if (!arg->done) { arg->cancelled = 1; @@ -549,7 +552,7 @@ fork_safe_do_getaddrinfo(void *ptr) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int timeout) { int retry; struct getaddrinfo_arg *arg; @@ -1021,7 +1024,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, } struct rb_addrinfo* -rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout) +rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout) { struct rb_addrinfo* res = NULL; struct addrinfo *ai; @@ -1056,7 +1059,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { - error = rb_getaddrinfo(hostp, portp, hints, &ai, timeout); + int t = NIL_P(timeout) ? -1 : rsock_value_timeout_to_msec(timeout); + error = rb_getaddrinfo(hostp, portp, hints, &ai, t); if (error == 0) { res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo)); res->allocated_by_malloc = 0; @@ -1089,7 +1093,7 @@ rsock_fd_family(int fd) } struct rb_addrinfo* -rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout) +rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout) { struct addrinfo hints; @@ -1380,8 +1384,7 @@ call_getaddrinfo(VALUE node, VALUE service, hints.ai_flags = NUM2INT(flags); } - unsigned int t = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); - res = rsock_getaddrinfo(node, service, &hints, socktype_hack, t); + res = rsock_getaddrinfo(node, service, &hints, socktype_hack, timeout); if (res == NULL) rb_raise(rb_eSocket, "host not found"); diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 638b7ede6ec72e..2ec3ab335aef86 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -327,8 +327,8 @@ void rb_freeaddrinfo(struct rb_addrinfo *ai); VALUE rsock_freeaddrinfo(VALUE arg); int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); int rsock_fd_family(int fd); -struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout); -struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout); +struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout); +struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout); VALUE rsock_fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len); VALUE rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len); @@ -453,7 +453,6 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar # endif #endif -unsigned int rsock_value_timeout_to_msec(VALUE); NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); void rsock_init_basicsocket(void); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index 26bf0bae8c046d..a8e5ae81190c21 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -965,7 +965,7 @@ sock_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, sock_sockaddr); } @@ -1183,7 +1183,7 @@ sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _) norevlookup = rsock_do_not_reverse_lookup; } - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); ret = make_addrinfo(res, norevlookup); rb_freeaddrinfo(res); @@ -1279,7 +1279,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM; /* af */ hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af); - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); sap = res->ai->ai_addr; salen = res->ai->ai_addrlen; } @@ -1335,7 +1335,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) static VALUE sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host) { - struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, Qnil); VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen); rb_freeaddrinfo(res); diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 300a426eda8471..7ce536e0af9ca3 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -113,7 +113,7 @@ tcp_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("TCPSocket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, tcp_sockaddr); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 5538f24523fc09..b2bc92553886aa 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -84,7 +84,7 @@ udp_connect(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -129,7 +129,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -212,7 +212,7 @@ udp_send(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, arg.fptr); arg.sarg.fd = arg.fptr->fd; arg.sarg.flags = NUM2INT(flags); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, Qnil); ret = rb_ensure(udp_send_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port); diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 686114f05c1418..c42527f3703173 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -1011,7 +1011,7 @@ def test_tcp_socket_all_hostname_resolution_failed Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| case family when Socket::AF_INET6 then raise SocketError - when Socket::AF_INET then sleep(0.001); raise SocketError, "Last hostname resolution error" + when Socket::AF_INET then sleep(0.01); raise SocketError, "Last hostname resolution error" end end From 8efaf5e6b6a25e0d237f3d71b75865661ae98268 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 19 Dec 2025 15:54:02 +0900 Subject: [PATCH 2057/2435] Adjust Stdlib section with 4.0.0 and added reference of RubyGems release notes. --- NEWS.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index af3e78ddaaba3f..7436d574016595 100644 --- a/NEWS.md +++ b/NEWS.md @@ -284,7 +284,7 @@ The following bundled gems are promoted from default gems. We only list stdlib changes that are notable feature changes. Other changes are listed in the following sections. We also listed release -history from the previous bundled version that is Ruby 3.3.0 if it has GitHub +history from the previous bundled version that is Ruby 3.4.0 if it has GitHub releases. The following default gem is added. @@ -324,9 +324,6 @@ The following default gems are updated. * weakref 0.1.4 * zlib 3.2.2 -The following bundled gems are added. - - The following bundled gems are updated. * minitest 6.0.0 @@ -349,6 +346,15 @@ The following bundled gems are updated. * csv 3.3.5 * repl_type_completor 0.1.12 +### RubyGems and Bundler + +see the following links for details. + +* [Upgrading to RubyGems/Bundler 4 - RubyGems Blog](https://blog.rubygems.org/2025/12/03/upgrade-to-rubygems-bundler-4.html) +* [4.0.0 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/03/4.0.0-released.html) +* [4.0.1 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/09/4.0.1-released.html) +* [4.0.2 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/17/4.0.2-released.html) + ## Supported platforms * Windows From 7b19f1a1ef4c66a8a8f8fb64cb08ecca2044884c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 14:24:33 +0900 Subject: [PATCH 2058/2435] Skip RBS Ractor test on Windows It seems hunging up. --- tool/rbs_skip_tests_windows | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/rbs_skip_tests_windows b/tool/rbs_skip_tests_windows index bdf3dddfd7a594..43888c98a71324 100644 --- a/tool/rbs_skip_tests_windows +++ b/tool/rbs_skip_tests_windows @@ -1,5 +1,8 @@ ARGFTest Failing on Windows +RactorSingletonTest Hangs up on Windows +RactorInstanceTest Hangs up on Windows + # NotImplementedError: fileno() function is unimplemented on this machine test_fileno(DirInstanceTest) test_fchdir(DirSingletonTest) From f0472f2d49c896bf072d327ff2a4ee009115266f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 17:16:15 +0900 Subject: [PATCH 2059/2435] [Feature #21785] [DOC] LEB128 support --- doc/language/packed_data.rdoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index dcb4d557352d88..97079f7f08dd8f 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -342,6 +342,22 @@ for one element in the input or output array. s.unpack('U*') # => [4194304] +- 'r' - Signed LEB128-encoded integer + (see {Signed LEB128}[https://en.wikipedia.org/wiki/LEB128#Signed_LEB128]) + + s = [1, 127, -128, 16383, -16384].pack("r*") + # => "\x01\xFF\x00\x80\x7F\xFF\xFF\x00\x80\x80\x7F" + s.unpack('r*') + # => [1, 127, -128, 16383, -16384] + +- 'R' - Unsigned LEB128-encoded integer + (see {Unsigned LEB128}[https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128]) + + s = [1, 127, 128, 16383, 16384].pack("R*") + # => "\x01\x7F\x80\x01\xFF\x7F\x80\x80\x01" + s.unpack('R*') + # => [1, 127, 128, 16383, 16384] + - 'w' - BER-encoded integer (see {BER encoding}[https://en.wikipedia.org/wiki/X.690#BER_encoding]): From dd2f7d6ae6ab53bea7a179338378e1d32c306747 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 13:51:27 +0900 Subject: [PATCH 2060/2435] [Bug #21794] Fix for platforms where O_CLOEXEC is not available --- box.c | 12 ++++++++++-- depend | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/box.c b/box.c index 3182fb4eb20758..54bd5c6258343f 100644 --- a/box.c +++ b/box.c @@ -9,6 +9,7 @@ #include "internal/file.h" #include "internal/gc.h" #include "internal/hash.h" +#include "internal/io.h" #include "internal/load.h" #include "internal/st.h" #include "internal/variable.h" @@ -627,8 +628,14 @@ copy_ext_file(const char *src_path, const char *dst_path) # else const int bin = 0; # endif - const int src_fd = open(src_path, O_RDONLY|bin); +# ifdef O_CLOEXEC + const int cloexec = O_CLOEXEC; +# else + const int cloexec = 0; +# endif + const int src_fd = open(src_path, O_RDONLY|cloexec|bin); if (src_fd < 0) return COPY_ERROR_SRC_OPEN; + if (!cloexec) rb_maygvl_fd_fix_cloexec(src_fd); struct stat src_st; if (fstat(src_fd, &src_st)) { @@ -636,11 +643,12 @@ copy_ext_file(const char *src_path, const char *dst_path) return COPY_ERROR_SRC_STAT; } - const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU); + const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|cloexec|bin, S_IRWXU); if (dst_fd < 0) { close(src_fd); return COPY_ERROR_DST_OPEN; } + if (!cloexec) rb_maygvl_fd_fix_cloexec(dst_fd); enum copy_error_type ret = COPY_ERROR_NONE; diff --git a/depend b/depend index 8834814227b0e8..11397bf647adf1 100644 --- a/depend +++ b/depend @@ -745,6 +745,7 @@ box.$(OBJEXT): $(top_srcdir)/internal/file.h box.$(OBJEXT): $(top_srcdir)/internal/gc.h box.$(OBJEXT): $(top_srcdir)/internal/hash.h box.$(OBJEXT): $(top_srcdir)/internal/imemo.h +box.$(OBJEXT): $(top_srcdir)/internal/io.h box.$(OBJEXT): $(top_srcdir)/internal/load.h box.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h box.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -951,6 +952,7 @@ box.$(OBJEXT): {$(VPATH)}internal/value_type.h box.$(OBJEXT): {$(VPATH)}internal/variable.h box.$(OBJEXT): {$(VPATH)}internal/warning_push.h box.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +box.$(OBJEXT): {$(VPATH)}io.h box.$(OBJEXT): {$(VPATH)}iseq.h box.$(OBJEXT): {$(VPATH)}method.h box.$(OBJEXT): {$(VPATH)}missing.h From 9ee2243bede65fba8b7fbce54a232b8c063a5616 Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Fri, 19 Dec 2025 11:58:26 +0100 Subject: [PATCH 2061/2435] Fiber scheduler: invoke `#io_write` hook on IO flush (#15609) Previously, calling IO#flush or closing an IO with unflushed buffered writes would just invoke `#blocking_operation_wait` and flush the write buffer using a `write` syscall. This change adds flushing through the fiber scheduler by invoking the `#io_write` hook. * Prefer IO::Buffer#write in IOScheduler * Use Dir.tmpdir for test file * Correctly handle errors in io_flush_buffer_fiber_scheduler --- io.c | 24 ++++++++++ test/fiber/scheduler.rb | 23 ++++++++++ test/fiber/test_scheduler.rb | 89 ++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/io.c b/io.c index e739cee3795eec..99268e8f05b1a4 100644 --- a/io.c +++ b/io.c @@ -1418,10 +1418,34 @@ io_flush_buffer_sync(void *arg) return (VALUE)-1; } +static inline VALUE +io_flush_buffer_fiber_scheduler(VALUE scheduler, rb_io_t *fptr) +{ + VALUE ret = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, fptr->wbuf.ptr+fptr->wbuf.off, fptr->wbuf.len, 0); + if (!UNDEF_P(ret)) { + ssize_t result = rb_fiber_scheduler_io_result_apply(ret); + if (result > 0) { + fptr->wbuf.off += result; + fptr->wbuf.len -= result; + } + return result >= 0 ? (VALUE)0 : (VALUE)-1; + } + return ret; +} + static VALUE io_flush_buffer_async(VALUE arg) { rb_io_t *fptr = (rb_io_t *)arg; + + VALUE scheduler = rb_fiber_scheduler_current(); + if (scheduler != Qnil) { + VALUE result = io_flush_buffer_fiber_scheduler(scheduler, fptr); + if (!UNDEF_P(result)) { + return result; + } + } + return rb_io_blocking_region_wait(fptr, io_flush_buffer_sync, fptr, RUBY_IO_WRITABLE); } diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 029c5043dc1b14..07b15c5ce4b86a 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -488,6 +488,29 @@ def blocking(&block) end end +class IOScheduler < Scheduler + def __io_ops__ + @__io_ops__ ||= [] + end + + def io_write(io, buffer, length, offset) + fd = io.fileno + str = buffer.get_string + __io_ops__ << [:io_write, fd, str] + Fiber.blocking { buffer.write(IO.for_fd(fd), 0, offset) } + end +end + +class IOErrorScheduler < Scheduler + def io_read(io, buffer, length, offset) + return -Errno::EBADF::Errno + end + + def io_write(io, buffer, length, offset) + return -Errno::EINVAL::Errno + end +end + # This scheduler has a broken implementation of `unblock`` in the sense that it # raises an exception. This is used to test the behavior of the scheduler when # unblock raises an exception. diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 97ce94bd270b69..4a8b4ee62d8e49 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true require 'test/unit' +require 'securerandom' +require 'fileutils' require_relative 'scheduler' class TestFiberScheduler < Test::Unit::TestCase @@ -283,4 +285,91 @@ def test_post_fork_fiber_blocking ensure thread.kill rescue nil end + + def test_io_write_on_flush + fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") + write_fd = nil + io_ops = nil + thread = Thread.new do + scheduler = IOScheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + File.open(fn, 'w+') do |f| + write_fd = f.fileno + f << 'foo' + f.flush + f << 'bar' + end + end + io_ops = scheduler.__io_ops__ + end + thread.join + assert_equal [ + [:io_write, write_fd, 'foo'], + [:io_write, write_fd, 'bar'] + ], io_ops + + assert_equal 'foobar', IO.read(fn) + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + + def test_io_read_error + fn = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it.read } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EBADF, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + + def test_io_write_error + fn = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it.sync = true; it << 'foo' } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EINVAL, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + + def test_io_write_flush_error + fn = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it << 'foo' } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EINVAL, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end end From e23a918a711c719f6ac257c83b81284fd137c1f6 Mon Sep 17 00:00:00 2001 From: Sharon Rosner Date: Fri, 19 Dec 2025 12:02:57 +0100 Subject: [PATCH 2062/2435] Update NEWS.md for Fiber Scheduler (#15629) --- NEWS.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NEWS.md b/NEWS.md index 7436d574016595..b302e188520f41 100644 --- a/NEWS.md +++ b/NEWS.md @@ -105,6 +105,15 @@ Note: We're only listing outstanding class updates. waiting on a blocking IO operation when the IO operation is closed. [[Feature #21166]] + * Introduce `Fiber::Scheduler#yield` to allow the fiber scheduler to + continue processing when signal exceptions are disabled. + [[Bug #21633]] + + * Reintroduce the `Fiber::Scheduler#io_close` hook for asynchronous `IO#close`. + + * Invoke `Fiber::Scheduler#io_write` when flushing the IO write buffer. + [[Bug #21789]] + * File * `File::Stat#birthtime` is now available on Linux via the statx @@ -266,6 +275,11 @@ Note: We're only listing outstanding class updates. * Introduce support for `Thread#raise(cause:)` argument similar to `Kernel#raise`. [[Feature #21360]] +* Pathname + + * Pathname has been promoted from a default gem to a core class of Ruby. + [[Feature #17473]] + ## Stdlib updates The following bundled gems are promoted from default gems. @@ -552,8 +566,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21552]: https://bugs.ruby-lang.org/issues/21552 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 +[Bug #21633]: https://bugs.ruby-lang.org/issues/21633 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 [Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 [Feature #21785]: https://bugs.ruby-lang.org/issues/21785 +[Bug #21789]: https://bugs.ruby-lang.org/issues/21789 From 8c7e6f241f5939eb572a47fd99bf626413eb2907 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 19 Dec 2025 12:58:05 +0000 Subject: [PATCH 2063/2435] Update bundled gems list as of 2025-12-19 --- NEWS.md | 4 ++++ gems/bundled_gems | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index b302e188520f41..b5071761976d05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -295,6 +295,9 @@ The following bundled gems are promoted from default gems. * readline 0.0.4 * fiddle 1.1.8 +The following bundled gems are added. + + We only list stdlib changes that are notable feature changes. Other changes are listed in the following sections. We also listed release @@ -345,6 +348,7 @@ The following bundled gems are updated. * rake 13.3.1 * test-unit 3.7.3 * rexml 3.4.4 +* rss 0.3.2 * net-ftp 0.3.9 * net-imap 0.6.2 * net-smtp 0.5.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index a305ca7091d775..dd28b2bdfe2e5f 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -11,7 +11,7 @@ power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml -rss 0.3.1 https://github.com/ruby/rss +rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp net-imap 0.6.2 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop From 04e90fe200d736db0a32a794b8dc742fa0cb5441 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Sat, 20 Dec 2025 03:49:50 +0900 Subject: [PATCH 2064/2435] skip TestFiberScheduler#test_io_write_on_flush because it makes GC.stat test fragile --- test/fiber/test_scheduler.rb | 49 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 4a8b4ee62d8e49..c20fe86ff4531d 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -287,33 +287,36 @@ def test_post_fork_fiber_blocking end def test_io_write_on_flush - fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") - write_fd = nil - io_ops = nil - thread = Thread.new do - scheduler = IOScheduler.new - Fiber.set_scheduler scheduler + omit "skip this test because it makes CI fragile" + begin + fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") + write_fd = nil + io_ops = nil + thread = Thread.new do + scheduler = IOScheduler.new + Fiber.set_scheduler scheduler - Fiber.schedule do - File.open(fn, 'w+') do |f| - write_fd = f.fileno - f << 'foo' - f.flush - f << 'bar' + Fiber.schedule do + File.open(fn, 'w+') do |f| + write_fd = f.fileno + f << 'foo' + f.flush + f << 'bar' + end end + io_ops = scheduler.__io_ops__ end - io_ops = scheduler.__io_ops__ - end - thread.join - assert_equal [ - [:io_write, write_fd, 'foo'], - [:io_write, write_fd, 'bar'] - ], io_ops + thread.join + assert_equal [ + [:io_write, write_fd, 'foo'], + [:io_write, write_fd, 'bar'] + ], io_ops - assert_equal 'foobar', IO.read(fn) - ensure - thread.kill rescue nil - FileUtils.rm_f(fn) + assert_equal 'foobar', IO.read(fn) + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end end def test_io_read_error From d9c0d4c71cd3500b2307c518b423ee58eeff9ae5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 19 Dec 2025 02:21:55 -0800 Subject: [PATCH 2065/2435] Don't copy invalidated CME in rb_vm_cc_table_dup The cc_entries list associated with the invalidated CME can be deleted from the table during GC, so it isn't safe to copy (and we shouldn't copy it anyways, it's stale data). --- vm_method.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vm_method.c b/vm_method.c index 2a6323e59300b5..2b3ac74d573434 100644 --- a/vm_method.c +++ b/vm_method.c @@ -149,10 +149,21 @@ vm_cc_table_dup_i(ID key, VALUE old_ccs_ptr, void *data) { VALUE new_table = (VALUE)data; struct rb_class_cc_entries *old_ccs = (struct rb_class_cc_entries *)old_ccs_ptr; + + if (METHOD_ENTRY_INVALIDATED(old_ccs->cme)) { + // Invalidated CME. This entry will be removed from the old table on + // the next GC mark, so it's unsafe (and undesirable) to copy + return ID_TABLE_CONTINUE; + } + size_t memsize = vm_ccs_alloc_size(old_ccs->capa); struct rb_class_cc_entries *new_ccs = ruby_xcalloc(1, memsize); rb_managed_id_table_insert(new_table, key, (VALUE)new_ccs); + // We hold the VM lock, so invalidation should not have happened between + // our earlier invalidation check and now. + VM_ASSERT(!METHOD_ENTRY_INVALIDATED(old_ccs->cme)); + memcpy(new_ccs, old_ccs, memsize); #if VM_CHECK_MODE > 0 @@ -169,6 +180,7 @@ vm_cc_table_dup_i(ID key, VALUE old_ccs_ptr, void *data) VALUE rb_vm_cc_table_dup(VALUE old_table) { + ASSERT_vm_locking(); VALUE new_table = rb_vm_cc_table_create(rb_managed_id_table_size(old_table)); rb_managed_id_table_foreach(old_table, vm_cc_table_dup_i, (void *)new_table); return new_table; From d540903ee77e81dd1ec3dec744273fbb394238fa Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Dec 2025 12:45:36 +0000 Subject: [PATCH 2066/2435] [DOC] Harmonize <= methods --- compar.c | 11 ++++++++--- hash.c | 5 ++--- numeric.c | 9 +++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/compar.c b/compar.c index c577e4741ffa13..eda7e83824a4e0 100644 --- a/compar.c +++ b/compar.c @@ -141,10 +141,15 @@ cmp_lt(VALUE x, VALUE y) /* * call-seq: - * obj <= other -> true or false + * self <= other -> true or false + * + * Returns whether +self+ is "less than or equal to" +other+; + * equivalent to (self <=> other) <= 0: + * + * 'foo' <= 'foo' # => true + * 'foo' <= 'food' # => true + * 'food' <= 'foo' # => false * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value less than or equal to 0. */ static VALUE diff --git a/hash.c b/hash.c index 9e1555518ec037..3669f55d5024d0 100644 --- a/hash.c +++ b/hash.c @@ -4888,10 +4888,9 @@ hash_le(VALUE hash1, VALUE hash2) /* * call-seq: - * self <= other_hash -> true or false + * self <= other -> true or false * - * Returns +true+ if the entries of +self+ are a subset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a subset of the entries of +other+: * * h0 = {foo: 0, bar: 1} * h1 = {foo: 0, bar: 1, baz: 2} diff --git a/numeric.c b/numeric.c index 3e770ceed3bc88..a71060c55db252 100644 --- a/numeric.c +++ b/numeric.c @@ -1738,7 +1738,8 @@ flo_lt(VALUE x, VALUE y) * call-seq: * self <= other -> true or false * - * Returns +true+ if +self+ is numerically less than or equal to +other+: + * Returns whether the value of +self+ is less than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 <= 3 # => true * 2.0 <= 3.0 # => true @@ -5081,10 +5082,10 @@ fix_le(VALUE x, VALUE y) /* * call-seq: - * self <= real -> true or false + * self <= other -> true or false * - * Returns +true+ if the value of +self+ is less than or equal to - * that of +other+: + * Returns whether the value of +self+ is less than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 <= 0 # => false * 1 <= 1 # => true From 42c4df9ac6d5974eac7efddf725ecf39f4c0fd9e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Dec 2025 20:06:49 +0000 Subject: [PATCH 2067/2435] [DOC] Harmonize String#<=> --- string.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/string.c b/string.c index 9819a5910fa76e..b11b441ac58536 100644 --- a/string.c +++ b/string.c @@ -4294,23 +4294,26 @@ rb_str_eql(VALUE str1, VALUE str2) /* * call-seq: - * self <=> other_string -> -1, 0, 1, or nil + * self <=> other -> -1, 0, 1, or nil * - * Compares +self+ and +other_string+, returning: + * Compares +self+ and +other+, + * evaluating their _contents_, not their _lengths_. * - * - -1 if +other_string+ is larger. - * - 0 if the two are equal. - * - 1 if +other_string+ is smaller. - * - +nil+ if the two are incomparable. + * Returns: + * + * - +-1+, if +self+ is smaller. + * - +0+, if the two are equal. + * - +1+, if +self+ is larger. + * - +nil+, if the two are incomparable. * * Examples: * - * 'foo' <=> 'foo' # => 0 - * 'foo' <=> 'food' # => -1 - * 'food' <=> 'foo' # => 1 - * 'FOO' <=> 'foo' # => -1 - * 'foo' <=> 'FOO' # => 1 - * 'foo' <=> 1 # => nil + * 'a' <=> 'b' # => -1 + * 'a' <=> 'ab' # => -1 + * 'a' <=> 'a' # => 0 + * 'b' <=> 'a' # => 1 + * 'ab' <=> 'a' # => 1 + * 'a' <=> :a # => nil * * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ From ed0fae5b501dea14617103ab5ec2d042f445ed7f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 18 Dec 2025 20:00:13 -0500 Subject: [PATCH 2068/2435] [ruby/mmtk] Extract heap count to MMTK_HEAP_COUNT macro https://github.com/ruby/mmtk/commit/4e789e118b --- gc/mmtk/mmtk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e1678dcf6ab0b4..58691e5a656b59 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -485,7 +485,9 @@ rb_gc_impl_init(void) rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1); } -static size_t heap_sizes[6] = { +#define MMTK_HEAP_COUNT 5 + +static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { 40, 80, 160, 320, 640, 0 }; @@ -610,7 +612,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags struct MMTk_ractor_cache *ractor_cache = cache_ptr; if (alloc_size > 640) rb_bug("too big"); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < MMTK_HEAP_COUNT; i++) { if (alloc_size == heap_sizes[i]) break; if (alloc_size < heap_sizes[i]) { alloc_size = heap_sizes[i]; From 8274c5e1428b5b88e885857d466822cbadc19761 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 18 Dec 2025 20:01:13 -0500 Subject: [PATCH 2069/2435] [ruby/mmtk] Extract max object size to MMTK_MAX_OBJ_SIZE https://github.com/ruby/mmtk/commit/ed9036c295 --- gc/mmtk/mmtk.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 58691e5a656b59..131aaf38b0fd27 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -462,6 +462,13 @@ void rb_gc_impl_set_params(void *objspace_ptr) { } static VALUE gc_verify_internal_consistency(VALUE self) { return Qnil; } +#define MMTK_HEAP_COUNT 5 +#define MMTK_MAX_OBJ_SIZE 640 + +static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { + 40, 80, 160, 320, MMTK_MAX_OBJ_SIZE, 0 +}; + void rb_gc_impl_init(void) { @@ -469,7 +476,7 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(sizeof(VALUE) * 5)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(640)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); OBJ_FREEZE(gc_constants); @@ -485,12 +492,6 @@ rb_gc_impl_init(void) rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1); } -#define MMTK_HEAP_COUNT 5 - -static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { - 40, 80, 160, 320, 640, 0 -}; - size_t * rb_gc_impl_heap_sizes(void *objspace_ptr) { @@ -611,7 +612,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags struct objspace *objspace = objspace_ptr; struct MMTk_ractor_cache *ractor_cache = cache_ptr; - if (alloc_size > 640) rb_bug("too big"); + if (alloc_size > MMTK_MAX_OBJ_SIZE) rb_bug("too big"); for (int i = 0; i < MMTK_HEAP_COUNT; i++) { if (alloc_size == heap_sizes[i]) break; if (alloc_size < heap_sizes[i]) { @@ -660,7 +661,7 @@ rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size) bool rb_gc_impl_size_allocatable_p(size_t size) { - return size <= 640; + return size <= MMTK_MAX_OBJ_SIZE; } // Malloc From bb0637a92f94dc0aa00e7dd8de4455bb23204068 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 19 Dec 2025 21:22:00 -0500 Subject: [PATCH 2070/2435] ZJIT: [DOC] Fix link to in-repo file. Mention GNU Make requirement --- doc/jit/zjit.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index b5a2f05604983f..c94270554fd349 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -47,7 +47,7 @@ ZJIT. Refer to [Building Ruby](rdoc-ref:contributing/building_ruby.md) for general build prerequists. Additionally, ZJIT requires Rust 1.85.0 or later. Release builds need only `rustc`. Development -builds require `cargo` and may download dependencies. +builds require `cargo` and may download dependencies. GNU Make is required. ### For normal use @@ -352,7 +352,7 @@ Ruby execution involves three distinct stacks and understanding them will help y The Ruby VM uses a single contiguous memory region (`ec->vm_stack`) containing two sub-stacks that grow toward each other. When they meet, stack overflow occurs. -See [doc/contributing/vm_stack_and_frames.md](contributing/vm_stack_and_frames.md) for detailed architecture and frame layout. +See [doc/contributing/vm_stack_and_frames.md](rdoc-ref:contributing/vm_stack_and_frames.md) for detailed architecture and frame layout. **Control Frame Stack:** From b7d4d7c911e1aa3882b1186ba0ba780801ab27ea Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 20 Dec 2025 00:19:54 +0000 Subject: [PATCH 2071/2435] [DOC] Harmonize several <=> methods --- complex.c | 10 +++++++--- numeric.c | 50 ++++++++++++++++++++++++++++++-------------------- string.c | 22 ++++++++++++++-------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/complex.c b/complex.c index 72e13b4e2ecde2..1fe68f80bebb97 100644 --- a/complex.c +++ b/complex.c @@ -1260,14 +1260,16 @@ nucomp_real_p(VALUE self) /* * call-seq: - * complex <=> object -> -1, 0, 1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - self.real <=> object.real if both of the following are true: + * - self.real <=> other.real if both of the following are true: * * - self.imag == 0. - * - object.imag == 0. # Always true if object is numeric but not complex. + * - other.imag == 0 (always true if +other+ is numeric but not complex). * * - +nil+ otherwise. * @@ -1280,6 +1282,8 @@ nucomp_real_p(VALUE self) * Complex.rect(1) <=> Complex.rect(1, 1) # => nil # object.imag not zero. * Complex.rect(1) <=> 'Foo' # => nil # object.imag not defined. * + * \Class \Complex includes module Comparable, + * each of whose methods uses Complex#<=> for comparison. */ static VALUE nucomp_cmp(VALUE self, VALUE other) diff --git a/numeric.c b/numeric.c index a71060c55db252..87d50ae4a1b9e8 100644 --- a/numeric.c +++ b/numeric.c @@ -1468,10 +1468,17 @@ num_eql(VALUE x, VALUE y) * call-seq: * self <=> other -> zero or nil * - * Returns zero if +self+ is the same as +other+, +nil+ otherwise. + * Compares +self+ and +other+. * - * No subclass in the Ruby Core or Standard Library uses this implementation. + * Returns: + * + * - Zero, if +self+ is the same as +other+. + * - +nil+, otherwise. + * + * \Class \Numeric includes module Comparable, + * each of whose methods uses Numeric#<=> for comparison. * + * No subclass in the Ruby Core or Standard Library uses this implementation. */ static VALUE @@ -1561,30 +1568,32 @@ rb_dbl_cmp(double a, double b) /* * call-seq: - * self <=> other -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * - * Returns a value that depends on the numeric relation - * between +self+ and +other+: + * Returns: * - * - -1, if +self+ is less than +other+. - * - 0, if +self+ is equal to +other+. - * - 1, if +self+ is greater than +other+. + * - +-1+, if +self+ is less than +other+. + * - +0+, if +self+ is equal to +other+. + * - +1+, if +self+ is greater than +other+. * - +nil+, if the two values are incommensurate. * * Examples: * + * 2.0 <=> 2.1 # => -1 * 2.0 <=> 2 # => 0 * 2.0 <=> 2.0 # => 0 * 2.0 <=> Rational(2, 1) # => 0 * 2.0 <=> Complex(2, 0) # => 0 * 2.0 <=> 1.9 # => 1 - * 2.0 <=> 2.1 # => -1 * 2.0 <=> 'foo' # => nil * - * This is the basis for the tests in the Comparable module. - * * Float::NAN <=> Float::NAN returns an implementation-dependent value. * + * \Class \Float includes module Comparable, + * each of whose methods uses Float#<=> for comparison. + * */ static VALUE @@ -4888,28 +4897,29 @@ fix_cmp(VALUE x, VALUE y) /* * call-seq: - * self <=> other -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - -1, if +self+ is less than +other+. - * - 0, if +self+ is equal to +other+. - * - 1, if +self+ is greater then +other+. + * - +-1+, if +self+ is less than +other+. + * - +0+, if +self+ is equal to +other+. + * - +1+, if +self+ is greater then +other+. * - +nil+, if +self+ and +other+ are incomparable. * * Examples: * * 1 <=> 2 # => -1 * 1 <=> 1 # => 0 - * 1 <=> 0 # => 1 - * 1 <=> 'foo' # => nil - * * 1 <=> 1.0 # => 0 * 1 <=> Rational(1, 1) # => 0 * 1 <=> Complex(1, 0) # => 0 + * 1 <=> 0 # => 1 + * 1 <=> 'foo' # => nil * - * This method is the basis for comparisons in module Comparable. - * + * \Class \Integer includes module Comparable, + * each of whose methods uses Integer#<=> for comparison. */ VALUE diff --git a/string.c b/string.c index b11b441ac58536..83219d1a26d085 100644 --- a/string.c +++ b/string.c @@ -12374,18 +12374,24 @@ sym_succ(VALUE sym) /* * call-seq: - * symbol <=> object -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+, using String#<=>. * - * If +object+ is a symbol, - * returns the equivalent of symbol.to_s <=> object.to_s: + * Returns: * - * :bar <=> :foo # => -1 - * :foo <=> :foo # => 0 - * :foo <=> :bar # => 1 + * - symbol.to_s <=> other.to_s, if +other+ is a symbol. + * - +nil+, otherwise. + * + * Examples: * - * Otherwise, returns +nil+: + * :bar <=> :foo # => -1 + * :foo <=> :foo # => 0 + * :foo <=> :bar # => 1 + * :foo <=> 'bar' # => nil * - * :foo <=> 'bar' # => nil + * \Class \Symbol includes module Comparable, + * each of whose methods uses Symbol#<=> for comparison. * * Related: String#<=>. */ From df68535055c2104dc97762aeb3ed2794f01c49cf Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 17:12:03 -0500 Subject: [PATCH 2072/2435] [DOC] Improve docs for Signal.trap --- signal.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/signal.c b/signal.c index 12af9058a25439..5110ea4401a4be 100644 --- a/signal.c +++ b/signal.c @@ -1346,31 +1346,36 @@ reserved_signal_p(int signo) /* * call-seq: - * Signal.trap( signal, command ) -> obj - * Signal.trap( signal ) {| | block } -> obj + * Signal.trap(signal, command) -> obj + * Signal.trap(signal) { ... } -> obj * - * Specifies the handling of signals. The first parameter is a signal - * name (a string such as ``SIGALRM'', ``SIGUSR1'', and so on) or a - * signal number. The characters ``SIG'' may be omitted from the - * signal name. The command or block specifies code to be run when the + * Specifies the handling of signals. Returns the previous handler for + * the given signal. + * + * Argument +signal+ is a signal name (a string or symbol such + * as +SIGALRM+ or +SIGUSR1+) or an integer signal number. When +signal+ + * is a string or symbol, the leading characters +SIG+ may be omitted. + * + * Argument +command+ or block provided specifies code to be run when the * signal is raised. - * If the command is the string ``IGNORE'' or ``SIG_IGN'', the signal - * will be ignored. - * If the command is ``DEFAULT'' or ``SIG_DFL'', the Ruby's default handler - * will be invoked. - * If the command is ``EXIT'', the script will be terminated by the signal. - * If the command is ``SYSTEM_DEFAULT'', the operating system's default - * handler will be invoked. - * Otherwise, the given command or block will be run. - * The special signal name ``EXIT'' or signal number zero will be - * invoked just prior to program termination. - * trap returns the previous handler for the given signal. + * + * Argument +command+ may also be a string or symbol with the following special + * values: + * + * - +IGNORE+, +SIG_IGN+: the signal will be ignored. + * - +DEFAULT+, +SIG_DFL+: Ruby's default handler will be invoked. + * - +EXIT+: the process will be terminated by the signal. + * - +SYSTEM_DEFAULT+: the operating system's default handler will be invoked. + * + * The special signal name +EXIT+ or signal number zero will be + * invoked just prior to program termination: * * Signal.trap(0, proc { puts "Terminating: #{$$}" }) * Signal.trap("CLD") { puts "Child died" } * fork && Process.wait * - * produces: + * Outputs: + * * Terminating: 27461 * Child died * Terminating: 27460 From 43b67356bcd636d92ec17bc48c1a492677a5b615 Mon Sep 17 00:00:00 2001 From: YO4 Date: Fri, 19 Dec 2025 01:06:22 +0900 Subject: [PATCH 2073/2435] remove obsolete workaround This appears to be a workaround for Windows XP behavior. It is unnecessary now. --- io.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/io.c b/io.c index 99268e8f05b1a4..42017b1c253035 100644 --- a/io.c +++ b/io.c @@ -2659,9 +2659,6 @@ io_fillbuf(rb_io_t *fptr) fptr->rbuf.len = 0; fptr->rbuf.capa = IO_RBUF_CAPA_FOR(fptr); fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); -#ifdef _WIN32 - fptr->rbuf.capa--; -#endif } if (fptr->rbuf.len == 0) { retry: @@ -3329,10 +3326,6 @@ io_shift_cbuf(rb_io_t *fptr, int len, VALUE *strp) static int io_setstrbuf(VALUE *str, long len) { -#ifdef _WIN32 - if (len > 0) - len = (len + 1) & ~1L; /* round up for wide char */ -#endif if (NIL_P(*str)) { *str = rb_str_new(0, len); return TRUE; From 029112359914af51f50917878d4f547864a228c6 Mon Sep 17 00:00:00 2001 From: YO4 Date: Fri, 19 Dec 2025 23:33:35 +0900 Subject: [PATCH 2074/2435] fix for a test case that depends on rbuf size --- test/ruby/test_io_m17n.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 3f905aa1d8caa2..83d4fb0c7b525e 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -2724,8 +2724,8 @@ def test_pos_dont_move_cursor_position def test_pos_with_buffer_end_cr bug6401 = '[ruby-core:44874]' with_tmpdir { - # Read buffer size is 8191. This generates '\r' at 8191. - lines = ["X" * 8187, "X"] + # Read buffer size is 8192. This generates '\r' at 8192. + lines = ["X" * 8188, "X"] generate_file("tmp", lines.join("\r\n") + "\r\n") open("tmp", "r") do |f| From 49f9c9bff29bce267b6aa362c6004d98db5c62f3 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Sat, 20 Dec 2025 16:14:38 +0900 Subject: [PATCH 2075/2435] Box: [DOC] Uodate the name from Namespace --- doc/language/box.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/language/box.md b/doc/language/box.md index aebce7188b3179..abc6af868fc470 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -11,7 +11,7 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## TODOs -* Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) +* Add the loaded box on iseq to check if another box tries running the iseq (add a field only when VM_CHECK_MODE?) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box * Make an internal data container class `Ruby::Box::Entry` invisible @@ -22,7 +22,7 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ### Enabling Ruby Box First, an environment variable should be set at the ruby process bootup: `RUBY_BOX=1`. -The only valid value is `1` to enable namespace. Other values (or unset `RUBY_BOX`) means disabling namespace. And setting the value after Ruby program starts doesn't work. +The only valid value is `1` to enable Ruby Box. Other values (or unset `RUBY_BOX`) means disabling Ruby Box. And setting the value after Ruby program starts doesn't work. ### Using Ruby Box @@ -75,7 +75,7 @@ There are two box types: There is the root box, just a single box in a Ruby process. Ruby bootstrap runs in the root box, and all builtin classes/modules are defined in the root box. (See "Builtin classes and modules".) -User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" box, which is a user namespace automatically created at the end of Ruby's bootstrap, copied from the root box. +User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" box, which is a user box automatically created at the end of Ruby's bootstrap, copied from the root box. When `Ruby::Box.new` is called, an "optional" box (a user, non-main box) is created, copied from the root box. All user boxes are flat, copied from the root box. From 77c3a9e447ec477be39e00072e1ce3348d0f4533 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 20 Dec 2025 16:12:52 +0900 Subject: [PATCH 2076/2435] Revert pack/unpack support for LEB128 https://bugs.ruby-lang.org/issues/21785#note-10 > It is too late to introduce it in Ruby 4.0, let's aim for 4.1. This reverts commits: * d0b72429a93e54f1f956b4aedfc25c57dc7001aa Add support for signed and unsigned LEB128 to pack/unpack. * 68a900e30b4ca1537d7975c3a619fd94fca7b084 add news for pack / unpack directives --- NEWS.md | 7 -- doc/language/packed_data.rdoc | 2 - pack.c | 83 -------------- spec/ruby/core/array/pack/r_spec.rb | 23 ---- spec/ruby/core/array/pack/shared/basic.rb | 4 +- spec/ruby/core/string/unpack/shared/basic.rb | 2 +- test/ruby/test_pack.rb | 112 ------------------- 7 files changed, 3 insertions(+), 230 deletions(-) delete mode 100644 spec/ruby/core/array/pack/r_spec.rb diff --git a/NEWS.md b/NEWS.md index b5071761976d05..2435498da9af12 100644 --- a/NEWS.md +++ b/NEWS.md @@ -37,9 +37,6 @@ Note: We're only listing outstanding class updates. * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] - * `Array#pack` accepts a new format `R` and `r` for unpacking unsigned - and signed LEB128 encoded integers. [[Feature #21785]] - * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -267,9 +264,6 @@ Note: We're only listing outstanding class updates. * `String#strip`, `strip!`, `lstrip`, `lstrip!`, `rstrip`, and `rstrip!` are extended to accept `*selectors` arguments. [[Feature #21552]] - * `String#unpack` accepts a new format `R` and `r` for unpacking unsigned - and signed LEB128 encoded integers. [[Feature #21785]] - * Thread * Introduce support for `Thread#raise(cause:)` argument similar to @@ -575,5 +569,4 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 [Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 -[Feature #21785]: https://bugs.ruby-lang.org/issues/21785 [Bug #21789]: https://bugs.ruby-lang.org/issues/21789 diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index 97079f7f08dd8f..f7cb4dbf74d99b 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -53,8 +53,6 @@ These tables summarize the directives for packing and unpacking. U | UTF-8 character w | BER-compressed integer - R | LEB128 encoded unsigned integer - r | LEB128 encoded signed integer === For Floats diff --git a/pack.c b/pack.c index 6f68b13ccac6c4..3a5c1bfb9677cf 100644 --- a/pack.c +++ b/pack.c @@ -667,56 +667,6 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) } break; - case 'r': /* r for SLEB128 encoding (signed) */ - case 'R': /* R for ULEB128 encoding (unsigned) */ - { - int pack_flags = INTEGER_PACK_LITTLE_ENDIAN; - - if (type == 'r') { - pack_flags |= INTEGER_PACK_2COMP; - } - - while (len-- > 0) { - size_t numbytes; - int sign; - char *cp; - - from = NEXTFROM; - from = rb_to_int(from); - numbytes = rb_absint_numwords(from, 7, NULL); - if (numbytes == 0) - numbytes = 1; - VALUE buf = rb_str_new(NULL, numbytes); - - sign = rb_integer_pack(from, RSTRING_PTR(buf), RSTRING_LEN(buf), 1, 1, pack_flags); - - if (sign < 0 && type == 'R') { - rb_raise(rb_eArgError, "can't encode negative numbers in ULEB128"); - } - - if (type == 'r') { - /* Check if we need an extra byte for sign extension */ - unsigned char last_byte = (unsigned char)RSTRING_PTR(buf)[numbytes - 1]; - if ((sign >= 0 && (last_byte & 0x40)) || /* positive but sign bit set */ - (sign < 0 && !(last_byte & 0x40))) { /* negative but sign bit clear */ - /* Need an extra byte */ - rb_str_resize(buf, numbytes + 1); - RSTRING_PTR(buf)[numbytes] = sign < 0 ? 0x7f : 0x00; - numbytes++; - } - } - - cp = RSTRING_PTR(buf); - while (1 < numbytes) { - *cp |= 0x80; - cp++; - numbytes--; - } - - rb_str_buf_cat(res, RSTRING_PTR(buf), RSTRING_LEN(buf)); - } - } - break; case 'u': /* uuencoded string */ case 'm': /* base64 encoded string */ from = NEXTFROM; @@ -1608,39 +1558,6 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset) } break; - case 'r': - case 'R': - { - int pack_flags = INTEGER_PACK_LITTLE_ENDIAN; - - if (type == 'r') { - pack_flags |= INTEGER_PACK_2COMP; - } - char *s0 = s; - while (len > 0 && s < send) { - if (*s & 0x80) { - s++; - } - else { - s++; - UNPACK_PUSH(rb_integer_unpack(s0, s-s0, 1, 1, pack_flags)); - len--; - s0 = s; - } - } - /* Handle incomplete value and remaining expected values with nil (only if not using *) */ - if (!star) { - if (s0 != s && len > 0) { - UNPACK_PUSH(Qnil); - len--; - } - while (len-- > 0) { - UNPACK_PUSH(Qnil); - } - } - } - break; - case 'w': { char *s0 = s; diff --git a/spec/ruby/core/array/pack/r_spec.rb b/spec/ruby/core/array/pack/r_spec.rb deleted file mode 100644 index 22be6fa6400cfa..00000000000000 --- a/spec/ruby/core/array/pack/r_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' -require_relative 'shared/basic' -require_relative 'shared/numeric_basic' -require_relative 'shared/integer' - -ruby_version_is "4.0" do - describe "Array#pack with format 'R'" do - it_behaves_like :array_pack_basic, 'R' - it_behaves_like :array_pack_basic_non_float, 'R' - it_behaves_like :array_pack_arguments, 'R' - it_behaves_like :array_pack_numeric_basic, 'R' - it_behaves_like :array_pack_integer, 'R' - end - - describe "Array#pack with format 'r'" do - it_behaves_like :array_pack_basic, 'r' - it_behaves_like :array_pack_basic_non_float, 'r' - it_behaves_like :array_pack_arguments, 'r' - it_behaves_like :array_pack_numeric_basic, 'r' - it_behaves_like :array_pack_integer, 'r' - end -end diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb index 77d7f2f71c0873..ebd9f75d9dc66a 100644 --- a/spec/ruby/core/array/pack/shared/basic.rb +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -37,7 +37,7 @@ # NOTE: it's just a plan of the Ruby core team it "warns that a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K'/) + -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/) -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/) -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/) end @@ -48,7 +48,7 @@ # NOTE: Added this case just to not forget about the decision in the ticket it "raise ArgumentError when a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a K" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'K'/) + -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) end diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb index 0ac2a951ed7220..b37a447683a95c 100644 --- a/spec/ruby/core/string/unpack/shared/basic.rb +++ b/spec/ruby/core/string/unpack/shared/basic.rb @@ -12,7 +12,7 @@ ruby_version_is "3.3" do # https://bugs.ruby-lang.org/issues/19150 it 'raise ArgumentError when a directive is unknown' do - -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive 'K'/) + -> { "abcdefgh".unpack("a R" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive 'R'/) -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive '0'/) -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive ':'/) end diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb index 9c40cfaa204f86..ca089f09c3dc4b 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -936,116 +936,4 @@ class Array assert_equal "oh no", v end; end - - def test_unpack_broken_R - assert_equal([nil], "\xFF".unpack("R")) - assert_nil("\xFF".unpack1("R")) - assert_equal([nil], "\xFF".unpack("r")) - assert_nil("\xFF".unpack1("r")) - - bytes = [256].pack("r") - assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("rrrr")) - - bytes = [256].pack("R") - assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("RRRR")) - - assert_equal([], "\xFF".unpack("R*")) - assert_equal([], "\xFF".unpack("r*")) - end - - def test_pack_unpack_R - # ULEB128 encoding (unsigned) - assert_equal("\x00", [0].pack("R")) - assert_equal("\x01", [1].pack("R")) - assert_equal("\x7f", [127].pack("R")) - assert_equal("\x80\x01", [128].pack("R")) - assert_equal("\xff\x7f", [0x3fff].pack("R")) - assert_equal("\x80\x80\x01", [0x4000].pack("R")) - assert_equal("\xff\xff\xff\xff\x0f", [0xffffffff].pack("R")) - assert_equal("\x80\x80\x80\x80\x10", [0x100000000].pack("R")) - assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01", [0xffff_ffff_ffff_ffff].pack("R")) - assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("R")) - - # Multiple values - assert_equal("\x01\x02", [1, 2].pack("R*")) - assert_equal("\x7f\x80\x01", [127, 128].pack("R*")) - - # Negative numbers should raise an error - assert_raise(ArgumentError) { [-1].pack("R") } - assert_raise(ArgumentError) { [-100].pack("R") } - - # Unpack tests - assert_equal([0], "\x00".unpack("R")) - assert_equal([1], "\x01".unpack("R")) - assert_equal([127], "\x7f".unpack("R")) - assert_equal([128], "\x80\x01".unpack("R")) - assert_equal([0x3fff], "\xff\x7f".unpack("R")) - assert_equal([0x4000], "\x80\x80\x01".unpack("R")) - assert_equal([0xffffffff], "\xff\xff\xff\xff\x0f".unpack("R")) - assert_equal([0x100000000], "\x80\x80\x80\x80\x10".unpack("R")) - assert_equal([0xffff_ffff_ffff_ffff], "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01".unpack("R")) - assert_equal([0xffff_ffff_ffff_ffff_ffff_ffff].pack("R"), "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f") - - # Multiple values - assert_equal([1, 2], "\x01\x02".unpack("R*")) - assert_equal([127, 128], "\x7f\x80\x01".unpack("R*")) - - # Round-trip test - values = [0, 1, 127, 128, 0x3fff, 0x4000, 0xffffffff, 0x100000000] - assert_equal(values, values.pack("R*").unpack("R*")) - end - - def test_pack_unpack_r - # SLEB128 encoding (signed) - assert_equal("\x00", [0].pack("r")) - assert_equal("\x01", [1].pack("r")) - assert_equal("\x7f", [-1].pack("r")) - assert_equal("\x7e", [-2].pack("r")) - assert_equal("\xff\x00", [127].pack("r")) - assert_equal("\x80\x01", [128].pack("r")) - assert_equal("\x81\x7f", [-127].pack("r")) - assert_equal("\x80\x7f", [-128].pack("r")) - - # Larger positive numbers - assert_equal("\xff\xff\x00", [0x3fff].pack("r")) - assert_equal("\x80\x80\x01", [0x4000].pack("r")) - - # Larger negative numbers - assert_equal("\x81\x80\x7f", [-0x3fff].pack("r")) - assert_equal("\x80\x80\x7f", [-0x4000].pack("r")) - - # Very large numbers - assert_equal("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1F", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) - assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) - - # Multiple values - assert_equal("\x00\x01\x7f", [0, 1, -1].pack("r*")) - - # Unpack tests - assert_equal([0], "\x00".unpack("r")) - assert_equal([1], "\x01".unpack("r")) - assert_equal([-1], "\x7f".unpack("r")) - assert_equal([-2], "\x7e".unpack("r")) - assert_equal([127], "\xff\x00".unpack("r")) - assert_equal([128], "\x80\x01".unpack("r")) - assert_equal([-127], "\x81\x7f".unpack("r")) - assert_equal([-128], "\x80\x7f".unpack("r")) - - # Larger numbers - assert_equal([0x3fff], "\xff\xff\x00".unpack("r")) - assert_equal([0x4000], "\x80\x80\x01".unpack("r")) - assert_equal([-0x3fff], "\x81\x80\x7f".unpack("r")) - assert_equal([-0x4000], "\x80\x80\x7f".unpack("r")) - - # Very large numbers - assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) - assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r")) - - # Multiple values - assert_equal([0, 1, -1], "\x00\x01\x7f".unpack("r*")) - - # Round-trip test - values = [0, 1, -1, 127, -127, 128, -128, 0x3fff, -0x3fff, 0x4000, -0x4000] - assert_equal(values, values.pack("r*").unpack("r*")) - end end From ec4ca91319212ac9109ceac305f527c998da1738 Mon Sep 17 00:00:00 2001 From: Victor Shepelev Date: Sat, 20 Dec 2025 13:07:38 +0200 Subject: [PATCH 2077/2435] Small documentation adjustments for new/updated features (#15634) * Document Range#to_set * Update Thread#raise and Fiber#raise signatures and docs * Add reference to String#strip to character_selectors.rdoc * Update *nil docs when calling methods * Enhance Array#find and #rfind docs * Add a notice to Kernel#raise about cause: --- array.c | 12 +++------ cont.c | 37 +++++++++++++++++---------- doc/language/character_selectors.rdoc | 4 +++ doc/syntax/calling_methods.rdoc | 5 ++-- eval.c | 3 +++ range.c | 14 ++++++++++ thread.c | 7 +++-- 7 files changed, 53 insertions(+), 29 deletions(-) diff --git a/array.c b/array.c index 27b84249bf44fe..e13239ad3daaa1 100644 --- a/array.c +++ b/array.c @@ -2102,9 +2102,7 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * * If no such element is found, calls +if_none_proc+ and returns its return value. * - * [1, 3, 5].find(proc {false}) {|element| element > 12} # => false - * [[:foo, 0], [:bar, 1], [:baz, 2]].find {|key, value| key.start_with?('b') } # => [:bar, 1] - * [[:foo, 0], [:bar, 1], [:baz, 2]].find(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * [1, 3, 5].find(proc {-1}) {|element| element > 12} # => -1 * * With no block given, returns an Enumerator. * @@ -2140,16 +2138,14 @@ rb_ary_find(int argc, VALUE *argv, VALUE ary) * Returns the last element for which the block returns a truthy value. * * With a block given, calls the block with successive elements of the array in - * reverse order; returns the last element for which the block returns a truthy + * reverse order; returns the first element for which the block returns a truthy * value: * - * (0..9).rfind {|element| element < 5} # => 4 + * [1, 2, 3, 4, 5, 6].rfind {|element| element < 5} # => 4 * * If no such element is found, calls +if_none_proc+ and returns its return value. * - * (0..9).rfind(proc {false}) {|element| element < -2} # => false - * {foo: 0, bar: 1, baz: 2}.rfind {|key, value| key.start_with?('b') } # => [:baz, 2] - * {foo: 0, bar: 1, baz: 2}.rfind(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * [1, 2, 3, 4].rfind(proc {0}) {|element| element < -2} # => 0 * * With no block given, returns an Enumerator. * diff --git a/cont.c b/cont.c index 0087994730419e..b33c1462bf3ea1 100644 --- a/cont.c +++ b/cont.c @@ -3246,28 +3246,37 @@ rb_fiber_raise(VALUE fiber, int argc, VALUE *argv) /* * call-seq: - * fiber.raise -> obj - * fiber.raise(string) -> obj - * fiber.raise(exception [, string [, array]]) -> obj + * raise(exception, message = exception.to_s, backtrace = nil, cause: $!) + * raise(message = nil, cause: $!) * * Raises an exception in the fiber at the point at which the last - * +Fiber.yield+ was called. If the fiber has not been started or has + * +Fiber.yield+ was called. + * + * f = Fiber.new { + * puts "Before the yield" + * Fiber.yield 1 # -- exception will be raised here + * puts "After the yield" + * } + * + * p f.resume + * f.raise "Gotcha" + * + * Output + * + * Before the first yield + * 1 + * t.rb:8:in 'Fiber.yield': Gotcha (RuntimeError) + * from t.rb:8:in 'block in
' + * + * If the fiber has not been started or has * already run to completion, raises +FiberError+. If the fiber is * yielding, it is resumed. If it is transferring, it is transferred into. * But if it is resuming, raises +FiberError+. * - * With no arguments, raises a +RuntimeError+. With a single +String+ - * argument, raises a +RuntimeError+ with the string as a message. Otherwise, - * the first parameter should be the name of an +Exception+ class (or an - * object that returns an +Exception+ object when sent an +exception+ - * message). The optional second parameter sets the message associated with - * the exception, and the third parameter is an array of callback information. - * Exceptions are caught by the +rescue+ clause of begin...end - * blocks. - * * Raises +FiberError+ if called on a Fiber belonging to another +Thread+. * - * See Kernel#raise for more information. + * See Kernel#raise for more information on arguments. + * */ static VALUE rb_fiber_m_raise(int argc, VALUE *argv, VALUE self) diff --git a/doc/language/character_selectors.rdoc b/doc/language/character_selectors.rdoc index 47cf242be7382b..20685b8392b655 100644 --- a/doc/language/character_selectors.rdoc +++ b/doc/language/character_selectors.rdoc @@ -14,6 +14,8 @@ Each of these instance methods accepts one or more character selectors: - String#delete!(*selectors): returns +self+ or +nil+. - String#squeeze(*selectors): returns a new string. - String#squeeze!(*selectors): returns +self+ or +nil+. +- String#strip(*selectors): returns a new string. +- String#strip!(*selectors): returns +self+ or +nil+. A character selector identifies zero or more characters in +self+ that are to be operands for the method. @@ -79,6 +81,8 @@ These instance methods accept multiple character selectors: - String#delete!(*selectors): returns +self+ or +nil+. - String#squeeze(*selectors): returns a new string. - String#squeeze!(*selectors): returns +self+ or +nil+. +- String#strip(*selectors): returns a new string. +- String#strip!(*selectors): returns +self+ or +nil+. In effect, the given selectors are formed into a single selector consisting of only those characters common to _all_ of the given selectors. diff --git a/doc/syntax/calling_methods.rdoc b/doc/syntax/calling_methods.rdoc index bf5916e99aa071..76babcc3dcd969 100644 --- a/doc/syntax/calling_methods.rdoc +++ b/doc/syntax/calling_methods.rdoc @@ -355,9 +355,8 @@ as one argument: # Prints the object itself: # # -This allows to handle one or many arguments polymorphically. Note also that +nil+ -has NilClass#to_a defined to return an empty array, so conditional unpacking is -possible: +This allows to handle one or many arguments polymorphically. Note also that *nil +is unpacked to an empty list of arguments, so conditional unpacking is possible: my_method(*(some_arguments if some_condition?)) diff --git a/eval.c b/eval.c index 0c80872bee76e5..deadd5dd6414fb 100644 --- a/eval.c +++ b/eval.c @@ -922,6 +922,9 @@ rb_f_raise(int argc, VALUE *argv) * With argument +exception+ not given, * argument +message+ and keyword argument +cause+ may be given, * but argument +backtrace+ may not be given. + * + * +cause+ can not be given as an only argument. + * */ static VALUE diff --git a/range.c b/range.c index f82d1a316ddd7d..82c252e3ef50cd 100644 --- a/range.c +++ b/range.c @@ -1018,6 +1018,20 @@ range_to_a(VALUE range) return rb_call_super(0, 0); } +/* + * call-seq: + * to_set -> set + * + * Returns a set containing the elements in +self+, if a finite collection; + * raises an exception otherwise. + * + * (1..4).to_set # => Set[1, 2, 3, 4] + * (1...4).to_set # => Set[1, 2, 3] + * + * (1..).to_set + * # in 'Range#to_set': cannot convert endless range to a set (RangeError) + * + */ static VALUE range_to_set(int argc, VALUE *argv, VALUE range) { diff --git a/thread.c b/thread.c index 788a0e9ad791a2..e66352c03f6c4a 100644 --- a/thread.c +++ b/thread.c @@ -2908,12 +2908,11 @@ rb_thread_fd_close(int fd) /* * call-seq: - * thr.raise - * thr.raise(string) - * thr.raise(exception [, string [, array]]) + * raise(exception, message = exception.to_s, backtrace = nil, cause: $!) + * raise(message = nil, cause: $!) * * Raises an exception from the given thread. The caller does not have to be - * +thr+. See Kernel#raise for more information. + * +thr+. See Kernel#raise for more information on arguments. * * Thread.abort_on_exception = true * a = Thread.new { sleep(200) } From a57039196f0ef5a3ee8710d85e52e8644b9bc0c2 Mon Sep 17 00:00:00 2001 From: Victor Shepelev Date: Sat, 20 Dec 2025 13:26:12 +0200 Subject: [PATCH 2078/2435] Add clarifications about the Enumerator.size (#15615) --- enumerator.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/enumerator.c b/enumerator.c index 5514d76dace3d4..89ec5035305753 100644 --- a/enumerator.c +++ b/enumerator.c @@ -1230,6 +1230,24 @@ enumerator_inspect(VALUE obj) * (1..100).to_a.permutation(4).size # => 94109400 * loop.size # => Float::INFINITY * (1..100).drop_while.size # => nil + * + * Note that enumerator size might be inaccurate, and should be rather treated as a hint. + * For example, there is no check that the size provided to ::new is accurate: + * + * e = Enumerator.new(5) { |y| 2.times { y << it} } + * e.size # => 5 + * e.to_a.size # => 2 + * + * Another example is an enumerator created by ::produce without a +size+ argument. + * Such enumerators return +Infinity+ for size, but this is inaccurate if the passed + * block raises StopIteration: + * + * e = Enumerator.produce(1) { it + 1 } + * e.size # => Infinity + * + * e = Enumerator.produce(1) { it > 3 ? raise(StopIteration) : it + 1 } + * e.size # => Infinity + * e.to_a.size # => 4 */ static VALUE @@ -3051,6 +3069,12 @@ producer_size(VALUE obj, VALUE args, VALUE eobj) * File.dirname(it) * } * traverser.size # => 4 + * + * # Finite enumerator with unknown size + * calendar = Enumerator.produce(Date.today, size: nil) { + * it.monday? ? raise(StopIteration) : it + 1 + * } + * calendar.size # => nil */ static VALUE enumerator_s_produce(int argc, VALUE *argv, VALUE klass) From 8f7c3603c1b168e4f7440216b197f11325e31fdf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 20 Dec 2025 21:37:45 +0900 Subject: [PATCH 2079/2435] Fix a fragile test `Dir.mktmpdir` concatenates a random base-36 number separated by "-", so may generate pathnames containing "-j4". --- test/rubygems/test_gem_commands_install_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 72ca9d8262583a..d2ca933a632c7d 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1610,7 +1610,7 @@ def test_pass_down_the_job_option_to_make gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) if vc_windows? && nmake_found? - refute_includes(gem_make_out, "-j4") + refute_includes(gem_make_out, " -j4") else assert_includes(gem_make_out, "make -j4") end From e4bcb64be1fe25c93a8f609a108d8d4c933d6a2f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 20 Dec 2025 12:08:28 +0100 Subject: [PATCH 2080/2435] [Doc] prefer encoding objects over encoding names --- dir.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dir.rb b/dir.rb index a05bd18630b923..0088bb39fa5399 100644 --- a/dir.rb +++ b/dir.rb @@ -178,7 +178,7 @@ class Dir # if +nil+ (the default), the file system's encoding is used: # # Dir.open('.').read.encoding # => # - # Dir.open('.', encoding: 'US-ASCII').read.encoding # => # + # Dir.open('.', encoding: Encoding::US_ASCII).read.encoding # => # # def self.open(name, encoding: nil, &block) dir = Primitive.dir_s_open(name, encoding) @@ -206,7 +206,7 @@ def self.open(name, encoding: nil, &block) # if +nil+ (the default), the file system's encoding is used: # # Dir.new('.').read.encoding # => # - # Dir.new('.', encoding: 'US-ASCII').read.encoding # => # + # Dir.new('.', encoding: Encoding::US_ASCI).read.encoding # => # # def initialize(name, encoding: nil) Primitive.dir_initialize(name, encoding) From e6520de3442659def3463d3bfdca3432f41b2d6a Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 17 Dec 2025 17:28:47 -0500 Subject: [PATCH 2081/2435] [Bug #21792] Build rubyspec-capiext only when excuting `test-spec` rubyspec-capiext is only needed for running specs, not for building or installing Ruby. --- common.mk | 3 ++- defs/gmake.mk | 6 +----- win32/Makefile.sub | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/common.mk b/common.mk index c0d663f68e5658..08fee9119a1ef9 100644 --- a/common.mk +++ b/common.mk @@ -804,6 +804,7 @@ clean-platform distclean-platform realclean-platform: RUBYSPEC_CAPIEXT = spec/ruby/optional/capi/ext RUBYSPEC_CAPIEXT_SRCDIR = $(srcdir)/$(RUBYSPEC_CAPIEXT) RUBYSPEC_CAPIEXT_DEPS = $(RUBYSPEC_CAPIEXT_SRCDIR)/rubyspec.h $(RUBY_H_INCLUDES) $(LIBRUBY) +RUBYSPEC_CAPIEXT_BUILD = $(enable_shared:yes=rubyspec-capiext) rubyspec-capiext: build-ext $(DOT_WAIT) # make-dependent rules should be included after this and built after build-ext. @@ -905,7 +906,7 @@ test: test-short # Separate to skip updating encs and exts by `make -o test-precheck` # for GNU make. -test-precheck: $(ENCSTATIC:static=lib)encs exts PHONY $(DOT_WAIT) +test-precheck: $(ENCSTATIC:static=lib)encs $(RUBYSPEC_CAPIEXT_BUILD) exts PHONY $(DOT_WAIT) yes-test-all-precheck: programs $(DOT_WAIT) test-precheck PRECHECK_TEST_ALL = yes-test-all-precheck diff --git a/defs/gmake.mk b/defs/gmake.mk index 2422151009599c..e6f553fa8c1b5d 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -529,11 +529,7 @@ RUBYSPEC_CAPIEXT_SO := $(patsubst %.c,$(RUBYSPEC_CAPIEXT)/%.$(DLEXT),$(notdir $( rubyspec-capiext: $(RUBYSPEC_CAPIEXT_SO) @ $(NULLCMD) -ifeq ($(ENABLE_SHARED),yes) -exts: rubyspec-capiext -endif - -spec/%/ spec/%_spec.rb: programs exts PHONY +spec/%/ spec/%_spec.rb: programs exts $(RUBYSPEC_CAPIEXT_BUILD) PHONY +$(RUNRUBY) -r./$(arch)-fake $(srcdir)/spec/mspec/bin/mspec-run -B $(srcdir)/spec/default.mspec $(SPECOPTS) $(patsubst %,$(srcdir)/%,$@) ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index c3db450abc30a0..80f8f5c2bf88a4 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1425,8 +1425,6 @@ rubyspec-capiext: $(RUBYSPEC_CAPIEXT_EXTS) $(Q)$(LDSHARED) -Fe$(@) -Fo$(*).obj $(INCFLAGS) $(CFLAGS) $(CPPFLAGS) $< $(LIBRUBYARG) -link $(DLDFLAGS) $(XLDFLAGS) $(LIBS) $(LOCAL_LIBS) -implib:$*.lib -pdb:$*.pdb -def:$*.def $(Q)$(RM) $*.def $*.exp $*.lib $*.obj $*.pdb -exts: rubyspec-capiext - yesterday: (set TZ=UTC-9) && \ for /f "usebackq" %H in \ From 6bf921051ceba1742318e3c92dddd50ba4f05d17 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 17:16:01 -0500 Subject: [PATCH 2082/2435] [ruby/mmtk] Call rb_bug when Ruby mutator thread panics This will allow the Ruby backtrace, memory mapping, etc. to be outputted when a Ruby mutator thread panics. https://github.com/ruby/mmtk/commit/d10fd325dd --- gc/mmtk/mmtk.c | 7 +++++++ gc/mmtk/mmtk.h | 1 + gc/mmtk/src/abi.rs | 1 + gc/mmtk/src/api.rs | 4 ++++ gc/mmtk/src/lib.rs | 8 ++++++++ 5 files changed, 21 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 131aaf38b0fd27..52f653a299ca09 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -355,6 +355,12 @@ rb_mmtk_special_const_p(MMTk_ObjectReference object) return RB_SPECIAL_CONST_P(obj); } +static void +rb_mmtk_mutator_thread_panic_handler(void) +{ + rb_bug("Ruby mutator thread panicked"); +} + // Bootup MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_init_gc_worker_thread, @@ -374,6 +380,7 @@ MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_global_tables_count, rb_mmtk_update_finalizer_table, rb_mmtk_special_const_p, + rb_mmtk_mutator_thread_panic_handler, }; // Use max 80% of the available memory by default for MMTk diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index b00133a820c546..18466c61108b40 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -69,6 +69,7 @@ typedef struct MMTk_RubyUpcalls { int (*global_tables_count)(void); void (*update_finalizer_table)(void); bool (*special_const_p)(MMTk_ObjectReference object); + void (*mutator_thread_panic_handler)(void); } MMTk_RubyUpcalls; typedef struct MMTk_RawVecOfObjRef { diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 2214441c9d6db7..1bd19fbe7efd1d 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -314,6 +314,7 @@ pub struct RubyUpcalls { pub global_tables_count: extern "C" fn() -> c_int, pub update_finalizer_table: extern "C" fn(), pub special_const_p: extern "C" fn(object: ObjectReference) -> bool, + pub mutator_thread_panic_handler: extern "C" fn(), } unsafe impl Sync for RubyUpcalls {} diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 5217eb4a758da1..006e987ea9d060 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -138,6 +138,10 @@ pub unsafe extern "C" fn mmtk_init_binding( upcalls: *const RubyUpcalls, weak_reference_dead_value: ObjectReference, ) { + crate::MUTATOR_THREAD_PANIC_HANDLER + .set((unsafe { (*upcalls).clone() }).mutator_thread_panic_handler) + .unwrap_or_else(|_| panic!("MUTATOR_THREAD_PANIC_HANDLER is already initialized")); + crate::set_panic_hook(); let builder = unsafe { Box::from_raw(builder) }; diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index d16a5bf42f2ec5..4bcafb597a2bb6 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -55,6 +55,11 @@ impl VMBinding for Ruby { type VMMemorySlice = RubyMemorySlice; } +/// The callback for mutator thread panic handler (which calls rb_bug to output +/// debugging information such as the Ruby backtrace and memory maps). +/// This is set before BINDING is set because mmtk_init could panic. +pub static MUTATOR_THREAD_PANIC_HANDLER: OnceCell = OnceCell::new(); + /// The singleton object for the Ruby binding itself. pub static BINDING: OnceCell = OnceCell::new(); @@ -132,6 +137,9 @@ pub(crate) fn set_panic_hook() { handle_gc_thread_panic(panic_info); } else { old_hook(panic_info); + (crate::MUTATOR_THREAD_PANIC_HANDLER + .get() + .expect("MUTATOR_THREAD_PANIC_HANDLER is not set"))(); } })); } From 5cdda61d00a61a7da701efa3ef332267d3724424 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 20 Dec 2025 09:26:02 -0600 Subject: [PATCH 2083/2435] [DOC] Enhancements for globals.md (#15545) * [DOC] Enhancements for globals.md --- doc/language/globals.md | 139 +++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index 716e62a7df87d5..4d42798b8ca154 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -10,76 +10,76 @@ To use the module: require 'English' ``` -## Summary +## In Brief ### Exceptions -| Variable | English | Contains | -|:--------:|:-----------------:|----------------------------------------------------| -| `$!` | `$ERROR_INFO` | Exception object; set by Kernel#raise. | -| `$@` | `$ERROR_POSITION` | Array of backtrace positions; set by Kernel#raise. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:--------:|:-----------------:|-----------------------------------------|:---------:|:---------:|---------------| +| `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | +| `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | ### Pattern Matching -| Variable | English | Contains | -|:-------------:|:-------------------:|--------------------------------------------------| -| `$~` | `$LAST_MATCH_INFO` | MatchData object; set by matcher method. | -| `$&` | `$MATCH` | Matched substring; set by matcher method. | -| `` $` `` | `$PRE_MATCH` | Substring left of match; set by matcher method. | -| `$'` | `$POST_MATCH` | Substring right of match; set by matcher method. | -| `$+` | `$LAST_PAREN_MATCH` | Last group matched; set by matcher method. | -| `$1` | | First group matched; set by matcher method. | -| `$2` | | Second group matched; set by matcher method. | -| $_n_ | | nth group matched; set by matcher method. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| +| `$~` | `$LAST_MATCH_INFO` | \MatchData object or `nil` | `nil` | No | Matcher methods | +| `$&` | `$MATCH` | Matched substring or `nil` | `nil` | No | Matcher methods | +| `` $` `` | `$PRE_MATCH` | Substring left of match or `nil` | `nil` | No | Matcher methods | +| `$'` | `$POST_MATCH` | Substring right of match or `nil` | `nil` | No | Matcher methods | +| `$+` | `$LAST_PAREN_MATCH` | Last group matched or `nil` | `nil` | No | Matcher methods | +| `$1` | | First group matched or `nil` | `nil` | Yes | Matcher methods | +| `$2` | | Second group matched or `nil` | `nil` | Yes | Matcher methods | +| $_n_ | | nth group matched or `nil` | `nil` | Yes | Matcher methods | ### Separators -| Variable | English | Contains | -|:-----------:|:--------------------------:|--------------------------------------------| -| `$/`, `$-0` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | -| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:---------------------------:|-------------------------|:---------:|:---------:|----------| +| `$/`, `$-0` | `$INPUT_RECORD_SEPARATOR` | Input record separator | Newline | No | | +| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator | `nil` | No | | ### Streams -| Variable | English | Contains | -|:---------:|:---------------------------:|-----------------------------------------------| -| `$stdin` | | Standard input stream; initially `STDIN`. | -| `$stdout` | | Standard input stream; initially `STDIOUT`. | -| `$stderr` | | Standard input stream; initially `STDERR`. | -| `$<` | `$DEFAULT_INPUT` | Default standard input; `ARGF` or `$stdin`. | -| `$>` | `$DEFAULT_OUTPUT` | Default standard output; initially `$stdout`. | -| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream. | -| `$_` | `$LAST_READ_LINE` | String from most recently read stream. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:---------:|:----------------------------:|:-------------------------------------------:|:---------:|:---------:|----------------------| +| `$stdin` | | Standard input stream | `STDIN` | No | | +| `$stdout` | | Standard output stream | `STDOUT` | No | | +| `$stderr` | | Standard error stream | `STDERR` | No | | +| `$<` | `$DEFAULT_INPUT` | Default standard input | `ARGF` | Yes | | +| `$>` | `$DEFAULT_OUTPUT` | Default standard output | `STDOUT` | No | | +| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream | 0 | No | Certain read methods | +| `$_` | `$LAST_READ_LINE` | String from most recently read stream | `nil` | No | Certain read methods | ### Processes -| Variable | English | Contains | -|:-------------------------:|:---------------------:|--------------------------------------------------------| -| `$0` | | Initially, the name of the executing program. | -| `$*` | `$ARGV` | Points to the `ARGV` array. | -| `$$` | `$PROCESS_ID`, `$PID` | Process ID of the current process. | -| `$?` | `$CHILD_STATUS` | Process::Status of most recently exited child process. | -| `$LOAD_PATH`, `$:`, `$-I` | | Array of paths to be searched. | -| `$LOADED_FEATURES`, `$"` | | Array of paths to loaded files. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-------------------------:|:----------------------:|---------------------------------|:-------------:|:---------:|----------| +| `$0`, `$PROGRAM_NAME` | | Program name | Program name | No | | +| `$*` | `$ARGV` | \ARGV array | `ARGV` | Yes | | +| `$$` | `$PROCESS_ID`, `$PID` | Process id | Process PID | Yes | | +| `$?` | `$CHILD_STATUS` | Status of recently exited child | `nil` | Yes | | +| `$LOAD_PATH`, `$:`, `$-I` | | \Array of search paths | Ruby defaults | Yes | | +| `$LOADED_FEATURES`, `$"` | | \Array of load paths | Ruby defaults | Yes | | ### Debugging -| Variable | English | Contains | -|:-----------:|:-------:|--------------------------------------------------------| -| `$FILENAME` | | The value returned by method ARGF.filename. | -| `$DEBUG` | | Initially, whether option `-d` or `--debug` was given. | -| `$VERBOSE` | | Initially, whether option `-V` or `-W` was given. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:--------:|--------------------------------------------|:----------------------------:|:---------:|----------| +| `$FILENAME` | | Value returned by method `ARGF.filename` | Command-line argument or '-' | Yes | | +| `$DEBUG` | | Whether option `-d` or `--debug` was given | Command-line option | No | | +| `$VERBOSE` | | Whether option `-V` or `-W` was given | Command-line option | No | | ### Other Variables -| Variable | English | Contains | -|:-----------:|:-------:|------------------------------------------------| -| `$-F`, `$;` | | Separator given with command-line option `-F`. | -| `$-a` | | Whether option `-a` was given. | -| `$-i` | | Extension given with command-line option `-i`. | -| `$-l` | | Whether option `-l` was given. | -| `$-p` | | Whether option `-p` was given. | -| `$F` | | Array of `$_` split by `$-F`. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:--------:|-----------------------------------------------|:---------:|:---------:|----------| +| `$-F`, `$;` | | Separator given with command-line option `-F` | | | | +| `$-a` | | Whether option `-a` was given | | Yes | | +| `$-i` | | Extension given with command-line option `-i` | | No | | +| `$-l` | | Whether option `-l` was given | | Yes | | +| `$-p` | | Whether option `-p` was given | | Yes | | +| `$F` | | \Array of `$_` split by `$-F` | | | | ## Exceptions @@ -352,6 +352,10 @@ Aliased as `$-v` and `$-w`. ## Other Variables +### `$-a` + +Whether command-line option `-a` was given; read-only. + ### `$-F` The default field separator in String#split; must be a String or a @@ -407,27 +411,27 @@ obtained by splitting `$_` by `$-F` is assigned at the start of each ### Environment -| Constant | Contains | -|:---------------------:|-------------------------------------------------------------------------------| -| `ENV` | Hash of current environment variable names and values. | -| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | -| `ARGV` | Array of the given command-line arguments. | -| `TOPLEVEL_BINDING` | Binding of the top level scope. | -| `RUBY_VERSION` | String Ruby version. | -| `RUBY_RELEASE_DATE` | String Ruby release date. | -| `RUBY_PLATFORM` | String Ruby platform. | -| `RUBY_PATCH_LEVEL` | String Ruby patch level. | -| `RUBY_REVISION` | String Ruby revision. | -| `RUBY_COPYRIGHT` | String Ruby copyright. | -| `RUBY_ENGINE` | String Ruby engine. | +| Constant | Contains | +|-----------------------|-------------------------------------------------------------------------------| +| `ENV` | Hash of current environment variable names and values. | +| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | +| `ARGV` | Array of the given command-line arguments. | +| `TOPLEVEL_BINDING` | Binding of the top level scope. | +| `RUBY_VERSION` | String Ruby version. | +| `RUBY_RELEASE_DATE` | String Ruby release date. | +| `RUBY_PLATFORM` | String Ruby platform. | +| `RUBY_PATCH_LEVEL` | String Ruby patch level. | +| `RUBY_REVISION` | String Ruby revision. | +| `RUBY_COPYRIGHT` | String Ruby copyright. | +| `RUBY_ENGINE` | String Ruby engine. | | `RUBY_ENGINE_VERSION` | String Ruby engine version. | -| `RUBY_DESCRIPTION` | String Ruby description. | +| `RUBY_DESCRIPTION` | String Ruby description. | -### Embedded Data +### Embedded \Data -| Constant | Contains | -|:--------:|--------------------------------------------------------------------| -| `DATA` | File containing embedded data (lines following `__END__`, if any). | +| Constant | Contains | +|:---------------------:|-------------------------------------------------------------------------------| +| `DATA` | File containing embedded data (lines following `__END__`, if any). | ## Streams @@ -607,3 +611,4 @@ Output: [command-line option `-p`]: rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing [command-line option `-v`]: rdoc-ref:language/options.md@v-3A+Print+Version-3B+Set+-24VERBOSE [command-line option `-w`]: rdoc-ref:language/options.md@w-3A+Synonym+for+-W1 + From fe9a7448b131a48ee37df720fbbfae3142d274ca Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 08:40:59 -0500 Subject: [PATCH 2084/2435] Check slot_size before zeroing memory for GC hook If the slot_size < RVALUE_SIZE then we would underflow in the memset. --- gc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index d229c1f5aba254..104b027cca788f 100644 --- a/gc.c +++ b/gc.c @@ -1001,7 +1001,10 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, shape_id_t shape_id, bool w if (UNLIKELY(rb_gc_event_hook_required_p(RUBY_INTERNAL_EVENT_NEWOBJ))) { int lev = RB_GC_VM_LOCK_NO_BARRIER(); { - memset((char *)obj + RVALUE_SIZE, 0, rb_gc_obj_slot_size(obj) - RVALUE_SIZE); + size_t slot_size = rb_gc_obj_slot_size(obj); + if (slot_size > RVALUE_SIZE) { + memset((char *)obj + RVALUE_SIZE, 0, slot_size - RVALUE_SIZE); + } /* We must disable GC here because the callback could call xmalloc * which could potentially trigger a GC, and a lot of code is unsafe From 6d5605b28a800be19357ec57ff6c8e1118aabca0 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 11:39:58 -0500 Subject: [PATCH 2085/2435] [ruby/mmtk] Make rb_gc_impl_heap_id_for_size use MMTK_HEAP_COUNT https://github.com/ruby/mmtk/commit/2185189df4 --- gc/mmtk/mmtk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 52f653a299ca09..c0c1bf30ea34c9 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -657,7 +657,7 @@ rb_gc_impl_obj_slot_size(VALUE obj) size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size) { - for (int i = 0; i < 5; i++) { + for (int i = 0; i < MMTK_HEAP_COUNT; i++) { if (size == heap_sizes[i]) return i; if (size < heap_sizes[i]) return i; } From 3fee7dd90d19790950f476614ae53a95b7730592 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Sat, 20 Dec 2025 15:28:04 -0500 Subject: [PATCH 2086/2435] Small improvements to doc/language/ractor.md (#15588) [DOC] Improvements to doc/language/ractor.md --- doc/language/ractor.md | 293 +++++++++++++++++++++++------------------ 1 file changed, 165 insertions(+), 128 deletions(-) diff --git a/doc/language/ractor.md b/doc/language/ractor.md index 224e36934b51d2..24b75c1c5b552e 100644 --- a/doc/language/ractor.md +++ b/doc/language/ractor.md @@ -1,39 +1,39 @@ -# Ractor - Ruby's Actor-like concurrent abstraction +# Ractor - Ruby's Actor-like concurrency abstraction -Ractor is designed to provide a parallel execution feature of Ruby without thread-safety concerns. +Ractors are designed to provide parallel execution of Ruby code without thread-safety concerns. ## Summary ### Multiple Ractors in an interpreter process -You can make multiple Ractors and they run in parallel. +You can create multiple Ractors which can run ruby code in parallel. -* `Ractor.new{ expr }` creates a new Ractor and `expr` is run in parallel on a parallel computer. -* Interpreter invokes with the first Ractor (called *main Ractor*). -* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). +* `Ractor.new{ expr }` creates a new Ractor and `expr` can run in parallel with other ractors on a multi-core computer. +* Ruby processes start with one Ractor (called the *main Ractor*). +* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. * Each Ractor contains one or more Threads. - * Threads within the same Ractor share a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. - * The overhead of creating a Ractor is similar to overhead of one Thread creation. + * Threads within the same Ractor share a Ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel. + * The overhead of creating a Ractor is slightly above the overhead of creating a Thread. -### Limited sharing between multiple ractors +### Limited sharing between Ractors -Ractors don't share everything, unlike threads. +Ractors don't share all objects, unlike Threads which can access any object other than objects stored in another Thread's thread-locals. -* Most objects are *Unshareable objects*, so you don't need to care about thread-safety problems which are caused by sharing. -* Some objects are *Shareable objects*. - * Immutable objects: frozen objects which don't refer to unshareable-objects. +* Most objects are *Unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across Ractors. +* Some objects are *Shareable objects*. Here is an incomplete list to give you an idea: + * Immutable objects: these are frozen objects which don't refer to unshareable-objects. * `i = 123`: `i` is an immutable object. * `s = "str".freeze`: `s` is an immutable object. - * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers unshareable-object `[2]` (which is not frozen). - * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers Symbol `:c` and shareable `Object` class object which is not frozen. - * Class/Module objects + * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers to the unshareable-object `[2]` (which is not frozen). + * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers to the Symbol `:c` and the shareable `Object` class. + * Class/Module objects are always shareable, even if they refer to unshareable objects. * Special shareable objects - * Ractor object itself. + * Ractor objects themselves are shareable. * And more... ### Communication between Ractors with `Ractor::Port` -Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. `Ractor::Port` is provided for this communication. +Ractors communicate with each other and synchronize their execution by exchanging messages. The `Ractor::Port` class provides this communication mechanism. ```ruby port = Ractor::Port.new @@ -43,72 +43,65 @@ Ractor.new port do |port| port << 42 end -port.receive # get a message to the port. Only the creator Ractor can receive from the port +port.receive # get a message from the port. Only the ractor that created the Port can receive from it. #=> 42 ``` -Ractors have its own default port and `Ractor#send`, `Ractor.receive` will use it. +All Ractors have a default port, which `Ractor#send`, `Ractor.receive` (etc) will use. -### Copy & Move semantics to send messages +### Copy & Move semantics when sending objects -To send unshareable objects as messages, objects are copied or moved. +To send unshareable objects to another ractor, objects are either copied or moved. -* Copy: use deep-copy. -* Move: move membership. - * Sender can not access the moved object after moving the object. - * Guarantee that at least only 1 Ractor can access the object. +* Copy: deep-copies the object to the other ractor. +* Move: moves membership to another ractor. + * The sending Ractor can not access the moved object after it moves. + * There is a guarantee that only one Ractor can access an unshareable object at once. ### Thread-safety -Ractor helps to write a thread-safe concurrent program, but we can make thread-unsafe programs with Ractors. +Ractors help to write thread-safe, concurrent programs. They allow sharing of data only through explicit message passing for +unshareable objects. Shareable objects are guaranteed to work correctly across ractors, even if the ractors are running in parallel. +This guarantee, however, only applies across ractors. You still need to use Mutexes and other thread-safety tools within a ractor if +you're using multiple ruby Threads. -* GOOD: Sharing limitation - * Most objects are unshareable, so we can't make data-racy and race-conditional programs. - * Shareable objects are protected by an interpreter or locking mechanism. -* BAD: Class/Module can violate this assumption - * To make it compatible with old behavior, classes and modules can introduce data-race and so on. - * Ruby programmers should take care if they modify class/module objects on multi Ractor programs. -* BAD: Ractor can't solve all thread-safety problems - * There are several blocking operations (waiting send) so you can make a program which has dead-lock and live-lock issues. - * Some kind of shareable objects can introduce transactions (STM, for example). However, misusing transactions will generate inconsistent state. - -Without Ractor, we need to trace all state-mutations to debug thread-safety issues. -With Ractor, you can concentrate on suspicious code which are shared with Ractors. + * Most objects are unshareable. You can't create data-races across ractors due to the inability to use these objects across ractors. + * Shareable objects are protected by locks (or otherwise don't need to be) so they can be used by more than one ractor at once. ## Creation and termination ### `Ractor.new` -* `Ractor.new{ expr }` generates another Ractor. +* `Ractor.new{ expr }` creates a Ractor. ```ruby -# Ractor.new with a block creates new Ractor +# Ractor.new with a block creates a new Ractor r = Ractor.new do - # This block will be run in parallel with other ractors + # This block can run in parallel with other ractors end -# You can name a Ractor with `name:` argument. -r = Ractor.new name: 'test-name' do +# You can name a Ractor with a `name:` argument. +r = Ractor.new name: 'my-first-ractor' do end # and Ractor#name returns its name. -r.name #=> 'test-name' +r.name #=> 'my-first-ractor' ``` -### Given block isolation +### Block isolation -The Ractor executes given `expr` in a given block. -Given block will be isolated from outer scope by the `Proc#isolate` method (not exposed yet for Ruby users). To prevent sharing unshareable objects between ractors, block outer-variables, `self` and other information are isolated. +The Ractor executes `expr` in the given block. +The given block will be isolated from its outer scope. To prevent sharing objects between ractors, outer variables, `self` and other information is isolated from the block. -`Proc#isolate` is called at Ractor creation time (when `Ractor.new` is called). If given Proc object is not able to isolate because of outer variables and so on, an error will be raised. +This isolation occurs at Ractor creation time (when `Ractor.new` is called). If the given block is not able to be isolated because of outer variables or `self`, an error will be raised. ```ruby begin a = true r = Ractor.new do - a #=> ArgumentError because this block accesses `a`. + a #=> ArgumentError because this block accesses outer variable `a`. end - r.join # see later + r.join # wait for ractor to finish rescue ArgumentError end ``` @@ -123,7 +116,7 @@ end r.value == self.object_id #=> false ``` -Passed arguments to `Ractor.new()` becomes block parameters for the given block. However, an interpreter does not pass the parameter object references, but send them as messages (see below for details). +Arguments passed to `Ractor.new()` become block parameters for the given block. However, Ruby does not pass the objects themselves, but sends them as messages (see below for details). ```ruby r = Ractor.new 'ok' do |msg| @@ -133,7 +126,7 @@ r.value #=> 'ok' ``` ```ruby -# almost similar to the last example +# similar to the last example r = Ractor.new do msg = Ractor.receive msg @@ -142,9 +135,9 @@ r.send 'ok' r.value #=> 'ok' ``` -### An execution result of given block +### The execution result of the given block -Return value of the given block becomes an outgoing message (see below for details). +The return value of the given block becomes an outgoing message (see below for details). ```ruby r = Ractor.new do @@ -153,11 +146,11 @@ end r.value #=> `ok` ``` -Error in the given block will be propagated to the receiver of an outgoing message. +An error in the given block will be propagated to the consumer of the outgoing message. ```ruby r = Ractor.new do - raise 'ok' # exception will be transferred to the receiver + raise 'ok' # exception will be transferred to the consumer end begin @@ -171,36 +164,39 @@ end ## Communication between Ractors -Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate with each other. +Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate: -* (1) Message sending/receiving via `Ractor::Port` +* (1) Sending and receiving messages via `Ractor::Port` * (2) Using shareable container objects * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) - * more? -Users can control program execution timing with (1), but should not control with (2) (only manage as critical section). +Users can control program execution timing with (1), but should not control with (2) (only perform critical sections). + +For sending and receiving messages, these are the fundamental APIs: + +* send/receive via `Ractor::Port`. + * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so it will never block the caller. + * `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread. + * `Ractor#send` and `Ractor.receive` use ports (their default port) internally, so are conceptually similar to the above. +* You can close a `Ractor::Port` by `Ractor::Port#close`. A port can only be closed by the ractor that created it. + * If a port is closed, you can't `send` to it. Doing so raises an exception. + * When a Ractor is terminated, the Ractor's ports are automatically closed. +* You can wait for a ractor's termination and receive its return value with `Ractor#value`. This is similar to `Thread#value`. -For message sending and receiving, there are two types of APIs: push type and pull type. +There are 3 ways to send an object as a message: -* (1) send/receive via `Ractor::Port`. - * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) send a message to the port. Ports are connected to the infinite size incoming queue so `Ractor::Port#send` will never block. - * `Ractor::Port#receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` calling will block the execution of a thread. -* `Ractor.select()` can wait for the success of `Ractor::Port#receive`. -* You can close `Ractor::Port` by `Ractor::Port#close` only by the creator Ractor of the port. - * If the port is closed, you can't `send` to the port. If `Ractor::Port#receive` is blocked for the closed port, then it will raise an exception. - * When a Ractor is terminated, the Ractor's ports are closed. -* There are 3 ways to send an object as a message - * (1) Send a reference: Sending a shareable object, send only a reference to the object (fast) - * (2) Copy an object: Sending an unshareable object by copying an object deeply (slow). Note that you can not send an object which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported. - * (3) Move an object: Sending an unshareable object reference with a membership. Sender Ractor can not access moved objects anymore (raise an exception) after moving it. Current implementation makes new object as a moved object for receiver Ractor and copies references of sending object to moved object. `T_DATA` objects are not supported. - * You can choose "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)` and `Ractor.yield(obj, move: true/false)` (default is `false` (COPY)). +1) Send a reference: sending a shareable object sends only a reference to the object (fast). +2) Copy an object: sending an unshareable object through copying it deeply (can be slow). Note that you can not send an object this way which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported. +3) Move an object: sending an unshareable object across ractors with a membership change. The sending Ractor can not access the moved object after moving it, otherwise an exception will be raised. Implementation note: `T_DATA` objects are not supported. + +You can choose between "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)`. The default is `false` ("Copy"). However, if the object is shareable it will automatically use `move`. ### Wait for multiple Ractors with `Ractor.select` -You can wait multiple Ractor port's receiving. +You can wait for messages on multiple ports at once. The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message. -To make convenient, `Ractor.select` can also accept Ractors to wait the termination of Ractors. +To make it convenient, `Ractor.select` can also accept Ractors. In this case, it waits for their termination. The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's block. Wait for a single ractor (same as `Ractor#value`): @@ -218,23 +214,21 @@ Waiting for two ractors: r1 = Ractor.new{'r1'} r2 = Ractor.new{'r2'} rs = [r1, r2] -as = [] +values = [] -# Wait for r1 or r2's Ractor.yield +# Wait for r1 or r2's termination r, obj = Ractor.select(*rs) rs.delete(r) -as << obj +values << obj # Second try (rs only contain not-closed ractors) r, obj = Ractor.select(*rs) rs.delete(r) -as << obj -as.sort == ['r1', 'r2'] #=> true +values << obj +values.sort == ['r1', 'r2'] #=> true ``` -TODO: Current `Ractor.select()` has the same issue of `select(2)`, so this interface should be refined. - -TODO: `select` syntax of go-language uses round-robin technique to make fair scheduling. Now `Ractor.select()` doesn't use it. +NOTE: Using `Ractor.select()` on a very large number of ractors has the same issue as `select(2)` currently. ### Closing Ractor's ports @@ -252,7 +246,7 @@ end r.join # success (wait for the termination) r.value # success (will return 'finish') -# the first Ractor which success the `Ractor#value` can get the result +# The ractor's termination value has already been given to another ractor Ractor.new r do |r| r.value #=> Ractor::Error end @@ -264,7 +258,7 @@ Example (try to send to closed (terminated) Ractor): r = Ractor.new do end -r.join # wait terminate +r.join # wait for termination begin r.send(1) @@ -289,7 +283,7 @@ end obj.object_id == r.value #=> false ``` -Some objects are not supported to copy the value, and raise an exception. +Some objects do not support copying, and raise an exception. ```ruby obj = Thread.new{} @@ -299,15 +293,13 @@ begin end rescue TypeError => e e.message #=> # -else - 'ng' # unreachable here end ``` ### Send a message by moving `Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor. -If the source Ractor touches the moved object (for example, call the method like `obj.foo()`), it will be an error. +If the source Ractor touches the moved object (for example, calls a method like `obj.foo()`), it will raise an error. ```ruby # move with Ractor#send @@ -316,23 +308,21 @@ r = Ractor.new do obj << ' world' end -str = 'hello' +str = 'hello'.dup r.send str, move: true +# str is now moved, and accessing str from this Ractor is prohibited modified = r.value #=> 'hello world' -# str is moved, and accessing str from this Ractor is prohibited begin # Error because it touches moved str. str << ' exception' # raise Ractor::MovedError rescue Ractor::MovedError modified #=> 'hello world' -else - raise 'unreachable' end ``` -Some objects are not supported to move, and an exception will be raised. +Some objects do not support moving, and an exception will be raised. ```ruby r = Ractor.new do @@ -342,34 +332,29 @@ end r.send(Thread.new{}, move: true) #=> allocator undefined for Thread (TypeError) ``` -To achieve the access prohibition for moved objects, _class replacement_ technique is used to implement it. +Once an object has been moved, the source object's class is changed to `Ractor::MovedObject`. ### Shareable objects -The following objects are shareable. - -* Immutable objects - * Small integers, some symbols, `true`, `false`, `nil` (a.k.a. `SPECIAL_CONST_P()` objects in internal) - * Frozen native objects - * Numeric objects: `Float`, `Complex`, `Rational`, big integers (`T_BIGNUM` in internal) - * All Symbols. - * Frozen `String` and `Regexp` objects (their instance variables should refer only shareable objects) -* Class, Module objects (`T_CLASS`, `T_MODULE` and `T_ICLASS` in internal) -* `Ractor` and other special objects which care about synchronization. +The following is an inexhaustive list of shareable objects: -Implementation: Now shareable objects (`RVALUE`) have `FL_SHAREABLE` flag. This flag can be added lazily. +* Small integers, big integers, `Float`, `Complex`, `Rational` +* All symbols, frozen Strings, `true`, `false`, `nil` +* `Regexp` objects, if they have no instance variables or their instance variables refer only to shareables +* Class and Module objects +* `Ractor` and other special objects which care about synchronization -To make shareable objects, `Ractor.make_shareable(obj)` method is provided. In this case, try to make shareable by freezing `obj` and recursively traversable objects. This method accepts `copy:` keyword (default value is false).`Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. +To make objects shareable, `Ractor.make_shareable(obj)` is provided. It tries to make the object shareable by freezing `obj` and recursively traversing its references to freeze them all. This method accepts the `copy:` keyword (default value is false). `Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. `Ractor.make_shareable(copy: false)` has no effect on an already shareable object. If the object cannot be made shareable, a `Ractor::Error` exception will be raised. ## Language changes to isolate unshareable objects between Ractors To isolate unshareable objects between Ractors, we introduced additional language semantics on multi-Ractor Ruby programs. -Note that without using Ractors, these additional semantics is not needed (100% compatible with Ruby 2). +Note that without using Ractors, these additional semantics are not needed (100% compatible with Ruby 2). ### Global variables -Only the main Ractor (a Ractor created at starting of interpreter) can access global variables. +Only the main Ractor can access global variables. ```ruby $gv = 1 @@ -388,7 +373,7 @@ Note that some special global variables, such as `$stdin`, `$stdout` and `$stder ### Instance variables of shareable objects -Instance variables of classes/modules can be get from non-main Ractors if the referring values are shareable objects. +Instance variables of classes/modules can be accessed from non-main Ractors only if their values are shareable objects. ```ruby class C @@ -428,8 +413,6 @@ Ractor.new do end.join ``` - - ```ruby shared = Ractor.new{} shared.instance_variable_set(:@iv, 'str') @@ -445,8 +428,6 @@ rescue Ractor::RemoteError => e end ``` -Note that instance variables for class/module objects are also prohibited on Ractors. - ### Class variables Only the main Ractor can access class variables. @@ -472,11 +453,11 @@ end ### Constants -Only the main Ractor can read constants which refer to the unshareable object. +Only the main Ractor can read constants which refer to an unshareable object. ```ruby class C - CONST = 'str' + CONST = 'str'.dup end r = Ractor.new do C::CONST @@ -488,13 +469,13 @@ rescue => e end ``` -Only the main Ractor can define constants which refer to the unshareable object. +Only the main Ractor can define constants which refer to an unshareable object. ```ruby class C end r = Ractor.new do - C::CONST = 'str' + C::CONST = 'str'.dup end begin r.join @@ -503,19 +484,19 @@ rescue => e end ``` -To make multi-ractor supported library, the constants should only refer shareable objects. +When creating/updating a library to support ractors, constants should only refer to shareable objects if they are to be used by non-main ractors. ```ruby TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} ``` -In this case, `TABLE` references an unshareable Hash object. So that other ractors can not refer `TABLE` constant. To make it shareable, we can use `Ractor.make_shareable()` like that. +In this case, `TABLE` refers to an unshareable Hash object. In order for other ractors to use `TABLE`, we need to make it shareable. We can use `Ractor.make_shareable()` like so: ```ruby TABLE = Ractor.make_shareable( {a: 'ko1', b: 'ko2', c: 'ko3'} ) ``` -To make it easy, Ruby 3.0 introduced new `shareable_constant_value` Directive. +To make it easy, Ruby 3.0 introduced a new `shareable_constant_value` file directive. ```ruby # shareable_constant_value: literal @@ -524,7 +505,7 @@ TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} #=> Same as: TABLE = Ractor.make_shareable( {a: 'ko1', b: 'ko2', c: 'ko3'} ) ``` -`shareable_constant_value` directive accepts the following modes (descriptions use the example: `CONST = expr`): +The `shareable_constant_value` directive accepts the following modes (descriptions use the example: `CONST = expr`): * none: Do nothing. Same as: `CONST = expr` * literal: @@ -533,15 +514,71 @@ TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} * experimental_everything: replaced to `CONST = Ractor.make_shareable(expr)`. * experimental_copy: replaced to `CONST = Ractor.make_shareable(expr, copy: true)`. -Except the `none` mode (default), it is guaranteed that the assigned constants refer to only shareable objects. +Except for the `none` mode (default), it is guaranteed that the constants in the file refer only to shareable objects. See [doc/syntax/comments.rdoc](syntax/comments.rdoc) for more details. -## Implementation note +### Shareable procs + +Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the Proc's block is isolated +from its outer environment, so it cannot access locals from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize +the value to a different shareable object. + +```ruby +p = Ractor.shareable_proc { p self } +p.call #=> nil +``` + +```ruby +begin + a = 1 + pr = Ractor.shareable_proc { p a } + pr.call # never gets here +rescue Ractor::IsolationError +end +``` -* Each Ractor has its own thread, it means each Ractor has at least 1 native thread. -* Each Ractor has its own ID (`rb_ractor_t::pub::id`). - * On debug mode, all unshareable objects are labeled with current Ractor's id, and it is checked to detect unshareable object leak (access an object from different Ractor) in VM. +In order to dynamically define a method with `define_method` that can be used from different ractors, you must +define it with a shareable proc. Alternatively, you can use `class_eval` or `module_eval` with a String. Even though +the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the +method. + +```ruby +class A + define_method :testing, &Ractor.shareable_proc do + p self + end +end +Ractor.new do + a = A.new + a.testing #=> # +end.join +``` + +This isolation must be done to prevent the method from accessing captured outer variables across Ractors. + +### Ractor-local storage + +You can store any object (even unshareables) in ractor-local storage. + +```ruby +r = Ractor.new do + values = [] + Ractor[:threads] = [] + 3.times do |i| + Ractor[:threads] << Thread.new do + values << [Ractor.receive, i+1] # Ractor.receive blocks the current thread in the current ractor until it receives a message + end + end + Ractor[:threads].each(&:join) + values +end + +r << 1 +r << 2 +r << 3 +r.value #=> [[1,1],[2,2],[3,3]] (the order can change with each run) +``` ## Examples From 48842525a8c3270b5dcc2b0a47f010658ef641a2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 17:05:11 -0500 Subject: [PATCH 2087/2435] Exclude TestObjSpace#test_dump_objects_dumps_page_slot_sizes for MMTk [ci skip] --- test/.excludes-mmtk/TestObjSpace.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/.excludes-mmtk/TestObjSpace.rb b/test/.excludes-mmtk/TestObjSpace.rb index 05666e46f07fc8..94eb2c436d4435 100644 --- a/test/.excludes-mmtk/TestObjSpace.rb +++ b/test/.excludes-mmtk/TestObjSpace.rb @@ -1,3 +1,4 @@ exclude(:test_dump_all_full, "testing behaviour specific to default GC") exclude(:test_dump_flag_age, "testing behaviour specific to default GC") exclude(:test_dump_flags, "testing behaviour specific to default GC") +exclude(:test_dump_objects_dumps_page_slot_sizes, "testing behaviour specific to default GC") From 7c1e37cfe13cf2df21e4ca23c91f5a53c81bc062 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 20 Dec 2025 22:57:00 +0000 Subject: [PATCH 2088/2435] [DOC] Tweaks for Module#<=> --- object.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/object.c b/object.c index b2b7a9d96038f0..93c8a483cfe26c 100644 --- a/object.c +++ b/object.c @@ -2012,13 +2012,15 @@ rb_mod_gt(VALUE mod, VALUE arg) /* * call-seq: - * self <=> object -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - +-1+, if +self+ includes +object+, if or +self+ is a subclass of +object+. - * - +0+, if +self+ and +object+ are the same. - * - +1+, if +object+ includes +self+, or if +object+ is a subclass of +self+. + * - +-1+, if +self+ includes +other+, if or +self+ is a subclass of +other+. + * - +0+, if +self+ and +other+ are the same. + * - +1+, if +other+ includes +self+, or if +other+ is a subclass of +self+. * - +nil+, if none of the above is true. * * Examples: @@ -2029,8 +2031,10 @@ rb_mod_gt(VALUE mod, VALUE arg) * Enumerable <=> Array # => 1 * # Class File is a subclass of class IO. * File <=> IO # => -1 - * IO <=> File # => 1 * File <=> File # => 0 + * IO <=> File # => 1 + * # Class File has no relationship to class String. + * File <=> String # => nil * */ From addbeb6cf337695647db6e265b5db87105d1c2e9 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 20 Dec 2025 22:22:35 +0000 Subject: [PATCH 2089/2435] [DOC] Note for String#<=> about Comparable --- string.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/string.c b/string.c index 83219d1a26d085..8c7c82c10f4fa8 100644 --- a/string.c +++ b/string.c @@ -4315,6 +4315,9 @@ rb_str_eql(VALUE str1, VALUE str2) * 'ab' <=> 'a' # => 1 * 'a' <=> :a # => nil * + * \Class \String includes module Comparable, + * each of whose methods uses String#<=> for comparison. + * * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ From 580872785181303f26f715b5acc611d0ec087256 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 20 Dec 2025 22:30:03 +0000 Subject: [PATCH 2090/2435] [DOC] Fix indentation error in Complex#<=> --- complex.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/complex.c b/complex.c index 1fe68f80bebb97..bb54d4f61f31ca 100644 --- a/complex.c +++ b/complex.c @@ -1282,8 +1282,8 @@ nucomp_real_p(VALUE self) * Complex.rect(1) <=> Complex.rect(1, 1) # => nil # object.imag not zero. * Complex.rect(1) <=> 'Foo' # => nil # object.imag not defined. * - * \Class \Complex includes module Comparable, - * each of whose methods uses Complex#<=> for comparison. + * \Class \Complex includes module Comparable, + * each of whose methods uses Complex#<=> for comparison. */ static VALUE nucomp_cmp(VALUE self, VALUE other) From f42535fa38ff487382db32158908635e1a356d5c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 16:33:21 -0500 Subject: [PATCH 2091/2435] Change test to define ivars in initialize method Defining ivars in initialize method guarantees the object to be embedded. --- test/ruby/test_object.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index cccd7359e1108e..41585ba150e160 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -359,11 +359,13 @@ def test_remove_instance_variable_re_embed require "objspace" c = Class.new do - def a = @a + attr_reader :a, :b, :c - def b = @b - - def c = @c + def initialize + @a = nil + @b = nil + @c = nil + end end o1 = c.new From 483ef3a0d2a6c097ca1606c5cb4a0fae8f3d4f43 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 17:01:05 -0500 Subject: [PATCH 2092/2435] Test test_remove_instance_variable_re_embed separately Shape tree pollution could cause this test to flake. --- test/ruby/test_object.rb | 63 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 41585ba150e160..f4dfe2251b884f 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -356,40 +356,41 @@ def test_remove_instance_variable end def test_remove_instance_variable_re_embed - require "objspace" + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + c = Class.new do + attr_reader :a, :b, :c - c = Class.new do - attr_reader :a, :b, :c - - def initialize - @a = nil - @b = nil - @c = nil + def initialize + @a = nil + @b = nil + @c = nil + end end - end - o1 = c.new - o2 = c.new - - o1.instance_variable_set(:@foo, 5) - o1.instance_variable_set(:@a, 0) - o1.instance_variable_set(:@b, 1) - o1.instance_variable_set(:@c, 2) - refute_includes ObjectSpace.dump(o1), '"embedded":true' - o1.remove_instance_variable(:@foo) - assert_includes ObjectSpace.dump(o1), '"embedded":true' - - o2.instance_variable_set(:@a, 0) - o2.instance_variable_set(:@b, 1) - o2.instance_variable_set(:@c, 2) - assert_includes ObjectSpace.dump(o2), '"embedded":true' - - assert_equal(0, o1.a) - assert_equal(1, o1.b) - assert_equal(2, o1.c) - assert_equal(0, o2.a) - assert_equal(1, o2.b) - assert_equal(2, o2.c) + o1 = c.new + o2 = c.new + + o1.instance_variable_set(:@foo, 5) + o1.instance_variable_set(:@a, 0) + o1.instance_variable_set(:@b, 1) + o1.instance_variable_set(:@c, 2) + refute_includes ObjectSpace.dump(o1), '"embedded":true' + o1.remove_instance_variable(:@foo) + assert_includes ObjectSpace.dump(o1), '"embedded":true' + + o2.instance_variable_set(:@a, 0) + o2.instance_variable_set(:@b, 1) + o2.instance_variable_set(:@c, 2) + assert_includes ObjectSpace.dump(o2), '"embedded":true' + + assert_equal(0, o1.a) + assert_equal(1, o1.b) + assert_equal(2, o1.c) + assert_equal(0, o2.a) + assert_equal(1, o2.b) + assert_equal(2, o2.c) + end; end def test_convert_string From e2243ed232d6a67e4a3e84e30e82b792a39b21e0 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 21 Dec 2025 00:32:30 +0000 Subject: [PATCH 2093/2435] [DOC] Correct typo --- string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/string.c b/string.c index 8c7c82c10f4fa8..b70cee020de6bd 100644 --- a/string.c +++ b/string.c @@ -12383,7 +12383,7 @@ sym_succ(VALUE sym) * * Returns: * - * - symbol.to_s <=> other.to_s, if +other+ is a symbol. + * - self.to_s <=> other.to_s, if +other+ is a symbol. * - +nil+, otherwise. * * Examples: From 1bc2a9158966a4a1556529d8bb50fae66fa11bb0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 21 Dec 2025 11:31:36 +0900 Subject: [PATCH 2094/2435] [DOC] Align tables in globals.md * Align "Contains" column in "Streams" table * Align some columns vertically * Remove a duplicate `$-a` description --- doc/language/globals.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index 4d42798b8ca154..a4199e488ab585 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -14,16 +14,16 @@ require 'English' ### Exceptions -| Variable | \English | Contains | Initially | Read-Only | Reset By | -|:--------:|:-----------------:|-----------------------------------------|:---------:|:---------:|---------------| -| `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | -| `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:--------:|:-----------------:|----------------------------------------|:---------:|:---------:|--------------| +| `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | +| `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | ### Pattern Matching | Variable | \English | Contains | Initially | Read-Only | Reset By | |:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| -| `$~` | `$LAST_MATCH_INFO` | \MatchData object or `nil` | `nil` | No | Matcher methods | +| `$~` | `$LAST_MATCH_INFO` | \MatchData object or `nil` | `nil` | No | Matcher methods | | `$&` | `$MATCH` | Matched substring or `nil` | `nil` | No | Matcher methods | | `` $` `` | `$PRE_MATCH` | Substring left of match or `nil` | `nil` | No | Matcher methods | | `$'` | `$POST_MATCH` | Substring right of match or `nil` | `nil` | No | Matcher methods | @@ -41,15 +41,15 @@ require 'English' ### Streams -| Variable | \English | Contains | Initially | Read-Only | Reset By | -|:---------:|:----------------------------:|:-------------------------------------------:|:---------:|:---------:|----------------------| -| `$stdin` | | Standard input stream | `STDIN` | No | | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:---------:|:----------------------------:|---------------------------------------------|:---------:|:---------:|----------------------| +| `$stdin` | | Standard input stream | `STDIN` | No | | | `$stdout` | | Standard output stream | `STDOUT` | No | | | `$stderr` | | Standard error stream | `STDERR` | No | | -| `$<` | `$DEFAULT_INPUT` | Default standard input | `ARGF` | Yes | | -| `$>` | `$DEFAULT_OUTPUT` | Default standard output | `STDOUT` | No | | -| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream | 0 | No | Certain read methods | -| `$_` | `$LAST_READ_LINE` | String from most recently read stream | `nil` | No | Certain read methods | +| `$<` | `$DEFAULT_INPUT` | Default standard input | `ARGF` | Yes | | +| `$>` | `$DEFAULT_OUTPUT` | Default standard output | `STDOUT` | No | | +| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream | 0 | No | Certain read methods | +| `$_` | `$LAST_READ_LINE` | String from most recently read stream | `nil` | No | Certain read methods | ### Processes @@ -64,11 +64,11 @@ require 'English' ### Debugging -| Variable | \English | Contains | Initially | Read-Only | Reset By | +| Variable | \English | Contains | Initially | Read-Only | Reset By | |:-----------:|:--------:|--------------------------------------------|:----------------------------:|:---------:|----------| -| `$FILENAME` | | Value returned by method `ARGF.filename` | Command-line argument or '-' | Yes | | -| `$DEBUG` | | Whether option `-d` or `--debug` was given | Command-line option | No | | -| `$VERBOSE` | | Whether option `-V` or `-W` was given | Command-line option | No | | +| `$FILENAME` | | Value returned by method `ARGF.filename` | Command-line argument or '-' | Yes | | +| `$DEBUG` | | Whether option `-d` or `--debug` was given | Command-line option | No | | +| `$VERBOSE` | | Whether option `-V` or `-W` was given | Command-line option | No | | ### Other Variables @@ -352,10 +352,6 @@ Aliased as `$-v` and `$-w`. ## Other Variables -### `$-a` - -Whether command-line option `-a` was given; read-only. - ### `$-F` The default field separator in String#split; must be a String or a From 038b158fa30d1b1cda153aeb10dac5f59f966036 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 16:08:01 -0500 Subject: [PATCH 2095/2435] [ruby/mmtk] Implement fast path for bump pointer allocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding a fast path for bump pointer allocator can improve allocation performance. For the following microbenchmark with MMTK_HEAP_MIN=100MiB: 10_000_000.times { String.new } Before: 810.7 ms ± 8.3 ms [User: 790.9 ms, System: 40.3 ms] After: 777.9 ms ± 10.4 ms [User: 759.0 ms, System: 37.9 ms] https://github.com/ruby/mmtk/commit/0ff5c9f579 --- gc/mmtk/cbindgen.toml | 5 +++++ gc/mmtk/mmtk.c | 34 +++++++++++++++++++++++++++++++--- gc/mmtk/mmtk.h | 7 +++++++ gc/mmtk/src/api.rs | 20 ++++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/gc/mmtk/cbindgen.toml b/gc/mmtk/cbindgen.toml index c66f829b3dd1d0..b99c30efc8c868 100644 --- a/gc/mmtk/cbindgen.toml +++ b/gc/mmtk/cbindgen.toml @@ -20,6 +20,11 @@ typedef void *MMTk_Address; typedef void *MMTk_ObjectReference; typedef void *MMTk_NullableObjectReference; typedef uint32_t MMTk_AllocationSemantics; + +typedef struct MMTk_BumpPointer { + uintptr_t cursor; + uintptr_t limit; +} MMTk_BumpPointer; """ [export] diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index c0c1bf30ea34c9..fe1a17b2451715 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -48,6 +48,8 @@ struct MMTk_ractor_cache { MMTk_Mutator *mutator; bool gc_mutator_p; + + MMTk_BumpPointer *bump_pointer; }; struct MMTk_final_job { @@ -447,6 +449,7 @@ rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor) ccan_list_add(&objspace->ractor_caches, &cache->list_node); cache->mutator = mmtk_bind_mutator(cache); + cache->bump_pointer = mmtk_get_bump_pointer_allocator(cache->mutator); return cache; } @@ -612,6 +615,24 @@ rb_gc_impl_config_set(void *objspace_ptr, VALUE hash) // Object allocation +static VALUE +rb_mmtk_alloc_fast_path(struct objspace *objspace, struct MMTk_ractor_cache *ractor_cache, size_t size) +{ + MMTk_BumpPointer *bump_pointer = ractor_cache->bump_pointer; + if (bump_pointer == NULL) return 0; + + uintptr_t new_cursor = bump_pointer->cursor + size; + + if (new_cursor > bump_pointer->limit) { + return 0; + } + else { + VALUE obj = (VALUE)bump_pointer->cursor; + bump_pointer->cursor = new_cursor; + return obj; + } +} + VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -632,13 +653,20 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_handle_user_collection_request(ractor_cache, false, false); } - VALUE *alloc_obj = mmtk_alloc(ractor_cache->mutator, alloc_size + 8, MMTk_MIN_OBJ_ALIGN, 0, MMTK_ALLOCATION_SEMANTICS_DEFAULT); + alloc_size += sizeof(VALUE); + + VALUE *alloc_obj = (VALUE *)rb_mmtk_alloc_fast_path(objspace, ractor_cache, alloc_size); + if (!alloc_obj) { + alloc_obj = mmtk_alloc(ractor_cache->mutator, alloc_size, MMTk_MIN_OBJ_ALIGN, 0, MMTK_ALLOCATION_SEMANTICS_DEFAULT); + } + alloc_obj++; - alloc_obj[-1] = alloc_size; + alloc_obj[-1] = alloc_size - sizeof(VALUE); alloc_obj[0] = flags; alloc_obj[1] = klass; - mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size + 8, MMTK_ALLOCATION_SEMANTICS_DEFAULT); + // TODO: implement fast path for mmtk_post_alloc + mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called mmtk_add_obj_free_candidate(alloc_obj); diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index 18466c61108b40..45521e2671b5cb 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -20,6 +20,11 @@ typedef void *MMTk_ObjectReference; typedef void *MMTk_NullableObjectReference; typedef uint32_t MMTk_AllocationSemantics; +typedef struct MMTk_BumpPointer { + uintptr_t cursor; + uintptr_t limit; +} MMTk_BumpPointer; + #define MMTk_OBJREF_OFFSET 8 @@ -93,6 +98,8 @@ void mmtk_initialize_collection(MMTk_VMThread tls); MMTk_Mutator *mmtk_bind_mutator(MMTk_VMMutatorThread tls); +MMTk_BumpPointer *mmtk_get_bump_pointer_allocator(MMTk_Mutator *m); + void mmtk_destroy_mutator(MMTk_Mutator *mutator); void mmtk_handle_user_collection_request(MMTk_VMMutatorThread tls, bool force, bool exhaustive); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 006e987ea9d060..b99cbdc939f151 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -2,6 +2,8 @@ // They are called by C functions and they need to pass raw pointers to Rust. #![allow(clippy::missing_safety_doc)] +use mmtk::util::alloc::BumpPointer; +use mmtk::util::alloc::ImmixAllocator; use mmtk::util::options::PlanSelector; use std::str::FromStr; use std::sync::atomic::Ordering; @@ -174,6 +176,24 @@ pub extern "C" fn mmtk_bind_mutator(tls: VMMutatorThread) -> *mut RubyMutator { Box::into_raw(memory_manager::bind_mutator(mmtk(), tls)) } +#[no_mangle] +pub unsafe extern "C" fn mmtk_get_bump_pointer_allocator(m: *mut RubyMutator) -> *mut BumpPointer { + match *crate::BINDING.get().unwrap().mmtk.get_options().plan { + PlanSelector::Immix => { + let mutator: &mut Mutator = unsafe { &mut *m }; + let allocator = + unsafe { mutator.allocator_mut(mmtk::util::alloc::AllocatorSelector::Immix(0)) }; + + if let Some(immix_allocator) = allocator.downcast_mut::>() { + &mut immix_allocator.bump_pointer as *mut BumpPointer + } else { + panic!("Failed to get bump pointer allocator"); + } + } + _ => std::ptr::null_mut(), + } +} + #[no_mangle] pub unsafe extern "C" fn mmtk_destroy_mutator(mutator: *mut RubyMutator) { // notify mmtk-core about destroyed mutator From 2ffd5ad05f0df2b33540f8fad4220aecbceab98e Mon Sep 17 00:00:00 2001 From: kitazawa <3143443+krororo@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:35:32 +0900 Subject: [PATCH 2096/2435] [DOC] Remove duplicate Pathname promotion entry at NEWS.md The Pathname promotion to core class is already documented in the "Core classes updates" section, making this duplicate entry redundant. --- NEWS.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2435498da9af12..da8c494b4b3ed1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -269,11 +269,6 @@ Note: We're only listing outstanding class updates. * Introduce support for `Thread#raise(cause:)` argument similar to `Kernel#raise`. [[Feature #21360]] -* Pathname - - * Pathname has been promoted from a default gem to a core class of Ruby. - [[Feature #17473]] - ## Stdlib updates The following bundled gems are promoted from default gems. From d8b33993e1ba1fc948db435f363e165e89badccc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 21 Dec 2025 20:31:30 +0900 Subject: [PATCH 2097/2435] Box: Fix an unused variable warning --- box.c | 1 + 1 file changed, 1 insertion(+) diff --git a/box.c b/box.c index 54bd5c6258343f..616a8acf7d88b5 100644 --- a/box.c +++ b/box.c @@ -759,6 +759,7 @@ rb_box_cleanup_local_extension(VALUE cleanup) #ifndef _WIN32 if (p) box_ext_cleanup_free(p); #endif + (void)p; } static int From 37b98f0df71e85a831677562dc08f6ce6cdae842 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 20 Dec 2025 11:42:36 -0500 Subject: [PATCH 2098/2435] [ruby/mmtk] Add a 32 byte heap for allocating smaller objects https://github.com/ruby/mmtk/commit/c4cca6c1c3 --- gc/mmtk/mmtk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index fe1a17b2451715..99c0125d97818d 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -472,11 +472,11 @@ void rb_gc_impl_set_params(void *objspace_ptr) { } static VALUE gc_verify_internal_consistency(VALUE self) { return Qnil; } -#define MMTK_HEAP_COUNT 5 +#define MMTK_HEAP_COUNT 6 #define MMTK_MAX_OBJ_SIZE 640 static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { - 40, 80, 160, 320, MMTK_MAX_OBJ_SIZE, 0 + 32, 40, 80, 160, 320, MMTK_MAX_OBJ_SIZE, 0 }; void From d0ec60dc7bd6dc6fb87b3949e3cb8127f6b9cc86 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Sun, 21 Dec 2025 13:26:10 -0500 Subject: [PATCH 2099/2435] More doc improvements to ractor.md (#15676) [DOC] More doc improvements to ractor.md --- doc/language/ractor.md | 134 +++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 73 deletions(-) diff --git a/doc/language/ractor.md b/doc/language/ractor.md index 24b75c1c5b552e..72fbde6e5a083d 100644 --- a/doc/language/ractor.md +++ b/doc/language/ractor.md @@ -4,28 +4,27 @@ Ractors are designed to provide parallel execution of Ruby code without thread-s ## Summary -### Multiple Ractors in an interpreter process +### Multiple Ractors in a ruby process -You can create multiple Ractors which can run ruby code in parallel. +You can create multiple Ractors which can run ruby code in parallel with each other. * `Ractor.new{ expr }` creates a new Ractor and `expr` can run in parallel with other ractors on a multi-core computer. -* Ruby processes start with one Ractor (called the *main Ractor*). -* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. -* Each Ractor contains one or more Threads. - * Threads within the same Ractor share a Ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel. - * The overhead of creating a Ractor is slightly above the overhead of creating a Thread. +* Ruby processes start with one ractor (called the *main ractor*). +* If the main ractor terminates, all other ractors receive termination requests, similar to how threads behave. +* Each Ractor contains one or more `Thread`s. + * Threads within the same ractor share a ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel. + * The overhead of creating a ractor is slightly above the overhead of creating a thread. ### Limited sharing between Ractors -Ractors don't share all objects, unlike Threads which can access any object other than objects stored in another Thread's thread-locals. +Ractors don't share all objects, unlike threads which can access any object other than objects stored in another thread's thread-locals. -* Most objects are *Unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across Ractors. -* Some objects are *Shareable objects*. Here is an incomplete list to give you an idea: - * Immutable objects: these are frozen objects which don't refer to unshareable-objects. - * `i = 123`: `i` is an immutable object. - * `s = "str".freeze`: `s` is an immutable object. - * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers to the unshareable-object `[2]` (which is not frozen). - * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers to the Symbol `:c` and the shareable `Object` class. +* Most objects are *unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across ractors. +* Some objects are *shareable objects*. Here is an incomplete list to give you an idea: + * `i = 123`: All `Integer`s are shareable. + * `s = "str".freeze`: Frozen strings are shareable if they have no instance variables that refer to unshareable objects. + * `a = [1, [2], 3].freeze`: `a` is not a shareable object because `a` refers to the unshareable object `[2]` (this Array is not frozen). + * `h = {c: Object}.freeze`: `h` is shareable because `Symbol`s and `Class`es are shareable, and the Hash is frozen. * Class/Module objects are always shareable, even if they refer to unshareable objects. * Special shareable objects * Ractor objects themselves are shareable. @@ -53,17 +52,17 @@ All Ractors have a default port, which `Ractor#send`, `Ractor.receive` (etc) wil To send unshareable objects to another ractor, objects are either copied or moved. -* Copy: deep-copies the object to the other ractor. +* Copy: deep-copies the object to the other ractor. All unshareable objects will be `Kernel#clone`ed. * Move: moves membership to another ractor. - * The sending Ractor can not access the moved object after it moves. - * There is a guarantee that only one Ractor can access an unshareable object at once. + * The sending ractor can not access the moved object after it moves. + * There is a guarantee that only one ractor can access an unshareable object at once. ### Thread-safety Ractors help to write thread-safe, concurrent programs. They allow sharing of data only through explicit message passing for unshareable objects. Shareable objects are guaranteed to work correctly across ractors, even if the ractors are running in parallel. -This guarantee, however, only applies across ractors. You still need to use Mutexes and other thread-safety tools within a ractor if -you're using multiple ruby Threads. +This guarantee, however, only applies across ractors. You still need to use `Mutex`es and other thread-safety tools within a ractor if +you're using multiple ruby `Thread`s. * Most objects are unshareable. You can't create data-races across ractors due to the inability to use these objects across ractors. * Shareable objects are protected by locks (or otherwise don't need to be) so they can be used by more than one ractor at once. @@ -72,7 +71,7 @@ you're using multiple ruby Threads. ### `Ractor.new` -* `Ractor.new{ expr }` creates a Ractor. +* `Ractor.new { expr }` creates a Ractor. ```ruby # Ractor.new with a block creates a new Ractor @@ -84,7 +83,6 @@ end r = Ractor.new name: 'my-first-ractor' do end -# and Ractor#name returns its name. r.name #=> 'my-first-ractor' ``` @@ -164,29 +162,30 @@ end ## Communication between Ractors -Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate: +Communication between ractors is achieved by sending and receiving messages. There are two ways to communicate: * (1) Sending and receiving messages via `Ractor::Port` -* (2) Using shareable container objects - * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) +* (2) Using shareable container objects. For example, the Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) Users can control program execution timing with (1), but should not control with (2) (only perform critical sections). For sending and receiving messages, these are the fundamental APIs: * send/receive via `Ractor::Port`. - * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so it will never block the caller. - * `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread. + * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so sending will never block the caller. + * `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread until a message is sent. * `Ractor#send` and `Ractor.receive` use ports (their default port) internally, so are conceptually similar to the above. * You can close a `Ractor::Port` by `Ractor::Port#close`. A port can only be closed by the ractor that created it. * If a port is closed, you can't `send` to it. Doing so raises an exception. - * When a Ractor is terminated, the Ractor's ports are automatically closed. + * When a ractor is terminated, the ractor's ports are automatically closed. * You can wait for a ractor's termination and receive its return value with `Ractor#value`. This is similar to `Thread#value`. There are 3 ways to send an object as a message: 1) Send a reference: sending a shareable object sends only a reference to the object (fast). + 2) Copy an object: sending an unshareable object through copying it deeply (can be slow). Note that you can not send an object this way which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported. + 3) Move an object: sending an unshareable object across ractors with a membership change. The sending Ractor can not access the moved object after moving it, otherwise an exception will be raised. Implementation note: `T_DATA` objects are not supported. You can choose between "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)`. The default is `false` ("Copy"). However, if the object is shareable it will automatically use `move`. @@ -194,10 +193,10 @@ You can choose between "Copy" and "Move" by the `move:` keyword, `Ractor#send(ob ### Wait for multiple Ractors with `Ractor.select` You can wait for messages on multiple ports at once. -The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message. +The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is the received message. -To make it convenient, `Ractor.select` can also accept Ractors. In this case, it waits for their termination. -The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's block. +To make it convenient, `Ractor.select` can also accept ractors. In this case, it waits for their termination. +The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of the ractor's block. Wait for a single ractor (same as `Ractor#value`): @@ -208,7 +207,7 @@ r, obj = Ractor.select(r1) r == r1 and obj == 'r1' #=> true ``` -Waiting for two ractors: +Wait for two ractors: ```ruby r1 = Ractor.new{'r1'} @@ -216,28 +215,25 @@ r2 = Ractor.new{'r2'} rs = [r1, r2] values = [] -# Wait for r1 or r2's termination -r, obj = Ractor.select(*rs) -rs.delete(r) -values << obj +while rs.any? + r, obj = Ractor.select(*rs) + rs.delete(r) + values << obj +end -# Second try (rs only contain not-closed ractors) -r, obj = Ractor.select(*rs) -rs.delete(r) -values << obj values.sort == ['r1', 'r2'] #=> true ``` NOTE: Using `Ractor.select()` on a very large number of ractors has the same issue as `select(2)` currently. -### Closing Ractor's ports +### Closing ports -* `Ractor::Port#close` close the ports (similar to `Queue#close`). - * `port.send(obj)` where `port` is closed, will raise an exception. +* `Ractor::Port#close` closes the port (similar to `Queue#close`). + * `port.send(obj)` will raise an exception when the port is closed. * When the queue connected to the port is empty and port is closed, `Ractor::Port#receive` raises an exception. If the queue is not empty, it dequeues an object without exceptions. * When a Ractor terminates, the ports are closed automatically. -Example (try to get a result from closed Ractor): +Example (try to get a result from closed ractor): ```ruby r = Ractor.new do @@ -249,29 +245,27 @@ r.value # success (will return 'finish') # The ractor's termination value has already been given to another ractor Ractor.new r do |r| r.value #=> Ractor::Error -end +end.join ``` -Example (try to send to closed (terminated) Ractor): +Example (try to send to closed port): ```ruby r = Ractor.new do end -r.join # wait for termination +r.join # wait for termination, closes default port begin r.send(1) rescue Ractor::ClosedError 'ok' -else - 'ng' end ``` ### Send a message by copying -`Ractor::Port#send(obj)` copy `obj` deeply if `obj` is an unshareable object. +`Ractor::Port#send(obj)` copies `obj` deeply if `obj` is an unshareable object. ```ruby obj = 'str'.dup @@ -299,10 +293,9 @@ end ### Send a message by moving `Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor. -If the source Ractor touches the moved object (for example, calls a method like `obj.foo()`), it will raise an error. +If the source ractor uses the moved object (for example, calls a method like `obj.foo()`), it will raise an error. ```ruby -# move with Ractor#send r = Ractor.new do obj = Ractor.receive obj << ' world' @@ -310,12 +303,12 @@ end str = 'hello'.dup r.send str, move: true -# str is now moved, and accessing str from this Ractor is prohibited +# str is now moved, and accessing str from this ractor is prohibited modified = r.value #=> 'hello world' begin - # Error because it touches moved str. + # Error because it uses moved str. str << ' exception' # raise Ractor::MovedError rescue Ractor::MovedError modified #=> 'hello world' @@ -338,19 +331,19 @@ Once an object has been moved, the source object's class is changed to `Ractor:: The following is an inexhaustive list of shareable objects: -* Small integers, big integers, `Float`, `Complex`, `Rational` -* All symbols, frozen Strings, `true`, `false`, `nil` +* `Integer`, `Float`, `Complex`, `Rational` +* `Symbol`, frozen `String` objects that don't refer to unshareables, `true`, `false`, `nil` * `Regexp` objects, if they have no instance variables or their instance variables refer only to shareables -* Class and Module objects -* `Ractor` and other special objects which care about synchronization +* `Class` and `Module` objects +* `Ractor` and other special objects which deal with synchronization To make objects shareable, `Ractor.make_shareable(obj)` is provided. It tries to make the object shareable by freezing `obj` and recursively traversing its references to freeze them all. This method accepts the `copy:` keyword (default value is false). `Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. `Ractor.make_shareable(copy: false)` has no effect on an already shareable object. If the object cannot be made shareable, a `Ractor::Error` exception will be raised. -## Language changes to isolate unshareable objects between Ractors +## Language changes to limit sharing between Ractors -To isolate unshareable objects between Ractors, we introduced additional language semantics on multi-Ractor Ruby programs. +To isolate unshareable objects across ractors, we introduced additional language semantics for multi-ractor Ruby programs. -Note that without using Ractors, these additional semantics are not needed (100% compatible with Ruby 2). +Note that when not using ractors, these additional semantics are not needed (100% compatible with Ruby 2). ### Global variables @@ -369,11 +362,11 @@ rescue Ractor::RemoteError => e end ``` -Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are Ractor-local. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. +Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are local to each ractor. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. ### Instance variables of shareable objects -Instance variables of classes/modules can be accessed from non-main Ractors only if their values are shareable objects. +Instance variables of classes/modules can be accessed from non-main ractors only if their values are shareable objects. ```ruby class C @@ -514,15 +507,13 @@ The `shareable_constant_value` directive accepts the following modes (descriptio * experimental_everything: replaced to `CONST = Ractor.make_shareable(expr)`. * experimental_copy: replaced to `CONST = Ractor.make_shareable(expr, copy: true)`. -Except for the `none` mode (default), it is guaranteed that the constants in the file refer only to shareable objects. +Except for the `none` mode (default), it is guaranteed that these constants refer only to shareable objects. -See [doc/syntax/comments.rdoc](syntax/comments.rdoc) for more details. +See [syntax/comments.rdoc](../syntax/comments.rdoc) for more details. ### Shareable procs -Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the Proc's block is isolated -from its outer environment, so it cannot access locals from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize -the value to a different shareable object. +Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the proc's block is isolated from its outer environment, so it cannot access variables from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize the value to a different shareable object. ```ruby p = Ractor.shareable_proc { p self } @@ -538,10 +529,7 @@ rescue Ractor::IsolationError end ``` -In order to dynamically define a method with `define_method` that can be used from different ractors, you must -define it with a shareable proc. Alternatively, you can use `class_eval` or `module_eval` with a String. Even though -the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the -method. +In order to dynamically define a method with `Module#define_method` that can be used from different ractors, you must define it with a shareable proc. Alternatively, you can use `Module#class_eval` or `Module#module_eval` with a String. Even though the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the method. ```ruby class A @@ -555,7 +543,7 @@ Ractor.new do end.join ``` -This isolation must be done to prevent the method from accessing captured outer variables across Ractors. +This isolation must be done to prevent the method from accessing and assigning captured outer variables across ractors. ### Ractor-local storage From 2d69facd2093ed9e51a6e10f8234c4fda07ef6f1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 21 Dec 2025 13:05:38 -0600 Subject: [PATCH 2100/2435] [DOC] Tweaks for Object#<=> --- object.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/object.c b/object.c index 93c8a483cfe26c..aaa3326ebf6df5 100644 --- a/object.c +++ b/object.c @@ -1766,21 +1766,33 @@ rb_obj_not_match(VALUE obj1, VALUE obj2) /* * call-seq: - * obj <=> other -> 0 or nil + * self <=> other -> 0 or nil * - * Returns 0 if +obj+ and +other+ are the same object - * or obj == other, otherwise nil. + * Compares +self+ and +other+. + * + * Returns: + * + * - +0+, if +self+ and +other+ are the same object, + * or if self == other. + * - +nil+, otherwise. + * + * Examples: + * + * o = Object.new + * o <=> o # => 0 + * o <=> o.dup # => nil + * + * A class that includes module Comparable + * should override this method by defining an instance method that: * - * The #<=> is used by various methods to compare objects, for example - * Enumerable#sort, Enumerable#max etc. + * - Take one argument, +other+. + * - Returns: * - * Your implementation of #<=> should return one of the following values: -1, 0, - * 1 or nil. -1 means self is smaller than other. 0 means self is equal to other. - * 1 means self is bigger than other. Nil means the two values could not be - * compared. + * - +-1+, if +self+ is less than +other+. + * - +0+, if +self+ is equal to +other+. + * - +1+, if +self+ is greater than +other+. + * - +nil+, if the two values are incommensurate. * - * When you define #<=>, you can include Comparable to gain the - * methods #<=, #<, #==, #>=, #> and #between?. */ static VALUE rb_obj_cmp(VALUE obj1, VALUE obj2) From ab4a0b4e0ec30fe7567fe6c9ef04d8ff851f256f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 21 Dec 2025 15:40:24 +0000 Subject: [PATCH 2101/2435] [DOC] Doc for File::Stat<=> --- file.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/file.c b/file.c index 0fdccb7d149be4..89294ea009e7b3 100644 --- a/file.c +++ b/file.c @@ -605,17 +605,31 @@ statx_mtimespec(const rb_io_stat_data *st) /* * call-seq: - * stat <=> other_stat -> -1, 0, 1, nil + * self <=> other -> -1, 0, 1, or nil * - * Compares File::Stat objects by comparing their respective modification - * times. + * Compares +self+ and +other+, by comparing their modification times; + * that is, by comparing self.mtime and other.mtime. * - * +nil+ is returned if +other_stat+ is not a File::Stat object + * Returns: * - * f1 = File.new("f1", "w") - * sleep 1 - * f2 = File.new("f2", "w") - * f1.stat <=> f2.stat #=> -1 + * - +-1+, if self.mtime is earlier. + * - +0+, if the two values are equal. + * - +1+, if self.mtime is later. + * - +nil+, if +other+ is not a File::Stat object. + * + * Examples: + * + * stat0 = File.stat('README.md') + * stat1 = File.stat('NEWS.md') + * stat0.mtime # => 2025-12-20 15:33:05.6972341 -0600 + * stat1.mtime # => 2025-12-20 16:02:08.2672945 -0600 + * stat0 <=> stat1 # => -1 + * stat0 <=> stat0.dup # => 0 + * stat1 <=> stat0 # => 1 + * stat0 <=> :foo # => nil + * + * \Class \File::Stat includes module Comparable, + * each of whose methods uses File::Stat#<=> for comparison. */ static VALUE From 76441060f230b8a797c1792e71dcce308ca3cf43 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 20:38:34 -0500 Subject: [PATCH 2102/2435] [DOC] Update call-seq for Method#<< --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index 8a7a6ba8630822..2fb9781b9f8727 100644 --- a/proc.c +++ b/proc.c @@ -4048,7 +4048,7 @@ rb_proc_compose_to_right(VALUE self, VALUE g) /* * call-seq: - * meth << g -> a_proc + * self << g -> a_proc * * Returns a proc that is the composition of this method and the given g. * The returned proc takes a variable number of arguments, calls g with them From 759187b56043c3cda2887e3445f19a35d55743cb Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 20:45:58 -0500 Subject: [PATCH 2103/2435] [DOC] Improve docs for Method#<< --- proc.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/proc.c b/proc.c index 2fb9781b9f8727..11591f6d8aefa3 100644 --- a/proc.c +++ b/proc.c @@ -4050,17 +4050,16 @@ rb_proc_compose_to_right(VALUE self, VALUE g) * call-seq: * self << g -> a_proc * - * Returns a proc that is the composition of this method and the given g. - * The returned proc takes a variable number of arguments, calls g with them - * then calls this method with the result. + * Returns a proc that is the composition of the given +g+ and this method. * - * def f(x) - * x * x - * end + * The returned proc takes a variable number of arguments. It first calls +g+ + * with the arguments, then calls +self+ with the return value of +g+. + * + * def f(ary) = ary << 'in f' * * f = self.method(:f) - * g = proc {|x| x + x } - * p (f << g).call(2) #=> 16 + * g = proc { |ary| ary << 'in proc' } + * (f << g).call([]) # => ["in proc", "in f"] */ static VALUE rb_method_compose_to_left(VALUE self, VALUE g) From 9f8231b7f8a7c625bbfdbfa0bfb838cd0ab1d941 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 20:48:05 -0500 Subject: [PATCH 2104/2435] [DOC] Update call-seq for Method#>> --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index 11591f6d8aefa3..3d0be507a16f41 100644 --- a/proc.c +++ b/proc.c @@ -4071,7 +4071,7 @@ rb_method_compose_to_left(VALUE self, VALUE g) /* * call-seq: - * meth >> g -> a_proc + * self >> g -> a_proc * * Returns a proc that is the composition of this method and the given g. * The returned proc takes a variable number of arguments, calls this method From cfb324e9d12d0d40a8f9052b97a860737b78224f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 19 Dec 2025 21:38:25 -0500 Subject: [PATCH 2105/2435] [DOC] Improve docs for Method#>> --- proc.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/proc.c b/proc.c index 3d0be507a16f41..441d2523233fa8 100644 --- a/proc.c +++ b/proc.c @@ -4073,17 +4073,16 @@ rb_method_compose_to_left(VALUE self, VALUE g) * call-seq: * self >> g -> a_proc * - * Returns a proc that is the composition of this method and the given g. - * The returned proc takes a variable number of arguments, calls this method - * with them then calls g with the result. + * Returns a proc that is the composition of this method and the given +g+. * - * def f(x) - * x * x - * end + * The returned proc takes a variable number of arguments. It first calls +self+ + * with the arguments, then calls +g+ with the return value of +self+. + * + * def f(ary) = ary << 'in f' * * f = self.method(:f) - * g = proc {|x| x + x } - * p (f >> g).call(2) #=> 8 + * g = proc { |ary| ary << 'in proc' } + * (f >> g).call([]) # => ["in f", "in proc"] */ static VALUE rb_method_compose_to_right(VALUE self, VALUE g) From 87e78e6f8a7cf88bd3f8d3df2fbcfdb9c53a5294 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 22 Dec 2025 14:39:41 +0900 Subject: [PATCH 2106/2435] [DOC] Add multiline condition code example that was already possible To avoid the misconception that previously conditional code had to be written on a single line. --- NEWS.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index da8c494b4b3ed1..49747a703e3077 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ Note that each entry is kept to a minimum, see links for details. * Logical binary operators (`||`, `&&`, `and` and `or`) at the beginning of a line continue the previous line, like fluent dot. - The following two code examples are equal: + The following code examples are equal: ```ruby if condition1 @@ -21,12 +21,21 @@ Note that each entry is kept to a minimum, see links for details. end ``` + Previously: + ```ruby if condition1 && condition2 ... end ``` + ```ruby + if condition1 && + condition2 + ... + end + ``` + [[Feature #20925]] ## Core classes updates From 2191768980f61a0610acf4cfef558d5c6635e3c8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 22 Dec 2025 14:58:51 +0900 Subject: [PATCH 2107/2435] [DOC] Refine packed_data.rdoc * Escape unexpected links * Remove unnecessary path name from in-file reference --- doc/language/packed_data.rdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index f7cb4dbf74d99b..088e6d86536db7 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -151,7 +151,7 @@ Any directive may be followed by either of these modifiers: [65, 66].pack('C*') # => "AB" 'AB'.unpack('C*') # => [65, 66] -- Integer +count+ - The directive is to be applied +count+ times: +- \Integer +count+ - The directive is to be applied +count+ times: [65, 66].pack('C2') # => "AB" [65, 66].pack('C3') # Raises ArgumentError. @@ -159,7 +159,7 @@ Any directive may be followed by either of these modifiers: 'AB'.unpack('C3') # => [65, 66, nil] Note: Directives in %w[A a Z m] use +count+ differently; - see {String Directives}[rdoc-ref:language/packed_data.rdoc@String+Directives]. + see {\String Directives}[rdoc-ref:@String+Directives]. If elements don't fit the provided directive, only least significant bits are encoded: From 5272f2bc95770bf483795532ac807884f8932c23 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 22 Dec 2025 12:00:09 +0000 Subject: [PATCH 2108/2435] Bump RDoc to 7.0.2 (#15691) --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 49747a703e3077..23a433c0c4e9d6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -286,7 +286,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 7.0.1 +* rdoc 7.0.2 * win32ole 1.9.2 * irb 1.16.0 * reline 0.6.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index dd28b2bdfe2e5f..0b63393506cbe1 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 7.0.1 https://github.com/ruby/rdoc +rdoc 7.0.2 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From f98bbb74740d80ad865fe294f8c8035ec0a9bb10 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 22 Dec 2025 08:12:33 -0600 Subject: [PATCH 2109/2435] [DOC] Doc for Rational#<=> --- rational.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/rational.c b/rational.c index c172f06d535e0f..5463395459c027 100644 --- a/rational.c +++ b/rational.c @@ -1077,20 +1077,30 @@ rb_rational_pow(VALUE self, VALUE other) /* * call-seq: - * rational <=> numeric -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil * - * Returns -1, 0, or +1 depending on whether +rational+ is - * less than, equal to, or greater than +numeric+. + * Compares +self+ and +other+. * - * +nil+ is returned if the two values are incomparable. + * Returns: * - * Rational(2, 3) <=> Rational(2, 3) #=> 0 - * Rational(5) <=> 5 #=> 0 - * Rational(2, 3) <=> Rational(1, 3) #=> 1 - * Rational(1, 3) <=> 1 #=> -1 - * Rational(1, 3) <=> 0.3 #=> 1 + * - +-1+, if +self+ is less than +other+. + * - +0+, if the two values are the same. + * - +1+, if +self+ is greater than +other+. + * - +nil+, if the two values are incomparable. + * + * Examples: + * + * Rational(2, 3) <=> Rational(4, 3) # => -1 + * Rational(2, 1) <=> Rational(2, 1) # => 0 + * Rational(2, 1) <=> 2 # => 0 + * Rational(2, 1) <=> 2.0 # => 0 + * Rational(2, 1) <=> Complex(2, 0) # => 0 + * Rational(4, 3) <=> Rational(2, 3) # => 1 + * Rational(4, 3) <=> :foo # => nil + * + * \Class \Rational includes module Comparable, + * each of whose methods uses Rational#<=> for comparison. * - * Rational(1, 3) <=> "0.3" #=> nil */ VALUE rb_rational_cmp(VALUE self, VALUE other) From 6a2cd630d5d4188f2d6d94a136821af72774e7ba Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 18 Dec 2025 18:23:30 -0500 Subject: [PATCH 2110/2435] [DOC] Improve call-seq of Proc#call --- proc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proc.c b/proc.c index 441d2523233fa8..637a30e0c64885 100644 --- a/proc.c +++ b/proc.c @@ -1068,13 +1068,12 @@ f_lambda(VALUE _) * Document-method: Proc#yield * * call-seq: - * prc.call(params,...) -> obj - * prc[params,...] -> obj - * prc.(params,...) -> obj - * prc.yield(params,...) -> obj + * call(...) -> obj + * self[...] -> obj + * yield(...) -> obj * - * Invokes the block, setting the block's parameters to the values in - * params using something close to method calling semantics. + * Invokes the block, setting the block's parameters to the arguments + * using something close to method calling semantics. * Returns the value of the last expression evaluated in the block. * * a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } From 481f16f3f1298c8962bdbc97f6c1eff9df90002a Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 22 Dec 2025 13:22:01 -0500 Subject: [PATCH 2111/2435] [DOC] Improve ractor class docs (grammar, code examples) (#15686) --- ractor.rb | 197 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 84 deletions(-) diff --git a/ractor.rb b/ractor.rb index 0002eece2ce700..6679ad15886d88 100644 --- a/ractor.rb +++ b/ractor.rb @@ -1,4 +1,4 @@ -# Ractor.new makes a new \Ractor, which can run in parallel. +# Ractor.new creates a new \Ractor, which can run in parallel with other ractors. # # # The simplest ractor # r = Ractor.new {puts "I am in Ractor!"} @@ -9,7 +9,7 @@ # concerns such as data-races and race-conditions are not possible. The other benefit is parallelism. # # To achieve this, object sharing is limited across ractors. -# For example, unlike in threads, ractors can't access all the objects available in other ractors. Even objects normally +# Unlike in threads, ractors can't access all the objects available in other ractors. For example, objects normally # available through variables in the outer scope are prohibited from being used across ractors. # # a = 1 @@ -21,16 +21,15 @@ # a = 1 # r = Ractor.new(a) { |a1| puts "I am in Ractor! a=#{a1}"} # -# On CRuby (the default implementation), Global Virtual Machine Lock (GVL) is held per ractor, so -# ractors can perform in parallel without locking each other. This is unlike the situation with threads -# on CRuby. +# On CRuby (the default implementation), the Global Virtual Machine Lock (GVL) is held per ractor, so +# ractors can run in parallel. This is unlike the situation with threads on CRuby. # # Instead of accessing shared state, objects should be passed to and from ractors by # sending and receiving them as messages. # # a = 1 # r = Ractor.new do -# a_in_ractor = receive # receive blocks until somebody passes a message +# a_in_ractor = receive # receive blocks the Thread until our default port gets sent a message # puts "I am in Ractor! a=#{a_in_ractor}" # end # r.send(a) # pass it @@ -42,14 +41,14 @@ # # == Shareable and unshareable objects # -# When an object is sent to and from a ractor, it's important to understand whether the +# When an object is sent to a ractor, it's important to understand whether the # object is shareable or unshareable. Most Ruby objects are unshareable objects. Even # frozen objects can be unshareable if they contain (through their instance variables) unfrozen # objects. # # Shareable objects are those which can be used by several ractors at once without compromising # thread-safety, for example numbers, +true+ and +false+. Ractor.shareable? allows you to check this, -# and Ractor.make_shareable tries to make the object shareable if it's not already, and gives an error +# and Ractor.make_shareable tries to make the object shareable if it's not already and gives an error # if it can't do it. # # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are shareable @@ -67,7 +66,7 @@ # # When a shareable object is sent via #send, no additional processing occurs # on it and it becomes usable by both ractors. When an unshareable object is sent, it can be -# either _copied_ or _moved_. The first is the default, and it copies the object fully by +# either _copied_ or _moved_. Copying is the default, and it copies the object fully by # deep cloning (Object#clone) the non-shareable parts of its structure. # # data = ['foo'.dup, 'bar'.freeze] @@ -108,12 +107,12 @@ # Outside: moved? true # test.rb:9:in `method_missing': can not send any methods to a moved object (Ractor::MovedError) # -# Notice that even +inspect+ (and more basic methods like __id__) is inaccessible +# Notice that even +inspect+ and more basic methods like __id__ are inaccessible # on a moved object. # -# +Class+ and +Module+ objects are shareable so the class/module definitions are shared between ractors. -# \Ractor objects are also shareable. All operations on shareable objects are thread-safe, so the thread-safety property -# will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them. +# +Class+ and +Module+ objects are shareable and their class/module definitions are shared between ractors. +# \Ractor objects are also shareable. All operations on shareable objects are thread-safe across ractors. +# Defining mutable, shareable objects in Ruby is not possible, but C extensions can introduce them. # # It is prohibited to access (get) instance variables of shareable objects in other ractors if the values of the # variables aren't shareable. This can occur because modules/classes are shareable, but they can have @@ -178,15 +177,16 @@ # # == Note on code examples # -# In the examples below, sometimes we use the following method to wait for ractors that -# are not currently blocked to finish (or to make progress). +# In the examples below, sometimes we use the following method to wait for ractors +# to make progress or finish. # # def wait # sleep(0.1) # end # -# It is **only for demonstration purposes** and shouldn't be used in a real code. -# Most of the time, #join is used to wait for ractors to finish. +# This is **only for demonstration purposes** and shouldn't be used in a real code. +# Most of the time, #join is used to wait for ractors to finish and Ractor.receive is used +# to wait for messages. # # == Reference # @@ -197,9 +197,9 @@ class Ractor # call-seq: # Ractor.new(*args, name: nil) {|*args| block } -> ractor # - # Create a new \Ractor with args and a block. + # Creates a new \Ractor with args and a block. # - # The given block (Proc) will be isolated (can't access any outer variables). +self+ + # The given block (Proc) is isolated (can't access any outer variables). +self+ # inside the block will refer to the current \Ractor. # # r = Ractor.new { puts "Hi, I am #{self.inspect}" } @@ -247,7 +247,7 @@ def self.current } end - # Returns the number of Ractors currently running or blocking (waiting). + # Returns the number of ractors currently running or blocking (waiting). # # Ractor.count #=> 1 # r = Ractor.new(name: 'example') { Ractor.receive } @@ -263,9 +263,48 @@ def self.count # # call-seq: - # Ractor.select(*ports) -> [...] + # Ractor.select(*ractors_or_ports) -> [ractor or port, obj] + # + # Blocks the current Thread until one of the given ports has received a message. Returns an + # array of two elements where the first element is the Port and the second is the received object. + # This method can also accept Ractor objects themselves, and in that case will wait until one + # has terminated and return a two-element array where the first element is the ractor and the + # second is its termination value. + # + # p1, p2 = Ractor::Port.new, Ractor::Port.new + # ps = [p1, p2] + # rs = 2.times.map do |i| + # Ractor.new(ps.shift, i) do |p, i| + # sleep rand(0.99) + # p.send("r#{i}") + # sleep rand(0.99) + # "r#{i} done" + # end + # end + # + # waiting_on = [p1, p2, *rs] + # until waiting_on.empty? + # received_on, obj = Ractor.select(*waiting_on) + # waiting_on.delete(received_on) + # puts obj + # end + # + # # r0 + # # r1 + # # r1 done + # # r0 done + # + # The following example is almost equivalent to ractors.map(&:value) except the thread + # is unblocked when any of the ractors has terminated as opposed to waiting for their termination in + # the array element order. + # + # values = [] + # until ractors.empty? + # r, val = Ractor.select(*ractors) + # ractors.delete(r) + # values << val + # end # - # TBD def self.select(*ports) raise ArgumentError, 'specify at least one Ractor::Port or Ractor' if ports.empty? @@ -306,7 +345,7 @@ def self.select(*ports) # call-seq: # Ractor.receive -> obj # - # Receive a message from the default port. + # Receives a message from the current ractor's default port. def self.receive Ractor.current.default_port.receive end @@ -323,9 +362,9 @@ class << self # # call-seq: - # ractor.send(msg) -> self + # ractor.send(msg, move: false) -> self # - # It is equivalent to default_port.send(msg) + # This is equivalent to Port#send to the ractor's #default_port. def send(...) default_port.send(...) self @@ -344,22 +383,22 @@ def inspect alias to_s inspect - # The name set in Ractor.new, or +nil+. + # Returns the name set in Ractor.new, or +nil+. def name __builtin_cexpr! %q{RACTOR_PTR(self)->name} end class RemoteError - # The Ractor an uncaught exception is raised in. + # The Ractor in which the uncaught exception was raised. attr_reader :ractor end # # call-seq: - # Ractor.current.close -> true | false + # ractor.close -> true | false # - # Closes default_port. Closing port is allowed only by the ractor which creates this port. - # So this close method also allowed by the current Ractor. + # Closes the default port. Closing a port is allowed only by the ractor which created the port. + # Therefore, the receiver must be the current ractor. # def close default_port.close @@ -371,7 +410,7 @@ def close # # Checks if the object is shareable by ractors. # - # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are shareable + # Ractor.shareable?(1) #=> true -- numbers are shareable # Ractor.shareable?('foo') #=> false, unless the string is frozen due to # frozen_string_literal: true # Ractor.shareable?('foo'.freeze) #=> true # @@ -386,7 +425,7 @@ def self.shareable? obj # call-seq: # Ractor.make_shareable(obj, copy: false) -> shareable_obj # - # Make +obj+ shareable between ractors. + # Makes +obj+ shareable between ractors. # # +obj+ and all the objects it refers to will be frozen, unless they are # already shareable. @@ -429,8 +468,8 @@ def self.make_shareable obj, copy: false end end - # get a value from ractor-local storage for current Ractor - # Obsolete and use Ractor.[] instead. + # Gets a value from ractor-local storage for the current Ractor. + # Obsolete, use Ractor.[] instead. def [](sym) if (self != Ractor.current) raise RuntimeError, "Cannot get ractor local storage for non-current ractor" @@ -438,8 +477,8 @@ def [](sym) Primitive.ractor_local_value(sym) end - # set a value in ractor-local storage for current Ractor - # Obsolete and use Ractor.[]= instead. + # Sets a value in ractor-local storage for the current Ractor. + # Obsolete, use Ractor.[]= instead. def []=(sym, val) if (self != Ractor.current) raise RuntimeError, "Cannot set ractor local storage for non-current ractor" @@ -447,12 +486,12 @@ def []=(sym, val) Primitive.ractor_local_value_set(sym, val) end - # get a value from ractor-local storage of current Ractor + # Gets a value from ractor-local storage for the current Ractor. def self.[](sym) Primitive.ractor_local_value(sym) end - # set a value in ractor-local storage of current Ractor + # Sets a value in ractor-local storage for the current Ractor. def self.[]=(sym, val) Primitive.ractor_local_value_set(sym, val) end @@ -460,8 +499,8 @@ def self.[]=(sym, val) # call-seq: # Ractor.store_if_absent(key){ init_block } # - # If the corresponding ractor-local value is not set, yield a value with - # init_block and store the value in a thread-safe manner. + # If the corresponding ractor-local value is not set, yields a value with + # init_block and stores the value in a thread-safe manner. # This method returns the stored value. # # (1..10).map{ @@ -476,14 +515,14 @@ def self.store_if_absent(sym) Primitive.ractor_local_value_store_if_absent(sym) end - # returns main ractor + # Returns the main ractor. def self.main __builtin_cexpr! %q{ rb_ractor_self(GET_VM()->ractor.main_ractor); } end - # return true if the current ractor is main ractor + # Returns true if the current ractor is the main ractor. def self.main? __builtin_cexpr! %q{ RBOOL(GET_VM()->ractor.main_ractor == rb_ec_ractor_ptr(ec)) @@ -522,7 +561,7 @@ def require feature # :nodoc: -- otherwise RDoc outputs it as a class method # call-seq: # ractor.default_port -> port object # - # return default port of the Ractor. + # Returns the default port of the Ractor. # def default_port __builtin_cexpr! %q{ @@ -534,14 +573,14 @@ def default_port # call-seq: # ractor.join -> self # - # Wait for the termination of the Ractor. - # If the Ractor was aborted (terminated with an exception), - # Ractor#value is called to raise an exception. + # Waits for the termination of the Ractor. + # If the Ractor was aborted (terminated by an unhandled exception), + # the exception is raised in the current ractor. # # Ractor.new{}.join #=> ractor # # Ractor.new{ raise "foo" }.join - # #=> raise an exception "foo (RuntimeError)" + # #=> raises the exception "foo (RuntimeError)" # def join port = Port.new @@ -560,9 +599,9 @@ def join # call-seq: # ractor.value -> obj # - # Waits for +ractor+ to complete, using #join, and return its value or raise - # the exception which terminated the Ractor. The value will not be copied even - # if it is unshareable object. Therefore at most 1 Ractor can get a value. + # Waits for +ractor+ to complete and returns its value or raises the exception + # which terminated the Ractor. The termination value will be moved to the calling + # Ractor. Therefore, at most 1 Ractor can receive another ractor's termination value. # # r = Ractor.new{ [1, 2] } # r.value #=> [1, 2] (unshareable object) @@ -578,10 +617,11 @@ def value # call-seq: # ractor.monitor(port) -> self # - # Add another ractor's port to the monitored list of the receiver. If +self+ terminates, - # the port is sent a Symbol object. - # :exited will be sent if the ractor terminated without an exception. - # :aborted will be sent if the ractor terminated with an exception. + # Registers the port as a monitoring port for this ractor. When the ractor terminates, + # the port receives a Symbol object. + # + # * +:exited+ is sent if the ractor terminates without an unhandled exception. + # * +:aborted+ is sent if the ractor terminates by an unhandled exception. # # r = Ractor.new{ some_task() } # r.monitor(port = Ractor::Port.new) @@ -589,7 +629,7 @@ def value # # r = Ractor.new{ raise "foo" } # r.monitor(port = Ractor::Port.new) - # port.receive #=> :aborted and r is terminated with an exception "foo" + # port.receive #=> :aborted and r is terminated by the RuntimeError "foo" # def monitor port __builtin_ractor_monitor(port) @@ -599,7 +639,7 @@ def monitor port # call-seq: # ractor.unmonitor(port) -> self # - # Remove the given port from the ractor's monitored list. + # Unregisters the port from the monitoring ports for this ractor. # def unmonitor port __builtin_ractor_unmonitor(port) @@ -613,13 +653,13 @@ def unmonitor port # in the Proc will be replaced with the value passed via the `self:` keyword, # or +nil+ if not given. # - # In a shareable Proc, you can not access any outer variables. + # In a shareable Proc, access to any outer variables if prohibited. # # a = 42 # Ractor.shareable_proc{ p a } # #=> can not isolate a Proc because it accesses outer variables (a). (ArgumentError) # - # The `self` should be a shareable object + # The value of `self` in the Proc must be a shareable object. # # Ractor.shareable_proc(self: self){} # #=> self should be shareable: main (Ractor::IsolationError) @@ -634,9 +674,9 @@ def self.shareable_proc self: nil # # call-seq: - # Ractor.shareable_lambda{} -> shareable lambda + # Ractor.shareable_lambda(self: nil){} -> shareable lambda # - # Same as Ractor.shareable_proc, but returns a lambda. + # Same as Ractor.shareable_proc, but returns a lambda Proc. # def self.shareable_lambda self: nil Primitive.attr! :use_block @@ -652,7 +692,8 @@ class Port # call-seq: # port.receive -> msg # - # Receive a message from the port (which was sent there by Port#send). + # Receives a message from the port (which was sent there by Port#send). Only the ractor + # that created the port can receive messages this way. # # port = Ractor::Port.new # r = Ractor.new port do |port| @@ -664,7 +705,7 @@ class Port # r.join # # This will print: "Received: message1" # - # The method blocks if the message queue is empty. + # The method blocks the current Thread if the message queue is empty. # # port = Ractor::Port.new # r = Ractor.new port do |port| @@ -690,8 +731,8 @@ class Port # Still received only one # Received: message2 # - # If the port is closed, the method raises Ractor::ClosedError - # if there are no more messages in the message queue: + # If the port is closed and there are no more messages in the message queue, + # the method raises Ractor::ClosedError. # # port = Ractor::Port.new # port.close @@ -707,7 +748,7 @@ def receive # call-seq: # port.send(msg, move: false) -> self # - # Send a message to a port to be accepted by port.receive. + # Sends a message to the port to be accepted by port.receive. # # port = Ractor::Port.new # r = Ractor.new(port) do |port| @@ -717,7 +758,7 @@ def receive # puts "Received #{value}" # # Prints: "Received: message" # - # The method is non-blocking (will return immediately even if the ractor is not ready + # The method is non-blocking (it will return immediately even if the ractor that created the port is not ready # to receive anything): # # port = Ractor::Port.new @@ -727,7 +768,7 @@ def receive # # Prints: "Sent successfully" immediately # end # - # An attempt to send to a port which already closed its execution will raise Ractor::ClosedError. + # An attempt to send to a closed port will raise Ractor::ClosedError. # # r = Ractor.new {Ractor::Port.new} # r.join @@ -738,7 +779,7 @@ def receive # # If the +obj+ is unshareable, by default it will be copied into the receiving ractor by deep cloning. # - # If the object is shareable, it only send a reference to the object without cloning. + # If the object is shareable, a reference to the object will be sent to the receiving ractor. # def send obj, move: false __builtin_cexpr! %q{ @@ -752,22 +793,10 @@ def send obj, move: false # call-seq: # port.close # - # Close the port. On the closed port, sending is prohibited. - # Receiving is also not allowed if there is no sent messages arrived before closing. - # - # port = Ractor::Port.new - # Ractor.new port do |port| - # port.send 1 # OK - # port.send 2 # OK - # port.close - # port.send 3 # raise Ractor::ClosedError - # end - # - # port.receive #=> 1 - # port.receive #=> 2 - # port.receive #=> raise Ractor::ClosedError + # Closes the port. Sending to a closed port is prohibited. + # Receiving is also prohibited if there are no messages in its message queue. # - # Now, only a Ractor which creates the port is allowed to close ports. + # Only the Ractor which created the port is allowed to close it. # # port = Ractor::Port.new # Ractor.new port do |port| @@ -784,7 +813,7 @@ def close # call-seq: # port.closed? -> true/false # - # Return the port is closed or not. + # Returns whether or not the port is closed. def closed? __builtin_cexpr! %q{ ractor_port_closed_p(ec, self); From af30e4714ccb0dabff85298ee2305d84103b98d1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 22 Dec 2025 11:24:08 -0500 Subject: [PATCH 2112/2435] [ruby/mmtk] Implement Ruby heap This heap emulates the growth characteristics of the Ruby default GC's heap. By default, the heap grows by 40%, requires at least 20% empty after a GC, and allows at most 65% empty before it shrinks the heap. This is all configurable via the same environment variables the default GC uses (`RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO`, `RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO`, `RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO`, respectively). The Ruby heap can be enabled via the `MMTK_HEAP_MODE=ruby` environment variable. Compared to the dynamic heap in MMTk (which uses the MemBalancer algorithm), the Ruby heap allows the heap to grow more generously, which uses a bit more memory but offers significant performance gains because it runs GC much less frequently. We can see in the benchmarks below that this Ruby heap heap gives faster performance than the dynamic heap in every benchmark, with over 2x faster in many of them. We see that memory is often around 10-20% higher with certain outliers that use significantly more memory like hexapdf and erubi-rails. We can also see that this brings MMTk's Ruby heap much closer in performance to the default GC. Ruby heap benchmark results: -------------- -------------- ---------- --------- bench ruby heap (ms) stddev (%) RSS (MiB) activerecord 233.6 10.7 85.9 chunky-png 457.1 1.1 79.3 erubi-rails 1148.0 3.8 133.3 hexapdf 1570.5 2.4 403.0 liquid-c 42.8 5.3 43.4 liquid-compile 41.3 7.6 52.6 liquid-render 102.8 3.8 55.3 lobsters 651.9 8.0 426.3 mail 106.4 1.8 67.2 psych-load 1552.1 0.8 43.4 railsbench 1707.2 6.0 145.6 rubocop 127.2 15.3 148.8 ruby-lsp 136.6 11.7 113.7 sequel 47.2 5.9 44.4 shipit 1197.5 3.6 301.0 -------------- -------------- ---------- --------- Dynamic heap benchmark results: -------------- ----------------- ---------- --------- bench dynamic heap (ms) stddev (%) RSS (MiB) activerecord 845.3 3.1 76.7 chunky-png 525.9 0.4 38.9 erubi-rails 2694.9 3.4 115.8 hexapdf 2344.8 5.6 164.9 liquid-c 73.7 5.0 40.5 liquid-compile 107.1 6.8 40.3 liquid-render 147.2 1.7 39.5 lobsters 697.6 4.5 342.0 mail 224.6 2.1 64.0 psych-load 4326.7 0.6 37.4 railsbench 3218.0 5.5 124.7 rubocop 203.6 6.1 110.9 ruby-lsp 350.7 3.2 79.0 sequel 121.8 2.5 39.6 shipit 1510.1 3.1 220.8 -------------- ----------------- ---------- --------- Default GC benchmark results: -------------- --------------- ---------- --------- bench default GC (ms) stddev (%) RSS (MiB) activerecord 148.4 0.6 67.9 chunky-png 440.2 0.7 57.0 erubi-rails 722.7 0.3 97.8 hexapdf 1466.2 1.7 254.3 liquid-c 32.5 3.6 42.3 liquid-compile 31.2 1.9 35.4 liquid-render 88.3 0.7 30.8 lobsters 633.6 7.0 305.4 mail 76.6 1.6 53.2 psych-load 1166.2 1.3 29.1 railsbench 1262.9 2.3 114.7 rubocop 105.6 0.8 95.4 ruby-lsp 101.6 1.4 75.4 sequel 27.4 1.2 33.1 shipit 1083.1 1.5 163.4 -------------- --------------- ---------- --------- https://github.com/ruby/mmtk/commit/c0ca29922d --- gc/mmtk/src/api.rs | 64 +++++++++++++++- gc/mmtk/src/collection.rs | 6 ++ gc/mmtk/src/heap/mod.rs | 4 + gc/mmtk/src/heap/ruby_heap_trigger.rs | 104 ++++++++++++++++++++++++++ gc/mmtk/src/lib.rs | 1 + 5 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 gc/mmtk/src/heap/mod.rs create mode 100644 gc/mmtk/src/heap/ruby_heap_trigger.rs diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index b99cbdc939f151..d735a81cac810d 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -4,6 +4,7 @@ use mmtk::util::alloc::BumpPointer; use mmtk::util::alloc::ImmixAllocator; +use mmtk::util::conversions; use mmtk::util::options::PlanSelector; use std::str::FromStr; use std::sync::atomic::Ordering; @@ -13,6 +14,8 @@ use crate::abi::RubyBindingOptions; use crate::abi::RubyUpcalls; use crate::binding; use crate::binding::RubyBinding; +use crate::heap::RubyHeapTriggerConfig; +use crate::heap::RUBY_HEAP_TRIGGER_CONFIG; use crate::mmtk; use crate::utils::default_heap_max; use crate::utils::parse_capacity; @@ -79,6 +82,29 @@ fn mmtk_builder_default_parse_heap_max() -> usize { parse_env_var_with("MMTK_HEAP_MAX", parse_capacity).unwrap_or_else(default_heap_max) } +fn parse_float_env_var(key: &str, default: f64, min: f64, max: f64) -> f64 { + parse_env_var_with(key, |s| { + let mut float = f64::from_str(s).unwrap_or(default); + + if float <= min { + eprintln!( + "{key} has value {float} which must be greater than {min}, using default instead" + ); + float = default; + } + + if float >= max { + eprintln!( + "{key} has value {float} which must be less than {max}, using default instead" + ); + float = default; + } + + Some(float) + }) + .unwrap_or(default) +} + fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCTriggerSelector { let make_fixed = || GCTriggerSelector::FixedHeapSize(heap_max); let make_dynamic = || GCTriggerSelector::DynamicHeapSize(heap_min, heap_max); @@ -86,6 +112,25 @@ fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCT parse_env_var_with("MMTK_HEAP_MODE", |s| match s { "fixed" => Some(make_fixed()), "dynamic" => Some(make_dynamic()), + "ruby" => { + let min_ratio = parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", 0.2, 0.0, 1.0); + let goal_ratio = + parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", 0.4, min_ratio, 1.0); + let max_ratio = + parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO", 0.65, goal_ratio, 1.0); + + crate::heap::RUBY_HEAP_TRIGGER_CONFIG + .set(RubyHeapTriggerConfig { + min_heap_pages: conversions::bytes_to_pages_up(heap_min), + max_heap_pages: conversions::bytes_to_pages_up(heap_max), + heap_pages_min_ratio: min_ratio, + heap_pages_goal_ratio: goal_ratio, + heap_pages_max_ratio: max_ratio, + }) + .unwrap_or_else(|_| panic!("RUBY_HEAP_TRIGGER_CONFIG is already set")); + + Some(GCTriggerSelector::Delegated) + } _ => None, }) .unwrap_or_else(make_dynamic) @@ -146,7 +191,7 @@ pub unsafe extern "C" fn mmtk_init_binding( crate::set_panic_hook(); - let builder = unsafe { Box::from_raw(builder) }; + let builder: Box = unsafe { Box::from_raw(builder) }; let binding_options = RubyBindingOptions { ractor_check_mode: false, suffix_size: 0, @@ -388,11 +433,12 @@ pub extern "C" fn mmtk_plan() -> *const u8 { pub extern "C" fn mmtk_heap_mode() -> *const u8 { static FIXED_HEAP: &[u8] = b"fixed\0"; static DYNAMIC_HEAP: &[u8] = b"dynamic\0"; + static RUBY_HEAP: &[u8] = b"ruby\0"; match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(_) => FIXED_HEAP.as_ptr(), GCTriggerSelector::DynamicHeapSize(_, _) => DYNAMIC_HEAP.as_ptr(), - _ => panic!("Unknown heap mode"), + GCTriggerSelector::Delegated => RUBY_HEAP.as_ptr(), } } @@ -401,7 +447,12 @@ pub extern "C" fn mmtk_heap_min() -> usize { match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(_) => 0, GCTriggerSelector::DynamicHeapSize(min_size, _) => min_size, - _ => panic!("Unknown heap mode"), + GCTriggerSelector::Delegated => conversions::pages_to_bytes( + RUBY_HEAP_TRIGGER_CONFIG + .get() + .expect("RUBY_HEAP_TRIGGER_CONFIG not set") + .min_heap_pages, + ), } } @@ -410,7 +461,12 @@ pub extern "C" fn mmtk_heap_max() -> usize { match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(max_size) => max_size, GCTriggerSelector::DynamicHeapSize(_, max_size) => max_size, - _ => panic!("Unknown heap mode"), + GCTriggerSelector::Delegated => conversions::pages_to_bytes( + RUBY_HEAP_TRIGGER_CONFIG + .get() + .expect("RUBY_HEAP_TRIGGER_CONFIG not set") + .max_heap_pages, + ), } } diff --git a/gc/mmtk/src/collection.rs b/gc/mmtk/src/collection.rs index 41c508afd9115d..824747b04c4051 100644 --- a/gc/mmtk/src/collection.rs +++ b/gc/mmtk/src/collection.rs @@ -1,9 +1,11 @@ use crate::abi::GCThreadTLS; use crate::api::RubyMutator; +use crate::heap::RubyHeapTrigger; use crate::{mmtk, upcalls, Ruby}; use mmtk::memory_manager; use mmtk::scheduler::*; +use mmtk::util::heap::GCTriggerPolicy; use mmtk::util::{VMMutatorThread, VMThread, VMWorkerThread}; use mmtk::vm::{Collection, GCThreadContext}; use std::sync::atomic::Ordering; @@ -67,6 +69,10 @@ impl Collection for VMCollection { fn vm_live_bytes() -> usize { (upcalls().vm_live_bytes)() } + + fn create_gc_trigger() -> Box> { + Box::new(RubyHeapTrigger::default()) + } } impl VMCollection { diff --git a/gc/mmtk/src/heap/mod.rs b/gc/mmtk/src/heap/mod.rs new file mode 100644 index 00000000000000..6af7c1b2e50682 --- /dev/null +++ b/gc/mmtk/src/heap/mod.rs @@ -0,0 +1,4 @@ +mod ruby_heap_trigger; +pub use ruby_heap_trigger::RubyHeapTrigger; +pub use ruby_heap_trigger::RubyHeapTriggerConfig; +pub use ruby_heap_trigger::RUBY_HEAP_TRIGGER_CONFIG; diff --git a/gc/mmtk/src/heap/ruby_heap_trigger.rs b/gc/mmtk/src/heap/ruby_heap_trigger.rs new file mode 100644 index 00000000000000..9215e2ebb0ec04 --- /dev/null +++ b/gc/mmtk/src/heap/ruby_heap_trigger.rs @@ -0,0 +1,104 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use mmtk::util::heap::GCTriggerPolicy; +use mmtk::util::heap::SpaceStats; +use mmtk::Plan; +use mmtk::MMTK; +use once_cell::sync::OnceCell; + +use crate::Ruby; + +pub static RUBY_HEAP_TRIGGER_CONFIG: OnceCell = OnceCell::new(); + +pub struct RubyHeapTriggerConfig { + /// Min heap size + pub min_heap_pages: usize, + /// Max heap size + pub max_heap_pages: usize, + /// Minimum ratio of empty space after a GC before the heap will grow + pub heap_pages_min_ratio: f64, + /// Ratio the heap will grow by + pub heap_pages_goal_ratio: f64, + /// Maximum ratio of empty space after a GC before the heap will shrink + pub heap_pages_max_ratio: f64, +} + +pub struct RubyHeapTrigger { + /// Target number of heap pages + target_heap_pages: AtomicUsize, +} + +impl GCTriggerPolicy for RubyHeapTrigger { + fn is_gc_required( + &self, + space_full: bool, + space: Option>, + plan: &dyn Plan, + ) -> bool { + // Let the plan decide + plan.collection_required(space_full, space) + } + + fn on_gc_end(&self, mmtk: &'static MMTK) { + if let Some(plan) = mmtk.get_plan().generational() { + if plan.is_current_gc_nursery() { + // Nursery GC + } else { + // Full GC + } + + panic!("TODO: support for generational GC not implemented") + } else { + let used_pages = mmtk.get_plan().get_used_pages(); + + let target_min = + (used_pages as f64 * (1.0 + Self::get_config().heap_pages_min_ratio)) as usize; + let target_max = + (used_pages as f64 * (1.0 + Self::get_config().heap_pages_max_ratio)) as usize; + let new_target = + (((used_pages as f64) * (1.0 + Self::get_config().heap_pages_goal_ratio)) as usize) + .clamp( + Self::get_config().min_heap_pages, + Self::get_config().max_heap_pages, + ); + + if used_pages < target_min || used_pages > target_max { + self.target_heap_pages.store(new_target, Ordering::Relaxed); + } + } + } + + fn is_heap_full(&self, plan: &dyn Plan) -> bool { + plan.get_reserved_pages() > self.target_heap_pages.load(Ordering::Relaxed) + } + + fn get_current_heap_size_in_pages(&self) -> usize { + self.target_heap_pages.load(Ordering::Relaxed) + } + + fn get_max_heap_size_in_pages(&self) -> usize { + Self::get_config().max_heap_pages + } + + fn can_heap_size_grow(&self) -> bool { + self.target_heap_pages.load(Ordering::Relaxed) < Self::get_config().max_heap_pages + } +} + +impl Default for RubyHeapTrigger { + fn default() -> Self { + let min_heap_pages = Self::get_config().min_heap_pages; + + Self { + target_heap_pages: AtomicUsize::new(min_heap_pages), + } + } +} + +impl RubyHeapTrigger { + fn get_config<'b>() -> &'b RubyHeapTriggerConfig { + RUBY_HEAP_TRIGGER_CONFIG + .get() + .expect("Attempt to use RUBY_HEAP_TRIGGER_CONFIG before it is initialized") + } +} diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index 4bcafb597a2bb6..8647793522ed82 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -25,6 +25,7 @@ pub mod active_plan; pub mod api; pub mod binding; pub mod collection; +pub mod heap; pub mod object_model; pub mod reference_glue; pub mod scanning; From e69f41a0a88df1d843f5d94cc4e5e757b96300e0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 22 Dec 2025 16:52:52 -0600 Subject: [PATCH 2113/2435] [DOC] Languages in Examples (#15697) * [DOC] Languages in Examples * Update doc/contributing/documentation_guide.md Co-authored-by: Jeremy Evans * Update doc/contributing/documentation_guide.md Co-authored-by: Jeremy Evans --------- Co-authored-by: Jeremy Evans --- doc/contributing/documentation_guide.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index 8a73543e6c9076..29a1c72b02f0de 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -326,6 +326,16 @@ Alternatives: - Example {source}[https://github.com/ruby/ruby/blob/34d802f32f00df1ac0220b62f72605827c16bad8/doc/contributing/glossary.md?plain=1]. - Corresponding {output}[https://docs.ruby-lang.org/en/master/contributing/glossary_md.html]. +### Languages in Examples + +For symbols and strings in documentation examples: + +- Prefer \English in \English documentation: 'Hello'. +- Prefer Japanese in Japanese documentation: 'こんにちは'. +- If a second language is needed (as, for example, characters with different byte-sizes), + prefer Japanese in \English documentation and \English in Japanese documentation. +- Use other languages examples only as necessary: see String#capitalize. + ## Documenting Classes and Modules The general structure of the class or module documentation should be: From 0b3199a65333a0ea8a3a653b3151674602ad4e84 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 22 Dec 2025 19:08:21 -0500 Subject: [PATCH 2114/2435] [DOC] Fix backticks in docs for Set#add --- set.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/set.c b/set.c index 734a6ecaea2107..469078ee9968af 100644 --- a/set.c +++ b/set.c @@ -697,7 +697,7 @@ set_i_join(int argc, VALUE *argv, VALUE set) * call-seq: * add(obj) -> self * - * Adds the given object to the set and returns self. Use `merge` to + * Adds the given object to the set and returns self. Use Set#merge to * add many elements at once. * * Set[1, 2].add(3) #=> Set[1, 2, 3] From 8eaf6739fda591233b07f3ada7ed4e87845347b9 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 23 Dec 2025 13:00:33 +0900 Subject: [PATCH 2115/2435] [ruby/error_highlight] Bump version https://github.com/ruby/error_highlight/commit/dc2dad6632 --- lib/error_highlight/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb index d7a29c7c1e68db..f0a5376b143b75 100644 --- a/lib/error_highlight/version.rb +++ b/lib/error_highlight/version.rb @@ -1,3 +1,3 @@ module ErrorHighlight - VERSION = "0.7.0" + VERSION = "0.7.1" end From ed1aac5486ce340d39d0266b570aa0d0243dc114 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 23 Dec 2025 04:04:07 +0000 Subject: [PATCH 2116/2435] Update default gems list at 8eaf6739fda591233b07f3ada7ed4e [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 23a433c0c4e9d6..4ee0ece96df5e1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -315,6 +315,7 @@ The following default gems are updated. * digest 3.2.1 * english 0.8.1 * erb 6.0.1 +* error_highlight 0.7.1 * etc 1.4.6 * fcntl 1.3.0 * fileutils 1.8.0 From 1c3ef2719155b56f14bf734c82b26ef0bbaac798 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 23 Dec 2025 13:48:03 +0900 Subject: [PATCH 2117/2435] Merge RubyGems 4.0.3 and Bundler 4.0.3 --- lib/bundler/lazy_specification.rb | 57 ++++++++++----- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- lib/rubygems/psych_tree.rb | 2 +- lib/rubygems/request_set/lockfile.rb | 2 +- lib/rubygems/security/policy.rb | 2 +- lib/rubygems/source.rb | 2 +- spec/bundler/bundler/plugin/events_spec.rb | 12 +++- spec/bundler/bundler/plugin_spec.rb | 8 +++ spec/bundler/bundler/uri_normalizer_spec.rb | 25 +++++++ .../install/gemfile/specific_platform_spec.rb | 69 +++++++++++++++++-- .../realworld/fixtures/tapioca/Gemfile.lock | 2 +- .../realworld/fixtures/warbler/Gemfile.lock | 2 +- spec/bundler/support/windows_tag_group.rb | 1 + .../test_gem_commands_install_command.rb | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 19 files changed, 164 insertions(+), 34 deletions(-) create mode 100644 spec/bundler/bundler/uri_normalizer_spec.rb diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 81ded54797793a..786dbcae658618 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -138,24 +138,16 @@ def materialize_for_installation source.local! if use_exact_resolved_specifications? - materialize(self) do |matching_specs| - choose_compatible(matching_specs) - end - else - materialize([name, version]) do |matching_specs| - target_platform = source.is_a?(Source::Path) ? platform : Bundler.local_platform - - installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, target_platform) - - specification = choose_compatible(installable_candidates, fallback_to_non_installable: false) - return specification unless specification.nil? + spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) } + return spec if spec - if target_platform != platform - installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, platform) - end - - choose_compatible(installable_candidates) + # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant + # In non-frozen mode, return nil to trigger re-resolution and lockfile update + if Bundler.frozen_bundle? + materialize([name, version]) {|specs| resolve_best_platform(specs) } end + else + materialize([name, version]) {|specs| resolve_best_platform(specs) } end end @@ -190,6 +182,39 @@ def use_exact_resolved_specifications? !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? end + # Try platforms in order of preference until finding a compatible spec. + # Used for legacy lockfiles and as a fallback when the exact locked spec + # is incompatible. Falls back to frozen bundle behavior if none match. + def resolve_best_platform(specs) + find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs) + end + + def find_compatible_platform_spec(specs) + candidate_platforms.each do |plat| + candidates = MatchPlatform.select_best_platform_match(specs, plat) + spec = choose_compatible(candidates, fallback_to_non_installable: false) + return spec if spec + end + nil + end + + # Platforms to try in order of preference. Ruby platform is last since it + # requires compilation, but works when precompiled gems are incompatible. + def candidate_platforms + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + [target, platform, Gem::Platform::RUBY].uniq + end + + # In frozen mode, accept any candidate. Will error at install time. + # When target differs from locked platform, prefer locked platform's candidates + # to preserve lockfile integrity. + def frozen_bundle_fallback(specs) + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + fallback_platform = target == platform ? target : platform + candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform) + choose_compatible(candidates) + end + def ruby_platform_materializes_to_ruby_platform? generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index ccb6ecab387818..732b4a05631e84 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.2".freeze + VERSION = "4.0.3".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index a5d6c7fb66a52c..e99176fec0e107 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.2" + VERSION = "4.0.3" end require_relative "rubygems/defaults" diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb index 24857adb9d3c41..8b4c425a3340ba 100644 --- a/lib/rubygems/psych_tree.rb +++ b/lib/rubygems/psych_tree.rb @@ -22,7 +22,7 @@ def visit_Hash(o) def register(target, obj) end - # This is ported over from the yaml_tree in 1.9.3 + # This is ported over from the YAMLTree implementation in Ruby 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index c446b3ae51abd5..da6dd329bc07f2 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -38,7 +38,7 @@ def initialize(message, column, line, path) end ## - # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ + # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 7b86ac57639ea2..128958ab802ab2 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -143,7 +143,7 @@ def check_root(chain, time) end ## - # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and + # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 8b031e27a8d611..f203b7312b1105 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -102,7 +102,7 @@ def update_cache? end ## - # Fetches a specification for the given +name_tuple+. + # Fetches a specification for the given Gem::NameTuple. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher diff --git a/spec/bundler/bundler/plugin/events_spec.rb b/spec/bundler/bundler/plugin/events_spec.rb index 28d70c6fdddde1..77e5fdb74cb5e9 100644 --- a/spec/bundler/bundler/plugin/events_spec.rb +++ b/spec/bundler/bundler/plugin/events_spec.rb @@ -2,7 +2,17 @@ RSpec.describe Bundler::Plugin::Events do context "plugin events" do - before { Bundler::Plugin::Events.send :reset } + before do + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } + Bundler::Plugin::Events.send :reset + end + + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end describe "#define" do it "raises when redefining a constant" do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index fea39250006735..e416772a367240 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -279,6 +279,7 @@ s.write "plugins.rb", code end + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } Bundler::Plugin::Events.send(:reset) Bundler::Plugin::Events.send(:define, :EVENT1, "event-1") Bundler::Plugin::Events.send(:define, :EVENT2, "event-2") @@ -291,6 +292,13 @@ allow(index).to receive(:load_paths).with("foo-plugin").and_return([]) end + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end + let(:code) { <<-RUBY } Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" } RUBY diff --git a/spec/bundler/bundler/uri_normalizer_spec.rb b/spec/bundler/bundler/uri_normalizer_spec.rb new file mode 100644 index 00000000000000..1308e860145929 --- /dev/null +++ b/spec/bundler/bundler/uri_normalizer_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::URINormalizer do + describe ".normalize_suffix" do + context "when trailing_slash is true" do + it "adds a trailing slash when missing" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: true)).to eq("https://example.com/") + end + + it "keeps the trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: true)).to eq("https://example.com/") + end + end + + context "when trailing_slash is false" do + it "removes a trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: false)).to eq("https://example.com") + end + + it "keeps the value unchanged when no trailing slash exists" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: false)).to eq("https://example.com") + end + end + end +end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 39d2700474a766..448800d57817f3 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -157,6 +157,11 @@ end context "when running on a legacy lockfile locked only to ruby" do + # Exercises the legacy lockfile path (use_exact_resolved_specifications? = false) + # because most_specific_locked_platform is ruby, matching the generic platform. + # Key insight: when target (arm64-darwin-22) != platform (ruby), the code tries + # both platforms before falling back, preserving lockfile integrity. + around do |example| build_repo4 do build_gem "nokogiri", "1.3.10" @@ -192,13 +197,69 @@ end it "still installs the generic ruby variant if necessary" do - bundle "install --verbose" - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install" + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") end it "still installs the generic ruby variant if necessary, even in frozen mode" do - bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") + end + end + + context "when platform-specific gem has incompatible required_ruby_version" do + # Key insight: candidate_platforms tries [target, platform, ruby] in order. + # Ruby platform is last since it requires compilation, but works when + # precompiled gems are incompatible with the current Ruby version. + # + # Note: This fix requires the lockfile to include both ruby and platform- + # specific variants (typical after `bundle lock --add-platform`). If the + # lockfile only has platform-specific gems, frozen mode cannot help because + # Bundler.setup would still expect the locked (incompatible) gem. + + # Exercises the exact spec path (use_exact_resolved_specifications? = true) + # because lockfile has platform-specific entry as most_specific_locked_platform + it "falls back to ruby platform in frozen mode when lockfile includes both variants" do + build_repo4 do + build_gem "nokogiri", "1.18.10" + build_gem "nokogiri", "1.18.10" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + # Lockfile has both ruby and platform-specific gem (typical after `bundle lock --add-platform`) + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.10) + nokogiri (1.18.10-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.18.10") + expect(the_bundle).not_to include_gem("nokogiri 1.18.10 x86_64-linux") + end end end diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index d6734a6569491d..2db720a69f4632 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.2 + 4.0.3 diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 012f3fee972811..c37fbbb7eed556 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.2 + 4.0.3 diff --git a/spec/bundler/support/windows_tag_group.rb b/spec/bundler/support/windows_tag_group.rb index 8eb0a749dafbaf..c41c446462c436 100644 --- a/spec/bundler/support/windows_tag_group.rb +++ b/spec/bundler/support/windows_tag_group.rb @@ -184,6 +184,7 @@ module WindowsTagGroup "spec/bundler/resolver/candidate_spec.rb", "spec/bundler/digest_spec.rb", "spec/bundler/fetcher/gem_remote_fetcher_spec.rb", + "spec/bundler/uri_normalizer_spec.rb", ], }.freeze end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index d2ca933a632c7d..72ca9d8262583a 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1610,7 +1610,7 @@ def test_pass_down_the_job_option_to_make gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) if vc_windows? && nmake_found? - refute_includes(gem_make_out, " -j4") + refute_includes(gem_make_out, "-j4") else assert_includes(gem_make_out, "make -j4") end diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 30e29782705f8e..fff9cfe70cb104 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.2 + 4.0.3 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index ca3f816b7f8a4c..70704bfc38896c 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.2 + 4.0.3 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 1e31691b7f734a..669e5492a8a0f0 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.2 + 4.0.3 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index a257f8f8bc5b40..d8f7d77122846d 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.2 + 4.0.3 From 3a92e07a51768908316bbeace92a8fabd56097c2 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 23 Dec 2025 07:11:02 +0000 Subject: [PATCH 2118/2435] Update default gems list at 1c3ef2719155b56f14bf734c82b26e [ci skip] --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4ee0ece96df5e1..3a0a6abb96a461 100644 --- a/NEWS.md +++ b/NEWS.md @@ -308,8 +308,8 @@ The following default gem is added. The following default gems are updated. -* RubyGems 4.0.2 -* bundler 4.0.2 +* RubyGems 4.0.3 +* bundler 4.0.3 * date 3.5.1 * delegate 0.6.1 * digest 3.2.1 From b04e61c3909a5fa63e1235c03d160ed778cae086 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 23 Dec 2025 16:30:58 +0900 Subject: [PATCH 2119/2435] Added release note for RubyGems 4.0.3 --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 3a0a6abb96a461..410b89a6de550e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -371,6 +371,7 @@ see the following links for details. * [4.0.0 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/03/4.0.0-released.html) * [4.0.1 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/09/4.0.1-released.html) * [4.0.2 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/17/4.0.2-released.html) +* [4.0.3 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/23/4.0.3-released.html) ## Supported platforms From d879f9f69691778c6c9ffe4b084172a4546bb46b Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 21 Dec 2025 22:58:08 -0800 Subject: [PATCH 2120/2435] [DOC] Fix example in Ruby::Box documentation --- doc/language/box.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/language/box.md b/doc/language/box.md index abc6af868fc470..f2b59e5b39cafe 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -243,7 +243,7 @@ yay #=> "foo" box = Ruby::Box.new box.require('foo') -box.Foo.say #=> "foo" +box::Foo.say #=> "foo" yay # NoMethodError ``` From 2ed9a7bde26a06cf5eff38e01702af80251d29b3 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Tue, 23 Dec 2025 17:51:32 +0900 Subject: [PATCH 2121/2435] Bundle RBS 3.10.0 (#15701) * Bundle RBS 3.10.0 * Unskip BigDecimal tests --- gems/bundled_gems | 2 +- tool/rbs_skip_tests | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 0b63393506cbe1..3bdfc91219202b 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -18,7 +18,7 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.10.0.pre.2 https://github.com/ruby/rbs badcb9165b52c1b7ccaa6251e4d5bbd78329c5a7 +rbs 3.10.0 https://github.com/ruby/rbs typeprof 0.31.0 https://github.com/ruby/typeprof debug 1.11.1 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index 05ffb4da2771a5..d5139705b3c4d3 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -45,7 +45,3 @@ test_linear_time?(RegexpSingletonTest) test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby - -# BigDecimal v4.0.0 changed behavior -test_divmod(BigDecimalTest) -test_precs(BigDecimalTest) From 3ddf69500ffd76efd28e45d2e101a85fca0d8a61 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 23 Dec 2025 08:52:15 +0000 Subject: [PATCH 2122/2435] Update bundled gems list as of 2025-12-23 --- NEWS.md | 4 ++-- gems/bundled_gems | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 410b89a6de550e..1b7efe4252464d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -353,8 +353,8 @@ The following bundled gems are updated. * net-smtp 0.5.1 * matrix 0.4.3 * prime 0.1.4 -* rbs 3.10.0.pre.2 -* typeprof 0.31.0 +* rbs 3.10.0 +* typeprof 0.31.1 * debug 1.11.1 * base64 0.3.0 * bigdecimal 4.0.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 3bdfc91219202b..495f7afc694e0d 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -19,7 +19,7 @@ net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime rbs 3.10.0 https://github.com/ruby/rbs -typeprof 0.31.0 https://github.com/ruby/typeprof +typeprof 0.31.1 https://github.com/ruby/typeprof debug 1.11.1 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m From 515119541095bcb84cb8d85db644d836eeeeef33 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 20 Dec 2025 21:37:45 +0900 Subject: [PATCH 2123/2435] Fix a fragile test `Dir.mktmpdir` concatenates a random base-36 number separated by "-", so may generate pathnames containing "-j4". --- test/rubygems/test_gem_commands_install_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 72ca9d8262583a..d2ca933a632c7d 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1610,7 +1610,7 @@ def test_pass_down_the_job_option_to_make gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) if vc_windows? && nmake_found? - refute_includes(gem_make_out, "-j4") + refute_includes(gem_make_out, " -j4") else assert_includes(gem_make_out, "make -j4") end From ab565a3e0de7c6e75b31a32d60a0a6f77ff4122f Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 23 Dec 2025 19:41:56 +0900 Subject: [PATCH 2124/2435] Box: split the test for CI timeouts --- test/ruby/test_box.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index b62ac3c544722d..e584d233ca0145 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -780,7 +780,7 @@ def foo_box = Ruby::Box.current end; end - def test_loading_extension_libs_in_main_box + def test_loading_extension_libs_in_main_box_1 pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; @@ -797,6 +797,15 @@ def test_loading_extension_libs_in_main_box require "json" require "psych" require "yaml" + expected = 1 + assert_equal expected, 1 + end; + end + + def test_loading_extension_libs_in_main_box_2 + pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; require "zlib" require "open3" require "ipaddr" From d517e04806616d2384fd2e1e3aa63eea99036669 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 22 Dec 2025 11:51:04 -0500 Subject: [PATCH 2125/2435] [DOC] Combine docs for Method#call aliases RDoc does not parse the documentation for the Method#call aliases, so we should combine the aliases into one documentation. --- proc.c | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/proc.c b/proc.c index 637a30e0c64885..4f775c899228b5 100644 --- a/proc.c +++ b/proc.c @@ -2642,13 +2642,20 @@ method_dup(VALUE self) return clone; } -/* Document-method: Method#=== - * +/* * call-seq: - * method === obj -> result_of_method + * meth.call(args, ...) -> obj + * meth[args, ...] -> obj + * method === obj -> result_of_method + * + * Invokes the meth with the specified arguments, returning the + * method's return value. * - * Invokes the method with +obj+ as the parameter like #call. - * This allows a method object to be the target of a +when+ clause + * m = 12.method("+") + * m.call(3) #=> 15 + * m.call(20) #=> 32 + * + * Using Method#=== allows a method object to be the target of a +when+ clause * in a case statement. * * require 'prime' @@ -2659,32 +2666,6 @@ method_dup(VALUE self) * end */ - -/* Document-method: Method#[] - * - * call-seq: - * meth[args, ...] -> obj - * - * Invokes the meth with the specified arguments, returning the - * method's return value, like #call. - * - * m = 12.method("+") - * m[3] #=> 15 - * m[20] #=> 32 - */ - -/* - * call-seq: - * meth.call(args, ...) -> obj - * - * Invokes the meth with the specified arguments, returning the - * method's return value. - * - * m = 12.method("+") - * m.call(3) #=> 15 - * m.call(20) #=> 32 - */ - static VALUE rb_method_call_pass_called_kw(int argc, const VALUE *argv, VALUE method) { From d7d11090991d217b1eeb36d5dce5102bdba8b277 Mon Sep 17 00:00:00 2001 From: Victor Shepelev Date: Tue, 23 Dec 2025 19:09:41 +0200 Subject: [PATCH 2126/2435] Describe base code layout rules (#15696) * Describe base code layout rules * Enhance optional keyword explanation * Change the logical operators description --- doc/syntax.rdoc | 3 ++ doc/syntax/layout.rdoc | 118 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 doc/syntax/layout.rdoc diff --git a/doc/syntax.rdoc b/doc/syntax.rdoc index cb427b6f0f03a2..a48c83ff152dc2 100644 --- a/doc/syntax.rdoc +++ b/doc/syntax.rdoc @@ -2,6 +2,9 @@ The Ruby syntax is large and is split up into the following sections: +{Code Layout}[rdoc-ref:syntax/layout.rdoc] :: + Breaking code in lines + Literals[rdoc-ref:syntax/literals.rdoc] :: Numbers, Strings, Arrays, Hashes, etc. diff --git a/doc/syntax/layout.rdoc b/doc/syntax/layout.rdoc new file mode 100644 index 00000000000000..f07447587ba333 --- /dev/null +++ b/doc/syntax/layout.rdoc @@ -0,0 +1,118 @@ += Code Layout + +Expressions in Ruby are separated by line breaks: + + x = 1 + y = 2 + z = x + y + +Line breaks also used as logical separators of the headers of some of control structures from their bodies: + + if z > 3 # line break ends the condition and starts the body + puts "more" + end + + while x < 3 # line break ends the condition and starts the body + x += 1 + end + +; can be used as an expressions separator instead of a line break: + + x = 1; y = 2; z = x + y + if z > 3; puts "more"; end + +Traditionally, expressions separated by ; is used only in short scripts and experiments. + +In some control structures, there is an optional keyword that can be used instead of a line break to separate their elements: + + # if, elsif, until and case ... when: 'then' is an optional separator: + + if z > 3 then puts "more" end + + case x + when Numeric then "number" + when String then "string" + else "object" + end + + # while and until: 'do' is an optional separator + while x < 3 do x +=1 end + +Also, line breaks can be skipped in some places where it doesn't create any ambiguity. Note in the example above: no line break needed before +end+, just as no line break needed after +else+. + +== Breaking expressions in lines + +One expression might be split into several lines when each line can be unambiguously identified as "incomplete" without the next one. + +These works: + + x = # incomplete without something after = + 1 + # incomplete without something after + + 2 + + File.read "test.txt", # incomplete without something after , + enconding: "utf-8" + +These would not: + + # unintended interpretation: + x = 1 # already complete expression + + 2 # interpreted as a separate +2 + + # syntax error: + File.read "test.txt" # already complete expression + , encoding: "utf-8" # attempt to parse as a new expression, SyntaxError + +The exceptions to the rule are lines starting with . ("leading dot" style of method calls) or logical operators &&/|| and and/or: + + # OK, interpreted as a chain of calls + File.read('test.txt') + .strip("\n") + .split("\t") + .sort + + # OK, interpreted as a chain of logical operators: + File.empty?('test.txt') + || File.size('test.txt') < 10 + || File.read('test.txt').strip.empty? + +If the expressions is broken into multiple lines in any of the ways described above, comments between separate lines are allowed: + + sum = base_salary + + # see "yearly bonuses section" + yearly_bonus(year) + + # per-employee coefficient is described + # in another module + personal_coeff(employee) + + # We want to short-circuit on empty files + File.empty?('test.txt') + # Or almost empty ones + || File.size('test.txt') < 10 + # Otherwise we check if it is full of spaces + || File.read('test.txt').strip.empty? + +Finally, the code can explicitly tell Ruby that the expression is continued on the next line with \\: + + # Unusual, but works + File.read "test.txt" \ + , encoding: "utf-8" + + # More regular usage (joins the strings on parsing instead + # of concatenating them in runtime, as + would do): + TEXT = "One pretty long line" \ + "one more long line" \ + "one other line of the text" + +The \\ works as a parse time line break escape, so with it, comments can not be inserted between the lines: + + TEXT = "line 1" \ + # here would be line 2: + "line 2" + + # This is interpreted as if there was no line break where \ is, + # i.e. the same as + TEXT = "line 1" # here would be line 2: + "line 2" + + puts TEXT #=> "line 1" From e2cf92eddc5403316b0d449b02ba403a27610d7b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 23 Dec 2025 12:59:41 -0500 Subject: [PATCH 2127/2435] Move special const check to gc.c for rb_gc_impl_object_moved_p --- gc.c | 20 +++++++++++++++----- gc/default/default.c | 14 +++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/gc.c b/gc.c index 104b027cca788f..8e239011e6f295 100644 --- a/gc.c +++ b/gc.c @@ -402,7 +402,7 @@ void rb_vm_update_references(void *ptr); #define RMOVED(obj) ((struct RMoved *)(obj)) #define TYPED_UPDATE_IF_MOVED(_objspace, _type, _thing) do { \ - if (rb_gc_impl_object_moved_p((_objspace), (VALUE)(_thing))) { \ + if (gc_object_moved_p_internal((_objspace), (VALUE)(_thing))) { \ *(_type *)&(_thing) = (_type)gc_location_internal(_objspace, (VALUE)_thing); \ } \ } while (0) @@ -2868,6 +2868,16 @@ gc_mark_machine_stack_location_maybe(VALUE obj, void *data) #endif } +static bool +gc_object_moved_p_internal(void *objspace, VALUE obj) +{ + if (SPECIAL_CONST_P(obj)) { + return false; + } + + return rb_gc_impl_object_moved_p(objspace, obj); +} + static VALUE gc_location_internal(void *objspace, VALUE value) { @@ -3646,7 +3656,7 @@ check_id_table_move(VALUE value, void *data) { void *objspace = (void *)data; - if (rb_gc_impl_object_moved_p(objspace, (VALUE)value)) { + if (gc_object_moved_p_internal(objspace, (VALUE)value)) { return ID_TABLE_REPLACE; } @@ -3690,7 +3700,7 @@ update_id_table(VALUE *value, void *data, int existing) { void *objspace = (void *)data; - if (rb_gc_impl_object_moved_p(objspace, (VALUE)*value)) { + if (gc_object_moved_p_internal(objspace, (VALUE)*value)) { *value = gc_location_internal(objspace, (VALUE)*value); } @@ -3733,11 +3743,11 @@ update_const_tbl_i(VALUE value, void *objspace) { rb_const_entry_t *ce = (rb_const_entry_t *)value; - if (rb_gc_impl_object_moved_p(objspace, ce->value)) { + if (gc_object_moved_p_internal(objspace, ce->value)) { ce->value = gc_location_internal(objspace, ce->value); } - if (rb_gc_impl_object_moved_p(objspace, ce->file)) { + if (gc_object_moved_p_internal(objspace, ce->file)) { ce->file = gc_location_internal(objspace, ce->file); } diff --git a/gc/default/default.c b/gc/default/default.c index be0be4a37335e4..ef100d7dea27dc 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1375,16 +1375,12 @@ check_rvalue_consistency(rb_objspace_t *objspace, const VALUE obj) static inline bool gc_object_moved_p(rb_objspace_t *objspace, VALUE obj) { - if (RB_SPECIAL_CONST_P(obj)) { - return FALSE; - } - else { - int ret; - asan_unpoisoning_object(obj) { - ret = BUILTIN_TYPE(obj) == T_MOVED; - } - return ret; + + bool ret; + asan_unpoisoning_object(obj) { + ret = BUILTIN_TYPE(obj) == T_MOVED; } + return ret; } static inline int From 01d1cbbbec3d40cb2e3f564371beb9dc3729c7ba Mon Sep 17 00:00:00 2001 From: Victor Shepelev Date: Wed, 24 Dec 2025 02:53:08 +0200 Subject: [PATCH 2128/2435] [DOC] Enhance Fiber::Scheduler docs (#15708) --- scheduler.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/scheduler.c b/scheduler.c index 592bdcd1ef45ef..b23ddad41e70cc 100644 --- a/scheduler.c +++ b/scheduler.c @@ -293,13 +293,15 @@ rb_fiber_scheduler_blocking_operation_new(void *(*function)(void *), void *data, * * Hook methods are: * - * * #io_wait, #io_read, #io_write, #io_pread, #io_pwrite, and #io_select, #io_close + * * #io_wait, #io_read, #io_write, #io_pread, #io_pwrite #io_select, and #io_close * * #process_wait * * #kernel_sleep * * #timeout_after * * #address_resolve * * #block and #unblock * * #blocking_operation_wait + * * #fiber_interrupt + * * #yield * * (the list is expanded as Ruby developers make more methods having non-blocking calls) * * When not specified otherwise, the hook implementations are mandatory: if they are not @@ -371,6 +373,9 @@ Init_Fiber_Scheduler(void) rb_define_method(rb_cFiberScheduler, "unblock", rb_fiber_scheduler_unblock, 2); rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler_fiber, -2); rb_define_method(rb_cFiberScheduler, "blocking_operation_wait", rb_fiber_scheduler_blocking_operation_wait, -2); + rb_define_method(rb_cFiberScheduler, "yield", rb_fiber_scheduler_yield, 0); + rb_define_method(rb_cFiberScheduler, "fiber_interrupt", rb_fiber_scheduler_fiber_interrupt, 2); + rb_define_method(rb_cFiberScheduler, "io_close", rb_fiber_scheduler_io_close, 1); #endif } @@ -527,7 +532,7 @@ rb_fiber_scheduler_make_timeout(struct timeval *timeout) * Document-method: Fiber::Scheduler#kernel_sleep * call-seq: kernel_sleep(duration = nil) * - * Invoked by Kernel#sleep and Mutex#sleep and is expected to provide + * Invoked by Kernel#sleep and Thread::Mutex#sleep and is expected to provide * an implementation of sleeping in a non-blocking way. Implementation might * register the current fiber in some list of "which fiber wait until what * moment", call Fiber.yield to pass control, and then in #close resume @@ -586,7 +591,7 @@ rb_fiber_scheduler_yield(VALUE scheduler) * However, as a result of this design, if the +block+ does not invoke any * non-blocking operations, it will be impossible to interrupt it. If you * desire to provide predictable points for timeouts, consider adding - * +sleep(0)+. + * sleep(0). * * If the block is executed successfully, its result will be returned. * @@ -641,7 +646,7 @@ rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags) * Document-method: Fiber::Scheduler#block * call-seq: block(blocker, timeout = nil) * - * Invoked by methods like Thread.join, and by Mutex, to signify that current + * Invoked by methods like Thread.join, and by Thread::Mutex, to signify that current * Fiber is blocked until further notice (e.g. #unblock) or until +timeout+ has * elapsed. * @@ -661,8 +666,8 @@ rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout) * Document-method: Fiber::Scheduler#unblock * call-seq: unblock(blocker, fiber) * - * Invoked to wake up Fiber previously blocked with #block (for example, Mutex#lock - * calls #block and Mutex#unlock calls #unblock). The scheduler should use + * Invoked to wake up Fiber previously blocked with #block (for example, Thread::Mutex#lock + * calls #block and Thread::Mutex#unlock calls #unblock). The scheduler should use * the +fiber+ parameter to understand which fiber is unblocked. * * +blocker+ is what was awaited for, but it is informational only (for debugging @@ -1021,6 +1026,14 @@ rb_fiber_scheduler_io_pwrite_memory(VALUE scheduler, VALUE io, rb_off_t from, co return result; } +/* + * Document-method: Fiber::Scheduler#io_close + * call-seq: io_close(fd) + * + * Invoked by Ruby's core methods to notify scheduler that the IO object is closed. Note that + * the method will receive an integer file descriptor of the closed object, not an object + * itself. + */ VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io) { @@ -1076,7 +1089,8 @@ rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname) * call-seq: blocking_operation_wait(blocking_operation) * * Invoked by Ruby's core methods to run a blocking operation in a non-blocking way. - * The blocking_operation is a Fiber::Scheduler::BlockingOperation that encapsulates the blocking operation. + * The blocking_operation is an opaque object that encapsulates the blocking operation + * and responds to a #call method without any arguments. * * If the scheduler doesn't implement this method, or if the scheduler doesn't execute * the blocking operation, Ruby will fall back to the non-scheduler implementation. @@ -1118,6 +1132,15 @@ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*functi return result; } +/* + * Document-method: Fiber::Scheduler#fiber_interrupt + * call-seq: fiber_interrupt(fiber, exception) + * + * Invoked by Ruby's core methods to notify the scheduler that the blocked fiber should be interrupted + * with an exception. For example, IO#close uses this method to interrupt fibers that are performing + * blocking IO operations. + * + */ VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception) { VALUE arguments[] = { From 2df72c0c1a686052ab00f853bb15bd67dcbfedd4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Dec 2025 13:09:05 +1300 Subject: [PATCH 2129/2435] Fix flaky test. --- test/fiber/scheduler.rb | 2 +- test/fiber/test_scheduler.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 07b15c5ce4b86a..820c46dfb01743 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -497,7 +497,7 @@ def io_write(io, buffer, length, offset) fd = io.fileno str = buffer.get_string __io_ops__ << [:io_write, fd, str] - Fiber.blocking { buffer.write(IO.for_fd(fd), 0, offset) } + Fiber.blocking { buffer.write(io, 0, offset) } end end diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index c20fe86ff4531d..0cbd49dacab5c6 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -287,7 +287,6 @@ def test_post_fork_fiber_blocking end def test_io_write_on_flush - omit "skip this test because it makes CI fragile" begin fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") write_fd = nil From 30d9782c5c07db2d5df44656eb0218616f7b0bb5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 24 Dec 2025 13:26:07 +1300 Subject: [PATCH 2130/2435] Tidy up fiber scheduler tests. --- test/fiber/scheduler.rb | 16 +++++--- test/fiber/test_scheduler.rb | 79 ++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 820c46dfb01743..8f1ce4376b2c29 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -489,15 +489,19 @@ def blocking(&block) end class IOScheduler < Scheduler - def __io_ops__ - @__io_ops__ ||= [] + def operations + @operations ||= [] end def io_write(io, buffer, length, offset) - fd = io.fileno - str = buffer.get_string - __io_ops__ << [:io_write, fd, str] - Fiber.blocking { buffer.write(io, 0, offset) } + descriptor = io.fileno + string = buffer.get_string + + self.operations << [:io_write, descriptor, string] + + Fiber.blocking do + buffer.write(io, 0, offset) + end end end diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 0cbd49dacab5c6..d3696267f7934b 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -288,90 +288,99 @@ def test_post_fork_fiber_blocking def test_io_write_on_flush begin - fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") - write_fd = nil - io_ops = nil + path = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") + descriptor = nil + operations = nil + thread = Thread.new do scheduler = IOScheduler.new Fiber.set_scheduler scheduler Fiber.schedule do - File.open(fn, 'w+') do |f| - write_fd = f.fileno - f << 'foo' - f.flush - f << 'bar' + File.open(path, 'w+') do |file| + descriptor = file.fileno + file << 'foo' + file.flush + file << 'bar' end end - io_ops = scheduler.__io_ops__ + + operations = scheduler.operations end + thread.join assert_equal [ - [:io_write, write_fd, 'foo'], - [:io_write, write_fd, 'bar'] - ], io_ops + [:io_write, descriptor, 'foo'], + [:io_write, descriptor, 'bar'] + ], operations - assert_equal 'foobar', IO.read(fn) + assert_equal 'foobar', IO.read(path) ensure thread.kill rescue nil - FileUtils.rm_f(fn) + FileUtils.rm_f(path) end end def test_io_read_error - fn = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}") - exception = nil + path = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}") + error = nil + thread = Thread.new do scheduler = IOErrorScheduler.new Fiber.set_scheduler scheduler Fiber.schedule do - File.open(fn, 'w+') { it.read } - rescue => e - exception = e + File.open(path, 'w+') { it.read } + rescue => error + # Ignore. end end + thread.join - assert_kind_of Errno::EBADF, exception + assert_kind_of Errno::EBADF, error ensure thread.kill rescue nil - FileUtils.rm_f(fn) + FileUtils.rm_f(path) end def test_io_write_error - fn = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}") - exception = nil + path = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}") + error = nil + thread = Thread.new do scheduler = IOErrorScheduler.new Fiber.set_scheduler scheduler Fiber.schedule do - File.open(fn, 'w+') { it.sync = true; it << 'foo' } - rescue => e - exception = e + File.open(path, 'w+') { it.sync = true; it << 'foo' } + rescue => error + # Ignore. end end + thread.join - assert_kind_of Errno::EINVAL, exception + assert_kind_of Errno::EINVAL, error ensure thread.kill rescue nil - FileUtils.rm_f(fn) + FileUtils.rm_f(path) end def test_io_write_flush_error - fn = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}") - exception = nil + path = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}") + error = nil + thread = Thread.new do scheduler = IOErrorScheduler.new Fiber.set_scheduler scheduler Fiber.schedule do - File.open(fn, 'w+') { it << 'foo' } - rescue => e - exception = e + File.open(path, 'w+') { it << 'foo' } + rescue => error + # Ignore. end end + thread.join - assert_kind_of Errno::EINVAL, exception + assert_kind_of Errno::EINVAL, error ensure thread.kill rescue nil - FileUtils.rm_f(fn) + FileUtils.rm_f(path) end end From e2a58c45b1c79204ae80b3960f44a818ec00b94e Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 21 Dec 2025 14:49:57 +0300 Subject: [PATCH 2131/2435] [DOC] Fix minor typo in signals.rdoc --- doc/language/signals.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/language/signals.rdoc b/doc/language/signals.rdoc index 403eb66549e5c2..a82dab81c68e92 100644 --- a/doc/language/signals.rdoc +++ b/doc/language/signals.rdoc @@ -17,7 +17,7 @@ for its internal data structures, but it does not know when it is safe for data structures in YOUR code. Ruby implements deferred signal handling by registering short C functions with only {async-signal-safe functions}[http://man7.org/linux/man-pages/man7/signal-safety.7.html] as -signal handlers. These short C functions only do enough tell the VM to +signal handlers. These short C functions only do enough to tell the VM to run callbacks registered via Signal.trap later in the main Ruby Thread. == Unsafe methods to call in Signal.trap blocks From 688c1f6c5e96dfa3e4f6b16c617545ded7c8c0b4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Dec 2025 15:22:57 +0900 Subject: [PATCH 2132/2435] [DOC] Reword "Pattern Matching" to "Regular Expression" In ruby, since 3.1 at least, the words "Pattern Matching" should refer the syntax. --- doc/language/globals.md | 4 ++-- doc/string/partition.rdoc | 4 ++-- doc/string/rpartition.rdoc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index a4199e488ab585..905a23ed05dbe4 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -19,7 +19,7 @@ require 'English' | `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | | `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | -### Pattern Matching +### Regular Expression | Variable | \English | Contains | Initially | Read-Only | Reset By | |:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| @@ -127,7 +127,7 @@ Output: English - `$ERROR_POSITION`. -## Pattern Matching +## Regular Expression These global variables store information about the most recent successful match in the current scope. diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index 330e6b03987fcf..86c3a9ca0a975a 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -17,7 +17,7 @@ Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, performs the equivalent of self.match(pattern) -(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): +(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): 'hello'.partition(/h/) # => ["", "h", "ello"] 'hello'.partition(/l/) # => ["he", "l", "lo"] @@ -30,7 +30,7 @@ If +pattern+ is a Regexp, performs the equivalent of self.match(pattern)self.index(pattern) -(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): +(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): 'hello'.partition('h') # => ["", "h", "ello"] 'hello'.partition('l') # => ["he", "l", "lo"] diff --git a/doc/string/rpartition.rdoc b/doc/string/rpartition.rdoc index 11b0571bfb2ba7..879b6ee2864295 100644 --- a/doc/string/rpartition.rdoc +++ b/doc/string/rpartition.rdoc @@ -23,7 +23,7 @@ The pattern used is: Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, searches for the last matching substring -(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): +(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): 'hello'.rpartition(/l/) # => ["hel", "l", "o"] 'hello'.rpartition(/ll/) # => ["he", "ll", "o"] @@ -36,7 +36,7 @@ If +pattern+ is a Regexp, searches for the last matching substring If +pattern+ is not a Regexp, converts it to a string (if it is not already one), then searches for the last matching substring -(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Pattern+Matching]): +(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): 'hello'.rpartition('l') # => ["hel", "l", "o"] 'hello'.rpartition('ll') # => ["he", "ll", "o"] From ceea8060e4cc94846b2ff32fe8b0bb39049eda2e Mon Sep 17 00:00:00 2001 From: YO4 Date: Fri, 3 Oct 2025 22:04:13 +0900 Subject: [PATCH 2133/2435] Properly handle test cases terminated by signals in test-bundled-gems Process::Status#exitstatus turn into nil when child process is signeled. When exit_code was unchanged, test-bundled-gems.rb returned 0 and make was unable to detect the failure. Fix this. --- tool/test-bundled-gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index 006ebd981af913..98b6bb9048c5a8 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -133,7 +133,7 @@ puts colorize.decorate(mesg, "skip") else failed << gem - exit_code = $?.exitstatus if $?.exitstatus + exit_code = 1 end end end From 6e2bf5df4eeab8e37fab86206d4f2e8ab36a60b7 Mon Sep 17 00:00:00 2001 From: aguspe Date: Tue, 23 Dec 2025 18:07:15 +0100 Subject: [PATCH 2134/2435] [Tests] Assert Module#set_temporary_name returns self The return value of Module#set_temporary_name was changed to return `self`, but the existing tests did not verify this. --- test/ruby/test_module.rb | 8 ++++---- variable.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3a47c2551a813e..30a7c5d9bc1c22 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -3367,11 +3367,11 @@ def test_set_temporary_name m.const_set(:N, Module.new) assert_match(/\A#::N\z/, m::N.name) - m::N.set_temporary_name(name = "fake_name_under_M") + assert_same m::N, m::N.set_temporary_name(name = "fake_name_under_M") name.upcase! assert_equal("fake_name_under_M", m::N.name) assert_raise(FrozenError) {m::N.name.upcase!} - m::N.set_temporary_name(nil) + assert_same m::N, m::N.set_temporary_name(nil) assert_nil(m::N.name) m::N.const_set(:O, Module.new) @@ -3379,14 +3379,14 @@ def test_set_temporary_name m::N.const_set(:Recursive, m) m.const_set(:A, 42) - m.set_temporary_name(name = "fake_name") + assert_same m, m.set_temporary_name(name = "fake_name") name.upcase! assert_equal("fake_name", m.name) assert_raise(FrozenError) {m.name.upcase!} assert_equal("fake_name::N", m::N.name) assert_equal("fake_name::N::O", m::N::O.name) - m.set_temporary_name(nil) + assert_same m, m.set_temporary_name(nil) assert_nil m.name assert_nil m::N.name assert_nil m::N::O.name diff --git a/variable.c b/variable.c index 085ba240e412ee..ff8d24d78aef6c 100644 --- a/variable.c +++ b/variable.c @@ -279,7 +279,7 @@ set_sub_temporary_name(VALUE mod, VALUE name) * m.name #=> nil * * c = Class.new - * c.set_temporary_name("MyClass(with description)") + * c.set_temporary_name("MyClass(with description)") # => MyClass(with description) * * c.new # => # * From 10a68210b4c96ab2de21357907627e1fb34ce000 Mon Sep 17 00:00:00 2001 From: TOMITA Masahiro Date: Thu, 6 Nov 2025 09:09:14 +0900 Subject: [PATCH 2135/2435] [DOC] Fix IO::Buffer document --- io_buffer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 85061076cd9476..f1afc3d3baf48e 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -571,7 +571,7 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) * buffer.get_string(0, 1) * # => "t" * string - * # => "best" + * # => "test" * * buffer.resize(100) * # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError) @@ -3784,9 +3784,9 @@ io_buffer_not_inplace(VALUE self) * * File.write('test.txt', 'test data') * # => 9 - * buffer = IO::Buffer.map(File.open('test.txt')) + * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) * # => - * # # + * # # * # ... * buffer.get_string(5, 2) # read 2 bytes, starting from offset 5 * # => "da" From 9154d72a3e342b6bf101d0d1e3c8bbd0feee3422 Mon Sep 17 00:00:00 2001 From: zverok Date: Tue, 23 Dec 2025 22:17:12 +0200 Subject: [PATCH 2136/2435] Improve CGI.escape* docs --- lib/cgi/escape.rb | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb index 6d84773fdd62f6..555d24a5da2109 100644 --- a/lib/cgi/escape.rb +++ b/lib/cgi/escape.rb @@ -1,20 +1,28 @@ # frozen_string_literal: true -# :stopdoc +# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape +# +# require 'cgi/escape' +# +# CGI.escape("Ruby programming language") +# #=> "Ruby+programming+language" +# CGI.escapeURIComponent("Ruby programming language") +# #=> "Ruby%20programming%20language" +# +# See CGI::Escape module for methods list and their description. class CGI module Escape; end include Escape extend Escape module EscapeExt; end # :nodoc: end -# :startdoc: -# Escape/unescape for CGI, HTML, URI. +# Web-related escape/unescape functionality. module CGI::Escape @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) # URL-encode a string into application/x-www-form-urlencoded. - # Space characters (+" "+) are encoded with plus signs (+"+"+) + # Space characters (" ") are encoded with plus signs ("+") # url_encoded_string = CGI.escape("'Stop!' said Fred") # # => "%27Stop%21%27+said+Fred" def escape(string) @@ -41,7 +49,7 @@ def unescape(string, encoding = @@accept_charset) end # URL-encode a string following RFC 3986 - # Space characters (+" "+) are encoded with (+"%20"+) + # Space characters (" ") are encoded with ("%20") # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") # # => "%27Stop%21%27%20said%20Fred" def escapeURIComponent(string) @@ -69,7 +77,7 @@ def unescapeURIComponent(string, encoding = @@accept_charset) alias unescape_uri_component unescapeURIComponent # The set of special characters and their escaped values - TABLE_FOR_ESCAPE_HTML__ = { + TABLE_FOR_ESCAPE_HTML__ = { # :nodoc: "'" => ''', '&' => '&', '"' => '"', @@ -77,7 +85,7 @@ def unescapeURIComponent(string, encoding = @@accept_charset) '>' => '>', } - # Escape special characters in HTML, namely '&\"<> + # \Escape special characters in HTML, namely '&\"<> # CGI.escapeHTML('Usage: foo "bar" ') # # => "Usage: foo "bar" <baz>" def escapeHTML(string) @@ -160,11 +168,9 @@ def unescapeHTML(string) string.force_encoding enc end - # Synonym for CGI.escapeHTML(str) alias escape_html escapeHTML alias h escapeHTML - # Synonym for CGI.unescapeHTML(str) alias unescape_html unescapeHTML # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there @@ -175,7 +181,7 @@ def unescapeHTML(string) end end - # Escape only the tags of certain HTML elements in +string+. + # \Escape only the tags of certain HTML elements in +string+. # # Takes an element or elements or array of elements. Each element # is specified by the name of the element, without angle brackets. @@ -199,7 +205,7 @@ def escapeElement(string, *elements) end end - # Undo escaping such as that done by CGI.escapeElement() + # Undo escaping such as that done by CGI.escapeElement # # print CGI.unescapeElement( # CGI.escapeHTML('
'), "A", "IMG") @@ -219,10 +225,8 @@ def unescapeElement(string, *elements) end end - # Synonym for CGI.escapeElement(str) alias escape_element escapeElement - # Synonym for CGI.unescapeElement(str) alias unescape_element unescapeElement end From ab683d56bc625c83d815741b3bfd9c606b14517f Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 23 Dec 2025 20:46:57 -0600 Subject: [PATCH 2137/2435] [DOC] Cross-links between Japanese and English pages (#15705) * [DOC] Cross-links between Japanese and English pages --- COPYING | 2 ++ COPYING.ja | 2 ++ doc/extension.ja.rdoc | 2 ++ doc/extension.rdoc | 2 ++ 4 files changed, 8 insertions(+) diff --git a/COPYING b/COPYING index 48e5a96de7c82c..428ce03ed7e6f0 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,5 @@ +{日本語}[rdoc-ref:COPYING.ja] + Ruby is copyrighted free software by Yukihiro Matsumoto . You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: diff --git a/COPYING.ja b/COPYING.ja index 230376bc603be2..5de2dbcc8f10eb 100644 --- a/COPYING.ja +++ b/COPYING.ja @@ -1,3 +1,5 @@ +{English}[rdoc-ref:COPYING] + 本プログラムはフリーソフトウェアです.2-clause BSDL または以下に示す条件で本プログラムを再配布できます 2-clause BSDLについてはBSDLファイルを参照して下さい. diff --git a/doc/extension.ja.rdoc b/doc/extension.ja.rdoc index 2f7856f3d439df..381b94a230b462 100644 --- a/doc/extension.ja.rdoc +++ b/doc/extension.ja.rdoc @@ -1,5 +1,7 @@ # extension.ja.rdoc - -*- RDoc -*- created at: Mon Aug 7 16:45:54 JST 1995 +{English}[rdoc-ref:extension.rdoc] + = Rubyの拡張ライブラリの作り方 Rubyの拡張ライブラリの作り方を説明します. diff --git a/doc/extension.rdoc b/doc/extension.rdoc index 6cf4c4926c93fc..18dc5817d45830 100644 --- a/doc/extension.rdoc +++ b/doc/extension.rdoc @@ -1,5 +1,7 @@ # extension.rdoc - -*- RDoc -*- created at: Mon Aug 7 16:45:54 JST 1995 +{日本語}[rdoc-ref:extension.ja.rdoc] + = Creating extension libraries for Ruby This document explains how to make extension libraries for Ruby. From 202028aea170e43609f5061548578a1b5681414b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 11:56:41 +0900 Subject: [PATCH 2138/2435] Update the latest results of test-bundled-gems --- tool/test-bundled-gems.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index 98b6bb9048c5a8..778fe3311aa6f4 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -10,11 +10,9 @@ github_actions = ENV["GITHUB_ACTIONS"] == "true" DEFAULT_ALLOWED_FAILURES = RUBY_PLATFORM =~ /mswin|mingw/ ? [ - 'rbs', 'debug', 'irb', - 'power_assert', - 'net-imap', + 'csv', ] : [] allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || '' allowed_failures = allowed_failures.split(',').concat(DEFAULT_ALLOWED_FAILURES).uniq.reject(&:empty?) From 342d25785c0332ba556da2bae960d2a4f4b8baad Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 23 Dec 2025 22:11:38 -0500 Subject: [PATCH 2139/2435] [DOC] Fix backticks in Coverage.peek_result --- ext/coverage/coverage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index 74d9f3ea4655fe..1f82193567eb1c 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -337,7 +337,7 @@ coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h) * Coverage.peek_result => hash * * Returns a hash that contains filename as key and coverage array as value. - * This is the same as `Coverage.result(stop: false, clear: false)`. + * This is the same as Coverage.result(stop: false, clear: false). * * { * "file.rb" => [1, 2, nil], From 7d2815d907e6dc7ce0075a8dd1a4e45b8e647921 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 13:48:35 +0900 Subject: [PATCH 2140/2435] Add flag for prevent to update NEWS.md for release day. --- .github/workflows/bundled_gems.yml | 4 ++++ .github/workflows/default_gems_list.yml | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index ad2e3915199102..5521752d2f517c 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -1,5 +1,8 @@ name: bundled_gems +env: + UPDATE_NEWS_ENABLED: false + on: push: branches: ['master'] @@ -67,6 +70,7 @@ jobs: - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled + if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} - name: Check diffs id: diff diff --git a/.github/workflows/default_gems_list.yml b/.github/workflows/default_gems_list.yml index 420228f3997d45..ba6d6ee73c528d 100644 --- a/.github/workflows/default_gems_list.yml +++ b/.github/workflows/default_gems_list.yml @@ -1,6 +1,9 @@ name: Update default gems list on: [push, pull_request, merge_group] +env: + UPDATE_NEWS_ENABLED: false + concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} @@ -65,7 +68,7 @@ jobs: - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb default - if: ${{ steps.gems.outcome == 'success' }} + if: ${{ steps.gems.outcome == 'success' && env.UPDATE_NEWS_ENABLED == 'true' }} - name: Check diffs id: diff From 6af9b8d59af176183c15589afc64505f5ada692c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 13:59:08 +0900 Subject: [PATCH 2141/2435] Minor update at stdlib section --- NEWS.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1b7efe4252464d..2c5a758e6b06c3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -280,6 +280,12 @@ Note: We're only listing outstanding class updates. ## Stdlib updates +We only list stdlib changes that are notable feature changes. + +Other changes are listed in the following sections. We also listed release +history from the previous bundled version that is Ruby 3.4.0 if it has GitHub +releases. + The following bundled gems are promoted from default gems. * ostruct 0.6.3 @@ -293,15 +299,6 @@ The following bundled gems are promoted from default gems. * readline 0.0.4 * fiddle 1.1.8 -The following bundled gems are added. - - -We only list stdlib changes that are notable feature changes. - -Other changes are listed in the following sections. We also listed release -history from the previous bundled version that is Ruby 3.4.0 if it has GitHub -releases. - The following default gem is added. * win32-registry 0.1.2 @@ -365,7 +362,7 @@ The following bundled gems are updated. ### RubyGems and Bundler -see the following links for details. +Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for details. * [Upgrading to RubyGems/Bundler 4 - RubyGems Blog](https://blog.rubygems.org/2025/12/03/upgrade-to-rubygems-bundler-4.html) * [4.0.0 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/03/4.0.0-released.html) From 44693ee32990fdd609e91ebb2970b3110b3f08e5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 24 Dec 2025 11:30:11 +0900 Subject: [PATCH 2142/2435] Fix a possible memory leak in dtoa Fix GH-15061 --- missing/dtoa.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/missing/dtoa.c b/missing/dtoa.c index cbd6e6ebae23fb..ba8cd46ebd9484 100644 --- a/missing/dtoa.c +++ b/missing/dtoa.c @@ -547,10 +547,13 @@ Balloc(int k) } static void -Bfree(Bigint *v) +Bclear(Bigint **vp) { - FREE(v); + Bigint *v = *vp; + *vp = NULL; + if (v) FREE(v); } +#define Bfree(v) Bclear(&(v)) #define Bcopy(x,y) memcpy((char *)&(x)->sign, (char *)&(y)->sign, \ (y)->wds*sizeof(Long) + 2*sizeof(int)) From fc19ce0a0187a8d69df88a1231ec93b0e3e990b2 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 24 Dec 2025 11:14:44 +0900 Subject: [PATCH 2143/2435] Lrama v0.7.1 --- tool/lrama/NEWS.md | 405 ++++- tool/lrama/exe/lrama | 2 +- tool/lrama/lib/lrama.rb | 10 +- tool/lrama/lib/lrama/bitmap.rb | 23 +- tool/lrama/lib/lrama/command.rb | 138 +- tool/lrama/lib/lrama/context.rb | 46 +- tool/lrama/lib/lrama/counterexamples.rb | 304 +++- .../lib/lrama/counterexamples/derivation.rb | 18 +- .../lib/lrama/counterexamples/example.rb | 69 +- tool/lrama/lib/lrama/counterexamples/node.rb | 30 + tool/lrama/lib/lrama/counterexamples/path.rb | 26 +- .../lrama/counterexamples/production_path.rb | 19 - .../lib/lrama/counterexamples/start_path.rb | 23 - .../lib/lrama/counterexamples/state_item.rb | 25 +- .../lrama/counterexamples/transition_path.rb | 19 - .../lrama/lib/lrama/counterexamples/triple.rb | 36 +- tool/lrama/lib/lrama/diagnostics.rb | 36 - tool/lrama/lib/lrama/diagram.rb | 77 + tool/lrama/lib/lrama/digraph.rb | 35 +- tool/lrama/lib/lrama/erb.rb | 29 + tool/lrama/lib/lrama/grammar.rb | 266 ++- tool/lrama/lib/lrama/grammar/auxiliary.rb | 7 +- tool/lrama/lib/lrama/grammar/binding.rb | 62 +- tool/lrama/lib/lrama/grammar/code.rb | 17 +- .../lib/lrama/grammar/code/destructor_code.rb | 11 + .../lrama/grammar/code/initial_action_code.rb | 3 + .../lrama/grammar/code/no_reference_code.rb | 3 + .../lib/lrama/grammar/code/printer_code.rb | 11 + .../lib/lrama/grammar/code/rule_action.rb | 17 + tool/lrama/lib/lrama/grammar/counter.rb | 10 + tool/lrama/lib/lrama/grammar/destructor.rb | 15 +- tool/lrama/lib/lrama/grammar/error_token.rb | 15 +- tool/lrama/lib/lrama/grammar/inline.rb | 3 + .../lib/lrama/grammar/inline/resolver.rb | 80 + tool/lrama/lib/lrama/grammar/parameterized.rb | 5 + .../resolver.rb | 27 +- .../rhs.rb | 9 +- .../lib/lrama/grammar/parameterized/rule.rb | 36 + .../lib/lrama/grammar/parameterizing_rule.rb | 5 - .../lrama/grammar/parameterizing_rule/rule.rb | 24 - tool/lrama/lib/lrama/grammar/percent_code.rb | 13 +- tool/lrama/lib/lrama/grammar/precedence.rb | 44 +- tool/lrama/lib/lrama/grammar/printer.rb | 9 + tool/lrama/lib/lrama/grammar/reference.rb | 13 + tool/lrama/lib/lrama/grammar/rule.rb | 62 +- tool/lrama/lib/lrama/grammar/rule_builder.rb | 153 +- tool/lrama/lib/lrama/grammar/stdlib.y | 116 +- tool/lrama/lib/lrama/grammar/symbol.rb | 82 +- .../lib/lrama/grammar/symbols/resolver.rb | 67 +- tool/lrama/lib/lrama/grammar/type.rb | 14 +- tool/lrama/lib/lrama/grammar/union.rb | 13 +- tool/lrama/lib/lrama/grammar_validator.rb | 37 - tool/lrama/lib/lrama/lexer.rb | 74 +- tool/lrama/lib/lrama/lexer/location.rb | 33 +- tool/lrama/lib/lrama/lexer/token.rb | 62 +- tool/lrama/lib/lrama/lexer/token/base.rb | 73 + tool/lrama/lib/lrama/lexer/token/char.rb | 17 +- tool/lrama/lib/lrama/lexer/token/empty.rb | 14 + tool/lrama/lib/lrama/lexer/token/ident.rb | 4 +- .../lib/lrama/lexer/token/instantiate_rule.rb | 8 +- tool/lrama/lib/lrama/lexer/token/int.rb | 14 + tool/lrama/lib/lrama/lexer/token/str.rb | 11 + tool/lrama/lib/lrama/lexer/token/tag.rb | 4 +- tool/lrama/lib/lrama/lexer/token/token.rb | 11 + tool/lrama/lib/lrama/lexer/token/user_code.rb | 100 +- tool/lrama/lib/lrama/logger.rb | 14 +- tool/lrama/lib/lrama/option_parser.rb | 72 +- tool/lrama/lib/lrama/options.rb | 32 +- tool/lrama/lib/lrama/output.rb | 15 +- tool/lrama/lib/lrama/parser.rb | 1577 +++++++++-------- tool/lrama/lib/lrama/report.rb | 4 - tool/lrama/lib/lrama/report/duration.rb | 27 - tool/lrama/lib/lrama/report/profile.rb | 16 - tool/lrama/lib/lrama/reporter.rb | 39 + tool/lrama/lib/lrama/reporter/conflicts.rb | 44 + tool/lrama/lib/lrama/reporter/grammar.rb | 39 + tool/lrama/lib/lrama/reporter/precedences.rb | 54 + tool/lrama/lib/lrama/reporter/profile.rb | 4 + .../lib/lrama/reporter/profile/call_stack.rb | 45 + .../lib/lrama/reporter/profile/memory.rb | 44 + tool/lrama/lib/lrama/reporter/rules.rb | 43 + tool/lrama/lib/lrama/reporter/states.rb | 387 ++++ tool/lrama/lib/lrama/reporter/terms.rb | 44 + tool/lrama/lib/lrama/state.rb | 501 +++--- tool/lrama/lib/lrama/state/action.rb | 5 + tool/lrama/lib/lrama/state/action/goto.rb | 33 + tool/lrama/lib/lrama/state/action/reduce.rb | 71 + tool/lrama/lib/lrama/state/action/shift.rb | 39 + .../lib/lrama/state/inadequacy_annotation.rb | 140 ++ .../lrama/lib/lrama/{states => state}/item.rb | 37 +- tool/lrama/lib/lrama/state/reduce.rb | 37 - .../lib/lrama/state/reduce_reduce_conflict.rb | 15 +- .../lib/lrama/state/resolved_conflict.rb | 42 +- tool/lrama/lib/lrama/state/shift.rb | 15 - .../lib/lrama/state/shift_reduce_conflict.rb | 15 +- tool/lrama/lib/lrama/states.rb | 622 +++++-- tool/lrama/lib/lrama/states_reporter.rb | 362 ---- tool/lrama/lib/lrama/trace_reporter.rb | 45 - tool/lrama/lib/lrama/tracer.rb | 51 + tool/lrama/lib/lrama/tracer/actions.rb | 22 + tool/lrama/lib/lrama/tracer/closure.rb | 30 + tool/lrama/lib/lrama/tracer/duration.rb | 38 + .../lib/lrama/tracer/only_explicit_rules.rb | 24 + tool/lrama/lib/lrama/tracer/rules.rb | 23 + tool/lrama/lib/lrama/tracer/state.rb | 33 + tool/lrama/lib/lrama/version.rb | 3 +- tool/lrama/lib/lrama/warnings.rb | 33 + tool/lrama/lib/lrama/warnings/conflicts.rb | 27 + .../lib/lrama/warnings/implicit_empty.rb | 29 + .../lib/lrama/warnings/name_conflicts.rb | 63 + .../lib/lrama/warnings/redefined_rules.rb | 23 + tool/lrama/lib/lrama/warnings/required.rb | 23 + .../lib/lrama/warnings/useless_precedence.rb | 25 + tool/lrama/template/bison/_yacc.h | 8 + tool/lrama/template/diagram/diagram.html | 102 ++ 115 files changed, 5663 insertions(+), 2417 deletions(-) create mode 100644 tool/lrama/lib/lrama/counterexamples/node.rb delete mode 100644 tool/lrama/lib/lrama/counterexamples/production_path.rb delete mode 100644 tool/lrama/lib/lrama/counterexamples/start_path.rb delete mode 100644 tool/lrama/lib/lrama/counterexamples/transition_path.rb delete mode 100644 tool/lrama/lib/lrama/diagnostics.rb create mode 100644 tool/lrama/lib/lrama/diagram.rb create mode 100644 tool/lrama/lib/lrama/erb.rb create mode 100644 tool/lrama/lib/lrama/grammar/inline.rb create mode 100644 tool/lrama/lib/lrama/grammar/inline/resolver.rb create mode 100644 tool/lrama/lib/lrama/grammar/parameterized.rb rename tool/lrama/lib/lrama/grammar/{parameterizing_rule => parameterized}/resolver.rb (60%) rename tool/lrama/lib/lrama/grammar/{parameterizing_rule => parameterized}/rhs.rb (73%) create mode 100644 tool/lrama/lib/lrama/grammar/parameterized/rule.rb delete mode 100644 tool/lrama/lib/lrama/grammar/parameterizing_rule.rb delete mode 100644 tool/lrama/lib/lrama/grammar/parameterizing_rule/rule.rb delete mode 100644 tool/lrama/lib/lrama/grammar_validator.rb create mode 100644 tool/lrama/lib/lrama/lexer/token/base.rb create mode 100644 tool/lrama/lib/lrama/lexer/token/empty.rb create mode 100644 tool/lrama/lib/lrama/lexer/token/int.rb create mode 100644 tool/lrama/lib/lrama/lexer/token/str.rb create mode 100644 tool/lrama/lib/lrama/lexer/token/token.rb delete mode 100644 tool/lrama/lib/lrama/report.rb delete mode 100644 tool/lrama/lib/lrama/report/duration.rb delete mode 100644 tool/lrama/lib/lrama/report/profile.rb create mode 100644 tool/lrama/lib/lrama/reporter.rb create mode 100644 tool/lrama/lib/lrama/reporter/conflicts.rb create mode 100644 tool/lrama/lib/lrama/reporter/grammar.rb create mode 100644 tool/lrama/lib/lrama/reporter/precedences.rb create mode 100644 tool/lrama/lib/lrama/reporter/profile.rb create mode 100644 tool/lrama/lib/lrama/reporter/profile/call_stack.rb create mode 100644 tool/lrama/lib/lrama/reporter/profile/memory.rb create mode 100644 tool/lrama/lib/lrama/reporter/rules.rb create mode 100644 tool/lrama/lib/lrama/reporter/states.rb create mode 100644 tool/lrama/lib/lrama/reporter/terms.rb create mode 100644 tool/lrama/lib/lrama/state/action.rb create mode 100644 tool/lrama/lib/lrama/state/action/goto.rb create mode 100644 tool/lrama/lib/lrama/state/action/reduce.rb create mode 100644 tool/lrama/lib/lrama/state/action/shift.rb create mode 100644 tool/lrama/lib/lrama/state/inadequacy_annotation.rb rename tool/lrama/lib/lrama/{states => state}/item.rb (61%) delete mode 100644 tool/lrama/lib/lrama/state/reduce.rb delete mode 100644 tool/lrama/lib/lrama/state/shift.rb delete mode 100644 tool/lrama/lib/lrama/states_reporter.rb delete mode 100644 tool/lrama/lib/lrama/trace_reporter.rb create mode 100644 tool/lrama/lib/lrama/tracer.rb create mode 100644 tool/lrama/lib/lrama/tracer/actions.rb create mode 100644 tool/lrama/lib/lrama/tracer/closure.rb create mode 100644 tool/lrama/lib/lrama/tracer/duration.rb create mode 100644 tool/lrama/lib/lrama/tracer/only_explicit_rules.rb create mode 100644 tool/lrama/lib/lrama/tracer/rules.rb create mode 100644 tool/lrama/lib/lrama/tracer/state.rb create mode 100644 tool/lrama/lib/lrama/warnings.rb create mode 100644 tool/lrama/lib/lrama/warnings/conflicts.rb create mode 100644 tool/lrama/lib/lrama/warnings/implicit_empty.rb create mode 100644 tool/lrama/lib/lrama/warnings/name_conflicts.rb create mode 100644 tool/lrama/lib/lrama/warnings/redefined_rules.rb create mode 100644 tool/lrama/lib/lrama/warnings/required.rb create mode 100644 tool/lrama/lib/lrama/warnings/useless_precedence.rb create mode 100644 tool/lrama/template/diagram/diagram.html diff --git a/tool/lrama/NEWS.md b/tool/lrama/NEWS.md index a535332ec37a32..f71118a9130f3e 100644 --- a/tool/lrama/NEWS.md +++ b/tool/lrama/NEWS.md @@ -1,8 +1,343 @@ # NEWS for Lrama +## Lrama 0.7.1 (2025-12-24) + +### Optimize IELR + +Optimized performance to a level that allows for IELR testing in practical applications. + +https://github.com/ruby/lrama/pull/595 +https://github.com/ruby/lrama/pull/605 +https://github.com/ruby/lrama/pull/685 +https://github.com/ruby/lrama/pull/700 + +### Introduce counterexamples timeout + +Counterexample searches can sometimes take a long time, so we've added a timeout to abort the process after a set period. The current limits are: + +* 10 seconds per case +* 120 seconds total (cumulative) + +Please note that these are hard-coded and cannot be modified by the user in the current version. + +https://github.com/ruby/lrama/pull/623 + +### Optimize Counterexamples + +Optimized counterexample search performance. + +https://github.com/ruby/lrama/pull/607 +https://github.com/ruby/lrama/pull/610 +https://github.com/ruby/lrama/pull/614 +https://github.com/ruby/lrama/pull/622 +https://github.com/ruby/lrama/pull/627 +https://github.com/ruby/lrama/pull/629 +https://github.com/ruby/lrama/pull/659 + +### Support parameterized rule's arguments include inline + +Allow to use %inline directive with Parameterized rules arguments. When an inline rule is used as an argument to a Parameterized rule, it expands inline at the point of use. + +```yacc +%rule %inline op : '+' + | '-' + ; +%% +operation : op? + ; +``` + +This expands to: + +```yacc +operation : /* empty */ + | '+' + | '-' + ; +``` + +https://github.com/ruby/lrama/pull/637 + +### Render conflicts of each state on output file + +Added token information for conflicts in the output file. +These information are useful when a state has many actions. + +``` +State 1 + + 4 class: keyword_class • tSTRING "end" + 5 $@1: ε • [tSTRING] + 7 class: keyword_class • $@1 tSTRING '!' "end" $@2 + 8 $@3: ε • [tSTRING] + 10 class: keyword_class • $@3 tSTRING '?' "end" $@4 + + Conflict on tSTRING. shift/reduce($@1) + Conflict on tSTRING. shift/reduce($@3) + Conflict on tSTRING. reduce($@1)/reduce($@3) + + tSTRING shift, and go to state 6 + + tSTRING reduce using rule 5 ($@1) + tSTRING reduce using rule 8 ($@3) + + $@1 go to state 7 + $@3 go to state 8 +``` + +https://github.com/ruby/lrama/pull/541 + +### Render the origin of conflicted tokens on output file + +For example, for the grammar file like below: + +``` +%% + +program: expr + ; + +expr: expr '+' expr + | tNUMBER + ; + +%% +``` + +Lrama generates output file which describes where `"plus"` (`'+'`) look ahead tokens come from: + +``` +State 6 + + 2 expr: expr • "plus" expr + 2 | expr "plus" expr • ["end of file", "plus"] + + Conflict on "plus". shift/reduce(expr) + "plus" comes from state 0 goto by expr + "plus" comes from state 5 goto by expr +``` + +state 0 and state 5 look like below: + +``` +State 0 + + 0 $accept: • program "end of file" + 1 program: • expr + 2 expr: • expr "plus" expr + 3 | • tNUMBER + + tNUMBER shift, and go to state 1 + + program go to state 2 + expr go to state 3 + +State 5 + + 2 expr: • expr "plus" expr + 2 | expr "plus" • expr + 3 | • tNUMBER + + tNUMBER shift, and go to state 1 + + expr go to state 6 +``` + +https://github.com/ruby/lrama/pull/726 + +### Render precedences usage information on output file + +For example, for the grammar file like below: + +``` +%left tPLUS +%right tUPLUS + +%% + +program: expr ; + +expr: tUPLUS expr + | expr tPLUS expr + | tNUMBER + ; + +%% +``` + +Lrama generates output file which describes where these precedences are used to resolve conflicts: + +``` +Precedences + precedence on "unary+" is used to resolve conflict on + LALR + state 5. Conflict between reduce by "expr -> tUPLUS expr" and shift "+" resolved as reduce ("+" < "unary+"). + precedence on "+" is used to resolve conflict on + LALR + state 5. Conflict between reduce by "expr -> tUPLUS expr" and shift "+" resolved as reduce ("+" < "unary+"). + state 8. Conflict between reduce by "expr -> expr tPLUS expr" and shift "+" resolved as reduce (%left "+"). +``` + +https://github.com/ruby/lrama/pull/741 + +### Add support for reporting Rule Usage Frequency + +Support to report rule usage frequency statistics for analyzing grammar characteristics. +Run `exe/lrama --report=rules` to show how frequently each terminal and non-terminal symbol is used in the grammar rules. + +```console +$ exe/lrama --report=rules sample/calc.y +Rule Usage Frequency + 0 tSTRING (4 times) + 1 keyword_class (3 times) + 2 keyword_end (3 times) + 3 '+' (2 times) + 4 string (2 times) + 5 string_1 (2 times) + 6 '!' (1 times) + 7 '-' (1 times) + 8 '?' (1 times) + 9 EOI (1 times) + 10 class (1 times) + 11 program (1 times) + 12 string_2 (1 times) + 13 strings_1 (1 times) + 14 strings_2 (1 times) + 15 tNUMBER (1 times) +``` + +This feature provides insights into the language characteristics by showing: +- Which symbols are most frequently used in the grammar +- The distribution of terminal and non-terminal usage +- Potential areas for grammar optimization or refactoring + +The frequency statistics help developers understand the grammar structure and can be useful for: +- Grammar complexity analysis +- Performance optimization hints +- Language design decisions +- Documentation and educational purposes + +https://github.com/ruby/lrama/pull/677 + +### Render Split States information on output file + +For example, for the grammar file like below: + +``` +%token a +%token b +%token c +%define lr.type ielr + +%precedence tLOWEST +%precedence a +%precedence tHIGHEST + +%% + +S: a A B a + | b A B b + ; + +A: a C D E + ; + +B: c + | // empty + ; + +C: D + ; + +D: a + ; + +E: a + | %prec tHIGHEST // empty + ; + +%% +``` + +Lrama generates output file which describes where which new states are created when IELR is enabled: + +``` +Split States + + State 19 is split from state 4 + State 20 is split from state 9 + State 21 is split from state 14 +``` + +https://github.com/ruby/lrama/pull/624 + +### Add ioption support to the Standard library + +Support `ioption` (inline option) rule, which is expanded inline without creating intermediate rules. + +Unlike the regular `option` rule that generates a separate rule, `ioption` directly expands at the point of use: + +```yacc +program: ioption(number) expr + +// Expanded inline to: + +program: expr + | number expr +``` + +This differs from the regular `option` which would generate: + +```yacc +program: option(number) expr + +// Expanded to: + +program: option_number expr +option_number: %empty + | number +``` + +The `ioption` rule provides more compact grammar generation by avoiding intermediate rule creation, which can be beneficial for reducing the parser's rule count and potentially improving performance. + +This feature is inspired by Menhir's standard library and maintains compatibility with [Menhir's `ioption` behavior](https://github.com/let-def/menhir/blob/e8ba7bef219acd355798072c42abbd11335ecf09/src/standard.mly#L33-L41). + +https://github.com/ruby/lrama/pull/666 + +### Syntax Diagrams + +Lrama provides an API for generating HTML syntax diagrams. These visual diagrams are highly useful as grammar development tools and can also serve as a form of automatic self-documentation. + +![Syntax Diagrams](https://github.com/user-attachments/assets/5d9bca77-93fd-4416-bc24-9a0f70693a22) + +If you use syntax diagrams, you add `--diagram` option. + +```console +$ exe/lrama --diagram sample.y +``` + +https://github.com/ruby/lrama/pull/523 + +### Support `--profile` option + +You can profile parser generation process without modification for Lrama source code. +Currently `--profile=call-stack` and `--profile=memory` are supported. + +```console +$ exe/lrama --profile=call-stack sample/calc.y +``` + +Then "tmp/stackprof-cpu-myapp.dump" is generated. + +https://github.com/ruby/lrama/pull/525 + +### Add support Start-Symbol: `%start` + +https://github.com/ruby/lrama/pull/576 + ## Lrama 0.7.0 (2025-01-21) -## [EXPERIMENTAL] Support the generation of the IELR(1) parser described in this paper +### [EXPERIMENTAL] Support the generation of the IELR(1) parser described in this paper Support the generation of the IELR(1) parser described in this paper. https://www.sciencedirect.com/science/article/pii/S0167642309001191 @@ -15,12 +350,12 @@ If you use IELR(1) parser, you can write the following directive in your grammar But, currently IELR(1) parser is experimental feature. If you find any bugs, please report it to us. Thank you. -## Support `-t` option as same as `--debug` option +### Support `-t` option as same as `--debug` option Support to `-t` option as same as `--debug` option. These options align with Bison behavior. So same as `--debug` option. -## Trace only explicit rules +### Trace only explicit rules Support to trace only explicit rules. If you use `--trace=rules` option, it shows include mid-rule actions. If you want to show only explicit rules, you can use `--trace=only-explicit-rules` option. @@ -97,9 +432,9 @@ nterm.y:6:7: symbol EOI redeclared as a nonterminal ## Lrama 0.6.10 (2024-09-11) -### Aliased Named References for actions of RHS in parameterizing rules +### Aliased Named References for actions of RHS in Parameterizing rules -Allow to use aliased named references for actions of RHS in parameterizing rules. +Allow to use aliased named references for actions of RHS in Parameterizing rules. ```yacc %rule sum(X, Y): X[summand] '+' Y[addend] { $$ = $summand + $addend } @@ -109,9 +444,9 @@ Allow to use aliased named references for actions of RHS in parameterizing rules https://github.com/ruby/lrama/pull/410 -### Named References for actions of RHS in parameterizing rules caller side +### Named References for actions of RHS in Parameterizing rules caller side -Allow to use named references for actions of RHS in parameterizing rules caller side. +Allow to use named references for actions of RHS in Parameterizing rules caller side. ```yacc opt_nl: '\n'?[nl] { $$ = $nl; } @@ -120,9 +455,9 @@ opt_nl: '\n'?[nl] { $$ = $nl; } https://github.com/ruby/lrama/pull/414 -### Widen the definable position of parameterizing rules +### Widen the definable position of Parameterizing rules -Allow to define parameterizing rules in the middle of the grammar. +Allow to define Parameterizing rules in the middle of the grammar. ```yacc %rule defined_option(X): /* empty */ @@ -186,15 +521,15 @@ Change to `%locations` directive not set by default. https://github.com/ruby/lrama/pull/446 -### Diagnostics report for parameterizing rules redefine +### Diagnostics report for parameterized rules redefine -Support to warning redefined parameterizing rules. -Run `exe/lrama -W` or `exe/lrama --warnings` to show redefined parameterizing rules. +Support to warning redefined parameterized rules. +Run `exe/lrama -W` or `exe/lrama --warnings` to show redefined parameterized rules. ```console $ exe/lrama -W sample/calc.y -parameterizing rule redefined: redefined_method(X) -parameterizing rule redefined: redefined_method(X) +parameterized rule redefined: redefined_method(X) +parameterized rule redefined: redefined_method(X) ``` https://github.com/ruby/lrama/pull/448 @@ -208,9 +543,9 @@ https://github.com/ruby/lrama/pull/457 ## Lrama 0.6.9 (2024-05-02) -### Callee side tag specification of parameterizing rules +### Callee side tag specification of Parameterizing rules -Allow to specify tag on callee side of parameterizing rules. +Allow to specify tag on callee side of Parameterizing rules. ```yacc %union { @@ -221,9 +556,9 @@ Allow to specify tag on callee side of parameterizing rules. ; ``` -### Named References for actions of RHS in parameterizing rules +### Named References for actions of RHS in Parameterizing rules -Allow to use named references for actions of RHS in parameterizing rules. +Allow to use named references for actions of RHS in Parameterizing rules. ```yacc %rule option(number): /* empty */ @@ -233,9 +568,9 @@ Allow to use named references for actions of RHS in parameterizing rules. ## Lrama 0.6.8 (2024-04-29) -### Nested parameterizing rules with tag +### Nested Parameterizing rules with tag -Allow to nested parameterizing rules with tag. +Allow to nested Parameterizing rules with tag. ```yacc %union { @@ -257,9 +592,9 @@ Allow to nested parameterizing rules with tag. ## Lrama 0.6.7 (2024-04-28) -### RHS of user defined parameterizing rules contains `'symbol'?`, `'symbol'+` and `'symbol'*`. +### RHS of user defined Parameterizing rules contains `'symbol'?`, `'symbol'+` and `'symbol'*`. -User can use `'symbol'?`, `'symbol'+` and `'symbol'*` in RHS of user defined parameterizing rules. +User can use `'symbol'?`, `'symbol'+` and `'symbol'*` in RHS of user defined Parameterizing rules. ``` %rule with_word_seps(X): /* empty */ @@ -319,7 +654,7 @@ expr : number { $$ = $1; } ### Typed Midrule Actions -User can specify the type of mid rule action by tag (``) instead of specifying it with in an action. +User can specify the type of mid-rule action by tag (``) instead of specifying it with in an action. ```yacc primary: k_case expr_value terms? @@ -394,7 +729,7 @@ https://github.com/ruby/lrama/pull/382 User can set codes for freeing semantic value resources by using `%destructor`. In general, these resources are freed by actions or after parsing. -However if syntax error happens in parsing, these codes may not be executed. +However, if syntax error happens in parsing, these codes may not be executed. Codes associated to `%destructor` are executed when semantic value is popped from the stack by an error. ```yacc @@ -432,7 +767,7 @@ Lrama introduces two features to support another semantic value stack by parser 1. Callback entry points User can emulate semantic value stack by these callbacks. -Lrama provides these five callbacks. Registered functions are called when each event happen. For example %after-shift function is called when shift happens on original semantic value stack. +Lrama provides these five callbacks. Registered functions are called when each event happens. For example %after-shift function is called when shift happens on original semantic value stack. * `%after-shift` function_name * `%before-reduce` function_name @@ -460,15 +795,15 @@ https://github.com/ruby/lrama/pull/367 ### %no-stdlib directive If `%no-stdlib` directive is set, Lrama doesn't load Lrama standard library for -parameterizing rules, stdlib.y. +parameterized rules, stdlib.y. https://github.com/ruby/lrama/pull/344 ## Lrama 0.6.1 (2024-01-13) -### Nested parameterizing rules +### Nested Parameterizing rules -Allow to pass an instantiated rule to other parameterizing rules. +Allow to pass an instantiated rule to other Parameterizing rules. ```yacc %rule constant(X) : X @@ -485,7 +820,7 @@ program : option(constant(number)) // Nested rule %% ``` -Allow to use nested parameterizing rules when define parameterizing rules. +Allow to use nested Parameterizing rules when define Parameterizing rules. ```yacc %rule option(x) : /* empty */ @@ -510,9 +845,9 @@ https://github.com/ruby/lrama/pull/337 ## Lrama 0.6.0 (2023-12-25) -### User defined parameterizing rules +### User defined Parameterizing rules -Allow to define parameterizing rule by `%rule` directive. +Allow to define Parameterizing rule by `%rule` directive. ```yacc %rule pair(X, Y): X Y { $$ = $1 + $2; } @@ -532,7 +867,7 @@ https://github.com/ruby/lrama/pull/285 ## Lrama 0.5.11 (2023-12-02) -### Type specification of parameterizing rules +### Type specification of Parameterizing rules Allow to specify type of rules by specifying tag, `` in below example. Tag is post-modification style. @@ -556,13 +891,13 @@ https://github.com/ruby/lrama/pull/272 ### Parameterizing rules (option, nonempty_list, list) -Support function call style parameterizing rules for `option`, `nonempty_list` and `list`. +Support function call style Parameterizing rules for `option`, `nonempty_list` and `list`. https://github.com/ruby/lrama/pull/197 ### Parameterizing rules (separated_list) -Support `separated_list` and `separated_nonempty_list` parameterizing rules. +Support `separated_list` and `separated_nonempty_list` Parameterizing rules. ```text program: separated_list(',', number) @@ -618,7 +953,7 @@ https://github.com/ruby/lrama/pull/181 ### Racc parser -Replace Lrama's parser from hand written parser to LR parser generated by Racc. +Replace Lrama's parser from handwritten parser to LR parser generated by Racc. Lrama uses `--embedded` option to generate LR parser because Racc is changed from default gem to bundled gem by Ruby 3.3 (https://github.com/ruby/lrama/pull/132). https://github.com/ruby/lrama/pull/62 diff --git a/tool/lrama/exe/lrama b/tool/lrama/exe/lrama index 1aece5d1410125..710ac0cb965888 100755 --- a/tool/lrama/exe/lrama +++ b/tool/lrama/exe/lrama @@ -4,4 +4,4 @@ $LOAD_PATH << File.join(__dir__, "../lib") require "lrama" -Lrama::Command.new.run(ARGV.dup) +Lrama::Command.new(ARGV.dup).run diff --git a/tool/lrama/lib/lrama.rb b/tool/lrama/lib/lrama.rb index fe2e05807c8994..56ba0044d4a651 100644 --- a/tool/lrama/lib/lrama.rb +++ b/tool/lrama/lib/lrama.rb @@ -4,19 +4,19 @@ require_relative "lrama/command" require_relative "lrama/context" require_relative "lrama/counterexamples" -require_relative "lrama/diagnostics" +require_relative "lrama/diagram" require_relative "lrama/digraph" +require_relative "lrama/erb" require_relative "lrama/grammar" -require_relative "lrama/grammar_validator" require_relative "lrama/lexer" require_relative "lrama/logger" require_relative "lrama/option_parser" require_relative "lrama/options" require_relative "lrama/output" require_relative "lrama/parser" -require_relative "lrama/report" +require_relative "lrama/reporter" require_relative "lrama/state" require_relative "lrama/states" -require_relative "lrama/states_reporter" -require_relative "lrama/trace_reporter" +require_relative "lrama/tracer" require_relative "lrama/version" +require_relative "lrama/warnings" diff --git a/tool/lrama/lib/lrama/bitmap.rb b/tool/lrama/lib/lrama/bitmap.rb index 098c6e0b777a2c..88b255b012463a 100644 --- a/tool/lrama/lib/lrama/bitmap.rb +++ b/tool/lrama/lib/lrama/bitmap.rb @@ -3,7 +3,10 @@ module Lrama module Bitmap - # @rbs (Array[Integer] ary) -> Integer + # @rbs! + # type bitmap = Integer + + # @rbs (Array[Integer] ary) -> bitmap def self.from_array(ary) bit = 0 @@ -14,21 +17,31 @@ def self.from_array(ary) bit end - # @rbs (Integer int) -> Array[Integer] + # @rbs (Integer int) -> bitmap + def self.from_integer(int) + 1 << int + end + + # @rbs (bitmap int) -> Array[Integer] def self.to_array(int) a = [] #: Array[Integer] i = 0 - while int > 0 do - if int & 1 == 1 + len = int.bit_length + while i < len do + if int[i] == 1 a << i end i += 1 - int >>= 1 end a end + + # @rbs (bitmap int, Integer size) -> Array[bool] + def self.to_bool_array(int, size) + Array.new(size) { |i| int[i] == 1 } + end end end diff --git a/tool/lrama/lib/lrama/command.rb b/tool/lrama/lib/lrama/command.rb index 3ff39d578d0319..17aad1a1c112b1 100644 --- a/tool/lrama/lib/lrama/command.rb +++ b/tool/lrama/lib/lrama/command.rb @@ -5,64 +5,116 @@ class Command LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__))) STDLIB_FILE_PATH = File.join(LRAMA_LIB, 'grammar', 'stdlib.y') - def run(argv) - begin - options = OptionParser.new.parse(argv) - rescue => e - message = e.message - message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty? - abort message - end - - Report::Duration.enable if options.trace_opts[:time] + def initialize(argv) + @logger = Lrama::Logger.new + @options = OptionParser.parse(argv) + @tracer = Tracer.new(STDERR, **@options.trace_opts) + @reporter = Reporter.new(**@options.report_opts) + @warnings = Warnings.new(@logger, @options.warnings) + rescue => e + abort format_error_message(e.message) + end - text = options.y.read - options.y.close if options.y != STDIN - begin - grammar = Lrama::Parser.new(text, options.grammar_file, options.debug, options.define).parse - unless grammar.no_stdlib - stdlib_grammar = Lrama::Parser.new(File.read(STDLIB_FILE_PATH), STDLIB_FILE_PATH, options.debug).parse - grammar.insert_before_parameterizing_rules(stdlib_grammar.parameterizing_rules) + def run + Lrama::Reporter::Profile::CallStack.report(@options.profile_opts[:call_stack]) do + Lrama::Reporter::Profile::Memory.report(@options.profile_opts[:memory]) do + execute_command_workflow end - grammar.prepare - grammar.validate! - rescue => e - raise e if options.debug - message = e.message - message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty? - abort message end - states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure])) + end + + private + + def execute_command_workflow + @tracer.enable_duration + text = read_input + grammar = build_grammar(text) + states, context = compute_status(grammar) + render_reports(states) if @options.report_file + @tracer.trace(grammar) + render_diagram(grammar) + render_output(context, grammar) + states.validate!(@logger) + @warnings.warn(grammar, states) + end + + def read_input + text = @options.y.read + @options.y.close unless @options.y == STDIN + text + end + + def build_grammar(text) + grammar = + Lrama::Parser.new(text, @options.grammar_file, @options.debug, @options.locations, @options.define).parse + merge_stdlib(grammar) + prepare_grammar(grammar) + grammar + rescue => e + raise e if @options.debug + abort format_error_message(e.message) + end + + def format_error_message(message) + return message unless Exception.to_tty? + + message.gsub(/.+/, "\e[1m\\&\e[m") + end + + def merge_stdlib(grammar) + return if grammar.no_stdlib + + stdlib_text = File.read(STDLIB_FILE_PATH) + stdlib_grammar = Lrama::Parser.new( + stdlib_text, + STDLIB_FILE_PATH, + @options.debug, + @options.locations, + @options.define, + ).parse + + grammar.prepend_parameterized_rules(stdlib_grammar.parameterized_rules) + end + + def prepare_grammar(grammar) + grammar.prepare + grammar.validate! + end + + def compute_status(grammar) + states = Lrama::States.new(grammar, @tracer) states.compute states.compute_ielr if grammar.ielr_defined? - context = Lrama::Context.new(states) + [states, Lrama::Context.new(states)] + end - if options.report_file - reporter = Lrama::StatesReporter.new(states) - File.open(options.report_file, "w+") do |f| - reporter.report(f, **options.report_opts) - end + def render_reports(states) + File.open(@options.report_file, "w+") do |f| + @reporter.report(f, states) end + end - reporter = Lrama::TraceReporter.new(grammar) - reporter.report(**options.trace_opts) + def render_diagram(grammar) + return unless @options.diagram - File.open(options.outfile, "w+") do |f| + File.open(@options.diagram_file, "w+") do |f| + Lrama::Diagram.render(out: f, grammar: grammar) + end + end + + def render_output(context, grammar) + File.open(@options.outfile, "w+") do |f| Lrama::Output.new( out: f, - output_file_path: options.outfile, - template_name: options.skeleton, - grammar_file_path: options.grammar_file, - header_file_path: options.header_file, + output_file_path: @options.outfile, + template_name: @options.skeleton, + grammar_file_path: @options.grammar_file, + header_file_path: @options.header_file, context: context, grammar: grammar, - error_recovery: options.error_recovery, + error_recovery: @options.error_recovery, ).render end - - logger = Lrama::Logger.new - exit false unless Lrama::GrammarValidator.new(grammar, states, logger).valid? - Lrama::Diagnostics.new(grammar, states, logger).run(options.diagnostic) end end end diff --git a/tool/lrama/lib/lrama/context.rb b/tool/lrama/lib/lrama/context.rb index 9f406f8de0bc65..eb068c1b9e4880 100644 --- a/tool/lrama/lib/lrama/context.rb +++ b/tool/lrama/lib/lrama/context.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require_relative "report/duration" +require_relative "tracer/duration" module Lrama # This is passed to a template class Context - include Report::Duration + include Tracer::Duration ErrorActionNumber = -Float::INFINITY BaseMin = -Float::INFINITY @@ -231,8 +231,8 @@ def compute_yydefact end # Shift is selected when S/R conflict exists. - state.selected_term_transitions.each do |shift, next_state| - actions[shift.next_sym.number] = next_state.id + state.selected_term_transitions.each do |shift| + actions[shift.next_sym.number] = shift.to_state.id end state.resolved_conflicts.select do |conflict| @@ -292,18 +292,18 @@ def compute_yydefgoto # of a default nterm transition destination. @yydefgoto = Array.new(@states.nterms.count, 0) # Mapping from nterm to next_states - nterm_to_next_states = {} + nterm_to_to_states = {} @states.states.each do |state| - state.nterm_transitions.each do |shift, next_state| - key = shift.next_sym - nterm_to_next_states[key] ||= [] - nterm_to_next_states[key] << [state, next_state] # [from_state, to_state] + state.nterm_transitions.each do |goto| + key = goto.next_sym + nterm_to_to_states[key] ||= [] + nterm_to_to_states[key] << [state, goto.to_state] # [from_state, to_state] end end @states.nterms.each do |nterm| - if (states = nterm_to_next_states[nterm]) + if (states = nterm_to_to_states[nterm]) default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first default_goto = default_state.id not_default_gotos = [] @@ -417,27 +417,25 @@ def compute_packed_table res = lowzero - froms_and_tos.first[0] + # Find the smallest `res` such that `@table[res + from]` is empty for all `from` in `froms_and_tos` while true do - ok = true + advanced = false - froms_and_tos.each do |from, to| - loc = res + from - - if @table[loc] - # If the cell of table is set, can not use the cell. - ok = false - break - end + while used_res[res] + res += 1 + advanced = true end - if ok && used_res[res] - ok = false + froms_and_tos.each do |from, to| + while @table[res + from] + res += 1 + advanced = true + end end - if ok + unless advanced + # no advance means that the current `res` satisfies the condition break - else - res += 1 end end diff --git a/tool/lrama/lib/lrama/counterexamples.rb b/tool/lrama/lib/lrama/counterexamples.rb index ee2b5d5959539d..60d830d048e96d 100644 --- a/tool/lrama/lib/lrama/counterexamples.rb +++ b/tool/lrama/lib/lrama/counterexamples.rb @@ -1,35 +1,64 @@ +# rbs_inline: enabled # frozen_string_literal: true require "set" +require "timeout" require_relative "counterexamples/derivation" require_relative "counterexamples/example" +require_relative "counterexamples/node" require_relative "counterexamples/path" -require_relative "counterexamples/production_path" -require_relative "counterexamples/start_path" require_relative "counterexamples/state_item" -require_relative "counterexamples/transition_path" require_relative "counterexamples/triple" module Lrama # See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf # 4. Constructing Nonunifying Counterexamples class Counterexamples - attr_reader :transitions, :productions - + PathSearchTimeLimit = 10 # 10 sec + CumulativeTimeLimit = 120 # 120 sec + + # @rbs! + # @states: States + # @iterate_count: Integer + # @total_duration: Float + # @exceed_cumulative_time_limit: bool + # @state_items: Hash[[State, State::Item], StateItem] + # @triples: Hash[Integer, Triple] + # @transitions: Hash[[StateItem, Grammar::Symbol], StateItem] + # @reverse_transitions: Hash[[StateItem, Grammar::Symbol], Set[StateItem]] + # @productions: Hash[StateItem, Set[StateItem]] + # @reverse_productions: Hash[[State, Grammar::Symbol], Set[StateItem]] # Grammar::Symbol is nterm + # @state_item_shift: Integer + + attr_reader :transitions #: Hash[[StateItem, Grammar::Symbol], StateItem] + attr_reader :productions #: Hash[StateItem, Set[StateItem]] + + # @rbs (States states) -> void def initialize(states) @states = states + @iterate_count = 0 + @total_duration = 0 + @exceed_cumulative_time_limit = false + @triples = {} + setup_state_items setup_transitions setup_productions end + # @rbs () -> "#" def to_s "#" end alias :inspect :to_s + # @rbs (State conflict_state) -> Array[Example] def compute(conflict_state) conflict_state.conflicts.flat_map do |conflict| + # Check cumulative time limit for not each path search method call but each conflict + # to avoid one of example's path to be nil. + next if @exceed_cumulative_time_limit + case conflict.type when :shift_reduce # @type var conflict: State::ShiftReduceConflict @@ -38,22 +67,50 @@ def compute(conflict_state) # @type var conflict: State::ReduceReduceConflict reduce_reduce_examples(conflict_state, conflict) end + rescue Timeout::Error => e + STDERR.puts "Counterexamples calculation for state #{conflict_state.id} #{e.message} with #{@iterate_count} iteration" + increment_total_duration(PathSearchTimeLimit) + nil end.compact end private + # @rbs (State state, State::Item item) -> StateItem + def get_state_item(state, item) + @state_items[[state, item]] + end + + # For optimization, create all StateItem in advance + # and use them by fetching an instance from `@state_items`. + # Do not create new StateItem instance in the shortest path search process + # to avoid miss hash lookup. + # + # @rbs () -> void + def setup_state_items + @state_items = {} + count = 0 + + @states.states.each do |state| + state.items.each do |item| + @state_items[[state, item]] = StateItem.new(count, state, item) + count += 1 + end + end + + @state_item_shift = Math.log(count, 2).ceil + end + + # @rbs () -> void def setup_transitions - # Hash [StateItem, Symbol] => StateItem @transitions = {} - # Hash [StateItem, Symbol] => Set(StateItem) @reverse_transitions = {} @states.states.each do |src_state| trans = {} #: Hash[Grammar::Symbol, State] - src_state.transitions.each do |shift, next_state| - trans[shift.next_sym] = next_state + src_state.transitions.each do |transition| + trans[transition.next_sym] = transition.to_state end src_state.items.each do |src_item| @@ -63,8 +120,8 @@ def setup_transitions dest_state.kernels.each do |dest_item| next unless (src_item.rule == dest_item.rule) && (src_item.position + 1 == dest_item.position) - src_state_item = StateItem.new(src_state, src_item) - dest_state_item = StateItem.new(dest_state, dest_item) + src_state_item = get_state_item(src_state, src_item) + dest_state_item = get_state_item(dest_state, dest_item) @transitions[[src_state_item, sym]] = dest_state_item @@ -77,21 +134,20 @@ def setup_transitions end end + # @rbs () -> void def setup_productions - # Hash [StateItem] => Set(Item) @productions = {} - # Hash [State, Symbol] => Set(Item). Symbol is nterm @reverse_productions = {} @states.states.each do |state| - # LHS => Set(Item) - h = {} #: Hash[Grammar::Symbol, Set[States::Item]] + # Grammar::Symbol is LHS + h = {} #: Hash[Grammar::Symbol, Set[StateItem]] state.closure.each do |item| sym = item.lhs h[sym] ||= Set.new - h[sym] << item + h[sym] << get_state_item(state, item) end state.items.each do |item| @@ -99,101 +155,118 @@ def setup_productions next if item.next_sym.term? sym = item.next_sym - state_item = StateItem.new(state, item) - # @type var key: [State, Grammar::Symbol] - key = [state, sym] - + state_item = get_state_item(state, item) @productions[state_item] = h[sym] + # @type var key: [State, Grammar::Symbol] + key = [state, sym] @reverse_productions[key] ||= Set.new - @reverse_productions[key] << item + @reverse_productions[key] << state_item end end end + # For optimization, use same Triple if it's already created. + # Do not create new Triple instance anywhere else + # to avoid miss hash lookup. + # + # @rbs (StateItem state_item, Bitmap::bitmap precise_lookahead_set) -> Triple + def get_triple(state_item, precise_lookahead_set) + key = (precise_lookahead_set << @state_item_shift) | state_item.id + @triples[key] ||= Triple.new(state_item, precise_lookahead_set) + end + + # @rbs (State conflict_state, State::ShiftReduceConflict conflict) -> Example def shift_reduce_example(conflict_state, conflict) conflict_symbol = conflict.symbols.first - # @type var shift_conflict_item: ::Lrama::States::Item + # @type var shift_conflict_item: ::Lrama::State::Item shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol } - path2 = shortest_path(conflict_state, conflict.reduce.item, conflict_symbol) - path1 = find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item) + path2 = with_timeout("#shortest_path:") do + shortest_path(conflict_state, conflict.reduce.item, conflict_symbol) + end + path1 = with_timeout("#find_shift_conflict_shortest_path:") do + find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item) + end Example.new(path1, path2, conflict, conflict_symbol, self) end + # @rbs (State conflict_state, State::ReduceReduceConflict conflict) -> Example def reduce_reduce_examples(conflict_state, conflict) conflict_symbol = conflict.symbols.first - path1 = shortest_path(conflict_state, conflict.reduce1.item, conflict_symbol) - path2 = shortest_path(conflict_state, conflict.reduce2.item, conflict_symbol) + path1 = with_timeout("#shortest_path:") do + shortest_path(conflict_state, conflict.reduce1.item, conflict_symbol) + end + path2 = with_timeout("#shortest_path:") do + shortest_path(conflict_state, conflict.reduce2.item, conflict_symbol) + end Example.new(path1, path2, conflict, conflict_symbol, self) end - def find_shift_conflict_shortest_path(reduce_path, conflict_state, conflict_item) - state_items = find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item) - build_paths_from_state_items(state_items) - end + # @rbs (Array[StateItem]? reduce_state_items, State conflict_state, State::Item conflict_item) -> Array[StateItem] + def find_shift_conflict_shortest_path(reduce_state_items, conflict_state, conflict_item) + time1 = Time.now.to_f + @iterate_count = 0 - def find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item) - target_state_item = StateItem.new(conflict_state, conflict_item) + target_state_item = get_state_item(conflict_state, conflict_item) result = [target_state_item] - reversed_reduce_path = reduce_path.to_a.reverse + reversed_state_items = reduce_state_items.to_a.reverse # Index for state_item i = 0 - while (path = reversed_reduce_path[i]) + while (state_item = reversed_state_items[i]) # Index for prev_state_item j = i + 1 _j = j - while (prev_path = reversed_reduce_path[j]) - if prev_path.production? + while (prev_state_item = reversed_state_items[j]) + if prev_state_item.type == :production j += 1 else break end end - state_item = path.to - prev_state_item = prev_path&.to - if target_state_item == state_item || target_state_item.item.start_item? result.concat( - reversed_reduce_path[_j..-1] #: Array[StartPath|TransitionPath|ProductionPath] - .map(&:to)) + reversed_state_items[_j..-1] #: Array[StateItem] + ) break end - if target_state_item.item.beginning_of_rule? - queue = [] #: Array[Array[StateItem]] - queue << [target_state_item] + if target_state_item.type == :production + queue = [] #: Array[Node[StateItem]] + queue << Node.new(target_state_item, nil) # Find reverse production while (sis = queue.shift) - si = sis.last + @iterate_count += 1 + si = sis.elem # Reach to start state if si.item.start_item? - sis.shift - result.concat(sis) + a = Node.to_a(sis).reverse + a.shift + result.concat(a) target_state_item = si break end - if si.item.beginning_of_rule? + if si.type == :production # @type var key: [State, Grammar::Symbol] key = [si.state, si.item.lhs] - @reverse_productions[key].each do |item| - state_item = StateItem.new(si.state, item) - queue << (sis + [state_item]) + @reverse_productions[key].each do |state_item| + queue << Node.new(state_item, sis) end else # @type var key: [StateItem, Grammar::Symbol] key = [si, si.item.previous_sym] @reverse_transitions[key].each do |prev_target_state_item| next if prev_target_state_item.state != prev_state_item&.state - sis.shift - result.concat(sis) + a = Node.to_a(sis).reverse + a.shift + result.concat(a) result << prev_target_state_item target_state_item = prev_target_state_item i = j @@ -216,68 +289,106 @@ def find_shift_conflict_shortest_state_items(reduce_path, conflict_state, confli end end + time2 = Time.now.to_f + duration = time2 - time1 + increment_total_duration(duration) + + if Tracer::Duration.enabled? + STDERR.puts sprintf(" %s %10.5f s", "find_shift_conflict_shortest_path #{@iterate_count} iteration", duration) + end + result.reverse end - def build_paths_from_state_items(state_items) - state_items.zip([nil] + state_items).map do |si, prev_si| - case - when prev_si.nil? - StartPath.new(si) - when si.item.beginning_of_rule? - ProductionPath.new(prev_si, si) - else - TransitionPath.new(prev_si, si) + # @rbs (StateItem target) -> Set[StateItem] + def reachable_state_items(target) + result = Set.new + queue = [target] + + while (state_item = queue.shift) + next if result.include?(state_item) + result << state_item + + @reverse_transitions[[state_item, state_item.item.previous_sym]]&.each do |prev_state_item| + queue << prev_state_item + end + + if state_item.item.beginning_of_rule? + @reverse_productions[[state_item.state, state_item.item.lhs]]&.each do |si| + queue << si + end end end + + result end + # @rbs (State conflict_state, State::Item conflict_reduce_item, Grammar::Symbol conflict_term) -> ::Array[StateItem]? def shortest_path(conflict_state, conflict_reduce_item, conflict_term) - # queue: is an array of [Triple, [Path]] - queue = [] #: Array[[Triple, Array[StartPath|TransitionPath|ProductionPath]]] + time1 = Time.now.to_f + @iterate_count = 0 + + queue = [] #: Array[[Triple, Path]] visited = {} #: Hash[Triple, true] start_state = @states.states.first #: Lrama::State + conflict_term_bit = Bitmap::from_integer(conflict_term.number) raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1 + reachable = reachable_state_items(get_state_item(conflict_state, conflict_reduce_item)) + start = get_triple(get_state_item(start_state, start_state.kernels.first), Bitmap::from_integer(@states.eof_symbol.number)) - start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol])) + queue << [start, Path.new(start.state_item, nil)] - queue << [start, [StartPath.new(start.state_item)]] + while (triple, path = queue.shift) + @iterate_count += 1 - while true - triple, paths = queue.shift + # Found + if (triple.state == conflict_state) && (triple.item == conflict_reduce_item) && (triple.l & conflict_term_bit != 0) + state_items = [path.state_item] - next if visited[triple] - visited[triple] = true + while (path = path.parent) + state_items << path.state_item + end - # Found - if triple.state == conflict_state && triple.item == conflict_reduce_item && triple.l.include?(conflict_term) - return paths + time2 = Time.now.to_f + duration = time2 - time1 + increment_total_duration(duration) + + if Tracer::Duration.enabled? + STDERR.puts sprintf(" %s %10.5f s", "shortest_path #{@iterate_count} iteration", duration) + end + + return state_items.reverse end # transition - triple.state.transitions.each do |shift, next_state| - next unless triple.item.next_sym && triple.item.next_sym == shift.next_sym - next_state.kernels.each do |kernel| - next if kernel.rule != triple.item.rule - t = Triple.new(next_state, kernel, triple.l) - queue << [t, paths + [TransitionPath.new(triple.state_item, t.state_item)]] + next_state_item = @transitions[[triple.state_item, triple.item.next_sym]] + if next_state_item && reachable.include?(next_state_item) + # @type var t: Triple + t = get_triple(next_state_item, triple.l) + unless visited[t] + visited[t] = true + queue << [t, Path.new(t.state_item, path)] end end # production step - triple.state.closure.each do |item| - next unless triple.item.next_sym && triple.item.next_sym == item.lhs + @productions[triple.state_item]&.each do |si| + next unless reachable.include?(si) + l = follow_l(triple.item, triple.l) - t = Triple.new(triple.state, item, l) - queue << [t, paths + [ProductionPath.new(triple.state_item, t.state_item)]] + # @type var t: Triple + t = get_triple(si, l) + unless visited[t] + visited[t] = true + queue << [t, Path.new(t.state_item, path)] + end end - - break if queue.empty? end return nil end + # @rbs (State::Item item, Bitmap::bitmap current_l) -> Bitmap::bitmap def follow_l(item, current_l) # 1. follow_L (A -> X1 ... Xn-1 • Xn) = L # 2. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = {Xk+2} if Xk+2 is a terminal @@ -287,11 +398,28 @@ def follow_l(item, current_l) when item.number_of_rest_symbols == 1 current_l when item.next_next_sym.term? - Set.new([item.next_next_sym]) + item.next_next_sym.number_bitmap when !item.next_next_sym.nullable - item.next_next_sym.first_set + item.next_next_sym.first_set_bitmap else - item.next_next_sym.first_set + follow_l(item.new_by_next_position, current_l) + item.next_next_sym.first_set_bitmap | follow_l(item.new_by_next_position, current_l) + end + end + + # @rbs [T] (String message) { -> T } -> T + def with_timeout(message) + Timeout.timeout(PathSearchTimeLimit, Timeout::Error, message + " timeout of #{PathSearchTimeLimit} sec exceeded") do + yield + end + end + + # @rbs (Float|Integer duration) -> void + def increment_total_duration(duration) + @total_duration += duration + + if !@exceed_cumulative_time_limit && @total_duration > CumulativeTimeLimit + @exceed_cumulative_time_limit = true + STDERR.puts "CumulativeTimeLimit #{CumulativeTimeLimit} sec exceeded then skip following Counterexamples calculation" end end end diff --git a/tool/lrama/lib/lrama/counterexamples/derivation.rb b/tool/lrama/lib/lrama/counterexamples/derivation.rb index 368d7f1032f92d..a2b74767a941cf 100644 --- a/tool/lrama/lib/lrama/counterexamples/derivation.rb +++ b/tool/lrama/lib/lrama/counterexamples/derivation.rb @@ -1,34 +1,44 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Counterexamples class Derivation - attr_reader :item, :left, :right - attr_writer :right + # @rbs! + # @item: State::Item + # @left: Derivation? - def initialize(item, left, right = nil) + attr_reader :item #: State::Item + attr_reader :left #: Derivation? + attr_accessor :right #: Derivation? + + # @rbs (State::Item item, Derivation? left) -> void + def initialize(item, left) @item = item @left = left - @right = right end + # @rbs () -> ::String def to_s "#" end alias :inspect :to_s + # @rbs () -> Array[String] def render_strings_for_report result = [] #: Array[String] _render_for_report(self, 0, result, 0) result.map(&:rstrip) end + # @rbs () -> String def render_for_report render_strings_for_report.join("\n") end private + # @rbs (Derivation derivation, Integer offset, Array[String] strings, Integer index) -> Integer def _render_for_report(derivation, offset, strings, index) item = derivation.item if strings[index] diff --git a/tool/lrama/lib/lrama/counterexamples/example.rb b/tool/lrama/lib/lrama/counterexamples/example.rb index bb08428fcd8f11..c007f45af4ee38 100644 --- a/tool/lrama/lib/lrama/counterexamples/example.rb +++ b/tool/lrama/lib/lrama/counterexamples/example.rb @@ -1,12 +1,31 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Counterexamples class Example - attr_reader :path1, :path2, :conflict, :conflict_symbol + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @path1: ::Array[StateItem] + # @path2: ::Array[StateItem] + # @conflict: State::conflict + # @conflict_symbol: Grammar::Symbol + # @counterexamples: Counterexamples + # @derivations1: Derivation + # @derivations2: Derivation + + attr_reader :path1 #: ::Array[StateItem] + attr_reader :path2 #: ::Array[StateItem] + attr_reader :conflict #: State::conflict + attr_reader :conflict_symbol #: Grammar::Symbol # path1 is shift conflict when S/R conflict # path2 is always reduce conflict + # + # @rbs (Array[StateItem]? path1, Array[StateItem]? path2, State::conflict conflict, Grammar::Symbol conflict_symbol, Counterexamples counterexamples) -> void def initialize(path1, path2, conflict, conflict_symbol, counterexamples) @path1 = path1 @path2 = path2 @@ -15,69 +34,75 @@ def initialize(path1, path2, conflict, conflict_symbol, counterexamples) @counterexamples = counterexamples end + # @rbs () -> (:shift_reduce | :reduce_reduce) def type @conflict.type end + # @rbs () -> State::Item def path1_item - @path1.last.to.item + @path1.last.item end + # @rbs () -> State::Item def path2_item - @path2.last.to.item + @path2.last.item end + # @rbs () -> Derivation def derivations1 @derivations1 ||= _derivations(path1) end + # @rbs () -> Derivation def derivations2 @derivations2 ||= _derivations(path2) end private - def _derivations(paths) + # @rbs (Array[StateItem] state_items) -> Derivation + def _derivations(state_items) derivation = nil #: Derivation current = :production - last_path = paths.last #: Path - lookahead_sym = last_path.to.item.end_of_rule? ? @conflict_symbol : nil + last_state_item = state_items.last #: StateItem + lookahead_sym = last_state_item.item.end_of_rule? ? @conflict_symbol : nil - paths.reverse_each do |path| - item = path.to.item + state_items.reverse_each do |si| + item = si.item case current when :production - case path - when StartPath + case si.type + when :start derivation = Derivation.new(item, derivation) current = :start - when TransitionPath + when :transition derivation = Derivation.new(item, derivation) current = :transition - when ProductionPath + when :production derivation = Derivation.new(item, derivation) current = :production else - raise "Unexpected. #{path}" + raise "Unexpected. #{si}" end if lookahead_sym && item.next_next_sym && item.next_next_sym.first_set.include?(lookahead_sym) - state_item = @counterexamples.transitions[[path.to, item.next_sym]] - derivation2 = find_derivation_for_symbol(state_item, lookahead_sym) + si2 = @counterexamples.transitions[[si, item.next_sym]] + derivation2 = find_derivation_for_symbol(si2, lookahead_sym) derivation.right = derivation2 # steep:ignore lookahead_sym = nil end when :transition - case path - when StartPath + case si.type + when :start derivation = Derivation.new(item, derivation) current = :start - when TransitionPath + when :transition # ignore current = :transition - when ProductionPath + when :production # ignore current = :production end @@ -91,6 +116,7 @@ def _derivations(paths) derivation end + # @rbs (StateItem state_item, Grammar::Symbol sym) -> Derivation? def find_derivation_for_symbol(state_item, sym) queue = [] #: Array[Array[StateItem]] queue << [state_item] @@ -110,9 +136,8 @@ def find_derivation_for_symbol(state_item, sym) end if next_sym.nterm? && next_sym.first_set.include?(sym) - @counterexamples.productions[si].each do |next_item| - next if next_item.empty_rule? - next_si = StateItem.new(si.state, next_item) + @counterexamples.productions[si].each do |next_si| + next if next_si.item.empty_rule? next if sis.include?(next_si) queue << (sis + [next_si]) end diff --git a/tool/lrama/lib/lrama/counterexamples/node.rb b/tool/lrama/lib/lrama/counterexamples/node.rb new file mode 100644 index 00000000000000..9214a0e7f1315e --- /dev/null +++ b/tool/lrama/lib/lrama/counterexamples/node.rb @@ -0,0 +1,30 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Counterexamples + # @rbs generic E < Object -- Type of an element + class Node + attr_reader :elem #: E + attr_reader :next_node #: Node[E]? + + # @rbs [E < Object] (Node[E] node) -> Array[E] + def self.to_a(node) + a = [] # steep:ignore UnannotatedEmptyCollection + + while (node) + a << node.elem + node = node.next_node + end + + a + end + + # @rbs (E elem, Node[E]? next_node) -> void + def initialize(elem, next_node) + @elem = elem + @next_node = next_node + end + end + end +end diff --git a/tool/lrama/lib/lrama/counterexamples/path.rb b/tool/lrama/lib/lrama/counterexamples/path.rb index 0a5823dd21690d..6b1325f73b1abf 100644 --- a/tool/lrama/lib/lrama/counterexamples/path.rb +++ b/tool/lrama/lib/lrama/counterexamples/path.rb @@ -1,29 +1,27 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Counterexamples class Path - def initialize(from_state_item, to_state_item) - @from_state_item = from_state_item - @to_state_item = to_state_item - end + # @rbs! + # @state_item: StateItem + # @parent: Path? - def from - @from_state_item - end + attr_reader :state_item #: StateItem + attr_reader :parent #: Path? - def to - @to_state_item + # @rbs (StateItem state_item, Path? parent) -> void + def initialize(state_item, parent) + @state_item = state_item + @parent = parent end + # @rbs () -> ::String def to_s - "#" + "#" end alias :inspect :to_s - - def type - raise NotImplementedError - end end end end diff --git a/tool/lrama/lib/lrama/counterexamples/production_path.rb b/tool/lrama/lib/lrama/counterexamples/production_path.rb deleted file mode 100644 index 0a230c7fce72fd..00000000000000 --- a/tool/lrama/lib/lrama/counterexamples/production_path.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Counterexamples - class ProductionPath < Path - def type - :production - end - - def transition? - false - end - - def production? - true - end - end - end -end diff --git a/tool/lrama/lib/lrama/counterexamples/start_path.rb b/tool/lrama/lib/lrama/counterexamples/start_path.rb deleted file mode 100644 index c0351c8248972a..00000000000000 --- a/tool/lrama/lib/lrama/counterexamples/start_path.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Counterexamples - class StartPath < Path - def initialize(to_state_item) - super nil, to_state_item - end - - def type - :start - end - - def transition? - false - end - - def production? - false - end - end - end -end diff --git a/tool/lrama/lib/lrama/counterexamples/state_item.rb b/tool/lrama/lib/lrama/counterexamples/state_item.rb index c919818324c2f4..8c2481d7938122 100644 --- a/tool/lrama/lib/lrama/counterexamples/state_item.rb +++ b/tool/lrama/lib/lrama/counterexamples/state_item.rb @@ -1,8 +1,31 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Counterexamples - class StateItem < Struct.new(:state, :item) + class StateItem + attr_reader :id #: Integer + attr_reader :state #: State + attr_reader :item #: State::Item + + # @rbs (Integer id, State state, State::Item item) -> void + def initialize(id, state, item) + @id = id + @state = state + @item = item + end + + # @rbs () -> (:start | :transition | :production) + def type + case + when item.start_item? + :start + when item.beginning_of_rule? + :production + else + :transition + end + end end end end diff --git a/tool/lrama/lib/lrama/counterexamples/transition_path.rb b/tool/lrama/lib/lrama/counterexamples/transition_path.rb deleted file mode 100644 index 47bfbc4f98d15d..00000000000000 --- a/tool/lrama/lib/lrama/counterexamples/transition_path.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Counterexamples - class TransitionPath < Path - def type - :transition - end - - def transition? - true - end - - def production? - false - end - end - end -end diff --git a/tool/lrama/lib/lrama/counterexamples/triple.rb b/tool/lrama/lib/lrama/counterexamples/triple.rb index 64014ee223521b..98fe051f530f8d 100644 --- a/tool/lrama/lib/lrama/counterexamples/triple.rb +++ b/tool/lrama/lib/lrama/counterexamples/triple.rb @@ -1,21 +1,39 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Counterexamples - # s: state - # itm: item within s - # l: precise lookahead set - class Triple < Struct.new(:s, :itm, :l) - alias :state :s - alias :item :itm - alias :precise_lookahead_set :l + class Triple + attr_reader :precise_lookahead_set #: Bitmap::bitmap + alias :l :precise_lookahead_set + + # @rbs (StateItem state_item, Bitmap::bitmap precise_lookahead_set) -> void + def initialize(state_item, precise_lookahead_set) + @state_item = state_item + @precise_lookahead_set = precise_lookahead_set + end + + # @rbs () -> State + def state + @state_item.state + end + alias :s :state + + # @rbs () -> State::Item + def item + @state_item.item + end + alias :itm :item + + # @rbs () -> StateItem def state_item - StateItem.new(state, item) + @state_item end + # @rbs () -> ::String def inspect - "#{state.inspect}. #{item.display_name}. #{l.map(&:id).map(&:s_value)}" + "#{state.inspect}. #{item.display_name}. #{l.to_s(2)}" end alias :to_s :inspect end diff --git a/tool/lrama/lib/lrama/diagnostics.rb b/tool/lrama/lib/lrama/diagnostics.rb deleted file mode 100644 index e9da398c89c9c6..00000000000000 --- a/tool/lrama/lib/lrama/diagnostics.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Diagnostics - def initialize(grammar, states, logger) - @grammar = grammar - @states = states - @logger = logger - end - - def run(diagnostic) - if diagnostic - diagnose_conflict - diagnose_parameterizing_redefined - end - end - - private - - def diagnose_conflict - if @states.sr_conflicts_count != 0 - @logger.warn("shift/reduce conflicts: #{@states.sr_conflicts_count} found") - end - - if @states.rr_conflicts_count != 0 - @logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found") - end - end - - def diagnose_parameterizing_redefined - @grammar.parameterizing_rule_resolver.redefined_rules.each do |rule| - @logger.warn("parameterizing rule redefined: #{rule}") - end - end - end -end diff --git a/tool/lrama/lib/lrama/diagram.rb b/tool/lrama/lib/lrama/diagram.rb new file mode 100644 index 00000000000000..985808933fb10f --- /dev/null +++ b/tool/lrama/lib/lrama/diagram.rb @@ -0,0 +1,77 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Diagram + class << self + # @rbs (IO out, Grammar grammar, String template_name) -> void + def render(out:, grammar:, template_name: 'diagram/diagram.html') + return unless require_railroad_diagrams + new(out: out, grammar: grammar, template_name: template_name).render + end + + # @rbs () -> bool + def require_railroad_diagrams + require "railroad_diagrams" + true + rescue LoadError + warn "railroad_diagrams is not installed. Please run `bundle install`." + false + end + end + + # @rbs (IO out, Grammar grammar, String template_name) -> void + def initialize(out:, grammar:, template_name: 'diagram/diagram.html') + @grammar = grammar + @out = out + @template_name = template_name + end + + # @rbs () -> void + def render + RailroadDiagrams::TextDiagram.set_formatting(RailroadDiagrams::TextDiagram::PARTS_UNICODE) + @out << ERB.render(template_file, output: self) + end + + # @rbs () -> string + def default_style + RailroadDiagrams::Style::default_style + end + + # @rbs () -> string + def diagrams + result = +'' + @grammar.unique_rule_s_values.each do |s_value| + diagrams = + @grammar.select_rules_by_s_value(s_value).map { |r| r.to_diagrams } + add_diagram( + s_value, + RailroadDiagrams::Diagram.new( + RailroadDiagrams::Choice.new(0, *diagrams), + ), + result + ) + end + result + end + + private + + # @rbs () -> string + def template_dir + File.expand_path('../../template', __dir__) + end + + # @rbs () -> string + def template_file + File.join(template_dir, @template_name) + end + + # @rbs (String name, RailroadDiagrams::Diagram diagram, String result) -> void + def add_diagram(name, diagram, result) + result << "\n

#{RailroadDiagrams.escape_html(name)}

" + diagram.write_svg(result.method(:<<)) + result << "\n" + end + end +end diff --git a/tool/lrama/lib/lrama/digraph.rb b/tool/lrama/lib/lrama/digraph.rb index 2161f304743cf6..52865f52dde90d 100644 --- a/tool/lrama/lib/lrama/digraph.rb +++ b/tool/lrama/lib/lrama/digraph.rb @@ -2,13 +2,34 @@ # frozen_string_literal: true module Lrama - # Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625) + # Digraph Algorithm of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625) # - # @rbs generic X < Object -- Type of a member of `sets` - # @rbs generic Y < _Or -- Type of sets assigned to a member of `sets` + # Digraph is an algorithm for graph data structure. + # The algorithm efficiently traverses SCC (Strongly Connected Component) of graph + # and merges nodes attributes within the same SCC. + # + # `compute_read_sets` and `compute_follow_sets` have the same structure. + # Graph of gotos and attributes of gotos are given then compute propagated attributes for each node. + # + # In the case of `compute_read_sets`: + # + # * Set of gotos is nodes of graph + # * `reads_relation` is edges of graph + # * `direct_read_sets` is nodes attributes + # + # In the case of `compute_follow_sets`: + # + # * Set of gotos is nodes of graph + # * `includes_relation` is edges of graph + # * `read_sets` is nodes attributes + # + # + # @rbs generic X < Object -- Type of a node + # @rbs generic Y < _Or -- Type of attribute sets assigned to a node which should support merge operation (#| method) class Digraph - # TODO: rbs-inline 0.10.0 doesn't support instance variables. + # TODO: rbs-inline 0.11.0 doesn't support instance variables. # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 # # @rbs! # interface _Or @@ -21,9 +42,9 @@ class Digraph # @h: Hash[X, (Integer|Float)?] # @result: Hash[X, Y] - # @rbs sets: Array[X] - # @rbs relation: Hash[X, Array[X]] - # @rbs base_function: Hash[X, Y] + # @rbs sets: Array[X] -- Nodes of graph + # @rbs relation: Hash[X, Array[X]] -- Edges of graph + # @rbs base_function: Hash[X, Y] -- Attributes of nodes # @rbs return: void def initialize(sets, relation, base_function) diff --git a/tool/lrama/lib/lrama/erb.rb b/tool/lrama/lib/lrama/erb.rb new file mode 100644 index 00000000000000..8f8be54811657e --- /dev/null +++ b/tool/lrama/lib/lrama/erb.rb @@ -0,0 +1,29 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +require "erb" + +module Lrama + class ERB + # @rbs (String file, **untyped kwargs) -> String + def self.render(file, **kwargs) + new(file).render(**kwargs) + end + + # @rbs (String file) -> void + def initialize(file) + input = File.read(file) + if ::ERB.instance_method(:initialize).parameters.last.first == :key + @erb = ::ERB.new(input, trim_mode: '-') + else + @erb = ::ERB.new(input, nil, '-') # steep:ignore UnexpectedPositionalArgument + end + @erb.filename = file + end + + # @rbs (**untyped kwargs) -> String + def render(**kwargs) + @erb.result_with_hash(kwargs) + end + end +end diff --git a/tool/lrama/lib/lrama/grammar.rb b/tool/lrama/lib/lrama/grammar.rb index 214ca1a3f238ba..95a80bb01cbcdf 100644 --- a/tool/lrama/lib/lrama/grammar.rb +++ b/tool/lrama/lib/lrama/grammar.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true require "forwardable" @@ -7,7 +8,8 @@ require_relative "grammar/counter" require_relative "grammar/destructor" require_relative "grammar/error_token" -require_relative "grammar/parameterizing_rule" +require_relative "grammar/inline" +require_relative "grammar/parameterized" require_relative "grammar/percent_code" require_relative "grammar/precedence" require_relative "grammar/printer" @@ -23,19 +25,89 @@ module Lrama # Grammar is the result of parsing an input grammar file class Grammar + # @rbs! + # + # interface _DelegatedMethods + # def rules: () -> Array[Rule] + # def accept_symbol: () -> Grammar::Symbol + # def eof_symbol: () -> Grammar::Symbol + # def undef_symbol: () -> Grammar::Symbol + # def precedences: () -> Array[Precedence] + # + # # delegate to @symbols_resolver + # def symbols: () -> Array[Grammar::Symbol] + # def terms: () -> Array[Grammar::Symbol] + # def nterms: () -> Array[Grammar::Symbol] + # def find_symbol_by_s_value!: (::String s_value) -> Grammar::Symbol + # def ielr_defined?: () -> bool + # end + # + # include Symbols::Resolver::_DelegatedMethods + # + # @rule_counter: Counter + # @percent_codes: Array[PercentCode] + # @printers: Array[Printer] + # @destructors: Array[Destructor] + # @error_tokens: Array[ErrorToken] + # @symbols_resolver: Symbols::Resolver + # @types: Array[Type] + # @rule_builders: Array[RuleBuilder] + # @rules: Array[Rule] + # @sym_to_rules: Hash[Integer, Array[Rule]] + # @parameterized_resolver: Parameterized::Resolver + # @empty_symbol: Grammar::Symbol + # @eof_symbol: Grammar::Symbol + # @error_symbol: Grammar::Symbol + # @undef_symbol: Grammar::Symbol + # @accept_symbol: Grammar::Symbol + # @aux: Auxiliary + # @no_stdlib: bool + # @locations: bool + # @define: Hash[String, String] + # @required: bool + # @union: Union + # @precedences: Array[Precedence] + # @start_nterm: Lrama::Lexer::Token::Base? + extend Forwardable - attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver - attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action, - :after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack, - :symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations, :define + attr_reader :percent_codes #: Array[PercentCode] + attr_reader :eof_symbol #: Grammar::Symbol + attr_reader :error_symbol #: Grammar::Symbol + attr_reader :undef_symbol #: Grammar::Symbol + attr_reader :accept_symbol #: Grammar::Symbol + attr_reader :aux #: Auxiliary + attr_reader :parameterized_resolver #: Parameterized::Resolver + attr_reader :precedences #: Array[Precedence] + attr_accessor :union #: Union + attr_accessor :expect #: Integer + attr_accessor :printers #: Array[Printer] + attr_accessor :error_tokens #: Array[ErrorToken] + attr_accessor :lex_param #: String + attr_accessor :parse_param #: String + attr_accessor :initial_action #: Grammar::Code::InitialActionCode + attr_accessor :after_shift #: Lexer::Token::Base + attr_accessor :before_reduce #: Lexer::Token::Base + attr_accessor :after_reduce #: Lexer::Token::Base + attr_accessor :after_shift_error_token #: Lexer::Token::Base + attr_accessor :after_pop_stack #: Lexer::Token::Base + attr_accessor :symbols_resolver #: Symbols::Resolver + attr_accessor :types #: Array[Type] + attr_accessor :rules #: Array[Rule] + attr_accessor :rule_builders #: Array[RuleBuilder] + attr_accessor :sym_to_rules #: Hash[Integer, Array[Rule]] + attr_accessor :no_stdlib #: bool + attr_accessor :locations #: bool + attr_accessor :define #: Hash[String, String] + attr_accessor :required #: bool def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term, :find_term_by_s_value, :find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol, :find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type, :fill_printer, :fill_destructor, :fill_error_token, :sort_by_number! - def initialize(rule_counter, define = {}) + # @rbs (Counter rule_counter, bool locations, Hash[String, String] define) -> void + def initialize(rule_counter, locations, define = {}) @rule_counter = rule_counter # Code defined by "%code" @@ -48,7 +120,7 @@ def initialize(rule_counter, define = {}) @rule_builders = [] @rules = [] @sym_to_rules = {} - @parameterizing_rule_resolver = ParameterizingRule::Resolver.new + @parameterized_resolver = Parameterized::Resolver.new @empty_symbol = nil @eof_symbol = nil @error_symbol = nil @@ -56,93 +128,131 @@ def initialize(rule_counter, define = {}) @accept_symbol = nil @aux = Auxiliary.new @no_stdlib = false - @locations = false - @define = define.map {|d| d.split('=') }.to_h + @locations = locations + @define = define + @required = false + @precedences = [] + @start_nterm = nil append_special_symbols end + # @rbs (Counter rule_counter, Counter midrule_action_counter) -> RuleBuilder def create_rule_builder(rule_counter, midrule_action_counter) - RuleBuilder.new(rule_counter, midrule_action_counter, @parameterizing_rule_resolver) + RuleBuilder.new(rule_counter, midrule_action_counter, @parameterized_resolver) end + # @rbs (id: Lexer::Token::Base, code: Lexer::Token::UserCode) -> Array[PercentCode] def add_percent_code(id:, code:) @percent_codes << PercentCode.new(id.s_value, code.s_value) end + # @rbs (ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], token_code: Lexer::Token::UserCode, lineno: Integer) -> Array[Destructor] def add_destructor(ident_or_tags:, token_code:, lineno:) @destructors << Destructor.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno) end + # @rbs (ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], token_code: Lexer::Token::UserCode, lineno: Integer) -> Array[Printer] def add_printer(ident_or_tags:, token_code:, lineno:) @printers << Printer.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno) end + # @rbs (ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], token_code: Lexer::Token::UserCode, lineno: Integer) -> Array[ErrorToken] def add_error_token(ident_or_tags:, token_code:, lineno:) @error_tokens << ErrorToken.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno) end + # @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag) -> Array[Type] def add_type(id:, tag:) @types << Type.new(id: id, tag: tag) end - def add_nonassoc(sym, precedence) - set_precedence(sym, Precedence.new(type: :nonassoc, precedence: precedence)) + # @rbs (Grammar::Symbol sym, Integer precedence, String s_value, Integer lineno) -> Precedence + def add_nonassoc(sym, precedence, s_value, lineno) + set_precedence(sym, Precedence.new(symbol: sym, s_value: s_value, type: :nonassoc, precedence: precedence, lineno: lineno)) + end + + # @rbs (Grammar::Symbol sym, Integer precedence, String s_value, Integer lineno) -> Precedence + def add_left(sym, precedence, s_value, lineno) + set_precedence(sym, Precedence.new(symbol: sym, s_value: s_value, type: :left, precedence: precedence, lineno: lineno)) end - def add_left(sym, precedence) - set_precedence(sym, Precedence.new(type: :left, precedence: precedence)) + # @rbs (Grammar::Symbol sym, Integer precedence, String s_value, Integer lineno) -> Precedence + def add_right(sym, precedence, s_value, lineno) + set_precedence(sym, Precedence.new(symbol: sym, s_value: s_value, type: :right, precedence: precedence, lineno: lineno)) end - def add_right(sym, precedence) - set_precedence(sym, Precedence.new(type: :right, precedence: precedence)) + # @rbs (Grammar::Symbol sym, Integer precedence, String s_value, Integer lineno) -> Precedence + def add_precedence(sym, precedence, s_value, lineno) + set_precedence(sym, Precedence.new(symbol: sym, s_value: s_value, type: :precedence, precedence: precedence, lineno: lineno)) end - def add_precedence(sym, precedence) - set_precedence(sym, Precedence.new(type: :precedence, precedence: precedence)) + # @rbs (Lrama::Lexer::Token::Base id) -> Lrama::Lexer::Token::Base + def set_start_nterm(id) + # When multiple `%start` directives are defined, Bison does not generate an error, + # whereas Lrama does generate an error. + # Related Bison's specification are + # refs: https://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html + if @start_nterm.nil? + @start_nterm = id + else + start = @start_nterm #: Lrama::Lexer::Token::Base + raise "Start non-terminal is already set to #{start.s_value} (line: #{start.first_line}). Cannot set to #{id.s_value} (line: #{id.first_line})." + end end + # @rbs (Grammar::Symbol sym, Precedence precedence) -> (Precedence | bot) def set_precedence(sym, precedence) - raise "" if sym.nterm? + @precedences << precedence sym.precedence = precedence end + # @rbs (Grammar::Code::NoReferenceCode code, Integer lineno) -> Union def set_union(code, lineno) @union = Union.new(code: code, lineno: lineno) end + # @rbs (RuleBuilder builder) -> Array[RuleBuilder] def add_rule_builder(builder) @rule_builders << builder end - def add_parameterizing_rule(rule) - @parameterizing_rule_resolver.add_parameterizing_rule(rule) + # @rbs (Parameterized::Rule rule) -> Array[Parameterized::Rule] + def add_parameterized_rule(rule) + @parameterized_resolver.add_rule(rule) end - def parameterizing_rules - @parameterizing_rule_resolver.rules + # @rbs () -> Array[Parameterized::Rule] + def parameterized_rules + @parameterized_resolver.rules end - def insert_before_parameterizing_rules(rules) - @parameterizing_rule_resolver.rules = rules + @parameterizing_rule_resolver.rules + # @rbs (Array[Parameterized::Rule] rules) -> Array[Parameterized::Rule] + def prepend_parameterized_rules(rules) + @parameterized_resolver.rules = rules + @parameterized_resolver.rules end + # @rbs (Integer prologue_first_lineno) -> Integer def prologue_first_lineno=(prologue_first_lineno) @aux.prologue_first_lineno = prologue_first_lineno end + # @rbs (String prologue) -> String def prologue=(prologue) @aux.prologue = prologue end + # @rbs (Integer epilogue_first_lineno) -> Integer def epilogue_first_lineno=(epilogue_first_lineno) @aux.epilogue_first_lineno = epilogue_first_lineno end + # @rbs (String epilogue) -> String def epilogue=(epilogue) @aux.epilogue = epilogue end + # @rbs () -> void def prepare resolve_inline_rules normalize_rules @@ -151,6 +261,7 @@ def prepare fill_default_precedence fill_symbols fill_sym_to_rules + sort_precedence compute_nullable compute_first_set set_locations @@ -159,25 +270,51 @@ def prepare # TODO: More validation methods # # * Validation for no_declared_type_reference + # + # @rbs () -> void def validate! @symbols_resolver.validate! + validate_no_precedence_for_nterm! validate_rule_lhs_is_nterm! + validate_duplicated_precedence! end + # @rbs (Grammar::Symbol sym) -> Array[Rule] def find_rules_by_symbol!(sym) find_rules_by_symbol(sym) || (raise "Rules for #{sym} not found") end + # @rbs (Grammar::Symbol sym) -> Array[Rule]? def find_rules_by_symbol(sym) @sym_to_rules[sym.number] end + # @rbs (String s_value) -> Array[Rule] + def select_rules_by_s_value(s_value) + @rules.select {|rule| rule.lhs.id.s_value == s_value } + end + + # @rbs () -> Array[String] + def unique_rule_s_values + @rules.map {|rule| rule.lhs.id.s_value }.uniq + end + + # @rbs () -> bool def ielr_defined? @define.key?('lr.type') && @define['lr.type'] == 'ielr' end private + # @rbs () -> void + def sort_precedence + @precedences.sort_by! do |prec| + prec.symbol.number + end + @precedences.freeze + end + + # @rbs () -> Array[Grammar::Symbol] def compute_nullable @rules.each do |rule| case @@ -227,6 +364,7 @@ def compute_nullable end end + # @rbs () -> Array[Grammar::Symbol] def compute_first_set terms.each do |term| term.first_set = Set.new([term]).freeze @@ -262,12 +400,14 @@ def compute_first_set end end + # @rbs () -> Array[RuleBuilder] def setup_rules @rule_builders.each do |builder| builder.setup_rules end end + # @rbs () -> Grammar::Symbol def append_special_symbols # YYEMPTY (token_id: -2, number: -2) is added when a template is evaluated # term = add_term(id: Token.new(Token::Ident, "YYEMPTY"), token_id: -2) @@ -298,11 +438,12 @@ def append_special_symbols @accept_symbol = term end + # @rbs () -> void def resolve_inline_rules while @rule_builders.any?(&:has_inline_rules?) do @rule_builders = @rule_builders.flat_map do |builder| if builder.has_inline_rules? - builder.resolve_inline_rules + Inline::Resolver.new(builder).resolve else builder end @@ -310,14 +451,10 @@ def resolve_inline_rules end end + # @rbs () -> void def normalize_rules - # Add $accept rule to the top of rules - rule_builder = @rule_builders.first # : RuleBuilder - lineno = rule_builder ? rule_builder.line : 0 - @rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [rule_builder.lhs, @eof_symbol.id], token_code: nil, lineno: lineno) - + add_accept_rule setup_rules - @rule_builders.each do |builder| builder.rules.each do |rule| add_nterm(id: rule._lhs, tag: rule.lhs_tag) @@ -325,23 +462,42 @@ def normalize_rules end end - @rules.sort_by!(&:id) + nterms.freeze + @rules.sort_by!(&:id).freeze + end + + # Add $accept rule to the top of rules + def add_accept_rule + if @start_nterm + start = @start_nterm #: Lrama::Lexer::Token::Base + @rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [start, @eof_symbol.id], token_code: nil, lineno: start.line) + else + rule_builder = @rule_builders.first #: RuleBuilder + lineno = rule_builder ? rule_builder.line : 0 + lhs = rule_builder.lhs #: Lexer::Token::Base + @rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [lhs, @eof_symbol.id], token_code: nil, lineno: lineno) + end end # Collect symbols from rules + # + # @rbs () -> void def collect_symbols @rules.flat_map(&:_rhs).each do |s| case s when Lrama::Lexer::Token::Char add_term(id: s) - when Lrama::Lexer::Token + when Lrama::Lexer::Token::Base # skip else raise "Unknown class: #{s}" end end + + terms.freeze end + # @rbs () -> void def set_lhs_and_rhs @rules.each do |rule| rule.lhs = token_to_symbol(rule._lhs) if rule._lhs @@ -355,6 +511,8 @@ def set_lhs_and_rhs # Rule inherits precedence from the last term in RHS. # # https://www.gnu.org/software/bison/manual/html_node/How-Precedence.html + # + # @rbs () -> void def fill_default_precedence @rules.each do |rule| # Explicitly specified precedence has the highest priority @@ -369,6 +527,7 @@ def fill_default_precedence end end + # @rbs () -> Array[Grammar::Symbol] def fill_symbols fill_symbol_number fill_nterm_type(@types) @@ -378,6 +537,7 @@ def fill_symbols sort_by_number! end + # @rbs () -> Array[Rule] def fill_sym_to_rules @rules.each do |rule| key = rule.lhs.number @@ -386,13 +546,48 @@ def fill_sym_to_rules end end + # @rbs () -> void + def validate_no_precedence_for_nterm! + errors = [] #: Array[String] + + nterms.each do |nterm| + next if nterm.precedence.nil? + + errors << "[BUG] Precedence #{nterm.name} (line: #{nterm.precedence.lineno}) is defined for nonterminal symbol (line: #{nterm.id.first_line}). Precedence can be defined for only terminal symbol." + end + + return if errors.empty? + + raise errors.join("\n") + end + + # @rbs () -> void def validate_rule_lhs_is_nterm! errors = [] #: Array[String] rules.each do |rule| next if rule.lhs.nterm? - errors << "[BUG] LHS of #{rule.display_name} (line: #{rule.lineno}) is term. It should be nterm." + errors << "[BUG] LHS of #{rule.display_name} (line: #{rule.lineno}) is terminal symbol. It should be nonterminal symbol." + end + + return if errors.empty? + + raise errors.join("\n") + end + + # # @rbs () -> void + def validate_duplicated_precedence! + errors = [] #: Array[String] + seen = {} #: Hash[String, Precedence] + + precedences.each do |prec| + s_value = prec.s_value + if first = seen[s_value] + errors << "%#{prec.type} redeclaration for #{s_value} (line: #{prec.lineno}) previous declaration was %#{first.type} (line: #{first.lineno})" + else + seen[s_value] = prec + end end return if errors.empty? @@ -400,6 +595,7 @@ def validate_rule_lhs_is_nterm! raise errors.join("\n") end + # @rbs () -> void def set_locations @locations = @locations || @rules.any? {|rule| rule.contains_at_reference? } end diff --git a/tool/lrama/lib/lrama/grammar/auxiliary.rb b/tool/lrama/lib/lrama/grammar/auxiliary.rb index 2bacee6f1a87e7..76cfb74d4d5767 100644 --- a/tool/lrama/lib/lrama/grammar/auxiliary.rb +++ b/tool/lrama/lib/lrama/grammar/auxiliary.rb @@ -1,9 +1,14 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar # Grammar file information not used by States but by Output - class Auxiliary < Struct.new(:prologue_first_lineno, :prologue, :epilogue_first_lineno, :epilogue, keyword_init: true) + class Auxiliary + attr_accessor :prologue_first_lineno #: Integer? + attr_accessor :prologue #: String? + attr_accessor :epilogue_first_lineno #: Integer? + attr_accessor :epilogue #: String? end end end diff --git a/tool/lrama/lib/lrama/grammar/binding.rb b/tool/lrama/lib/lrama/grammar/binding.rb index 2efb918a0b2205..5940d153a9fa6c 100644 --- a/tool/lrama/lib/lrama/grammar/binding.rb +++ b/tool/lrama/lib/lrama/grammar/binding.rb @@ -4,51 +4,63 @@ module Lrama class Grammar class Binding - # @rbs @actual_args: Array[Lexer::Token] - # @rbs @param_to_arg: Hash[String, Lexer::Token] + # @rbs @actual_args: Array[Lexer::Token::Base] + # @rbs @param_to_arg: Hash[String, Lexer::Token::Base] - # @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> void + # @rbs (Array[Lexer::Token::Base] params, Array[Lexer::Token::Base] actual_args) -> void def initialize(params, actual_args) @actual_args = actual_args - @param_to_arg = map_params_to_args(params, @actual_args) + @param_to_arg = build_param_to_arg(params, @actual_args) end - # @rbs (Lexer::Token sym) -> Lexer::Token + # @rbs (Lexer::Token::Base sym) -> Lexer::Token::Base def resolve_symbol(sym) - if sym.is_a?(Lexer::Token::InstantiateRule) - Lrama::Lexer::Token::InstantiateRule.new( - s_value: sym.s_value, location: sym.location, args: resolved_args(sym), lhs_tag: sym.lhs_tag - ) - else - param_to_arg(sym) - end + return create_instantiate_rule(sym) if sym.is_a?(Lexer::Token::InstantiateRule) + find_arg_for_param(sym) end # @rbs (Lexer::Token::InstantiateRule token) -> String def concatenated_args_str(token) - "#{token.rule_name}_#{token_to_args_s_values(token).join('_')}" + "#{token.rule_name}_#{format_args(token)}" end private - # @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> Hash[String, Lexer::Token] - def map_params_to_args(params, actual_args) - params.zip(actual_args).map do |param, arg| - [param.s_value, arg] - end.to_h + # @rbs (Lexer::Token::InstantiateRule sym) -> Lexer::Token::InstantiateRule + def create_instantiate_rule(sym) + Lrama::Lexer::Token::InstantiateRule.new( + s_value: sym.s_value, + location: sym.location, + args: resolve_args(sym.args), + lhs_tag: sym.lhs_tag + ) end - # @rbs (Lexer::Token::InstantiateRule sym) -> Array[Lexer::Token] - def resolved_args(sym) - sym.args.map { |arg| resolve_symbol(arg) } + # @rbs (Array[Lexer::Token::Base]) -> Array[Lexer::Token::Base] + def resolve_args(args) + args.map { |arg| resolve_symbol(arg) } end - # @rbs (Lexer::Token sym) -> Lexer::Token - def param_to_arg(sym) - if (arg = @param_to_arg[sym.s_value].dup) + # @rbs (Lexer::Token::Base sym) -> Lexer::Token::Base + def find_arg_for_param(sym) + if (arg = @param_to_arg[sym.s_value]&.dup) arg.alias_name = sym.alias_name + arg + else + sym end - arg || sym + end + + # @rbs (Array[Lexer::Token::Base] params, Array[Lexer::Token::Base] actual_args) -> Hash[String, Lexer::Token::Base?] + def build_param_to_arg(params, actual_args) + params.zip(actual_args).map do |param, arg| + [param.s_value, arg] + end.to_h + end + + # @rbs (Lexer::Token::InstantiateRule token) -> String + def format_args(token) + token_to_args_s_values(token).join('_') end # @rbs (Lexer::Token::InstantiateRule token) -> Array[String] diff --git a/tool/lrama/lib/lrama/grammar/code.rb b/tool/lrama/lib/lrama/grammar/code.rb index b6c1cc49e74287..f1b860eeba3f96 100644 --- a/tool/lrama/lib/lrama/grammar/code.rb +++ b/tool/lrama/lib/lrama/grammar/code.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true require "forwardable" @@ -10,17 +11,28 @@ module Lrama class Grammar class Code + # @rbs! + # + # # delegated + # def s_value: -> String + # def line: -> Integer + # def column: -> Integer + # def references: -> Array[Lrama::Grammar::Reference] + extend Forwardable def_delegators "token_code", :s_value, :line, :column, :references - attr_reader :type, :token_code + attr_reader :type #: ::Symbol + attr_reader :token_code #: Lexer::Token::UserCode + # @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode) -> void def initialize(type:, token_code:) @type = type @token_code = token_code end + # @rbs (Code other) -> bool def ==(other) self.class == other.class && self.type == other.type && @@ -28,6 +40,8 @@ def ==(other) end # $$, $n, @$, @n are translated to C code + # + # @rbs () -> String def translated_code t_code = s_value.dup @@ -45,6 +59,7 @@ def translated_code private + # @rbs (Lrama::Grammar::Reference ref) -> bot def reference_to_c(ref) raise NotImplementedError.new("#reference_to_c is not implemented") end diff --git a/tool/lrama/lib/lrama/grammar/code/destructor_code.rb b/tool/lrama/lib/lrama/grammar/code/destructor_code.rb index 794017257c5486..d71b62e5133591 100644 --- a/tool/lrama/lib/lrama/grammar/code/destructor_code.rb +++ b/tool/lrama/lib/lrama/grammar/code/destructor_code.rb @@ -1,9 +1,18 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Code class DestructorCode < Code + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @tag: Lexer::Token::Tag + + # @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, tag: Lexer::Token::Tag) -> void def initialize(type:, token_code:, tag:) super(type: type, token_code: token_code) @tag = tag @@ -17,6 +26,8 @@ def initialize(type:, token_code:, tag:) # * ($1) error # * (@1) error # * ($:1) error + # + # @rbs (Reference ref) -> (String | bot) def reference_to_c(ref) case when ref.type == :dollar && ref.name == "$" # $$ diff --git a/tool/lrama/lib/lrama/grammar/code/initial_action_code.rb b/tool/lrama/lib/lrama/grammar/code/initial_action_code.rb index 02f2badc9e32e8..cb36041524fcc1 100644 --- a/tool/lrama/lib/lrama/grammar/code/initial_action_code.rb +++ b/tool/lrama/lib/lrama/grammar/code/initial_action_code.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama @@ -12,6 +13,8 @@ class InitialActionCode < Code # * ($1) error # * (@1) error # * ($:1) error + # + # @rbs (Reference ref) -> (String | bot) def reference_to_c(ref) case when ref.type == :dollar && ref.name == "$" # $$ diff --git a/tool/lrama/lib/lrama/grammar/code/no_reference_code.rb b/tool/lrama/lib/lrama/grammar/code/no_reference_code.rb index ab12f32e297567..1d39919979a0a6 100644 --- a/tool/lrama/lib/lrama/grammar/code/no_reference_code.rb +++ b/tool/lrama/lib/lrama/grammar/code/no_reference_code.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama @@ -12,6 +13,8 @@ class NoReferenceCode < Code # * ($1) error # * (@1) error # * ($:1) error + # + # @rbs (Reference ref) -> bot def reference_to_c(ref) case when ref.type == :dollar # $$, $n diff --git a/tool/lrama/lib/lrama/grammar/code/printer_code.rb b/tool/lrama/lib/lrama/grammar/code/printer_code.rb index c0b8d24306ff05..c6e25d523553c3 100644 --- a/tool/lrama/lib/lrama/grammar/code/printer_code.rb +++ b/tool/lrama/lib/lrama/grammar/code/printer_code.rb @@ -1,9 +1,18 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Code class PrinterCode < Code + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @tag: Lexer::Token::Tag + + # @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, tag: Lexer::Token::Tag) -> void def initialize(type:, token_code:, tag:) super(type: type, token_code: token_code) @tag = tag @@ -17,6 +26,8 @@ def initialize(type:, token_code:, tag:) # * ($1) error # * (@1) error # * ($:1) error + # + # @rbs (Reference ref) -> (String | bot) def reference_to_c(ref) case when ref.type == :dollar && ref.name == "$" # $$ diff --git a/tool/lrama/lib/lrama/grammar/code/rule_action.rb b/tool/lrama/lib/lrama/grammar/code/rule_action.rb index 363ecdf25dfc19..e71e93e5a5bb8d 100644 --- a/tool/lrama/lib/lrama/grammar/code/rule_action.rb +++ b/tool/lrama/lib/lrama/grammar/code/rule_action.rb @@ -1,9 +1,18 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Code class RuleAction < Code + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @rule: Rule + + # @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, rule: Rule) -> void def initialize(type:, token_code:, rule:) super(type: type, token_code: token_code) @rule = rule @@ -38,6 +47,8 @@ def initialize(type:, token_code:, rule:) # "Position in grammar" $1 # "Index for yyvsp" 0 # "$:n" $:1 + # + # @rbs (Reference ref) -> String def reference_to_c(ref) case when ref.type == :dollar && ref.name == "$" # $$ @@ -66,6 +77,7 @@ def reference_to_c(ref) end end + # @rbs () -> Integer def position_in_rhs # If rule is not derived rule, User Code is only action at # the end of rule RHS. In such case, the action is located on @@ -74,15 +86,20 @@ def position_in_rhs end # If this is midrule action, RHS is an RHS of the original rule. + # + # @rbs () -> Array[Grammar::Symbol] def rhs (@rule.original_rule || @rule).rhs end # Unlike `rhs`, LHS is always an LHS of the rule. + # + # @rbs () -> Grammar::Symbol def lhs @rule.lhs end + # @rbs (Reference ref) -> bot def raise_tag_not_found_error(ref) raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'" end diff --git a/tool/lrama/lib/lrama/grammar/counter.rb b/tool/lrama/lib/lrama/grammar/counter.rb index dc91b87b711f2d..ced934309d7d15 100644 --- a/tool/lrama/lib/lrama/grammar/counter.rb +++ b/tool/lrama/lib/lrama/grammar/counter.rb @@ -1,12 +1,22 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Counter + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @number: Integer + + # @rbs (Integer number) -> void def initialize(number) @number = number end + # @rbs () -> Integer def increment n = @number @number += 1 diff --git a/tool/lrama/lib/lrama/grammar/destructor.rb b/tool/lrama/lib/lrama/grammar/destructor.rb index a2b6fde0ed5b3d..0ce8611e776c3b 100644 --- a/tool/lrama/lib/lrama/grammar/destructor.rb +++ b/tool/lrama/lib/lrama/grammar/destructor.rb @@ -1,8 +1,21 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) + class Destructor + attr_reader :ident_or_tags #: Array[Lexer::Token::Ident|Lexer::Token::Tag] + attr_reader :token_code #: Lexer::Token::UserCode + attr_reader :lineno #: Integer + + # @rbs (ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], token_code: Lexer::Token::UserCode, lineno: Integer) -> void + def initialize(ident_or_tags:, token_code:, lineno:) + @ident_or_tags = ident_or_tags + @token_code = token_code + @lineno = lineno + end + + # @rbs (Lexer::Token::Tag tag) -> String def translated_code(tag) Code::DestructorCode.new(type: :destructor, token_code: token_code, tag: tag).translated_code end diff --git a/tool/lrama/lib/lrama/grammar/error_token.rb b/tool/lrama/lib/lrama/grammar/error_token.rb index 50eaafeebc66fd..9d9ed54ae20edc 100644 --- a/tool/lrama/lib/lrama/grammar/error_token.rb +++ b/tool/lrama/lib/lrama/grammar/error_token.rb @@ -1,8 +1,21 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class ErrorToken < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) + class ErrorToken + attr_reader :ident_or_tags #: Array[Lexer::Token::Ident | Lexer::Token::Tag] + attr_reader :token_code #: Lexer::Token::UserCode + attr_reader :lineno #: Integer + + # @rbs (ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], token_code: Lexer::Token::UserCode, lineno: Integer) -> void + def initialize(ident_or_tags:, token_code:, lineno:) + @ident_or_tags = ident_or_tags + @token_code = token_code + @lineno = lineno + end + + # @rbs (Lexer::Token::Tag tag) -> String def translated_code(tag) Code::PrinterCode.new(type: :error_token, token_code: token_code, tag: tag).translated_code end diff --git a/tool/lrama/lib/lrama/grammar/inline.rb b/tool/lrama/lib/lrama/grammar/inline.rb new file mode 100644 index 00000000000000..c02ab6002ba1f8 --- /dev/null +++ b/tool/lrama/lib/lrama/grammar/inline.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'inline/resolver' diff --git a/tool/lrama/lib/lrama/grammar/inline/resolver.rb b/tool/lrama/lib/lrama/grammar/inline/resolver.rb new file mode 100644 index 00000000000000..aca689ccfb4325 --- /dev/null +++ b/tool/lrama/lib/lrama/grammar/inline/resolver.rb @@ -0,0 +1,80 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Grammar + class Inline + class Resolver + # @rbs (Lrama::Grammar::RuleBuilder rule_builder) -> void + def initialize(rule_builder) + @rule_builder = rule_builder + end + + # @rbs () -> Array[Lrama::Grammar::RuleBuilder] + def resolve + resolved_builders = [] #: Array[Lrama::Grammar::RuleBuilder] + @rule_builder.rhs.each_with_index do |token, i| + if (rule = @rule_builder.parameterized_resolver.find_inline(token)) + rule.rhs.each do |rhs| + builder = build_rule(rhs, token, i, rule) + resolved_builders << builder + end + break + end + end + resolved_builders + end + + private + + # @rbs (Lrama::Grammar::Parameterized::Rhs rhs, Lrama::Lexer::Token token, Integer index, Lrama::Grammar::Parameterized::Rule rule) -> Lrama::Grammar::RuleBuilder + def build_rule(rhs, token, index, rule) + builder = RuleBuilder.new( + @rule_builder.rule_counter, + @rule_builder.midrule_action_counter, + @rule_builder.parameterized_resolver, + lhs_tag: @rule_builder.lhs_tag + ) + resolve_rhs(builder, rhs, index, token, rule) + builder.lhs = @rule_builder.lhs + builder.line = @rule_builder.line + builder.precedence_sym = @rule_builder.precedence_sym + builder.user_code = replace_user_code(rhs, index) + builder + end + + # @rbs (Lrama::Grammar::RuleBuilder builder, Lrama::Grammar::Parameterized::Rhs rhs, Integer index, Lrama::Lexer::Token token, Lrama::Grammar::Parameterized::Rule rule) -> void + def resolve_rhs(builder, rhs, index, token, rule) + @rule_builder.rhs.each_with_index do |tok, i| + if i == index + rhs.symbols.each do |sym| + if token.is_a?(Lexer::Token::InstantiateRule) + bindings = Binding.new(rule.parameters, token.args) + builder.add_rhs(bindings.resolve_symbol(sym)) + else + builder.add_rhs(sym) + end + end + else + builder.add_rhs(tok) + end + end + end + + # @rbs (Lrama::Grammar::Parameterized::Rhs rhs, Integer index) -> Lrama::Lexer::Token::UserCode + def replace_user_code(rhs, index) + user_code = @rule_builder.user_code + return user_code if rhs.user_code.nil? || user_code.nil? + + code = user_code.s_value.gsub(/\$#{index + 1}/, rhs.user_code.s_value) + user_code.references.each do |ref| + next if ref.index.nil? || ref.index <= index # nil は $$ の場合 + code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (rhs.symbols.count - 1)}") + code = code.gsub(/@#{ref.index}/, "@#{ref.index + (rhs.symbols.count - 1)}") + end + Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location) + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/grammar/parameterized.rb b/tool/lrama/lib/lrama/grammar/parameterized.rb new file mode 100644 index 00000000000000..48db3433f379a8 --- /dev/null +++ b/tool/lrama/lib/lrama/grammar/parameterized.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative 'parameterized/resolver' +require_relative 'parameterized/rhs' +require_relative 'parameterized/rule' diff --git a/tool/lrama/lib/lrama/grammar/parameterizing_rule/resolver.rb b/tool/lrama/lib/lrama/grammar/parameterized/resolver.rb similarity index 60% rename from tool/lrama/lib/lrama/grammar/parameterizing_rule/resolver.rb rename to tool/lrama/lib/lrama/grammar/parameterized/resolver.rb index 06f2f1cef7b9e4..558f3081906b3f 100644 --- a/tool/lrama/lib/lrama/grammar/parameterizing_rule/resolver.rb +++ b/tool/lrama/lib/lrama/grammar/parameterized/resolver.rb @@ -1,40 +1,49 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class ParameterizingRule + class Parameterized class Resolver - attr_accessor :rules, :created_lhs_list + attr_accessor :rules #: Array[Rule] + attr_accessor :created_lhs_list #: Array[Lexer::Token::Base] + # @rbs () -> void def initialize @rules = [] @created_lhs_list = [] end - def add_parameterizing_rule(rule) + # @rbs (Rule rule) -> Array[Rule] + def add_rule(rule) @rules << rule end + # @rbs (Lexer::Token::InstantiateRule token) -> Rule? def find_rule(token) select_rules(@rules, token).last end + # @rbs (Lexer::Token::Base token) -> Rule? def find_inline(token) - @rules.reverse.find { |rule| rule.name == token.s_value && rule.is_inline } + @rules.reverse.find { |rule| rule.name == token.s_value && rule.inline? } end + # @rbs (String lhs_s_value) -> Lexer::Token::Base? def created_lhs(lhs_s_value) @created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value } end + # @rbs () -> Array[Rule] def redefined_rules @rules.select { |rule| @rules.count { |r| r.name == rule.name && r.required_parameters_count == rule.required_parameters_count } > 1 } end private + # @rbs (Array[Rule] rules, Lexer::Token::InstantiateRule token) -> Array[Rule] def select_rules(rules, token) - rules = select_not_inline_rules(rules) + rules = reject_inline_rules(rules) rules = select_rules_by_name(rules, token.rule_name) rules = rules.select { |rule| rule.required_parameters_count == token.args_count } if rules.empty? @@ -44,14 +53,16 @@ def select_rules(rules, token) end end - def select_not_inline_rules(rules) - rules.select { |rule| !rule.is_inline } + # @rbs (Array[Rule] rules) -> Array[Rule] + def reject_inline_rules(rules) + rules.reject(&:inline?) end + # @rbs (Array[Rule] rules, String rule_name) -> Array[Rule] def select_rules_by_name(rules, rule_name) rules = rules.select { |rule| rule.name == rule_name } if rules.empty? - raise "Parameterizing rule does not exist. `#{rule_name}`" + raise "Parameterized rule does not exist. `#{rule_name}`" else rules end diff --git a/tool/lrama/lib/lrama/grammar/parameterizing_rule/rhs.rb b/tool/lrama/lib/lrama/grammar/parameterized/rhs.rb similarity index 73% rename from tool/lrama/lib/lrama/grammar/parameterizing_rule/rhs.rb rename to tool/lrama/lib/lrama/grammar/parameterized/rhs.rb index f60781c0534905..663de49100341d 100644 --- a/tool/lrama/lib/lrama/grammar/parameterizing_rule/rhs.rb +++ b/tool/lrama/lib/lrama/grammar/parameterized/rhs.rb @@ -1,17 +1,22 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class ParameterizingRule + class Parameterized class Rhs - attr_accessor :symbols, :user_code, :precedence_sym + attr_accessor :symbols #: Array[Lexer::Token::Base] + attr_accessor :user_code #: Lexer::Token::UserCode? + attr_accessor :precedence_sym #: Grammar::Symbol? + # @rbs () -> void def initialize @symbols = [] @user_code = nil @precedence_sym = nil end + # @rbs (Grammar::Binding bindings) -> Lexer::Token::UserCode? def resolve_user_code(bindings) return unless user_code diff --git a/tool/lrama/lib/lrama/grammar/parameterized/rule.rb b/tool/lrama/lib/lrama/grammar/parameterized/rule.rb new file mode 100644 index 00000000000000..7048be3cffc377 --- /dev/null +++ b/tool/lrama/lib/lrama/grammar/parameterized/rule.rb @@ -0,0 +1,36 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Grammar + class Parameterized + class Rule + attr_reader :name #: String + attr_reader :parameters #: Array[Lexer::Token::Base] + attr_reader :rhs #: Array[Rhs] + attr_reader :required_parameters_count #: Integer + attr_reader :tag #: Lexer::Token::Tag? + + # @rbs (String name, Array[Lexer::Token::Base] parameters, Array[Rhs] rhs, tag: Lexer::Token::Tag?, is_inline: bool) -> void + def initialize(name, parameters, rhs, tag: nil, is_inline: false) + @name = name + @parameters = parameters + @rhs = rhs + @tag = tag + @is_inline = is_inline + @required_parameters_count = parameters.count + end + + # @rbs () -> String + def to_s + "#{@name}(#{@parameters.map(&:s_value).join(', ')})" + end + + # @rbs () -> bool + def inline? + @is_inline + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/grammar/parameterizing_rule.rb b/tool/lrama/lib/lrama/grammar/parameterizing_rule.rb deleted file mode 100644 index ddc1a467ce974e..00000000000000 --- a/tool/lrama/lib/lrama/grammar/parameterizing_rule.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require_relative 'parameterizing_rule/resolver' -require_relative 'parameterizing_rule/rhs' -require_relative 'parameterizing_rule/rule' diff --git a/tool/lrama/lib/lrama/grammar/parameterizing_rule/rule.rb b/tool/lrama/lib/lrama/grammar/parameterizing_rule/rule.rb deleted file mode 100644 index cc200d2fb60da9..00000000000000 --- a/tool/lrama/lib/lrama/grammar/parameterizing_rule/rule.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Grammar - class ParameterizingRule - class Rule - attr_reader :name, :parameters, :rhs_list, :required_parameters_count, :tag, :is_inline - - def initialize(name, parameters, rhs_list, tag: nil, is_inline: false) - @name = name - @parameters = parameters - @rhs_list = rhs_list - @tag = tag - @is_inline = is_inline - @required_parameters_count = parameters.count - end - - def to_s - "#{@name}(#{@parameters.map(&:s_value).join(', ')})" - end - end - end - end -end diff --git a/tool/lrama/lib/lrama/grammar/percent_code.rb b/tool/lrama/lib/lrama/grammar/percent_code.rb index 416a2d27534b97..9afb903056dff4 100644 --- a/tool/lrama/lib/lrama/grammar/percent_code.rb +++ b/tool/lrama/lib/lrama/grammar/percent_code.rb @@ -1,10 +1,21 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class PercentCode - attr_reader :name, :code + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @name: String + # @code: String + attr_reader :name #: String + attr_reader :code #: String + + # @rbs (String name, String code) -> void def initialize(name, code) @name = name @code = code diff --git a/tool/lrama/lib/lrama/grammar/precedence.rb b/tool/lrama/lib/lrama/grammar/precedence.rb index 13cf960c32a5f4..b4c6403372dc0b 100644 --- a/tool/lrama/lib/lrama/grammar/precedence.rb +++ b/tool/lrama/lib/lrama/grammar/precedence.rb @@ -1,13 +1,55 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class Precedence < Struct.new(:type, :precedence, keyword_init: true) + class Precedence < Struct.new(:type, :symbol, :precedence, :s_value, :lineno, keyword_init: true) include Comparable + # @rbs! + # type type_enum = :left | :right | :nonassoc | :precedence + # + # attr_accessor type: type_enum + # attr_accessor symbol: Grammar::Symbol + # attr_accessor precedence: Integer + # attr_accessor s_value: String + # attr_accessor lineno: Integer + # + # def initialize: (?type: type_enum, ?symbol: Grammar::Symbol, ?precedence: Integer, ?s_value: ::String, ?lineno: Integer) -> void + attr_reader :used_by_lalr #: Array[State::ResolvedConflict] + attr_reader :used_by_ielr #: Array[State::ResolvedConflict] + + # @rbs (Precedence other) -> Integer def <=>(other) self.precedence <=> other.precedence end + + # @rbs (State::ResolvedConflict resolved_conflict) -> void + def mark_used_by_lalr(resolved_conflict) + @used_by_lalr ||= [] #: Array[State::ResolvedConflict] + @used_by_lalr << resolved_conflict + end + + # @rbs (State::ResolvedConflict resolved_conflict) -> void + def mark_used_by_ielr(resolved_conflict) + @used_by_ielr ||= [] #: Array[State::ResolvedConflict] + @used_by_ielr << resolved_conflict + end + + # @rbs () -> bool + def used_by? + used_by_lalr? || used_by_ielr? + end + + # @rbs () -> bool + def used_by_lalr? + !@used_by_lalr.nil? && !@used_by_lalr.empty? + end + + # @rbs () -> bool + def used_by_ielr? + !@used_by_ielr.nil? && !@used_by_ielr.empty? + end end end end diff --git a/tool/lrama/lib/lrama/grammar/printer.rb b/tool/lrama/lib/lrama/grammar/printer.rb index b78459e819a7bf..490fe701dbafe7 100644 --- a/tool/lrama/lib/lrama/grammar/printer.rb +++ b/tool/lrama/lib/lrama/grammar/printer.rb @@ -1,8 +1,17 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Printer < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) + # @rbs! + # attr_accessor ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag] + # attr_accessor token_code: Lexer::Token::UserCode + # attr_accessor lineno: Integer + # + # def initialize: (?ident_or_tags: Array[Lexer::Token::Ident|Lexer::Token::Tag], ?token_code: Lexer::Token::UserCode, ?lineno: Integer) -> void + + # @rbs (Lexer::Token::Tag tag) -> String def translated_code(tag) Code::PrinterCode.new(type: :printer, token_code: token_code, tag: tag).translated_code end diff --git a/tool/lrama/lib/lrama/grammar/reference.rb b/tool/lrama/lib/lrama/grammar/reference.rb index b044516bdb9a83..7e3badfecc3911 100644 --- a/tool/lrama/lib/lrama/grammar/reference.rb +++ b/tool/lrama/lib/lrama/grammar/reference.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama @@ -8,6 +9,18 @@ class Grammar # index: Integer # ex_tag: "$1" (Optional) class Reference < Struct.new(:type, :name, :number, :index, :ex_tag, :first_column, :last_column, keyword_init: true) + # @rbs! + # attr_accessor type: ::Symbol + # attr_accessor name: String + # attr_accessor number: Integer + # attr_accessor index: Integer + # attr_accessor ex_tag: Lexer::Token::Base? + # attr_accessor first_column: Integer + # attr_accessor last_column: Integer + # + # def initialize: (type: ::Symbol, ?name: String, ?number: Integer, ?index: Integer, ?ex_tag: Lexer::Token::Base?, first_column: Integer, last_column: Integer) -> void + + # @rbs () -> (String|Integer) def value name || number end diff --git a/tool/lrama/lib/lrama/grammar/rule.rb b/tool/lrama/lib/lrama/grammar/rule.rb index 445752ae0dd952..d00d6a88830ae9 100644 --- a/tool/lrama/lib/lrama/grammar/rule.rb +++ b/tool/lrama/lib/lrama/grammar/rule.rb @@ -1,11 +1,38 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar # _rhs holds original RHS element. Use rhs to refer to Symbol. class Rule < Struct.new(:id, :_lhs, :lhs, :lhs_tag, :_rhs, :rhs, :token_code, :position_in_original_rule_rhs, :nullable, :precedence_sym, :lineno, keyword_init: true) - attr_accessor :original_rule + # @rbs! + # + # interface _DelegatedMethods + # def lhs: -> Grammar::Symbol + # def rhs: -> Array[Grammar::Symbol] + # end + # + # attr_accessor id: Integer + # attr_accessor _lhs: Lexer::Token::Base + # attr_accessor lhs: Grammar::Symbol + # attr_accessor lhs_tag: Lexer::Token::Tag? + # attr_accessor _rhs: Array[Lexer::Token::Base] + # attr_accessor rhs: Array[Grammar::Symbol] + # attr_accessor token_code: Lexer::Token::UserCode? + # attr_accessor position_in_original_rule_rhs: Integer + # attr_accessor nullable: bool + # attr_accessor precedence_sym: Grammar::Symbol? + # attr_accessor lineno: Integer? + # + # def initialize: ( + # ?id: Integer, ?_lhs: Lexer::Token::Base?, ?lhs: Lexer::Token::Base, ?lhs_tag: Lexer::Token::Tag?, ?_rhs: Array[Lexer::Token::Base], ?rhs: Array[Grammar::Symbol], + # ?token_code: Lexer::Token::UserCode?, ?position_in_original_rule_rhs: Integer?, ?nullable: bool, + # ?precedence_sym: Grammar::Symbol?, ?lineno: Integer? + # ) -> void + attr_accessor :original_rule #: Rule + + # @rbs (Rule other) -> bool def ==(other) self.class == other.class && self.lhs == other.lhs && @@ -18,12 +45,14 @@ def ==(other) self.lineno == other.lineno end + # @rbs () -> String def display_name l = lhs.id.s_value r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(" ") "#{l} -> #{r}" end + # @rbs () -> String def display_name_without_action l = lhs.id.s_value r = empty_rule? ? "ε" : rhs.map do |r| @@ -33,7 +62,18 @@ def display_name_without_action "#{l} -> #{r}" end + # @rbs () -> (RailroadDiagrams::Skip | RailroadDiagrams::Sequence) + def to_diagrams + if rhs.empty? + RailroadDiagrams::Skip.new + else + RailroadDiagrams::Sequence.new(*rhs_to_diagram) + end + end + # Used by #user_actions + # + # @rbs () -> String def as_comment l = lhs.id.s_value r = empty_rule? ? "%empty" : rhs.map(&:display_name).join(" ") @@ -41,35 +81,55 @@ def as_comment "#{l}: #{r}" end + # @rbs () -> String def with_actions "#{display_name} {#{token_code&.s_value}}" end # opt_nl: ε <-- empty_rule # | '\n' <-- not empty_rule + # + # @rbs () -> bool def empty_rule? rhs.empty? end + # @rbs () -> Precedence? def precedence precedence_sym&.precedence end + # @rbs () -> bool def initial_rule? id == 0 end + # @rbs () -> String? def translated_code return nil unless token_code Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code end + # @rbs () -> bool def contains_at_reference? return false unless token_code token_code.references.any? {|r| r.type == :at } end + + private + + # @rbs () -> Array[(RailroadDiagrams::Terminal | RailroadDiagrams::NonTerminal)] + def rhs_to_diagram + rhs.map do |r| + if r.term + RailroadDiagrams::Terminal.new(r.id.s_value) + else + RailroadDiagrams::NonTerminal.new(r.id.s_value) + end + end + end end end end diff --git a/tool/lrama/lib/lrama/grammar/rule_builder.rb b/tool/lrama/lib/lrama/grammar/rule_builder.rb index 481a3780f49aa5..34fdca6c86bf45 100644 --- a/tool/lrama/lib/lrama/grammar/rule_builder.rb +++ b/tool/lrama/lib/lrama/grammar/rule_builder.rb @@ -1,15 +1,38 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class RuleBuilder - attr_accessor :lhs, :line - attr_reader :lhs_tag, :rhs, :user_code, :precedence_sym - - def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolver, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false) + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @position_in_original_rule_rhs: Integer? + # @skip_preprocess_references: bool + # @rules: Array[Rule] + # @rule_builders_for_parameterized: Array[RuleBuilder] + # @rule_builders_for_derived_rules: Array[RuleBuilder] + # @parameterized_rules: Array[Rule] + # @midrule_action_rules: Array[Rule] + # @replaced_rhs: Array[Lexer::Token::Base]? + + attr_accessor :lhs #: Lexer::Token::Base? + attr_accessor :line #: Integer? + attr_reader :rule_counter #: Counter + attr_reader :midrule_action_counter #: Counter + attr_reader :parameterized_resolver #: Grammar::Parameterized::Resolver + attr_reader :lhs_tag #: Lexer::Token::Tag? + attr_reader :rhs #: Array[Lexer::Token::Base] + attr_reader :user_code #: Lexer::Token::UserCode? + attr_reader :precedence_sym #: Grammar::Symbol? + + # @rbs (Counter rule_counter, Counter midrule_action_counter, Grammar::Parameterized::Resolver parameterized_resolver, ?Integer position_in_original_rule_rhs, ?lhs_tag: Lexer::Token::Tag?, ?skip_preprocess_references: bool) -> void + def initialize(rule_counter, midrule_action_counter, parameterized_resolver, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false) @rule_counter = rule_counter @midrule_action_counter = midrule_action_counter - @parameterizing_rule_resolver = parameterizing_rule_resolver + @parameterized_resolver = parameterized_resolver @position_in_original_rule_rhs = position_in_original_rule_rhs @skip_preprocess_references = skip_preprocess_references @@ -20,12 +43,13 @@ def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolve @precedence_sym = nil @line = nil @rules = [] - @rule_builders_for_parameterizing_rules = [] + @rule_builders_for_parameterized = [] @rule_builders_for_derived_rules = [] - @parameterizing_rules = [] + @parameterized_rules = [] @midrule_action_rules = [] end + # @rbs (Lexer::Token::Base rhs) -> void def add_rhs(rhs) @line ||= rhs.line @@ -34,6 +58,7 @@ def add_rhs(rhs) @rhs << rhs end + # @rbs (Lexer::Token::UserCode? user_code) -> void def user_code=(user_code) @line ||= user_code&.line @@ -42,72 +67,59 @@ def user_code=(user_code) @user_code = user_code end + # @rbs (Grammar::Symbol? precedence_sym) -> void def precedence_sym=(precedence_sym) flush_user_code @precedence_sym = precedence_sym end + # @rbs () -> void def complete_input freeze_rhs end + # @rbs () -> void def setup_rules preprocess_references unless @skip_preprocess_references process_rhs + resolve_inline_rules build_rules end + # @rbs () -> Array[Grammar::Rule] def rules - @parameterizing_rules + @midrule_action_rules + @rules + @parameterized_rules + @midrule_action_rules + @rules end + # @rbs () -> bool def has_inline_rules? - rhs.any? { |token| @parameterizing_rule_resolver.find_inline(token) } - end - - def resolve_inline_rules - resolved_builders = [] #: Array[RuleBuilder] - rhs.each_with_index do |token, i| - if (inline_rule = @parameterizing_rule_resolver.find_inline(token)) - inline_rule.rhs_list.each do |inline_rhs| - rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: lhs_tag) - if token.is_a?(Lexer::Token::InstantiateRule) - resolve_inline_rhs(rule_builder, inline_rhs, i, Binding.new(inline_rule.parameters, token.args)) - else - resolve_inline_rhs(rule_builder, inline_rhs, i) - end - rule_builder.lhs = lhs - rule_builder.line = line - rule_builder.precedence_sym = precedence_sym - rule_builder.user_code = replace_inline_user_code(inline_rhs, i) - resolved_builders << rule_builder - end - break - end - end - resolved_builders + rhs.any? { |token| @parameterized_resolver.find_inline(token) } end private + # @rbs () -> void def freeze_rhs @rhs.freeze end + # @rbs () -> void def preprocess_references numberize_references end + # @rbs () -> void def build_rules - tokens = @replaced_rhs + tokens = @replaced_rhs #: Array[Lexer::Token::Base] + return if tokens.any? { |t| @parameterized_resolver.find_inline(t) } rule = Rule.new( id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code, position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line ) @rules = [rule] - @parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder| + @parameterized_rules = @rule_builders_for_parameterized.map do |rule_builder| rule_builder.rules end.flatten @midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder| @@ -120,31 +132,33 @@ def build_rules # rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on. # `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`. + # + # @rbs () -> void def process_rhs return if @replaced_rhs - @replaced_rhs = [] + replaced_rhs = [] #: Array[Lexer::Token::Base] rhs.each_with_index do |token, i| case token when Lrama::Lexer::Token::Char - @replaced_rhs << token + replaced_rhs << token when Lrama::Lexer::Token::Ident - @replaced_rhs << token + replaced_rhs << token when Lrama::Lexer::Token::InstantiateRule - parameterizing_rule = @parameterizing_rule_resolver.find_rule(token) - raise "Unexpected token. #{token}" unless parameterizing_rule + parameterized_rule = @parameterized_resolver.find_rule(token) + raise "Unexpected token. #{token}" unless parameterized_rule - bindings = Binding.new(parameterizing_rule.parameters, token.args) + bindings = Binding.new(parameterized_rule.parameters, token.args) lhs_s_value = bindings.concatenated_args_str(token) - if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value)) - @replaced_rhs << created_lhs + if (created_lhs = @parameterized_resolver.created_lhs(lhs_s_value)) + replaced_rhs << created_lhs else lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location) - @replaced_rhs << lhs_token - @parameterizing_rule_resolver.created_lhs_list << lhs_token - parameterizing_rule.rhs_list.each do |r| - rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: token.lhs_tag || parameterizing_rule.tag) + replaced_rhs << lhs_token + @parameterized_resolver.created_lhs_list << lhs_token + parameterized_rule.rhs.each do |r| + rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterized_resolver, lhs_tag: token.lhs_tag || parameterized_rule.tag) rule_builder.lhs = lhs_token r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) } rule_builder.line = line @@ -152,51 +166,48 @@ def process_rhs rule_builder.user_code = r.resolve_user_code(bindings) rule_builder.complete_input rule_builder.setup_rules - @rule_builders_for_parameterizing_rules << rule_builder + @rule_builders_for_parameterized << rule_builder end end when Lrama::Lexer::Token::UserCode prefix = token.referred ? "@" : "$@" tag = token.tag || lhs_tag new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s) - @replaced_rhs << new_token + replaced_rhs << new_token - rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, i, lhs_tag: tag, skip_preprocess_references: true) + rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterized_resolver, i, lhs_tag: tag, skip_preprocess_references: true) rule_builder.lhs = new_token rule_builder.user_code = token rule_builder.complete_input rule_builder.setup_rules @rule_builders_for_derived_rules << rule_builder + when Lrama::Lexer::Token::Empty + # Noop else raise "Unexpected token. #{token}" end end - end - def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil) - rhs.each_with_index do |token, i| - if index == i - inline_rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) } - else - rule_builder.add_rhs(token) - end - end + @replaced_rhs = replaced_rhs end - def replace_inline_user_code(inline_rhs, index) - return user_code if inline_rhs.user_code.nil? - return user_code if user_code.nil? - - code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value) - user_code.references.each do |ref| - next if ref.index.nil? || ref.index <= index # nil is a case for `$$` - code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}") - code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}") + # @rbs () -> void + def resolve_inline_rules + while @rule_builders_for_parameterized.any?(&:has_inline_rules?) do + @rule_builders_for_parameterized = @rule_builders_for_parameterized.flat_map do |rule_builder| + if rule_builder.has_inline_rules? + inlined_builders = Inline::Resolver.new(rule_builder).resolve + inlined_builders.each { |builder| builder.setup_rules } + inlined_builders + else + rule_builder + end + end end - Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location) end + # @rbs () -> void def numberize_references # Bison n'th component is 1-origin (rhs + [user_code]).compact.each.with_index(1) do |token, i| @@ -209,7 +220,10 @@ def numberize_references if ref_name == '$' ref.name = '$' else - candidates = ([lhs] + rhs).each_with_index.select {|token, _i| token.referred_by?(ref_name) } + candidates = ([lhs] + rhs).each_with_index.select do |token, _i| + # @type var token: Lexer::Token::Base + token.referred_by?(ref_name) + end if candidates.size >= 2 token.invalid_ref(ref, "Referring symbol `#{ref_name}` is duplicated.") @@ -244,6 +258,7 @@ def numberize_references end end + # @rbs () -> void def flush_user_code if (c = @user_code) @rhs << c diff --git a/tool/lrama/lib/lrama/grammar/stdlib.y b/tool/lrama/lib/lrama/grammar/stdlib.y index d6e89c908c5139..dd397c9e08d476 100644 --- a/tool/lrama/lib/lrama/grammar/stdlib.y +++ b/tool/lrama/lib/lrama/grammar/stdlib.y @@ -3,26 +3,43 @@ stdlib.y This is lrama's standard library. It provides a number of - parameterizing rule definitions, such as options and lists, + parameterized rule definitions, such as options and lists, that should be useful in a number of situations. **********************************************************************/ +%% + // ------------------------------------------------------------------- // Options /* - * program: option(number) + * program: option(X) + * + * => + * + * program: option_X + * option_X: %empty + * option_X: X + */ +%rule option(X) + : /* empty */ + | X + ; + + +/* + * program: ioption(X) * * => * - * program: option_number - * option_number: %empty - * option_number: number + * program: %empty + * program: X */ -%rule option(X): /* empty */ - | X - ; +%rule %inline ioption(X) + : /* empty */ + | X + ; // ------------------------------------------------------------------- // Sequences @@ -35,8 +52,9 @@ * program: preceded_opening_X * preceded_opening_X: opening X */ -%rule preceded(opening, X): opening X { $$ = $2; } - ; +%rule preceded(opening, X) + : opening X { $$ = $2; } + ; /* * program: terminated(X, closing) @@ -46,8 +64,9 @@ * program: terminated_X_closing * terminated_X_closing: X closing */ -%rule terminated(X, closing): X closing { $$ = $1; } - ; +%rule terminated(X, closing) + : X closing { $$ = $1; } + ; /* * program: delimited(opening, X, closing) @@ -57,66 +76,67 @@ * program: delimited_opening_X_closing * delimited_opening_X_closing: opening X closing */ -%rule delimited(opening, X, closing): opening X closing { $$ = $2; } - ; +%rule delimited(opening, X, closing) + : opening X closing { $$ = $2; } + ; // ------------------------------------------------------------------- // Lists /* - * program: list(number) + * program: list(X) * * => * - * program: list_number - * list_number: %empty - * list_number: list_number number + * program: list_X + * list_X: %empty + * list_X: list_X X */ -%rule list(X): /* empty */ - | list(X) X - ; +%rule list(X) + : /* empty */ + | list(X) X + ; /* - * program: nonempty_list(number) + * program: nonempty_list(X) * * => * - * program: nonempty_list_number - * nonempty_list_number: number - * nonempty_list_number: nonempty_list_number number + * program: nonempty_list_X + * nonempty_list_X: X + * nonempty_list_X: nonempty_list_X X */ -%rule nonempty_list(X): X - | nonempty_list(X) X - ; +%rule nonempty_list(X) + : X + | nonempty_list(X) X + ; /* - * program: separated_nonempty_list(comma, number) + * program: separated_nonempty_list(separator, X) * * => * - * program: separated_nonempty_list_comma_number - * separated_nonempty_list_comma_number: number - * separated_nonempty_list_comma_number: separated_nonempty_list_comma_number comma number + * program: separated_nonempty_list_separator_X + * separated_nonempty_list_separator_X: X + * separated_nonempty_list_separator_X: separated_nonempty_list_separator_X separator X */ -%rule separated_nonempty_list(separator, X): X - | separated_nonempty_list(separator, X) separator X - ; +%rule separated_nonempty_list(separator, X) + : X + | separated_nonempty_list(separator, X) separator X + ; /* - * program: separated_list(comma, number) + * program: separated_list(separator, X) * * => * - * program: separated_list_comma_number - * separated_list_comma_number: option_separated_nonempty_list_comma_number - * option_separated_nonempty_list_comma_number: %empty - * option_separated_nonempty_list_comma_number: separated_nonempty_list_comma_number - * separated_nonempty_list_comma_number: number - * separated_nonempty_list_comma_number: comma separated_nonempty_list_comma_number number + * program: separated_list_separator_X + * separated_list_separator_X: option_separated_nonempty_list_separator_X + * option_separated_nonempty_list_separator_X: %empty + * option_separated_nonempty_list_separator_X: separated_nonempty_list_separator_X + * separated_nonempty_list_separator_X: X + * separated_nonempty_list_separator_X: separator separated_nonempty_list_separator_X X */ -%rule separated_list(separator, X): option(separated_nonempty_list(separator, X)) - ; - -%% - -%union{}; +%rule separated_list(separator, X) + : option(separated_nonempty_list(separator, X)) + ; diff --git a/tool/lrama/lib/lrama/grammar/symbol.rb b/tool/lrama/lib/lrama/grammar/symbol.rb index f9dffcad6c6b96..07aee0c0a20be9 100644 --- a/tool/lrama/lib/lrama/grammar/symbol.rb +++ b/tool/lrama/lib/lrama/grammar/symbol.rb @@ -1,19 +1,35 @@ +# rbs_inline: enabled # frozen_string_literal: true # Symbol is both of nterm and term # `number` is both for nterm and term # `token_id` is tokentype for term, internal sequence number for nterm # -# TODO: Add validation for ASCII code range for Token::Char module Lrama class Grammar class Symbol - attr_accessor :id, :alias_name, :tag, :number, :token_id, :nullable, :precedence, - :printer, :destructor, :error_token, :first_set, :first_set_bitmap - attr_reader :term - attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol + attr_accessor :id #: Lexer::Token::Base + attr_accessor :alias_name #: String? + attr_reader :number #: Integer + attr_accessor :number_bitmap #: Bitmap::bitmap + attr_accessor :tag #: Lexer::Token::Tag? + attr_accessor :token_id #: Integer + attr_accessor :nullable #: bool + attr_accessor :precedence #: Precedence? + attr_accessor :printer #: Printer? + attr_accessor :destructor #: Destructor? + attr_accessor :error_token #: ErrorToken + attr_accessor :first_set #: Set[Grammar::Symbol] + attr_accessor :first_set_bitmap #: Bitmap::bitmap + attr_reader :term #: bool + attr_writer :eof_symbol #: bool + attr_writer :error_symbol #: bool + attr_writer :undef_symbol #: bool + attr_writer :accept_symbol #: bool + # @rbs (id: Lexer::Token::Base, term: bool, ?alias_name: String?, ?number: Integer?, ?tag: Lexer::Token::Tag?, + # ?token_id: Integer?, ?nullable: bool?, ?precedence: Precedence?, ?printer: Printer?) -> void def initialize(id:, term:, alias_name: nil, number: nil, tag: nil, token_id: nil, nullable: nil, precedence: nil, printer: nil, destructor: nil) @id = id @alias_name = alias_name @@ -27,77 +43,105 @@ def initialize(id:, term:, alias_name: nil, number: nil, tag: nil, token_id: nil @destructor = destructor end + # @rbs (Integer) -> void + def number=(number) + @number = number + @number_bitmap = Bitmap::from_integer(number) + end + + # @rbs () -> bool def term? term end + # @rbs () -> bool def nterm? !term end + # @rbs () -> bool def eof_symbol? !!@eof_symbol end + # @rbs () -> bool def error_symbol? !!@error_symbol end + # @rbs () -> bool def undef_symbol? !!@undef_symbol end + # @rbs () -> bool def accept_symbol? !!@accept_symbol end + # @rbs () -> bool + def midrule? + return false if term? + + name.include?("$") || name.include?("@") + end + + # @rbs () -> String + def name + id.s_value + end + + # @rbs () -> String def display_name - alias_name || id.s_value + alias_name || name end # name for yysymbol_kind_t # # See: b4_symbol_kind_base # @type var name: String + # @rbs () -> String def enum_name case when accept_symbol? - name = "YYACCEPT" + res = "YYACCEPT" when eof_symbol? - name = "YYEOF" + res = "YYEOF" when term? && id.is_a?(Lrama::Lexer::Token::Char) - name = number.to_s + display_name + res = number.to_s + display_name when term? && id.is_a?(Lrama::Lexer::Token::Ident) - name = id.s_value - when nterm? && (id.s_value.include?("$") || id.s_value.include?("@")) - name = number.to_s + id.s_value + res = name + when midrule? + res = number.to_s + name when nterm? - name = id.s_value + res = name else raise "Unexpected #{self}" end - "YYSYMBOL_" + name.gsub(/\W+/, "_") + "YYSYMBOL_" + res.gsub(/\W+/, "_") end # comment for yysymbol_kind_t + # + # @rbs () -> String? def comment case when accept_symbol? # YYSYMBOL_YYACCEPT - id.s_value + name when eof_symbol? # YYEOF alias_name when (term? && 0 < token_id && token_id < 128) # YYSYMBOL_3_backslash_, YYSYMBOL_14_ - alias_name || id.s_value - when id.s_value.include?("$") || id.s_value.include?("@") + display_name + when midrule? # YYSYMBOL_21_1 - id.s_value + name else # YYSYMBOL_keyword_class, YYSYMBOL_strings_1 - alias_name || id.s_value + display_name end end end diff --git a/tool/lrama/lib/lrama/grammar/symbols/resolver.rb b/tool/lrama/lib/lrama/grammar/symbols/resolver.rb index 52f4ff90bdf1b8..085a835d2838de 100644 --- a/tool/lrama/lib/lrama/grammar/symbols/resolver.rb +++ b/tool/lrama/lib/lrama/grammar/symbols/resolver.rb @@ -1,24 +1,54 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Symbols class Resolver - attr_reader :terms, :nterms - + # @rbs! + # + # interface _DelegatedMethods + # def symbols: () -> Array[Grammar::Symbol] + # def nterms: () -> Array[Grammar::Symbol] + # def terms: () -> Array[Grammar::Symbol] + # def add_nterm: (id: Lexer::Token::Base, ?alias_name: String?, ?tag: Lexer::Token::Tag?) -> Grammar::Symbol + # def add_term: (id: Lexer::Token::Base, ?alias_name: String?, ?tag: Lexer::Token::Tag?, ?token_id: Integer?, ?replace: bool) -> Grammar::Symbol + # def find_symbol_by_number!: (Integer number) -> Grammar::Symbol + # def find_symbol_by_id!: (Lexer::Token::Base id) -> Grammar::Symbol + # def token_to_symbol: (Lexer::Token::Base token) -> Grammar::Symbol + # def find_symbol_by_s_value!: (::String s_value) -> Grammar::Symbol + # def fill_nterm_type: (Array[Grammar::Type] types) -> void + # def fill_symbol_number: () -> void + # def fill_printer: (Array[Grammar::Printer] printers) -> void + # def fill_destructor: (Array[Destructor] destructors) -> (Destructor | bot) + # def fill_error_token: (Array[Grammar::ErrorToken] error_tokens) -> void + # def sort_by_number!: () -> Array[Grammar::Symbol] + # end + # + # @symbols: Array[Grammar::Symbol]? + # @number: Integer + # @used_numbers: Hash[Integer, bool] + + attr_reader :terms #: Array[Grammar::Symbol] + attr_reader :nterms #: Array[Grammar::Symbol] + + # @rbs () -> void def initialize @terms = [] @nterms = [] end + # @rbs () -> Array[Grammar::Symbol] def symbols @symbols ||= (@terms + @nterms) end + # @rbs () -> Array[Grammar::Symbol] def sort_by_number! symbols.sort_by!(&:number) end + # @rbs (id: Lexer::Token::Base, ?alias_name: String?, ?tag: Lexer::Token::Tag?, ?token_id: Integer?, ?replace: bool) -> Grammar::Symbol def add_term(id:, alias_name: nil, tag: nil, token_id: nil, replace: false) if token_id && (sym = find_symbol_by_token_id(token_id)) if replace @@ -43,6 +73,7 @@ def add_term(id:, alias_name: nil, tag: nil, token_id: nil, replace: false) term end + # @rbs (id: Lexer::Token::Base, ?alias_name: String?, ?tag: Lexer::Token::Tag?) -> Grammar::Symbol def add_nterm(id:, alias_name: nil, tag: nil) if (sym = find_symbol_by_id(id)) return sym @@ -57,32 +88,39 @@ def add_nterm(id:, alias_name: nil, tag: nil) nterm end + # @rbs (::String s_value) -> Grammar::Symbol? def find_term_by_s_value(s_value) terms.find { |s| s.id.s_value == s_value } end + # @rbs (::String s_value) -> Grammar::Symbol? def find_symbol_by_s_value(s_value) symbols.find { |s| s.id.s_value == s_value } end + # @rbs (::String s_value) -> Grammar::Symbol def find_symbol_by_s_value!(s_value) find_symbol_by_s_value(s_value) || (raise "Symbol not found. value: `#{s_value}`") end + # @rbs (Lexer::Token::Base id) -> Grammar::Symbol? def find_symbol_by_id(id) symbols.find do |s| s.id == id || s.alias_name == id.s_value end end + # @rbs (Lexer::Token::Base id) -> Grammar::Symbol def find_symbol_by_id!(id) find_symbol_by_id(id) || (raise "Symbol not found. #{id}") end + # @rbs (Integer token_id) -> Grammar::Symbol? def find_symbol_by_token_id(token_id) symbols.find {|s| s.token_id == token_id } end + # @rbs (Integer number) -> Grammar::Symbol def find_symbol_by_number!(number) sym = symbols[number] @@ -92,6 +130,7 @@ def find_symbol_by_number!(number) sym end + # @rbs () -> void def fill_symbol_number # YYEMPTY = -2 # YYEOF = 0 @@ -102,6 +141,7 @@ def fill_symbol_number fill_nterms_number end + # @rbs (Array[Grammar::Type] types) -> void def fill_nterm_type(types) types.each do |type| nterm = find_nterm_by_id!(type.id) @@ -109,6 +149,7 @@ def fill_nterm_type(types) end end + # @rbs (Array[Grammar::Printer] printers) -> void def fill_printer(printers) symbols.each do |sym| printers.each do |printer| @@ -126,6 +167,7 @@ def fill_printer(printers) end end + # @rbs (Array[Destructor] destructors) -> (Array[Grammar::Symbol] | bot) def fill_destructor(destructors) symbols.each do |sym| destructors.each do |destructor| @@ -143,6 +185,7 @@ def fill_destructor(destructors) end end + # @rbs (Array[Grammar::ErrorToken] error_tokens) -> void def fill_error_token(error_tokens) symbols.each do |sym| error_tokens.each do |token| @@ -160,28 +203,33 @@ def fill_error_token(error_tokens) end end + # @rbs (Lexer::Token::Base token) -> Grammar::Symbol def token_to_symbol(token) case token - when Lrama::Lexer::Token + when Lrama::Lexer::Token::Base find_symbol_by_id!(token) else raise "Unknown class: #{token}" end end + # @rbs () -> void def validate! validate_number_uniqueness! validate_alias_name_uniqueness! + validate_symbols! end private + # @rbs (Lexer::Token::Base id) -> Grammar::Symbol def find_nterm_by_id!(id) @nterms.find do |s| s.id == id end || (raise "Symbol not found. #{id}") end + # @rbs () -> void def fill_terms_number # Character literal in grammar file has # token id corresponding to ASCII code by default, @@ -245,6 +293,7 @@ def fill_terms_number end end + # @rbs () -> void def fill_nterms_number token_id = 0 @@ -266,6 +315,7 @@ def fill_nterms_number end end + # @rbs () -> Hash[Integer, bool] def used_numbers return @used_numbers if defined?(@used_numbers) @@ -276,6 +326,7 @@ def used_numbers @used_numbers end + # @rbs () -> void def validate_number_uniqueness! invalid = symbols.group_by(&:number).select do |number, syms| syms.count > 1 @@ -286,6 +337,7 @@ def validate_number_uniqueness! raise "Symbol number is duplicated. #{invalid}" end + # @rbs () -> void def validate_alias_name_uniqueness! invalid = symbols.select(&:alias_name).group_by(&:alias_name).select do |alias_name, syms| syms.count > 1 @@ -295,6 +347,15 @@ def validate_alias_name_uniqueness! raise "Symbol alias name is duplicated. #{invalid}" end + + # @rbs () -> void + def validate_symbols! + symbols.each { |sym| sym.id.validate } + errors = symbols.map { |sym| sym.id.errors }.flatten.compact + return if errors.empty? + + raise errors.join("\n") + end end end end diff --git a/tool/lrama/lib/lrama/grammar/type.rb b/tool/lrama/lib/lrama/grammar/type.rb index 65537288b310b4..c6317694472e61 100644 --- a/tool/lrama/lib/lrama/grammar/type.rb +++ b/tool/lrama/lib/lrama/grammar/type.rb @@ -1,15 +1,27 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar class Type - attr_reader :id, :tag + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @id: Lexer::Token::Base + # @tag: Lexer::Token::Tag + attr_reader :id #: Lexer::Token::Base + attr_reader :tag #: Lexer::Token::Tag + + # @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag) -> void def initialize(id:, tag:) @id = id @tag = tag end + # @rbs (Grammar::Type other) -> bool def ==(other) self.class == other.class && self.id == other.id && diff --git a/tool/lrama/lib/lrama/grammar/union.rb b/tool/lrama/lib/lrama/grammar/union.rb index 5f1bee0069af23..774cc66fc6617e 100644 --- a/tool/lrama/lib/lrama/grammar/union.rb +++ b/tool/lrama/lib/lrama/grammar/union.rb @@ -1,8 +1,19 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class Grammar - class Union < Struct.new(:code, :lineno, keyword_init: true) + class Union + attr_reader :code #: Grammar::Code::NoReferenceCode + attr_reader :lineno #: Integer + + # @rbs (code: Grammar::Code::NoReferenceCode, lineno: Integer) -> void + def initialize(code:, lineno:) + @code = code + @lineno = lineno + end + + # @rbs () -> String def braces_less_code # Braces is already removed by lexer code.s_value diff --git a/tool/lrama/lib/lrama/grammar_validator.rb b/tool/lrama/lib/lrama/grammar_validator.rb deleted file mode 100644 index 7790499589dfcc..00000000000000 --- a/tool/lrama/lib/lrama/grammar_validator.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class GrammarValidator - def initialize(grammar, states, logger) - @grammar = grammar - @states = states - @logger = logger - end - - def valid? - conflicts_within_threshold? - end - - private - - def conflicts_within_threshold? - return true unless @grammar.expect - - [sr_conflicts_within_threshold(@grammar.expect), rr_conflicts_within_threshold(0)].all? - end - - def sr_conflicts_within_threshold(expected) - return true if expected == @states.sr_conflicts_count - - @logger.error("shift/reduce conflicts: #{@states.sr_conflicts_count} found, #{expected} expected") - false - end - - def rr_conflicts_within_threshold(expected) - return true if expected == @states.rr_conflicts_count - - @logger.error("reduce/reduce conflicts: #{@states.rr_conflicts_count} found, #{expected} expected") - false - end - end -end diff --git a/tool/lrama/lib/lrama/lexer.rb b/tool/lrama/lib/lrama/lexer.rb index c50af82ae4c300..ce98b505a72ae7 100644 --- a/tool/lrama/lib/lrama/lexer.rb +++ b/tool/lrama/lib/lrama/lexer.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true require "strscan" @@ -8,10 +9,26 @@ module Lrama class Lexer - attr_reader :head_line, :head_column, :line - attr_accessor :status, :end_symbol - - SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'].freeze + # @rbs! + # + # type token = lexer_token | c_token + # + # type lexer_token = [String, Token::Token] | + # [::Symbol, Token::Tag] | + # [::Symbol, Token::Char] | + # [::Symbol, Token::Str] | + # [::Symbol, Token::Int] | + # [::Symbol, Token::Ident] + # + # type c_token = [:C_DECLARATION, Token::UserCode] + + attr_reader :head_line #: Integer + attr_reader :head_column #: Integer + attr_reader :line #: Integer + attr_accessor :status #: :initial | :c_declaration + attr_accessor :end_symbol #: String? + + SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'].freeze #: Array[String] PERCENT_TOKENS = %w( %union %token @@ -42,8 +59,11 @@ class Lexer %no-stdlib %inline %locations - ).freeze + %categories + %start + ).freeze #: Array[String] + # @rbs (GrammarFile grammar_file) -> void def initialize(grammar_file) @grammar_file = grammar_file @scanner = StringScanner.new(grammar_file.text) @@ -53,6 +73,7 @@ def initialize(grammar_file) @end_symbol = nil end + # @rbs () -> token? def next_token case @status when :initial @@ -62,10 +83,12 @@ def next_token end end + # @rbs () -> Integer def column @scanner.pos - @head end + # @rbs () -> Location def location Location.new( grammar_file: @grammar_file, @@ -74,13 +97,14 @@ def location ) end + # @rbs () -> lexer_token? def lex_token until @scanner.eos? do case when @scanner.scan(/\n/) newline when @scanner.scan(/\s+/) - # noop + @scanner.matched.count("\n").times { newline } when @scanner.scan(/\/\*/) lex_comment when @scanner.scan(/\/\/.*(?\n)?/) @@ -96,11 +120,11 @@ def lex_token when @scanner.eos? return when @scanner.scan(/#{SYMBOLS.join('|')}/) - return [@scanner.matched, @scanner.matched] + return [@scanner.matched, Lrama::Lexer::Token::Token.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/#{PERCENT_TOKENS.join('|')}/) - return [@scanner.matched, @scanner.matched] + return [@scanner.matched, Lrama::Lexer::Token::Token.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/[\?\+\*]/) - return [@scanner.matched, @scanner.matched] + return [@scanner.matched, Lrama::Lexer::Token::Token.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/<\w+>/) return [:TAG, Lrama::Lexer::Token::Tag.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/'.'/) @@ -108,9 +132,9 @@ def lex_token when @scanner.scan(/'\\\\'|'\\b'|'\\t'|'\\f'|'\\r'|'\\n'|'\\v'|'\\13'/) return [:CHARACTER, Lrama::Lexer::Token::Char.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/".*?"/) - return [:STRING, %Q(#{@scanner.matched})] + return [:STRING, Lrama::Lexer::Token::Str.new(s_value: %Q(#{@scanner.matched}), location: location)] when @scanner.scan(/\d+/) - return [:INTEGER, Integer(@scanner.matched)] + return [:INTEGER, Lrama::Lexer::Token::Int.new(s_value: Integer(@scanner.matched), location: location)] when @scanner.scan(/([a-zA-Z_.][-a-zA-Z0-9_.]*)/) token = Lrama::Lexer::Token::Ident.new(s_value: @scanner.matched, location: location) type = @@ -121,51 +145,53 @@ def lex_token end return [type, token] else - raise ParseError, "Unexpected token: #{@scanner.peek(10).chomp}." + raise ParseError, location.generate_error_message("Unexpected token") # steep:ignore UnknownConstant end end + # @rbs () -> c_token def lex_c_code nested = 0 - code = '' + code = +'' reset_first_position until @scanner.eos? do case when @scanner.scan(/{/) - code += @scanner.matched + code << @scanner.matched nested += 1 when @scanner.scan(/}/) if nested == 0 && @end_symbol == '}' @scanner.unscan return [:C_DECLARATION, Lrama::Lexer::Token::UserCode.new(s_value: code, location: location)] else - code += @scanner.matched + code << @scanner.matched nested -= 1 end when @scanner.check(/#{@end_symbol}/) return [:C_DECLARATION, Lrama::Lexer::Token::UserCode.new(s_value: code, location: location)] when @scanner.scan(/\n/) - code += @scanner.matched + code << @scanner.matched newline when @scanner.scan(/".*?"/) - code += %Q(#{@scanner.matched}) + code << %Q(#{@scanner.matched}) @line += @scanner.matched.count("\n") when @scanner.scan(/'.*?'/) - code += %Q(#{@scanner.matched}) + code << %Q(#{@scanner.matched}) when @scanner.scan(/[^\"'\{\}\n]+/) - code += @scanner.matched - when @scanner.scan(/#{Regexp.escape(@end_symbol)}/) - code += @scanner.matched + code << @scanner.matched + when @scanner.scan(/#{Regexp.escape(@end_symbol)}/) # steep:ignore + code << @scanner.matched else - code += @scanner.getch + code << @scanner.getch end end - raise ParseError, "Unexpected code: #{code}." + raise ParseError, location.generate_error_message("Unexpected code: #{code}") # steep:ignore UnknownConstant end private + # @rbs () -> void def lex_comment until @scanner.eos? do case @@ -178,11 +204,13 @@ def lex_comment end end + # @rbs () -> void def reset_first_position @head_line = line @head_column = column end + # @rbs () -> void def newline @line += 1 @head = @scanner.pos diff --git a/tool/lrama/lib/lrama/lexer/location.rb b/tool/lrama/lib/lrama/lexer/location.rb index defdbf8a0bc575..4465576d53ba4f 100644 --- a/tool/lrama/lib/lrama/lexer/location.rb +++ b/tool/lrama/lib/lrama/lexer/location.rb @@ -69,15 +69,15 @@ def to_s def generate_error_message(error_message) <<~ERROR.chomp #{path}:#{first_line}:#{first_column}: #{error_message} - #{line_with_carets} + #{error_with_carets} ERROR end # @rbs () -> String - def line_with_carets + def error_with_carets <<~TEXT - #{text} - #{carets} + #{formatted_first_lineno} | #{text} + #{line_number_padding} | #{carets_line} TEXT end @@ -89,13 +89,30 @@ def path end # @rbs () -> String - def blanks - (text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ') + def carets_line + leading_whitespace + highlight_marker end # @rbs () -> String - def carets - blanks + '^' * (last_column - first_column) + def leading_whitespace + (text[0...first_column] or raise "Invalid first_column: #{first_column}") + .gsub(/[^\t]/, ' ') + end + + # @rbs () -> String + def highlight_marker + length = last_column - first_column + '^' + '~' * [0, length - 1].max + end + + # @rbs () -> String + def formatted_first_lineno + first_line.to_s.rjust(4) + end + + # @rbs () -> String + def line_number_padding + ' ' * formatted_first_lineno.length end # @rbs () -> String diff --git a/tool/lrama/lib/lrama/lexer/token.rb b/tool/lrama/lib/lrama/lexer/token.rb index 63da8be4a40653..37f77aa06955b6 100644 --- a/tool/lrama/lib/lrama/lexer/token.rb +++ b/tool/lrama/lib/lrama/lexer/token.rb @@ -1,70 +1,20 @@ # rbs_inline: enabled # frozen_string_literal: true +require_relative 'token/base' require_relative 'token/char' +require_relative 'token/empty' require_relative 'token/ident' require_relative 'token/instantiate_rule' +require_relative 'token/int' +require_relative 'token/str' require_relative 'token/tag' +require_relative 'token/token' require_relative 'token/user_code' module Lrama class Lexer - class Token - attr_reader :s_value #: String - attr_reader :location #: Location - attr_accessor :alias_name #: String - attr_accessor :referred #: bool - - # @rbs (s_value: String, ?alias_name: String, ?location: Location) -> void - def initialize(s_value:, alias_name: nil, location: nil) - s_value.freeze - @s_value = s_value - @alias_name = alias_name - @location = location - end - - # @rbs () -> String - def to_s - "value: `#{s_value}`, location: #{location}" - end - - # @rbs (String string) -> bool - def referred_by?(string) - [self.s_value, self.alias_name].compact.include?(string) - end - - # @rbs (Token other) -> bool - def ==(other) - self.class == other.class && self.s_value == other.s_value - end - - # @rbs () -> Integer - def first_line - location.first_line - end - alias :line :first_line - - # @rbs () -> Integer - def first_column - location.first_column - end - alias :column :first_column - - # @rbs () -> Integer - def last_line - location.last_line - end - - # @rbs () -> Integer - def last_column - location.last_column - end - - # @rbs (Lrama::Grammar::Reference ref, String message) -> bot - def invalid_ref(ref, message) - location = self.location.partial_location(ref.first_column, ref.last_column) - raise location.generate_error_message(message) - end + module Token end end end diff --git a/tool/lrama/lib/lrama/lexer/token/base.rb b/tool/lrama/lib/lrama/lexer/token/base.rb new file mode 100644 index 00000000000000..3df93bbc737f7c --- /dev/null +++ b/tool/lrama/lib/lrama/lexer/token/base.rb @@ -0,0 +1,73 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Lexer + module Token + class Base + attr_reader :s_value #: String + attr_reader :location #: Location + attr_accessor :alias_name #: String + attr_accessor :referred #: bool + attr_reader :errors #: Array[String] + + # @rbs (s_value: String, ?alias_name: String, ?location: Location) -> void + def initialize(s_value:, alias_name: nil, location: nil) + s_value.freeze + @s_value = s_value + @alias_name = alias_name + @location = location + @errors = [] + end + + # @rbs () -> String + def to_s + "value: `#{s_value}`, location: #{location}" + end + + # @rbs (String string) -> bool + def referred_by?(string) + [self.s_value, self.alias_name].compact.include?(string) + end + + # @rbs (Lexer::Token::Base other) -> bool + def ==(other) + self.class == other.class && self.s_value == other.s_value + end + + # @rbs () -> Integer + def first_line + location.first_line + end + alias :line :first_line + + # @rbs () -> Integer + def first_column + location.first_column + end + alias :column :first_column + + # @rbs () -> Integer + def last_line + location.last_line + end + + # @rbs () -> Integer + def last_column + location.last_column + end + + # @rbs (Lrama::Grammar::Reference ref, String message) -> bot + def invalid_ref(ref, message) + location = self.location.partial_location(ref.first_column, ref.last_column) + raise location.generate_error_message(message) + end + + # @rbs () -> bool + def validate + true + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/lexer/token/char.rb b/tool/lrama/lib/lrama/lexer/token/char.rb index fcab7a588f5e40..f4ef7c9fbcd90e 100644 --- a/tool/lrama/lib/lrama/lexer/token/char.rb +++ b/tool/lrama/lib/lrama/lexer/token/char.rb @@ -3,8 +3,21 @@ module Lrama class Lexer - class Token - class Char < Token + module Token + class Char < Base + # @rbs () -> void + def validate + validate_ascii_code_range + end + + private + + # @rbs () -> void + def validate_ascii_code_range + unless s_value.ascii_only? + errors << "Invalid character: `#{s_value}`. Only ASCII characters are allowed." + end + end end end end diff --git a/tool/lrama/lib/lrama/lexer/token/empty.rb b/tool/lrama/lib/lrama/lexer/token/empty.rb new file mode 100644 index 00000000000000..375e256493bdef --- /dev/null +++ b/tool/lrama/lib/lrama/lexer/token/empty.rb @@ -0,0 +1,14 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Lexer + module Token + class Empty < Base + def initialize(location: nil) + super(s_value: '%empty', location: location) + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/lexer/token/ident.rb b/tool/lrama/lib/lrama/lexer/token/ident.rb index 8b1328a040fadd..4880be907330c8 100644 --- a/tool/lrama/lib/lrama/lexer/token/ident.rb +++ b/tool/lrama/lib/lrama/lexer/token/ident.rb @@ -3,8 +3,8 @@ module Lrama class Lexer - class Token - class Ident < Token + module Token + class Ident < Base end end end diff --git a/tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb b/tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb index 37d412aa838b3f..7051ba75a4b2ab 100644 --- a/tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb +++ b/tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb @@ -3,12 +3,12 @@ module Lrama class Lexer - class Token - class InstantiateRule < Token - attr_reader :args #: Array[Lexer::Token] + module Token + class InstantiateRule < Base + attr_reader :args #: Array[Lexer::Token::Base] attr_reader :lhs_tag #: Lexer::Token::Tag? - # @rbs (s_value: String, ?alias_name: String, ?location: Location, ?args: Array[Lexer::Token], ?lhs_tag: Lexer::Token::Tag?) -> void + # @rbs (s_value: String, ?alias_name: String, ?location: Location, ?args: Array[Lexer::Token::Base], ?lhs_tag: Lexer::Token::Tag?) -> void def initialize(s_value:, alias_name: nil, location: nil, args: [], lhs_tag: nil) super s_value: s_value, alias_name: alias_name, location: location @args = args diff --git a/tool/lrama/lib/lrama/lexer/token/int.rb b/tool/lrama/lib/lrama/lexer/token/int.rb new file mode 100644 index 00000000000000..7daf48d4d36970 --- /dev/null +++ b/tool/lrama/lib/lrama/lexer/token/int.rb @@ -0,0 +1,14 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Lexer + module Token + class Int < Base + # @rbs! + # def initialize: (s_value: Integer, ?alias_name: String, ?location: Location) -> void + # def s_value: () -> Integer + end + end + end +end diff --git a/tool/lrama/lib/lrama/lexer/token/str.rb b/tool/lrama/lib/lrama/lexer/token/str.rb new file mode 100644 index 00000000000000..cf9de6cf0f49bd --- /dev/null +++ b/tool/lrama/lib/lrama/lexer/token/str.rb @@ -0,0 +1,11 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Lexer + module Token + class Str < Base + end + end + end +end diff --git a/tool/lrama/lib/lrama/lexer/token/tag.rb b/tool/lrama/lib/lrama/lexer/token/tag.rb index b346ef7c5c4075..68c6268219af09 100644 --- a/tool/lrama/lib/lrama/lexer/token/tag.rb +++ b/tool/lrama/lib/lrama/lexer/token/tag.rb @@ -3,8 +3,8 @@ module Lrama class Lexer - class Token - class Tag < Token + module Token + class Tag < Base # @rbs () -> String def member # Omit "<>" diff --git a/tool/lrama/lib/lrama/lexer/token/token.rb b/tool/lrama/lib/lrama/lexer/token/token.rb new file mode 100644 index 00000000000000..935797efc68f8f --- /dev/null +++ b/tool/lrama/lib/lrama/lexer/token/token.rb @@ -0,0 +1,11 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Lexer + module Token + class Token < Base + end + end + end +end diff --git a/tool/lrama/lib/lrama/lexer/token/user_code.rb b/tool/lrama/lib/lrama/lexer/token/user_code.rb index 4ef40e6dc8f790..166f04954a9275 100644 --- a/tool/lrama/lib/lrama/lexer/token/user_code.rb +++ b/tool/lrama/lib/lrama/lexer/token/user_code.rb @@ -5,8 +5,8 @@ module Lrama class Lexer - class Token - class UserCode < Token + module Token + class UserCode < Base attr_accessor :tag #: Lexer::Token::Tag # @rbs () -> Array[Lrama::Grammar::Reference] @@ -38,43 +38,69 @@ def _references # @rbs (StringScanner scanner) -> Lrama::Grammar::Reference? def scan_reference(scanner) start = scanner.pos - case - # $ references - # It need to wrap an identifier with brackets to use ".-" for identifiers - when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\$/) # $$, $$ - tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil - return Lrama::Grammar::Reference.new(type: :dollar, name: "$", ex_tag: tag, first_column: start, last_column: scanner.pos) - when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?(\d+)/) # $1, $2, $1 - tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil - return Lrama::Grammar::Reference.new(type: :dollar, number: Integer(scanner[2]), index: Integer(scanner[2]), ex_tag: tag, first_column: start, last_column: scanner.pos) - when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $program (named reference without brackets) - tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil - return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos) - when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $[expr.right], $[expr-right], $[expr.right] (named reference with brackets) - tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil - return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos) - - # @ references - # It need to wrap an identifier with brackets to use ".-" for identifiers - when scanner.scan(/@\$/) # @$ - return Lrama::Grammar::Reference.new(type: :at, name: "$", first_column: start, last_column: scanner.pos) - when scanner.scan(/@(\d+)/) # @1 - return Lrama::Grammar::Reference.new(type: :at, number: Integer(scanner[1]), index: Integer(scanner[1]), first_column: start, last_column: scanner.pos) - when scanner.scan(/@([a-zA-Z][a-zA-Z0-9_]*)/) # @foo, @expr (named reference without brackets) - return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos) - when scanner.scan(/@\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # @[expr.right], @[expr-right] (named reference with brackets) - return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos) + if scanner.scan(/ + # $ references + # It need to wrap an identifier with brackets to use ".-" for identifiers + \$(<[a-zA-Z0-9_]+>)?(?: + (\$) # $$, $$ + | (\d+) # $1, $2, $1 + | ([a-zA-Z_][a-zA-Z0-9_]*) # $foo, $expr, $program (named reference without brackets) + | \[([a-zA-Z_.][-a-zA-Z0-9_.]*)\] # $[expr.right], $[expr-right], $[expr.right] (named reference with brackets) + ) + | + # @ references + # It need to wrap an identifier with brackets to use ".-" for identifiers + @(?: + (\$) # @$ + | (\d+) # @1 + | ([a-zA-Z_][a-zA-Z0-9_]*) # @foo, @expr (named reference without brackets) + | \[([a-zA-Z_.][-a-zA-Z0-9_.]*)\] # @[expr.right], @[expr-right] (named reference with brackets) + ) + | + # $: references + \$: + (?: + (\$) # $:$ + | (\d+) # $:1 + | ([a-zA-Z_][a-zA-Z0-9_]*) # $:foo, $:expr (named reference without brackets) + | \[([a-zA-Z_.][-a-zA-Z0-9_.]*)\] # $:[expr.right], $:[expr-right] (named reference with brackets) + ) + /x) + case + # $ references + when scanner[2] # $$, $$ + tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil + return Lrama::Grammar::Reference.new(type: :dollar, name: "$", ex_tag: tag, first_column: start, last_column: scanner.pos) + when scanner[3] # $1, $2, $1 + tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil + return Lrama::Grammar::Reference.new(type: :dollar, number: Integer(scanner[3]), index: Integer(scanner[3]), ex_tag: tag, first_column: start, last_column: scanner.pos) + when scanner[4] # $foo, $expr, $program (named reference without brackets) + tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil + return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[4], ex_tag: tag, first_column: start, last_column: scanner.pos) + when scanner[5] # $[expr.right], $[expr-right], $[expr.right] (named reference with brackets) + tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil + return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[5], ex_tag: tag, first_column: start, last_column: scanner.pos) - # $: references - when scanner.scan(/\$:\$/) # $:$ - return Lrama::Grammar::Reference.new(type: :index, name: "$", first_column: start, last_column: scanner.pos) - when scanner.scan(/\$:(\d+)/) # $:1 - return Lrama::Grammar::Reference.new(type: :index, number: Integer(scanner[1]), first_column: start, last_column: scanner.pos) - when scanner.scan(/\$:([a-zA-Z_][a-zA-Z0-9_]*)/) # $:foo, $:expr (named reference without brackets) - return Lrama::Grammar::Reference.new(type: :index, name: scanner[1], first_column: start, last_column: scanner.pos) - when scanner.scan(/\$:\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $:[expr.right], $:[expr-right] (named reference with brackets) - return Lrama::Grammar::Reference.new(type: :index, name: scanner[1], first_column: start, last_column: scanner.pos) + # @ references + when scanner[6] # @$ + return Lrama::Grammar::Reference.new(type: :at, name: "$", first_column: start, last_column: scanner.pos) + when scanner[7] # @1 + return Lrama::Grammar::Reference.new(type: :at, number: Integer(scanner[7]), index: Integer(scanner[7]), first_column: start, last_column: scanner.pos) + when scanner[8] # @foo, @expr (named reference without brackets) + return Lrama::Grammar::Reference.new(type: :at, name: scanner[8], first_column: start, last_column: scanner.pos) + when scanner[9] # @[expr.right], @[expr-right] (named reference with brackets) + return Lrama::Grammar::Reference.new(type: :at, name: scanner[9], first_column: start, last_column: scanner.pos) + # $: references + when scanner[10] # $:$ + return Lrama::Grammar::Reference.new(type: :index, name: "$", first_column: start, last_column: scanner.pos) + when scanner[11] # $:1 + return Lrama::Grammar::Reference.new(type: :index, number: Integer(scanner[11]), index: Integer(scanner[11]), first_column: start, last_column: scanner.pos) + when scanner[12] # $:foo, $:expr (named reference without brackets) + return Lrama::Grammar::Reference.new(type: :index, name: scanner[12], first_column: start, last_column: scanner.pos) + when scanner[13] # $:[expr.right], $:[expr-right] (named reference with brackets) + return Lrama::Grammar::Reference.new(type: :index, name: scanner[13], first_column: start, last_column: scanner.pos) + end end end end diff --git a/tool/lrama/lib/lrama/logger.rb b/tool/lrama/lib/lrama/logger.rb index 88bb9209604d60..291eea5296ec3e 100644 --- a/tool/lrama/lib/lrama/logger.rb +++ b/tool/lrama/lib/lrama/logger.rb @@ -8,14 +8,24 @@ def initialize(out = STDERR) @out = out end + # @rbs () -> void + def line_break + @out << "\n" + end + # @rbs (String message) -> void - def warn(message) + def trace(message) @out << message << "\n" end + # @rbs (String message) -> void + def warn(message) + @out << 'warning: ' << message << "\n" + end + # @rbs (String message) -> void def error(message) - @out << message << "\n" + @out << 'error: ' << message << "\n" end end end diff --git a/tool/lrama/lib/lrama/option_parser.rb b/tool/lrama/lib/lrama/option_parser.rb index 23988a5fbb8099..5a15d59c7bba97 100644 --- a/tool/lrama/lib/lrama/option_parser.rb +++ b/tool/lrama/lib/lrama/option_parser.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true require 'optparse' @@ -5,17 +6,32 @@ module Lrama # Handle option parsing for the command line interface. class OptionParser + # @rbs! + # @options: Lrama::Options + # @trace: Array[String] + # @report: Array[String] + # @profile: Array[String] + + # @rbs (Array[String]) -> Lrama::Options + def self.parse(argv) + new.parse(argv) + end + + # @rbs () -> void def initialize @options = Options.new @trace = [] @report = [] + @profile = [] end + # @rbs (Array[String]) -> Lrama::Options def parse(argv) parse_by_option_parser(argv) @options.trace_opts = validate_trace(@trace) @options.report_opts = validate_report(@report) + @options.profile_opts = validate_profile(@profile) @options.grammar_file = argv.shift unless @options.grammar_file @@ -46,6 +62,7 @@ def parse(argv) private + # @rbs (Array[String]) -> void def parse_by_option_parser(argv) ::OptionParser.new do |o| o.banner = <<~BANNER @@ -60,7 +77,14 @@ def parse_by_option_parser(argv) o.separator 'Tuning the Parser:' o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v } o.on('-t', '--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true } - o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") {|v| @options.define = v } + o.separator " same as '-Dparse.trace'" + o.on('--locations', 'enable location support') {|v| @options.locations = true } + o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") do |v| + @options.define = v.each_with_object({}) do |item, hash| # steep:ignore UnannotatedEmptyCollection + key, value = item.split('=', 2) + hash[key] = value + end + end o.separator '' o.separator 'Output:' o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v } @@ -91,10 +115,19 @@ def parse_by_option_parser(argv) o.on_tail ' time display generation time' o.on_tail ' all include all the above traces' o.on_tail ' none disable all traces' + o.on('--diagram=[FILE]', 'generate a diagram of the rules') do |v| + @options.diagram = true + @options.diagram_file = v if v + end + o.on('--profile=PROFILES', Array, 'profiles parser generation parts') {|v| @profile = v } + o.on_tail '' + o.on_tail 'PROFILES is a list of comma-separated words that can include:' + o.on_tail ' call-stack use sampling call-stack profiler (stackprof gem)' + o.on_tail ' memory use memory profiler (memory_profiler gem)' o.on('-v', '--verbose', "same as '--report=state'") {|_v| @report << 'states' } o.separator '' o.separator 'Diagnostics:' - o.on('-W', '--warnings', 'report the warnings') {|v| @options.diagnostic = true } + o.on('-W', '--warnings', 'report the warnings') {|v| @options.warnings = true } o.separator '' o.separator 'Error Recovery:' o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true } @@ -107,9 +140,10 @@ def parse_by_option_parser(argv) end end - ALIASED_REPORTS = { cex: :counterexamples }.freeze - VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze + ALIASED_REPORTS = { cex: :counterexamples }.freeze #: Hash[Symbol, Symbol] + VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze #: Array[Symbol] + # @rbs (Array[String]) -> Hash[Symbol, bool] def validate_report(report) h = { grammar: true } return h if report.empty? @@ -131,6 +165,7 @@ def validate_report(report) return h end + # @rbs (String) -> Symbol def aliased_report_option(opt) (ALIASED_REPORTS[opt.to_sym] || opt).to_sym end @@ -139,15 +174,16 @@ def aliased_report_option(opt) locations scan parse automaton bitsets closure grammar rules only-explicit-rules actions resource sets muscles tools m4-early m4 skeleton time ielr cex - ].freeze + ].freeze #: Array[String] NOT_SUPPORTED_TRACES = %w[ locations scan parse bitsets grammar resource sets muscles tools m4-early m4 skeleton ielr cex - ].freeze - SUPPORTED_TRACES = VALID_TRACES - NOT_SUPPORTED_TRACES + ].freeze #: Array[String] + SUPPORTED_TRACES = VALID_TRACES - NOT_SUPPORTED_TRACES #: Array[String] + # @rbs (Array[String]) -> Hash[Symbol, bool] def validate_trace(trace) - h = {} + h = {} #: Hash[Symbol, bool] return h if trace.empty? || trace == ['none'] all_traces = SUPPORTED_TRACES - %w[only-explicit-rules] if trace == ['all'] @@ -159,7 +195,25 @@ def validate_trace(trace) if SUPPORTED_TRACES.include?(t) h[t.gsub(/-/, '_').to_sym] = true else - raise "Invalid trace option \"#{t}\"." + raise "Invalid trace option \"#{t}\".\nValid options are [#{SUPPORTED_TRACES.join(", ")}]." + end + end + + return h + end + + VALID_PROFILES = %w[call-stack memory].freeze #: Array[String] + + # @rbs (Array[String]) -> Hash[Symbol, bool] + def validate_profile(profile) + h = {} #: Hash[Symbol, bool] + return h if profile.empty? + + profile.each do |t| + if VALID_PROFILES.include?(t) + h[t.gsub(/-/, '_').to_sym] = true + else + raise "Invalid profile option \"#{t}\".\nValid options are [#{VALID_PROFILES.join(", ")}]." end end diff --git a/tool/lrama/lib/lrama/options.rb b/tool/lrama/lib/lrama/options.rb index 08f75a770faa00..87aec62448a959 100644 --- a/tool/lrama/lib/lrama/options.rb +++ b/tool/lrama/lib/lrama/options.rb @@ -1,28 +1,46 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama # Command line options. class Options - attr_accessor :skeleton, :header, :header_file, - :report_file, :outfile, - :error_recovery, :grammar_file, - :trace_opts, :report_opts, - :diagnostic, :y, :debug, :define + attr_accessor :skeleton #: String + attr_accessor :locations #: bool + attr_accessor :header #: bool + attr_accessor :header_file #: String? + attr_accessor :report_file #: String? + attr_accessor :outfile #: String + attr_accessor :error_recovery #: bool + attr_accessor :grammar_file #: String + attr_accessor :trace_opts #: Hash[Symbol, bool]? + attr_accessor :report_opts #: Hash[Symbol, bool]? + attr_accessor :warnings #: bool + attr_accessor :y #: IO + attr_accessor :debug #: bool + attr_accessor :define #: Hash[String, String] + attr_accessor :diagram #: bool + attr_accessor :diagram_file #: String + attr_accessor :profile_opts #: Hash[Symbol, bool]? + # @rbs () -> void def initialize @skeleton = "bison/yacc.c" + @locations = false @define = {} @header = false @header_file = nil @report_file = nil @outfile = "y.tab.c" @error_recovery = false - @grammar_file = nil + @grammar_file = '' @trace_opts = nil @report_opts = nil - @diagnostic = false + @warnings = false @y = STDIN @debug = false + @diagram = false + @diagram_file = "diagram.html" + @profile_opts = nil end end end diff --git a/tool/lrama/lib/lrama/output.rb b/tool/lrama/lib/lrama/output.rb index 3c7316ac6d871c..d527be8bd40f89 100644 --- a/tool/lrama/lib/lrama/output.rb +++ b/tool/lrama/lib/lrama/output.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require "erb" require "forwardable" -require_relative "report/duration" +require_relative "tracer/duration" module Lrama class Output extend Forwardable - include Report::Duration + include Tracer::Duration attr_reader :grammar_file_path, :context, :grammar, :error_recovery, :include_header @@ -43,7 +42,7 @@ def self.erb(input) end def render_partial(file) - render_template(partial_file(file)) + ERB.render(partial_file(file), context: @context, output: self) end def render @@ -405,16 +404,10 @@ def percent_code(name) private def eval_template(file, path) - tmp = render_template(file) + tmp = ERB.render(file, context: @context, output: self) replace_special_variables(tmp, path) end - def render_template(file) - erb = self.class.erb(File.read(file)) - erb.filename = file - erb.result_with_hash(context: @context, output: self) - end - def template_file File.join(template_dir, @template_name) end diff --git a/tool/lrama/lib/lrama/parser.rb b/tool/lrama/lib/lrama/parser.rb index 177e784e5c4eb0..20c3ad347f0491 100644 --- a/tool/lrama/lib/lrama/parser.rb +++ b/tool/lrama/lib/lrama/parser.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.8.1 @@ -654,22 +655,25 @@ def token_to_str(t) module Lrama class Parser < Racc::Parser -module_eval(<<'...end parser.y/module_eval...', 'parser.y', 428) +module_eval(<<'...end parser.y/module_eval...', 'parser.y', 504) -include Lrama::Report::Duration +include Lrama::Tracer::Duration -def initialize(text, path, debug = false, define = {}) +def initialize(text, path, debug = false, locations = false, define = {}) + @path = path @grammar_file = Lrama::Lexer::GrammarFile.new(path, text) - @yydebug = debug + @yydebug = debug || define.key?('parse.trace') @rule_counter = Lrama::Grammar::Counter.new(0) @midrule_action_counter = Lrama::Grammar::Counter.new(1) + @locations = locations @define = define end def parse - report_duration(:parse) do + message = "parse '#{File.basename(@path)}'" + report_duration(message) do @lexer = Lrama::Lexer.new(@grammar_file) - @grammar = Lrama::Grammar.new(@rule_counter, @define) + @grammar = Lrama::Grammar.new(@rule_counter, @locations, @define) @precedence_number = 0 reset_precs do_parse @@ -682,7 +686,14 @@ def next_token end def on_error(error_token_id, error_value, value_stack) - if error_value.is_a?(Lrama::Lexer::Token) + case error_value + when Lrama::Lexer::Token::Int + location = error_value.location + value = "#{error_value.s_value}" + when Lrama::Lexer::Token::Token + location = error_value.location + value = "\"#{error_value.s_value}\"" + when Lrama::Lexer::Token::Base location = error_value.location value = "'#{error_value.s_value}'" else @@ -696,7 +707,7 @@ def on_error(error_token_id, error_value, value_stack) end def on_action_error(error_message, error_value) - if error_value.is_a?(Lrama::Lexer::Token) + if error_value.is_a?(Lrama::Lexer::Token::Base) location = error_value.location else location = @lexer.location @@ -708,10 +719,15 @@ def on_action_error(error_message, error_value) private def reset_precs - @prec_seen = false + @opening_prec_seen = false + @trailing_prec_seen = false @code_after_prec = false end +def prec_seen? + @opening_prec_seen || @trailing_prec_seen +end + def begin_c_declaration(end_symbol) @lexer.status = :c_declaration @lexer.end_symbol = end_symbol @@ -729,306 +745,322 @@ def raise_parse_error(error_message, location) ##### State transition tables begin ### racc_action_table = [ - 89, 49, 90, 167, 49, 101, 173, 49, 101, 167, - 49, 101, 173, 6, 101, 80, 49, 49, 48, 48, - 41, 76, 76, 49, 49, 48, 48, 42, 76, 76, - 49, 49, 48, 48, 101, 96, 113, 49, 87, 48, - 150, 101, 96, 151, 45, 171, 169, 170, 151, 176, - 170, 91, 169, 170, 81, 176, 170, 20, 24, 25, - 26, 27, 28, 29, 30, 31, 87, 32, 33, 34, - 35, 36, 37, 38, 39, 49, 4, 48, 5, 101, - 96, 181, 182, 183, 128, 20, 24, 25, 26, 27, - 28, 29, 30, 31, 46, 32, 33, 34, 35, 36, - 37, 38, 39, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 53, 20, 24, 25, 26, 27, 28, 29, - 30, 31, 53, 32, 33, 34, 35, 36, 37, 38, - 39, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 44, 20, 24, 25, 26, 27, 28, 29, 30, 31, - 53, 32, 33, 34, 35, 36, 37, 38, 39, 49, - 4, 48, 5, 101, 96, 49, 49, 48, 48, 101, - 101, 49, 49, 48, 48, 101, 101, 49, 49, 48, - 197, 101, 101, 49, 49, 197, 48, 101, 101, 49, - 49, 197, 48, 101, 181, 182, 183, 128, 204, 210, - 217, 205, 205, 205, 49, 49, 48, 48, 49, 49, - 48, 48, 49, 49, 48, 48, 181, 182, 183, 116, - 117, 56, 53, 53, 53, 53, 53, 62, 63, 64, - 65, 66, 68, 68, 68, 82, 53, 53, 104, 108, - 108, 115, 122, 123, 125, 128, 129, 133, 139, 140, - 141, 142, 144, 145, 101, 154, 139, 157, 154, 161, - 162, 68, 164, 165, 172, 177, 154, 184, 128, 188, - 154, 190, 128, 154, 199, 154, 128, 68, 165, 206, - 165, 68, 68, 215, 128, 68 ] + 98, 98, 99, 99, 87, 53, 53, 52, 178, 110, + 110, 97, 53, 53, 184, 178, 110, 110, 53, 181, + 184, 162, 110, 6, 163, 181, 181, 53, 53, 52, + 52, 181, 79, 79, 53, 53, 52, 52, 43, 79, + 79, 53, 4, 52, 5, 110, 88, 94, 182, 125, + 126, 163, 100, 100, 180, 193, 194, 195, 137, 185, + 188, 180, 4, 44, 5, 185, 188, 94, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 46, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 47, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 47, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 12, 13, 50, + 57, 14, 15, 16, 17, 18, 19, 20, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 57, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 12, 13, 57, + 60, 14, 15, 16, 17, 18, 19, 20, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 57, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 53, 53, 52, + 52, 110, 105, 53, 53, 52, 52, 110, 105, 53, + 53, 52, 52, 110, 105, 53, 53, 52, 52, 110, + 105, 53, 53, 52, 52, 110, 110, 53, 53, 52, + 209, 110, 110, 53, 53, 209, 52, 110, 110, 53, + 53, 209, 52, 110, 193, 194, 195, 137, 216, 222, + 229, 217, 217, 217, 53, 53, 52, 52, 193, 194, + 195, 57, 57, 57, 57, 66, 67, 68, 69, 70, + 72, 72, 72, 86, 89, 47, 57, 57, 113, 117, + 117, 79, 123, 124, 131, 47, 133, 137, 139, 143, + 149, 150, 151, 152, 133, 155, 156, 157, 110, 166, + 149, 169, 172, 173, 72, 175, 176, 183, 189, 166, + 196, 137, 200, 202, 137, 166, 211, 166, 137, 72, + 176, 218, 176, 72, 72, 227, 137, 72 ] racc_action_check = [ - 47, 153, 47, 153, 159, 153, 159, 178, 159, 178, - 189, 178, 189, 1, 189, 39, 35, 36, 35, 36, - 5, 35, 36, 37, 38, 37, 38, 6, 37, 38, - 59, 74, 59, 74, 59, 59, 74, 60, 45, 60, - 138, 60, 60, 138, 9, 156, 153, 153, 156, 159, - 159, 47, 178, 178, 39, 189, 189, 45, 45, 45, - 45, 45, 45, 45, 45, 45, 83, 45, 45, 45, - 45, 45, 45, 45, 45, 61, 0, 61, 0, 61, - 61, 166, 166, 166, 166, 83, 83, 83, 83, 83, - 83, 83, 83, 83, 11, 83, 83, 83, 83, 83, - 83, 83, 83, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 13, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 14, 3, 3, 3, 3, 3, 3, 3, - 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 15, 8, 8, 8, 8, 8, 8, 8, 8, 97, - 2, 97, 2, 97, 97, 71, 108, 71, 108, 71, - 108, 109, 169, 109, 169, 109, 169, 176, 184, 176, - 184, 176, 184, 190, 205, 190, 205, 190, 205, 206, - 12, 206, 12, 206, 174, 174, 174, 174, 196, 201, - 214, 196, 201, 214, 69, 76, 69, 76, 104, 105, - 104, 105, 111, 113, 111, 113, 198, 198, 198, 81, - 81, 16, 17, 20, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 40, 51, 56, 67, 70, - 72, 80, 84, 85, 86, 87, 93, 107, 115, 116, - 117, 118, 127, 128, 134, 140, 141, 143, 144, 145, - 146, 150, 151, 152, 158, 163, 165, 167, 168, 171, - 172, 173, 175, 177, 187, 188, 192, 193, 195, 197, - 200, 202, 204, 209, 210, 216 ] + 51, 97, 51, 97, 41, 75, 165, 75, 165, 75, + 165, 51, 171, 190, 171, 190, 171, 190, 201, 165, + 201, 148, 201, 1, 148, 171, 190, 36, 37, 36, + 37, 201, 36, 37, 38, 39, 38, 39, 5, 38, + 39, 117, 0, 117, 0, 117, 41, 46, 168, 88, + 88, 168, 51, 97, 165, 177, 177, 177, 177, 171, + 171, 190, 2, 6, 2, 201, 201, 90, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 9, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 10, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 11, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 3, 3, 12, + 14, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 15, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 8, 8, 16, + 17, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 18, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 63, 13, 63, + 13, 63, 63, 64, 73, 64, 73, 64, 64, 65, + 78, 65, 78, 65, 65, 106, 79, 106, 79, 106, + 106, 118, 180, 118, 180, 118, 180, 188, 196, 188, + 196, 188, 196, 202, 217, 202, 217, 202, 217, 218, + 113, 218, 113, 218, 186, 186, 186, 186, 208, 213, + 226, 208, 213, 226, 114, 123, 114, 123, 210, 210, + 210, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 40, 42, 47, 55, 60, 71, 74, + 76, 80, 81, 87, 91, 92, 93, 94, 102, 116, + 124, 125, 126, 127, 133, 136, 137, 138, 144, 150, + 151, 153, 156, 158, 162, 163, 164, 170, 174, 176, + 178, 179, 182, 184, 187, 189, 199, 200, 204, 205, + 207, 209, 212, 214, 216, 221, 222, 228 ] racc_action_pointer = [ - 66, 13, 150, 90, nil, 13, 27, nil, 118, 35, - nil, 88, 187, 63, 73, 101, 216, 173, nil, nil, - 174, nil, nil, nil, 175, 176, 177, 222, 223, 224, - 225, 226, 224, 225, 226, 13, 14, 20, 21, 10, - 233, nil, nil, nil, nil, 34, nil, -5, nil, nil, - nil, 187, nil, nil, nil, nil, 188, nil, nil, 27, - 34, 72, nil, nil, nil, nil, nil, 230, nil, 201, - 231, 162, 232, nil, 28, nil, 202, nil, nil, nil, - 200, 215, nil, 62, 233, 221, 222, 191, nil, nil, - nil, nil, nil, 244, nil, nil, nil, 156, nil, nil, - nil, nil, nil, nil, 205, 206, nil, 241, 163, 168, - nil, 209, nil, 210, nil, 243, 206, 209, 240, nil, - nil, nil, nil, nil, nil, nil, nil, 209, 248, nil, - nil, nil, nil, nil, 247, nil, nil, nil, -2, nil, - 208, 251, nil, 255, 211, 204, 210, nil, nil, nil, - 253, 257, 217, -2, nil, nil, 3, nil, 218, 1, - nil, nil, nil, 222, nil, 219, 30, 226, 214, 169, - nil, 226, 223, 230, 143, 218, 174, 226, 4, nil, - nil, nil, nil, nil, 175, nil, nil, 272, 228, 7, - 180, nil, 222, 269, nil, 232, 156, 238, 165, nil, - 234, 157, 273, nil, 274, 181, 186, nil, nil, 233, - 230, nil, nil, nil, 158, nil, 277, nil, nil ] + 32, 23, 52, 93, nil, 31, 63, nil, 123, 68, + 74, 84, 103, 165, 94, 111, 123, 135, 141, nil, + nil, nil, nil, nil, 215, 216, 217, 218, 230, 231, + 232, 233, 234, 232, 233, 234, 24, 25, 31, 32, + 238, -1, 242, nil, nil, nil, 43, 232, nil, nil, + nil, -5, nil, nil, nil, 230, nil, nil, nil, nil, + 231, nil, nil, 164, 170, 176, nil, nil, nil, nil, + nil, 240, nil, 171, 241, 2, 242, nil, 177, 183, + 243, 244, nil, nil, nil, nil, nil, 209, 45, nil, + 63, 245, 242, 243, 202, nil, nil, -4, nil, nil, + nil, nil, 256, nil, nil, nil, 182, nil, nil, nil, + nil, nil, nil, 207, 221, nil, 253, 38, 188, nil, + nil, nil, nil, 222, 255, 215, 218, 252, nil, nil, + nil, nil, nil, 251, nil, nil, 219, 261, 250, nil, + nil, nil, nil, nil, 261, nil, nil, nil, -24, nil, + 219, 265, nil, 269, nil, nil, 216, nil, 256, nil, + nil, nil, 266, 270, 227, 3, nil, nil, 3, nil, + 228, 9, nil, nil, 232, nil, 229, 3, 236, 226, + 189, nil, 236, nil, 239, nil, 162, 229, 194, 235, + 10, nil, nil, nil, nil, nil, 195, nil, nil, 284, + 237, 15, 200, nil, 233, 281, nil, 241, 173, 247, + 176, nil, 243, 174, 285, nil, 286, 201, 206, nil, + nil, 278, 241, nil, nil, nil, 175, nil, 289, nil, + nil ] racc_action_default = [ - -1, -128, -1, -3, -10, -128, -128, -2, -3, -128, - -16, -128, -128, -128, -128, -128, -128, -128, -24, -25, - -128, -32, -33, -34, -128, -128, -128, -128, -128, -128, - -128, -128, -50, -50, -50, -128, -128, -128, -128, -128, - -128, -13, 219, -4, -26, -128, -17, -123, -93, -94, - -122, -14, -19, -85, -20, -21, -128, -23, -31, -128, - -128, -128, -38, -39, -40, -41, -42, -43, -51, -128, - -44, -128, -45, -46, -88, -90, -128, -47, -48, -49, - -128, -128, -11, -5, -7, -95, -128, -68, -18, -124, - -125, -126, -15, -128, -22, -27, -28, -29, -35, -83, - -84, -127, -36, -37, -128, -52, -54, -56, -128, -79, - -81, -88, -89, -128, -91, -128, -128, -128, -128, -6, - -8, -9, -120, -96, -97, -98, -69, -128, -128, -86, - -30, -55, -53, -57, -76, -82, -80, -92, -128, -62, - -66, -128, -12, -128, -66, -128, -128, -58, -77, -78, - -50, -128, -60, -64, -67, -70, -128, -121, -99, -100, - -102, -119, -87, -128, -63, -66, -68, -93, -68, -128, - -116, -128, -66, -93, -68, -68, -128, -66, -65, -71, - -72, -108, -109, -110, -128, -74, -75, -128, -66, -101, - -128, -103, -68, -50, -107, -59, -128, -93, -111, -117, - -61, -128, -50, -106, -50, -128, -128, -112, -113, -128, - -68, -104, -73, -114, -128, -118, -50, -115, -105 ] + -1, -136, -1, -3, -10, -136, -136, -2, -3, -136, + -14, -14, -136, -136, -136, -136, -136, -136, -136, -28, + -29, -34, -35, -36, -136, -136, -136, -136, -136, -136, + -136, -136, -136, -54, -54, -54, -136, -136, -136, -136, + -136, -136, -136, -13, 231, -4, -136, -14, -16, -17, + -20, -131, -100, -101, -130, -18, -23, -89, -24, -25, + -136, -27, -37, -136, -136, -136, -41, -42, -43, -44, + -45, -46, -55, -136, -47, -136, -48, -49, -92, -136, + -95, -97, -98, -50, -51, -52, -53, -136, -136, -11, + -5, -7, -14, -136, -72, -15, -21, -131, -132, -133, + -134, -19, -136, -26, -30, -31, -32, -38, -87, -88, + -135, -39, -40, -136, -56, -58, -60, -136, -83, -85, + -93, -94, -96, -136, -136, -136, -136, -136, -6, -8, + -9, -128, -104, -102, -105, -73, -136, -136, -136, -90, + -33, -59, -57, -61, -80, -86, -84, -99, -136, -66, + -70, -136, -12, -136, -103, -109, -136, -22, -136, -62, + -81, -82, -54, -136, -64, -68, -71, -74, -136, -129, + -106, -107, -127, -91, -136, -67, -70, -72, -100, -72, + -136, -124, -136, -109, -100, -110, -72, -72, -136, -70, + -69, -75, -76, -116, -117, -118, -136, -78, -79, -136, + -70, -108, -136, -111, -72, -54, -115, -63, -136, -100, + -119, -125, -65, -136, -54, -114, -54, -136, -136, -120, + -121, -136, -72, -112, -77, -122, -136, -126, -54, -123, + -113 ] racc_goto_table = [ - 69, 109, 50, 152, 57, 127, 84, 58, 112, 160, - 114, 59, 60, 61, 86, 52, 54, 55, 98, 102, - 103, 159, 106, 110, 175, 74, 74, 74, 74, 138, - 9, 1, 3, 180, 7, 43, 120, 160, 109, 109, - 195, 192, 121, 94, 119, 112, 40, 137, 118, 189, - 47, 200, 86, 92, 175, 156, 130, 131, 132, 107, - 135, 136, 88, 196, 111, 207, 111, 70, 72, 201, - 73, 77, 78, 79, 67, 147, 134, 178, 148, 149, - 93, 146, 124, 166, 179, 214, 185, 158, 208, 174, - 187, 209, 191, 193, 107, 107, 143, nil, nil, 186, - nil, 111, nil, 111, nil, nil, 194, nil, 166, nil, - 202, nil, nil, nil, 198, nil, nil, nil, 163, 174, - 198, nil, nil, nil, nil, nil, nil, nil, 216, nil, - nil, nil, nil, nil, nil, 213, 198, nil, nil, nil, + 73, 118, 136, 54, 48, 49, 164, 96, 91, 120, + 121, 93, 187, 148, 107, 111, 112, 119, 134, 171, + 56, 58, 59, 3, 61, 7, 78, 78, 78, 78, + 62, 63, 64, 65, 115, 74, 76, 192, 1, 129, + 168, 95, 187, 118, 118, 207, 204, 201, 77, 83, + 84, 85, 128, 138, 147, 93, 212, 140, 154, 145, + 146, 101, 130, 116, 42, 127, 103, 208, 78, 78, + 219, 9, 51, 213, 141, 142, 45, 71, 159, 144, + 190, 160, 161, 102, 158, 191, 132, 197, 122, 226, + 170, 177, 220, 199, 203, 205, 221, 186, 153, nil, + nil, nil, nil, 116, 116, nil, 198, nil, nil, nil, + nil, nil, 214, 78, 206, nil, 177, nil, nil, nil, + nil, nil, 210, nil, nil, nil, nil, 186, 210, 174, + 228, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 225, 210, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 203, nil, nil, nil, nil, nil, nil, nil, nil, - 211, nil, 212, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 218 ] + nil, nil, 215, nil, nil, nil, nil, nil, nil, nil, + nil, 223, nil, 224, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 230 ] racc_goto_check = [ - 27, 20, 29, 33, 15, 40, 8, 15, 46, 39, - 46, 15, 15, 15, 12, 16, 16, 16, 22, 22, - 22, 50, 28, 43, 38, 29, 29, 29, 29, 32, - 7, 1, 6, 36, 6, 7, 5, 39, 20, 20, - 33, 36, 9, 15, 8, 46, 10, 46, 11, 50, - 13, 33, 12, 16, 38, 32, 22, 28, 28, 29, - 43, 43, 14, 37, 29, 36, 29, 24, 24, 37, - 25, 25, 25, 25, 23, 30, 31, 34, 41, 42, - 44, 45, 48, 20, 40, 37, 40, 49, 51, 20, - 52, 53, 40, 40, 29, 29, 54, nil, nil, 20, - nil, 29, nil, 29, nil, nil, 20, nil, 20, nil, - 40, nil, nil, nil, 20, nil, nil, nil, 27, 20, - 20, nil, nil, nil, nil, nil, nil, nil, 40, nil, - nil, nil, nil, nil, nil, 20, 20, nil, nil, nil, + 29, 22, 42, 31, 14, 14, 35, 16, 8, 48, + 48, 13, 40, 34, 24, 24, 24, 45, 52, 54, + 18, 18, 18, 6, 17, 6, 31, 31, 31, 31, + 17, 17, 17, 17, 30, 26, 26, 38, 1, 5, + 34, 14, 40, 22, 22, 35, 38, 54, 27, 27, + 27, 27, 8, 16, 48, 13, 35, 24, 52, 45, + 45, 18, 9, 31, 10, 11, 17, 39, 31, 31, + 38, 7, 15, 39, 30, 30, 7, 25, 32, 33, + 36, 43, 44, 46, 47, 42, 14, 42, 50, 39, + 53, 22, 55, 56, 42, 42, 57, 22, 58, nil, + nil, nil, nil, 31, 31, nil, 22, nil, nil, nil, + nil, nil, 42, 31, 22, nil, 22, nil, nil, nil, + nil, nil, 22, nil, nil, nil, nil, 22, 22, 29, + 42, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 22, 22, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 27, nil, nil, nil, nil, nil, nil, nil, nil, - 27, nil, 27, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 27 ] + nil, nil, 29, nil, nil, nil, nil, nil, nil, nil, + nil, 29, nil, 29, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 29 ] racc_goto_pointer = [ - nil, 31, nil, nil, nil, -48, 32, 27, -39, -42, - 42, -34, -31, 38, 15, -13, 2, nil, nil, nil, - -70, nil, -41, 42, 34, 35, nil, -32, -47, -10, - -59, -31, -86, -137, -88, nil, -133, -121, -135, -135, - -82, -56, -55, -48, 27, -48, -66, nil, -3, -57, - -123, -110, -80, -108, -26 ] + nil, 38, nil, nil, nil, -52, 23, 68, -38, -29, + 60, -24, nil, -35, -6, 59, -44, 6, 6, nil, + nil, nil, -74, nil, -49, 44, 1, 12, nil, -33, + -39, -10, -66, -37, -111, -144, -96, nil, -140, -129, + -159, nil, -92, -63, -62, -58, 26, -55, -69, nil, + 8, nil, -75, -65, -136, -118, -88, -115, -33 ] racc_goto_default = [ - nil, nil, 2, 8, 83, nil, nil, nil, nil, nil, - nil, nil, 10, nil, nil, 51, nil, 21, 22, 23, - 95, 97, nil, nil, nil, nil, 105, 71, nil, 99, - nil, nil, nil, nil, 153, 126, nil, nil, 168, 155, - nil, 100, nil, nil, nil, nil, 75, 85, nil, nil, - nil, nil, nil, nil, nil ] + nil, nil, 2, 8, 90, nil, nil, nil, nil, nil, + nil, nil, 10, 11, nil, nil, nil, 55, nil, 21, + 22, 23, 104, 106, nil, nil, nil, nil, 114, 75, + nil, 108, nil, nil, nil, nil, 165, 135, nil, nil, + 179, 167, nil, 109, nil, nil, nil, nil, 81, 80, + 82, 92, nil, nil, nil, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 0, 63, :_reduce_1, - 2, 63, :_reduce_2, - 0, 64, :_reduce_3, - 2, 64, :_reduce_4, - 1, 65, :_reduce_5, - 2, 65, :_reduce_6, - 0, 66, :_reduce_none, - 1, 66, :_reduce_none, - 5, 58, :_reduce_none, - 0, 67, :_reduce_10, - 0, 68, :_reduce_11, - 5, 59, :_reduce_12, - 2, 59, :_reduce_none, - 1, 73, :_reduce_14, - 2, 73, :_reduce_15, - 1, 60, :_reduce_none, - 2, 60, :_reduce_17, - 3, 60, :_reduce_18, - 2, 60, :_reduce_none, - 2, 60, :_reduce_20, - 2, 60, :_reduce_21, - 3, 60, :_reduce_22, - 2, 60, :_reduce_23, - 1, 60, :_reduce_24, - 1, 60, :_reduce_25, - 2, 60, :_reduce_none, - 1, 78, :_reduce_27, - 1, 78, :_reduce_28, - 1, 79, :_reduce_29, - 2, 79, :_reduce_30, - 2, 69, :_reduce_31, - 1, 69, :_reduce_none, - 1, 69, :_reduce_none, - 1, 69, :_reduce_none, - 3, 69, :_reduce_35, - 3, 69, :_reduce_36, - 3, 69, :_reduce_37, - 2, 69, :_reduce_38, - 2, 69, :_reduce_39, - 2, 69, :_reduce_40, - 2, 69, :_reduce_41, - 2, 69, :_reduce_42, - 2, 74, :_reduce_none, - 2, 74, :_reduce_44, - 2, 74, :_reduce_45, - 2, 74, :_reduce_46, - 2, 74, :_reduce_47, - 2, 74, :_reduce_48, - 2, 74, :_reduce_49, - 0, 84, :_reduce_none, - 1, 84, :_reduce_none, - 1, 85, :_reduce_52, - 2, 85, :_reduce_53, - 2, 80, :_reduce_54, - 3, 80, :_reduce_55, - 0, 88, :_reduce_none, - 1, 88, :_reduce_none, - 3, 83, :_reduce_58, - 8, 75, :_reduce_59, - 5, 76, :_reduce_60, - 8, 76, :_reduce_61, - 1, 89, :_reduce_62, - 3, 89, :_reduce_63, - 1, 90, :_reduce_64, - 3, 90, :_reduce_65, - 0, 96, :_reduce_none, - 1, 96, :_reduce_none, - 0, 97, :_reduce_none, - 1, 97, :_reduce_none, - 1, 91, :_reduce_70, - 3, 91, :_reduce_71, - 3, 91, :_reduce_72, - 6, 91, :_reduce_73, - 3, 91, :_reduce_74, - 3, 91, :_reduce_75, - 0, 99, :_reduce_none, - 1, 99, :_reduce_none, - 1, 87, :_reduce_78, - 1, 100, :_reduce_79, - 2, 100, :_reduce_80, - 2, 81, :_reduce_81, - 3, 81, :_reduce_82, - 1, 77, :_reduce_none, - 1, 77, :_reduce_none, - 0, 101, :_reduce_85, - 0, 102, :_reduce_86, - 5, 72, :_reduce_87, - 1, 103, :_reduce_88, - 2, 103, :_reduce_89, - 1, 82, :_reduce_90, - 2, 82, :_reduce_91, - 3, 82, :_reduce_92, - 1, 86, :_reduce_93, - 1, 86, :_reduce_94, - 0, 105, :_reduce_none, - 1, 105, :_reduce_none, + 0, 64, :_reduce_1, + 2, 64, :_reduce_2, + 0, 65, :_reduce_3, + 2, 65, :_reduce_4, + 1, 66, :_reduce_5, + 2, 66, :_reduce_6, + 0, 67, :_reduce_none, + 1, 67, :_reduce_none, + 5, 59, :_reduce_none, + 0, 68, :_reduce_10, + 0, 69, :_reduce_11, + 5, 60, :_reduce_12, + 2, 60, :_reduce_13, + 0, 72, :_reduce_14, + 2, 72, :_reduce_15, 2, 61, :_reduce_none, 2, 61, :_reduce_none, - 4, 104, :_reduce_99, - 1, 106, :_reduce_100, - 3, 106, :_reduce_101, - 1, 107, :_reduce_102, - 3, 107, :_reduce_103, - 5, 107, :_reduce_104, - 7, 107, :_reduce_105, - 4, 107, :_reduce_106, - 3, 107, :_reduce_107, - 1, 93, :_reduce_108, - 1, 93, :_reduce_109, - 1, 93, :_reduce_110, - 0, 108, :_reduce_none, - 1, 108, :_reduce_none, - 2, 94, :_reduce_113, - 3, 94, :_reduce_114, - 4, 94, :_reduce_115, - 0, 109, :_reduce_116, - 0, 110, :_reduce_117, - 5, 95, :_reduce_118, - 3, 92, :_reduce_119, - 0, 111, :_reduce_120, - 3, 62, :_reduce_121, - 1, 70, :_reduce_none, - 0, 71, :_reduce_none, + 1, 76, :_reduce_18, + 2, 76, :_reduce_19, + 2, 70, :_reduce_20, + 3, 70, :_reduce_21, + 5, 70, :_reduce_22, + 2, 70, :_reduce_none, + 2, 70, :_reduce_24, + 2, 70, :_reduce_25, + 3, 70, :_reduce_26, + 2, 70, :_reduce_27, + 1, 70, :_reduce_28, + 1, 70, :_reduce_29, + 1, 81, :_reduce_30, + 1, 81, :_reduce_31, + 1, 82, :_reduce_32, + 2, 82, :_reduce_33, 1, 71, :_reduce_none, 1, 71, :_reduce_none, 1, 71, :_reduce_none, - 1, 98, :_reduce_127 ] - -racc_reduce_n = 128 - -racc_shift_n = 219 + 2, 71, :_reduce_37, + 3, 71, :_reduce_38, + 3, 71, :_reduce_39, + 3, 71, :_reduce_40, + 2, 71, :_reduce_41, + 2, 71, :_reduce_42, + 2, 71, :_reduce_43, + 2, 71, :_reduce_44, + 2, 71, :_reduce_45, + 2, 77, :_reduce_none, + 2, 77, :_reduce_47, + 2, 77, :_reduce_48, + 2, 77, :_reduce_49, + 2, 77, :_reduce_50, + 2, 77, :_reduce_51, + 2, 77, :_reduce_52, + 2, 77, :_reduce_53, + 0, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 88, :_reduce_56, + 2, 88, :_reduce_57, + 2, 83, :_reduce_58, + 3, 83, :_reduce_59, + 0, 91, :_reduce_none, + 1, 91, :_reduce_none, + 3, 86, :_reduce_62, + 8, 78, :_reduce_63, + 5, 79, :_reduce_64, + 8, 79, :_reduce_65, + 1, 92, :_reduce_66, + 3, 92, :_reduce_67, + 1, 93, :_reduce_68, + 3, 93, :_reduce_69, + 0, 99, :_reduce_none, + 1, 99, :_reduce_none, + 0, 100, :_reduce_none, + 1, 100, :_reduce_none, + 1, 94, :_reduce_74, + 3, 94, :_reduce_75, + 3, 94, :_reduce_76, + 6, 94, :_reduce_77, + 3, 94, :_reduce_78, + 3, 94, :_reduce_79, + 0, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 90, :_reduce_82, + 1, 103, :_reduce_83, + 2, 103, :_reduce_84, + 2, 84, :_reduce_85, + 3, 84, :_reduce_86, + 1, 80, :_reduce_none, + 1, 80, :_reduce_none, + 0, 104, :_reduce_89, + 0, 105, :_reduce_90, + 5, 75, :_reduce_91, + 1, 106, :_reduce_92, + 2, 106, :_reduce_93, + 2, 107, :_reduce_94, + 1, 108, :_reduce_95, + 2, 108, :_reduce_96, + 1, 85, :_reduce_97, + 1, 85, :_reduce_98, + 3, 85, :_reduce_99, + 1, 89, :_reduce_none, + 1, 89, :_reduce_none, + 1, 110, :_reduce_102, + 2, 110, :_reduce_103, + 2, 62, :_reduce_none, + 2, 62, :_reduce_none, + 4, 109, :_reduce_106, + 1, 111, :_reduce_107, + 3, 111, :_reduce_108, + 0, 112, :_reduce_109, + 2, 112, :_reduce_110, + 3, 112, :_reduce_111, + 5, 112, :_reduce_112, + 7, 112, :_reduce_113, + 4, 112, :_reduce_114, + 3, 112, :_reduce_115, + 1, 96, :_reduce_116, + 1, 96, :_reduce_117, + 1, 96, :_reduce_118, + 0, 113, :_reduce_none, + 1, 113, :_reduce_none, + 2, 97, :_reduce_121, + 3, 97, :_reduce_122, + 4, 97, :_reduce_123, + 0, 114, :_reduce_124, + 0, 115, :_reduce_125, + 5, 98, :_reduce_126, + 3, 95, :_reduce_127, + 0, 116, :_reduce_128, + 3, 63, :_reduce_129, + 1, 73, :_reduce_none, + 0, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 74, :_reduce_none, + 1, 101, :_reduce_135 ] + +racc_reduce_n = 136 + +racc_shift_n = 231 racc_token_table = { false => 0, @@ -1044,52 +1076,53 @@ def raise_parse_error(error_message, location) "%{" => 10, "%}" => 11, "%require" => 12, - "%expect" => 13, - "%define" => 14, - "%param" => 15, - "%lex-param" => 16, - "%parse-param" => 17, - "%code" => 18, - "%initial-action" => 19, - "%no-stdlib" => 20, - "%locations" => 21, - ";" => 22, - "%union" => 23, - "%destructor" => 24, - "%printer" => 25, - "%error-token" => 26, - "%after-shift" => 27, - "%before-reduce" => 28, - "%after-reduce" => 29, - "%after-shift-error-token" => 30, - "%after-pop-stack" => 31, - "-temp-group" => 32, - "%token" => 33, - "%type" => 34, - "%nterm" => 35, - "%left" => 36, - "%right" => 37, - "%precedence" => 38, - "%nonassoc" => 39, - "%rule" => 40, - "(" => 41, - ")" => 42, - ":" => 43, - "%inline" => 44, - "," => 45, - "|" => 46, - "%empty" => 47, - "%prec" => 48, - "{" => 49, - "}" => 50, - "?" => 51, - "+" => 52, - "*" => 53, - "[" => 54, - "]" => 55, - "{...}" => 56 } - -racc_nt_base = 57 + ";" => 13, + "%expect" => 14, + "%define" => 15, + "{" => 16, + "}" => 17, + "%param" => 18, + "%lex-param" => 19, + "%parse-param" => 20, + "%code" => 21, + "%initial-action" => 22, + "%no-stdlib" => 23, + "%locations" => 24, + "%union" => 25, + "%destructor" => 26, + "%printer" => 27, + "%error-token" => 28, + "%after-shift" => 29, + "%before-reduce" => 30, + "%after-reduce" => 31, + "%after-shift-error-token" => 32, + "%after-pop-stack" => 33, + "-temp-group" => 34, + "%token" => 35, + "%type" => 36, + "%nterm" => 37, + "%left" => 38, + "%right" => 39, + "%precedence" => 40, + "%nonassoc" => 41, + "%start" => 42, + "%rule" => 43, + "(" => 44, + ")" => 45, + ":" => 46, + "%inline" => 47, + "," => 48, + "|" => 49, + "%empty" => 50, + "%prec" => 51, + "?" => 52, + "+" => 53, + "*" => 54, + "[" => 55, + "]" => 56, + "{...}" => 57 } + +racc_nt_base = 58 racc_use_result_var = true @@ -1124,8 +1157,11 @@ def raise_parse_error(error_message, location) "\"%{\"", "\"%}\"", "\"%require\"", + "\";\"", "\"%expect\"", "\"%define\"", + "\"{\"", + "\"}\"", "\"%param\"", "\"%lex-param\"", "\"%parse-param\"", @@ -1133,7 +1169,6 @@ def raise_parse_error(error_message, location) "\"%initial-action\"", "\"%no-stdlib\"", "\"%locations\"", - "\";\"", "\"%union\"", "\"%destructor\"", "\"%printer\"", @@ -1151,6 +1186,7 @@ def raise_parse_error(error_message, location) "\"%right\"", "\"%precedence\"", "\"%nonassoc\"", + "\"%start\"", "\"%rule\"", "\"(\"", "\")\"", @@ -1160,8 +1196,6 @@ def raise_parse_error(error_message, location) "\"|\"", "\"%empty\"", "\"%prec\"", - "\"{\"", - "\"}\"", "\"?\"", "\"+\"", "\"*\"", @@ -1180,7 +1214,9 @@ def raise_parse_error(error_message, location) "\"-option@epilogue_declaration\"", "@1", "@2", + "parser_option", "grammar_declaration", + "\"-many@;\"", "variable", "value", "param", @@ -1204,9 +1240,9 @@ def raise_parse_error(error_message, location) "rule_rhs_list", "rule_rhs", "named_ref", - "parameterizing_suffix", - "parameterizing_args", - "midrule_action", + "parameterized_suffix", + "parameterized_args", + "action", "\"-option@%empty\"", "\"-option@named_ref\"", "string_as_id", @@ -1215,11 +1251,13 @@ def raise_parse_error(error_message, location) "@3", "@4", "\"-many1@id\"", + "\"-group@TAG-\\\"-many1@id\\\"\"", + "\"-many1@-group@TAG-\\\"-many1@id\\\"\"", "rules", - "\"-option@;\"", + "\"-many1@;\"", "rhs_list", "rhs", - "\"-option@parameterizing_suffix\"", + "\"-option@parameterized_suffix\"", "@5", "@6", "@7" ] @@ -1279,10 +1317,9 @@ def _reduce_6(val, _values, result) # reduce 9 omitted -module_eval(<<'.,.,', 'parser.y', 12) +module_eval(<<'.,.,', 'parser.y', 13) def _reduce_10(val, _values, result) - begin_c_declaration("%}") - @grammar.prologue_first_lineno = @lexer.line + begin_c_declaration("%}") result end @@ -1290,7 +1327,7 @@ def _reduce_10(val, _values, result) module_eval(<<'.,.,', 'parser.y', 17) def _reduce_11(val, _values, result) - end_c_declaration + end_c_declaration result end @@ -1298,22 +1335,29 @@ def _reduce_11(val, _values, result) module_eval(<<'.,.,', 'parser.y', 21) def _reduce_12(val, _values, result) - @grammar.prologue = val[2].s_value + @grammar.prologue_first_lineno = val[0].first_line + @grammar.prologue = val[2].s_value result end .,., -# reduce 13 omitted +module_eval(<<'.,.,', 'parser.y', 26) + def _reduce_13(val, _values, result) + @grammar.required = true -module_eval(<<'.,.,', 'parser.y', 54) + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 34) def _reduce_14(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 54) +module_eval(<<'.,.,', 'parser.y', 34) def _reduce_15(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result @@ -1322,150 +1366,140 @@ def _reduce_15(val, _values, result) # reduce 16 omitted -module_eval(<<'.,.,', 'parser.y', 26) - def _reduce_17(val, _values, result) - @grammar.expect = val[1] +# reduce 17 omitted + +module_eval(<<'.,.,', 'parser.y', 77) + def _reduce_18(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 27) - def _reduce_18(val, _values, result) - @grammar.define[val[1].s_value] = val[2]&.s_value +module_eval(<<'.,.,', 'parser.y', 77) + def _reduce_19(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -# reduce 19 omitted - -module_eval(<<'.,.,', 'parser.y', 31) +module_eval(<<'.,.,', 'parser.y', 36) def _reduce_20(val, _values, result) - val[1].each {|token| - @grammar.lex_param = Grammar::Code::NoReferenceCode.new(type: :lex_param, token_code: token).token_code.s_value - } + @grammar.expect = val[1].s_value result end .,., -module_eval(<<'.,.,', 'parser.y', 37) +module_eval(<<'.,.,', 'parser.y', 40) def _reduce_21(val, _values, result) - val[1].each {|token| - @grammar.parse_param = Grammar::Code::NoReferenceCode.new(type: :parse_param, token_code: token).token_code.s_value - } + @grammar.define[val[1].s_value] = val[2]&.s_value result end .,., -module_eval(<<'.,.,', 'parser.y', 43) +module_eval(<<'.,.,', 'parser.y', 44) def _reduce_22(val, _values, result) - @grammar.add_percent_code(id: val[1], code: val[2]) + @grammar.define[val[1].s_value] = val[3]&.s_value result end .,., -module_eval(<<'.,.,', 'parser.y', 47) - def _reduce_23(val, _values, result) - @grammar.initial_action = Grammar::Code::InitialActionCode.new(type: :initial_action, token_code: val[1]) - - result - end -.,., +# reduce 23 omitted module_eval(<<'.,.,', 'parser.y', 49) def _reduce_24(val, _values, result) - @grammar.no_stdlib = true + val[1].each {|token| + @grammar.lex_param = Grammar::Code::NoReferenceCode.new(type: :lex_param, token_code: token).token_code.s_value + } + result end .,., -module_eval(<<'.,.,', 'parser.y', 50) +module_eval(<<'.,.,', 'parser.y', 55) def _reduce_25(val, _values, result) - @grammar.locations = true + val[1].each {|token| + @grammar.parse_param = Grammar::Code::NoReferenceCode.new(type: :parse_param, token_code: token).token_code.s_value + } + result end .,., -# reduce 26 omitted +module_eval(<<'.,.,', 'parser.y', 61) + def _reduce_26(val, _values, result) + @grammar.add_percent_code(id: val[1], code: val[2]) + + result + end +.,., -module_eval(<<'.,.,', 'parser.y', 109) +module_eval(<<'.,.,', 'parser.y', 65) def _reduce_27(val, _values, result) - result = val + @grammar.initial_action = Grammar::Code::InitialActionCode.new(type: :initial_action, token_code: val[1]) + result end .,., -module_eval(<<'.,.,', 'parser.y', 109) +module_eval(<<'.,.,', 'parser.y', 69) def _reduce_28(val, _values, result) - result = val + @grammar.no_stdlib = true + result end .,., -module_eval(<<'.,.,', 'parser.y', 109) +module_eval(<<'.,.,', 'parser.y', 73) def _reduce_29(val, _values, result) - result = val[1] ? val[1].unshift(val[0]) : val + @grammar.locations = true + result end .,., -module_eval(<<'.,.,', 'parser.y', 109) +module_eval(<<'.,.,', 'parser.y', 133) def _reduce_30(val, _values, result) - result = val[1] ? val[1].unshift(val[0]) : val + result = val result end .,., -module_eval(<<'.,.,', 'parser.y', 55) +module_eval(<<'.,.,', 'parser.y', 133) def _reduce_31(val, _values, result) - @grammar.set_union( - Grammar::Code::NoReferenceCode.new(type: :union, token_code: val[1]), - val[1].line - ) - + result = val result end .,., -# reduce 32 omitted - -# reduce 33 omitted - -# reduce 34 omitted - -module_eval(<<'.,.,', 'parser.y', 65) - def _reduce_35(val, _values, result) - @grammar.add_destructor( - ident_or_tags: val[2].flatten, - token_code: val[1], - lineno: val[1].line - ) - +module_eval(<<'.,.,', 'parser.y', 133) + def _reduce_32(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 73) - def _reduce_36(val, _values, result) - @grammar.add_printer( - ident_or_tags: val[2].flatten, - token_code: val[1], - lineno: val[1].line - ) - +module_eval(<<'.,.,', 'parser.y', 133) + def _reduce_33(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 81) +# reduce 34 omitted + +# reduce 35 omitted + +# reduce 36 omitted + +module_eval(<<'.,.,', 'parser.y', 82) def _reduce_37(val, _values, result) - @grammar.add_error_token( - ident_or_tags: val[2].flatten, - token_code: val[1], - lineno: val[1].line - ) + @grammar.set_union( + Grammar::Code::NoReferenceCode.new(type: :union, token_code: val[1]), + val[1].line + ) result end @@ -1473,665 +1507,762 @@ def _reduce_37(val, _values, result) module_eval(<<'.,.,', 'parser.y', 89) def _reduce_38(val, _values, result) - @grammar.after_shift = val[1] + @grammar.add_destructor( + ident_or_tags: val[2].flatten, + token_code: val[1], + lineno: val[1].line + ) result end .,., -module_eval(<<'.,.,', 'parser.y', 93) +module_eval(<<'.,.,', 'parser.y', 97) def _reduce_39(val, _values, result) - @grammar.before_reduce = val[1] + @grammar.add_printer( + ident_or_tags: val[2].flatten, + token_code: val[1], + lineno: val[1].line + ) result end .,., -module_eval(<<'.,.,', 'parser.y', 97) +module_eval(<<'.,.,', 'parser.y', 105) def _reduce_40(val, _values, result) - @grammar.after_reduce = val[1] + @grammar.add_error_token( + ident_or_tags: val[2].flatten, + token_code: val[1], + lineno: val[1].line + ) result end .,., -module_eval(<<'.,.,', 'parser.y', 101) +module_eval(<<'.,.,', 'parser.y', 113) def _reduce_41(val, _values, result) - @grammar.after_shift_error_token = val[1] + @grammar.after_shift = val[1] result end .,., -module_eval(<<'.,.,', 'parser.y', 105) +module_eval(<<'.,.,', 'parser.y', 117) def _reduce_42(val, _values, result) - @grammar.after_pop_stack = val[1] + @grammar.before_reduce = val[1] result end .,., -# reduce 43 omitted - -module_eval(<<'.,.,', 'parser.y', 111) - def _reduce_44(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - @grammar.add_type(id: id, tag: hash[:tag]) - } - } +module_eval(<<'.,.,', 'parser.y', 121) + def _reduce_43(val, _values, result) + @grammar.after_reduce = val[1] result end .,., -module_eval(<<'.,.,', 'parser.y', 119) - def _reduce_45(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - if @grammar.find_term_by_s_value(id.s_value) - on_action_error("symbol #{id.s_value} redeclared as a nonterminal", id) - else - @grammar.add_type(id: id, tag: hash[:tag]) - end - } - } +module_eval(<<'.,.,', 'parser.y', 125) + def _reduce_44(val, _values, result) + @grammar.after_shift_error_token = val[1] result end .,., -module_eval(<<'.,.,', 'parser.y', 131) - def _reduce_46(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - sym = @grammar.add_term(id: id) - @grammar.add_left(sym, @precedence_number) - } - } - @precedence_number += 1 +module_eval(<<'.,.,', 'parser.y', 129) + def _reduce_45(val, _values, result) + @grammar.after_pop_stack = val[1] result end .,., -module_eval(<<'.,.,', 'parser.y', 141) +# reduce 46 omitted + +module_eval(<<'.,.,', 'parser.y', 136) def _reduce_47(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - sym = @grammar.add_term(id: id) - @grammar.add_right(sym, @precedence_number) - } - } - @precedence_number += 1 + val[1].each {|hash| + hash[:tokens].each {|id| + @grammar.add_type(id: id, tag: hash[:tag]) + } + } result end .,., -module_eval(<<'.,.,', 'parser.y', 151) +module_eval(<<'.,.,', 'parser.y', 144) def _reduce_48(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - sym = @grammar.add_term(id: id) - @grammar.add_precedence(sym, @precedence_number) - } - } - @precedence_number += 1 + val[1].each {|hash| + hash[:tokens].each {|id| + if @grammar.find_term_by_s_value(id.s_value) + on_action_error("symbol #{id.s_value} redeclared as a nonterminal", id) + else + @grammar.add_type(id: id, tag: hash[:tag]) + end + } + } result end .,., -module_eval(<<'.,.,', 'parser.y', 161) +module_eval(<<'.,.,', 'parser.y', 156) def _reduce_49(val, _values, result) - val[1].each {|hash| - hash[:tokens].each {|id| - sym = @grammar.add_term(id: id) - @grammar.add_nonassoc(sym, @precedence_number) - } - } - @precedence_number += 1 + val[1].each {|hash| + hash[:tokens].each {|id| + sym = @grammar.add_term(id: id, tag: hash[:tag]) + @grammar.add_left(sym, @precedence_number, id.s_value, id.first_line) + } + } + @precedence_number += 1 + + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 166) + def _reduce_50(val, _values, result) + val[1].each {|hash| + hash[:tokens].each {|id| + sym = @grammar.add_term(id: id, tag: hash[:tag]) + @grammar.add_right(sym, @precedence_number, id.s_value, id.first_line) + } + } + @precedence_number += 1 result end .,., -# reduce 50 omitted +module_eval(<<'.,.,', 'parser.y', 176) + def _reduce_51(val, _values, result) + val[1].each {|hash| + hash[:tokens].each {|id| + sym = @grammar.add_term(id: id, tag: hash[:tag]) + @grammar.add_precedence(sym, @precedence_number, id.s_value, id.first_line) + } + } + @precedence_number += 1 -# reduce 51 omitted + result + end +.,., -module_eval(<<'.,.,', 'parser.y', 184) +module_eval(<<'.,.,', 'parser.y', 186) def _reduce_52(val, _values, result) - result = val[1] ? val[1].unshift(val[0]) : val + val[1].each {|hash| + hash[:tokens].each {|id| + sym = @grammar.add_term(id: id, tag: hash[:tag]) + @grammar.add_nonassoc(sym, @precedence_number, id.s_value, id.first_line) + } + } + @precedence_number += 1 + result end .,., -module_eval(<<'.,.,', 'parser.y', 184) +module_eval(<<'.,.,', 'parser.y', 196) def _reduce_53(val, _values, result) + @grammar.set_start_nterm(val[1]) + + result + end +.,., + +# reduce 54 omitted + +# reduce 55 omitted + +module_eval(<<'.,.,', 'parser.y', 214) + def _reduce_56(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 214) + def _reduce_57(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 172) - def _reduce_54(val, _values, result) - val[1].each {|token_declaration| - @grammar.add_term(id: token_declaration[0], alias_name: token_declaration[2], token_id: token_declaration[1], tag: val[0], replace: true) - } +module_eval(<<'.,.,', 'parser.y', 202) + def _reduce_58(val, _values, result) + val[1].each {|token_declaration| + @grammar.add_term(id: token_declaration[0], alias_name: token_declaration[2], token_id: token_declaration[1]&.s_value, tag: val[0], replace: true) + } result end .,., -module_eval(<<'.,.,', 'parser.y', 178) - def _reduce_55(val, _values, result) - val[2].each {|token_declaration| - @grammar.add_term(id: token_declaration[0], alias_name: token_declaration[2], token_id: token_declaration[1], tag: val[1], replace: true) - } +module_eval(<<'.,.,', 'parser.y', 208) + def _reduce_59(val, _values, result) + val[2].each {|token_declaration| + @grammar.add_term(id: token_declaration[0], alias_name: token_declaration[2], token_id: token_declaration[1]&.s_value, tag: val[1], replace: true) + } result end .,., -# reduce 56 omitted +# reduce 60 omitted -# reduce 57 omitted +# reduce 61 omitted -module_eval(<<'.,.,', 'parser.y', 183) - def _reduce_58(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 213) + def _reduce_62(val, _values, result) result = val result end .,., -module_eval(<<'.,.,', 'parser.y', 187) - def _reduce_59(val, _values, result) - rule = Grammar::ParameterizingRule::Rule.new(val[1].s_value, val[3], val[7], tag: val[5]) - @grammar.add_parameterizing_rule(rule) +module_eval(<<'.,.,', 'parser.y', 218) + def _reduce_63(val, _values, result) + rule = Grammar::Parameterized::Rule.new(val[1].s_value, val[3], val[7], tag: val[5]) + @grammar.add_parameterized_rule(rule) result end .,., -module_eval(<<'.,.,', 'parser.y', 193) - def _reduce_60(val, _values, result) - rule = Grammar::ParameterizingRule::Rule.new(val[2].s_value, [], val[4], is_inline: true) - @grammar.add_parameterizing_rule(rule) +module_eval(<<'.,.,', 'parser.y', 225) + def _reduce_64(val, _values, result) + rule = Grammar::Parameterized::Rule.new(val[2].s_value, [], val[4], is_inline: true) + @grammar.add_parameterized_rule(rule) result end .,., -module_eval(<<'.,.,', 'parser.y', 198) - def _reduce_61(val, _values, result) - rule = Grammar::ParameterizingRule::Rule.new(val[2].s_value, val[4], val[7], is_inline: true) - @grammar.add_parameterizing_rule(rule) +module_eval(<<'.,.,', 'parser.y', 230) + def _reduce_65(val, _values, result) + rule = Grammar::Parameterized::Rule.new(val[2].s_value, val[4], val[7], is_inline: true) + @grammar.add_parameterized_rule(rule) result end .,., -module_eval(<<'.,.,', 'parser.y', 202) - def _reduce_62(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 235) + def _reduce_66(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'parser.y', 203) - def _reduce_63(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 236) + def _reduce_67(val, _values, result) result = val[0].append(val[2]) result end .,., -module_eval(<<'.,.,', 'parser.y', 207) - def _reduce_64(val, _values, result) - builder = val[0] - result = [builder] +module_eval(<<'.,.,', 'parser.y', 241) + def _reduce_68(val, _values, result) + builder = val[0] + result = [builder] result end .,., -module_eval(<<'.,.,', 'parser.y', 212) - def _reduce_65(val, _values, result) - builder = val[2] - result = val[0].append(builder) +module_eval(<<'.,.,', 'parser.y', 246) + def _reduce_69(val, _values, result) + builder = val[2] + result = val[0].append(builder) result end .,., -# reduce 66 omitted +# reduce 70 omitted -# reduce 67 omitted +# reduce 71 omitted -# reduce 68 omitted +# reduce 72 omitted -# reduce 69 omitted +# reduce 73 omitted -module_eval(<<'.,.,', 'parser.y', 218) - def _reduce_70(val, _values, result) - reset_precs - result = Grammar::ParameterizingRule::Rhs.new +module_eval(<<'.,.,', 'parser.y', 253) + def _reduce_74(val, _values, result) + reset_precs + result = Grammar::Parameterized::Rhs.new result end .,., -module_eval(<<'.,.,', 'parser.y', 223) - def _reduce_71(val, _values, result) - token = val[1] - token.alias_name = val[2] - builder = val[0] - builder.symbols << token - result = builder +module_eval(<<'.,.,', 'parser.y', 258) + def _reduce_75(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + token = val[1] + token.alias_name = val[2] + builder = val[0] + builder.symbols << token + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 231) - def _reduce_72(val, _values, result) - builder = val[0] - builder.symbols << Lrama::Lexer::Token::InstantiateRule.new(s_value: val[2], location: @lexer.location, args: [val[1]]) - result = builder +module_eval(<<'.,.,', 'parser.y', 267) + def _reduce_76(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + builder = val[0] + builder.symbols << Lrama::Lexer::Token::InstantiateRule.new(s_value: val[2], location: @lexer.location, args: [val[1]]) + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 237) - def _reduce_73(val, _values, result) - builder = val[0] - builder.symbols << Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, location: @lexer.location, args: val[3], lhs_tag: val[5]) - result = builder +module_eval(<<'.,.,', 'parser.y', 274) + def _reduce_77(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + builder = val[0] + builder.symbols << Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, location: @lexer.location, args: val[3], lhs_tag: val[5]) + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 243) - def _reduce_74(val, _values, result) - user_code = val[1] - user_code.alias_name = val[2] - builder = val[0] - builder.user_code = user_code - result = builder +module_eval(<<'.,.,', 'parser.y', 281) + def _reduce_78(val, _values, result) + user_code = val[1] + user_code.alias_name = val[2] + builder = val[0] + builder.user_code = user_code + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 251) - def _reduce_75(val, _values, result) - sym = @grammar.find_symbol_by_id!(val[2]) - @prec_seen = true - builder = val[0] - builder.precedence_sym = sym - result = builder +module_eval(<<'.,.,', 'parser.y', 289) + def _reduce_79(val, _values, result) + on_action_error("multiple %prec in a rule", val[0]) if prec_seen? + sym = @grammar.find_symbol_by_id!(val[2]) + if val[0].rhs.empty? + @opening_prec_seen = true + else + @trailing_prec_seen = true + end + builder = val[0] + builder.precedence_sym = sym + result = builder result end .,., -# reduce 76 omitted +# reduce 80 omitted -# reduce 77 omitted +# reduce 81 omitted -module_eval(<<'.,.,', 'parser.y', 258) - def _reduce_78(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 301) + def _reduce_82(val, _values, result) result = val[0].s_value if val[0] result end .,., -module_eval(<<'.,.,', 'parser.y', 271) - def _reduce_79(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 315) + def _reduce_83(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 271) - def _reduce_80(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 315) + def _reduce_84(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 262) - def _reduce_81(val, _values, result) - result = if val[0] - [{tag: val[0], tokens: val[1]}] - else - [{tag: nil, tokens: val[1]}] - end +module_eval(<<'.,.,', 'parser.y', 306) + def _reduce_85(val, _values, result) + result = if val[0] + [{tag: val[0], tokens: val[1]}] + else + [{tag: nil, tokens: val[1]}] + end result end .,., -module_eval(<<'.,.,', 'parser.y', 268) - def _reduce_82(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 312) + def _reduce_86(val, _values, result) result = val[0].append({tag: val[1], tokens: val[2]}) result end .,., -# reduce 83 omitted +# reduce 87 omitted -# reduce 84 omitted +# reduce 88 omitted -module_eval(<<'.,.,', 'parser.y', 274) - def _reduce_85(val, _values, result) - begin_c_declaration("}") +module_eval(<<'.,.,', 'parser.y', 321) + def _reduce_89(val, _values, result) + begin_c_declaration("}") result end .,., -module_eval(<<'.,.,', 'parser.y', 278) - def _reduce_86(val, _values, result) - end_c_declaration +module_eval(<<'.,.,', 'parser.y', 325) + def _reduce_90(val, _values, result) + end_c_declaration result end .,., -module_eval(<<'.,.,', 'parser.y', 282) - def _reduce_87(val, _values, result) - result = val[2] +module_eval(<<'.,.,', 'parser.y', 329) + def _reduce_91(val, _values, result) + result = val[2] result end .,., -module_eval(<<'.,.,', 'parser.y', 290) - def _reduce_88(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 338) + def _reduce_92(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 290) - def _reduce_89(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 338) + def _reduce_93(val, _values, result) result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 285) - def _reduce_90(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 338) + def _reduce_94(val, _values, result) + result = val + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 338) + def _reduce_95(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 338) + def _reduce_96(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val + result + end +.,., + +module_eval(<<'.,.,', 'parser.y', 333) + def _reduce_97(val, _values, result) result = [{tag: nil, tokens: val[0]}] result end .,., -module_eval(<<'.,.,', 'parser.y', 286) - def _reduce_91(val, _values, result) - result = [{tag: val[0], tokens: val[1]}] +module_eval(<<'.,.,', 'parser.y', 334) + def _reduce_98(val, _values, result) + result = val[0].map {|tag, ids| {tag: tag, tokens: ids} } result end .,., -module_eval(<<'.,.,', 'parser.y', 287) - def _reduce_92(val, _values, result) - result = val[0].append({tag: val[1], tokens: val[2]}) +module_eval(<<'.,.,', 'parser.y', 335) + def _reduce_99(val, _values, result) + result = [{tag: nil, tokens: val[0]}, {tag: val[1], tokens: val[2]}] result end .,., -module_eval(<<'.,.,', 'parser.y', 289) - def _reduce_93(val, _values, result) - on_action_error("ident after %prec", val[0]) if @prec_seen +# reduce 100 omitted + +# reduce 101 omitted + +module_eval(<<'.,.,', 'parser.y', 346) + def _reduce_102(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -module_eval(<<'.,.,', 'parser.y', 290) - def _reduce_94(val, _values, result) - on_action_error("char after %prec", val[0]) if @prec_seen +module_eval(<<'.,.,', 'parser.y', 346) + def _reduce_103(val, _values, result) + result = val[1] ? val[1].unshift(val[0]) : val result end .,., -# reduce 95 omitted +# reduce 104 omitted -# reduce 96 omitted +# reduce 105 omitted -# reduce 97 omitted +module_eval(<<'.,.,', 'parser.y', 348) + def _reduce_106(val, _values, result) + lhs = val[0] + lhs.alias_name = val[1] + val[3].each do |builder| + builder.lhs = lhs + builder.complete_input + @grammar.add_rule_builder(builder) + end -# reduce 98 omitted + result + end +.,., -module_eval(<<'.,.,', 'parser.y', 298) - def _reduce_99(val, _values, result) - lhs = val[0] - lhs.alias_name = val[1] - val[3].each do |builder| - builder.lhs = lhs - builder.complete_input - @grammar.add_rule_builder(builder) - end +module_eval(<<'.,.,', 'parser.y', 360) + def _reduce_107(val, _values, result) + if val[0].rhs.count > 1 + empties = val[0].rhs.select { |sym| sym.is_a?(Lrama::Lexer::Token::Empty) } + empties.each do |empty| + on_action_error("%empty on non-empty rule", empty) + end + end + builder = val[0] + if !builder.line + builder.line = @lexer.line - 1 + end + result = [builder] result end .,., -module_eval(<<'.,.,', 'parser.y', 309) - def _reduce_100(val, _values, result) - builder = val[0] - if !builder.line - builder.line = @lexer.line - 1 - end - result = [builder] +module_eval(<<'.,.,', 'parser.y', 374) + def _reduce_108(val, _values, result) + builder = val[2] + if !builder.line + builder.line = @lexer.line - 1 + end + result = val[0].append(builder) result end .,., -module_eval(<<'.,.,', 'parser.y', 317) - def _reduce_101(val, _values, result) - builder = val[2] - if !builder.line - builder.line = @lexer.line - 1 - end - result = val[0].append(builder) +module_eval(<<'.,.,', 'parser.y', 384) + def _reduce_109(val, _values, result) + reset_precs + result = @grammar.create_rule_builder(@rule_counter, @midrule_action_counter) result end .,., -module_eval(<<'.,.,', 'parser.y', 326) - def _reduce_102(val, _values, result) - reset_precs - result = @grammar.create_rule_builder(@rule_counter, @midrule_action_counter) +module_eval(<<'.,.,', 'parser.y', 389) + def _reduce_110(val, _values, result) + builder = val[0] + builder.add_rhs(Lrama::Lexer::Token::Empty.new(location: @lexer.location)) + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 331) - def _reduce_103(val, _values, result) - token = val[1] - token.alias_name = val[2] - builder = val[0] - builder.add_rhs(token) - result = builder +module_eval(<<'.,.,', 'parser.y', 395) + def _reduce_111(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + token = val[1] + token.alias_name = val[2] + builder = val[0] + builder.add_rhs(token) + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 339) - def _reduce_104(val, _values, result) - token = Lrama::Lexer::Token::InstantiateRule.new(s_value: val[2], alias_name: val[3], location: @lexer.location, args: [val[1]], lhs_tag: val[4]) - builder = val[0] - builder.add_rhs(token) - builder.line = val[1].first_line - result = builder +module_eval(<<'.,.,', 'parser.y', 404) + def _reduce_112(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + token = Lrama::Lexer::Token::InstantiateRule.new(s_value: val[2], alias_name: val[3], location: @lexer.location, args: [val[1]], lhs_tag: val[4]) + builder = val[0] + builder.add_rhs(token) + builder.line = val[1].first_line + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 347) - def _reduce_105(val, _values, result) - token = Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, alias_name: val[5], location: @lexer.location, args: val[3], lhs_tag: val[6]) - builder = val[0] - builder.add_rhs(token) - builder.line = val[1].first_line - result = builder +module_eval(<<'.,.,', 'parser.y', 413) + def _reduce_113(val, _values, result) + on_action_error("intermediate %prec in a rule", val[1]) if @trailing_prec_seen + token = Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, alias_name: val[5], location: @lexer.location, args: val[3], lhs_tag: val[6]) + builder = val[0] + builder.add_rhs(token) + builder.line = val[1].first_line + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 355) - def _reduce_106(val, _values, result) - user_code = val[1] - user_code.alias_name = val[2] - user_code.tag = val[3] - builder = val[0] - builder.user_code = user_code - result = builder +module_eval(<<'.,.,', 'parser.y', 422) + def _reduce_114(val, _values, result) + user_code = val[1] + user_code.alias_name = val[2] + user_code.tag = val[3] + builder = val[0] + builder.user_code = user_code + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 364) - def _reduce_107(val, _values, result) - sym = @grammar.find_symbol_by_id!(val[2]) - @prec_seen = true - builder = val[0] - builder.precedence_sym = sym - result = builder +module_eval(<<'.,.,', 'parser.y', 431) + def _reduce_115(val, _values, result) + on_action_error("multiple %prec in a rule", val[0]) if prec_seen? + sym = @grammar.find_symbol_by_id!(val[2]) + if val[0].rhs.empty? + @opening_prec_seen = true + else + @trailing_prec_seen = true + end + builder = val[0] + builder.precedence_sym = sym + result = builder result end .,., -module_eval(<<'.,.,', 'parser.y', 371) - def _reduce_108(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 444) + def _reduce_116(val, _values, result) result = "option" result end .,., -module_eval(<<'.,.,', 'parser.y', 372) - def _reduce_109(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 445) + def _reduce_117(val, _values, result) result = "nonempty_list" result end .,., -module_eval(<<'.,.,', 'parser.y', 373) - def _reduce_110(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 446) + def _reduce_118(val, _values, result) result = "list" result end .,., -# reduce 111 omitted +# reduce 119 omitted -# reduce 112 omitted +# reduce 120 omitted -module_eval(<<'.,.,', 'parser.y', 377) - def _reduce_113(val, _values, result) - result = if val[1] - [Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, location: @lexer.location, args: val[0])] - else - [val[0]] - end +module_eval(<<'.,.,', 'parser.y', 451) + def _reduce_121(val, _values, result) + result = if val[1] + [Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, location: @lexer.location, args: val[0])] + else + [val[0]] + end result end .,., -module_eval(<<'.,.,', 'parser.y', 383) - def _reduce_114(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 457) + def _reduce_122(val, _values, result) result = val[0].append(val[2]) result end .,., -module_eval(<<'.,.,', 'parser.y', 384) - def _reduce_115(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 458) + def _reduce_123(val, _values, result) result = [Lrama::Lexer::Token::InstantiateRule.new(s_value: val[0].s_value, location: @lexer.location, args: val[2])] result end .,., -module_eval(<<'.,.,', 'parser.y', 388) - def _reduce_116(val, _values, result) - if @prec_seen - on_action_error("multiple User_code after %prec", val[0]) if @code_after_prec - @code_after_prec = true - end - begin_c_declaration("}") +module_eval(<<'.,.,', 'parser.y', 463) + def _reduce_124(val, _values, result) + if prec_seen? + on_action_error("multiple User_code after %prec", val[0]) if @code_after_prec + @code_after_prec = true + end + begin_c_declaration("}") result end .,., -module_eval(<<'.,.,', 'parser.y', 396) - def _reduce_117(val, _values, result) - end_c_declaration +module_eval(<<'.,.,', 'parser.y', 471) + def _reduce_125(val, _values, result) + end_c_declaration result end .,., -module_eval(<<'.,.,', 'parser.y', 400) - def _reduce_118(val, _values, result) - result = val[2] +module_eval(<<'.,.,', 'parser.y', 475) + def _reduce_126(val, _values, result) + result = val[2] result end .,., -module_eval(<<'.,.,', 'parser.y', 403) - def _reduce_119(val, _values, result) +module_eval(<<'.,.,', 'parser.y', 478) + def _reduce_127(val, _values, result) result = val[1].s_value result end .,., -module_eval(<<'.,.,', 'parser.y', 407) - def _reduce_120(val, _values, result) - begin_c_declaration('\Z') - @grammar.epilogue_first_lineno = @lexer.line + 1 +module_eval(<<'.,.,', 'parser.y', 483) + def _reduce_128(val, _values, result) + begin_c_declaration('\Z') result end .,., -module_eval(<<'.,.,', 'parser.y', 412) - def _reduce_121(val, _values, result) - end_c_declaration - @grammar.epilogue = val[2].s_value +module_eval(<<'.,.,', 'parser.y', 487) + def _reduce_129(val, _values, result) + end_c_declaration + @grammar.epilogue_first_lineno = val[0].first_line + 1 + @grammar.epilogue = val[2].s_value result end .,., -# reduce 122 omitted +# reduce 130 omitted -# reduce 123 omitted +# reduce 131 omitted -# reduce 124 omitted +# reduce 132 omitted -# reduce 125 omitted +# reduce 133 omitted -# reduce 126 omitted +# reduce 134 omitted -module_eval(<<'.,.,', 'parser.y', 423) - def _reduce_127(val, _values, result) - result = Lrama::Lexer::Token::Ident.new(s_value: val[0]) +module_eval(<<'.,.,', 'parser.y', 499) + def _reduce_135(val, _values, result) + result = Lrama::Lexer::Token::Ident.new(s_value: val[0].s_value) result end .,., diff --git a/tool/lrama/lib/lrama/report.rb b/tool/lrama/lib/lrama/report.rb deleted file mode 100644 index 890e5f1e8c5cc4..00000000000000 --- a/tool/lrama/lib/lrama/report.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -require_relative 'report/duration' -require_relative 'report/profile' diff --git a/tool/lrama/lib/lrama/report/duration.rb b/tool/lrama/lib/lrama/report/duration.rb deleted file mode 100644 index fe09a0d028f501..00000000000000 --- a/tool/lrama/lib/lrama/report/duration.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Report - module Duration - def self.enable - @_report_duration_enabled = true - end - - def self.enabled? - !!@_report_duration_enabled - end - - def report_duration(method_name) - time1 = Time.now.to_f - result = yield - time2 = Time.now.to_f - - if Duration.enabled? - puts sprintf("%s %10.5f s", method_name, time2 - time1) - end - - return result - end - end - end -end diff --git a/tool/lrama/lib/lrama/report/profile.rb b/tool/lrama/lib/lrama/report/profile.rb deleted file mode 100644 index 10488cf9130bd1..00000000000000 --- a/tool/lrama/lib/lrama/report/profile.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class Report - module Profile - # See "Profiling Lrama" in README.md for how to use. - def self.report_profile - require "stackprof" - - StackProf.run(mode: :cpu, raw: true, out: 'tmp/stackprof-cpu-myapp.dump') do - yield - end - end - end - end -end diff --git a/tool/lrama/lib/lrama/reporter.rb b/tool/lrama/lib/lrama/reporter.rb new file mode 100644 index 00000000000000..ed25cc7f8fcaf2 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter.rb @@ -0,0 +1,39 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +require_relative 'reporter/conflicts' +require_relative 'reporter/grammar' +require_relative 'reporter/precedences' +require_relative 'reporter/profile' +require_relative 'reporter/rules' +require_relative 'reporter/states' +require_relative 'reporter/terms' + +module Lrama + class Reporter + include Lrama::Tracer::Duration + + # @rbs (**bool options) -> void + def initialize(**options) + @options = options + @rules = Rules.new(**options) + @terms = Terms.new(**options) + @conflicts = Conflicts.new + @precedences = Precedences.new + @grammar = Grammar.new(**options) + @states = States.new(**options) + end + + # @rbs (File io, Lrama::States states) -> void + def report(io, states) + report_duration(:report) do + report_duration(:report_rules) { @rules.report(io, states) } + report_duration(:report_terms) { @terms.report(io, states) } + report_duration(:report_conflicts) { @conflicts.report(io, states) } + report_duration(:report_precedences) { @precedences.report(io, states) } + report_duration(:report_grammar) { @grammar.report(io, states) } + report_duration(:report_states) { @states.report(io, states, ielr: states.ielr_defined?) } + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/conflicts.rb b/tool/lrama/lib/lrama/reporter/conflicts.rb new file mode 100644 index 00000000000000..f4d8c604c9efa5 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/conflicts.rb @@ -0,0 +1,44 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class Conflicts + # @rbs (IO io, Lrama::States states) -> void + def report(io, states) + report_conflicts(io, states) + end + + private + + # @rbs (IO io, Lrama::States states) -> void + def report_conflicts(io, states) + has_conflict = false + + states.states.each do |state| + messages = format_conflict_messages(state.conflicts) + + unless messages.empty? + has_conflict = true + io << "State #{state.id} conflicts: #{messages.join(', ')}\n" + end + end + + io << "\n\n" if has_conflict + end + + # @rbs (Array[(Lrama::State::ShiftReduceConflict | Lrama::State::ReduceReduceConflict)] conflicts) -> Array[String] + def format_conflict_messages(conflicts) + conflict_types = { + shift_reduce: "shift/reduce", + reduce_reduce: "reduce/reduce" + } + + conflict_types.keys.map do |type| + type_conflicts = conflicts.select { |c| c.type == type } + "#{type_conflicts.count} #{conflict_types[type]}" unless type_conflicts.empty? + end.compact + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/grammar.rb b/tool/lrama/lib/lrama/reporter/grammar.rb new file mode 100644 index 00000000000000..dc3f3f6bfd9a22 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/grammar.rb @@ -0,0 +1,39 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class Grammar + # @rbs (?grammar: bool, **bool _) -> void + def initialize(grammar: false, **_) + @grammar = grammar + end + + # @rbs (IO io, Lrama::States states) -> void + def report(io, states) + return unless @grammar + + io << "Grammar\n" + last_lhs = nil + + states.rules.each do |rule| + if rule.empty_rule? + r = "ε" + else + r = rule.rhs.map(&:display_name).join(" ") + end + + if rule.lhs == last_lhs + io << sprintf("%5d %s| %s", rule.id, " " * rule.lhs.display_name.length, r) << "\n" + else + io << "\n" + io << sprintf("%5d %s: %s", rule.id, rule.lhs.display_name, r) << "\n" + end + + last_lhs = rule.lhs + end + io << "\n\n" + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/precedences.rb b/tool/lrama/lib/lrama/reporter/precedences.rb new file mode 100644 index 00000000000000..73c0888700c7db --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/precedences.rb @@ -0,0 +1,54 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class Precedences + # @rbs (IO io, Lrama::States states) -> void + def report(io, states) + report_precedences(io, states) + end + + private + + # @rbs (IO io, Lrama::States states) -> void + def report_precedences(io, states) + used_precedences = states.precedences.select(&:used_by?) + + return if used_precedences.empty? + + io << "Precedences\n\n" + + used_precedences.each do |precedence| + io << " precedence on #{precedence.symbol.display_name} is used to resolve conflict on\n" + + if precedence.used_by_lalr? + io << " LALR\n" + + precedence.used_by_lalr.uniq.sort_by do |resolved_conflict| + resolved_conflict.state.id + end.each do |resolved_conflict| + io << " state #{resolved_conflict.state.id}. #{resolved_conflict.report_precedences_message}\n" + end + + io << "\n" + end + + if precedence.used_by_ielr? + io << " IELR\n" + + precedence.used_by_ielr.uniq.sort_by do |resolved_conflict| + resolved_conflict.state.id + end.each do |resolved_conflict| + io << " state #{resolved_conflict.state.id}. #{resolved_conflict.report_precedences_message}\n" + end + + io << "\n" + end + end + + io << "\n" + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/profile.rb b/tool/lrama/lib/lrama/reporter/profile.rb new file mode 100644 index 00000000000000..b569b94d4f3549 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/profile.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require_relative 'profile/call_stack' +require_relative 'profile/memory' diff --git a/tool/lrama/lib/lrama/reporter/profile/call_stack.rb b/tool/lrama/lib/lrama/reporter/profile/call_stack.rb new file mode 100644 index 00000000000000..8a4d44b61ca437 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/profile/call_stack.rb @@ -0,0 +1,45 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + module Profile + module CallStack + # See "Call-stack Profiling Lrama" in README.md for how to use. + # + # @rbs enabled: bool + # @rbs &: -> void + # @rbs return: StackProf::result | void + def self.report(enabled) + if enabled && require_stackprof + ex = nil #: Exception? + path = 'tmp/stackprof-cpu-myapp.dump' + + StackProf.run(mode: :cpu, raw: true, out: path) do + yield + rescue Exception => e + ex = e + end + + STDERR.puts("Call-stack Profiling result is generated on #{path}") + + if ex + raise ex + end + else + yield + end + end + + # @rbs return: bool + def self.require_stackprof + require "stackprof" + true + rescue LoadError + warn "stackprof is not installed. Please run `bundle install`." + false + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/profile/memory.rb b/tool/lrama/lib/lrama/reporter/profile/memory.rb new file mode 100644 index 00000000000000..a019581fdfe514 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/profile/memory.rb @@ -0,0 +1,44 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + module Profile + module Memory + # See "Memory Profiling Lrama" in README.md for how to use. + # + # @rbs enabled: bool + # @rbs &: -> void + # @rbs return: StackProf::result | void + def self.report(enabled) + if enabled && require_memory_profiler + ex = nil #: Exception? + + report = MemoryProfiler.report do # steep:ignore UnknownConstant + yield + rescue Exception => e + ex = e + end + + report.pretty_print(to_file: "tmp/memory_profiler.txt") + + if ex + raise ex + end + else + yield + end + end + + # @rbs return: bool + def self.require_memory_profiler + require "memory_profiler" + true + rescue LoadError + warn "memory_profiler is not installed. Please run `bundle install`." + false + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/rules.rb b/tool/lrama/lib/lrama/reporter/rules.rb new file mode 100644 index 00000000000000..3e8bf19a0a62eb --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/rules.rb @@ -0,0 +1,43 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class Rules + # @rbs (?rules: bool, **bool _) -> void + def initialize(rules: false, **_) + @rules = rules + end + + # @rbs (IO io, Lrama::States states) -> void + def report(io, states) + return unless @rules + + used_rules = states.rules.flat_map(&:rhs) + + unless used_rules.empty? + io << "Rule Usage Frequency\n\n" + frequency_counts = used_rules.each_with_object(Hash.new(0)) { |rule, counts| counts[rule] += 1 } + + frequency_counts + .select { |rule,| !rule.midrule? } + .sort_by { |rule, count| [-count, rule.name] } + .each_with_index { |(rule, count), i| io << sprintf("%5d %s (%d times)", i, rule.name, count) << "\n" } + io << "\n\n" + end + + unused_rules = states.rules.map(&:lhs).select do |rule| + !used_rules.include?(rule) && rule.token_id != 0 + end + + unless unused_rules.empty? + io << "#{unused_rules.count} Unused Rules\n\n" + unused_rules.each_with_index do |rule, index| + io << sprintf("%5d %s", index, rule.display_name) << "\n" + end + io << "\n\n" + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/states.rb b/tool/lrama/lib/lrama/reporter/states.rb new file mode 100644 index 00000000000000..d152d0511a46cf --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/states.rb @@ -0,0 +1,387 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class States + # @rbs (?itemsets: bool, ?lookaheads: bool, ?solved: bool, ?counterexamples: bool, ?verbose: bool, **bool _) -> void + def initialize(itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false, **_) + @itemsets = itemsets + @lookaheads = lookaheads + @solved = solved + @counterexamples = counterexamples + @verbose = verbose + end + + # @rbs (IO io, Lrama::States states, ielr: bool) -> void + def report(io, states, ielr: false) + cex = Counterexamples.new(states) if @counterexamples + + states.compute_la_sources_for_conflicted_states + report_split_states(io, states.states) if ielr + + states.states.each do |state| + report_state_header(io, state) + report_items(io, state) + report_conflicts(io, state) + report_shifts(io, state) + report_nonassoc_errors(io, state) + report_reduces(io, state) + report_nterm_transitions(io, state) + report_conflict_resolutions(io, state) if @solved + report_counterexamples(io, state, cex) if @counterexamples && state.has_conflicts? # @type var cex: Lrama::Counterexamples + report_verbose_info(io, state, states) if @verbose + # End of Report State + io << "\n" + end + end + + private + + # @rbs (IO io, Array[Lrama::State] states) -> void + def report_split_states(io, states) + ss = states.select(&:split_state?) + + return if ss.empty? + + io << "Split States\n\n" + + ss.each do |state| + io << " State #{state.id} is split from state #{state.lalr_isocore.id}\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_state_header(io, state) + io << "State #{state.id}\n\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_items(io, state) + last_lhs = nil + list = @itemsets ? state.items : state.kernels + + list.sort_by {|i| [i.rule_id, i.position] }.each do |item| + r = item.empty_rule? ? "ε •" : item.rhs.map(&:display_name).insert(item.position, "•").join(" ") + + l = if item.lhs == last_lhs + " " * item.lhs.id.s_value.length + "|" + else + item.lhs.id.s_value + ":" + end + + la = "" + if @lookaheads && item.end_of_rule? + reduce = state.find_reduce_by_item!(item) + look_ahead = reduce.selected_look_ahead + unless look_ahead.empty? + la = " [#{look_ahead.compact.map(&:display_name).join(", ")}]" + end + end + + last_lhs = item.lhs + io << sprintf("%5i %s %s%s", item.rule_id, l, r, la) << "\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_conflicts(io, state) + return if state.conflicts.empty? + + state.conflicts.each do |conflict| + syms = conflict.symbols.map { |sym| sym.display_name } + io << " Conflict on #{syms.join(", ")}. " + + case conflict.type + when :shift_reduce + # @type var conflict: Lrama::State::ShiftReduceConflict + io << "shift/reduce(#{conflict.reduce.item.rule.lhs.display_name})\n" + + conflict.symbols.each do |token| + conflict.reduce.look_ahead_sources[token].each do |goto| # steep:ignore NoMethod + io << " #{token.display_name} comes from state #{goto.from_state.id} goto by #{goto.next_sym.display_name}\n" + end + end + when :reduce_reduce + # @type var conflict: Lrama::State::ReduceReduceConflict + io << "reduce(#{conflict.reduce1.item.rule.lhs.display_name})/reduce(#{conflict.reduce2.item.rule.lhs.display_name})\n" + + conflict.symbols.each do |token| + conflict.reduce1.look_ahead_sources[token].each do |goto| # steep:ignore NoMethod + io << " #{token.display_name} comes from state #{goto.from_state.id} goto by #{goto.next_sym.display_name}\n" + end + + conflict.reduce2.look_ahead_sources[token].each do |goto| # steep:ignore NoMethod + io << " #{token.display_name} comes from state #{goto.from_state.id} goto by #{goto.next_sym.display_name}\n" + end + end + else + raise "Unknown conflict type #{conflict.type}" + end + + io << "\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_shifts(io, state) + shifts = state.term_transitions.reject(&:not_selected) + + return if shifts.empty? + + next_syms = shifts.map(&:next_sym) + max_len = next_syms.map(&:display_name).map(&:length).max + shifts.each do |shift| + io << " #{shift.next_sym.display_name.ljust(max_len)} shift, and go to state #{shift.to_state.id}\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_nonassoc_errors(io, state) + error_symbols = state.resolved_conflicts.select { |resolved| resolved.which == :error }.map { |error| error.symbol.display_name } + + return if error_symbols.empty? + + max_len = error_symbols.map(&:length).max + error_symbols.each do |name| + io << " #{name.ljust(max_len)} error (nonassociative)\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_reduces(io, state) + reduce_pairs = [] #: Array[[Lrama::Grammar::Symbol, Lrama::State::Action::Reduce]] + + state.non_default_reduces.each do |reduce| + reduce.look_ahead&.each do |term| + reduce_pairs << [term, reduce] + end + end + + return if reduce_pairs.empty? && !state.default_reduction_rule + + max_len = [ + reduce_pairs.map(&:first).map(&:display_name).map(&:length).max || 0, + state.default_reduction_rule ? "$default".length : 0 + ].max + + reduce_pairs.sort_by { |term, _| term.number }.each do |term, reduce| + rule = reduce.item.rule + io << " #{term.display_name.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.display_name})\n" + end + + if (r = state.default_reduction_rule) + s = "$default".ljust(max_len) + + if r.initial_rule? + io << " #{s} accept\n" + else + io << " #{s} reduce using rule #{r.id} (#{r.lhs.display_name})\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_nterm_transitions(io, state) + return if state.nterm_transitions.empty? + + goto_transitions = state.nterm_transitions.sort_by do |goto| + goto.next_sym.number + end + + max_len = goto_transitions.map(&:next_sym).map do |nterm| + nterm.id.s_value.length + end.max + goto_transitions.each do |goto| + io << " #{goto.next_sym.id.s_value.ljust(max_len)} go to state #{goto.to_state.id}\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state) -> void + def report_conflict_resolutions(io, state) + return if state.resolved_conflicts.empty? + + state.resolved_conflicts.each do |resolved| + io << " #{resolved.report_message}\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::Counterexamples cex) -> void + def report_counterexamples(io, state, cex) + examples = cex.compute(state) + + examples.each do |example| + is_shift_reduce = example.type == :shift_reduce + label0 = is_shift_reduce ? "shift/reduce" : "reduce/reduce" + label1 = is_shift_reduce ? "Shift derivation" : "First Reduce derivation" + label2 = is_shift_reduce ? "Reduce derivation" : "Second Reduce derivation" + + io << " #{label0} conflict on token #{example.conflict_symbol.id.s_value}:\n" + io << " #{example.path1_item}\n" + io << " #{example.path2_item}\n" + io << " #{label1}\n" + + example.derivations1.render_strings_for_report.each do |str| + io << " #{str}\n" + end + + io << " #{label2}\n" + + example.derivations2.render_strings_for_report.each do |str| + io << " #{str}\n" + end + end + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_verbose_info(io, state, states) + report_direct_read_sets(io, state, states) + report_reads_relation(io, state, states) + report_read_sets(io, state, states) + report_includes_relation(io, state, states) + report_lookback_relation(io, state, states) + report_follow_sets(io, state, states) + report_look_ahead_sets(io, state, states) + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_direct_read_sets(io, state, states) + io << " [Direct Read sets]\n" + direct_read_sets = states.direct_read_sets + + state.nterm_transitions.each do |goto| + terms = direct_read_sets[goto] + next unless terms && !terms.empty? + + str = terms.map { |sym| sym.id.s_value }.join(", ") + io << " read #{goto.next_sym.id.s_value} shift #{str}\n" + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_reads_relation(io, state, states) + io << " [Reads Relation]\n" + + state.nterm_transitions.each do |goto| + goto2 = states.reads_relation[goto] + next unless goto2 + + goto2.each do |goto2| + io << " (State #{goto2.from_state.id}, #{goto2.next_sym.id.s_value})\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_read_sets(io, state, states) + io << " [Read sets]\n" + read_sets = states.read_sets + + state.nterm_transitions.each do |goto| + terms = read_sets[goto] + next unless terms && !terms.empty? + + terms.each do |sym| + io << " #{sym.id.s_value}\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_includes_relation(io, state, states) + io << " [Includes Relation]\n" + + state.nterm_transitions.each do |goto| + gotos = states.includes_relation[goto] + next unless gotos + + gotos.each do |goto2| + io << " (State #{state.id}, #{goto.next_sym.id.s_value}) -> (State #{goto2.from_state.id}, #{goto2.next_sym.id.s_value})\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_lookback_relation(io, state, states) + io << " [Lookback Relation]\n" + + states.rules.each do |rule| + gotos = states.lookback_relation.dig(state.id, rule.id) + next unless gotos + + gotos.each do |goto2| + io << " (Rule: #{rule.display_name}) -> (State #{goto2.from_state.id}, #{goto2.next_sym.id.s_value})\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_follow_sets(io, state, states) + io << " [Follow sets]\n" + follow_sets = states.follow_sets + + state.nterm_transitions.each do |goto| + terms = follow_sets[goto] + next unless terms + + terms.each do |sym| + io << " #{goto.next_sym.id.s_value} -> #{sym.id.s_value}\n" + end + end + + io << "\n" + end + + # @rbs (IO io, Lrama::State state, Lrama::States states) -> void + def report_look_ahead_sets(io, state, states) + io << " [Look-Ahead Sets]\n" + look_ahead_rules = [] #: Array[[Lrama::Grammar::Rule, Array[Lrama::Grammar::Symbol]]] + + states.rules.each do |rule| + syms = states.la.dig(state.id, rule.id) + next unless syms + + look_ahead_rules << [rule, syms] + end + + return if look_ahead_rules.empty? + + max_len = look_ahead_rules.flat_map { |_, syms| syms.map { |s| s.id.s_value.length } }.max + + look_ahead_rules.each do |rule, syms| + syms.each do |sym| + io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n" + end + end + + io << "\n" + end + end + end +end diff --git a/tool/lrama/lib/lrama/reporter/terms.rb b/tool/lrama/lib/lrama/reporter/terms.rb new file mode 100644 index 00000000000000..f72d8b1a1a8137 --- /dev/null +++ b/tool/lrama/lib/lrama/reporter/terms.rb @@ -0,0 +1,44 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Reporter + class Terms + # @rbs (?terms: bool, **bool _) -> void + def initialize(terms: false, **_) + @terms = terms + end + + # @rbs (IO io, Lrama::States states) -> void + def report(io, states) + return unless @terms + + look_aheads = states.states.each do |state| + state.reduces.flat_map do |reduce| + reduce.look_ahead unless reduce.look_ahead.nil? + end + end + + next_terms = states.states.flat_map do |state| + state.term_transitions.map {|shift| shift.next_sym } + end + + unused_symbols = states.terms.reject do |term| + (look_aheads + next_terms).include?(term) + end + + io << states.terms.count << " Terms\n\n" + + io << states.nterms.count << " Non-Terminals\n\n" + + unless unused_symbols.empty? + io << "#{unused_symbols.count} Unused Terms\n\n" + unused_symbols.each_with_index do |term, index| + io << sprintf("%5d %s", index, term.id.s_value) << "\n" + end + io << "\n\n" + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/state.rb b/tool/lrama/lib/lrama/state.rb index 3008786ced0271..50912e094e427c 100644 --- a/tool/lrama/lib/lrama/state.rb +++ b/tool/lrama/lib/lrama/state.rb @@ -1,17 +1,62 @@ +# rbs_inline: enabled # frozen_string_literal: true -require_relative "state/reduce" +require_relative "state/action" +require_relative "state/inadequacy_annotation" +require_relative "state/item" require_relative "state/reduce_reduce_conflict" require_relative "state/resolved_conflict" -require_relative "state/shift" require_relative "state/shift_reduce_conflict" module Lrama class State - attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts, - :default_reduction_rule, :closure, :items - attr_accessor :shifts, :reduces, :ielr_isocores, :lalr_isocore - + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # type conflict = State::ShiftReduceConflict | State::ReduceReduceConflict + # type transition = Action::Shift | Action::Goto + # type lookahead_set = Hash[Item, Array[Grammar::Symbol]] + # + # @id: Integer + # @accessing_symbol: Grammar::Symbol + # @kernels: Array[Item] + # @items: Array[Item] + # @items_to_state: Hash[Array[Item], State] + # @conflicts: Array[conflict] + # @resolved_conflicts: Array[ResolvedConflict] + # @default_reduction_rule: Grammar::Rule? + # @closure: Array[Item] + # @nterm_transitions: Array[Action::Goto] + # @term_transitions: Array[Action::Shift] + # @transitions: Array[transition] + # @internal_dependencies: Hash[Action::Goto, Array[Action::Goto]] + # @successor_dependencies: Hash[Action::Goto, Array[Action::Goto]] + + attr_reader :id #: Integer + attr_reader :accessing_symbol #: Grammar::Symbol + attr_reader :kernels #: Array[Item] + attr_reader :conflicts #: Array[conflict] + attr_reader :resolved_conflicts #: Array[ResolvedConflict] + attr_reader :default_reduction_rule #: Grammar::Rule? + attr_reader :closure #: Array[Item] + attr_reader :items #: Array[Item] + attr_reader :annotation_list #: Array[InadequacyAnnotation] + attr_reader :predecessors #: Array[State] + attr_reader :items_to_state #: Hash[Array[Item], State] + attr_reader :lane_items #: Hash[State, Array[[Item, Item]]] + + attr_accessor :_transitions #: Array[[Grammar::Symbol, Array[Item]]] + attr_accessor :reduces #: Array[Action::Reduce] + attr_accessor :ielr_isocores #: Array[State] + attr_accessor :lalr_isocore #: State + attr_accessor :lookaheads_recomputed #: bool + attr_accessor :follow_kernel_items #: Hash[Action::Goto, Hash[Item, bool]] + attr_accessor :always_follows #: Hash[Action::Goto, Array[Grammar::Symbol]] + attr_accessor :goto_follows #: Hash[Action::Goto, Array[Grammar::Symbol]] + + # @rbs (Integer id, Grammar::Symbol accessing_symbol, Array[Item] kernels) -> void def initialize(id, accessing_symbol, kernels) @id = id @accessing_symbol = accessing_symbol @@ -28,48 +73,72 @@ def initialize(id, accessing_symbol, kernels) @ielr_isocores = [self] @internal_dependencies = {} @successor_dependencies = {} + @annotation_list = [] + @lookaheads_recomputed = false + @follow_kernel_items = {} @always_follows = {} + @goto_follows = {} + @lhs_contributions = {} + @lane_items = {} + end + + # @rbs (State other) -> bool + def ==(other) + self.id == other.id end + # @rbs (Array[Item] closure) -> void def closure=(closure) @closure = closure @items = @kernels + @closure end + # @rbs () -> Array[Action::Reduce] def non_default_reduces reduces.reject do |reduce| reduce.rule == @default_reduction_rule end end - def compute_shifts_reduces - _shifts = {} + # @rbs () -> void + def compute_transitions_and_reduces + _transitions = {} + @_lane_items ||= {} reduces = [] items.each do |item| # TODO: Consider what should be pushed if item.end_of_rule? - reduces << Reduce.new(item) + reduces << Action::Reduce.new(item) else key = item.next_sym - _shifts[key] ||= [] - _shifts[key] << item.new_by_next_position + _transitions[key] ||= [] + @_lane_items[key] ||= [] + next_item = item.new_by_next_position + _transitions[key] << next_item + @_lane_items[key] << [item, next_item] end end # It seems Bison 3.8.2 iterates transitions order by symbol number - shifts = _shifts.sort_by do |next_sym, new_items| + transitions = _transitions.sort_by do |next_sym, to_items| next_sym.number - end.map do |next_sym, new_items| - Shift.new(next_sym, new_items.flatten) end - self.shifts = shifts.freeze + + self._transitions = transitions.freeze self.reduces = reduces.freeze end + # @rbs (Grammar::Symbol next_sym, State next_state) -> void + def set_lane_items(next_sym, next_state) + @lane_items[next_state] = @_lane_items[next_sym] + end + + # @rbs (Array[Item] items, State next_state) -> void def set_items_to_state(items, next_state) @items_to_state[items] = next_state end + # @rbs (Grammar::Rule rule, Array[Grammar::Symbol] look_ahead) -> void def set_look_ahead(rule, look_ahead) reduce = reduces.find do |r| r.rule == rule @@ -78,50 +147,78 @@ def set_look_ahead(rule, look_ahead) reduce.look_ahead = look_ahead end - def nterm_transitions - @nterm_transitions ||= transitions.select {|shift, _| shift.next_sym.nterm? } + # @rbs (Grammar::Rule rule, Hash[Grammar::Symbol, Array[Action::Goto]] sources) -> void + def set_look_ahead_sources(rule, sources) + reduce = reduces.find do |r| + r.rule == rule + end + + reduce.look_ahead_sources = sources + end + + # @rbs () -> Array[Action::Goto] + def nterm_transitions # steep:ignore + @nterm_transitions ||= transitions.select {|transition| transition.is_a?(Action::Goto) } end - def term_transitions - @term_transitions ||= transitions.select {|shift, _| shift.next_sym.term? } + # @rbs () -> Array[Action::Shift] + def term_transitions # steep:ignore + @term_transitions ||= transitions.select {|transition| transition.is_a?(Action::Shift) } end + # @rbs () -> Array[transition] def transitions - @transitions ||= shifts.map {|shift| [shift, @items_to_state[shift.next_items]] } + @transitions ||= _transitions.map do |next_sym, to_items| + if next_sym.term? + Action::Shift.new(self, next_sym, to_items.flatten, @items_to_state[to_items]) + else + Action::Goto.new(self, next_sym, to_items.flatten, @items_to_state[to_items]) + end + end end - def update_transition(shift, next_state) - set_items_to_state(shift.next_items, next_state) + # @rbs (transition transition, State next_state) -> void + def update_transition(transition, next_state) + set_items_to_state(transition.to_items, next_state) next_state.append_predecessor(self) - clear_transitions_cache + update_transitions_caches(transition) end - def clear_transitions_cache + # @rbs () -> void + def update_transitions_caches(transition) + new_transition = + if transition.next_sym.term? + Action::Shift.new(self, transition.next_sym, transition.to_items, @items_to_state[transition.to_items]) + else + Action::Goto.new(self, transition.next_sym, transition.to_items, @items_to_state[transition.to_items]) + end + + @transitions.delete(transition) + @transitions << new_transition @nterm_transitions = nil @term_transitions = nil - @transitions = nil + + @follow_kernel_items[new_transition] = @follow_kernel_items.delete(transition) + @always_follows[new_transition] = @always_follows.delete(transition) end + # @rbs () -> Array[Action::Shift] def selected_term_transitions - term_transitions.reject do |shift, next_state| + term_transitions.reject do |shift| shift.not_selected end end # Move to next state by sym + # + # @rbs (Grammar::Symbol sym) -> State def transition(sym) result = nil if sym.term? - term_transitions.each do |shift, next_state| - term = shift.next_sym - result = next_state if term == sym - end + result = term_transitions.find {|shift| shift.next_sym == sym }.to_state else - nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym - result = next_state if nterm == sym - end + result = nterm_transitions.find {|goto| goto.next_sym == sym }.to_state end raise "Can not transit by #{sym} #{self}" if result.nil? @@ -129,12 +226,14 @@ def transition(sym) result end + # @rbs (Item item) -> Action::Reduce def find_reduce_by_item!(item) reduces.find do |r| r.item == item end || (raise "reduce is not found. #{item}") end + # @rbs (Grammar::Rule default_reduction_rule) -> void def default_reduction_rule=(default_reduction_rule) @default_reduction_rule = default_reduction_rule @@ -145,200 +244,219 @@ def default_reduction_rule=(default_reduction_rule) end end + # @rbs () -> bool def has_conflicts? !@conflicts.empty? end + # @rbs () -> Array[conflict] def sr_conflicts @conflicts.select do |conflict| conflict.type == :shift_reduce end end + # @rbs () -> Array[conflict] def rr_conflicts @conflicts.select do |conflict| conflict.type == :reduce_reduce end end + # Clear information related to conflicts. + # IELR computation re-calculates conflicts and default reduction of states + # after LALR computation. + # Call this method before IELR computation to avoid duplicated conflicts information + # is stored. + # + # @rbs () -> void + def clear_conflicts + @conflicts = [] + @resolved_conflicts = [] + @default_reduction_rule = nil + + term_transitions.each(&:clear_conflicts) + reduces.each(&:clear_conflicts) + end + + # @rbs () -> bool + def split_state? + @lalr_isocore != self + end + + # Definition 3.40 (propagate_lookaheads) + # + # @rbs (State next_state) -> lookahead_set def propagate_lookaheads(next_state) - next_state.kernels.map {|item| + next_state.kernels.map {|next_kernel| lookahead_sets = - if item.position == 1 - goto_follow_set(item.lhs) - else - kernel = kernels.find {|k| k.predecessor_item_of?(item) } + if next_kernel.position > 1 + kernel = kernels.find {|k| k.predecessor_item_of?(next_kernel) } item_lookahead_set[kernel] + else + goto_follow_set(next_kernel.lhs) end - [item, lookahead_sets & next_state.lookahead_set_filters[item]] + [next_kernel, lookahead_sets & next_state.lookahead_set_filters[next_kernel]] }.to_h end - def lookaheads_recomputed - !@item_lookahead_set.nil? - end - - def compatible_lookahead?(filtered_lookahead) + # Definition 3.43 (is_compatible) + # + # @rbs (lookahead_set filtered_lookahead) -> bool + def is_compatible?(filtered_lookahead) !lookaheads_recomputed || - @lalr_isocore.annotation_list.all? {|token, actions| - a = dominant_contribution(token, actions, item_lookahead_set) - b = dominant_contribution(token, actions, filtered_lookahead) + @lalr_isocore.annotation_list.all? {|annotation| + a = annotation.dominant_contribution(item_lookahead_set) + b = annotation.dominant_contribution(filtered_lookahead) a.nil? || b.nil? || a == b } end + # Definition 3.38 (lookahead_set_filters) + # + # @rbs () -> lookahead_set def lookahead_set_filters - kernels.map {|kernel| - [kernel, - @lalr_isocore.annotation_list.select {|token, actions| - token.term? && actions.any? {|action, contributions| - !contributions.nil? && contributions.key?(kernel) && contributions[kernel] - } - }.map {|token, _| token } - ] + @lookahead_set_filters ||= kernels.map {|kernel| + [kernel, @lalr_isocore.annotation_list.select {|annotation| annotation.contributed?(kernel) }.map(&:token)] }.to_h end - def dominant_contribution(token, actions, lookaheads) - a = actions.select {|action, contributions| - contributions.nil? || contributions.any? {|item, contributed| contributed && lookaheads[item].include?(token) } - }.map {|action, _| action } - return nil if a.empty? - a.reject {|action| - if action.is_a?(State::Shift) - action.not_selected - elsif action.is_a?(State::Reduce) - action.not_selected_symbols.include?(token) - end - } - end - + # Definition 3.27 (inadequacy_lists) + # + # @rbs () -> Hash[Grammar::Symbol, Array[Action::Shift | Action::Reduce]] def inadequacy_list return @inadequacy_list if @inadequacy_list - shift_contributions = shifts.map {|shift| - [shift.next_sym, [shift]] - }.to_h - reduce_contributions = reduces.map {|reduce| - (reduce.look_ahead || []).map {|sym| - [sym, [reduce]] - }.to_h - }.reduce(Hash.new([])) {|hash, cont| - hash.merge(cont) {|_, a, b| a | b } - } + inadequacy_list = {} - list = shift_contributions.merge(reduce_contributions) {|_, a, b| a | b } - @inadequacy_list = list.select {|token, actions| token.term? && actions.size > 1 } - end - - def annotation_list - return @annotation_list if @annotation_list - - @annotation_list = annotate_manifestation - @annotation_list = @items_to_state.values.map {|next_state| next_state.annotate_predecessor(self) } - .reduce(@annotation_list) {|result, annotations| - result.merge(annotations) {|_, actions_a, actions_b| - if actions_a.nil? || actions_b.nil? - actions_a || actions_b - else - actions_a.merge(actions_b) {|_, contributions_a, contributions_b| - if contributions_a.nil? || contributions_b.nil? - next contributions_a || contributions_b - end - - contributions_a.merge(contributions_b) {|_, contributed_a, contributed_b| - contributed_a || contributed_b - } - } - end - } - } + term_transitions.each do |shift| + inadequacy_list[shift.next_sym] ||= [] + inadequacy_list[shift.next_sym] << shift.dup + end + reduces.each do |reduce| + next if reduce.look_ahead.nil? + + reduce.look_ahead.each do |token| + inadequacy_list[token] ||= [] + inadequacy_list[token] << reduce.dup + end + end + + @inadequacy_list = inadequacy_list.select {|token, actions| actions.size > 1 } end + # Definition 3.30 (annotate_manifestation) + # + # @rbs () -> void def annotate_manifestation - inadequacy_list.transform_values {|actions| - actions.map {|action| - if action.is_a?(Shift) + inadequacy_list.each {|token, actions| + contribution_matrix = actions.map {|action| + if action.is_a?(Action::Shift) [action, nil] - elsif action.is_a?(Reduce) - if action.rule.empty_rule? - [action, lhs_contributions(action.rule.lhs, inadequacy_list.key(actions))] - else - contributions = kernels.map {|kernel| [kernel, kernel.rule == action.rule && kernel.end_of_rule?] }.to_h - [action, contributions] - end + else + [action, action.rule.empty_rule? ? lhs_contributions(action.rule.lhs, token) : kernels.map {|k| [k, k.rule == action.item.rule && k.end_of_rule?] }.to_h] end }.to_h + @annotation_list << InadequacyAnnotation.new(self, token, actions, contribution_matrix) } end + # Definition 3.32 (annotate_predecessor) + # + # @rbs (State predecessor) -> void def annotate_predecessor(predecessor) - annotation_list.transform_values {|actions| - token = annotation_list.key(actions) - actions.transform_values {|inadequacy| - next nil if inadequacy.nil? - lhs_adequacy = kernels.any? {|kernel| - inadequacy[kernel] && kernel.position == 1 && predecessor.lhs_contributions(kernel.lhs, token).nil? - } - if lhs_adequacy - next nil + propagating_list = annotation_list.map {|annotation| + contribution_matrix = annotation.contribution_matrix.map {|action, contributions| + if contributions.nil? + [action, nil] + elsif first_kernels.any? {|kernel| contributions[kernel] && predecessor.lhs_contributions(kernel.lhs, annotation.token).empty? } + [action, nil] else - predecessor.kernels.map {|pred_k| - [pred_k, kernels.any? {|k| - inadequacy[k] && ( - pred_k.predecessor_item_of?(k) && predecessor.item_lookahead_set[pred_k].include?(token) || - k.position == 1 && predecessor.lhs_contributions(k.lhs, token)[pred_k] - ) - }] + cs = predecessor.lane_items[self].map {|pred_kernel, kernel| + c = contributions[kernel] && ( + (kernel.position > 1 && predecessor.item_lookahead_set[pred_kernel].include?(annotation.token)) || + (kernel.position == 1 && predecessor.lhs_contributions(kernel.lhs, annotation.token)[pred_kernel]) + ) + [pred_kernel, c] }.to_h + [action, cs] end - } - } + }.to_h + + # Observation 3.33 (Simple Split-Stable Dominance) + # + # If all of contributions in the contribution_matrix are + # always contribution or never contribution, we can stop annotate propagations + # to the predecessor state. + next nil if contribution_matrix.all? {|_, contributions| contributions.nil? || contributions.all? {|_, contributed| !contributed } } + + InadequacyAnnotation.new(annotation.state, annotation.token, annotation.actions, contribution_matrix) + }.compact + predecessor.append_annotation_list(propagating_list) end - def lhs_contributions(sym, token) - shift, next_state = nterm_transitions.find {|sh, _| sh.next_sym == sym } - if always_follows(shift, next_state).include?(token) - nil - else - kernels.map {|kernel| [kernel, follow_kernel_items(shift, next_state, kernel) && item_lookahead_set[kernel].include?(token)] }.to_h - end + # @rbs () -> Array[Item] + def first_kernels + @first_kernels ||= kernels.select {|kernel| kernel.position == 1 } end - def follow_kernel_items(shift, next_state, kernel) - queue = [[self, shift, next_state]] - until queue.empty? - st, sh, next_st = queue.pop - return true if kernel.next_sym == sh.next_sym && kernel.symbols_after_transition.all?(&:nullable) - st.internal_dependencies(sh, next_st).each {|v| queue << v } + # @rbs (Array[InadequacyAnnotation] propagating_list) -> void + def append_annotation_list(propagating_list) + annotation_list.each do |annotation| + merging_list = propagating_list.select {|a| a.state == annotation.state && a.token == annotation.token && a.actions == annotation.actions } + annotation.merge_matrix(merging_list.map(&:contribution_matrix)) + propagating_list -= merging_list end - false + + @annotation_list += propagating_list end + # Definition 3.31 (compute_lhs_contributions) + # + # @rbs (Grammar::Symbol sym, Grammar::Symbol token) -> (nil | Hash[Item, bool]) + def lhs_contributions(sym, token) + return @lhs_contributions[sym][token] unless @lhs_contributions.dig(sym, token).nil? + + transition = nterm_transitions.find {|goto| goto.next_sym == sym } + @lhs_contributions[sym] ||= {} + @lhs_contributions[sym][token] = + if always_follows[transition].include?(token) + {} + else + kernels.map {|kernel| [kernel, follow_kernel_items[transition][kernel] && item_lookahead_set[kernel].include?(token)] }.to_h + end + end + + # Definition 3.26 (item_lookahead_sets) + # + # @rbs () -> lookahead_set def item_lookahead_set return @item_lookahead_set if @item_lookahead_set - kernels.map {|item| + @item_lookahead_set = kernels.map {|k| [k, []] }.to_h + @item_lookahead_set = kernels.map {|kernel| value = - if item.lhs.accept_symbol? + if kernel.lhs.accept_symbol? [] - elsif item.position > 1 - prev_items = predecessors_with_item(item) + elsif kernel.position > 1 + prev_items = predecessors_with_item(kernel) prev_items.map {|st, i| st.item_lookahead_set[i] }.reduce([]) {|acc, syms| acc |= syms } - elsif item.position == 1 - prev_state = @predecessors.find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } } - shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs } - prev_state.goto_follows(shift, next_state) + elsif kernel.position == 1 + prev_state = @predecessors.find {|p| p.transitions.any? {|transition| transition.next_sym == kernel.lhs } } + goto = prev_state.nterm_transitions.find {|goto| goto.next_sym == kernel.lhs } + prev_state.goto_follows[goto] end - [item, value] + [kernel, value] }.to_h end + # @rbs (lookahead_set k) -> void def item_lookahead_set=(k) @item_lookahead_set = k end + # @rbs (Item item) -> Array[[State, Item]] def predecessors_with_item(item) result = [] @predecessors.each do |pre| @@ -349,69 +467,53 @@ def predecessors_with_item(item) result end + # @rbs (State prev_state) -> void def append_predecessor(prev_state) @predecessors << prev_state @predecessors.uniq! end + # Definition 3.39 (compute_goto_follow_set) + # + # @rbs (Grammar::Symbol nterm_token) -> Array[Grammar::Symbol] def goto_follow_set(nterm_token) return [] if nterm_token.accept_symbol? - shift, next_state = @lalr_isocore.nterm_transitions.find {|sh, _| sh.next_sym == nterm_token } + goto = @lalr_isocore.nterm_transitions.find {|g| g.next_sym == nterm_token } @kernels - .select {|kernel| follow_kernel_items(shift, next_state, kernel) } + .select {|kernel| @lalr_isocore.follow_kernel_items[goto][kernel] } .map {|kernel| item_lookahead_set[kernel] } - .reduce(always_follows(shift, next_state)) {|result, terms| result |= terms } - end - - def goto_follows(shift, next_state) - queue = internal_dependencies(shift, next_state) + predecessor_dependencies(shift, next_state) - terms = always_follows(shift, next_state) - until queue.empty? - st, sh, next_st = queue.pop - terms |= st.always_follows(sh, next_st) - st.internal_dependencies(sh, next_st).each {|v| queue << v } - st.predecessor_dependencies(sh, next_st).each {|v| queue << v } - end - terms - end - - def always_follows(shift, next_state) - return @always_follows[[shift, next_state]] if @always_follows[[shift, next_state]] - - queue = internal_dependencies(shift, next_state) + successor_dependencies(shift, next_state) - terms = [] - until queue.empty? - st, sh, next_st = queue.pop - terms |= next_st.term_transitions.map {|sh, _| sh.next_sym } - st.internal_dependencies(sh, next_st).each {|v| queue << v } - st.successor_dependencies(sh, next_st).each {|v| queue << v } - end - @always_follows[[shift, next_state]] = terms + .reduce(@lalr_isocore.always_follows[goto]) {|result, terms| result |= terms } end - def internal_dependencies(shift, next_state) - return @internal_dependencies[[shift, next_state]] if @internal_dependencies[[shift, next_state]] + # Definition 3.8 (Goto Follows Internal Relation) + # + # @rbs (Action::Goto goto) -> Array[Action::Goto] + def internal_dependencies(goto) + return @internal_dependencies[goto] if @internal_dependencies[goto] syms = @items.select {|i| - i.next_sym == shift.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0 + i.next_sym == goto.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0 }.map(&:lhs).uniq - @internal_dependencies[[shift, next_state]] = nterm_transitions.select {|sh, _| syms.include?(sh.next_sym) }.map {|goto| [self, *goto] } + @internal_dependencies[goto] = nterm_transitions.select {|goto2| syms.include?(goto2.next_sym) } end - def successor_dependencies(shift, next_state) - return @successor_dependencies[[shift, next_state]] if @successor_dependencies[[shift, next_state]] + # Definition 3.5 (Goto Follows Successor Relation) + # + # @rbs (Action::Goto goto) -> Array[Action::Goto] + def successor_dependencies(goto) + return @successor_dependencies[goto] if @successor_dependencies[goto] - @successor_dependencies[[shift, next_state]] = - next_state.nterm_transitions - .select {|next_shift, _| next_shift.next_sym.nullable } - .map {|transition| [next_state, *transition] } + @successor_dependencies[goto] = goto.to_state.nterm_transitions.select {|next_goto| next_goto.next_sym.nullable } end - def predecessor_dependencies(shift, next_state) + # Definition 3.9 (Goto Follows Predecessor Relation) + # + # @rbs (Action::Goto goto) -> Array[Action::Goto] + def predecessor_dependencies(goto) state_items = [] @kernels.select {|kernel| - kernel.next_sym == shift.next_sym && kernel.symbols_after_transition.all?(&:nullable) + kernel.next_sym == goto.next_sym && kernel.symbols_after_transition.all?(&:nullable) }.each do |item| queue = predecessors_with_item(item) until queue.empty? @@ -425,8 +527,7 @@ def predecessor_dependencies(shift, next_state) end state_items.map {|state, item| - sh, next_st = state.nterm_transitions.find {|shi, _| shi.next_sym == item.lhs } - [state, sh, next_st] + state.nterm_transitions.find {|goto2| goto2.next_sym == item.lhs } } end end diff --git a/tool/lrama/lib/lrama/state/action.rb b/tool/lrama/lib/lrama/state/action.rb new file mode 100644 index 00000000000000..791685fc23681a --- /dev/null +++ b/tool/lrama/lib/lrama/state/action.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "action/goto" +require_relative "action/reduce" +require_relative "action/shift" diff --git a/tool/lrama/lib/lrama/state/action/goto.rb b/tool/lrama/lib/lrama/state/action/goto.rb new file mode 100644 index 00000000000000..4c2c82afdc9dcc --- /dev/null +++ b/tool/lrama/lib/lrama/state/action/goto.rb @@ -0,0 +1,33 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class State + class Action + class Goto + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @from_state: State + # @next_sym: Grammar::Symbol + # @to_items: Array[Item] + # @to_state: State + + attr_reader :from_state #: State + attr_reader :next_sym #: Grammar::Symbol + attr_reader :to_items #: Array[Item] + attr_reader :to_state #: State + + # @rbs (State from_state, Grammar::Symbol next_sym, Array[Item] to_items, State to_state) -> void + def initialize(from_state, next_sym, to_items, to_state) + @from_state = from_state + @next_sym = next_sym + @to_items = to_items + @to_state = to_state + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/state/action/reduce.rb b/tool/lrama/lib/lrama/state/action/reduce.rb new file mode 100644 index 00000000000000..9678ab0a98cfad --- /dev/null +++ b/tool/lrama/lib/lrama/state/action/reduce.rb @@ -0,0 +1,71 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class State + class Action + class Reduce + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @item: Item + # @look_ahead: Array[Grammar::Symbol]? + # @look_ahead_sources: Hash[Grammar::Symbol, Array[Action::Goto]]? + # @not_selected_symbols: Array[Grammar::Symbol] + + attr_reader :item #: Item + attr_reader :look_ahead #: Array[Grammar::Symbol]? + attr_reader :look_ahead_sources #: Hash[Grammar::Symbol, Array[Action::Goto]]? + attr_reader :not_selected_symbols #: Array[Grammar::Symbol] + + # https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html + attr_accessor :default_reduction #: bool + + # @rbs (Item item) -> void + def initialize(item) + @item = item + @look_ahead = nil + @look_ahead_sources = nil + @not_selected_symbols = [] + end + + # @rbs () -> Grammar::Rule + def rule + @item.rule + end + + # @rbs (Array[Grammar::Symbol] look_ahead) -> Array[Grammar::Symbol] + def look_ahead=(look_ahead) + @look_ahead = look_ahead.freeze + end + + # @rbs (Hash[Grammar::Symbol, Array[Action::Goto]] sources) -> Hash[Grammar::Symbol, Array[Action::Goto]] + def look_ahead_sources=(sources) + @look_ahead_sources = sources.freeze + end + + # @rbs (Grammar::Symbol sym) -> Array[Grammar::Symbol] + def add_not_selected_symbol(sym) + @not_selected_symbols << sym + end + + # @rbs () -> (::Array[Grammar::Symbol?]) + def selected_look_ahead + if look_ahead + look_ahead - @not_selected_symbols + else + [] + end + end + + # @rbs () -> void + def clear_conflicts + @not_selected_symbols = [] + @default_reduction = nil + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/state/action/shift.rb b/tool/lrama/lib/lrama/state/action/shift.rb new file mode 100644 index 00000000000000..52d9f8c4f09dc6 --- /dev/null +++ b/tool/lrama/lib/lrama/state/action/shift.rb @@ -0,0 +1,39 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class State + class Action + class Shift + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @from_state: State + # @next_sym: Grammar::Symbol + # @to_items: Array[Item] + # @to_state: State + + attr_reader :from_state #: State + attr_reader :next_sym #: Grammar::Symbol + attr_reader :to_items #: Array[Item] + attr_reader :to_state #: State + attr_accessor :not_selected #: bool + + # @rbs (State from_state, Grammar::Symbol next_sym, Array[Item] to_items, State to_state) -> void + def initialize(from_state, next_sym, to_items, to_state) + @from_state = from_state + @next_sym = next_sym + @to_items = to_items + @to_state = to_state + end + + # @rbs () -> void + def clear_conflicts + @not_selected = nil + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/state/inadequacy_annotation.rb b/tool/lrama/lib/lrama/state/inadequacy_annotation.rb new file mode 100644 index 00000000000000..3654fa460727d7 --- /dev/null +++ b/tool/lrama/lib/lrama/state/inadequacy_annotation.rb @@ -0,0 +1,140 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class State + class InadequacyAnnotation + # @rbs! + # type action = Action::Shift | Action::Reduce + + attr_accessor :state #: State + attr_accessor :token #: Grammar::Symbol + attr_accessor :actions #: Array[action] + attr_accessor :contribution_matrix #: Hash[action, Hash[Item, bool]] + + # @rbs (State state, Grammar::Symbol token, Array[action] actions, Hash[action, Hash[Item, bool]] contribution_matrix) -> void + def initialize(state, token, actions, contribution_matrix) + @state = state + @token = token + @actions = actions + @contribution_matrix = contribution_matrix + end + + # @rbs (Item item) -> bool + def contributed?(item) + @contribution_matrix.any? {|action, contributions| !contributions.nil? && contributions[item] } + end + + # @rbs (Array[Hash[action, Hash[Item, bool]]] another_matrixes) -> void + def merge_matrix(another_matrixes) + another_matrixes.each do |another_matrix| + @contribution_matrix.merge!(another_matrix) {|action, contributions, another_contributions| + next contributions if another_contributions.nil? + next another_contributions if contributions.nil? + + contributions.merge!(another_contributions) {|_, contributed, another_contributed| contributed || another_contributed } + } + end + end + + # Definition 3.42 (dominant_contribution) + # + # @rbs (State::lookahead_set lookaheads) -> Array[action]? + def dominant_contribution(lookaheads) + actions = @actions.select {|action| + contribution_matrix[action].nil? || contribution_matrix[action].any? {|item, contributed| contributed && lookaheads[item].include?(@token) } + } + return nil if actions.empty? + + resolve_conflict(actions) + end + + # @rbs (Array[action] actions) -> Array[action] + def resolve_conflict(actions) + # @type var shifts: Array[Action::Shift] + # @type var reduces: Array[Action::Reduce] + shifts = actions.select {|action| action.is_a?(Action::Shift)} + reduces = actions.select {|action| action.is_a?(Action::Reduce) } + + shifts.each do |shift| + reduces.each do |reduce| + sym = shift.next_sym + + shift_prec = sym.precedence + reduce_prec = reduce.item.rule.precedence + + # Can resolve only when both have prec + unless shift_prec && reduce_prec + next + end + + case + when shift_prec < reduce_prec + # Reduce is selected + actions.delete(shift) + next + when shift_prec > reduce_prec + # Shift is selected + actions.delete(reduce) + next + end + + # shift_prec == reduce_prec, then check associativity + case sym.precedence&.type + when :precedence + # %precedence only specifies precedence and not specify associativity + # then a conflict is unresolved if precedence is same. + next + when :right + # Shift is selected + actions.delete(reduce) + next + when :left + # Reduce is selected + actions.delete(shift) + next + when :nonassoc + # Can not resolve + # + # nonassoc creates "run-time" error, precedence creates "compile-time" error. + # Then omit both the shift and reduce. + # + # https://www.gnu.org/software/bison/manual/html_node/Using-Precedence.html + actions.delete(shift) + actions.delete(reduce) + else + raise "Unknown precedence type. #{sym}" + end + end + end + + actions + end + + # @rbs () -> String + def to_s + "State: #{@state.id}, Token: #{@token.id.s_value}, Actions: #{actions_to_s}, Contributions: #{contribution_matrix_to_s}" + end + + private + + # @rbs () -> String + def actions_to_s + '[' + @actions.map {|action| + if action.is_a?(Action::Shift) || action.is_a?(Action::Goto) + action.class.name + elsif action.is_a?(Action::Reduce) + "#{action.class.name}: (#{action.item})" + end + }.join(', ') + ']' + end + + # @rbs () -> String + def contribution_matrix_to_s + '[' + @contribution_matrix.map {|action, contributions| + "#{(action.is_a?(Action::Shift) || action.is_a?(Action::Goto)) ? action.class.name : "#{action.class.name}: (#{action.item})"}: " + contributions&.transform_keys(&:to_s).to_s + }.join(', ') + ']' + end + end + end +end diff --git a/tool/lrama/lib/lrama/states/item.rb b/tool/lrama/lib/lrama/state/item.rb similarity index 61% rename from tool/lrama/lib/lrama/states/item.rb rename to tool/lrama/lib/lrama/state/item.rb index e89cb9695b6c7c..3ecdd70b76262a 100644 --- a/tool/lrama/lib/lrama/states/item.rb +++ b/tool/lrama/lib/lrama/state/item.rb @@ -1,3 +1,4 @@ +# rbs_inline: enabled # frozen_string_literal: true # TODO: Validate position is not over rule rhs @@ -5,84 +6,112 @@ require "forwardable" module Lrama - class States + class State class Item < Struct.new(:rule, :position, keyword_init: true) + # @rbs! + # include Grammar::Rule::_DelegatedMethods + # + # attr_accessor rule: Grammar::Rule + # attr_accessor position: Integer + # + # def initialize: (?rule: Grammar::Rule, ?position: Integer) -> void + extend Forwardable def_delegators "rule", :lhs, :rhs # Optimization for States#setup_state + # + # @rbs () -> Integer def hash [rule_id, position].hash end + # @rbs () -> Integer def rule_id rule.id end + # @rbs () -> bool def empty_rule? rule.empty_rule? end + # @rbs () -> Integer def number_of_rest_symbols - rhs.count - position + @number_of_rest_symbols ||= rhs.count - position end + # @rbs () -> Grammar::Symbol def next_sym rhs[position] end + # @rbs () -> Grammar::Symbol def next_next_sym - rhs[position + 1] + @next_next_sym ||= rhs[position + 1] end + # @rbs () -> Grammar::Symbol def previous_sym rhs[position - 1] end + # @rbs () -> bool def end_of_rule? rhs.count == position end + # @rbs () -> bool def beginning_of_rule? position == 0 end + # @rbs () -> bool def start_item? rule.initial_rule? && beginning_of_rule? end + # @rbs () -> State::Item def new_by_next_position Item.new(rule: rule, position: position + 1) end + # @rbs () -> Array[Grammar::Symbol] def symbols_before_dot # steep:ignore rhs[0...position] end + # @rbs () -> Array[Grammar::Symbol] def symbols_after_dot # steep:ignore rhs[position..-1] end - def symbols_after_transition + # @rbs () -> Array[Grammar::Symbol] + def symbols_after_transition # steep:ignore rhs[position+1..-1] end + # @rbs () -> ::String def to_s "#{lhs.id.s_value}: #{display_name}" end + # @rbs () -> ::String def display_name r = rhs.map(&:display_name).insert(position, "•").join(" ") "#{r} (rule #{rule_id})" end # Right after position + # + # @rbs () -> ::String def display_rest r = symbols_after_dot.map(&:display_name).join(" ") ". #{r} (rule #{rule_id})" end + # @rbs (State::Item other_item) -> bool def predecessor_item_of?(other_item) rule == other_item.rule && position == other_item.position - 1 end diff --git a/tool/lrama/lib/lrama/state/reduce.rb b/tool/lrama/lib/lrama/state/reduce.rb deleted file mode 100644 index 54ab87b468c48d..00000000000000 --- a/tool/lrama/lib/lrama/state/reduce.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class State - class Reduce - # https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html - attr_reader :item, :look_ahead, :not_selected_symbols - attr_accessor :default_reduction - - def initialize(item) - @item = item - @look_ahead = nil - @not_selected_symbols = [] - end - - def rule - @item.rule - end - - def look_ahead=(look_ahead) - @look_ahead = look_ahead.freeze - end - - def add_not_selected_symbol(sym) - @not_selected_symbols << sym - end - - def selected_look_ahead - if look_ahead - look_ahead - @not_selected_symbols - else - [] - end - end - end - end -end diff --git a/tool/lrama/lib/lrama/state/reduce_reduce_conflict.rb b/tool/lrama/lib/lrama/state/reduce_reduce_conflict.rb index 736d08376a73c7..55ecad40bdd2c3 100644 --- a/tool/lrama/lib/lrama/state/reduce_reduce_conflict.rb +++ b/tool/lrama/lib/lrama/state/reduce_reduce_conflict.rb @@ -1,8 +1,21 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class State - class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true) + class ReduceReduceConflict + attr_reader :symbols #: Array[Grammar::Symbol] + attr_reader :reduce1 #: State::Action::Reduce + attr_reader :reduce2 #: State::Action::Reduce + + # @rbs (symbols: Array[Grammar::Symbol], reduce1: State::Action::Reduce, reduce2: State::Action::Reduce) -> void + def initialize(symbols:, reduce1:, reduce2:) + @symbols = symbols + @reduce1 = reduce1 + @reduce2 = reduce2 + end + + # @rbs () -> :reduce_reduce def type :reduce_reduce end diff --git a/tool/lrama/lib/lrama/state/resolved_conflict.rb b/tool/lrama/lib/lrama/state/resolved_conflict.rb index 3bb3d1446e7214..014533c23315bb 100644 --- a/tool/lrama/lib/lrama/state/resolved_conflict.rb +++ b/tool/lrama/lib/lrama/state/resolved_conflict.rb @@ -1,20 +1,54 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class State + # * state: A state on which the conflct is resolved # * symbol: A symbol under discussion # * reduce: A reduce under discussion # * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative) - class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true) + # * resolved_by_precedence: If the conflict is resolved by precedence definition or not + class ResolvedConflict + # @rbs! + # type which_enum = :reduce | :shift | :error + + attr_reader :state #: State + attr_reader :symbol #: Grammar::Symbol + attr_reader :reduce #: State::Action::Reduce + attr_reader :which #: which_enum + attr_reader :resolved_by_precedence #: bool + + # @rbs (state: State, symbol: Grammar::Symbol, reduce: State::Action::Reduce, which: which_enum, resolved_by_precedence: bool) -> void + def initialize(state:, symbol:, reduce:, which:, resolved_by_precedence:) + @state = state + @symbol = symbol + @reduce = reduce + @which = which + @resolved_by_precedence = resolved_by_precedence + end + + # @rbs () -> (::String | bot) def report_message + "Conflict between rule #{reduce.rule.id} and token #{symbol.display_name} #{how_resolved}." + end + + # @rbs () -> (::String | bot) + def report_precedences_message + "Conflict between reduce by \"#{reduce.rule.display_name}\" and shift #{symbol.display_name} #{how_resolved}." + end + + private + + # @rbs () -> (::String | bot) + def how_resolved s = symbol.display_name r = reduce.rule.precedence_sym&.display_name case - when which == :shift && same_prec + when which == :shift && resolved_by_precedence msg = "resolved as #{which} (%right #{s})" when which == :shift msg = "resolved as #{which} (#{r} < #{s})" - when which == :reduce && same_prec + when which == :reduce && resolved_by_precedence msg = "resolved as #{which} (%left #{s})" when which == :reduce msg = "resolved as #{which} (#{s} < #{r})" @@ -24,7 +58,7 @@ def report_message raise "Unknown direction. #{self}" end - "Conflict between rule #{reduce.rule.id} and token #{s} #{msg}." + msg end end end diff --git a/tool/lrama/lib/lrama/state/shift.rb b/tool/lrama/lib/lrama/state/shift.rb deleted file mode 100644 index 81ef013a17c009..00000000000000 --- a/tool/lrama/lib/lrama/state/shift.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class State - class Shift - attr_reader :next_sym, :next_items - attr_accessor :not_selected - - def initialize(next_sym, next_items) - @next_sym = next_sym - @next_items = next_items - end - end - end -end diff --git a/tool/lrama/lib/lrama/state/shift_reduce_conflict.rb b/tool/lrama/lib/lrama/state/shift_reduce_conflict.rb index fd66834539e924..548f2de614a8b1 100644 --- a/tool/lrama/lib/lrama/state/shift_reduce_conflict.rb +++ b/tool/lrama/lib/lrama/state/shift_reduce_conflict.rb @@ -1,8 +1,21 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama class State - class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true) + class ShiftReduceConflict + attr_reader :symbols #: Array[Grammar::Symbol] + attr_reader :shift #: State::Action::Shift + attr_reader :reduce #: State::Action::Reduce + + # @rbs (symbols: Array[Grammar::Symbol], shift: State::Action::Shift, reduce: State::Action::Reduce) -> void + def initialize(symbols:, shift:, reduce:) + @symbols = symbols + @shift = shift + @reduce = reduce + end + + # @rbs () -> :shift_reduce def type :shift_reduce end diff --git a/tool/lrama/lib/lrama/states.rb b/tool/lrama/lib/lrama/states.rb index fd8ded905f0699..ddce627df400a5 100644 --- a/tool/lrama/lib/lrama/states.rb +++ b/tool/lrama/lib/lrama/states.rb @@ -1,8 +1,9 @@ +# rbs_inline: enabled # frozen_string_literal: true require "forwardable" -require_relative "report/duration" -require_relative "states/item" +require_relative "tracer/duration" +require_relative "state/item" module Lrama # States is passed to a template file @@ -10,17 +11,42 @@ module Lrama # "Efficient Computation of LALR(1) Look-Ahead Sets" # https://dl.acm.org/doi/pdf/10.1145/69622.357187 class States + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # type state_id = Integer + # type rule_id = Integer + # + # include Grammar::_DelegatedMethods + # + # @grammar: Grammar + # @tracer: Tracer + # @states: Array[State] + # @direct_read_sets: Hash[State::Action::Goto, Bitmap::bitmap] + # @reads_relation: Hash[State::Action::Goto, Array[State::Action::Goto]] + # @read_sets: Hash[State::Action::Goto, Bitmap::bitmap] + # @includes_relation: Hash[State::Action::Goto, Array[State::Action::Goto]] + # @lookback_relation: Hash[state_id, Hash[rule_id, Array[State::Action::Goto]]] + # @follow_sets: Hash[State::Action::Goto, Bitmap::bitmap] + # @la: Hash[state_id, Hash[rule_id, Bitmap::bitmap]] + extend Forwardable - include Lrama::Report::Duration + include Lrama::Tracer::Duration - def_delegators "@grammar", :symbols, :terms, :nterms, :rules, - :accept_symbol, :eof_symbol, :undef_symbol, :find_symbol_by_s_value! + def_delegators "@grammar", :symbols, :terms, :nterms, :rules, :precedences, + :accept_symbol, :eof_symbol, :undef_symbol, :find_symbol_by_s_value!, :ielr_defined? - attr_reader :states, :reads_relation, :includes_relation, :lookback_relation + attr_reader :states #: Array[State] + attr_reader :reads_relation #: Hash[State::Action::Goto, Array[State::Action::Goto]] + attr_reader :includes_relation #: Hash[State::Action::Goto, Array[State::Action::Goto]] + attr_reader :lookback_relation #: Hash[state_id, Hash[rule_id, Array[State::Action::Goto]]] - def initialize(grammar, trace_state: false) + # @rbs (Grammar grammar, Tracer tracer) -> void + def initialize(grammar, tracer) @grammar = grammar - @trace_state = trace_state + @tracer = tracer @states = [] @@ -28,7 +54,7 @@ def initialize(grammar, trace_state: false) # where p is state, A is nterm, t is term. # # `@direct_read_sets` is a hash whose - # key is [state.id, nterm.token_id], + # key is goto, # value is bitmap of term. @direct_read_sets = {} @@ -37,14 +63,14 @@ def initialize(grammar, trace_state: false) # where p, r are state, A, C are nterm. # # `@reads_relation` is a hash whose - # key is [state.id, nterm.token_id], - # value is array of [state.id, nterm.token_id]. + # key is goto, + # value is array of goto. @reads_relation = {} # `Read(p, A) =s DR(p, A) ∪ ∪{Read(r, C) | (p, A) reads (r, C)}` # # `@read_sets` is a hash whose - # key is [state.id, nterm.token_id], + # key is goto, # value is bitmap of term. @read_sets = {} @@ -52,112 +78,163 @@ def initialize(grammar, trace_state: false) # where p, p' are state, A, B are nterm, β, γ is sequence of symbol. # # `@includes_relation` is a hash whose - # key is [state.id, nterm.token_id], - # value is array of [state.id, nterm.token_id]. + # key is goto, + # value is array of goto. @includes_relation = {} # `(q, A -> ω) lookback (p, A) iff p -(ω)-> q` # where p, q are state, A -> ω is rule, A is nterm, ω is sequence of symbol. # - # `@lookback_relation` is a hash whose - # key is [state.id, rule.id], - # value is array of [state.id, nterm.token_id]. + # `@lookback_relation` is a two-stage hash whose + # first key is state_id, + # second key is rule_id, + # value is array of goto. @lookback_relation = {} # `Follow(p, A) =s Read(p, A) ∪ ∪{Follow(p', B) | (p, A) includes (p', B)}` # # `@follow_sets` is a hash whose - # key is [state.id, rule.id], + # key is goto, # value is bitmap of term. @follow_sets = {} # `LA(q, A -> ω) = ∪{Follow(p, A) | (q, A -> ω) lookback (p, A)` # - # `@la` is a hash whose - # key is [state.id, rule.id], + # `@la` is a two-stage hash whose + # first key is state_id, + # second key is rule_id, # value is bitmap of term. @la = {} end + # @rbs () -> void def compute - # Look Ahead Sets report_duration(:compute_lr0_states) { compute_lr0_states } - report_duration(:compute_direct_read_sets) { compute_direct_read_sets } - report_duration(:compute_reads_relation) { compute_reads_relation } - report_duration(:compute_read_sets) { compute_read_sets } - report_duration(:compute_includes_relation) { compute_includes_relation } - report_duration(:compute_lookback_relation) { compute_lookback_relation } - report_duration(:compute_follow_sets) { compute_follow_sets } + + # Look Ahead Sets report_duration(:compute_look_ahead_sets) { compute_look_ahead_sets } # Conflicts - report_duration(:compute_conflicts) { compute_conflicts } + report_duration(:compute_conflicts) { compute_conflicts(:lalr) } report_duration(:compute_default_reduction) { compute_default_reduction } end + # @rbs () -> void def compute_ielr + # Preparation + report_duration(:clear_conflicts) { clear_conflicts } + # Phase 1 + report_duration(:compute_predecessors) { compute_predecessors } + report_duration(:compute_follow_kernel_items) { compute_follow_kernel_items } + report_duration(:compute_always_follows) { compute_always_follows } + report_duration(:compute_goto_follows) { compute_goto_follows } + # Phase 2 + report_duration(:compute_inadequacy_annotations) { compute_inadequacy_annotations } + # Phase 3 report_duration(:split_states) { split_states } - report_duration(:compute_direct_read_sets) { compute_direct_read_sets } - report_duration(:compute_reads_relation) { compute_reads_relation } - report_duration(:compute_read_sets) { compute_read_sets } - report_duration(:compute_includes_relation) { compute_includes_relation } - report_duration(:compute_lookback_relation) { compute_lookback_relation } - report_duration(:compute_follow_sets) { compute_follow_sets } + # Phase 4 + report_duration(:clear_look_ahead_sets) { clear_look_ahead_sets } report_duration(:compute_look_ahead_sets) { compute_look_ahead_sets } - report_duration(:compute_conflicts) { compute_conflicts } - + # Phase 5 + report_duration(:compute_conflicts) { compute_conflicts(:ielr) } report_duration(:compute_default_reduction) { compute_default_reduction } end - def reporter - StatesReporter.new(self) - end - + # @rbs () -> Integer def states_count @states.count end + # @rbs () -> Hash[State::Action::Goto, Array[Grammar::Symbol]] def direct_read_sets - @direct_read_sets.transform_values do |v| + @_direct_read_sets ||= @direct_read_sets.transform_values do |v| bitmap_to_terms(v) end end + # @rbs () -> Hash[State::Action::Goto, Array[Grammar::Symbol]] def read_sets - @read_sets.transform_values do |v| + @_read_sets ||= @read_sets.transform_values do |v| bitmap_to_terms(v) end end + # @rbs () -> Hash[State::Action::Goto, Array[Grammar::Symbol]] def follow_sets - @follow_sets.transform_values do |v| + @_follow_sets ||= @follow_sets.transform_values do |v| bitmap_to_terms(v) end end + # @rbs () -> Hash[state_id, Hash[rule_id, Array[Grammar::Symbol]]] def la - @la.transform_values do |v| - bitmap_to_terms(v) + @_la ||= @la.transform_values do |second_hash| + second_hash.transform_values do |v| + bitmap_to_terms(v) + end end end + # @rbs () -> Integer def sr_conflicts_count @sr_conflicts_count ||= @states.flat_map(&:sr_conflicts).count end + # @rbs () -> Integer def rr_conflicts_count @rr_conflicts_count ||= @states.flat_map(&:rr_conflicts).count end - private + # @rbs (Logger logger) -> void + def validate!(logger) + validate_conflicts_within_threshold!(logger) + end - def trace_state - if @trace_state - yield STDERR + def compute_la_sources_for_conflicted_states + reflexive = {} + @states.each do |state| + state.nterm_transitions.each do |goto| + reflexive[goto] = [goto] + end + end + + # compute_read_sets + read_sets = Digraph.new(nterm_transitions, @reads_relation, reflexive).compute + # compute_follow_sets + follow_sets = Digraph.new(nterm_transitions, @includes_relation, read_sets).compute + + @states.select(&:has_conflicts?).each do |state| + lookback_relation_on_state = @lookback_relation[state.id] + next unless lookback_relation_on_state + rules.each do |rule| + ary = lookback_relation_on_state[rule.id] + next unless ary + + sources = {} + + ary.each do |goto| + source = follow_sets[goto] + + next unless source + + source.each do |goto2| + tokens = direct_read_sets[goto2] + tokens.each do |token| + sources[token] ||= [] + sources[token] |= [goto2] + end + end + end + + state.set_look_ahead_sources(rule, sources) + end end end + private + + # @rbs (Grammar::Symbol accessing_symbol, Array[State::Item] kernels, Hash[Array[State::Item], State] states_created) -> [State, bool] def create_state(accessing_symbol, kernels, states_created) # A item can appear in some states, # so need to use `kernels` (not `kernels.first`) as a key. @@ -204,27 +281,25 @@ def create_state(accessing_symbol, kernels, states_created) return [state, true] end + # @rbs (State state) -> void def setup_state(state) # closure closure = [] - visited = {} queued = {} items = state.kernels.dup items.each do |item| - queued[item] = true + queued[item.rule_id] = true if item.position == 0 end while (item = items.shift) do - visited[item] = true - if (sym = item.next_sym) && sym.nterm? @grammar.find_rules_by_symbol!(sym).each do |rule| - i = Item.new(rule: rule, position: 0) - next if queued[i] + next if queued[rule.id] + i = State::Item.new(rule: rule, position: 0) closure << i items << i - queued[i] = true + queued[i.rule_id] = true end end end @@ -232,119 +307,107 @@ def setup_state(state) state.closure = closure.sort_by {|i| i.rule.id } # Trace - trace_state do |out| - out << "Closure: input\n" - state.kernels.each do |item| - out << " #{item.display_rest}\n" - end - out << "\n\n" - out << "Closure: output\n" - state.items.each do |item| - out << " #{item.display_rest}\n" - end - out << "\n\n" - end + @tracer.trace_closure(state) # shift & reduce - state.compute_shifts_reduces + state.compute_transitions_and_reduces end + # @rbs (Array[State] states, State state) -> void def enqueue_state(states, state) # Trace - previous = state.kernels.first.previous_sym - trace_state do |out| - out << sprintf("state_list_append (state = %d, symbol = %d (%s))\n", - @states.count, previous.number, previous.display_name) - end + @tracer.trace_state_list_append(@states.count, state) states << state end + # @rbs () -> void def compute_lr0_states # State queue states = [] states_created = {} - state, _ = create_state(symbols.first, [Item.new(rule: @grammar.rules.first, position: 0)], states_created) + state, _ = create_state(symbols.first, [State::Item.new(rule: @grammar.rules.first, position: 0)], states_created) enqueue_state(states, state) while (state = states.shift) do # Trace - # - # Bison 3.8.2 renders "(reached by "end-of-input")" for State 0 but - # I think it is not correct... - previous = state.kernels.first.previous_sym - trace_state do |out| - out << "Processing state #{state.id} (reached by #{previous.display_name})\n" - end + @tracer.trace_state(state) setup_state(state) - state.shifts.each do |shift| - new_state, created = create_state(shift.next_sym, shift.next_items, states_created) - state.set_items_to_state(shift.next_items, new_state) - if created - enqueue_state(states, new_state) - new_state.append_predecessor(state) - end + # `State#transitions` can not be used here + # because `items_to_state` of the `state` is not set yet. + state._transitions.each do |next_sym, to_items| + new_state, created = create_state(next_sym, to_items, states_created) + state.set_items_to_state(to_items, new_state) + state.set_lane_items(next_sym, new_state) + enqueue_state(states, new_state) if created end end end + # @rbs () -> Array[State::Action::Goto] def nterm_transitions a = [] @states.each do |state| - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym - a << [state, nterm, next_state] + state.nterm_transitions.each do |goto| + a << goto end end a end + # @rbs () -> void + def compute_look_ahead_sets + report_duration(:compute_direct_read_sets) { compute_direct_read_sets } + report_duration(:compute_reads_relation) { compute_reads_relation } + report_duration(:compute_read_sets) { compute_read_sets } + report_duration(:compute_includes_relation) { compute_includes_relation } + report_duration(:compute_lookback_relation) { compute_lookback_relation } + report_duration(:compute_follow_sets) { compute_follow_sets } + report_duration(:compute_la) { compute_la } + end + + # @rbs () -> void def compute_direct_read_sets @states.each do |state| - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym - - ary = next_state.term_transitions.map do |shift, _| + state.nterm_transitions.each do |goto| + ary = goto.to_state.term_transitions.map do |shift| shift.next_sym.number end - key = [state.id, nterm.token_id] - @direct_read_sets[key] = Bitmap.from_array(ary) + @direct_read_sets[goto] = Bitmap.from_array(ary) end end end + # @rbs () -> void def compute_reads_relation @states.each do |state| - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym - next_state.nterm_transitions.each do |shift2, _next_state2| - nterm2 = shift2.next_sym + state.nterm_transitions.each do |goto| + goto.to_state.nterm_transitions.each do |goto2| + nterm2 = goto2.next_sym if nterm2.nullable - key = [state.id, nterm.token_id] - @reads_relation[key] ||= [] - @reads_relation[key] << [next_state.id, nterm2.token_id] + @reads_relation[goto] ||= [] + @reads_relation[goto] << goto2 end end end end end + # @rbs () -> void def compute_read_sets - sets = nterm_transitions.map do |state, nterm, next_state| - [state.id, nterm.token_id] - end - - @read_sets = Digraph.new(sets, @reads_relation, @direct_read_sets).compute + @read_sets = Digraph.new(nterm_transitions, @reads_relation, @direct_read_sets).compute end # Execute transition of state by symbols # then return final state. + # + # @rbs (State state, Array[Grammar::Symbol] symbols) -> State def transition(state, symbols) symbols.each do |sym| state = state.transition(sym) @@ -353,10 +416,11 @@ def transition(state, symbols) state end + # @rbs () -> void def compute_includes_relation @states.each do |state| - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym + state.nterm_transitions.each do |goto| + nterm = goto.next_sym @grammar.find_rules_by_symbol!(nterm).each do |rule| i = rule.rhs.count - 1 @@ -366,10 +430,12 @@ def compute_includes_relation break if sym.term? state2 = transition(state, rule.rhs[0...i]) # p' = state, B = nterm, p = state2, A = sym - key = [state2.id, sym.token_id] + key = state2.nterm_transitions.find do |goto2| + goto2.next_sym.token_id == sym.token_id + end || (raise "Goto by #{sym.name} on state #{state2.id} is not found") # TODO: need to omit if state == state2 ? @includes_relation[key] ||= [] - @includes_relation[key] << [state.id, nterm.token_id] + @includes_relation[key] << goto break unless sym.nullable i -= 1 end @@ -378,45 +444,46 @@ def compute_includes_relation end end + # @rbs () -> void def compute_lookback_relation @states.each do |state| - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym + state.nterm_transitions.each do |goto| + nterm = goto.next_sym @grammar.find_rules_by_symbol!(nterm).each do |rule| state2 = transition(state, rule.rhs) # p = state, A = nterm, q = state2, A -> ω = rule - key = [state2.id, rule.id] - @lookback_relation[key] ||= [] - @lookback_relation[key] << [state.id, nterm.token_id] + @lookback_relation[state2.id] ||= {} + @lookback_relation[state2.id][rule.id] ||= [] + @lookback_relation[state2.id][rule.id] << goto end end end end + # @rbs () -> void def compute_follow_sets - sets = nterm_transitions.map do |state, nterm, next_state| - [state.id, nterm.token_id] - end - - @follow_sets = Digraph.new(sets, @includes_relation, @read_sets).compute + @follow_sets = Digraph.new(nterm_transitions, @includes_relation, @read_sets).compute end - def compute_look_ahead_sets + # @rbs () -> void + def compute_la @states.each do |state| + lookback_relation_on_state = @lookback_relation[state.id] + next unless lookback_relation_on_state rules.each do |rule| - ary = @lookback_relation[[state.id, rule.id]] + ary = lookback_relation_on_state[rule.id] next unless ary - ary.each do |state2_id, nterm_token_id| + ary.each do |goto| # q = state, A -> ω = rule, p = state2, A = nterm - follows = @follow_sets[[state2_id, nterm_token_id]] + follows = @follow_sets[goto] next if follows == 0 - key = [state.id, rule.id] - @la[key] ||= 0 - look_ahead = @la[key] | follows - @la[key] |= look_ahead + @la[state.id] ||= {} + @la[state.id][rule.id] ||= 0 + look_ahead = @la[state.id][rule.id] | follows + @la[state.id][rule.id] |= look_ahead # No risk of conflict when # * the state only has single reduce @@ -429,6 +496,7 @@ def compute_look_ahead_sets end end + # @rbs (Bitmap::bitmap bit) -> Array[Grammar::Symbol] def bitmap_to_terms(bit) ary = Bitmap.to_array(bit) ary.map do |i| @@ -436,14 +504,16 @@ def bitmap_to_terms(bit) end end - def compute_conflicts - compute_shift_reduce_conflicts + # @rbs () -> void + def compute_conflicts(lr_type) + compute_shift_reduce_conflicts(lr_type) compute_reduce_reduce_conflicts end - def compute_shift_reduce_conflicts + # @rbs () -> void + def compute_shift_reduce_conflicts(lr_type) states.each do |state| - state.shifts.each do |shift| + state.term_transitions.each do |shift| state.reduces.each do |reduce| sym = shift.next_sym @@ -463,43 +533,57 @@ def compute_shift_reduce_conflicts case when shift_prec < reduce_prec # Reduce is selected - state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :reduce) + resolved_conflict = State::ResolvedConflict.new(state: state, symbol: sym, reduce: reduce, which: :reduce, resolved_by_precedence: false) + state.resolved_conflicts << resolved_conflict shift.not_selected = true + mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) next when shift_prec > reduce_prec # Shift is selected - state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :shift) + resolved_conflict = State::ResolvedConflict.new(state: state, symbol: sym, reduce: reduce, which: :shift, resolved_by_precedence: false) + state.resolved_conflicts << resolved_conflict reduce.add_not_selected_symbol(sym) + mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) next end # shift_prec == reduce_prec, then check associativity case sym.precedence.type when :precedence + # Can not resolve the conflict + # # %precedence only specifies precedence and not specify associativity # then a conflict is unresolved if precedence is same. state.conflicts << State::ShiftReduceConflict.new(symbols: [sym], shift: shift, reduce: reduce) next when :right # Shift is selected - state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :shift, same_prec: true) + resolved_conflict = State::ResolvedConflict.new(state: state, symbol: sym, reduce: reduce, which: :shift, resolved_by_precedence: true) + state.resolved_conflicts << resolved_conflict reduce.add_not_selected_symbol(sym) + mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) next when :left # Reduce is selected - state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :reduce, same_prec: true) + resolved_conflict = State::ResolvedConflict.new(state: state, symbol: sym, reduce: reduce, which: :reduce, resolved_by_precedence: true) + state.resolved_conflicts << resolved_conflict shift.not_selected = true + mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) next when :nonassoc - # Can not resolve + # The conflict is resolved # - # nonassoc creates "run-time" error, precedence creates "compile-time" error. - # Then omit both the shift and reduce. + # %nonassoc creates "run-time" error by removing both shift and reduce from + # the state. This makes the state to get syntax error if the conflicted token appears. + # On the other hand, %precedence creates "compile-time" error by keeping both + # shift and reduce on the state. This makes the state to be conflicted on the token. # # https://www.gnu.org/software/bison/manual/html_node/Using-Precedence.html - state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :error) + resolved_conflict = State::ResolvedConflict.new(state: state, symbol: sym, reduce: reduce, which: :error, resolved_by_precedence: false) + state.resolved_conflicts << resolved_conflict shift.not_selected = true reduce.add_not_selected_symbol(sym) + mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) else raise "Unknown precedence type. #{sym}" end @@ -508,35 +592,41 @@ def compute_shift_reduce_conflicts end end + # @rbs (Grammar::Precedence shift_prec, Grammar::Precedence reduce_prec, State::ResolvedConflict resolved_conflict) -> void + def mark_precedences_used(lr_type, shift_prec, reduce_prec, resolved_conflict) + case lr_type + when :lalr + shift_prec.mark_used_by_lalr(resolved_conflict) + reduce_prec.mark_used_by_lalr(resolved_conflict) + when :ielr + shift_prec.mark_used_by_ielr(resolved_conflict) + reduce_prec.mark_used_by_ielr(resolved_conflict) + end + end + + # @rbs () -> void def compute_reduce_reduce_conflicts states.each do |state| - count = state.reduces.count - - (0...count).each do |i| - reduce1 = state.reduces[i] - next if reduce1.look_ahead.nil? + state.reduces.combination(2) do |reduce1, reduce2| + next if reduce1.look_ahead.nil? || reduce2.look_ahead.nil? - ((i+1)...count).each do |j| - reduce2 = state.reduces[j] - next if reduce2.look_ahead.nil? + intersection = reduce1.look_ahead & reduce2.look_ahead - intersection = reduce1.look_ahead & reduce2.look_ahead - - unless intersection.empty? - state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2) - end + unless intersection.empty? + state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2) end end end end + # @rbs () -> void def compute_default_reduction states.each do |state| next if state.reduces.empty? # Do not set, if conflict exist next unless state.conflicts.empty? # Do not set, if shift with `error` exists. - next if state.shifts.map(&:next_sym).include?(@grammar.error_symbol) + next if state.term_transitions.map {|shift| shift.next_sym }.include?(@grammar.error_symbol) state.default_reduction_rule = state.reduces.map do |r| [r.rule, r.rule.id, (r.look_ahead || []).count] @@ -546,35 +636,171 @@ def compute_default_reduction end end + # @rbs () -> void + def clear_conflicts + states.each(&:clear_conflicts) + end + + # Definition 3.15 (Predecessors) + # + # @rbs () -> void + def compute_predecessors + @states.each do |state| + state.transitions.each do |transition| + transition.to_state.append_predecessor(state) + end + end + end + + # Definition 3.16 (follow_kernel_items) + # + # @rbs () -> void + def compute_follow_kernel_items + set = nterm_transitions + relation = compute_goto_internal_relation + base_function = compute_goto_bitmaps + Digraph.new(set, relation, base_function).compute.each do |goto, follow_kernel_items| + state = goto.from_state + state.follow_kernel_items[goto] = state.kernels.map {|kernel| + [kernel, Bitmap.to_bool_array(follow_kernel_items, state.kernels.count)] + }.to_h + end + end + + # @rbs () -> Hash[State::Action::Goto, Array[State::Action::Goto]] + def compute_goto_internal_relation + relations = {} + + @states.each do |state| + state.nterm_transitions.each do |goto| + relations[goto] = state.internal_dependencies(goto) + end + end + + relations + end + + # @rbs () -> Hash[State::Action::Goto, Bitmap::bitmap] + def compute_goto_bitmaps + nterm_transitions.map {|goto| + bools = goto.from_state.kernels.map.with_index {|kernel, i| i if kernel.next_sym == goto.next_sym && kernel.symbols_after_transition.all?(&:nullable) }.compact + [goto, Bitmap.from_array(bools)] + }.to_h + end + + # Definition 3.20 (always_follows, one closure) + # + # @rbs () -> void + def compute_always_follows + set = nterm_transitions + relation = compute_goto_successor_or_internal_relation + base_function = compute_transition_bitmaps + Digraph.new(set, relation, base_function).compute.each do |goto, always_follows_bitmap| + goto.from_state.always_follows[goto] = bitmap_to_terms(always_follows_bitmap) + end + end + + # @rbs () -> Hash[State::Action::Goto, Array[State::Action::Goto]] + def compute_goto_successor_or_internal_relation + relations = {} + + @states.each do |state| + state.nterm_transitions.each do |goto| + relations[goto] = state.successor_dependencies(goto) + state.internal_dependencies(goto) + end + end + + relations + end + + # @rbs () -> Hash[State::Action::Goto, Bitmap::bitmap] + def compute_transition_bitmaps + nterm_transitions.map {|goto| + [goto, Bitmap.from_array(goto.to_state.term_transitions.map {|shift| shift.next_sym.number })] + }.to_h + end + + # Definition 3.24 (goto_follows, via always_follows) + # + # @rbs () -> void + def compute_goto_follows + set = nterm_transitions + relation = compute_goto_internal_or_predecessor_dependencies + base_function = compute_always_follows_bitmaps + Digraph.new(set, relation, base_function).compute.each do |goto, goto_follows_bitmap| + goto.from_state.goto_follows[goto] = bitmap_to_terms(goto_follows_bitmap) + end + end + + # @rbs () -> Hash[State::Action::Goto, Array[State::Action::Goto]] + def compute_goto_internal_or_predecessor_dependencies + relations = {} + + @states.each do |state| + state.nterm_transitions.each do |goto| + relations[goto] = state.internal_dependencies(goto) + state.predecessor_dependencies(goto) + end + end + + relations + end + + # @rbs () -> Hash[State::Action::Goto, Bitmap::bitmap] + def compute_always_follows_bitmaps + nterm_transitions.map {|goto| + [goto, Bitmap.from_array(goto.from_state.always_follows[goto].map(&:number))] + }.to_h + end + + # @rbs () -> void def split_states @states.each do |state| - state.transitions.each do |shift, next_state| - compute_state(state, shift, next_state) + state.transitions.each do |transition| + compute_state(state, transition, transition.to_state) end end end + # @rbs () -> void + def compute_inadequacy_annotations + @states.each do |state| + state.annotate_manifestation + end + + queue = @states.reject {|state| state.annotation_list.empty? } + + while (curr = queue.shift) do + curr.predecessors.each do |pred| + cache = pred.annotation_list.dup + curr.annotate_predecessor(pred) + queue << pred if cache != pred.annotation_list && !queue.include?(pred) + end + end + end + + # @rbs (State state, State::lookahead_set filtered_lookaheads) -> void def merge_lookaheads(state, filtered_lookaheads) return if state.kernels.all? {|item| (filtered_lookaheads[item] - state.item_lookahead_set[item]).empty? } state.item_lookahead_set = state.item_lookahead_set.merge {|_, v1, v2| v1 | v2 } - state.transitions.each do |shift, next_state| - next if next_state.lookaheads_recomputed - compute_state(state, shift, next_state) + state.transitions.each do |transition| + next if transition.to_state.lookaheads_recomputed + compute_state(state, transition, transition.to_state) end end - def compute_state(state, shift, next_state) - filtered_lookaheads = state.propagate_lookaheads(next_state) - s = next_state.ielr_isocores.find {|st| st.compatible_lookahead?(filtered_lookaheads) } + # @rbs (State state, State::Action::Shift | State::Action::Goto transition, State next_state) -> void + def compute_state(state, transition, next_state) + propagating_lookaheads = state.propagate_lookaheads(next_state) + s = next_state.ielr_isocores.find {|st| st.is_compatible?(propagating_lookaheads) } if s.nil? - s = next_state.ielr_isocores.last + s = next_state.lalr_isocore new_state = State.new(@states.count, s.accessing_symbol, s.kernels) new_state.closure = s.closure - new_state.compute_shifts_reduces - s.transitions.each do |sh, next_state| - new_state.set_items_to_state(sh.next_items, next_state) + new_state.compute_transitions_and_reduces + s.transitions.each do |transition| + new_state.set_items_to_state(transition.to_items, transition.to_state) end @states << new_state new_state.lalr_isocore = s @@ -582,14 +808,60 @@ def compute_state(state, shift, next_state) s.ielr_isocores.each do |st| st.ielr_isocores = s.ielr_isocores end - new_state.item_lookahead_set = filtered_lookaheads - state.update_transition(shift, new_state) + new_state.lookaheads_recomputed = true + new_state.item_lookahead_set = propagating_lookaheads + state.update_transition(transition, new_state) elsif(!s.lookaheads_recomputed) - s.item_lookahead_set = filtered_lookaheads + s.lookaheads_recomputed = true + s.item_lookahead_set = propagating_lookaheads else - state.update_transition(shift, s) - merge_lookaheads(s, filtered_lookaheads) + merge_lookaheads(s, propagating_lookaheads) + state.update_transition(transition, s) if state.items_to_state[transition.to_items].id != s.id end end + + # @rbs (Logger logger) -> void + def validate_conflicts_within_threshold!(logger) + exit false unless conflicts_within_threshold?(logger) + end + + # @rbs (Logger logger) -> bool + def conflicts_within_threshold?(logger) + return true unless @grammar.expect + + [sr_conflicts_within_threshold?(logger), rr_conflicts_within_threshold?(logger)].all? + end + + # @rbs (Logger logger) -> bool + def sr_conflicts_within_threshold?(logger) + return true if @grammar.expect == sr_conflicts_count + + logger.error("shift/reduce conflicts: #{sr_conflicts_count} found, #{@grammar.expect} expected") + false + end + + # @rbs (Logger logger) -> bool + def rr_conflicts_within_threshold?(logger, expected: 0) + return true if expected == rr_conflicts_count + + logger.error("reduce/reduce conflicts: #{rr_conflicts_count} found, #{expected} expected") + false + end + + # @rbs () -> void + def clear_look_ahead_sets + @direct_read_sets.clear + @reads_relation.clear + @read_sets.clear + @includes_relation.clear + @lookback_relation.clear + @follow_sets.clear + @la.clear + + @_direct_read_sets = nil + @_read_sets = nil + @_follow_sets = nil + @_la = nil + end end end diff --git a/tool/lrama/lib/lrama/states_reporter.rb b/tool/lrama/lib/lrama/states_reporter.rb deleted file mode 100644 index 64ff4de1006c58..00000000000000 --- a/tool/lrama/lib/lrama/states_reporter.rb +++ /dev/null @@ -1,362 +0,0 @@ -# frozen_string_literal: true - -module Lrama - class StatesReporter - include Lrama::Report::Duration - - def initialize(states) - @states = states - end - - def report(io, **options) - report_duration(:report) do - _report(io, **options) - end - end - - private - - def _report(io, grammar: false, rules: false, terms: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false) - report_unused_rules(io) if rules - report_unused_terms(io) if terms - report_conflicts(io) - report_grammar(io) if grammar - report_states(io, itemsets, lookaheads, solved, counterexamples, verbose) - end - - def report_unused_terms(io) - look_aheads = @states.states.each do |state| - state.reduces.flat_map do |reduce| - reduce.look_ahead unless reduce.look_ahead.nil? - end - end - - next_terms = @states.states.flat_map do |state| - state.shifts.map(&:next_sym).select(&:term?) - end - - unused_symbols = @states.terms.select do |term| - !(look_aheads + next_terms).include?(term) - end - - unless unused_symbols.empty? - io << "#{unused_symbols.count} Unused Terms\n\n" - unused_symbols.each_with_index do |term, index| - io << sprintf("%5d %s\n", index, term.id.s_value) - end - io << "\n\n" - end - end - - def report_unused_rules(io) - used_rules = @states.rules.flat_map(&:rhs) - - unused_rules = @states.rules.map(&:lhs).select do |rule| - !used_rules.include?(rule) && rule.token_id != 0 - end - - unless unused_rules.empty? - io << "#{unused_rules.count} Unused Rules\n\n" - unused_rules.each_with_index do |rule, index| - io << sprintf("%5d %s\n", index, rule.display_name) - end - io << "\n\n" - end - end - - def report_conflicts(io) - has_conflict = false - - @states.states.each do |state| - messages = [] - cs = state.conflicts.group_by(&:type) - if cs[:shift_reduce] - messages << "#{cs[:shift_reduce].count} shift/reduce" - end - - if cs[:reduce_reduce] - messages << "#{cs[:reduce_reduce].count} reduce/reduce" - end - - unless messages.empty? - has_conflict = true - io << "State #{state.id} conflicts: #{messages.join(', ')}\n" - end - end - - if has_conflict - io << "\n\n" - end - end - - def report_grammar(io) - io << "Grammar\n" - last_lhs = nil - - @states.rules.each do |rule| - if rule.empty_rule? - r = "ε" - else - r = rule.rhs.map(&:display_name).join(" ") - end - - if rule.lhs == last_lhs - io << sprintf("%5d %s| %s\n", rule.id, " " * rule.lhs.display_name.length, r) - else - io << "\n" - io << sprintf("%5d %s: %s\n", rule.id, rule.lhs.display_name, r) - end - - last_lhs = rule.lhs - end - io << "\n\n" - end - - def report_states(io, itemsets, lookaheads, solved, counterexamples, verbose) - if counterexamples - cex = Counterexamples.new(@states) - end - - @states.states.each do |state| - # Report State - io << "State #{state.id}\n\n" - - # Report item - last_lhs = nil - list = itemsets ? state.items : state.kernels - list.sort_by {|i| [i.rule_id, i.position] }.each do |item| - if item.empty_rule? - r = "ε •" - else - r = item.rhs.map(&:display_name).insert(item.position, "•").join(" ") - end - if item.lhs == last_lhs - l = " " * item.lhs.id.s_value.length + "|" - else - l = item.lhs.id.s_value + ":" - end - la = "" - if lookaheads && item.end_of_rule? - reduce = state.find_reduce_by_item!(item) - look_ahead = reduce.selected_look_ahead - unless look_ahead.empty? - la = " [#{look_ahead.map(&:display_name).join(", ")}]" - end - end - last_lhs = item.lhs - - io << sprintf("%5i %s %s%s\n", item.rule_id, l, r, la) - end - io << "\n" - - # Report shifts - tmp = state.term_transitions.reject do |shift, _| - shift.not_selected - end.map do |shift, next_state| - [shift.next_sym, next_state.id] - end - max_len = tmp.map(&:first).map(&:display_name).map(&:length).max - tmp.each do |term, state_id| - io << " #{term.display_name.ljust(max_len)} shift, and go to state #{state_id}\n" - end - io << "\n" unless tmp.empty? - - # Report error caused by %nonassoc - nl = false - tmp = state.resolved_conflicts.select do |resolved| - resolved.which == :error - end.map do |error| - error.symbol.display_name - end - max_len = tmp.map(&:length).max - tmp.each do |name| - nl = true - io << " #{name.ljust(max_len)} error (nonassociative)\n" - end - io << "\n" unless tmp.empty? - - # Report reduces - nl = false - max_len = state.non_default_reduces.flat_map(&:look_ahead).compact.map(&:display_name).map(&:length).max || 0 - max_len = [max_len, "$default".length].max if state.default_reduction_rule - ary = [] - - state.non_default_reduces.each do |reduce| - reduce.look_ahead.each do |term| - ary << [term, reduce] - end - end - - ary.sort_by do |term, reduce| - term.number - end.each do |term, reduce| - rule = reduce.item.rule - io << " #{term.display_name.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.display_name})\n" - nl = true - end - - if (r = state.default_reduction_rule) - nl = true - s = "$default".ljust(max_len) - - if r.initial_rule? - io << " #{s} accept\n" - else - io << " #{s} reduce using rule #{r.id} (#{r.lhs.display_name})\n" - end - end - io << "\n" if nl - - # Report nonterminal transitions - tmp = [] - max_len = 0 - state.nterm_transitions.each do |shift, next_state| - nterm = shift.next_sym - tmp << [nterm, next_state.id] - max_len = [max_len, nterm.id.s_value.length].max - end - tmp.uniq! - tmp.sort_by! do |nterm, state_id| - nterm.number - end - tmp.each do |nterm, state_id| - io << " #{nterm.id.s_value.ljust(max_len)} go to state #{state_id}\n" - end - io << "\n" unless tmp.empty? - - if solved - # Report conflict resolutions - state.resolved_conflicts.each do |resolved| - io << " #{resolved.report_message}\n" - end - io << "\n" unless state.resolved_conflicts.empty? - end - - if counterexamples && state.has_conflicts? - # Report counterexamples - examples = cex.compute(state) - examples.each do |example| - label0 = example.type == :shift_reduce ? "shift/reduce" : "reduce/reduce" - label1 = example.type == :shift_reduce ? "Shift derivation" : "First Reduce derivation" - label2 = example.type == :shift_reduce ? "Reduce derivation" : "Second Reduce derivation" - - io << " #{label0} conflict on token #{example.conflict_symbol.id.s_value}:\n" - io << " #{example.path1_item}\n" - io << " #{example.path2_item}\n" - io << " #{label1}\n" - example.derivations1.render_strings_for_report.each do |str| - io << " #{str}\n" - end - io << " #{label2}\n" - example.derivations2.render_strings_for_report.each do |str| - io << " #{str}\n" - end - end - end - - if verbose - # Report direct_read_sets - io << " [Direct Read sets]\n" - direct_read_sets = @states.direct_read_sets - @states.nterms.each do |nterm| - terms = direct_read_sets[[state.id, nterm.token_id]] - next unless terms - next if terms.empty? - - str = terms.map {|sym| sym.id.s_value }.join(", ") - io << " read #{nterm.id.s_value} shift #{str}\n" - end - io << "\n" - - # Report reads_relation - io << " [Reads Relation]\n" - @states.nterms.each do |nterm| - a = @states.reads_relation[[state.id, nterm.token_id]] - next unless a - - a.each do |state_id2, nterm_id2| - n = @states.nterms.find {|n| n.token_id == nterm_id2 } - io << " (State #{state_id2}, #{n.id.s_value})\n" - end - end - io << "\n" - - # Report read_sets - io << " [Read sets]\n" - read_sets = @states.read_sets - @states.nterms.each do |nterm| - terms = read_sets[[state.id, nterm.token_id]] - next unless terms - next if terms.empty? - - terms.each do |sym| - io << " #{sym.id.s_value}\n" - end - end - io << "\n" - - # Report includes_relation - io << " [Includes Relation]\n" - @states.nterms.each do |nterm| - a = @states.includes_relation[[state.id, nterm.token_id]] - next unless a - - a.each do |state_id2, nterm_id2| - n = @states.nterms.find {|n| n.token_id == nterm_id2 } - io << " (State #{state.id}, #{nterm.id.s_value}) -> (State #{state_id2}, #{n.id.s_value})\n" - end - end - io << "\n" - - # Report lookback_relation - io << " [Lookback Relation]\n" - @states.rules.each do |rule| - a = @states.lookback_relation[[state.id, rule.id]] - next unless a - - a.each do |state_id2, nterm_id2| - n = @states.nterms.find {|n| n.token_id == nterm_id2 } - io << " (Rule: #{rule.display_name}) -> (State #{state_id2}, #{n.id.s_value})\n" - end - end - io << "\n" - - # Report follow_sets - io << " [Follow sets]\n" - follow_sets = @states.follow_sets - @states.nterms.each do |nterm| - terms = follow_sets[[state.id, nterm.token_id]] - - next unless terms - - terms.each do |sym| - io << " #{nterm.id.s_value} -> #{sym.id.s_value}\n" - end - end - io << "\n" - - # Report LA - io << " [Look-Ahead Sets]\n" - tmp = [] - max_len = 0 - @states.rules.each do |rule| - syms = @states.la[[state.id, rule.id]] - next unless syms - - tmp << [rule, syms] - max_len = ([max_len] + syms.map {|s| s.id.s_value.length }).max - end - tmp.each do |rule, syms| - syms.each do |sym| - io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n" - end - end - io << "\n" unless tmp.empty? - end - - # End of Report State - io << "\n" - end - end - end -end diff --git a/tool/lrama/lib/lrama/trace_reporter.rb b/tool/lrama/lib/lrama/trace_reporter.rb deleted file mode 100644 index bcf1ef1e50327e..00000000000000 --- a/tool/lrama/lib/lrama/trace_reporter.rb +++ /dev/null @@ -1,45 +0,0 @@ -# rbs_inline: enabled -# frozen_string_literal: true - -module Lrama - class TraceReporter - # @rbs (Lrama::Grammar grammar) -> void - def initialize(grammar) - @grammar = grammar - end - - # @rbs (**Hash[Symbol, bool] options) -> void - def report(**options) - _report(**options) - end - - private - - # @rbs rules: (bool rules, bool actions, bool only_explicit_rules, **untyped _) -> void - def _report(rules: false, actions: false, only_explicit_rules: false, **_) - report_rules if rules && !only_explicit_rules - report_only_explicit_rules if only_explicit_rules - report_actions if actions - end - - # @rbs () -> void - def report_rules - puts "Grammar rules:" - @grammar.rules.each { |rule| puts rule.display_name } - end - - # @rbs () -> void - def report_only_explicit_rules - puts "Grammar rules:" - @grammar.rules.each do |rule| - puts rule.display_name_without_action if rule.lhs.first_set.any? - end - end - - # @rbs () -> void - def report_actions - puts "Grammar rules with actions:" - @grammar.rules.each { |rule| puts rule.with_actions } - end - end -end diff --git a/tool/lrama/lib/lrama/tracer.rb b/tool/lrama/lib/lrama/tracer.rb new file mode 100644 index 00000000000000..fda699a6654a12 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer.rb @@ -0,0 +1,51 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +require_relative "tracer/actions" +require_relative "tracer/closure" +require_relative "tracer/duration" +require_relative "tracer/only_explicit_rules" +require_relative "tracer/rules" +require_relative "tracer/state" + +module Lrama + class Tracer + # @rbs (IO io, **bool options) -> void + def initialize(io, **options) + @io = io + @options = options + @only_explicit_rules = OnlyExplicitRules.new(io, **options) + @rules = Rules.new(io, **options) + @actions = Actions.new(io, **options) + @closure = Closure.new(io, **options) + @state = State.new(io, **options) + end + + # @rbs (Lrama::Grammar grammar) -> void + def trace(grammar) + @only_explicit_rules.trace(grammar) + @rules.trace(grammar) + @actions.trace(grammar) + end + + # @rbs (Lrama::State state) -> void + def trace_closure(state) + @closure.trace(state) + end + + # @rbs (Lrama::State state) -> void + def trace_state(state) + @state.trace(state) + end + + # @rbs (Integer state_count, Lrama::State state) -> void + def trace_state_list_append(state_count, state) + @state.trace_list_append(state_count, state) + end + + # @rbs () -> void + def enable_duration + Duration.enable if @options[:time] + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/actions.rb b/tool/lrama/lib/lrama/tracer/actions.rb new file mode 100644 index 00000000000000..7b9c9b9f530103 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/actions.rb @@ -0,0 +1,22 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + class Actions + # @rbs (IO io, ?actions: bool, **bool options) -> void + def initialize(io, actions: false, **options) + @io = io + @actions = actions + end + + # @rbs (Lrama::Grammar grammar) -> void + def trace(grammar) + return unless @actions + + @io << "Grammar rules with actions:" << "\n" + grammar.rules.each { |rule| @io << rule.with_actions << "\n" } + end + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/closure.rb b/tool/lrama/lib/lrama/tracer/closure.rb new file mode 100644 index 00000000000000..5b2f0b27e65f9f --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/closure.rb @@ -0,0 +1,30 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + class Closure + # @rbs (IO io, ?automaton: bool, ?closure: bool, **bool) -> void + def initialize(io, automaton: false, closure: false, **_) + @io = io + @closure = automaton || closure + end + + # @rbs (Lrama::State state) -> void + def trace(state) + return unless @closure + + @io << "Closure: input" << "\n" + state.kernels.each do |item| + @io << " #{item.display_rest}" << "\n" + end + @io << "\n\n" + @io << "Closure: output" << "\n" + state.items.each do |item| + @io << " #{item.display_rest}" << "\n" + end + @io << "\n\n" + end + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/duration.rb b/tool/lrama/lib/lrama/tracer/duration.rb new file mode 100644 index 00000000000000..91c49625b2c087 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/duration.rb @@ -0,0 +1,38 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + module Duration + # TODO: rbs-inline 0.11.0 doesn't support instance variables. + # Move these type declarations above instance variable definitions, once it's supported. + # see: https://github.com/soutaro/rbs-inline/pull/149 + # + # @rbs! + # @_report_duration_enabled: bool + + # @rbs () -> void + def self.enable + @_report_duration_enabled = true + end + + # @rbs () -> bool + def self.enabled? + !!@_report_duration_enabled + end + + # @rbs [T] (_ToS message) { -> T } -> T + def report_duration(message) + time1 = Time.now.to_f + result = yield + time2 = Time.now.to_f + + if Duration.enabled? + STDERR.puts sprintf("%s %10.5f s", message, time2 - time1) + end + + return result + end + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/only_explicit_rules.rb b/tool/lrama/lib/lrama/tracer/only_explicit_rules.rb new file mode 100644 index 00000000000000..4f64e7d2f47a36 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/only_explicit_rules.rb @@ -0,0 +1,24 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + class OnlyExplicitRules + # @rbs (IO io, ?only_explicit: bool, **bool) -> void + def initialize(io, only_explicit: false, **_) + @io = io + @only_explicit = only_explicit + end + + # @rbs (Lrama::Grammar grammar) -> void + def trace(grammar) + return unless @only_explicit + + @io << "Grammar rules:" << "\n" + grammar.rules.each do |rule| + @io << rule.display_name_without_action << "\n" if rule.lhs.first_set.any? + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/rules.rb b/tool/lrama/lib/lrama/tracer/rules.rb new file mode 100644 index 00000000000000..d6e85b8432f300 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/rules.rb @@ -0,0 +1,23 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + class Rules + # @rbs (IO io, ?rules: bool, ?only_explicit: bool, **bool) -> void + def initialize(io, rules: false, only_explicit: false, **_) + @io = io + @rules = rules + @only_explicit = only_explicit + end + + # @rbs (Lrama::Grammar grammar) -> void + def trace(grammar) + return if !@rules || @only_explicit + + @io << "Grammar rules:" << "\n" + grammar.rules.each { |rule| @io << rule.display_name << "\n" } + end + end + end +end diff --git a/tool/lrama/lib/lrama/tracer/state.rb b/tool/lrama/lib/lrama/tracer/state.rb new file mode 100644 index 00000000000000..21c0047f8e8047 --- /dev/null +++ b/tool/lrama/lib/lrama/tracer/state.rb @@ -0,0 +1,33 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Tracer + class State + # @rbs (IO io, ?automaton: bool, ?closure: bool, **bool) -> void + def initialize(io, automaton: false, closure: false, **_) + @io = io + @state = automaton || closure + end + + # @rbs (Lrama::State state) -> void + def trace(state) + return unless @state + + # Bison 3.8.2 renders "(reached by "end-of-input")" for State 0 but + # I think it is not correct... + previous = state.kernels.first.previous_sym + @io << "Processing state #{state.id} (reached by #{previous.display_name})" << "\n" + end + + # @rbs (Integer state_count, Lrama::State state) -> void + def trace_list_append(state_count, state) + return unless @state + + previous = state.kernels.first.previous_sym + @io << sprintf("state_list_append (state = %d, symbol = %d (%s))", + state_count, previous.number, previous.display_name) << "\n" + end + end + end +end diff --git a/tool/lrama/lib/lrama/version.rb b/tool/lrama/lib/lrama/version.rb index 12ece5a8f2b153..d649b749391910 100644 --- a/tool/lrama/lib/lrama/version.rb +++ b/tool/lrama/lib/lrama/version.rb @@ -1,5 +1,6 @@ +# rbs_inline: enabled # frozen_string_literal: true module Lrama - VERSION = "0.7.0".freeze + VERSION = "0.7.1".freeze #: String end diff --git a/tool/lrama/lib/lrama/warnings.rb b/tool/lrama/lib/lrama/warnings.rb new file mode 100644 index 00000000000000..52f09144ef0313 --- /dev/null +++ b/tool/lrama/lib/lrama/warnings.rb @@ -0,0 +1,33 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +require_relative 'warnings/conflicts' +require_relative 'warnings/implicit_empty' +require_relative 'warnings/name_conflicts' +require_relative 'warnings/redefined_rules' +require_relative 'warnings/required' +require_relative 'warnings/useless_precedence' + +module Lrama + class Warnings + # @rbs (Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @conflicts = Conflicts.new(logger, warnings) + @implicit_empty = ImplicitEmpty.new(logger, warnings) + @name_conflicts = NameConflicts.new(logger, warnings) + @redefined_rules = RedefinedRules.new(logger, warnings) + @required = Required.new(logger, warnings) + @useless_precedence = UselessPrecedence.new(logger, warnings) + end + + # @rbs (Lrama::Grammar grammar, Lrama::States states) -> void + def warn(grammar, states) + @conflicts.warn(states) + @implicit_empty.warn(grammar) + @name_conflicts.warn(grammar) + @redefined_rules.warn(grammar) + @required.warn(grammar) + @useless_precedence.warn(grammar, states) + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/conflicts.rb b/tool/lrama/lib/lrama/warnings/conflicts.rb new file mode 100644 index 00000000000000..6ba0de6f9c5e64 --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/conflicts.rb @@ -0,0 +1,27 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + class Conflicts + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::States states) -> void + def warn(states) + return unless @warnings + + if states.sr_conflicts_count != 0 + @logger.warn("shift/reduce conflicts: #{states.sr_conflicts_count} found") + end + + if states.rr_conflicts_count != 0 + @logger.warn("reduce/reduce conflicts: #{states.rr_conflicts_count} found") + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/implicit_empty.rb b/tool/lrama/lib/lrama/warnings/implicit_empty.rb new file mode 100644 index 00000000000000..ba81adca01cbba --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/implicit_empty.rb @@ -0,0 +1,29 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + # Warning rationale: Empty rules are easily overlooked and ambiguous + # - Empty alternatives like `rule: | "token";` can be missed during code reading + # - Difficult to distinguish between intentional empty rules vs. omissions + # - Explicit marking with %empty directive comment improves clarity + class ImplicitEmpty + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::Grammar grammar) -> void + def warn(grammar) + return unless @warnings + + grammar.rule_builders.each do |builder| + if builder.rhs.empty? + @logger.warn("warning: empty rule without %empty") + end + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/name_conflicts.rb b/tool/lrama/lib/lrama/warnings/name_conflicts.rb new file mode 100644 index 00000000000000..c0754ab55125ba --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/name_conflicts.rb @@ -0,0 +1,63 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + # Warning rationale: Parameterized rule names conflicting with symbol names + # - When a %rule name is identical to a terminal or non-terminal symbol name, + # it reduces grammar readability and may cause unintended behavior + # - Detecting these conflicts helps improve grammar definition quality + class NameConflicts + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::Grammar grammar) -> void + def warn(grammar) + return unless @warnings + return if grammar.parameterized_rules.empty? + + symbol_names = collect_symbol_names(grammar) + check_conflicts(grammar.parameterized_rules, symbol_names) + end + + private + + # @rbs (Lrama::Grammar grammar) -> Set[String] + def collect_symbol_names(grammar) + symbol_names = Set.new + + collect_term_names(grammar.terms, symbol_names) + collect_nterm_names(grammar.nterms, symbol_names) + + symbol_names + end + + # @rbs (Array[untyped] terms, Set[String] symbol_names) -> void + def collect_term_names(terms, symbol_names) + terms.each do |term| + symbol_names.add(term.id.s_value) + symbol_names.add(term.alias_name) if term.alias_name + end + end + + # @rbs (Array[untyped] nterms, Set[String] symbol_names) -> void + def collect_nterm_names(nterms, symbol_names) + nterms.each do |nterm| + symbol_names.add(nterm.id.s_value) + end + end + + # @rbs (Array[untyped] parameterized_rules, Set[String] symbol_names) -> void + def check_conflicts(parameterized_rules, symbol_names) + parameterized_rules.each do |param_rule| + next unless symbol_names.include?(param_rule.name) + + @logger.warn("warning: parameterized rule name \"#{param_rule.name}\" conflicts with symbol name") + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/redefined_rules.rb b/tool/lrama/lib/lrama/warnings/redefined_rules.rb new file mode 100644 index 00000000000000..8ac2f1f1034dae --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/redefined_rules.rb @@ -0,0 +1,23 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + class RedefinedRules + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::Grammar grammar) -> void + def warn(grammar) + return unless @warnings + + grammar.parameterized_resolver.redefined_rules.each do |rule| + @logger.warn("parameterized rule redefined: #{rule}") + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/required.rb b/tool/lrama/lib/lrama/warnings/required.rb new file mode 100644 index 00000000000000..4ab1ed787ed9eb --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/required.rb @@ -0,0 +1,23 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + class Required + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings = false, **_) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::Grammar grammar) -> void + def warn(grammar) + return unless @warnings + + if grammar.required + @logger.warn("currently, %require is simply valid as a grammar but does nothing") + end + end + end + end +end diff --git a/tool/lrama/lib/lrama/warnings/useless_precedence.rb b/tool/lrama/lib/lrama/warnings/useless_precedence.rb new file mode 100644 index 00000000000000..2913d6d7e5f17b --- /dev/null +++ b/tool/lrama/lib/lrama/warnings/useless_precedence.rb @@ -0,0 +1,25 @@ +# rbs_inline: enabled +# frozen_string_literal: true + +module Lrama + class Warnings + class UselessPrecedence + # @rbs (Lrama::Logger logger, bool warnings) -> void + def initialize(logger, warnings) + @logger = logger + @warnings = warnings + end + + # @rbs (Lrama::Grammar grammar, Lrama::States states) -> void + def warn(grammar, states) + return unless @warnings + + grammar.precedences.each do |precedence| + unless precedence.used_by? + @logger.warn("Precedence #{precedence.s_value} (line: #{precedence.lineno}) is defined but not used in any rule.") + end + end + end + end + end +end diff --git a/tool/lrama/template/bison/_yacc.h b/tool/lrama/template/bison/_yacc.h index 34ed6d81f59d74..3e270c91710855 100644 --- a/tool/lrama/template/bison/_yacc.h +++ b/tool/lrama/template/bison/_yacc.h @@ -28,6 +28,7 @@ extern int yydebug; <%-# b4_declare_yylstype -%> <%-# b4_value_type_define -%> /* Value type. */ +<% if output.grammar.union %> #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED union YYSTYPE { @@ -40,6 +41,13 @@ typedef union YYSTYPE YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif +<% else %> +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef int YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif +<% end %> <%-# b4_location_type_define -%> /* Location type. */ diff --git a/tool/lrama/template/diagram/diagram.html b/tool/lrama/template/diagram/diagram.html new file mode 100644 index 00000000000000..3e87e6e5192700 --- /dev/null +++ b/tool/lrama/template/diagram/diagram.html @@ -0,0 +1,102 @@ + + + + Lrama syntax diagrams + + + + + + <%= output.diagrams %> + + + From 352de161ff9765bbb68a2ec015f2c02b5b0b534b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 24 Dec 2025 11:04:23 +0900 Subject: [PATCH 2144/2435] Remove an extra dot from `RUBY_API_VERSION_STR` --- version.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version.c b/version.c index c9a219fb1385c1..554783265201f0 100644 --- a/version.c +++ b/version.c @@ -51,8 +51,7 @@ #define RUBY_API_VERSION_STR \ STRINGIZE(RUBY_API_VERSION_MAJOR) "." \ - STRINGIZE(RUBY_API_VERSION_MINOR) "." \ - "" + STRINGIZE(RUBY_API_VERSION_MINOR) const int ruby_api_version[] = { RUBY_API_VERSION_MAJOR, RUBY_API_VERSION_MINOR, From f7b48456ebffa1dfe7fe201b27c9c34b5703a53e Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 23 Dec 2025 22:27:06 +0900 Subject: [PATCH 2145/2435] Box: show the fully qualified URL of the Ruby::Box doc --- box.c | 5 ++++- test/ruby/test_box.rb | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/box.c b/box.c index 616a8acf7d88b5..830172cdd52345 100644 --- a/box.c +++ b/box.c @@ -902,6 +902,8 @@ rb_box_eval(VALUE box_value, VALUE str) static int box_experimental_warned = 0; +RUBY_EXTERN const char ruby_api_version_name[]; + void rb_initialize_main_box(void) { @@ -914,7 +916,8 @@ rb_initialize_main_box(void) if (!box_experimental_warned) { rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, "Ruby::Box is experimental, and the behavior may change in the future!\n" - "See doc/language/box.md for known issues, etc."); + "See https://docs.ruby-lang.org/en/%s/Ruby/Box.html for known issues, etc.", + ruby_api_version_name); box_experimental_warned = 1; } diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index e584d233ca0145..f35d07c0863dc2 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -3,10 +3,10 @@ require 'test/unit' class TestBox < Test::Unit::TestCase - EXPERIMENTAL_WARNINGS = [ - "warning: Ruby::Box is experimental, and the behavior may change in the future!", - "See doc/language/box.md for known issues, etc." - ].join("\n") + EXPERIMENTAL_WARNING_LINE_PATTERNS = [ + /ruby(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/, + %r{See https://docs.ruby-lang.org/en/(master|\d\.\d)/Ruby/Box.html for known issues, etc.} + ] ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} def setup @@ -650,8 +650,9 @@ def test_prelude_gems_and_loaded_features end; # No additional warnings except for experimental warnings - assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS assert_equal 2, error.size + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0] + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1] assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' @@ -672,8 +673,9 @@ def test_prelude_gems_and_loaded_features_with_disable_gems puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join end; - assert_includes error.join("\n"), EXPERIMENTAL_WARNINGS assert_equal 2, error.size + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0] + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1] refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' From 88d6c5aaa82105d96c37847723bcf0151deb6497 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 23 Dec 2025 22:43:40 -0800 Subject: [PATCH 2146/2435] [DOC] Update ZJIT status in NEWS.md As for Ruby v4.0.0-preview3, ZJIT support is enabled by default on supported platforms. The previous phrasing is not relevant for most users. Replaced with brief instructions for enabling the JIT itself. --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2c5a758e6b06c3..d0a8887ed11874 100644 --- a/NEWS.md +++ b/NEWS.md @@ -510,7 +510,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * ZJIT * Introduce an [experimental method-based JIT compiler](https://docs.ruby-lang.org/en/master/jit/zjit_md.html). - To enable `--zjit` support, build Ruby with Rust 1.85.0 or later. + To enable ZJIT on supported platforms, supply the `--zjit` option or call `RubyVM::ZJIT.enable` at runtime. * As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT. We encourage experimentation with ZJIT, but advise against deploying it in production for now. * Our goal is to make ZJIT faster than YJIT and production-ready in Ruby 4.1. From 04a646220148cad882b0a92665e617c6aca94bc2 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 24 Dec 2025 06:55:30 +0000 Subject: [PATCH 2147/2435] Update bundled gems list as of 2025-12-24 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 495f7afc694e0d..c15ded2fcfc6a0 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.3 https://github.com/test-unit/test-unit +test-unit 3.7.5 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From 422ff2737050e4c2f8bf1b42f738f08d713df910 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 16:09:57 +0900 Subject: [PATCH 2148/2435] Added https://github.com/ruby/net-http/issues/205 to NEWS.md --- NEWS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/NEWS.md b/NEWS.md index d0a8887ed11874..646a774e4df950 100644 --- a/NEWS.md +++ b/NEWS.md @@ -440,6 +440,16 @@ Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for det install the `sorted_set` gem and `require 'sorted_set'` to use `SortedSet`. [[Feature #21287]] +* Net::HTTP + + * The default behavior of automatically setting the `Content-Type` header + to `application/x-www-form-urlencoded` for requests with a body + (e.g., `POST`, `PUT`) when the header was not explicitly set has been + removed. If your application relied on this automatic default, your + requests will now be sent without a Content-Type header, potentially + breaking compatibility with certain servers. + [[GH-net-http #205]] + ## C API updates * IO @@ -573,3 +583,4 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 [Bug #21789]: https://bugs.ruby-lang.org/issues/21789 +[GH-net-http #205]: https://github.com/ruby/net-http/issues/205 From 1c07158203f00d020841f2400002f6e9b62d1318 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 16:10:41 +0900 Subject: [PATCH 2149/2435] Update to test-unit 3.7.5 at NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 646a774e4df950..5d649f7ac3d284 100644 --- a/NEWS.md +++ b/NEWS.md @@ -342,7 +342,7 @@ The following bundled gems are updated. * minitest 6.0.0 * power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.3 +* test-unit 3.7.5 * rexml 3.4.4 * rss 0.3.2 * net-ftp 0.3.9 From ca67e72bcd580e9216e3d73e4a06a4bae6f0bd09 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 17:26:10 +0900 Subject: [PATCH 2150/2435] Added release histories of default/bundled gems from Ruby 3.4.8 --- NEWS.md | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 214 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5d649f7ac3d284..57f9e20a00a752 100644 --- a/NEWS.md +++ b/NEWS.md @@ -289,15 +289,24 @@ releases. The following bundled gems are promoted from default gems. * ostruct 0.6.3 + * 0.6.1 to [v0.6.2][ostruct-v0.6.2], [v0.6.3][ostruct-v0.6.3] * pstore 0.2.0 + * 0.1.4 to [v0.2.0][pstore-v0.2.0] * benchmark 0.5.0 + * 0.4.0 to [v0.4.1][benchmark-v0.4.1], [v0.5.0][benchmark-v0.5.0] * logger 1.7.0 + * 1.6.4 to [v1.6.5][logger-v1.6.5], [v1.6.6][logger-v1.6.6], [v1.7.0][logger-v1.7.0] * rdoc 7.0.2 + * 6.14.0 to [v6.14.1][rdoc-v6.14.1], [v6.14.2][rdoc-v6.14.2], [v6.15.0][rdoc-v6.15.0], [v6.15.1][rdoc-v6.15.1], [v6.16.0][rdoc-v6.16.0], [v6.16.1][rdoc-v6.16.1], [v6.17.0][rdoc-v6.17.0], [v7.0.0][rdoc-v7.0.0], [v7.0.1][rdoc-v7.0.1], [v7.0.2][rdoc-v7.0.2] * win32ole 1.9.2 + * 1.9.1 to [v1.9.2][win32ole-v1.9.2] * irb 1.16.0 + * 1.14.3 to [v1.15.0][irb-v1.15.0], [v1.15.1][irb-v1.15.1], [v1.15.2][irb-v1.15.2], [v1.15.3][irb-v1.15.3], [v1.16.0][irb-v1.16.0] * reline 0.6.3 + * 0.6.0 to [v0.6.1][reline-v0.6.1], [v0.6.2][reline-v0.6.2], [v0.6.3][reline-v0.6.3] * readline 0.0.4 * fiddle 1.1.8 + * 1.1.6 to [v1.1.7][fiddle-v1.1.7], [v1.1.8][fiddle-v1.1.8] The following default gem is added. @@ -308,57 +317,91 @@ The following default gems are updated. * RubyGems 4.0.3 * bundler 4.0.3 * date 3.5.1 + * 3.4.1 to [v3.5.0][date-v3.5.0], [v3.5.1][date-v3.5.1] * delegate 0.6.1 + * 0.4.0 to [v0.5.0][delegate-v0.5.0], [v0.6.0][delegate-v0.6.0], [v0.6.1][delegate-v0.6.1] * digest 3.2.1 + * 3.2.0 to [v3.2.1][digest-v3.2.1] * english 0.8.1 + * 0.8.0 to [v0.8.1][english-v0.8.1] * erb 6.0.1 -* error_highlight 0.7.1 -* etc 1.4.6 + * 4.0.4 to [v5.1.2][erb-v5.1.2], [v5.1.3][erb-v5.1.3], [v6.0.0][erb-v6.0.0], [v6.0.1][erb-v6.0.1] * fcntl 1.3.0 + * 1.2.0 to [v1.3.0][fcntl-v1.3.0] * fileutils 1.8.0 + * 1.7.3 to [v1.8.0][fileutils-v1.8.0] * forwardable 1.4.0 + * 1.3.3 to [v1.4.0][forwardable-v1.4.0] * io-console 0.8.2 -* io-nonblock 0.3.2 + * 0.8.1 to [v0.8.2][io-console-v0.8.2] * io-wait 0.4.0 -* ipaddr 1.2.8 + * 0.3.2 to [v0.3.3][io-wait-v0.3.3], [v0.3.5.test1][io-wait-v0.3.5.test1], [v0.3.5][io-wait-v0.3.5], [v0.3.6][io-wait-v0.3.6], [v0.4.0][io-wait-v0.4.0] * json 2.18.0 + * 2.9.1 to [v2.10.0][json-v2.10.0], [v2.10.1][json-v2.10.1], [v2.10.2][json-v2.10.2], [v2.11.0][json-v2.11.0], [v2.11.1][json-v2.11.1], [v2.11.2][json-v2.11.2], [v2.11.3][json-v2.11.3], [v2.12.0][json-v2.12.0], [v2.12.1][json-v2.12.1], [v2.12.2][json-v2.12.2], [v2.13.0][json-v2.13.0], [v2.13.1][json-v2.13.1], [v2.13.2][json-v2.13.2], [v2.14.0][json-v2.14.0], [v2.14.1][json-v2.14.1], [v2.15.0][json-v2.15.0], [v2.15.1][json-v2.15.1], [v2.15.2][json-v2.15.2], [v2.16.0][json-v2.16.0], [v2.17.0][json-v2.17.0], [v2.17.1][json-v2.17.1], [v2.18.0][json-v2.18.0] * net-http 0.9.1 + * 0.6.0 to [v0.7.0][net-http-v0.7.0], [v0.8.0][net-http-v0.8.0], [v0.9.0][net-http-v0.9.0], [v0.9.1][net-http-v0.9.1] * openssl 4.0.0 + * 3.3.1 to [v3.3.2][openssl-v3.3.2], [v4.0.0][openssl-v4.0.0] * optparse 0.8.1 + * 0.6.0 to [v0.7.0][optparse-v0.7.0], [v0.8.0][optparse-v0.8.0], [v0.8.1][optparse-v0.8.1] * pp 0.6.3 + * 0.6.2 to [v0.6.3][pp-v0.6.3] * prism 1.7.0 + * 1.5.2 to [v1.6.0][prism-v1.6.0], [v1.7.0][prism-v1.7.0] * psych 5.3.1 + * 5.2.2 to [v5.2.3][psych-v5.2.3], [v5.2.4][psych-v5.2.4], [v5.2.5][psych-v5.2.5], [v5.2.6][psych-v5.2.6], [v5.3.0][psych-v5.3.0], [v5.3.1][psych-v5.3.1] * resolv 0.7.0 + * 0.6.2 to [v0.6.3][resolv-v0.6.3], [v0.7.0][resolv-v0.7.0] * stringio 3.2.0 + * 3.1.2 to [v3.1.3][stringio-v3.1.3], [v3.1.4][stringio-v3.1.4], [v3.1.5][stringio-v3.1.5], [v3.1.6][stringio-v3.1.6], [v3.1.7][stringio-v3.1.7], [v3.1.8][stringio-v3.1.8], [v3.1.9][stringio-v3.1.9], [v3.2.0][stringio-v3.2.0] * strscan 3.1.6 + * 3.1.2 to [v3.1.3][strscan-v3.1.3], [v3.1.4][strscan-v3.1.4], [v3.1.5][strscan-v3.1.5], [v3.1.6][strscan-v3.1.6] * time 0.4.2 + * 0.4.1 to [v0.4.2][time-v0.4.2] * timeout 0.6.0 + * 0.4.3 to [v0.4.4][timeout-v0.4.4], [v0.5.0][timeout-v0.5.0], [v0.6.0][timeout-v0.6.0] * uri 1.1.1 + * 1.0.4 to [v1.1.0][uri-v1.1.0], [v1.1.1][uri-v1.1.1] * weakref 0.1.4 + * 0.1.3 to [v0.1.4][weakref-v0.1.4] * zlib 3.2.2 + * 3.2.1 to [v3.2.2][zlib-v3.2.2] The following bundled gems are updated. * minitest 6.0.0 * power_assert 3.0.1 + * 2.0.5 to [v3.0.0][power_assert-v3.0.0], [v3.0.1][power_assert-v3.0.1] * rake 13.3.1 + * 13.2.1 to [v13.3.0][rake-v13.3.0], [v13.3.1][rake-v13.3.1] * test-unit 3.7.5 -* rexml 3.4.4 + * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3], [3.7.4][test-unit-3.7.4], [3.7.5][test-unit-3.7.5] * rss 0.3.2 + * 0.3.1 to [0.3.2][rss-0.3.2] * net-ftp 0.3.9 + * 0.3.8 to [v0.3.9][net-ftp-v0.3.9] * net-imap 0.6.2 + * 0.5.8 to [v0.5.9][net-imap-v0.5.9], [v0.5.10][net-imap-v0.5.10], [v0.5.11][net-imap-v0.5.11], [v0.5.12][net-imap-v0.5.12], [v0.5.13][net-imap-v0.5.13], [v0.6.0][net-imap-v0.6.0], [v0.6.1][net-imap-v0.6.1], [v0.6.2][net-imap-v0.6.2] * net-smtp 0.5.1 + * 0.5.0 to [v0.5.1][net-smtp-v0.5.1] * matrix 0.4.3 + * 0.4.2 to [v0.4.3][matrix-v0.4.3] * prime 0.1.4 + * 0.1.3 to [v0.1.4][prime-v0.1.4] * rbs 3.10.0 -* typeprof 0.31.1 + * 3.8.0 to [v3.8.1][rbs-v3.8.1], [v3.9.0.dev.1][rbs-v3.9.0.dev.1], [v3.9.0.pre.1][rbs-v3.9.0.pre.1], [v3.9.0.pre.2][rbs-v3.9.0.pre.2], [v3.9.0][rbs-v3.9.0], [v3.9.1][rbs-v3.9.1], [v3.9.2][rbs-v3.9.2], [v3.9.3][rbs-v3.9.3], [v3.9.4][rbs-v3.9.4], [v3.9.5][rbs-v3.9.5], [v3.10.0.pre.1][rbs-v3.10.0.pre.1], [v3.10.0.pre.2][rbs-v3.10.0.pre.2], [v3.10.0][rbs-v3.10.0] * debug 1.11.1 + * 1.11.0 to [v1.11.1][debug-v1.11.1] * base64 0.3.0 + * 0.2.0 to [v0.3.0][base64-v0.3.0] * bigdecimal 4.0.1 + * 3.1.8 to [v3.2.0][bigdecimal-v3.2.0], [v3.2.1][bigdecimal-v3.2.1], [v3.2.2][bigdecimal-v3.2.2], [v3.2.3][bigdecimal-v3.2.3], [v3.3.0][bigdecimal-v3.3.0], [v3.3.1][bigdecimal-v3.3.1], [v4.0.0][bigdecimal-v4.0.0], [v4.0.1][bigdecimal-v4.0.1] * drb 2.2.3 + * 2.2.1 to [v2.2.3][drb-v2.2.3] * syslog 0.3.0 + * 0.2.0 to [v0.3.0][syslog-v0.3.0] * csv 3.3.5 -* repl_type_completor 0.1.12 + * 3.3.2 to [v3.3.3][csv-v3.3.3], [v3.3.4][csv-v3.3.4], [v3.3.5][csv-v3.3.5] ### RubyGems and Bundler @@ -584,3 +627,167 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 [Bug #21789]: https://bugs.ruby-lang.org/issues/21789 [GH-net-http #205]: https://github.com/ruby/net-http/issues/205 +[ostruct-v0.6.2]: https://github.com/ruby/ostruct/releases/tag/v0.6.2 +[ostruct-v0.6.3]: https://github.com/ruby/ostruct/releases/tag/v0.6.3 +[pstore-v0.2.0]: https://github.com/ruby/pstore/releases/tag/v0.2.0 +[benchmark-v0.4.1]: https://github.com/ruby/benchmark/releases/tag/v0.4.1 +[benchmark-v0.5.0]: https://github.com/ruby/benchmark/releases/tag/v0.5.0 +[logger-v1.6.5]: https://github.com/ruby/logger/releases/tag/v1.6.5 +[logger-v1.6.6]: https://github.com/ruby/logger/releases/tag/v1.6.6 +[logger-v1.7.0]: https://github.com/ruby/logger/releases/tag/v1.7.0 +[rdoc-v6.14.1]: https://github.com/ruby/rdoc/releases/tag/v6.14.1 +[rdoc-v6.14.2]: https://github.com/ruby/rdoc/releases/tag/v6.14.2 +[rdoc-v6.15.0]: https://github.com/ruby/rdoc/releases/tag/v6.15.0 +[rdoc-v6.15.1]: https://github.com/ruby/rdoc/releases/tag/v6.15.1 +[rdoc-v6.16.0]: https://github.com/ruby/rdoc/releases/tag/v6.16.0 +[rdoc-v6.16.1]: https://github.com/ruby/rdoc/releases/tag/v6.16.1 +[rdoc-v6.17.0]: https://github.com/ruby/rdoc/releases/tag/v6.17.0 +[rdoc-v7.0.0]: https://github.com/ruby/rdoc/releases/tag/v7.0.0 +[rdoc-v7.0.1]: https://github.com/ruby/rdoc/releases/tag/v7.0.1 +[rdoc-v7.0.2]: https://github.com/ruby/rdoc/releases/tag/v7.0.2 +[win32ole-v1.9.2]: https://github.com/ruby/win32ole/releases/tag/v1.9.2 +[irb-v1.15.0]: https://github.com/ruby/irb/releases/tag/v1.15.0 +[irb-v1.15.1]: https://github.com/ruby/irb/releases/tag/v1.15.1 +[irb-v1.15.2]: https://github.com/ruby/irb/releases/tag/v1.15.2 +[irb-v1.15.3]: https://github.com/ruby/irb/releases/tag/v1.15.3 +[irb-v1.16.0]: https://github.com/ruby/irb/releases/tag/v1.16.0 +[reline-v0.6.1]: https://github.com/ruby/reline/releases/tag/v0.6.1 +[reline-v0.6.2]: https://github.com/ruby/reline/releases/tag/v0.6.2 +[reline-v0.6.3]: https://github.com/ruby/reline/releases/tag/v0.6.3 +[fiddle-v1.1.7]: https://github.com/ruby/fiddle/releases/tag/v1.1.7 +[fiddle-v1.1.8]: https://github.com/ruby/fiddle/releases/tag/v1.1.8 +[date-v3.5.0]: https://github.com/ruby/date/releases/tag/v3.5.0 +[date-v3.5.1]: https://github.com/ruby/date/releases/tag/v3.5.1 +[delegate-v0.5.0]: https://github.com/ruby/delegate/releases/tag/v0.5.0 +[delegate-v0.6.0]: https://github.com/ruby/delegate/releases/tag/v0.6.0 +[delegate-v0.6.1]: https://github.com/ruby/delegate/releases/tag/v0.6.1 +[digest-v3.2.1]: https://github.com/ruby/digest/releases/tag/v3.2.1 +[english-v0.8.1]: https://github.com/ruby/english/releases/tag/v0.8.1 +[erb-v5.1.2]: https://github.com/ruby/erb/releases/tag/v5.1.2 +[erb-v5.1.3]: https://github.com/ruby/erb/releases/tag/v5.1.3 +[erb-v6.0.0]: https://github.com/ruby/erb/releases/tag/v6.0.0 +[erb-v6.0.1]: https://github.com/ruby/erb/releases/tag/v6.0.1 +[fcntl-v1.3.0]: https://github.com/ruby/fcntl/releases/tag/v1.3.0 +[fileutils-v1.8.0]: https://github.com/ruby/fileutils/releases/tag/v1.8.0 +[forwardable-v1.4.0]: https://github.com/ruby/forwardable/releases/tag/v1.4.0 +[io-console-v0.8.2]: https://github.com/ruby/io-console/releases/tag/v0.8.2 +[io-wait-v0.3.3]: https://github.com/ruby/io-wait/releases/tag/v0.3.3 +[io-wait-v0.3.5.test1]: https://github.com/ruby/io-wait/releases/tag/v0.3.5.test1 +[io-wait-v0.3.5]: https://github.com/ruby/io-wait/releases/tag/v0.3.5 +[io-wait-v0.3.6]: https://github.com/ruby/io-wait/releases/tag/v0.3.6 +[io-wait-v0.4.0]: https://github.com/ruby/io-wait/releases/tag/v0.4.0 +[json-v2.10.0]: https://github.com/ruby/json/releases/tag/v2.10.0 +[json-v2.10.1]: https://github.com/ruby/json/releases/tag/v2.10.1 +[json-v2.10.2]: https://github.com/ruby/json/releases/tag/v2.10.2 +[json-v2.11.0]: https://github.com/ruby/json/releases/tag/v2.11.0 +[json-v2.11.1]: https://github.com/ruby/json/releases/tag/v2.11.1 +[json-v2.11.2]: https://github.com/ruby/json/releases/tag/v2.11.2 +[json-v2.11.3]: https://github.com/ruby/json/releases/tag/v2.11.3 +[json-v2.12.0]: https://github.com/ruby/json/releases/tag/v2.12.0 +[json-v2.12.1]: https://github.com/ruby/json/releases/tag/v2.12.1 +[json-v2.12.2]: https://github.com/ruby/json/releases/tag/v2.12.2 +[json-v2.13.0]: https://github.com/ruby/json/releases/tag/v2.13.0 +[json-v2.13.1]: https://github.com/ruby/json/releases/tag/v2.13.1 +[json-v2.13.2]: https://github.com/ruby/json/releases/tag/v2.13.2 +[json-v2.14.0]: https://github.com/ruby/json/releases/tag/v2.14.0 +[json-v2.14.1]: https://github.com/ruby/json/releases/tag/v2.14.1 +[json-v2.15.0]: https://github.com/ruby/json/releases/tag/v2.15.0 +[json-v2.15.1]: https://github.com/ruby/json/releases/tag/v2.15.1 +[json-v2.15.2]: https://github.com/ruby/json/releases/tag/v2.15.2 +[json-v2.16.0]: https://github.com/ruby/json/releases/tag/v2.16.0 +[json-v2.17.0]: https://github.com/ruby/json/releases/tag/v2.17.0 +[json-v2.17.1]: https://github.com/ruby/json/releases/tag/v2.17.1 +[json-v2.18.0]: https://github.com/ruby/json/releases/tag/v2.18.0 +[net-http-v0.7.0]: https://github.com/ruby/net-http/releases/tag/v0.7.0 +[net-http-v0.8.0]: https://github.com/ruby/net-http/releases/tag/v0.8.0 +[net-http-v0.9.0]: https://github.com/ruby/net-http/releases/tag/v0.9.0 +[net-http-v0.9.1]: https://github.com/ruby/net-http/releases/tag/v0.9.1 +[openssl-v3.3.2]: https://github.com/ruby/openssl/releases/tag/v3.3.2 +[openssl-v4.0.0]: https://github.com/ruby/openssl/releases/tag/v4.0.0 +[optparse-v0.7.0]: https://github.com/ruby/optparse/releases/tag/v0.7.0 +[optparse-v0.8.0]: https://github.com/ruby/optparse/releases/tag/v0.8.0 +[optparse-v0.8.1]: https://github.com/ruby/optparse/releases/tag/v0.8.1 +[pp-v0.6.3]: https://github.com/ruby/pp/releases/tag/v0.6.3 +[prism-v1.6.0]: https://github.com/ruby/prism/releases/tag/v1.6.0 +[prism-v1.7.0]: https://github.com/ruby/prism/releases/tag/v1.7.0 +[psych-v5.2.3]: https://github.com/ruby/psych/releases/tag/v5.2.3 +[psych-v5.2.4]: https://github.com/ruby/psych/releases/tag/v5.2.4 +[psych-v5.2.5]: https://github.com/ruby/psych/releases/tag/v5.2.5 +[psych-v5.2.6]: https://github.com/ruby/psych/releases/tag/v5.2.6 +[psych-v5.3.0]: https://github.com/ruby/psych/releases/tag/v5.3.0 +[psych-v5.3.1]: https://github.com/ruby/psych/releases/tag/v5.3.1 +[resolv-v0.6.3]: https://github.com/ruby/resolv/releases/tag/v0.6.3 +[resolv-v0.7.0]: https://github.com/ruby/resolv/releases/tag/v0.7.0 +[stringio-v3.1.3]: https://github.com/ruby/stringio/releases/tag/v3.1.3 +[stringio-v3.1.4]: https://github.com/ruby/stringio/releases/tag/v3.1.4 +[stringio-v3.1.5]: https://github.com/ruby/stringio/releases/tag/v3.1.5 +[stringio-v3.1.6]: https://github.com/ruby/stringio/releases/tag/v3.1.6 +[stringio-v3.1.7]: https://github.com/ruby/stringio/releases/tag/v3.1.7 +[stringio-v3.1.8]: https://github.com/ruby/stringio/releases/tag/v3.1.8 +[stringio-v3.1.9]: https://github.com/ruby/stringio/releases/tag/v3.1.9 +[stringio-v3.2.0]: https://github.com/ruby/stringio/releases/tag/v3.2.0 +[strscan-v3.1.3]: https://github.com/ruby/strscan/releases/tag/v3.1.3 +[strscan-v3.1.4]: https://github.com/ruby/strscan/releases/tag/v3.1.4 +[strscan-v3.1.5]: https://github.com/ruby/strscan/releases/tag/v3.1.5 +[strscan-v3.1.6]: https://github.com/ruby/strscan/releases/tag/v3.1.6 +[time-v0.4.2]: https://github.com/ruby/time/releases/tag/v0.4.2 +[timeout-v0.4.4]: https://github.com/ruby/timeout/releases/tag/v0.4.4 +[timeout-v0.5.0]: https://github.com/ruby/timeout/releases/tag/v0.5.0 +[timeout-v0.6.0]: https://github.com/ruby/timeout/releases/tag/v0.6.0 +[uri-v1.1.0]: https://github.com/ruby/uri/releases/tag/v1.1.0 +[uri-v1.1.1]: https://github.com/ruby/uri/releases/tag/v1.1.1 +[weakref-v0.1.4]: https://github.com/ruby/weakref/releases/tag/v0.1.4 +[zlib-v3.2.2]: https://github.com/ruby/zlib/releases/tag/v3.2.2 +[power_assert-v3.0.0]: https://github.com/ruby/power_assert/releases/tag/v3.0.0 +[power_assert-v3.0.1]: https://github.com/ruby/power_assert/releases/tag/v3.0.1 +[rake-v13.3.0]: https://github.com/ruby/rake/releases/tag/v13.3.0 +[rake-v13.3.1]: https://github.com/ruby/rake/releases/tag/v13.3.1 +[test-unit-3.6.8]: https://github.com/test-unit/test-unit/releases/tag/3.6.8 +[test-unit-3.6.9]: https://github.com/test-unit/test-unit/releases/tag/3.6.9 +[test-unit-3.7.0]: https://github.com/test-unit/test-unit/releases/tag/3.7.0 +[test-unit-3.7.1]: https://github.com/test-unit/test-unit/releases/tag/3.7.1 +[test-unit-3.7.2]: https://github.com/test-unit/test-unit/releases/tag/3.7.2 +[test-unit-3.7.3]: https://github.com/test-unit/test-unit/releases/tag/3.7.3 +[test-unit-3.7.4]: https://github.com/test-unit/test-unit/releases/tag/3.7.4 +[test-unit-3.7.5]: https://github.com/test-unit/test-unit/releases/tag/3.7.5 +[rss-0.3.2]: https://github.com/ruby/rss/releases/tag/0.3.2 +[net-ftp-v0.3.9]: https://github.com/ruby/net-ftp/releases/tag/v0.3.9 +[net-imap-v0.5.9]: https://github.com/ruby/net-imap/releases/tag/v0.5.9 +[net-imap-v0.5.10]: https://github.com/ruby/net-imap/releases/tag/v0.5.10 +[net-imap-v0.5.11]: https://github.com/ruby/net-imap/releases/tag/v0.5.11 +[net-imap-v0.5.12]: https://github.com/ruby/net-imap/releases/tag/v0.5.12 +[net-imap-v0.5.13]: https://github.com/ruby/net-imap/releases/tag/v0.5.13 +[net-imap-v0.6.0]: https://github.com/ruby/net-imap/releases/tag/v0.6.0 +[net-imap-v0.6.1]: https://github.com/ruby/net-imap/releases/tag/v0.6.1 +[net-imap-v0.6.2]: https://github.com/ruby/net-imap/releases/tag/v0.6.2 +[net-smtp-v0.5.1]: https://github.com/ruby/net-smtp/releases/tag/v0.5.1 +[matrix-v0.4.3]: https://github.com/ruby/matrix/releases/tag/v0.4.3 +[prime-v0.1.4]: https://github.com/ruby/prime/releases/tag/v0.1.4 +[rbs-v3.8.1]: https://github.com/ruby/rbs/releases/tag/v3.8.1 +[rbs-v3.9.0.dev.1]: https://github.com/ruby/rbs/releases/tag/v3.9.0.dev.1 +[rbs-v3.9.0.pre.1]: https://github.com/ruby/rbs/releases/tag/v3.9.0.pre.1 +[rbs-v3.9.0.pre.2]: https://github.com/ruby/rbs/releases/tag/v3.9.0.pre.2 +[rbs-v3.9.0]: https://github.com/ruby/rbs/releases/tag/v3.9.0 +[rbs-v3.9.1]: https://github.com/ruby/rbs/releases/tag/v3.9.1 +[rbs-v3.9.2]: https://github.com/ruby/rbs/releases/tag/v3.9.2 +[rbs-v3.9.3]: https://github.com/ruby/rbs/releases/tag/v3.9.3 +[rbs-v3.9.4]: https://github.com/ruby/rbs/releases/tag/v3.9.4 +[rbs-v3.9.5]: https://github.com/ruby/rbs/releases/tag/v3.9.5 +[rbs-v3.10.0.pre.1]: https://github.com/ruby/rbs/releases/tag/v3.10.0.pre.1 +[rbs-v3.10.0.pre.2]: https://github.com/ruby/rbs/releases/tag/v3.10.0.pre.2 +[rbs-v3.10.0]: https://github.com/ruby/rbs/releases/tag/v3.10.0 +[debug-v1.11.1]: https://github.com/ruby/debug/releases/tag/v1.11.1 +[base64-v0.3.0]: https://github.com/ruby/base64/releases/tag/v0.3.0 +[bigdecimal-v3.2.0]: https://github.com/ruby/bigdecimal/releases/tag/v3.2.0 +[bigdecimal-v3.2.1]: https://github.com/ruby/bigdecimal/releases/tag/v3.2.1 +[bigdecimal-v3.2.2]: https://github.com/ruby/bigdecimal/releases/tag/v3.2.2 +[bigdecimal-v3.2.3]: https://github.com/ruby/bigdecimal/releases/tag/v3.2.3 +[bigdecimal-v3.3.0]: https://github.com/ruby/bigdecimal/releases/tag/v3.3.0 +[bigdecimal-v3.3.1]: https://github.com/ruby/bigdecimal/releases/tag/v3.3.1 +[bigdecimal-v4.0.0]: https://github.com/ruby/bigdecimal/releases/tag/v4.0.0 +[bigdecimal-v4.0.1]: https://github.com/ruby/bigdecimal/releases/tag/v4.0.1 +[drb-v2.2.3]: https://github.com/ruby/drb/releases/tag/v2.2.3 +[syslog-v0.3.0]: https://github.com/ruby/syslog/releases/tag/v0.3.0 +[csv-v3.3.3]: https://github.com/ruby/csv/releases/tag/v3.3.3 +[csv-v3.3.4]: https://github.com/ruby/csv/releases/tag/v3.3.4 +[csv-v3.3.5]: https://github.com/ruby/csv/releases/tag/v3.3.5 From aaed4ccc64467c77cd3670a309b39e4b283ff69f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 17:32:39 +0900 Subject: [PATCH 2151/2435] Restore gem updates that are accidentally deleted --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index 57f9e20a00a752..17e298b8aac667 100644 --- a/NEWS.md +++ b/NEWS.md @@ -326,6 +326,8 @@ The following default gems are updated. * 0.8.0 to [v0.8.1][english-v0.8.1] * erb 6.0.1 * 4.0.4 to [v5.1.2][erb-v5.1.2], [v5.1.3][erb-v5.1.3], [v6.0.0][erb-v6.0.0], [v6.0.1][erb-v6.0.1] +* error_highlight 0.7.1 +* etc 1.4.6 * fcntl 1.3.0 * 1.2.0 to [v1.3.0][fcntl-v1.3.0] * fileutils 1.8.0 @@ -334,8 +336,10 @@ The following default gems are updated. * 1.3.3 to [v1.4.0][forwardable-v1.4.0] * io-console 0.8.2 * 0.8.1 to [v0.8.2][io-console-v0.8.2] +* io-nonblock 0.3.2 * io-wait 0.4.0 * 0.3.2 to [v0.3.3][io-wait-v0.3.3], [v0.3.5.test1][io-wait-v0.3.5.test1], [v0.3.5][io-wait-v0.3.5], [v0.3.6][io-wait-v0.3.6], [v0.4.0][io-wait-v0.4.0] +* ipaddr 1.2.8 * json 2.18.0 * 2.9.1 to [v2.10.0][json-v2.10.0], [v2.10.1][json-v2.10.1], [v2.10.2][json-v2.10.2], [v2.11.0][json-v2.11.0], [v2.11.1][json-v2.11.1], [v2.11.2][json-v2.11.2], [v2.11.3][json-v2.11.3], [v2.12.0][json-v2.12.0], [v2.12.1][json-v2.12.1], [v2.12.2][json-v2.12.2], [v2.13.0][json-v2.13.0], [v2.13.1][json-v2.13.1], [v2.13.2][json-v2.13.2], [v2.14.0][json-v2.14.0], [v2.14.1][json-v2.14.1], [v2.15.0][json-v2.15.0], [v2.15.1][json-v2.15.1], [v2.15.2][json-v2.15.2], [v2.16.0][json-v2.16.0], [v2.17.0][json-v2.17.0], [v2.17.1][json-v2.17.1], [v2.18.0][json-v2.18.0] * net-http 0.9.1 @@ -376,6 +380,7 @@ The following bundled gems are updated. * 13.2.1 to [v13.3.0][rake-v13.3.0], [v13.3.1][rake-v13.3.1] * test-unit 3.7.5 * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3], [3.7.4][test-unit-3.7.4], [3.7.5][test-unit-3.7.5] +* rexml 3.4.4 * rss 0.3.2 * 0.3.1 to [0.3.2][rss-0.3.2] * net-ftp 0.3.9 @@ -390,6 +395,7 @@ The following bundled gems are updated. * 0.1.3 to [v0.1.4][prime-v0.1.4] * rbs 3.10.0 * 3.8.0 to [v3.8.1][rbs-v3.8.1], [v3.9.0.dev.1][rbs-v3.9.0.dev.1], [v3.9.0.pre.1][rbs-v3.9.0.pre.1], [v3.9.0.pre.2][rbs-v3.9.0.pre.2], [v3.9.0][rbs-v3.9.0], [v3.9.1][rbs-v3.9.1], [v3.9.2][rbs-v3.9.2], [v3.9.3][rbs-v3.9.3], [v3.9.4][rbs-v3.9.4], [v3.9.5][rbs-v3.9.5], [v3.10.0.pre.1][rbs-v3.10.0.pre.1], [v3.10.0.pre.2][rbs-v3.10.0.pre.2], [v3.10.0][rbs-v3.10.0] +* typeprof 0.31.1 * debug 1.11.1 * 1.11.0 to [v1.11.1][debug-v1.11.1] * base64 0.3.0 @@ -402,6 +408,7 @@ The following bundled gems are updated. * 0.2.0 to [v0.3.0][syslog-v0.3.0] * csv 3.3.5 * 3.3.2 to [v3.3.3][csv-v3.3.3], [v3.3.4][csv-v3.3.4], [v3.3.5][csv-v3.3.5] +* repl_type_completor 0.1.12 ### RubyGems and Bundler From 6e20e92d0ad006e508842839eed77b9642051874 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 18:36:35 +0900 Subject: [PATCH 2152/2435] Disable auto-update of bundled gems --- .github/workflows/bundled_gems.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 5521752d2f517c..f12bc849b16551 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -59,6 +59,7 @@ jobs: id: bundled_gems run: | ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT + if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} - name: Update spec/bundler/support/builders.rb run: | @@ -66,6 +67,7 @@ jobs: rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} shell: ruby -i~ {0} spec/bundler/support/builders.rb + if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} - name: Maintain updated gems list in NEWS run: | From c17307ac22f37f74786a4f016121c6ee8cc38915 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 24 Dec 2025 18:37:30 +0900 Subject: [PATCH 2153/2435] Rollback to test-unit 3.7.3 3.7.5 is not working with rbs-3.10.0 https://github.com/ruby/ruby/actions/runs/20480628393/job/58853288287#step:22:353 ``` D:/a/ruby/ruby/src/.bundle/gems/test-unit-3.7.5/lib/test/unit/testcase.rb:641:in 'block (2 levels) in Test::Unit::TestCase#run': failed to allocate memory (NoMemoryError) ``` --- NEWS.md | 6 ++---- gems/bundled_gems | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 17e298b8aac667..a4abd2bdbc7afe 100644 --- a/NEWS.md +++ b/NEWS.md @@ -378,8 +378,8 @@ The following bundled gems are updated. * 2.0.5 to [v3.0.0][power_assert-v3.0.0], [v3.0.1][power_assert-v3.0.1] * rake 13.3.1 * 13.2.1 to [v13.3.0][rake-v13.3.0], [v13.3.1][rake-v13.3.1] -* test-unit 3.7.5 - * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3], [3.7.4][test-unit-3.7.4], [3.7.5][test-unit-3.7.5] +* test-unit 3.7.3 + * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3] * rexml 3.4.4 * rss 0.3.2 * 0.3.1 to [0.3.2][rss-0.3.2] @@ -755,8 +755,6 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [test-unit-3.7.1]: https://github.com/test-unit/test-unit/releases/tag/3.7.1 [test-unit-3.7.2]: https://github.com/test-unit/test-unit/releases/tag/3.7.2 [test-unit-3.7.3]: https://github.com/test-unit/test-unit/releases/tag/3.7.3 -[test-unit-3.7.4]: https://github.com/test-unit/test-unit/releases/tag/3.7.4 -[test-unit-3.7.5]: https://github.com/test-unit/test-unit/releases/tag/3.7.5 [rss-0.3.2]: https://github.com/ruby/rss/releases/tag/0.3.2 [net-ftp-v0.3.9]: https://github.com/ruby/net-ftp/releases/tag/v0.3.9 [net-imap-v0.5.9]: https://github.com/ruby/net-imap/releases/tag/v0.5.9 diff --git a/gems/bundled_gems b/gems/bundled_gems index c15ded2fcfc6a0..495f7afc694e0d 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.5 https://github.com/test-unit/test-unit +test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From df1c9a06ac42871519a05d53fd6909567cc4531f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 24 Dec 2025 16:42:21 +0900 Subject: [PATCH 2154/2435] Win32: Remove an unused function Since 50e5c542cc0541fb38e52766d88d87bd8a96b072, `constat_reset` is no longer used. --- win32/win32.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index 97dc7148085c55..f25139ca8bfb4d 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -6737,20 +6737,6 @@ constat_handle(HANDLE h) return p; } -/* License: Ruby's */ -static void -constat_reset(HANDLE h) -{ - st_data_t data; - struct constat *p; - thread_exclusive(conlist) { - if (!conlist || conlist == conlist_disabled) continue; - if (!st_lookup(conlist, (st_data_t)h, &data)) continue; - p = (struct constat *)data; - p->vt100.state = constat_init; - } -} - #define FOREGROUND_MASK (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY) #define BACKGROUND_MASK (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY) From 2d0d95305c0973c0a8966a07fa029c55e27f94cd Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 24 Dec 2025 17:53:35 +0900 Subject: [PATCH 2155/2435] ext/-test-/scheduler/scheduler.c: explicitly ignore the result of write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` scheduler.c:44:5: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result] 44 | write(blocking_state->notify_descriptor, "x", 1); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` --- ext/-test-/scheduler/scheduler.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/-test-/scheduler/scheduler.c b/ext/-test-/scheduler/scheduler.c index f8384f597e35bb..b742a5573b55c4 100644 --- a/ext/-test-/scheduler/scheduler.c +++ b/ext/-test-/scheduler/scheduler.c @@ -41,7 +41,8 @@ blocking_operation(void *argument) { struct blocking_state *blocking_state = (struct blocking_state *)argument; - write(blocking_state->notify_descriptor, "x", 1); + ssize_t ret = write(blocking_state->notify_descriptor, "x", 1); + (void)ret; // ignore the result for now while (!blocking_state->interrupted) { struct timeval tv = {1, 0}; // 1 second timeout. From 17e4f28c2725495a2ff14e1899d791868d9ba42f Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 24 Dec 2025 17:43:22 +0900 Subject: [PATCH 2156/2435] Remove unintentional return --- test/ruby/test_box.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index f35d07c0863dc2..a531afa679689e 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -426,7 +426,7 @@ def test_add_constants_in_box EnvUtil.verbose_warning do @box.require_relative('box/consts') end - return + assert_equal 999, String::STR_CONST0 assert_raise(NameError) { String::STR_CONST1 } assert_raise(NameError) { String::STR_CONST2 } From f00abcfdc33f7bcbf4b30c947487ade8181e84f9 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 24 Dec 2025 17:43:37 +0900 Subject: [PATCH 2157/2435] Prevent "warning: assigned but unused variable - it" --- test/ruby/test_proc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index b50875e7b0aa24..f74342322f5b78 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1838,7 +1838,7 @@ def test_implicit_parameters_for_it def test_implicit_parameters_for_it_complex "foo".tap do - it = "bar" + it = it = "bar" assert_equal([], binding.implicit_parameters) assert_raise(NameError) { binding.implicit_parameter_get(:it) } @@ -1863,7 +1863,7 @@ def test_implicit_parameters_for_it_complex "foo".tap do it or flunk - it = "bar" + it = it = "bar" assert_equal([:it], binding.implicit_parameters) assert_equal("foo", binding.implicit_parameter_get(:it)) From 8de2622c1291afd29a9a570e6b396bbe722360a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 20 Dec 2025 21:37:45 +0900 Subject: [PATCH 2158/2435] Fix a fragile test `Dir.mktmpdir` concatenates a random base-36 number separated by "-", so may generate pathnames containing "-j2". --- test/rubygems/test_gem_commands_update_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 3bb4a72c4151ad..5ed12ad4814095 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -722,7 +722,7 @@ def test_pass_down_the_job_option_to_make gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) if vc_windows? && nmake_found? - refute_includes(gem_make_out, "-j2") + refute_includes(gem_make_out, " -j2") else assert_includes(gem_make_out, "make -j2") end From ba2f6972193cdbd7c1e77e26212513e47926b115 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Wed, 24 Dec 2025 22:47:02 +0900 Subject: [PATCH 2159/2435] Revert "Extract `ruby_api_version_name`" This reverts commit 9b576cd6255aba97e5e2f55f4b09f00c7dd0e839. --- ruby.c | 9 ++++----- test/ruby/test_rubyoptions.rb | 19 +++++++++---------- version.c | 10 +--------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/ruby.c b/ruby.c index 11376cbe7066a9..36d6ed203b5d11 100644 --- a/ruby.c +++ b/ruby.c @@ -61,6 +61,7 @@ #include "ruby/util.h" #include "ruby/version.h" #include "ruby/internal/error.h" +#include "version.h" #define singlebit_only_p(x) !((x) & ((x)-1)) STATIC_ASSERT(Qnil_1bit_from_Qfalse, singlebit_only_p(Qnil^Qfalse)); @@ -305,8 +306,6 @@ ruby_show_usage_line(const char *name, const char *secondary, const char *descri description, help, highlight, width, columns); } -RUBY_EXTERN const char ruby_api_version_name[]; - static void usage(const char *name, int help, int highlight, int columns) { @@ -409,9 +408,9 @@ usage(const char *name, int help, int highlight, int columns) unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16; #define SHOW(m) show_usage_line(&(m), help, highlight, w, columns) - printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name); - printf("\n""Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n", - ruby_api_version_name); + printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n\n", sb, se, name); + printf("Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n", + RUBY_PATCHLEVEL == -1 ? "master" : STRINGIZE(RUBY_VERSION_MAJOR) "." STRINGIZE(RUBY_VERSION_MINOR)); for (i = 0; i < num; ++i) SHOW(usage_msg[i]); diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 2ec6478a7fd058..96d932aea2e652 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -47,27 +47,26 @@ def test_source_file assert_in_out_err([], "", [], []) end - version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}" - OPTIONS_LINK = "https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html" - def test_usage assert_in_out_err(%w(-h)) do |r, e| - _, _, link, *r = r - assert_include(link, OPTIONS_LINK) - assert_operator(r.size, :<=, 24) - longer = r.select {|x| x.size >= 80} + assert_operator(r.size, :<=, 26) + longer = r[3..-1].select {|x| x.size >= 80} assert_equal([], longer) assert_equal([], e) + + version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}" + assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html") end end def test_usage_long assert_in_out_err(%w(--help)) do |r, e| - _, _, link, *r = r - assert_include(link, OPTIONS_LINK) - longer = r.select {|x| x.size > 80} + longer = r[3..-1].select {|x| x.size > 80} assert_equal([], longer) assert_equal([], e) + + version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}" + assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html") end end diff --git a/version.c b/version.c index 554783265201f0..448925f1fb2485 100644 --- a/version.c +++ b/version.c @@ -25,9 +25,8 @@ #ifdef RUBY_REVISION # if RUBY_PATCHLEVEL == -1 -# define RUBY_API_VERSION_NAME "master" # ifndef RUBY_BRANCH_NAME -# define RUBY_BRANCH_NAME RUBY_API_VERSION_NAME +# define RUBY_BRANCH_NAME "master" # endif # define RUBY_REVISION_STR " "RUBY_BRANCH_NAME" "RUBY_REVISION # else @@ -37,9 +36,6 @@ # define RUBY_REVISION "HEAD" # define RUBY_REVISION_STR "" #endif -#ifndef RUBY_API_VERSION_NAME -# define RUBY_API_VERSION_NAME RUBY_API_VERSION_STR -#endif #if !defined RUBY_RELEASE_DATETIME || RUBY_PATCHLEVEL != -1 # undef RUBY_RELEASE_DATETIME # define RUBY_RELEASE_DATETIME RUBY_RELEASE_DATE @@ -49,9 +45,6 @@ #define MKSTR(type) rb_obj_freeze(rb_usascii_str_new_static(ruby_##type, sizeof(ruby_##type)-1)) #define MKINT(name) INT2FIX(ruby_##name) -#define RUBY_API_VERSION_STR \ - STRINGIZE(RUBY_API_VERSION_MAJOR) "." \ - STRINGIZE(RUBY_API_VERSION_MINOR) const int ruby_api_version[] = { RUBY_API_VERSION_MAJOR, RUBY_API_VERSION_MINOR, @@ -84,7 +77,6 @@ const char ruby_revision[] = RUBY_FULL_REVISION; const char ruby_release_date[] = RUBY_RELEASE_DATE; const char ruby_platform[] = RUBY_PLATFORM; const int ruby_patchlevel = RUBY_PATCHLEVEL; -const char ruby_api_version_name[] = RUBY_API_VERSION_NAME; const char ruby_description[] = "ruby " RUBY_VERSION RUBY_PATCHLEVEL_STR " " "(" RUBY_RELEASE_DATETIME RUBY_REVISION_STR ") " From 285e22edc55522f3466357c4c27615a6015d84dc Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Wed, 24 Dec 2025 22:52:23 +0900 Subject: [PATCH 2160/2435] Revert "Add link to Ruby options doc in help text" This reverts commit 31ff07ed1eb05d01f7da3c017d542137a3db1e94. * Don't add a test which only runs on production release * https://github.com/ruby/actions/actions/runs/20486784889/job/58870959976 * Don't add a new line to `ruby --help` * https://github.com/ruby/ruby/pull/14142#issuecomment-3689829564 --- doc/language/options.md | 7 ------- ruby.c | 6 +----- test/ruby/test_rubyoptions.rb | 12 +++--------- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/doc/language/options.md b/doc/language/options.md index c805c7dd65c2c0..cca87f42dedf0f 100644 --- a/doc/language/options.md +++ b/doc/language/options.md @@ -1,10 +1,3 @@ - - # Ruby Command-Line Options ## About the Examples diff --git a/ruby.c b/ruby.c index 36d6ed203b5d11..d7a608a00dd365 100644 --- a/ruby.c +++ b/ruby.c @@ -61,7 +61,6 @@ #include "ruby/util.h" #include "ruby/version.h" #include "ruby/internal/error.h" -#include "version.h" #define singlebit_only_p(x) !((x) & ((x)-1)) STATIC_ASSERT(Qnil_1bit_from_Qfalse, singlebit_only_p(Qnil^Qfalse)); @@ -408,10 +407,7 @@ usage(const char *name, int help, int highlight, int columns) unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16; #define SHOW(m) show_usage_line(&(m), help, highlight, w, columns) - printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n\n", sb, se, name); - printf("Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n", - RUBY_PATCHLEVEL == -1 ? "master" : STRINGIZE(RUBY_VERSION_MAJOR) "." STRINGIZE(RUBY_VERSION_MINOR)); - + printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name); for (i = 0; i < num; ++i) SHOW(usage_msg[i]); diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 96d932aea2e652..735d2681fb6a13 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -49,24 +49,18 @@ def test_source_file def test_usage assert_in_out_err(%w(-h)) do |r, e| - assert_operator(r.size, :<=, 26) - longer = r[3..-1].select {|x| x.size >= 80} + assert_operator(r.size, :<=, 25) + longer = r[1..-1].select {|x| x.size >= 80} assert_equal([], longer) assert_equal([], e) - - version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}" - assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html") end end def test_usage_long assert_in_out_err(%w(--help)) do |r, e| - longer = r[3..-1].select {|x| x.size > 80} + longer = r[1..-1].select {|x| x.size > 80} assert_equal([], longer) assert_equal([], e) - - version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}" - assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html") end end From 29ffc5d624f21d101d819bc77b7a048ab70d1c13 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Wed, 24 Dec 2025 23:07:35 +0900 Subject: [PATCH 2161/2435] Reapply "Extract `ruby_api_version_name`" This reverts commit ba2f6972193cdbd7c1e77e26212513e47926b115. Box already used ruby_api_version_name. --- ruby.c | 5 +++++ version.c | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ruby.c b/ruby.c index d7a608a00dd365..11376cbe7066a9 100644 --- a/ruby.c +++ b/ruby.c @@ -305,6 +305,8 @@ ruby_show_usage_line(const char *name, const char *secondary, const char *descri description, help, highlight, width, columns); } +RUBY_EXTERN const char ruby_api_version_name[]; + static void usage(const char *name, int help, int highlight, int columns) { @@ -408,6 +410,9 @@ usage(const char *name, int help, int highlight, int columns) #define SHOW(m) show_usage_line(&(m), help, highlight, w, columns) printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name); + printf("\n""Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n", + ruby_api_version_name); + for (i = 0; i < num; ++i) SHOW(usage_msg[i]); diff --git a/version.c b/version.c index 448925f1fb2485..554783265201f0 100644 --- a/version.c +++ b/version.c @@ -25,8 +25,9 @@ #ifdef RUBY_REVISION # if RUBY_PATCHLEVEL == -1 +# define RUBY_API_VERSION_NAME "master" # ifndef RUBY_BRANCH_NAME -# define RUBY_BRANCH_NAME "master" +# define RUBY_BRANCH_NAME RUBY_API_VERSION_NAME # endif # define RUBY_REVISION_STR " "RUBY_BRANCH_NAME" "RUBY_REVISION # else @@ -36,6 +37,9 @@ # define RUBY_REVISION "HEAD" # define RUBY_REVISION_STR "" #endif +#ifndef RUBY_API_VERSION_NAME +# define RUBY_API_VERSION_NAME RUBY_API_VERSION_STR +#endif #if !defined RUBY_RELEASE_DATETIME || RUBY_PATCHLEVEL != -1 # undef RUBY_RELEASE_DATETIME # define RUBY_RELEASE_DATETIME RUBY_RELEASE_DATE @@ -45,6 +49,9 @@ #define MKSTR(type) rb_obj_freeze(rb_usascii_str_new_static(ruby_##type, sizeof(ruby_##type)-1)) #define MKINT(name) INT2FIX(ruby_##name) +#define RUBY_API_VERSION_STR \ + STRINGIZE(RUBY_API_VERSION_MAJOR) "." \ + STRINGIZE(RUBY_API_VERSION_MINOR) const int ruby_api_version[] = { RUBY_API_VERSION_MAJOR, RUBY_API_VERSION_MINOR, @@ -77,6 +84,7 @@ const char ruby_revision[] = RUBY_FULL_REVISION; const char ruby_release_date[] = RUBY_RELEASE_DATE; const char ruby_platform[] = RUBY_PLATFORM; const int ruby_patchlevel = RUBY_PATCHLEVEL; +const char ruby_api_version_name[] = RUBY_API_VERSION_NAME; const char ruby_description[] = "ruby " RUBY_VERSION RUBY_PATCHLEVEL_STR " " "(" RUBY_RELEASE_DATETIME RUBY_REVISION_STR ") " From 479f8682c80e695c2a1f8dd66c82cc530c5bbf40 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Wed, 24 Dec 2025 23:39:23 +0900 Subject: [PATCH 2162/2435] Remove extra help --- ruby.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/ruby.c b/ruby.c index 11376cbe7066a9..b00fc1502dec1c 100644 --- a/ruby.c +++ b/ruby.c @@ -410,9 +410,6 @@ usage(const char *name, int help, int highlight, int columns) #define SHOW(m) show_usage_line(&(m), help, highlight, w, columns) printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name); - printf("\n""Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n", - ruby_api_version_name); - for (i = 0; i < num; ++i) SHOW(usage_msg[i]); From 96d876534e115ffbb92a72f7378da84fd54a3a87 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 24 Dec 2025 18:54:39 +0000 Subject: [PATCH 2163/2435] Bump RDoc to 7.0.3 --- NEWS.md | 5 +++-- gems/bundled_gems | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index a4abd2bdbc7afe..71dd7eab8f13b0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -296,8 +296,8 @@ The following bundled gems are promoted from default gems. * 0.4.0 to [v0.4.1][benchmark-v0.4.1], [v0.5.0][benchmark-v0.5.0] * logger 1.7.0 * 1.6.4 to [v1.6.5][logger-v1.6.5], [v1.6.6][logger-v1.6.6], [v1.7.0][logger-v1.7.0] -* rdoc 7.0.2 - * 6.14.0 to [v6.14.1][rdoc-v6.14.1], [v6.14.2][rdoc-v6.14.2], [v6.15.0][rdoc-v6.15.0], [v6.15.1][rdoc-v6.15.1], [v6.16.0][rdoc-v6.16.0], [v6.16.1][rdoc-v6.16.1], [v6.17.0][rdoc-v6.17.0], [v7.0.0][rdoc-v7.0.0], [v7.0.1][rdoc-v7.0.1], [v7.0.2][rdoc-v7.0.2] +* rdoc 7.0.3 + * 6.14.0 to [v6.14.1][rdoc-v6.14.1], [v6.14.2][rdoc-v6.14.2], [v6.15.0][rdoc-v6.15.0], [v6.15.1][rdoc-v6.15.1], [v6.16.0][rdoc-v6.16.0], [v6.16.1][rdoc-v6.16.1], [v6.17.0][rdoc-v6.17.0], [v7.0.0][rdoc-v7.0.0], [v7.0.1][rdoc-v7.0.1], [v7.0.2][rdoc-v7.0.2], [v7.0.3][rdoc-v7.0.3] * win32ole 1.9.2 * 1.9.1 to [v1.9.2][win32ole-v1.9.2] * irb 1.16.0 @@ -652,6 +652,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [rdoc-v7.0.0]: https://github.com/ruby/rdoc/releases/tag/v7.0.0 [rdoc-v7.0.1]: https://github.com/ruby/rdoc/releases/tag/v7.0.1 [rdoc-v7.0.2]: https://github.com/ruby/rdoc/releases/tag/v7.0.2 +[rdoc-v7.0.3]: https://github.com/ruby/rdoc/releases/tag/v7.0.3 [win32ole-v1.9.2]: https://github.com/ruby/win32ole/releases/tag/v1.9.2 [irb-v1.15.0]: https://github.com/ruby/irb/releases/tag/v1.15.0 [irb-v1.15.1]: https://github.com/ruby/irb/releases/tag/v1.15.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 495f7afc694e0d..91867b57d92139 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 7.0.2 https://github.com/ruby/rdoc +rdoc 7.0.3 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From d0193e924003b33ab46ee4ac13fbec5d53d02f1b Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 25 Dec 2025 01:40:57 +0900 Subject: [PATCH 2164/2435] Bundle test-unit 3.7.5 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 91867b57d92139..9461122e62dd73 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.3 https://github.com/test-unit/test-unit +test-unit 3.7.5 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From b92bc9654120547f9dd261ce04dacdb17bf720e1 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 25 Dec 2025 01:41:18 +0900 Subject: [PATCH 2165/2435] Skip test to avoid NoMemoryError --- tool/rbs_skip_tests | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index d5139705b3c4d3..ec149fdf5d0eef 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -45,3 +45,4 @@ test_linear_time?(RegexpSingletonTest) test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby +test_reachable_objects_from_root(ObjectSpaceTest) NoMemoryError with test-unit 3.7.5 From 8caea03213ff809544271d3135e0873d9c419532 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 25 Dec 2025 02:13:09 +0900 Subject: [PATCH 2166/2435] rbs_skip_tests_windows --- tool/rbs_skip_tests | 1 - tool/rbs_skip_tests_windows | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index ec149fdf5d0eef..d5139705b3c4d3 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -45,4 +45,3 @@ test_linear_time?(RegexpSingletonTest) test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby -test_reachable_objects_from_root(ObjectSpaceTest) NoMemoryError with test-unit 3.7.5 diff --git a/tool/rbs_skip_tests_windows b/tool/rbs_skip_tests_windows index 43888c98a71324..1b9f930e846624 100644 --- a/tool/rbs_skip_tests_windows +++ b/tool/rbs_skip_tests_windows @@ -105,3 +105,5 @@ test_new(TempfileSingletonTest) # Errno::EACCES: Permission denied @ apply2files - C:/a/_temp/d20250813-10156-f8z9pn/test.gz test_open(ZlibGzipReaderSingletonTest) + +test_reachable_objects_from_root(ObjectSpaceTest) To avoid NoMemoryError with test-unit 3.7.5 From 26a1a522b76cc9fd2ed2d963fd2df635f59b7a39 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Dec 2025 05:47:34 +0900 Subject: [PATCH 2167/2435] Revert "Rollback to test-unit 3.7.3" This reverts commit c17307ac22f37f74786a4f016121c6ee8cc38915. --- NEWS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 71dd7eab8f13b0..c1c6d2d2c83a48 100644 --- a/NEWS.md +++ b/NEWS.md @@ -378,8 +378,8 @@ The following bundled gems are updated. * 2.0.5 to [v3.0.0][power_assert-v3.0.0], [v3.0.1][power_assert-v3.0.1] * rake 13.3.1 * 13.2.1 to [v13.3.0][rake-v13.3.0], [v13.3.1][rake-v13.3.1] -* test-unit 3.7.3 - * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3] +* test-unit 3.7.5 + * 3.6.7 to [3.6.8][test-unit-3.6.8], [3.6.9][test-unit-3.6.9], [3.7.0][test-unit-3.7.0], [3.7.1][test-unit-3.7.1], [3.7.2][test-unit-3.7.2], [3.7.3][test-unit-3.7.3], [3.7.4][test-unit-3.7.4], [3.7.5][test-unit-3.7.5] * rexml 3.4.4 * rss 0.3.2 * 0.3.1 to [0.3.2][rss-0.3.2] @@ -756,6 +756,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [test-unit-3.7.1]: https://github.com/test-unit/test-unit/releases/tag/3.7.1 [test-unit-3.7.2]: https://github.com/test-unit/test-unit/releases/tag/3.7.2 [test-unit-3.7.3]: https://github.com/test-unit/test-unit/releases/tag/3.7.3 +[test-unit-3.7.4]: https://github.com/test-unit/test-unit/releases/tag/3.7.4 +[test-unit-3.7.5]: https://github.com/test-unit/test-unit/releases/tag/3.7.5 [rss-0.3.2]: https://github.com/ruby/rss/releases/tag/0.3.2 [net-ftp-v0.3.9]: https://github.com/ruby/net-ftp/releases/tag/v0.3.9 [net-imap-v0.5.9]: https://github.com/ruby/net-imap/releases/tag/v0.5.9 From 8d097bc472fc66221dee23bb8f9e0dddac16db23 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Dec 2025 05:52:41 +0900 Subject: [PATCH 2168/2435] Rollback to minitest-5.27.0 Test of 6.0.0 is not working with 4.0.0 stable version. https://github.com/ruby/actions/actions/runs/20488398805/job/58875672023#step:20:362 ``` rake aborted! NoMethodError: undefined method 'cov_filter=' for # (NoMethodError) self.cov_filter = %w[ tmp ] ^^^^^^^^^^^^^ /home/runner/work/actions/actions/ruby-4.0.0/gems/src/minitest/Rakefile:20:in 'block in ' /home/runner/work/actions/actions/ruby-4.0.0/.bundle/gems/hoe-3.20.0/lib/hoe.rb:394:in 'BasicObject#instance_eval' /home/runner/work/actions/actions/ruby-4.0.0/.bundle/gems/hoe-3.20.0/lib/hoe.rb:394:in 'Hoe.spec' /home/runner/work/actions/actions/ruby-4.0.0/gems/src/minitest/Rakefile:11:in '' /home/runner/work/actions/actions/ruby-4.0.0/.bundle/gems/rake-13.3.1/exe/rake:27:in '' (See full trace by running task with --trace) ``` --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index c1c6d2d2c83a48..877b849ab148ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -373,7 +373,7 @@ The following default gems are updated. The following bundled gems are updated. -* minitest 6.0.0 +* minitest 5.27.0 * power_assert 3.0.1 * 2.0.5 to [v3.0.0][power_assert-v3.0.0], [v3.0.1][power_assert-v3.0.1] * rake 13.3.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 9461122e62dd73..82dffd091e9edb 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 6.0.0 https://github.com/minitest/minitest +minitest 5.27.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.5 https://github.com/test-unit/test-unit From 3e82da723241b7205dc2b7706c66e9ead1826cde Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 24 Dec 2025 17:37:25 -0500 Subject: [PATCH 2169/2435] ZJIT: Don't mark control-flow opcodes as invalidating locals (#15694) jump, branchif, etc don't invalidate locals in the JIT; they might in the interpreter because they can execute arbitrary code, but the JIT side exits before that happens. --- zjit/src/hir.rs | 16 ++++++++++- zjit/src/hir/opt_tests.rs | 30 ++++++++++----------- zjit/src/hir/tests.rs | 56 ++++++++++++++++----------------------- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2f8f21225524d5..3f1ed83e4bd9b3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5299,6 +5299,20 @@ impl ProfileOracle { } } +fn invalidates_locals(opcode: u32, operands: *const VALUE) -> bool { + match opcode { + // Control-flow is non-leaf in the interpreter because it can execute arbitrary code on + // interrupt. But in the JIT, we side-exit if there is a pending interrupt. + YARVINSN_jump + | YARVINSN_branchunless + | YARVINSN_branchif + | YARVINSN_branchnil + | YARVINSN_leave => false, + // TODO(max): Read the invokebuiltin target from operands and determine if it's leaf + _ => unsafe { !rb_zjit_insn_leaf(opcode as i32, operands) } + } +} + /// The index of the self parameter in the HIR function pub const SELF_PARAM_IDX: usize = 0; @@ -5434,7 +5448,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } // Flag a future getlocal/setlocal to add a patch point if this instruction is not leaf. - if unsafe { !rb_zjit_insn_leaf(opcode as i32, pc.offset(1)) } { + if invalidates_locals(opcode, unsafe { pc.offset(1) }) { local_inval = true; } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 166d1d4754591e..2a70314fbb7f38 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -9638,26 +9638,24 @@ mod hir_opt_tests { Jump bb2(v8, v9, v10, v11, v12) bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:NilClass): CheckInterrupts - v27:BasicObject = GetLocal :a, l0, EP@6 - SetLocal :formatted, l0, EP@3, v27 - v39:BasicObject = GetLocal :formatted, l0, EP@3 + SetLocal :formatted, l0, EP@3, v15 PatchPoint SingleRactorMode - v56:HeapBasicObject = GuardType v14, HeapBasicObject - v57:HeapBasicObject = GuardShape v56, 0x1000 - StoreField v57, :@formatted@0x1001, v39 - WriteBarrier v57, v39 - v60:CShape[0x1002] = Const CShape(0x1002) - StoreField v57, :_shape_id@0x1003, v60 - v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) + v54:HeapBasicObject = GuardType v14, HeapBasicObject + v55:HeapBasicObject = GuardShape v54, 0x1000 + StoreField v55, :@formatted@0x1001, v15 + WriteBarrier v55, v15 + v58:CShape[0x1002] = Const CShape(0x1002) + StoreField v55, :_shape_id@0x1003, v58 + v43:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) - v65:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 - v48:BasicObject = GetLocal :a, l0, EP@6 - v49:BasicObject = GetLocal :_b, l0, EP@5 - v50:BasicObject = GetLocal :_c, l0, EP@4 - v51:BasicObject = GetLocal :formatted, l0, EP@3 + v63:BasicObject = CCallWithFrame v43, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 + v46:BasicObject = GetLocal :a, l0, EP@6 + v47:BasicObject = GetLocal :_b, l0, EP@5 + v48:BasicObject = GetLocal :_c, l0, EP@4 + v49:BasicObject = GetLocal :formatted, l0, EP@3 CheckInterrupts - Return v65 + Return v63 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index f67874fad7a832..9810e48145c16d 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1069,17 +1069,14 @@ pub mod hir_build_tests { v18:CBool = Test v11 IfFalse v18, bb3(v10, v11, v12) v22:Fixnum[3] = Const Value(3) - PatchPoint NoEPEscape(test) CheckInterrupts Jump bb4(v10, v11, v22) - bb3(v29:BasicObject, v30:BasicObject, v31:NilClass): - v35:Fixnum[4] = Const Value(4) - PatchPoint NoEPEscape(test) - Jump bb4(v29, v30, v35) - bb4(v40:BasicObject, v41:BasicObject, v42:Fixnum): - PatchPoint NoEPEscape(test) + bb3(v27:BasicObject, v28:BasicObject, v29:NilClass): + v33:Fixnum[4] = Const Value(4) + Jump bb4(v27, v28, v33) + bb4(v36:BasicObject, v37:BasicObject, v38:Fixnum): CheckInterrupts - Return v42 + Return v38 "); } @@ -1366,23 +1363,20 @@ pub mod hir_build_tests { CheckInterrupts Jump bb4(v10, v16, v20) bb4(v26:BasicObject, v27:BasicObject, v28:BasicObject): - PatchPoint NoEPEscape(test) - v34:Fixnum[0] = Const Value(0) - v37:BasicObject = SendWithoutBlock v28, :>, v34 # SendFallbackReason: Uncategorized(opt_gt) + v32:Fixnum[0] = Const Value(0) + v35:BasicObject = SendWithoutBlock v28, :>, v32 # SendFallbackReason: Uncategorized(opt_gt) CheckInterrupts - v40:CBool = Test v37 - IfTrue v40, bb3(v26, v27, v28) - v43:NilClass = Const Value(nil) - PatchPoint NoEPEscape(test) + v38:CBool = Test v35 + IfTrue v38, bb3(v26, v27, v28) + v41:NilClass = Const Value(nil) CheckInterrupts Return v27 - bb3(v53:BasicObject, v54:BasicObject, v55:BasicObject): - PatchPoint NoEPEscape(test) - v62:Fixnum[1] = Const Value(1) - v65:BasicObject = SendWithoutBlock v54, :+, v62 # SendFallbackReason: Uncategorized(opt_plus) - v70:Fixnum[1] = Const Value(1) - v73:BasicObject = SendWithoutBlock v55, :-, v70 # SendFallbackReason: Uncategorized(opt_minus) - Jump bb4(v53, v65, v73) + bb3(v49:BasicObject, v50:BasicObject, v51:BasicObject): + v56:Fixnum[1] = Const Value(1) + v59:BasicObject = SendWithoutBlock v50, :+, v56 # SendFallbackReason: Uncategorized(opt_plus) + v64:Fixnum[1] = Const Value(1) + v67:BasicObject = SendWithoutBlock v51, :-, v64 # SendFallbackReason: Uncategorized(opt_minus) + Jump bb4(v49, v59, v67) "); } @@ -3064,15 +3058,13 @@ pub mod hir_build_tests { CheckInterrupts v35:CBool[true] = Test v32 IfFalse v35, bb3(v16, v17, v18, v19, v20, v25) - PatchPoint NoEPEscape(open) - v42:BasicObject = InvokeBlock, v25 # SendFallbackReason: Uncategorized(invokeblock) - v45:BasicObject = InvokeBuiltin dir_s_close, v16, v25 + v40:BasicObject = InvokeBlock, v25 # SendFallbackReason: Uncategorized(invokeblock) + v43:BasicObject = InvokeBuiltin dir_s_close, v16, v25 CheckInterrupts - Return v42 - bb3(v51, v52, v53, v54, v55, v56): - PatchPoint NoEPEscape(open) + Return v40 + bb3(v49, v50, v51, v52, v53, v54): CheckInterrupts - Return v56 + Return v54 "); } @@ -3548,12 +3540,10 @@ pub mod hir_build_tests { v22:Fixnum[1] = Const Value(1) v24:Fixnum[1] = Const Value(1) v27:BasicObject = SendWithoutBlock v22, :+, v24 # SendFallbackReason: Uncategorized(opt_plus) - PatchPoint NoEPEscape(test) Jump bb3(v10, v27, v12) - bb3(v32:BasicObject, v33:BasicObject, v34:BasicObject): - PatchPoint NoEPEscape(test) + bb3(v30:BasicObject, v31:BasicObject, v32:BasicObject): CheckInterrupts - Return v33 + Return v31 "); } From 3c4cda10eb80fa38f2b27d4774e3e3e82bf190c9 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 24 Dec 2025 16:06:50 -0800 Subject: [PATCH 2170/2435] [DOC] Add back Rust 1.85.0 requirement to NEWS.md (#15728) * [DOC] Add back Rust 1.85.0 requirement to NEWS.md Addresses k0kubun's review in https://github.com/ruby/ruby/pull/15711#issuecomment-3690541074 NEWS.md serves both CRuby developers as well as end-users. As the release date closes in, it probably gets seen by more users than core developers (on the blog for example). Most users probably don't build Ruby by hand, and instead that is abstracted through tools like ruby-install or a package manager. For some users these tools may install pre-built binaries where they exist, in which case the Rust requirement doesn't apply. In other instances, the tools merely automate the build, in which case the correct rustc version is required to enable support. It is also a little confusing to talk about "enabling support for the JIT during the build" vs "enabling the JIT at runtime". This copy attempts to balance all of the above and hopefully gets the correct points across all intended audiences. * Apply suggestion from k0kubun Co-authored-by: Takashi Kokubun --------- Co-authored-by: Takashi Kokubun --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 877b849ab148ae..991f34965fe1fc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -570,7 +570,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * ZJIT * Introduce an [experimental method-based JIT compiler](https://docs.ruby-lang.org/en/master/jit/zjit_md.html). - To enable ZJIT on supported platforms, supply the `--zjit` option or call `RubyVM::ZJIT.enable` at runtime. + Where available, ZJIT can be enabled at runtime with the `--zjit` option or by calling `RubyVM::ZJIT.enable`. + When building Ruby, Rust 1.85.0 or later is required to include ZJIT support. * As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT. We encourage experimentation with ZJIT, but advise against deploying it in production for now. * Our goal is to make ZJIT faster than YJIT and production-ready in Ruby 4.1. From 84edb8456e90ae79428bf5e5db3accf29cdc9999 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Dec 2025 11:47:08 +0900 Subject: [PATCH 2171/2435] Revert "Rollback to minitest-5.27.0" This reverts commit 8d097bc472fc66221dee23bb8f9e0dddac16db23. --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 991f34965fe1fc..5d932fbf5d34f0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -373,7 +373,7 @@ The following default gems are updated. The following bundled gems are updated. -* minitest 5.27.0 +* minitest 6.0.0 * power_assert 3.0.1 * 2.0.5 to [v3.0.0][power_assert-v3.0.0], [v3.0.1][power_assert-v3.0.1] * rake 13.3.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 82dffd091e9edb..9461122e62dd73 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.27.0 https://github.com/minitest/minitest +minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.5 https://github.com/test-unit/test-unit From b908306f99db834470167d569b81661d7b19a02b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Dec 2025 11:45:27 +0900 Subject: [PATCH 2172/2435] [DOC] Reword "Regular Expression" to "Matched Data" `$~` and its accessors are related to regular expressions, but are not themselves. --- doc/language/globals.md | 4 ++-- doc/string/partition.rdoc | 4 ++-- doc/string/rpartition.rdoc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index 905a23ed05dbe4..dc891ff28addd1 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -19,7 +19,7 @@ require 'English' | `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | | `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | -### Regular Expression +### Matched Data | Variable | \English | Contains | Initially | Read-Only | Reset By | |:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| @@ -127,7 +127,7 @@ Output: English - `$ERROR_POSITION`. -## Regular Expression +## Matched Data These global variables store information about the most recent successful match in the current scope. diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index 86c3a9ca0a975a..d822f8ec0e0738 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -17,7 +17,7 @@ Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, performs the equivalent of self.match(pattern) -(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): +(also setting {matched-data variables}[rdoc-ref:language/globals.md@Matched+Data]): 'hello'.partition(/h/) # => ["", "h", "ello"] 'hello'.partition(/l/) # => ["he", "l", "lo"] @@ -30,7 +30,7 @@ If +pattern+ is a Regexp, performs the equivalent of self.match(pattern)self.index(pattern) -(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): +(and does _not_ set {matched-data global variables}[rdoc-ref:language/globals.md@Matched+Data]): 'hello'.partition('h') # => ["", "h", "ello"] 'hello'.partition('l') # => ["he", "l", "lo"] diff --git a/doc/string/rpartition.rdoc b/doc/string/rpartition.rdoc index 879b6ee2864295..6a17b5e944bffc 100644 --- a/doc/string/rpartition.rdoc +++ b/doc/string/rpartition.rdoc @@ -23,7 +23,7 @@ The pattern used is: Note that in the examples below, a returned string 'hello' is a copy of +self+, not +self+. If +pattern+ is a Regexp, searches for the last matching substring -(also setting {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): +(also setting {matched-data global variables}[rdoc-ref:language/globals.md@Matched+Data]): 'hello'.rpartition(/l/) # => ["hel", "l", "o"] 'hello'.rpartition(/ll/) # => ["he", "ll", "o"] @@ -36,7 +36,7 @@ If +pattern+ is a Regexp, searches for the last matching substring If +pattern+ is not a Regexp, converts it to a string (if it is not already one), then searches for the last matching substring -(and does _not_ set {pattern-matching global variables}[rdoc-ref:language/globals.md@Regular+Expression]): +(and does _not_ set {matched-data global variables}[rdoc-ref:language/globals.md@Matched+Data]): 'hello'.rpartition('l') # => ["hel", "l", "o"] 'hello'.rpartition('ll') # => ["he", "ll", "o"] From d375bcc965d4a0c661785fa94150f6202e99c1ce Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Dec 2025 11:56:41 +0900 Subject: [PATCH 2173/2435] [DOC] Escape capitalized word "data" not to be linked unexpectedly --- doc/language/globals.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/language/globals.md b/doc/language/globals.md index dc891ff28addd1..83a024b141865f 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -19,7 +19,7 @@ require 'English' | `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | | `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | -### Matched Data +### Matched \Data | Variable | \English | Contains | Initially | Read-Only | Reset By | |:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| @@ -127,7 +127,7 @@ Output: English - `$ERROR_POSITION`. -## Matched Data +## Matched \Data These global variables store information about the most recent successful match in the current scope. From 6a66129d6c289b0da99cd89592f5ee948da6f381 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Thu, 25 Dec 2025 15:31:50 +0900 Subject: [PATCH 2174/2435] fix the condition of www repo --- tool/releng/gen-mail.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/releng/gen-mail.rb b/tool/releng/gen-mail.rb index 6dc0e4cec1bf7d..17fa499d698fcd 100755 --- a/tool/releng/gen-mail.rb +++ b/tool/releng/gen-mail.rb @@ -10,7 +10,7 @@ # Confirm current directory is www.ruby-lang.org's working directory def confirm_w_r_l_o_wd File.foreach('.git/config') do |line| - return true if line.include?('git@github.com:ruby/www.ruby-lang.org.git') + return true if line.include?('ruby/www.ruby-lang.org.git') end abort "Run this script in www.ruby-lang.org's working directory" end From 099da884fe95ccf6c684a1563ed1c4b0fd8e1196 Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Thu, 25 Dec 2025 21:35:15 +0900 Subject: [PATCH 2175/2435] test_box: avoid failure with --program-suffix (#15734) --- test/ruby/test_box.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index a531afa679689e..28c9dd02b03d09 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true require 'test/unit' +require 'rbconfig' class TestBox < Test::Unit::TestCase EXPERIMENTAL_WARNING_LINE_PATTERNS = [ - /ruby(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/, + /#{RbConfig::CONFIG["ruby_install_name"] || "ruby"}(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/, %r{See https://docs.ruby-lang.org/en/(master|\d\.\d)/Ruby/Box.html for known issues, etc.} ] ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} From 16626d500da3f8a0536ed86813000cbcfc8acc79 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Jan 2025 11:38:01 -0500 Subject: [PATCH 2176/2435] Implement rb_darray_swap_remove --- darray.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/darray.h b/darray.h index c9035b74b642cd..dc9d282be28e7f 100644 --- a/darray.h +++ b/darray.h @@ -72,6 +72,20 @@ (*(ptr_to_ary))->meta.size++; \ } while (0) +/* Removes the element at idx and replaces it with the last element. + * ptr_to_ary and idx is evaluated multiple times. + * Warning: not bounds checked. + * + * void rb_darray_swap_remove(rb_darray(T) *ptr_to_ary, size_t idx); + */ +#define rb_darray_swap_remove(ptr_to_ary, idx) do { \ + size_t _darray_size = rb_darray_size(*(ptr_to_ary)); \ + if ((idx) != _darray_size - 1) { \ + (*(ptr_to_ary))->data[idx] = (*(ptr_to_ary))->data[_darray_size - 1]; \ + } \ + (*(ptr_to_ary))->meta.size--; \ +} while (0) + // Iterate over items of the array in a for loop // #define rb_darray_foreach(ary, idx_name, elem_ptr_var) \ From 10b97f52fd2cba189b8952fcb02c4d6dd9b1b6f2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Jan 2025 11:42:00 -0500 Subject: [PATCH 2177/2435] Implement declaring weak references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Feature #21084] # Summary The current way of marking weak references uses `rb_gc_mark_weak(VALUE *ptr)`. This presents challenges because Ruby's GC is incremental, meaning that if the `ptr` changes (e.g. realloc'd or free'd), then we could have an invalid memory access. This also overwrites `*ptr = Qundef` if `*ptr` is dead, which prevents any cleanup to be run (e.g. freeing memory or deleting entries from hash tables). This ticket proposes `rb_gc_declare_weak_references` which declares that an object has weak references and calls a cleanup function after marking, allowing the object to clean up any memory for dead objects. # Introduction In [[Feature #19783]](https://bugs.ruby-lang.org/issues/19783), I introduced an API allowing objects to mark weak references, the function signature looks like this: ```c void rb_gc_mark_weak(VALUE *ptr); ``` `rb_gc_mark_weak` is called during the marking phase of the GC to specify that the memory at `ptr` holds a pointer to a Ruby object that is weakly referenced. `rb_gc_mark_weak` appends this pointer to a list that is processed after the marking phase of the GC. If the object at `*ptr` is no longer alive, then it overwrites the object reference with a special value (`*ptr = Qundef`). However, this API resulted in two challenges: 1. Ruby's default GC is incremental, which means that the GC is not ran in one phase, but rather split into chunks of work that interleaves with Ruby execution. The `ptr` passed into `rb_gc_mark_weak` could be on the malloc heap, and that memory could be realloc'd or even free'd. We had to use workarounds such as `rb_gc_remove_weak` to ensure that there were no illegal memory accesses. This made `rb_gc_mark_weak` difficult to use, impacted runtime performance, and increased memory usage. 2. When an object dies, `rb_gc_mark_weak` only overwites the reference with `Qundef`. This means that if we want to do any cleanup (e.g. free a piece of memory or delete a hash table entry), we could not do that and had to defer this process elsewhere (e.g. during marking or runtime). In this ticket, I'm proposing a new API for weak references. Instead of an object marking its weak references during the marking phase, the object declares that it has weak references using the `rb_gc_declare_weak_references` function. This declaration occurs during runtime (e.g. after the object has been created) rather than during GC. After an object declares that it has weak references, it will have its callback function called after marking as long as that object is alive. This callback function can then call a special function `rb_gc_handle_weak_references_alive_p` to determine whether its references are alive. This will allow the callback function to do whatever it wants on the object, allowing it to perform any cleanup work it needs. This significantly simplifies the code for `ObjectSpace::WeakMap` and `ObjectSpace::WeakKeyMap` because it no longer needs to have the workarounds for the limitations of `rb_gc_mark_weak`. # Performance The performance results below demonstrate that `ObjectSpace::WeakMap#[]=` is now about 60% faster because the implementation has been simplified and the number of allocations has been reduced. We can see that there is not a significant impact on the performance of `ObjectSpace::WeakMap#[]`. Base: ``` ObjectSpace::WeakMap#[]= 4.620M (± 6.4%) i/s (216.44 ns/i) - 23.342M in 5.072149s ObjectSpace::WeakMap#[] 30.967M (± 1.9%) i/s (32.29 ns/i) - 154.998M in 5.007157s ``` Branch: ``` ObjectSpace::WeakMap#[]= 7.336M (± 2.8%) i/s (136.31 ns/i) - 36.755M in 5.013983s ObjectSpace::WeakMap#[] 30.902M (± 5.4%) i/s (32.36 ns/i) - 155.901M in 5.064060s ``` Code: ``` require "bundler/inline" gemfile do source "https://rubygems.org" gem "benchmark-ips" end wmap = ObjectSpace::WeakMap.new key = Object.new val = Object.new wmap[key] = val Benchmark.ips do |x| x.report("ObjectSpace::WeakMap#[]=") do |times| i = 0 while i < times wmap[Object.new] = Object.new i += 1 end end x.report("ObjectSpace::WeakMap#[]") do |times| i = 0 while i < times wmap[key] wmap[val] # does not exist i += 1 end end end ``` # Alternative designs Currently, `rb_gc_declare_weak_references` is designed to be an internal-only API. This allows us to assume the object types that call `rb_gc_declare_weak_references`. In the future, if we want to open up this API to third parties, we may want to change this function to something like: ```c void rb_gc_add_cleaner(VALUE obj, void (*callback)(VALUE obj)); ``` This will allow the third party to implement a custom `callback` that gets called after the marking phase of GC to clean up any dead references. I chose not to implement this design because it is less efficient as we would need to store a mapping from `obj` to `callback`, which requires extra memory. --- gc.c | 84 ++++++++++++++++--------- gc/default/default.c | 105 +++++++++++--------------------- gc/gc.h | 1 + gc/gc_impl.h | 5 +- include/ruby/internal/fl_type.h | 4 +- internal/gc.h | 4 +- test/ruby/test_gc.rb | 33 +++------- yjit/src/cruby_bindings.inc.rs | 2 +- 8 files changed, 109 insertions(+), 129 deletions(-) diff --git a/gc.c b/gc.c index 8e239011e6f295..850f2df7670b7b 100644 --- a/gc.c +++ b/gc.c @@ -637,8 +637,9 @@ typedef struct gc_function_map { void (*mark_and_move)(void *objspace_ptr, VALUE *ptr); void (*mark_and_pin)(void *objspace_ptr, VALUE obj); void (*mark_maybe)(void *objspace_ptr, VALUE obj); - void (*mark_weak)(void *objspace_ptr, VALUE *ptr); - void (*remove_weak)(void *objspace_ptr, VALUE parent_obj, VALUE *ptr); + // Weak references + void (*declare_weak_references)(void *objspace_ptr, VALUE obj); + bool (*handle_weak_references_alive_p)(void *objspace_ptr, VALUE obj); // Compaction bool (*object_moved_p)(void *objspace_ptr, VALUE obj); VALUE (*location)(void *objspace_ptr, VALUE value); @@ -811,8 +812,9 @@ ruby_modular_gc_init(void) load_modular_gc_func(mark_and_move); load_modular_gc_func(mark_and_pin); load_modular_gc_func(mark_maybe); - load_modular_gc_func(mark_weak); - load_modular_gc_func(remove_weak); + // Weak references + load_modular_gc_func(declare_weak_references); + load_modular_gc_func(handle_weak_references_alive_p); // Compaction load_modular_gc_func(object_moved_p); load_modular_gc_func(location); @@ -891,8 +893,9 @@ ruby_modular_gc_init(void) # define rb_gc_impl_mark_and_move rb_gc_functions.mark_and_move # define rb_gc_impl_mark_and_pin rb_gc_functions.mark_and_pin # define rb_gc_impl_mark_maybe rb_gc_functions.mark_maybe -# define rb_gc_impl_mark_weak rb_gc_functions.mark_weak -# define rb_gc_impl_remove_weak rb_gc_functions.remove_weak +// Weak references +# define rb_gc_impl_declare_weak_references rb_gc_functions.declare_weak_references +# define rb_gc_impl_handle_weak_references_alive_p rb_gc_functions.handle_weak_references_alive_p // Compaction # define rb_gc_impl_object_moved_p rb_gc_functions.object_moved_p # define rb_gc_impl_location rb_gc_functions.location @@ -1165,6 +1168,52 @@ rb_objspace_data_type_name(VALUE obj) } } +void +rb_gc_declare_weak_references(VALUE obj) +{ + rb_gc_impl_declare_weak_references(rb_gc_get_objspace(), obj); +} + +bool +rb_gc_handle_weak_references_alive_p(VALUE obj) +{ + if (SPECIAL_CONST_P(obj)) return true; + + return rb_gc_impl_handle_weak_references_alive_p(rb_gc_get_objspace(), obj); +} + +extern const rb_data_type_t weakmap_type; +void rb_wmap_handle_weak_references(VALUE obj); +extern const rb_data_type_t weakkeymap_type; +void rb_wkmap_handle_weak_references(VALUE obj); + +void +rb_gc_handle_weak_references(VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_DATA: + if (RTYPEDDATA_P(obj)) { + const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); + + if (type == &weakmap_type) { + rb_wmap_handle_weak_references(obj); + } + else if (type == &weakkeymap_type) { + rb_wkmap_handle_weak_references(obj); + } + else { + rb_bug("rb_gc_handle_weak_references: unknown TypedData %s", RTYPEDDATA_TYPE(obj)->wrap_struct_name); + } + } + else { + rb_bug("rb_gc_handle_weak_references: unknown T_DATA"); + } + break; + default: + rb_bug("rb_gc_handle_weak_references: type not supported\n"); + } +} + static void io_fptr_finalize(void *fptr) { @@ -2649,29 +2698,6 @@ rb_gc_mark_maybe(VALUE obj) gc_mark_maybe_internal(obj); } -void -rb_gc_mark_weak(VALUE *ptr) -{ - if (RB_SPECIAL_CONST_P(*ptr)) return; - - rb_vm_t *vm = GET_VM(); - void *objspace = vm->gc.objspace; - if (LIKELY(vm->gc.mark_func_data == NULL)) { - GC_ASSERT(rb_gc_impl_during_gc_p(objspace)); - - rb_gc_impl_mark_weak(objspace, ptr); - } - else { - GC_ASSERT(!rb_gc_impl_during_gc_p(objspace)); - } -} - -void -rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr) -{ - rb_gc_impl_remove_weak(rb_gc_get_objspace(), parent_obj, ptr); -} - ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(static void each_location(register const VALUE *x, register long n, void (*cb)(VALUE, void *), void *data)); static void each_location(register const VALUE *x, register long n, void (*cb)(VALUE, void *), void *data) diff --git a/gc/default/default.c b/gc/default/default.c index ef100d7dea27dc..0e92c35598f09f 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -589,7 +589,6 @@ typedef struct rb_objspace { /* Weak references */ size_t weak_references_count; - size_t retained_weak_references_count; } profile; VALUE gc_stress_mode; @@ -635,7 +634,7 @@ typedef struct rb_objspace { VALUE stress_to_class; #endif - rb_darray(VALUE *) weak_references; + rb_darray(VALUE) weak_references; rb_postponed_job_handle_t finalize_deferred_pjob; unsigned long live_ractor_cache_count; @@ -4414,6 +4413,10 @@ gc_grey(rb_objspace_t *objspace, VALUE obj) MARK_IN_BITMAP(GET_HEAP_MARKING_BITS(obj), obj); } + if (RB_FL_TEST_RAW(obj, RUBY_FL_WEAK_REFERENCE)) { + rb_darray_append_without_gc(&objspace->weak_references, obj); + } + push_mark_stack(&objspace->mark_stack, obj); } @@ -4532,53 +4535,6 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) } } -void -rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr) -{ - rb_objspace_t *objspace = objspace_ptr; - - VALUE obj = *ptr; - - gc_mark_check_t_none(objspace, obj); - - /* If we are in a minor GC and the other object is old, then obj should - * already be marked and cannot be reclaimed in this GC cycle so we don't - * need to add it to the weak references list. */ - if (!is_full_marking(objspace) && RVALUE_OLD_P(objspace, obj)) { - GC_ASSERT(RVALUE_MARKED(objspace, obj)); - GC_ASSERT(!objspace->flags.during_compacting); - - return; - } - - rgengc_check_relation(objspace, obj); - - rb_darray_append_without_gc(&objspace->weak_references, ptr); - - objspace->profile.weak_references_count++; -} - -void -rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr) -{ - rb_objspace_t *objspace = objspace_ptr; - - /* If we're not incremental marking, then the state of the objects can't - * change so we don't need to do anything. */ - if (!is_incremental_marking(objspace)) return; - /* If parent_obj has not been marked, then ptr has not yet been marked - * weak, so we don't need to do anything. */ - if (!RVALUE_MARKED(objspace, parent_obj)) return; - - VALUE **ptr_ptr; - rb_darray_foreach(objspace->weak_references, i, ptr_ptr) { - if (*ptr_ptr == ptr) { - *ptr_ptr = NULL; - break; - } - } -} - static int pin_value(st_data_t key, st_data_t value, st_data_t data) { @@ -5362,30 +5318,40 @@ gc_marks_wb_unprotected_objects(rb_objspace_t *objspace, rb_heap_t *heap) gc_mark_stacked_objects_all(objspace); } -static void -gc_update_weak_references(rb_objspace_t *objspace) +void +rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj) { - size_t retained_weak_references_count = 0; - VALUE **ptr_ptr; - rb_darray_foreach(objspace->weak_references, i, ptr_ptr) { - if (!*ptr_ptr) continue; + FL_SET_RAW(obj, RUBY_FL_WEAK_REFERENCE); +} - VALUE obj = **ptr_ptr; +bool +rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj) +{ + rb_objspace_t *objspace = objspace_ptr; - if (RB_SPECIAL_CONST_P(obj)) continue; + return RVALUE_MARKED(objspace, obj); +} - if (!RVALUE_MARKED(objspace, obj)) { - **ptr_ptr = Qundef; - } - else { - retained_weak_references_count++; - } +static void +gc_update_weak_references(rb_objspace_t *objspace) +{ + VALUE *obj_ptr; + rb_darray_foreach(objspace->weak_references, i, obj_ptr) { + rb_gc_handle_weak_references(*obj_ptr); } - objspace->profile.retained_weak_references_count = retained_weak_references_count; + size_t capa = rb_darray_capa(objspace->weak_references); + size_t size = rb_darray_size(objspace->weak_references); + + objspace->profile.weak_references_count = size; rb_darray_clear(objspace->weak_references); - rb_darray_resize_capa_without_gc(&objspace->weak_references, retained_weak_references_count); + + /* If the darray has capacity for more than four times the amount used, we + * shrink it down to half of that capacity. */ + if (capa > size * 4) { + rb_darray_resize_capa_without_gc(&objspace->weak_references, size * 2); + } } static void @@ -5942,6 +5908,10 @@ rgengc_rememberset_mark_plane(rb_objspace_t *objspace, uintptr_t p, bits_t bitse GC_ASSERT(RVALUE_OLD_P(objspace, obj) || RVALUE_WB_UNPROTECTED(objspace, obj)); gc_mark_children(objspace, obj); + + if (RB_FL_TEST_RAW(obj, RUBY_FL_WEAK_REFERENCE)) { + rb_darray_append_without_gc(&objspace->weak_references, obj); + } } p += BASE_SLOT_SIZE; bitset >>= 1; @@ -6503,7 +6473,6 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) objspace->profile.total_allocated_objects_at_gc_start = total_allocated_objects(objspace); objspace->profile.heap_used_at_gc_start = rb_darray_size(objspace->heap_pages.sorted); objspace->profile.weak_references_count = 0; - objspace->profile.retained_weak_references_count = 0; gc_prof_setup_new_record(objspace, reason); gc_reset_malloc_info(objspace, do_full_mark); @@ -7323,7 +7292,7 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned #endif static VALUE sym_newobj, sym_malloc, sym_method, sym_capi; static VALUE sym_none, sym_marking, sym_sweeping; - static VALUE sym_weak_references_count, sym_retained_weak_references_count; + static VALUE sym_weak_references_count; VALUE hash = Qnil, key = Qnil; VALUE major_by, need_major_by; unsigned int flags = orig_flags ? orig_flags : objspace->profile.latest_gc_info; @@ -7365,7 +7334,6 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned S(sweeping); S(weak_references_count); - S(retained_weak_references_count); #undef S } @@ -7418,7 +7386,6 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned } SET(weak_references_count, LONG2FIX(objspace->profile.weak_references_count)); - SET(retained_weak_references_count, LONG2FIX(objspace->profile.retained_weak_references_count)); #undef SET if (!NIL_P(key)) { diff --git a/gc/gc.h b/gc/gc.h index f1cf8038e0fe51..69dacf488f7db1 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -99,6 +99,7 @@ MODULAR_GC_FN void rb_gc_before_updating_jit_code(void); MODULAR_GC_FN void rb_gc_after_updating_jit_code(void); MODULAR_GC_FN bool rb_gc_obj_shareable_p(VALUE); MODULAR_GC_FN void rb_gc_rp(VALUE); +MODULAR_GC_FN void rb_gc_handle_weak_references(VALUE obj); #if USE_MODULAR_GC MODULAR_GC_FN bool rb_gc_event_hook_required_p(rb_event_flag_t event); diff --git a/gc/gc_impl.h b/gc/gc_impl.h index 3250483639e775..bb97fa4c09a4be 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -82,8 +82,9 @@ GC_IMPL_FN void rb_gc_impl_mark(void *objspace_ptr, VALUE obj); GC_IMPL_FN void rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr); GC_IMPL_FN void rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj); GC_IMPL_FN void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj); -GC_IMPL_FN void rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr); -GC_IMPL_FN void rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr); +// Weak references +GC_IMPL_FN void rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj); +GC_IMPL_FN bool rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj); // Compaction GC_IMPL_FN bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj); GC_IMPL_FN VALUE rb_gc_impl_location(void *objspace_ptr, VALUE value); diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 9bbcb9d2b82433..456ec77b87490a 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -295,11 +295,11 @@ ruby_fl_type { = 0, /** - * This flag is no longer in use + * This object weakly refers to other objects. * * @internal */ - RUBY_FL_UNUSED9 = (1<<9), + RUBY_FL_WEAK_REFERENCE = (1<<9), /** * This flag is no longer in use diff --git a/internal/gc.h b/internal/gc.h index ea001449a0030c..8fff2e83361192 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -214,8 +214,8 @@ size_t rb_gc_heap_id_for_size(size_t size); void rb_gc_mark_and_move(VALUE *ptr); -void rb_gc_mark_weak(VALUE *ptr); -void rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr); +void rb_gc_declare_weak_references(VALUE obj); +bool rb_gc_handle_weak_references_alive_p(VALUE obj); void rb_gc_ref_update_table_values_only(st_table *tbl); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 6639013a54ca32..594e2b8aa8a4ca 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -382,51 +382,36 @@ def test_latest_gc_info_need_major_by def test_latest_gc_info_weak_references_count assert_separately([], __FILE__, __LINE__, <<~RUBY) GC.disable - count = 10_000 + COUNT = 10_000 # Some weak references may be created, so allow some margin of error error_tolerance = 100 - # Run full GC to clear out weak references - GC.start - # Run full GC again to collect stats about weak references + # Run full GC to collect stats about weak references GC.start before_weak_references_count = GC.latest_gc_info(:weak_references_count) - before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count) - # Create some objects and place it in a WeakMap - wmap = ObjectSpace::WeakMap.new - ary = Array.new(count) do |i| - obj = Object.new - wmap[obj] = nil - obj + # Create some WeakMaps + ary = Array.new(COUNT) + COUNT.times.with_index do |i| + ary[i] = ObjectSpace::WeakMap.new end # Run full GC to collect stats about weak references GC.start - assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + count - error_tolerance) - assert_operator(GC.latest_gc_info(:retained_weak_references_count), :>=, before_retained_weak_references_count + count - error_tolerance) - assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count)) + assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + COUNT - error_tolerance) before_weak_references_count = GC.latest_gc_info(:weak_references_count) - before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count) # Clear ary, so if ary itself is somewhere on the stack, it won't hold all references ary.clear ary = nil - # Free ary, which should empty out the wmap + # Free ary, which should GC all the WeakMaps GC.start - # Run full GC again to collect stats about weak references - GC.start - - # Sometimes the WeakMap has a few elements, which might be held on by registers. - assert_operator(wmap.size, :<=, count / 1000) - assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance) - assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance) - assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count)) + assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - COUNT + error_tolerance) RUBY end diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 952cf88c205115..c081bc9e22abab 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -229,7 +229,7 @@ pub const RUBY_FL_TAINT: ruby_fl_type = 0; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; -pub const RUBY_FL_UNUSED9: ruby_fl_type = 512; +pub const RUBY_FL_WEAK_REFERENCE: ruby_fl_type = 512; pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; pub const RUBY_FL_USER0: ruby_fl_type = 4096; From b2feb09efeb931ce0647c6d237256451ee3b6dfa Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Jan 2025 11:45:25 -0500 Subject: [PATCH 2178/2435] Implement WeakMap and WeakKeyMap using declare weak references --- gc.c | 8 +- weakmap.c | 485 +++++++++++++++++------------------------------------- 2 files changed, 156 insertions(+), 337 deletions(-) diff --git a/gc.c b/gc.c index 850f2df7670b7b..ba358a656e8949 100644 --- a/gc.c +++ b/gc.c @@ -1182,9 +1182,9 @@ rb_gc_handle_weak_references_alive_p(VALUE obj) return rb_gc_impl_handle_weak_references_alive_p(rb_gc_get_objspace(), obj); } -extern const rb_data_type_t weakmap_type; +extern const rb_data_type_t rb_weakmap_type; void rb_wmap_handle_weak_references(VALUE obj); -extern const rb_data_type_t weakkeymap_type; +extern const rb_data_type_t rb_weakkeymap_type; void rb_wkmap_handle_weak_references(VALUE obj); void @@ -1195,10 +1195,10 @@ rb_gc_handle_weak_references(VALUE obj) if (RTYPEDDATA_P(obj)) { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); - if (type == &weakmap_type) { + if (type == &rb_weakmap_type) { rb_wmap_handle_weak_references(obj); } - else if (type == &weakkeymap_type) { + else if (type == &rb_weakkeymap_type) { rb_wkmap_handle_weak_references(obj); } else { diff --git a/weakmap.c b/weakmap.c index ecdd219f62a21e..7b6f27ce2bbbed 100644 --- a/weakmap.c +++ b/weakmap.c @@ -34,102 +34,11 @@ struct weakmap_entry { VALUE val; }; -static bool -wmap_live_p(VALUE obj) -{ - return !UNDEF_P(obj); -} - -struct wmap_foreach_data { - int (*func)(struct weakmap_entry *, st_data_t); - st_data_t arg; - - struct weakmap_entry *dead_entry; -}; - -static int -wmap_foreach_i(st_data_t key, st_data_t val, st_data_t arg) -{ - struct wmap_foreach_data *data = (struct wmap_foreach_data *)arg; - - if (data->dead_entry != NULL) { - ruby_sized_xfree(data->dead_entry, sizeof(struct weakmap_entry)); - data->dead_entry = NULL; - } - - struct weakmap_entry *entry = (struct weakmap_entry *)key; - RUBY_ASSERT(&entry->val == (VALUE *)val); - - if (wmap_live_p(entry->key) && wmap_live_p(entry->val)) { - VALUE k = entry->key; - VALUE v = entry->val; - - int ret = data->func(entry, data->arg); - - RB_GC_GUARD(k); - RB_GC_GUARD(v); - - return ret; - } - else { - /* We cannot free the weakmap_entry here because the ST_DELETE could - * hash the key which would read the weakmap_entry and would cause a - * use-after-free. Instead, we store this entry and free it on the next - * iteration. */ - data->dead_entry = entry; - - return ST_DELETE; - } -} - -static void -wmap_foreach(struct weakmap *w, int (*func)(struct weakmap_entry *, st_data_t), st_data_t arg) -{ - struct wmap_foreach_data foreach_data = { - .func = func, - .arg = arg, - .dead_entry = NULL, - }; - - st_foreach(w->table, wmap_foreach_i, (st_data_t)&foreach_data); - - ruby_sized_xfree(foreach_data.dead_entry, sizeof(struct weakmap_entry)); -} - -static int -wmap_mark_weak_table_i(struct weakmap_entry *entry, st_data_t _) -{ - rb_gc_mark_weak(&entry->key); - rb_gc_mark_weak(&entry->val); - - return ST_CONTINUE; -} - -static void -wmap_mark(void *ptr) -{ - struct weakmap *w = ptr; - if (w->table) { - wmap_foreach(w, wmap_mark_weak_table_i, (st_data_t)0); - } -} - -static int -wmap_free_table_i(st_data_t key, st_data_t val, st_data_t arg) -{ - struct weakmap_entry *entry = (struct weakmap_entry *)key; - RUBY_ASSERT(&entry->val == (VALUE *)val); - ruby_sized_xfree(entry, sizeof(struct weakmap_entry)); - - return ST_CONTINUE; -} - static void wmap_free(void *ptr) { struct weakmap *w = ptr; - st_foreach(w->table, wmap_free_table_i, 0); st_free_table(w->table); } @@ -154,40 +63,37 @@ struct wmap_compact_table_data { }; static int -wmap_compact_table_i(st_data_t key, st_data_t val, st_data_t d) +wmap_compact_table_each_i(st_data_t k, st_data_t v, st_data_t d, int error) { - struct wmap_compact_table_data *data = (struct wmap_compact_table_data *)d; - if (data->dead_entry != NULL) { - ruby_sized_xfree(data->dead_entry, sizeof(struct weakmap_entry)); - data->dead_entry = NULL; - } - - struct weakmap_entry *entry = (struct weakmap_entry *)key; + st_table *table = (st_table *)d; - entry->val = rb_gc_location(entry->val); + VALUE key = (VALUE)k; + VALUE val = (VALUE)v; - VALUE new_key = rb_gc_location(entry->key); + VALUE moved_key = rb_gc_location(key); + VALUE moved_val = rb_gc_location(val); /* If the key object moves, then we must reinsert because the hash is * based on the pointer rather than the object itself. */ - if (entry->key != new_key) { - DURING_GC_COULD_MALLOC_REGION_START(); - { - struct weakmap_entry *new_entry = xmalloc(sizeof(struct weakmap_entry)); - new_entry->key = new_key; - new_entry->val = entry->val; - st_insert(data->table, (st_data_t)&new_entry->key, (st_data_t)&new_entry->val); - } - DURING_GC_COULD_MALLOC_REGION_END(); - - /* We cannot free the weakmap_entry here because the ST_DELETE could - * hash the key which would read the weakmap_entry and would cause a - * use-after-free. Instead, we store this entry and free it on the next - * iteration. */ - data->dead_entry = entry; + if (key != moved_key) { + st_insert(table, (st_data_t)moved_key, (st_data_t)moved_val); return ST_DELETE; } + else if (val != moved_val) { + return ST_REPLACE; + } + else { + return ST_CONTINUE; + } +} + +static int +wmap_compact_table_replace_i(st_data_t *k, st_data_t *v, st_data_t d, int existing) +{ + RUBY_ASSERT((VALUE)*k == rb_gc_location((VALUE)*k)); + + *v = (st_data_t)rb_gc_location((VALUE)*v); return ST_CONTINUE; } @@ -198,21 +104,18 @@ wmap_compact(void *ptr) struct weakmap *w = ptr; if (w->table) { - struct wmap_compact_table_data compact_data = { - .table = w->table, - .dead_entry = NULL, - }; - - st_foreach(w->table, wmap_compact_table_i, (st_data_t)&compact_data); - - ruby_sized_xfree(compact_data.dead_entry, sizeof(struct weakmap_entry)); + DURING_GC_COULD_MALLOC_REGION_START(); + { + st_foreach_with_replace(w->table, wmap_compact_table_each_i, wmap_compact_table_replace_i, (st_data_t)w->table); + } + DURING_GC_COULD_MALLOC_REGION_END(); } } -static const rb_data_type_t weakmap_type = { +const rb_data_type_t rb_weakmap_type = { "weakmap", { - wmap_mark, + NULL, wmap_free, wmap_memsize, wmap_compact, @@ -223,21 +126,13 @@ static const rb_data_type_t weakmap_type = { static int wmap_cmp(st_data_t x, st_data_t y) { - VALUE x_obj = *(VALUE *)x; - VALUE y_obj = *(VALUE *)y; - - if (!wmap_live_p(x_obj) && !wmap_live_p(y_obj)) { - return x != y; - } - else { - return x_obj != y_obj; - } + return x != y; } static st_index_t wmap_hash(st_data_t n) { - return st_numhash(*(VALUE *)n); + return st_numhash(n); } static const struct st_hash_type wmap_hash_type = { @@ -245,12 +140,37 @@ static const struct st_hash_type wmap_hash_type = { wmap_hash, }; +static int +rb_wmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (rb_gc_handle_weak_references_alive_p(key) && + rb_gc_handle_weak_references_alive_p(val)) { + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +void +rb_wmap_handle_weak_references(VALUE self) +{ + struct weakmap *w; + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); + + st_foreach(w->table, rb_wmap_handle_weak_references_i, (st_data_t)0); +} + static VALUE wmap_allocate(VALUE klass) { struct weakmap *w; - VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w); + VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &rb_weakmap_type, w); + w->table = st_init_table(&wmap_hash_type); + + rb_gc_declare_weak_references(obj); + return obj; } @@ -266,8 +186,10 @@ wmap_inspect_append(VALUE str, VALUE obj) } static int -wmap_inspect_i(struct weakmap_entry *entry, st_data_t data) +wmap_inspect_i(st_data_t k, st_data_t v, st_data_t data) { + VALUE key = (VALUE)k; + VALUE val = (VALUE)v; VALUE str = (VALUE)data; if (RSTRING_PTR(str)[0] == '#') { @@ -278,9 +200,9 @@ wmap_inspect_i(struct weakmap_entry *entry, st_data_t data) RSTRING_PTR(str)[0] = '#'; } - wmap_inspect_append(str, entry->key); + wmap_inspect_append(str, key); rb_str_cat2(str, " => "); - wmap_inspect_append(str, entry->val); + wmap_inspect_append(str, val); return ST_CONTINUE; } @@ -290,11 +212,11 @@ wmap_inspect(VALUE self) { VALUE c = rb_class_name(CLASS_OF(self)); struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self); - wmap_foreach(w, wmap_inspect_i, (st_data_t)str); + st_foreach(w->table, wmap_inspect_i, (st_data_t)str); RSTRING_PTR(str)[0] = '#'; rb_str_cat2(str, ">"); @@ -303,9 +225,9 @@ wmap_inspect(VALUE self) } static int -wmap_each_i(struct weakmap_entry *entry, st_data_t _) +wmap_each_i(st_data_t k, st_data_t v, st_data_t _) { - rb_yield_values(2, entry->key, entry->val); + rb_yield_values(2, (VALUE)k, (VALUE)v); return ST_CONTINUE; } @@ -322,17 +244,17 @@ static VALUE wmap_each(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - wmap_foreach(w, wmap_each_i, (st_data_t)0); + st_foreach(w->table, wmap_each_i, (st_data_t)0); return self; } static int -wmap_each_key_i(struct weakmap_entry *entry, st_data_t _data) +wmap_each_key_i(st_data_t k, st_data_t _v, st_data_t _data) { - rb_yield(entry->key); + rb_yield((VALUE)k); return ST_CONTINUE; } @@ -349,17 +271,17 @@ static VALUE wmap_each_key(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - wmap_foreach(w, wmap_each_key_i, (st_data_t)0); + st_foreach(w->table, wmap_each_key_i, (st_data_t)0); return self; } static int -wmap_each_value_i(struct weakmap_entry *entry, st_data_t _data) +wmap_each_value_i(st_data_t k, st_data_t v, st_data_t _data) { - rb_yield(entry->val); + rb_yield((VALUE)v); return ST_CONTINUE; } @@ -376,19 +298,19 @@ static VALUE wmap_each_value(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - wmap_foreach(w, wmap_each_value_i, (st_data_t)0); + st_foreach(w->table, wmap_each_value_i, (st_data_t)0); return self; } static int -wmap_keys_i(struct weakmap_entry *entry, st_data_t arg) +wmap_keys_i(st_data_t k, st_data_t v, st_data_t data) { - VALUE ary = (VALUE)arg; + VALUE ary = (VALUE)data; - rb_ary_push(ary, entry->key); + rb_ary_push(ary, (VALUE)k); return ST_CONTINUE; } @@ -404,20 +326,20 @@ static VALUE wmap_keys(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); VALUE ary = rb_ary_new(); - wmap_foreach(w, wmap_keys_i, (st_data_t)ary); + st_foreach(w->table, wmap_keys_i, (st_data_t)ary); return ary; } static int -wmap_values_i(struct weakmap_entry *entry, st_data_t arg) +wmap_values_i(st_data_t k, st_data_t v, st_data_t data) { - VALUE ary = (VALUE)arg; + VALUE ary = (VALUE)data; - rb_ary_push(ary, entry->val); + rb_ary_push(ary, (VALUE)v); return ST_CONTINUE; } @@ -433,36 +355,14 @@ static VALUE wmap_values(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); VALUE ary = rb_ary_new(); - wmap_foreach(w, wmap_values_i, (st_data_t)ary); + st_foreach(w->table, wmap_values_i, (st_data_t)ary); return ary; } -static int -wmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t new_key_ptr, int existing) -{ - VALUE new_key = *(VALUE *)new_key_ptr; - VALUE new_val = *(((VALUE *)new_key_ptr) + 1); - - if (existing) { - RUBY_ASSERT(*(VALUE *)*key == new_key); - } - else { - struct weakmap_entry *entry = xmalloc(sizeof(struct weakmap_entry)); - - *key = (st_data_t)&entry->key; - *val = (st_data_t)&entry->val; - } - - *(VALUE *)*key = new_key; - *(VALUE *)*val = new_val; - - return ST_CONTINUE; -} - /* * call-seq: * map[key] = value -> value @@ -476,11 +376,9 @@ static VALUE wmap_aset(VALUE self, VALUE key, VALUE val) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - - VALUE pair[2] = { key, val }; + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - st_update(w->table, (st_data_t)pair, wmap_aset_replace, (st_data_t)pair); + st_insert(w->table, (st_data_t)key, (st_data_t)val); RB_OBJ_WRITTEN(self, Qundef, key); RB_OBJ_WRITTEN(self, Qundef, val); @@ -492,17 +390,13 @@ wmap_aset(VALUE self, VALUE key, VALUE val) static VALUE wmap_lookup(VALUE self, VALUE key) { - RUBY_ASSERT(wmap_live_p(key)); - struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); st_data_t data; - if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef; + if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef; - if (!wmap_live_p(*(VALUE *)data)) return Qundef; - - return *(VALUE *)data; + return (VALUE)data; } /* @@ -552,23 +446,12 @@ static VALUE wmap_delete(VALUE self, VALUE key) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - - VALUE orig_key = key; - st_data_t orig_key_data = (st_data_t)&orig_key; - st_data_t orig_val_data; - if (st_delete(w->table, &orig_key_data, &orig_val_data)) { - VALUE orig_val = *(VALUE *)orig_val_data; - - rb_gc_remove_weak(self, (VALUE *)orig_key_data); - rb_gc_remove_weak(self, (VALUE *)orig_val_data); - - struct weakmap_entry *entry = (struct weakmap_entry *)orig_key_data; - ruby_sized_xfree(entry, sizeof(struct weakmap_entry)); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - if (wmap_live_p(orig_val)) { - return orig_val; - } + st_data_t orig_key = (st_data_t)key; + st_data_t orig_val; + if (st_delete(w->table, &orig_key, &orig_val)) { + return (VALUE)orig_val; } if (rb_block_given_p()) { @@ -601,7 +484,7 @@ static VALUE wmap_size(VALUE self) { struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); st_index_t n = st_table_size(w->table); @@ -635,25 +518,11 @@ struct weakkeymap { }; static int -wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t data) +wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t _data) { - VALUE **dead_entry = (VALUE **)data; - ruby_sized_xfree(*dead_entry, sizeof(VALUE)); - *dead_entry = NULL; - - VALUE *key_ptr = (VALUE *)key; - - if (wmap_live_p(*key_ptr)) { - rb_gc_mark_weak(key_ptr); - rb_gc_mark_movable((VALUE)val_obj); + rb_gc_mark_movable((VALUE)val_obj); - return ST_CONTINUE; - } - else { - *dead_entry = key_ptr; - - return ST_DELETE; - } + return ST_CONTINUE; } static void @@ -661,27 +530,15 @@ wkmap_mark(void *ptr) { struct weakkeymap *w = ptr; if (w->table) { - VALUE *dead_entry = NULL; - st_foreach(w->table, wkmap_mark_table_i, (st_data_t)&dead_entry); - if (dead_entry != NULL) { - ruby_sized_xfree(dead_entry, sizeof(VALUE)); - } + st_foreach(w->table, wkmap_mark_table_i, (st_data_t)0); } } -static int -wkmap_free_table_i(st_data_t key, st_data_t _val, st_data_t _arg) -{ - ruby_sized_xfree((VALUE *)key, sizeof(VALUE)); - return ST_CONTINUE; -} - static void wkmap_free(void *ptr) { struct weakkeymap *w = ptr; - st_foreach(w->table, wkmap_free_table_i, 0); st_free_table(w->table); } @@ -701,26 +558,13 @@ wkmap_memsize(const void *ptr) } static int -wkmap_compact_table_i(st_data_t key, st_data_t val_obj, st_data_t data, int _error) +wkmap_compact_table_i(st_data_t key, st_data_t val, st_data_t _data, int _error) { - VALUE **dead_entry = (VALUE **)data; - ruby_sized_xfree(*dead_entry, sizeof(VALUE)); - *dead_entry = NULL; - - VALUE *key_ptr = (VALUE *)key; - - if (wmap_live_p(*key_ptr)) { - if (*key_ptr != rb_gc_location(*key_ptr) || val_obj != rb_gc_location(val_obj)) { - return ST_REPLACE; - } - - return ST_CONTINUE; + if ((VALUE)key != rb_gc_location((VALUE)key) || (VALUE)val != rb_gc_location((VALUE)val)) { + return ST_REPLACE; } - else { - *dead_entry = key_ptr; - return ST_DELETE; - } + return ST_CONTINUE; } static int @@ -728,7 +572,7 @@ wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _d { RUBY_ASSERT(existing); - *(VALUE *)*key_ptr = rb_gc_location(*(VALUE *)*key_ptr); + *key_ptr = (st_data_t)rb_gc_location((VALUE)*key_ptr); *val_ptr = (st_data_t)rb_gc_location((VALUE)*val_ptr); return ST_CONTINUE; @@ -740,15 +584,11 @@ wkmap_compact(void *ptr) struct weakkeymap *w = ptr; if (w->table) { - VALUE *dead_entry = NULL; - st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)&dead_entry); - if (dead_entry != NULL) { - ruby_sized_xfree(dead_entry, sizeof(VALUE)); - } + st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)0); } } -static const rb_data_type_t weakkeymap_type = { +const rb_data_type_t rb_weakkeymap_type = { "weakkeymap", { wkmap_mark, @@ -762,23 +602,16 @@ static const rb_data_type_t weakkeymap_type = { static int wkmap_cmp(st_data_t x, st_data_t y) { - VALUE x_obj = *(VALUE *)x; - VALUE y_obj = *(VALUE *)y; + VALUE x_obj = (VALUE)x; + VALUE y_obj = (VALUE)y; - if (wmap_live_p(x_obj) && wmap_live_p(y_obj)) { - return rb_any_cmp(x_obj, y_obj); - } - else { - /* If one of the objects is dead, then they cannot be the same. */ - return 1; - } + return rb_any_cmp(x_obj, y_obj); } static st_index_t wkmap_hash(st_data_t n) { - VALUE obj = *(VALUE *)n; - RUBY_ASSERT(wmap_live_p(obj)); + VALUE obj = (VALUE)n; return rb_any_hash(obj); } @@ -788,12 +621,37 @@ static const struct st_hash_type wkmap_hash_type = { wkmap_hash, }; +static int +rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (rb_gc_handle_weak_references_alive_p(key)) { + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +void +rb_wkmap_handle_weak_references(VALUE self) +{ + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); + + st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0); +} + static VALUE wkmap_allocate(VALUE klass) { struct weakkeymap *w; - VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &weakkeymap_type, w); + + VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &rb_weakkeymap_type, w); + w->table = st_init_table(&wkmap_hash_type); + + rb_gc_declare_weak_references(obj); + return obj; } @@ -801,10 +659,10 @@ static VALUE wkmap_lookup(VALUE self, VALUE key) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); st_data_t data; - if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef; + if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef; return (VALUE)data; } @@ -829,21 +687,6 @@ struct wkmap_aset_args { VALUE new_val; }; -static int -wkmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t data_args, int existing) -{ - struct wkmap_aset_args *args = (struct wkmap_aset_args *)data_args; - - if (!existing) { - *key = (st_data_t)xmalloc(sizeof(VALUE)); - } - - *(VALUE *)*key = args->new_key; - *val = (st_data_t)args->new_val; - - return ST_CONTINUE; -} - /* * call-seq: * map[key] = value -> value @@ -860,19 +703,14 @@ static VALUE wkmap_aset(VALUE self, VALUE key, VALUE val) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) { rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable"); UNREACHABLE_RETURN(Qnil); } - struct wkmap_aset_args args = { - .new_key = key, - .new_val = val, - }; - - st_update(w->table, (st_data_t)&key, wkmap_aset_replace, (st_data_t)&args); + st_insert(w->table, (st_data_t)key, (st_data_t)val); RB_OBJ_WRITTEN(self, Qundef, key); RB_OBJ_WRITTEN(self, Qundef, val); @@ -913,19 +751,12 @@ static VALUE wkmap_delete(VALUE self, VALUE key) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - VALUE orig_key = key; - st_data_t orig_key_data = (st_data_t)&orig_key; - st_data_t orig_val_data; - if (st_delete(w->table, &orig_key_data, &orig_val_data)) { - VALUE orig_val = (VALUE)orig_val_data; - - rb_gc_remove_weak(self, (VALUE *)orig_key_data); - - ruby_sized_xfree((VALUE *)orig_key_data, sizeof(VALUE)); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); - return orig_val; + st_data_t orig_key = (st_data_t)key; + st_data_t orig_val; + if (st_delete(w->table, &orig_key, &orig_val)) { + return (VALUE)orig_val; } if (rb_block_given_p()) { @@ -959,12 +790,12 @@ static VALUE wkmap_getkey(VALUE self, VALUE key) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); st_data_t orig_key; - if (!st_get_key(w->table, (st_data_t)&key, &orig_key)) return Qnil; + if (!st_get_key(w->table, (st_data_t)key, &orig_key)) return Qnil; - return *(VALUE *)orig_key; + return (VALUE)orig_key; } /* @@ -979,17 +810,6 @@ wkmap_has_key(VALUE self, VALUE key) return RBOOL(!UNDEF_P(wkmap_lookup(self, key))); } -static int -wkmap_clear_i(st_data_t key, st_data_t val, st_data_t data) -{ - VALUE self = (VALUE)data; - - /* This WeakKeyMap may have already been marked, so we need to remove the - * keys to prevent a use-after-free. */ - rb_gc_remove_weak(self, (VALUE *)key); - return wkmap_free_table_i(key, val, 0); -} - /* * call-seq: * map.clear -> self @@ -1000,9 +820,8 @@ static VALUE wkmap_clear(VALUE self) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); - st_foreach(w->table, wkmap_clear_i, (st_data_t)self); st_clear(w->table); return self; @@ -1023,7 +842,7 @@ static VALUE wkmap_inspect(VALUE self) { struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); st_index_t n = st_table_size(w->table); From a7ef2a9b0cedead5971ab91a22b2947a31555cca Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 21 Jan 2025 11:45:45 -0500 Subject: [PATCH 2179/2435] Implement declarative weak references in MMTk --- gc/mmtk/mmtk.c | 21 +++++++++++++++------ gc/mmtk/mmtk.h | 8 ++++---- gc/mmtk/src/abi.rs | 1 + gc/mmtk/src/api.rs | 12 +++++------- gc/mmtk/src/binding.rs | 5 ----- gc/mmtk/src/weak_proc.rs | 32 ++++++++++---------------------- 6 files changed, 35 insertions(+), 44 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 99c0125d97818d..634abc068a44f5 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -258,6 +258,12 @@ rb_mmtk_call_gc_mark_children(MMTk_ObjectReference object) rb_gc_mark_children(rb_gc_get_objspace(), (VALUE)object); } +static void +rb_mmtk_handle_weak_references(MMTk_ObjectReference object) +{ + rb_gc_handle_weak_references((VALUE)object); +} + static void rb_mmtk_call_obj_free(MMTk_ObjectReference object) { @@ -376,6 +382,7 @@ MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_scan_objspace, rb_mmtk_scan_object_ruby_style, rb_mmtk_call_gc_mark_children, + rb_mmtk_handle_weak_references, rb_mmtk_call_obj_free, rb_mmtk_vm_live_bytes, rb_mmtk_update_global_tables, @@ -406,7 +413,7 @@ void * rb_gc_impl_objspace_alloc(void) { MMTk_Builder *builder = rb_mmtk_builder_init(); - mmtk_init_binding(builder, NULL, &ruby_upcalls, (MMTk_ObjectReference)Qundef); + mmtk_init_binding(builder, NULL, &ruby_upcalls); return calloc(1, sizeof(struct objspace)); } @@ -768,16 +775,18 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) } } +// Weak references + void -rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr) +rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj) { - mmtk_mark_weak((MMTk_ObjectReference *)ptr); + mmtk_declare_weak_references((MMTk_ObjectReference)obj); } -void -rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr) +bool +rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj) { - mmtk_remove_weak((MMTk_ObjectReference *)ptr); + return mmtk_weak_references_alive_p((MMTk_ObjectReference)obj); } // Compaction diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index 45521e2671b5cb..77e82cf06e5614 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -68,6 +68,7 @@ typedef struct MMTk_RubyUpcalls { void (*scan_objspace)(void); void (*scan_object_ruby_style)(MMTk_ObjectReference object); void (*call_gc_mark_children)(MMTk_ObjectReference object); + void (*handle_weak_references)(MMTk_ObjectReference object); void (*call_obj_free)(MMTk_ObjectReference object); size_t (*vm_live_bytes)(void); void (*update_global_tables)(int tbl_idx); @@ -91,8 +92,7 @@ MMTk_Builder *mmtk_builder_default(void); void mmtk_init_binding(MMTk_Builder *builder, const struct MMTk_RubyBindingOptions *_binding_options, - const struct MMTk_RubyUpcalls *upcalls, - MMTk_ObjectReference weak_reference_dead_value); + const struct MMTk_RubyUpcalls *upcalls); void mmtk_initialize_collection(MMTk_VMThread tls); @@ -121,9 +121,9 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, void mmtk_add_obj_free_candidate(MMTk_ObjectReference object); -void mmtk_mark_weak(MMTk_ObjectReference *ptr); +void mmtk_declare_weak_references(MMTk_ObjectReference object); -void mmtk_remove_weak(const MMTk_ObjectReference *ptr); +bool mmtk_weak_references_alive_p(MMTk_ObjectReference object); void mmtk_object_reference_write_post(MMTk_Mutator *mutator, MMTk_ObjectReference object); diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 1bd19fbe7efd1d..fed4d82f4e31e6 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -308,6 +308,7 @@ pub struct RubyUpcalls { pub scan_objspace: extern "C" fn(), pub scan_object_ruby_style: extern "C" fn(object: ObjectReference), pub call_gc_mark_children: extern "C" fn(object: ObjectReference), + pub handle_weak_references: extern "C" fn(object: ObjectReference), pub call_obj_free: extern "C" fn(object: ObjectReference), pub vm_live_bytes: extern "C" fn() -> usize, pub update_global_tables: extern "C" fn(tbl_idx: c_int), diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index d735a81cac810d..92146f87913c78 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -183,7 +183,6 @@ pub unsafe extern "C" fn mmtk_init_binding( builder: *mut MMTKBuilder, _binding_options: *const RubyBindingOptions, upcalls: *const RubyUpcalls, - weak_reference_dead_value: ObjectReference, ) { crate::MUTATOR_THREAD_PANIC_HANDLER .set((unsafe { (*upcalls).clone() }).mutator_thread_panic_handler) @@ -203,7 +202,6 @@ pub unsafe extern "C" fn mmtk_init_binding( mmtk_static, &binding_options, upcalls, - weak_reference_dead_value, ); crate::BINDING @@ -306,16 +304,16 @@ pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference) { binding().weak_proc.add_obj_free_candidate(object) } -// =============== Marking =============== +// =============== Weak references =============== #[no_mangle] -pub extern "C" fn mmtk_mark_weak(ptr: &'static mut ObjectReference) { - binding().weak_proc.add_weak_reference(ptr); +pub extern "C" fn mmtk_declare_weak_references(object: ObjectReference) { + binding().weak_proc.add_weak_reference(object); } #[no_mangle] -pub extern "C" fn mmtk_remove_weak(ptr: &ObjectReference) { - binding().weak_proc.remove_weak_reference(ptr); +pub extern "C" fn mmtk_weak_references_alive_p(object: ObjectReference) -> bool { + object.is_reachable() } // =============== Write barriers =============== diff --git a/gc/mmtk/src/binding.rs b/gc/mmtk/src/binding.rs index 811cbf8abf8b32..ddbaafb0767b1f 100644 --- a/gc/mmtk/src/binding.rs +++ b/gc/mmtk/src/binding.rs @@ -56,8 +56,6 @@ pub struct RubyBinding { pub weak_proc: WeakProcessor, pub gc_thread_join_handles: Mutex>>, pub wb_unprotected_objects: Mutex>, - - pub weak_reference_dead_value: ObjectReference, } unsafe impl Sync for RubyBinding {} @@ -68,7 +66,6 @@ impl RubyBinding { mmtk: &'static MMTK, binding_options: &RubyBindingOptions, upcalls: *const abi::RubyUpcalls, - weak_reference_dead_value: ObjectReference, ) -> Self { unsafe { crate::BINDING_FAST.suffix_size = binding_options.suffix_size; @@ -82,8 +79,6 @@ impl RubyBinding { weak_proc: WeakProcessor::new(), gc_thread_join_handles: Default::default(), wb_unprotected_objects: Default::default(), - - weak_reference_dead_value, } } diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 68d3dd3273a135..ce80d323495e73 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -13,7 +13,7 @@ pub struct WeakProcessor { /// If it is a bottleneck, replace it with a lock-free data structure, /// or add candidates in batch. obj_free_candidates: Mutex>, - weak_references: Mutex>, + weak_references: Mutex>, } impl Default for WeakProcessor { @@ -53,19 +53,9 @@ impl WeakProcessor { std::mem::take(obj_free_candidates.as_mut()) } - pub fn add_weak_reference(&self, ptr: &'static mut ObjectReference) { + pub fn add_weak_reference(&self, object: ObjectReference) { let mut weak_references = self.weak_references.lock().unwrap(); - weak_references.push(ptr); - } - - pub fn remove_weak_reference(&self, ptr: &ObjectReference) { - let mut weak_references = self.weak_references.lock().unwrap(); - for (i, curr_ptr) in weak_references.iter().enumerate() { - if *curr_ptr == ptr { - weak_references.swap_remove(i); - break; - } - } + weak_references.push(object); } pub fn process_weak_stuff( @@ -133,17 +123,15 @@ impl GCWork for ProcessWeakReferences { .try_lock() .expect("Mutators should not be holding the lock."); - for ptr_ptr in weak_references.iter_mut() { - if (upcalls().special_const_p)(**ptr_ptr) { - continue; - } + weak_references.retain(|&object| { + if object.is_reachable() { + (upcalls().handle_weak_references)(object); - if !(**ptr_ptr).is_reachable() { - **ptr_ptr = crate::binding().weak_reference_dead_value; + true + } else { + false } - } - - weak_references.clear(); + }); } } From ade779b1e1fbfa7a60da1a3834859665225e310e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 6 Dec 2025 19:26:48 -0500 Subject: [PATCH 2180/2435] Implement callcache using declare weak references --- gc.c | 11 +++++++++++ imemo.c | 1 - vm_callinfo.h | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index ba358a656e8949..47e6d9a6b69589 100644 --- a/gc.c +++ b/gc.c @@ -1209,6 +1209,17 @@ rb_gc_handle_weak_references(VALUE obj) rb_bug("rb_gc_handle_weak_references: unknown T_DATA"); } break; + + case T_IMEMO: { + GC_ASSERT(imemo_type(obj) == imemo_callcache); + + struct rb_callcache *cc = (struct rb_callcache *)obj; + if (!rb_gc_handle_weak_references_alive_p(cc->klass)) { + vm_cc_invalidate(cc); + } + + break; + } default: rb_bug("rb_gc_handle_weak_references: type not supported\n"); } diff --git a/imemo.c b/imemo.c index 42f6615a5e83be..2d63b7cd99549a 100644 --- a/imemo.c +++ b/imemo.c @@ -372,7 +372,6 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment)); - rb_gc_mark_weak((VALUE *)&cc->klass); if ((vm_cc_super_p(cc) || vm_cc_refinement_p(cc))) { rb_gc_mark_movable((VALUE)cc->cme_); } diff --git a/vm_callinfo.h b/vm_callinfo.h index 2c51bf9092046e..3df4b4eb0e9500 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -342,6 +342,8 @@ vm_cc_new(VALUE klass, { cc_check_class(klass); struct rb_callcache *cc = SHAREABLE_IMEMO_NEW(struct rb_callcache, imemo_callcache, klass); + rb_gc_declare_weak_references((VALUE)cc); + *((struct rb_callable_method_entry_struct **)&cc->cme_) = (struct rb_callable_method_entry_struct *)cme; *((vm_call_handler *)&cc->call_) = call; From 0c07a4246c732751b9cfe45a1a29834382a74311 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 8 Dec 2025 15:41:43 -0500 Subject: [PATCH 2181/2435] Implement weak references on gen fields cache --- cont.c | 28 +++++++++++++++++++++++----- gc.c | 8 +++++++- vm.c | 3 --- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/cont.c b/cont.c index b33c1462bf3ea1..59322224b8f0a3 100644 --- a/cont.c +++ b/cont.c @@ -48,7 +48,8 @@ static const int DEBUG = 0; #define RB_PAGE_MASK (~(RB_PAGE_SIZE - 1)) static long pagesize; -static const rb_data_type_t cont_data_type, fiber_data_type; +static const rb_data_type_t cont_data_type; +const rb_data_type_t rb_fiber_data_type; static VALUE rb_cContinuation; static VALUE rb_cFiber; static VALUE rb_eFiberError; @@ -992,7 +993,7 @@ fiber_ptr(VALUE obj) { rb_fiber_t *fiber; - TypedData_Get_Struct(obj, rb_fiber_t, &fiber_data_type, fiber); + TypedData_Get_Struct(obj, rb_fiber_t, &rb_fiber_data_type, fiber); if (!fiber) rb_raise(rb_eFiberError, "uninitialized fiber"); return fiber; @@ -1211,7 +1212,7 @@ fiber_memsize(const void *ptr) VALUE rb_obj_is_fiber(VALUE obj) { - return RBOOL(rb_typeddata_is_kind_of(obj, &fiber_data_type)); + return RBOOL(rb_typeddata_is_kind_of(obj, &rb_fiber_data_type)); } static void @@ -1983,16 +1984,33 @@ rb_cont_call(int argc, VALUE *argv, VALUE contval) * */ -static const rb_data_type_t fiber_data_type = { +const rb_data_type_t rb_fiber_data_type = { "fiber", {fiber_mark, fiber_free, fiber_memsize, fiber_compact,}, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; +void +rb_fiber_handle_weak_references(VALUE obj) +{ + rb_fiber_t *fiber; + TypedData_Get_Struct(obj, rb_fiber_t, &rb_fiber_data_type, fiber); + + if (!fiber) return; + + if (!rb_gc_handle_weak_references_alive_p(fiber->cont.saved_ec.gen_fields_cache.obj) || + !rb_gc_handle_weak_references_alive_p(fiber->cont.saved_ec.gen_fields_cache.fields_obj)) { + fiber->cont.saved_ec.gen_fields_cache.obj = Qundef; + fiber->cont.saved_ec.gen_fields_cache.fields_obj = Qundef; + } +} + static VALUE fiber_alloc(VALUE klass) { - return TypedData_Wrap_Struct(klass, &fiber_data_type, 0); + VALUE obj = TypedData_Wrap_Struct(klass, &rb_fiber_data_type, 0); + rb_gc_declare_weak_references(obj); + return obj; } static rb_serial_t diff --git a/gc.c b/gc.c index 47e6d9a6b69589..a8faa1d642b89a 100644 --- a/gc.c +++ b/gc.c @@ -1187,6 +1187,9 @@ void rb_wmap_handle_weak_references(VALUE obj); extern const rb_data_type_t rb_weakkeymap_type; void rb_wkmap_handle_weak_references(VALUE obj); +extern const rb_data_type_t rb_fiber_data_type; +void rb_fiber_handle_weak_references(VALUE obj); + void rb_gc_handle_weak_references(VALUE obj) { @@ -1195,7 +1198,10 @@ rb_gc_handle_weak_references(VALUE obj) if (RTYPEDDATA_P(obj)) { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); - if (type == &rb_weakmap_type) { + if (type == &rb_fiber_data_type) { + rb_fiber_handle_weak_references(obj); + } + else if (type == &rb_weakmap_type) { rb_wmap_handle_weak_references(obj); } else if (type == &rb_weakkeymap_type) { diff --git a/vm.c b/vm.c index f78a779c3f3655..0c3a2d01a515d4 100644 --- a/vm.c +++ b/vm.c @@ -3733,9 +3733,6 @@ rb_execution_context_mark(const rb_execution_context_t *ec) rb_gc_mark(ec->private_const_reference); rb_gc_mark_movable(ec->storage); - - rb_gc_mark_weak((VALUE *)&ec->gen_fields_cache.obj); - rb_gc_mark_weak((VALUE *)&ec->gen_fields_cache.fields_obj); } void rb_fiber_mark_self(rb_fiber_t *fib); From 57637827e6161fcf0a3d999c0f5dda3d8f06ea39 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 16 Dec 2025 17:48:27 -0500 Subject: [PATCH 2182/2435] Implement cont using declare weak references --- cont.c | 25 ++++++++++++++++++++----- gc.c | 6 ++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/cont.c b/cont.c index 59322224b8f0a3..dbac05b05a0d5c 100644 --- a/cont.c +++ b/cont.c @@ -48,7 +48,7 @@ static const int DEBUG = 0; #define RB_PAGE_MASK (~(RB_PAGE_SIZE - 1)) static long pagesize; -static const rb_data_type_t cont_data_type; +const rb_data_type_t rb_cont_data_type; const rb_data_type_t rb_fiber_data_type; static VALUE rb_cContinuation; static VALUE rb_cFiber; @@ -983,7 +983,7 @@ cont_ptr(VALUE obj) { rb_context_t *cont; - TypedData_Get_Struct(obj, rb_context_t, &cont_data_type, cont); + TypedData_Get_Struct(obj, rb_context_t, &rb_cont_data_type, cont); return cont; } @@ -1242,13 +1242,27 @@ cont_save_machine_stack(rb_thread_t *th, rb_context_t *cont) asan_unpoison_memory_region(cont->machine.stack_src, size, false); MEMCPY(cont->machine.stack, cont->machine.stack_src, VALUE, size); } - -static const rb_data_type_t cont_data_type = { +const rb_data_type_t rb_cont_data_type = { "continuation", {cont_mark, cont_free, cont_memsize, cont_compact}, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; +void +rb_cont_handle_weak_references(VALUE obj) +{ + rb_context_t *cont; + TypedData_Get_Struct(obj, rb_context_t, &rb_cont_data_type, cont); + + if (!cont) return; + + if (!rb_gc_handle_weak_references_alive_p(cont->saved_ec.gen_fields_cache.obj) || + !rb_gc_handle_weak_references_alive_p(cont->saved_ec.gen_fields_cache.fields_obj)) { + cont->saved_ec.gen_fields_cache.obj = Qundef; + cont->saved_ec.gen_fields_cache.fields_obj = Qundef; + } +} + static inline void cont_save_thread(rb_context_t *cont, rb_thread_t *th) { @@ -1405,7 +1419,8 @@ cont_new(VALUE klass) rb_thread_t *th = GET_THREAD(); THREAD_MUST_BE_RUNNING(th); - contval = TypedData_Make_Struct(klass, rb_context_t, &cont_data_type, cont); + contval = TypedData_Make_Struct(klass, rb_context_t, &rb_cont_data_type, cont); + rb_gc_declare_weak_references(contval); cont->self = contval; cont_init(cont, th); return cont; diff --git a/gc.c b/gc.c index a8faa1d642b89a..87db89d04956e6 100644 --- a/gc.c +++ b/gc.c @@ -1190,6 +1190,9 @@ void rb_wkmap_handle_weak_references(VALUE obj); extern const rb_data_type_t rb_fiber_data_type; void rb_fiber_handle_weak_references(VALUE obj); +extern const rb_data_type_t rb_cont_data_type; +void rb_cont_handle_weak_references(VALUE obj); + void rb_gc_handle_weak_references(VALUE obj) { @@ -1201,6 +1204,9 @@ rb_gc_handle_weak_references(VALUE obj) if (type == &rb_fiber_data_type) { rb_fiber_handle_weak_references(obj); } + else if (type == &rb_cont_data_type) { + rb_cont_handle_weak_references(obj); + } else if (type == &rb_weakmap_type) { rb_wmap_handle_weak_references(obj); } From 7eb088084a4d6b93de511b359ce457f3559fcec3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 18 Dec 2025 19:21:35 -0500 Subject: [PATCH 2183/2435] Update zjit bindings --- zjit/src/cruby_bindings.inc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2689ec30cf8b2d..8f5ea5b8a837cd 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -290,7 +290,7 @@ pub const RUBY_FL_TAINT: ruby_fl_type = 0; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; -pub const RUBY_FL_UNUSED9: ruby_fl_type = 512; +pub const RUBY_FL_WEAK_REFERENCE: ruby_fl_type = 512; pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; pub const RUBY_FL_USER0: ruby_fl_type = 4096; From 99e9ca1f40e7811fd4b162e6c4a7566d3d6bdf11 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 25 Dec 2025 21:14:34 +0900 Subject: [PATCH 2184/2435] [DOC] Flush NEWS.md --- NEWS.md => doc/NEWS/NEWS-4.0.0.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename NEWS.md => doc/NEWS/NEWS-4.0.0.md (100%) diff --git a/NEWS.md b/doc/NEWS/NEWS-4.0.0.md similarity index 100% rename from NEWS.md rename to doc/NEWS/NEWS-4.0.0.md From 010dcf85567ee1898706a4a8e353ac915a3d8873 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 25 Dec 2025 21:14:35 +0900 Subject: [PATCH 2185/2435] Development of 4.1.0 started. --- NEWS.md | 49 +++++++++++++++++++++++++++++++++++++ include/ruby/internal/abi.h | 2 +- include/ruby/version.h | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 NEWS.md diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 00000000000000..19824f0e0d94de --- /dev/null +++ b/NEWS.md @@ -0,0 +1,49 @@ +# NEWS for Ruby 4.1.0 + +This document is a list of user-visible feature changes +since the **4.0.0** release, except for bug fixes. + +Note that each entry is kept to a minimum, see links for details. + +## Language changes + +## Core classes updates + +Note: We're only listing outstanding class updates. + +## Stdlib updates + +We only list stdlib changes that are notable feature changes. + +Other changes are listed in the following sections. We also listed release +history from the previous bundled version that is Ruby 3.4.0 if it has GitHub +releases. + +The following bundled gems are promoted from default gems. + +The following default gem is added. + +The following default gems are updated. + +The following bundled gems are updated. + +### RubyGems and Bundler + +Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for details. + +## Supported platforms + +## Compatibility issues + +## Stdlib compatibility issues + +## C API updates + +## Implementation improvements + +### Ractor + +A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractor implementation closer to leaving experimental status. + +## JIT + diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index e6d1fa7e8f3770..e735a67564d885 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,7 +24,7 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 1 +#define RUBY_ABI_VERSION 0 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ diff --git a/include/ruby/version.h b/include/ruby/version.h index 24c846a1ca6ce2..5bb381cea204e1 100644 --- a/include/ruby/version.h +++ b/include/ruby/version.h @@ -67,7 +67,7 @@ * Minor version. As of writing this version changes annually. Greater * version doesn't mean "better"; they just mean years passed. */ -#define RUBY_API_VERSION_MINOR 0 +#define RUBY_API_VERSION_MINOR 1 /** * Teeny version. This digit is kind of reserved these days. Kept 0 for the From 2fa02d6d2e325e0c746f353e70403389072b5dee Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 09:06:05 +0900 Subject: [PATCH 2186/2435] Enabled auto-update NEWS.md and sync for default gems again --- .github/workflows/default_gems_list.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/default_gems_list.yml b/.github/workflows/default_gems_list.yml index ba6d6ee73c528d..1c7e2195c8cf4a 100644 --- a/.github/workflows/default_gems_list.yml +++ b/.github/workflows/default_gems_list.yml @@ -2,7 +2,7 @@ name: Update default gems list on: [push, pull_request, merge_group] env: - UPDATE_NEWS_ENABLED: false + UPDATE_NEWS_ENABLED: true concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index a108bf420e16cc..3947001ccdf71a 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -1,7 +1,7 @@ name: Sync default gems env: - DEFAULT_GEM_SYNC_ENABLED: false + DEFAULT_GEM_SYNC_ENABLED: true on: workflow_dispatch: From a06a59e3b34cd5227363dc3af14dc6d1ce93e665 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 09:06:11 +0900 Subject: [PATCH 2187/2435] Rename and enabled auto-update bundled gems again --- .github/workflows/bundled_gems.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index f12bc849b16551..59f64e8312e1aa 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -1,7 +1,7 @@ name: bundled_gems env: - UPDATE_NEWS_ENABLED: false + UPDATE_ENABLED: true on: push: @@ -59,7 +59,7 @@ jobs: id: bundled_gems run: | ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT - if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} + if: ${{ env.UPDATE_ENABLED == 'true' }} - name: Update spec/bundler/support/builders.rb run: | @@ -67,12 +67,12 @@ jobs: rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} shell: ruby -i~ {0} spec/bundler/support/builders.rb - if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} + if: ${{ env.UPDATE_ENABLED == 'true' }} - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled - if: ${{ env.UPDATE_NEWS_ENABLED == 'true' }} + if: ${{ env.UPDATE_ENABLED == 'true' }} - name: Check diffs id: diff From 715d69ac717d461ff3e6be53194f58aa4349c317 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 26 Dec 2025 00:07:09 +0000 Subject: [PATCH 2188/2435] Update default gems list at a06a59e3b34cd5227363dc3af14dc6 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 19824f0e0d94de..b9b5fb3ed247ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,7 @@ The following default gem is added. The following default gems are updated. + The following bundled gems are updated. ### RubyGems and Bundler From 290fa0d8b4e0145f389c6fdaff3ef45b915d706b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 25 Dec 2025 19:30:50 -0500 Subject: [PATCH 2189/2435] [ruby/mmtk] Fix cargo fmt https://github.com/ruby/mmtk/commit/f4c46cabc7 --- gc/mmtk/src/api.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 92146f87913c78..0449d3959d7c1e 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -198,11 +198,7 @@ pub unsafe extern "C" fn mmtk_init_binding( let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); - let binding = RubyBinding::new( - mmtk_static, - &binding_options, - upcalls, - ); + let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); crate::BINDING .set(binding) From 67f830e0925c328c19fc1c2f513a174c6e3ca63d Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Wed, 17 Dec 2025 14:18:43 +0900 Subject: [PATCH 2190/2435] [ruby/stringio] Development of 3.2.1 started. https://github.com/ruby/stringio/commit/c9cd1c9947 --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 05bae94529b9db..11b3fff39c672a 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -13,7 +13,7 @@ **********************************************************************/ static const char *const -STRINGIO_VERSION = "3.2.0"; +STRINGIO_VERSION = "3.2.1"; #include From 354dc574de421e6cbfb4404abc49a5be462042a9 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 22 Dec 2025 18:42:36 -0600 Subject: [PATCH 2191/2435] [ruby/stringio] [DOC] Doc for StringIO#pread (https://github.com/ruby/stringio/pull/195) Previous doc unhelpfully pointed to `IO#pread`; this PR documents locally, with StringIO examples. https://github.com/ruby/stringio/commit/806f3d9741 --- doc/stringio/pread.rdoc | 65 +++++++++++++++++++++++++++++++++++++++++ ext/stringio/stringio.c | 6 ++-- 2 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 doc/stringio/pread.rdoc diff --git a/doc/stringio/pread.rdoc b/doc/stringio/pread.rdoc new file mode 100644 index 00000000000000..2dcbc18ad81a81 --- /dev/null +++ b/doc/stringio/pread.rdoc @@ -0,0 +1,65 @@ +**Note**: \Method +pread+ is different from other reading methods +in that it does not modify +self+ in any way; +thus, multiple threads may read safely from the same stream. + +Reads up to +maxlen+ bytes from the stream, +beginning at 0-based byte offset +offset+; +returns a string containing the read bytes. + +The returned string: + +- Contains +maxlen+ bytes from the stream, if available; + otherwise contains all available bytes. +- Has encoding +Encoding::ASCII_8BIT+. + +With only arguments +maxlen+ and +offset+ given, +returns a new string: + + english = 'Hello' # Five 1-byte characters. + strio = StringIO.new(english) + strio.pread(3, 0) # => "Hel" + strio.pread(3, 2) # => "llo" + strio.pread(0, 0) # => "" + strio.pread(50, 0) # => "Hello" + strio.pread(50, 2) # => "llo" + strio.pread(50, 4) # => "o" + strio.pread(0, 0).encoding + # => # + + russian = 'Привет' # Six 2-byte characters. + strio = StringIO.new(russian) + strio.pread(50, 0) # All 12 bytes. + # => "\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82" + strio.pread(3, 0) # => "\xD0\x9F\xD1" + strio.pread(3, 3) # => "\x80\xD0\xB8" + strio.pread(0, 0).encoding + # => # + + japanese = 'こんにちは' # Five 3-byte characters. + strio = StringIO.new(japanese) + strio.pread(50, 0) # All 15 bytes. + # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF" + strio.pread(6, 0) # => "\xE3\x81\x93\xE3\x82\x93" + strio.pread(1, 2) # => "\x93" + strio.pread(0, 0).encoding + # => # + +Raises an exception if +offset+ is out-of-range: + + strio = StringIO.new(english) + strio.pread(5, 50) # Raises EOFError: end of file reached + +With string argument +out_string+ given: + +- Reads as above. +- Overwrites the content of +out_string+ with the read bytes. + +Examples: + + out_string = 'Will be overwritten' + out_string.encoding # => # + result = StringIO.new(english).pread(50, 0, out_string) + result.__id__ == out_string.__id__ # => true + out_string # => "Hello" + out_string.encoding # => # + diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 11b3fff39c672a..1772be5f521d77 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1723,10 +1723,10 @@ strio_read(int argc, VALUE *argv, VALUE self) /* * call-seq: - * pread(maxlen, offset) -> string - * pread(maxlen, offset, out_string) -> string + * pread(maxlen, offset, out_string = nil) -> new_string or out_string + * + * :include: stringio/pread.rdoc * - * See IO#pread. */ static VALUE strio_pread(int argc, VALUE *argv, VALUE self) From 9a76ccdbabbd7d2814a3106cc10d2740b6120ab9 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 22 Dec 2025 18:43:00 -0600 Subject: [PATCH 2192/2435] [ruby/stringio] [DOC] Doc for StringIO#putc (https://github.com/ruby/stringio/pull/196) Previous doc merely linked to `IO#putc`. The new doc stays local, provides examples using `StringIO` objects. https://github.com/ruby/stringio/commit/8983f32c50 --- doc/stringio/putc.rdoc | 82 +++++++++++++++++++++++++++++++++++++++++ ext/stringio/stringio.c | 5 ++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 doc/stringio/putc.rdoc diff --git a/doc/stringio/putc.rdoc b/doc/stringio/putc.rdoc new file mode 100644 index 00000000000000..4636ffa0db8b34 --- /dev/null +++ b/doc/stringio/putc.rdoc @@ -0,0 +1,82 @@ +Replaces one or more bytes at position +pos+ +with bytes of the given argument; +advances the position by the count of bytes written; +returns the argument. + +\StringIO object for 1-byte characters. + + strio = StringIO.new('foo') + strio.pos # => 0 + +With 1-byte argument, replaces one byte: + + strio.putc('b') + strio.string # => "boo" + strio.pos # => 1 + strio.putc('a') # => "a" + strio.string # => "bao" + strio.pos # => 2 + strio.putc('r') # => "r" + strio.string # => "bar" + strio.pos # => 3 + strio.putc('n') # => "n" + strio.string # => "barn" + strio.pos # => 4 + +Fills with null characters if necessary: + + strio.pos = 6 + strio.putc('x') # => "x" + strio.string # => "barn\u0000\u0000x" + strio.pos # => 7 + +With integer argument, replaces one byte with the low-order byte of the integer: + + strio = StringIO.new('foo') + strio.putc(70) + strio.string # => "Foo" + strio.putc(79) + strio.string # => "FOo" + strio.putc(79 + 1024) + strio.string # => "FOO" + +\StringIO object for Multi-byte characters: + + greek = 'αβγδε' # Five 2-byte characters. + strio = StringIO.new(greek) + strio.string# => "αβγδε" + strio.string.b # => "\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5" + strio.string.bytesize # => 10 + strio.string.chars # => ["α", "β", "γ", "δ", "ε"] + strio.string.size # => 5 + +With 1-byte argument, replaces one byte of the string: + + strio.putc(' ') # 1-byte ascii space. + strio.pos # => 1 + strio.string # => " \xB1βγδε" + strio.string.b # => " \xB1\xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5" + strio.string.bytesize # => 10 + strio.string.chars # => [" ", "\xB1", "β", "γ", "δ", "ε"] + strio.string.size # => 6 + + strio.putc(' ') + strio.pos # => 2 + strio.string # => " βγδε" + strio.string.b # => " \xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5" + strio.string.bytesize # => 10 + strio.string.chars # => [" ", " ", "β", "γ", "δ", "ε"] + strio.string.size # => 6 + +With 2-byte argument, replaces two bytes of the string: + + strio.rewind + strio.putc('α') + strio.pos # => 2 + strio.string # => "αβγδε" + strio.string.b # => "\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5" + strio.string.bytesize # => 10 + strio.string.chars # => ["α", "β", "γ", "δ", "ε"] + strio.string.size # => 5 + +Related: #getc, #ungetc. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 1772be5f521d77..fa2a36cd0e58a8 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1615,9 +1615,10 @@ strio_write(VALUE self, VALUE str) /* * call-seq: - * strio.putc(obj) -> obj + * putc(object) -> object + * + * :include: stringio/putc.rdoc * - * See IO#putc. */ static VALUE strio_putc(VALUE self, VALUE ch) From ae46f916f1e686b5f7cc80402f2e8b5f299abc3c Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 22 Dec 2025 18:43:58 -0600 Subject: [PATCH 2193/2435] [ruby/stringio] [DOC] Doc for StringIO#read (https://github.com/ruby/stringio/pull/197) Previous doc merely linked to `IO#read`; new doc stays local, shows examples using `StringIO`. https://github.com/ruby/stringio/commit/e8b66f8cdd --- doc/stringio/read.rdoc | 83 +++++++++++++++++++++++++++++++++++++++++ ext/stringio/stringio.c | 5 ++- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 doc/stringio/read.rdoc diff --git a/doc/stringio/read.rdoc b/doc/stringio/read.rdoc new file mode 100644 index 00000000000000..46b9fa349f284f --- /dev/null +++ b/doc/stringio/read.rdoc @@ -0,0 +1,83 @@ +Reads and returns a string containing bytes read from the stream, +beginning at the current position; +advances the position by the count of bytes read. + +With no arguments given, +reads all remaining bytes in the stream; +returns a new string containing bytes read: + + strio = StringIO.new('Hello') # Five 1-byte characters. + strio.read # => "Hello" + strio.pos # => 5 + strio.read # => "" + StringIO.new('').read # => "" + +With non-negative argument +maxlen+ given, +reads +maxlen+ bytes as available; +returns a new string containing the bytes read, or +nil+ if none: + + strio.rewind + strio.read(3) # => "Hel" + strio.read(3) # => "lo" + strio.read(3) # => nil + + russian = 'Привет' # Six 2-byte characters. + russian.b + # => "\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82" + strio = StringIO.new(russian) + strio.read(6) # => "\xD0\x9F\xD1\x80\xD0\xB8" + strio.read(6) # => "\xD0\xB2\xD0\xB5\xD1\x82" + strio.read(6) # => nil + + japanese = 'こんにちは' + japanese.b + # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF" + strio = StringIO.new(japanese) + strio.read(9) # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB" + strio.read(9) # => "\xE3\x81\xA1\xE3\x81\xAF" + strio.read(9) # => nil + +With argument +max_len+ as +nil+ and string argument +out_string+ given, +reads the remaining bytes in the stream; +clears +out_string+ and writes the bytes into it; +returns +out_string+: + + out_string = 'Will be overwritten' + strio = StringIO.new('Hello') + strio.read(nil, out_string) # => "Hello" + strio.read(nil, out_string) # => "" + +With non-negative argument +maxlen+ and string argument +out_string+ given, +reads the +maxlen bytes from the stream, as availble; +clears +out_string+ and writes the bytes into it; +returns +out_string+ if any bytes were read, or +nil+ if none: + + out_string = 'Will be overwritten' + strio = StringIO.new('Hello') + strio.read(3, out_string) # => "Hel" + strio.read(3, out_string) # => "lo" + strio.read(3, out_string) # => nil + + out_string = 'Will be overwritten' + strio = StringIO.new(russian) + strio.read(6, out_string) # => "При" + strio.read(6, out_string) # => "вет" + strio.read(6, out_string) # => nil + strio.rewind + russian.b + # => "\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82" + strio.read(3) # => "\xD0\x9F\xD1" + strio.read(3) # => "\x80\xD0\xB8" + + out_string = 'Will be overwritten' + strio = StringIO.new(japanese) + strio.read(9, out_string) # => "こんに" + strio.read(9, out_string) # => "ちは" + strio.read(9, out_string) # => nil + strio.rewind + japanese.b + # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF" + strio.read(4) # => "\xE3\x81\x93\xE3" + strio.read(4) # => "\x82\x93\xE3\x81" + +Related: #gets, #readlines. diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index fa2a36cd0e58a8..93a419ff3172fa 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1650,9 +1650,10 @@ strio_putc(VALUE self, VALUE ch) /* * call-seq: - * strio.read([length [, outbuf]]) -> string, outbuf, or nil + * read(maxlen = nil, out_string = nil) → new_string, out_string, or nil + * + * :include: stringio/read.rdoc * - * See IO#read. */ static VALUE strio_read(int argc, VALUE *argv, VALUE self) From f09e35ee4a5283c1b6185383f9b89eb4caf99868 Mon Sep 17 00:00:00 2001 From: Maciej Mensfeld Date: Thu, 18 Dec 2025 09:36:35 +0100 Subject: [PATCH 2194/2435] [ruby/date] [ruby/date] Optimize Gregorian date conversions with Neri-Schneider algorithm Replace floating-point arithmetic and iterative loops with pure integer operations for ~40% faster Date operations. Date.ordinal and Date.commercial are ~2x faster due to O(1) first-day-of-year calculation. Reference: https://arxiv.org/abs/2102.06959 https://github.com/ruby/date/commit/cc639549d6 --- ext/date/date_core.c | 229 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 194 insertions(+), 35 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 6bcf272b62d8b0..e19248717b2d46 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -7,6 +7,7 @@ #include "ruby/util.h" #include #include +#include /* For uint64_t in Neri-Schneider algorithm */ #if defined(HAVE_SYS_TIME_H) #include #endif @@ -452,11 +453,27 @@ do {\ static int c_valid_civil_p(int, int, int, double, int *, int *, int *, int *); +/* Forward declarations for Neri-Schneider optimized functions */ +static int c_gregorian_civil_to_jd(int y, int m, int d); +static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); +inline static int c_gregorian_fdoy(int y); +inline static int c_gregorian_ldoy(int y); +inline static int c_gregorian_ldom_jd(int y, int m); +inline static int ns_jd_in_range(int jd); + static int c_find_fdoy(int y, double sg, int *rjd, int *ns) { int d, rm, rd; + /* Fast path: pure Gregorian calendar */ + if (isinf(sg) && sg < 0) { + *rjd = c_gregorian_fdoy(y); + *ns = 1; + return 1; + } + + /* Keep existing loop for Julian/reform period */ for (d = 1; d < 31; d++) if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) return 1; @@ -468,6 +485,14 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) { int i, rm, rd; + /* Fast path: pure Gregorian calendar */ + if (isinf(sg) && sg < 0) { + *rjd = c_gregorian_ldoy(y); + *ns = 1; + return 1; + } + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -493,6 +518,14 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) { int i, rm, rd; + /* Fast path: pure Gregorian calendar */ + if (isinf(sg) && sg < 0) { + *rjd = c_gregorian_ldom_jd(y, m); + *ns = 1; + return 1; + } + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -502,55 +535,74 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) static void c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) { - double a, b, jd; + int jd; - if (m <= 2) { - y -= 1; - m += 12; + /* Fast path: pure Gregorian calendar (sg == -infinity) */ + if (isinf(sg) && sg < 0) { + *rjd = c_gregorian_civil_to_jd(y, m, d); + *ns = 1; + return; } - a = floor(y / 100.0); - b = 2 - a + floor(a / 4.0); - jd = floor(365.25 * (y + 4716)) + - floor(30.6001 * (m + 1)) + - d + b - 1524; + + /* Calculate Gregorian JD using optimized algorithm */ + jd = c_gregorian_civil_to_jd(y, m, d); + if (jd < sg) { - jd -= b; + /* Before Gregorian switchover - use Julian calendar */ + int y2 = y, m2 = m; + if (m2 <= 2) { + y2 -= 1; + m2 += 12; + } + jd = (int)(floor(365.25 * (y2 + 4716)) + + floor(30.6001 * (m2 + 1)) + + d - 1524); *ns = 0; } - else + else { *ns = 1; + } - *rjd = (int)jd; + *rjd = jd; } static void c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) { - double x, a, b, c, d, e, y, m, dom; - - if (jd < sg) - a = jd; - else { - x = floor((jd - 1867216.25) / 36524.25); - a = jd + 1 + x - floor(x / 4.0); - } - b = a + 1524; - c = floor((b - 122.1) / 365.25); - d = floor(365.25 * c); - e = floor((b - d) / 30.6001); - dom = b - d - floor(30.6001 * e); - if (e <= 13) { - m = e - 1; - y = c - 4716; - } - else { - m = e - 13; - y = c - 4715; + /* Fast path: pure Gregorian or date after switchover, within safe range */ + if (((isinf(sg) && sg < 0) || jd >= sg) && ns_jd_in_range(jd)) { + c_gregorian_jd_to_civil(jd, ry, rm, rdom); + return; } - *ry = (int)y; - *rm = (int)m; - *rdom = (int)dom; + /* Original algorithm for Julian calendar or extreme dates */ + { + double x, a, b, c, d, e, y, m, dom; + + if (jd < sg) + a = jd; + else { + x = floor((jd - 1867216.25) / 36524.25); + a = jd + 1 + x - floor(x / 4.0); + } + b = a + 1524; + c = floor((b - 122.1) / 365.25); + d = floor(365.25 * c); + e = floor((b - d) / 30.6001); + dom = b - d - floor(30.6001 * e); + if (e <= 13) { + m = e - 1; + y = c - 4716; + } + else { + m = e - 13; + y = c - 4715; + } + + *ry = (int)y; + *rm = (int)m; + *rdom = (int)dom; + } } static void @@ -725,6 +777,113 @@ c_gregorian_last_day_of_month(int y, int m) return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; } +/* + * Neri-Schneider algorithm for optimized Gregorian date conversion. + * Reference: Neri & Schneider, "Euclidean Affine Functions and Applications + * to Calendar Algorithms", Software: Practice and Experience, 2023. + * https://arxiv.org/abs/2102.06959 + * + * This algorithm provides ~2-3x speedup over traditional floating-point + * implementations by using pure integer arithmetic with multiplication + * and bit-shifts instead of expensive division operations. + */ + +/* JDN of March 1, Year 0 in proleptic Gregorian calendar */ +#define NS_GREGORIAN_EPOCH 1721120 + +/* + * Safe bounds for Neri-Schneider algorithm to avoid integer overflow. + * These correspond to approximately years -1,000,000 to +1,000,000. + */ +#define NS_JD_MIN -364000000 +#define NS_JD_MAX 538000000 + +inline static int +ns_jd_in_range(int jd) +{ + return jd >= NS_JD_MIN && jd <= NS_JD_MAX; +} + +/* Optimized: Gregorian date -> Julian Day Number */ +static int +c_gregorian_civil_to_jd(int y, int m, int d) +{ + /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */ + int j = (m < 3) ? 1 : 0; + int y0 = y - j; + int m0 = j ? m + 12 : m; + int d0 = d - 1; + + /* Calculate year contribution with leap year correction */ + int q1 = DIV(y0, 100); + int yc = DIV(1461 * y0, 4) - q1 + DIV(q1, 4); + + /* Calculate month contribution using integer arithmetic */ + int mc = (979 * m0 - 2919) / 32; + + /* Combine and add epoch offset to get JDN */ + return yc + mc + d0 + NS_GREGORIAN_EPOCH; +} + +/* Optimized: Julian Day Number -> Gregorian date */ +static void +c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) +{ + int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; + uint64_t u2; + + /* Convert JDN to rata die (March 1, Year 0 epoch) */ + r0 = jd - NS_GREGORIAN_EPOCH; + + /* Extract century and day within 400-year cycle */ + /* Use Euclidean (floor) division for negative values */ + n1 = 4 * r0 + 3; + q1 = DIV(n1, 146097); /* Century */ + r1 = MOD(n1, 146097) / 4; /* Day within 400-year cycle */ + + /* Use 64-bit arithmetic for year calculation within century */ + n2 = 4 * r1 + 3; + u2 = (uint64_t)2939745 * (uint64_t)n2; + q2 = (int)(u2 >> 32); /* Year within century */ + r2 = (int)((uint32_t)u2 / 2939745 / 4); /* Day of year */ + + /* Calculate month and day using integer arithmetic */ + n3 = 2141 * r2 + 197913; + q3 = n3 >> 16; /* Month (3-14) */ + r3 = (n3 & 0xFFFF) / 2141; /* Day of month (0-based) */ + + /* Combine century and year */ + y0 = 100 * q1 + q2; + + /* Adjust for January/February (shift from fiscal year) */ + j = (r2 >= 306) ? 1 : 0; + + *ry = y0 + j; + *rm = j ? q3 - 12 : q3; + *rd = r3 + 1; +} + +/* O(1) first day of year for Gregorian calendar */ +inline static int +c_gregorian_fdoy(int y) +{ + return c_gregorian_civil_to_jd(y, 1, 1); +} + +/* O(1) last day of year for Gregorian calendar */ +inline static int +c_gregorian_ldoy(int y) +{ + return c_gregorian_civil_to_jd(y, 12, 31); +} + +/* O(1) last day of month (JDN) for Gregorian calendar */ +inline static int +c_gregorian_ldom_jd(int y, int m) +{ + return c_gregorian_civil_to_jd(y, m, c_gregorian_last_day_of_month(y, m)); +} + static int c_valid_julian_p(int y, int m, int d, int *rm, int *rd) { From ea03f263b51cfae3163e19dad5800c3a54d7cd1c Mon Sep 17 00:00:00 2001 From: Maciej Mensfeld Date: Thu, 18 Dec 2025 10:38:51 +0100 Subject: [PATCH 2195/2435] [ruby/date] improve styling https://github.com/ruby/date/commit/cd7a329dfd --- ext/date/date_core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index e19248717b2d46..e055db9fa667db 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -456,10 +456,10 @@ static int c_valid_civil_p(int, int, int, double, /* Forward declarations for Neri-Schneider optimized functions */ static int c_gregorian_civil_to_jd(int y, int m, int d); static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); -inline static int c_gregorian_fdoy(int y); -inline static int c_gregorian_ldoy(int y); -inline static int c_gregorian_ldom_jd(int y, int m); -inline static int ns_jd_in_range(int jd); +static int c_gregorian_fdoy(int y); +static int c_gregorian_ldoy(int y); +static int c_gregorian_ldom_jd(int y, int m); +static int ns_jd_in_range(int jd); static int c_find_fdoy(int y, double sg, int *rjd, int *ns) @@ -809,7 +809,7 @@ static int c_gregorian_civil_to_jd(int y, int m, int d) { /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */ - int j = (m < 3) ? 1 : 0; + int j = (m < 3) ? 1 : 0; int y0 = y - j; int m0 = j ? m + 12 : m; int d0 = d - 1; From bcaa127ecd4f408299c8514569329df1b124f556 Mon Sep 17 00:00:00 2001 From: Maciej Mensfeld Date: Thu, 18 Dec 2025 12:36:36 +0100 Subject: [PATCH 2196/2435] [ruby/date] code remarks, macros and r2.6 support https://github.com/ruby/date/commit/2682dc79c0 --- ext/date/date_core.c | 94 ++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index e055db9fa667db..23fe03c73b5a5e 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -7,7 +7,6 @@ #include "ruby/util.h" #include #include -#include /* For uint64_t in Neri-Schneider algorithm */ #if defined(HAVE_SYS_TIME_H) #include #endif @@ -453,6 +452,9 @@ do {\ static int c_valid_civil_p(int, int, int, double, int *, int *, int *, int *); +/* Check if using pure Gregorian calendar (sg == -Infinity) */ +#define c_gregorian_only_p(sg) (isinf(sg) && (sg) < 0) + /* Forward declarations for Neri-Schneider optimized functions */ static int c_gregorian_civil_to_jd(int y, int m, int d); static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); @@ -467,7 +469,7 @@ c_find_fdoy(int y, double sg, int *rjd, int *ns) int d, rm, rd; /* Fast path: pure Gregorian calendar */ - if (isinf(sg) && sg < 0) { + if (c_gregorian_only_p(sg)) { *rjd = c_gregorian_fdoy(y); *ns = 1; return 1; @@ -486,7 +488,7 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) int i, rm, rd; /* Fast path: pure Gregorian calendar */ - if (isinf(sg) && sg < 0) { + if (c_gregorian_only_p(sg)) { *rjd = c_gregorian_ldoy(y); *ns = 1; return 1; @@ -519,7 +521,7 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) int i, rm, rd; /* Fast path: pure Gregorian calendar */ - if (isinf(sg) && sg < 0) { + if (c_gregorian_only_p(sg)) { *rjd = c_gregorian_ldom_jd(y, m); *ns = 1; return 1; @@ -537,8 +539,8 @@ c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) { int jd; - /* Fast path: pure Gregorian calendar (sg == -infinity) */ - if (isinf(sg) && sg < 0) { + /* Fast path: pure Gregorian calendar */ + if (c_gregorian_only_p(sg)) { *rjd = c_gregorian_civil_to_jd(y, m, d); *ns = 1; return; @@ -570,7 +572,7 @@ static void c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) { /* Fast path: pure Gregorian or date after switchover, within safe range */ - if (((isinf(sg) && sg < 0) || jd >= sg) && ns_jd_in_range(jd)) { + if ((c_gregorian_only_p(sg) || jd >= sg) && ns_jd_in_range(jd)) { c_gregorian_jd_to_civil(jd, ry, rm, rdom); return; } @@ -789,7 +791,40 @@ c_gregorian_last_day_of_month(int y, int m) */ /* JDN of March 1, Year 0 in proleptic Gregorian calendar */ -#define NS_GREGORIAN_EPOCH 1721120 +#define NS_EPOCH 1721120 + +/* Days in a 4-year cycle (3 normal years + 1 leap year) */ +#define NS_DAYS_IN_4_YEARS 1461 + +/* Days in a 400-year Gregorian cycle (97 leap years in 400 years) */ +#define NS_DAYS_IN_400_YEARS 146097 + +/* Years per century */ +#define NS_YEARS_PER_CENTURY 100 + +/* + * Multiplier for extracting year within century using fixed-point arithmetic. + * This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function. + */ +#define NS_YEAR_MULTIPLIER 2939745 + +/* + * Coefficients for month calculation from day-of-year. + * Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16 + */ +#define NS_MONTH_COEFF 2141 +#define NS_MONTH_OFFSET 197913 + +/* + * Coefficients for civil date to JDN month contribution. + * Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32 + */ +#define NS_CIVIL_MONTH_COEFF 979 +#define NS_CIVIL_MONTH_OFFSET 2919 +#define NS_CIVIL_MONTH_DIVISOR 32 + +/* Days from March 1 to December 31 (for Jan/Feb year adjustment) */ +#define NS_DAYS_BEFORE_NEW_YEAR 306 /* * Safe bounds for Neri-Schneider algorithm to avoid integer overflow. @@ -815,14 +850,14 @@ c_gregorian_civil_to_jd(int y, int m, int d) int d0 = d - 1; /* Calculate year contribution with leap year correction */ - int q1 = DIV(y0, 100); - int yc = DIV(1461 * y0, 4) - q1 + DIV(q1, 4); + int q1 = DIV(y0, NS_YEARS_PER_CENTURY); + int yc = DIV(NS_DAYS_IN_4_YEARS * y0, 4) - q1 + DIV(q1, 4); /* Calculate month contribution using integer arithmetic */ - int mc = (979 * m0 - 2919) / 32; + int mc = (NS_CIVIL_MONTH_COEFF * m0 - NS_CIVIL_MONTH_OFFSET) / NS_CIVIL_MONTH_DIVISOR; /* Combine and add epoch offset to get JDN */ - return yc + mc + d0 + NS_GREGORIAN_EPOCH; + return yc + mc + d0 + NS_EPOCH; } /* Optimized: Julian Day Number -> Gregorian date */ @@ -830,33 +865,42 @@ static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) { int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; - uint64_t u2; +#ifdef HAVE_LONG_LONG + unsigned LONG_LONG u2; +#endif /* Convert JDN to rata die (March 1, Year 0 epoch) */ - r0 = jd - NS_GREGORIAN_EPOCH; + r0 = jd - NS_EPOCH; /* Extract century and day within 400-year cycle */ /* Use Euclidean (floor) division for negative values */ n1 = 4 * r0 + 3; - q1 = DIV(n1, 146097); /* Century */ - r1 = MOD(n1, 146097) / 4; /* Day within 400-year cycle */ + q1 = DIV(n1, NS_DAYS_IN_400_YEARS); + r1 = MOD(n1, NS_DAYS_IN_400_YEARS) / 4; - /* Use 64-bit arithmetic for year calculation within century */ + /* Calculate year within century and day of year */ n2 = 4 * r1 + 3; - u2 = (uint64_t)2939745 * (uint64_t)n2; - q2 = (int)(u2 >> 32); /* Year within century */ - r2 = (int)((uint32_t)u2 / 2939745 / 4); /* Day of year */ +#ifdef HAVE_LONG_LONG + /* Use 64-bit arithmetic to avoid overflow */ + u2 = (unsigned LONG_LONG)NS_YEAR_MULTIPLIER * (unsigned LONG_LONG)n2; + q2 = (int)(u2 >> 32); + r2 = (int)((unsigned int)u2 / NS_YEAR_MULTIPLIER / 4); +#else + /* Fallback for systems without 64-bit integers */ + q2 = n2 / NS_DAYS_IN_4_YEARS; + r2 = (n2 % NS_DAYS_IN_4_YEARS) / 4; +#endif /* Calculate month and day using integer arithmetic */ - n3 = 2141 * r2 + 197913; - q3 = n3 >> 16; /* Month (3-14) */ - r3 = (n3 & 0xFFFF) / 2141; /* Day of month (0-based) */ + n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET; + q3 = n3 >> 16; + r3 = (n3 & 0xFFFF) / NS_MONTH_COEFF; /* Combine century and year */ - y0 = 100 * q1 + q2; + y0 = NS_YEARS_PER_CENTURY * q1 + q2; /* Adjust for January/February (shift from fiscal year) */ - j = (r2 >= 306) ? 1 : 0; + j = (r2 >= NS_DAYS_BEFORE_NEW_YEAR) ? 1 : 0; *ry = y0 + j; *rm = j ? q3 - 12 : q3; From 5960fb9fa000898b863565a8f48b81fd25bff88a Mon Sep 17 00:00:00 2001 From: Maciej Mensfeld Date: Thu, 18 Dec 2025 12:43:22 +0100 Subject: [PATCH 2197/2435] [ruby/date] remove redundant code https://github.com/ruby/date/commit/5e6a458179 --- ext/date/date_core.c | 46 +++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 23fe03c73b5a5e..061f4d80d001b9 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -455,6 +455,24 @@ static int c_valid_civil_p(int, int, int, double, /* Check if using pure Gregorian calendar (sg == -Infinity) */ #define c_gregorian_only_p(sg) (isinf(sg) && (sg) < 0) +/* + * Fast path macros for pure Gregorian calendar. + * Sets *rjd to the JD value, *ns to 1 (new style), and returns. + */ +#define GREGORIAN_JD_FAST_PATH_RET(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return 1; \ + } + +#define GREGORIAN_JD_FAST_PATH(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return; \ + } + /* Forward declarations for Neri-Schneider optimized functions */ static int c_gregorian_civil_to_jd(int y, int m, int d); static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); @@ -468,12 +486,7 @@ c_find_fdoy(int y, double sg, int *rjd, int *ns) { int d, rm, rd; - /* Fast path: pure Gregorian calendar */ - if (c_gregorian_only_p(sg)) { - *rjd = c_gregorian_fdoy(y); - *ns = 1; - return 1; - } + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_fdoy(y), rjd, ns); /* Keep existing loop for Julian/reform period */ for (d = 1; d < 31; d++) @@ -487,12 +500,7 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) { int i, rm, rd; - /* Fast path: pure Gregorian calendar */ - if (c_gregorian_only_p(sg)) { - *rjd = c_gregorian_ldoy(y); - *ns = 1; - return 1; - } + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldoy(y), rjd, ns); /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) @@ -520,12 +528,7 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) { int i, rm, rd; - /* Fast path: pure Gregorian calendar */ - if (c_gregorian_only_p(sg)) { - *rjd = c_gregorian_ldom_jd(y, m); - *ns = 1; - return 1; - } + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldom_jd(y, m), rjd, ns); /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) @@ -539,12 +542,7 @@ c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) { int jd; - /* Fast path: pure Gregorian calendar */ - if (c_gregorian_only_p(sg)) { - *rjd = c_gregorian_civil_to_jd(y, m, d); - *ns = 1; - return; - } + GREGORIAN_JD_FAST_PATH(sg, c_gregorian_civil_to_jd(y, m, d), rjd, ns); /* Calculate Gregorian JD using optimized algorithm */ jd = c_gregorian_civil_to_jd(y, m, d); From 8024245854ac9e92947e7bd4a58223d8998d3893 Mon Sep 17 00:00:00 2001 From: Maciej Mensfeld Date: Thu, 18 Dec 2025 14:03:00 +0100 Subject: [PATCH 2198/2435] [ruby/date] remove conditional for uint64_t https://github.com/ruby/date/commit/47778c32d8 --- ext/date/date_core.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 061f4d80d001b9..9755fb819eaaa9 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -863,9 +863,7 @@ static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) { int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; -#ifdef HAVE_LONG_LONG - unsigned LONG_LONG u2; -#endif + uint64_t u2; /* Convert JDN to rata die (March 1, Year 0 epoch) */ r0 = jd - NS_EPOCH; @@ -878,16 +876,10 @@ c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) /* Calculate year within century and day of year */ n2 = 4 * r1 + 3; -#ifdef HAVE_LONG_LONG /* Use 64-bit arithmetic to avoid overflow */ - u2 = (unsigned LONG_LONG)NS_YEAR_MULTIPLIER * (unsigned LONG_LONG)n2; + u2 = (uint64_t)NS_YEAR_MULTIPLIER * (uint64_t)n2; q2 = (int)(u2 >> 32); - r2 = (int)((unsigned int)u2 / NS_YEAR_MULTIPLIER / 4); -#else - /* Fallback for systems without 64-bit integers */ - q2 = n2 / NS_DAYS_IN_4_YEARS; - r2 = (n2 % NS_DAYS_IN_4_YEARS) / 4; -#endif + r2 = (int)((uint32_t)u2 / NS_YEAR_MULTIPLIER / 4); /* Calculate month and day using integer arithmetic */ n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET; From c5376a3a167cbb90023e7610a4fafda22a5c381c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Dec 2025 11:11:31 +0900 Subject: [PATCH 2199/2435] [ruby/rubygems] Remove deprecated, unused Gem::List https://github.com/ruby/rubygems/commit/43371085f4 --- lib/rubygems/resolver.rb | 1 - lib/rubygems/specification.rb | 1 - lib/rubygems/util/list.rb | 40 ----------------------------------- 3 files changed, 42 deletions(-) delete mode 100644 lib/rubygems/util/list.rb diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index ed4cbde3bab12c..bc4fef893ead65 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -2,7 +2,6 @@ require_relative "dependency" require_relative "exceptions" -require_relative "util/list" ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index a9ec6aa3a3c84c..3d1f2dad910485 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -11,7 +11,6 @@ require_relative "stub_specification" require_relative "platform" require_relative "specification_record" -require_relative "util/list" require "rbconfig" diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb deleted file mode 100644 index 2899e8a2b9ed70..00000000000000 --- a/lib/rubygems/util/list.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gem - # The Gem::List class is currently unused and will be removed in the next major rubygems version - class List # :nodoc: - include Enumerable - attr_accessor :value, :tail - - def initialize(value = nil, tail = nil) - @value = value - @tail = tail - end - - def each - n = self - while n - yield n.value - n = n.tail - end - end - - def to_a - super.reverse - end - - def prepend(value) - List.new value, self - end - - def pretty_print(q) # :nodoc: - q.pp to_a - end - - def self.prepend(list, value) - return List.new(value) unless list - List.new value, list - end - end - deprecate_constant :List -end From bdbe8d50150447748eaa92a0cce7327d8dec9903 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 25 Nov 2025 14:17:03 -0500 Subject: [PATCH 2200/2435] [ruby/rubygems] Write gem files atomically This change updates `write_binary` to use a new class, `AtomicFileWriter.open` to write the gem's files. This implementation is borrowed from Active Support's [`atomic_write`](https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb). Atomic write will write the files to a temporary file and then once created, sets permissions and renames the file. If the file is corrupted - ie on failed download, an error occurs, or for some other reason, the real file will not be created. The changes made here make `verify_gz` obsolete, we don't need to verify it if we have successfully created the file atomically. If it exists, it is not corrupt. If it is corrupt, the file won't exist on disk. While writing tests for this functionality I replaced the `RemoteFetcher` stub with `FakeFetcher` except for where we really do need to overwrite the `RemoteFetcher`. The new test implementation is much clearer on what it's trying to accomplish versus the prior test implementation. https://github.com/ruby/rubygems/commit/0cd4b54291 --- lib/bundler/shared_helpers.rb | 3 +- lib/rubygems.rb | 11 +- lib/rubygems/util/atomic_file_writer.rb | 67 ++++++++++++ test/rubygems/test_gem_installer.rb | 34 +++--- test/rubygems/test_gem_remote_fetcher.rb | 134 +++++++++++------------ 5 files changed, 158 insertions(+), 91 deletions(-) create mode 100644 lib/rubygems/util/atomic_file_writer.rb diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 6419e4299760b7..2aa8abe0a078b0 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -105,7 +105,8 @@ def set_bundle_environment def filesystem_access(path, action = :write, &block) yield(path.dup) rescue Errno::EACCES => e - raise unless e.message.include?(path.to_s) || action == :create + path_basename = File.basename(path.to_s) + raise unless e.message.include?(path_basename) || action == :create raise PermissionError.new(path, action) rescue Errno::EAGAIN diff --git a/lib/rubygems.rb b/lib/rubygems.rb index e99176fec0e107..41b39a808b637f 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -17,6 +17,7 @@ module Gem require_relative "rubygems/errors" require_relative "rubygems/target_rbconfig" require_relative "rubygems/win_platform" +require_relative "rubygems/util/atomic_file_writer" ## # RubyGems is the Ruby standard for publishing and managing third party @@ -833,14 +834,12 @@ def self.read_binary(path) end ## - # Safely write a file in binary mode on all platforms. + # Atomically write a file in binary mode on all platforms. def self.write_binary(path, data) - File.binwrite(path, data) - rescue Errno::ENOSPC - # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. - File.delete(path) if File.exist?(path) - raise + Gem::AtomicFileWriter.open(path) do |file| + file.write(data) + end end ## diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb new file mode 100644 index 00000000000000..7d1d6a74168f95 --- /dev/null +++ b/lib/rubygems/util/atomic_file_writer.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Based on ActiveSupport's AtomicFile implementation +# Copyright (c) David Heinemeier Hansson +# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb +# Licensed under the MIT License + +module Gem + class AtomicFileWriter + ## + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + + def self.open(file_name) + temp_dir = File.dirname(file_name) + require "tempfile" unless defined?(Tempfile) + + Tempfile.create(".#{File.basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_value = yield temp_file + temp_file.close + + original_permissions = if File.exist?(file_name) + File.stat(file_name) + else + # If not possible, probe which are the default permissions in the + # destination directory. + probe_permissions_in(File.dirname(file_name)) + end + + # Set correct permissions on new file + if original_permissions + begin + File.chown(original_permissions.uid, original_permissions.gid, temp_file.path) + File.chmod(original_permissions.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + # Overwrite original file with temp file + File.rename(temp_file.path, file_name) + return_value + end + end + + def self.probe_permissions_in(dir) # :nodoc: + basename = [ + ".permissions_check", + Thread.current.object_id, + Process.pid, + rand(1_000_000), + ].join(".") + + file_name = File.join(dir, basename) + File.open(file_name, "w") {} + File.stat(file_name) + rescue Errno::ENOENT + nil + ensure + begin + File.unlink(file_name) if File.exist?(file_name) + rescue SystemCallError + end + end + end +end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 293fe1e823dd1a..0220a41f88a4f2 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -2385,25 +2385,31 @@ def test_leaves_no_empty_cached_spec_when_no_more_disk_space installer = Gem::Installer.for_spec @spec installer.gem_home = @gemhome - File.singleton_class.class_eval do - alias_method :original_binwrite, :binwrite - - def binwrite(path, data) + assert_raise(Errno::ENOSPC) do + Gem::AtomicFileWriter.open(@spec.spec_file) do raise Errno::ENOSPC end end - assert_raise Errno::ENOSPC do - installer.write_spec - end - assert_path_not_exist @spec.spec_file - ensure - File.singleton_class.class_eval do - remove_method :binwrite - alias_method :binwrite, :original_binwrite - remove_method :original_binwrite - end + end + + def test_write_default_spec + @spec = setup_base_spec + @spec.files = %w[a.rb b.rb c.rb] + + installer = Gem::Installer.for_spec @spec + installer.gem_home = @gemhome + + installer.write_default_spec + + assert_path_exist installer.default_spec_file + + loaded = Gem::Specification.load installer.default_spec_file + + assert_equal @spec.files, loaded.files + assert_equal @spec.name, loaded.name + assert_equal @spec.version, loaded.version end def test_dir diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 5c1d89fad6934a..9badd75b427169 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -60,7 +60,7 @@ def test_cache_update_path uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = util_fuck_with_fetcher "hello" + fetcher = fake_fetcher(uri.to_s, "hello") data = fetcher.cache_update_path uri, path @@ -75,7 +75,7 @@ def test_cache_update_path_with_utf8_internal_encoding path = File.join @tempdir, "file" data = String.new("\xC8").force_encoding(Encoding::BINARY) - fetcher = util_fuck_with_fetcher data + fetcher = fake_fetcher(uri.to_s, data) written_data = fetcher.cache_update_path uri, path @@ -88,7 +88,7 @@ def test_cache_update_path_no_update uri = Gem::URI "http://example/file" path = File.join @tempdir, "file" - fetcher = util_fuck_with_fetcher "hello" + fetcher = fake_fetcher(uri.to_s, "hello") data = fetcher.cache_update_path uri, path, false @@ -97,103 +97,79 @@ def test_cache_update_path_no_update assert_path_not_exist path end - def util_fuck_with_fetcher(data, blow = false) - fetcher = Gem::RemoteFetcher.fetcher - fetcher.instance_variable_set :@test_data, data - - if blow - def fetcher.fetch_path(arg, *rest) - # OMG I'm such an ass - class << self; remove_method :fetch_path; end - def self.fetch_path(arg, *rest) - @test_arg = arg - @test_data - end + def test_cache_update_path_overwrites_existing_file + uri = Gem::URI "http://example/file" + path = File.join @tempdir, "file" - raise Gem::RemoteFetcher::FetchError.new("haha!", "") - end - else - def fetcher.fetch_path(arg, *rest) - @test_arg = arg - @test_data - end - end + # Create existing file with old content + File.write(path, "old content") + assert_equal "old content", File.read(path) + + fetcher = fake_fetcher(uri.to_s, "new content") - fetcher + data = fetcher.cache_update_path uri, path + + assert_equal "new content", data + assert_equal "new content", File.read(path) end def test_download - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com") - assert_equal("http://gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_auth - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://user:password@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:password@gems.example.com") - assert_equal("http://user:password@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_token - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://token@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token@gems.example.com") - assert_equal("http://token@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_x_oauth_basic - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://token:x-oauth-basic@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://token:x-oauth-basic@gems.example.com") - assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end def test_download_with_encoded_auth - a1_data = nil - File.open @a1_gem, "rb" do |fp| - a1_data = fp.read - end + a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://user:%25pas%25sword@gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) a1_cache_gem = @a1.cache_file assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:%25pas%25sword@gems.example.com") - assert_equal("http://user:%25pas%25sword@gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end @@ -235,8 +211,9 @@ def test_download_local_space def test_download_install_dir a1_data = File.open @a1_gem, "rb", &:read + a1_url = "http://gems.example.com/gems/a-1.gem" - fetcher = util_fuck_with_fetcher a1_data + fetcher = fake_fetcher(a1_url, a1_data) install_dir = File.join @tempdir, "more_gems" @@ -245,8 +222,7 @@ def test_download_install_dir actual = fetcher.download(@a1, "http://gems.example.com", install_dir) assert_equal a1_cache_gem, actual - assert_equal("http://gems.example.com/gems/a-1.gem", - fetcher.instance_variable_get(:@test_arg).to_s) + assert_equal a1_url, fetcher.paths.last assert File.exist?(a1_cache_gem) end @@ -282,7 +258,12 @@ def test_download_read_only FileUtils.chmod 0o555, @a1.cache_dir FileUtils.chmod 0o555, @gemhome - fetcher = util_fuck_with_fetcher File.read(@a1_gem) + fetcher = Gem::RemoteFetcher.fetcher + def fetcher.fetch_path(uri, *rest) + File.read File.join(@test_gem_dir, "a-1.gem") + end + fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem)) + fetcher.download(@a1, "http://gems.example.com") a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name assert File.exist? a1_cache_gem @@ -301,19 +282,21 @@ def test_download_platform_legacy end e1.loaded_from = File.join(@gemhome, "specifications", e1.full_name) - e1_data = nil - File.open e1_gem, "rb" do |fp| - e1_data = fp.read - end + e1_data = File.open e1_gem, "rb", &:read - fetcher = util_fuck_with_fetcher e1_data, :blow_chunks + fetcher = Gem::RemoteFetcher.fetcher + def fetcher.fetch_path(uri, *rest) + @call_count ||= 0 + @call_count += 1 + raise Gem::RemoteFetcher::FetchError.new("error", uri) if @call_count == 1 + @test_data + end + fetcher.instance_variable_set(:@test_data, e1_data) e1_cache_gem = e1.cache_file assert_equal e1_cache_gem, fetcher.download(e1, "http://gems.example.com") - assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem", - fetcher.instance_variable_get(:@test_arg).to_s) assert File.exist?(e1_cache_gem) end @@ -592,6 +575,8 @@ def test_yaml_error_on_size end end + private + def assert_error(exception_class = Exception) got_exception = false @@ -603,4 +588,13 @@ def assert_error(exception_class = Exception) assert got_exception, "Expected exception conforming to #{exception_class}" end + + def fake_fetcher(url, data) + original_fetcher = Gem::RemoteFetcher.fetcher + fetcher = Gem::FakeFetcher.new + fetcher.data[url] = data + Gem::RemoteFetcher.fetcher = fetcher + ensure + Gem::RemoteFetcher.fetcher = original_fetcher + end end From 74becf1b61272c66e835c446525920eae0b8574a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 09:32:02 +0900 Subject: [PATCH 2201/2435] Start to develop 4.1.0.dev --- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- spec/bundler/realworld/fixtures/tapioca/Gemfile.lock | 2 +- spec/bundler/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 732b4a05631e84..ca7bb0719aac24 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.3".freeze + VERSION = "4.1.0.dev".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 41b39a808b637f..b52dd1b9d3e99a 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.3" + VERSION = "4.1.0.dev" end require_relative "rubygems/defaults" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index 2db720a69f4632..4ce06de722cfaa 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.3 + 4.1.0.dev diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index c37fbbb7eed556..2f2deea99408cc 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.3 + 4.1.0.dev diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index fff9cfe70cb104..72abb0efcbe1f6 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.3 + 4.1.0.dev diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 70704bfc38896c..251abc37dd0f23 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.3 + 4.1.0.dev diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 669e5492a8a0f0..1b48fe058257f4 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 BUNDLED WITH - 4.0.3 + 4.1.0.dev diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index d8f7d77122846d..6fb9d1c0c5eb77 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.3 + 4.1.0.dev From 4b7bbd43408f230997e216a557d586edd492172d Mon Sep 17 00:00:00 2001 From: Jean-Samuel Aubry-Guzzi Date: Wed, 29 Oct 2025 08:31:26 -0400 Subject: [PATCH 2202/2435] [ruby/resolv] Fix TCP Requester #recv_reply https://github.com/ruby/resolv/commit/96dc3d15fe --- lib/resolv.rb | 5 +++- test/resolv/test_dns.rb | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 0e62aaf8510496..e6153af2a9d1f5 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -930,8 +930,11 @@ def initialize(host, port=Port) end def recv_reply(readable_socks) - len = readable_socks[0].read(2).unpack('n')[0] + len_data = readable_socks[0].read(2) + raise Errno::ECONNRESET if len_data.nil? + len = len_data.unpack('n')[0] reply = @socks[0].read(len) + raise Errno::ECONNRESET if reply.nil? return reply, nil end diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index d5d2648e1bc649..1dda9bc6278193 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -822,4 +822,63 @@ def test_multiple_servers_with_timeout_and_truncated_tcp_fallback end end end + + def test_tcp_connection_closed_before_length + with_tcp('127.0.0.1', 0) do |t| + _, server_port, _, server_address = t.addr + + server_thread = Thread.new do + ct = t.accept + ct.recv(512) + ct.close + end + + client_thread = Thread.new do + requester = Resolv::DNS::Requester::TCP.new(server_address, server_port) + begin + msg = Resolv::DNS::Message.new + msg.add_question('example.org', Resolv::DNS::Resource::IN::A) + sender = requester.sender(msg, msg) + assert_raise(Resolv::ResolvTimeout) do + requester.request(sender, 2) + end + ensure + requester.close + end + end + + server_thread.join + client_thread.join + end + end + + def test_tcp_connection_closed_after_length + with_tcp('127.0.0.1', 0) do |t| + _, server_port, _, server_address = t.addr + + server_thread = Thread.new do + ct = t.accept + ct.recv(512) + ct.send([100].pack('n'), 0) + ct.close + end + + client_thread = Thread.new do + requester = Resolv::DNS::Requester::TCP.new(server_address, server_port) + begin + msg = Resolv::DNS::Message.new + msg.add_question('example.org', Resolv::DNS::Resource::IN::A) + sender = requester.sender(msg, msg) + assert_raise(Resolv::ResolvTimeout) do + requester.request(sender, 2) + end + ensure + requester.close + end + end + + server_thread.join + client_thread.join + end + end end From f8d0960af260219ab7c10a797ac62ecad25b2974 Mon Sep 17 00:00:00 2001 From: Jean-Samuel Aubry-Guzzi Date: Mon, 8 Dec 2025 11:22:55 -0500 Subject: [PATCH 2203/2435] [ruby/resolv] Handle TCP Requester #recv_reply incomplete data https://github.com/ruby/resolv/commit/9c640bdc4a --- lib/resolv.rb | 7 +++-- test/resolv/test_dns.rb | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index e6153af2a9d1f5..fa7d4e2e4753b3 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -721,7 +721,8 @@ def request(sender, tout) begin reply, from = recv_reply(select_result[0]) rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD - Errno::ECONNRESET # Windows + Errno::ECONNRESET, # Windows + EOFError # No name server running on the server? # Don't wait anymore. raise ResolvTimeout @@ -931,10 +932,10 @@ def initialize(host, port=Port) def recv_reply(readable_socks) len_data = readable_socks[0].read(2) - raise Errno::ECONNRESET if len_data.nil? + raise EOFError if len_data.nil? || len_data.bytesize != 2 len = len_data.unpack('n')[0] reply = @socks[0].read(len) - raise Errno::ECONNRESET if reply.nil? + raise EOFError if reply.nil? || reply.bytesize != len return reply, nil end diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 1dda9bc6278193..7a01909eeb9a4b 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -881,4 +881,65 @@ def test_tcp_connection_closed_after_length client_thread.join end end + + def test_tcp_connection_closed_with_partial_length_prefix + with_tcp('127.0.0.1', 0) do |t| + _, server_port, _, server_address = t.addr + + server_thread = Thread.new do + ct = t.accept + ct.recv(512) + ct.write "A" # 1 byte + ct.close + end + + client_thread = Thread.new do + requester = Resolv::DNS::Requester::TCP.new(server_address, server_port) + begin + msg = Resolv::DNS::Message.new + msg.add_question('example.org', Resolv::DNS::Resource::IN::A) + sender = requester.sender(msg, msg) + assert_raise(Resolv::ResolvTimeout) do + requester.request(sender, 2) + end + ensure + requester.close + end + end + + server_thread.join + client_thread.join + end + end + + def test_tcp_connection_closed_with_partial_message_body + with_tcp('127.0.0.1', 0) do |t| + _, server_port, _, server_address = t.addr + + server_thread = Thread.new do + ct = t.accept + ct.recv(512) + ct.write([10].pack('n')) # length 10 + ct.write "12345" # 5 bytes (partial) + ct.close + end + + client_thread = Thread.new do + requester = Resolv::DNS::Requester::TCP.new(server_address, server_port) + begin + msg = Resolv::DNS::Message.new + msg.add_question('example.org', Resolv::DNS::Resource::IN::A) + sender = requester.sender(msg, msg) + assert_raise(Resolv::ResolvTimeout) do + requester.request(sender, 2) + end + ensure + requester.close + end + end + + server_thread.join + client_thread.join + end + end end From 44a17656842a567eb82c43024daaf9fcaff61e5d Mon Sep 17 00:00:00 2001 From: t-mangoe Date: Sun, 21 Dec 2025 09:16:58 +0900 Subject: [PATCH 2204/2435] [ruby/timeout] add test case for string argument https://github.com/ruby/timeout/commit/fef9d07f44 --- test/test_timeout.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index fead81f571929d..b11fc92aeab1cb 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -54,6 +54,12 @@ def test_raise_for_neg_second end end + def test_raise_for_string_argument + assert_raise(NoMethodError) do + Timeout.timeout("1") { sleep(0.01) } + end + end + def test_included c = Class.new do include Timeout From f3149af35a30c4eb334006faef729a6e514d4cf2 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 21 Dec 2025 05:58:23 +0900 Subject: [PATCH 2205/2435] [ruby/prism] Sync `Prism::Translation::ParserCurrent` with Ruby 4.0 This PR updates the fallback version for `Prism::Translation::ParserCurrent` from 3.4 to 4.0. Currently, the fallback resolves to `Parser34`, as shown below: ```console $ ruby -v -rprism -rprism/translation/parser_current -e 'p Prism::Translation::ParserCurrent' ruby 3.0.7p220 (2024-04-23 revision https://github.com/ruby/prism/commit/724a071175) [x86_64-darwin23] warning: `Prism::Translation::Current` is loading Prism::Translation::Parser34, but you are running 3.0. Prism::Translation::Parser34 ``` Following the comment "Keep this in sync with released Ruby.", it seems like the right time to set this to Ruby 4.0, which is scheduled for release this week. https://github.com/ruby/prism/commit/115f0a118c --- lib/prism/translation/parser_current.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index ac6daf7082e416..f13eff6bbe2d90 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -16,7 +16,7 @@ module Translation ParserCurrent = Parser41 else # Keep this in sync with released Ruby. - parser = Parser34 + parser = Parser40 major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments warn "warning: `Prism::Translation::Current` is loading #{parser.name}, " \ "but you are running #{major}.#{minor}." From 1f526b348978bf71efc75f562227c1e872df27fd Mon Sep 17 00:00:00 2001 From: Taketo Takashima Date: Wed, 17 Dec 2025 23:39:43 +0900 Subject: [PATCH 2206/2435] [ruby/ipaddr] Follow-up fix for InvalidAddressError messages https://github.com/ruby/ipaddr/commit/b92ef74b91 --- lib/ipaddr.rb | 14 +++++++------- test/test_ipaddr.rb | 27 +++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 725ff309d27f48..6b67d7eec6706a 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -368,7 +368,7 @@ def _ipv4_compat? # :nodoc: # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) @@ -380,7 +380,7 @@ def ipv4_mapped def ipv4_compat warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end clone = self.clone.set(@addr, Socket::AF_INET6) clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) @@ -413,7 +413,7 @@ def reverse # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.arpa" end @@ -421,7 +421,7 @@ def ip6_arpa # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.int" end @@ -743,19 +743,19 @@ def in6_addr(left) right = '' when RE_IPV6ADDRLIKE_COMPRESSED if $4 - left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" + left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{left}" addr = in_addr($~[4,4]) left = $1 right = $3 + '0:0' else left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" left = $1 right = $2 addr = 0 end else - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" end l = left.split(':') r = right.split(':') diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 005927cd054cd9..4b7229fc17b686 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -207,6 +207,15 @@ def test_ipv4_compat assert_equal(112, b.prefix) end + def test_ipv4_compat_with_error_message + e = assert_raise(IPAddr::InvalidAddressError) do + assert_warning(/obsolete/) { + IPAddr.new('2001:db8::').ipv4_compat + } + end + assert_equal('not an IPv4 address: 2001:db8::', e.message) + end + def test_ipv4_mapped a = IPAddr.new("::ffff:192.168.1.2") assert_equal("::ffff:192.168.1.2", a.to_s) @@ -224,6 +233,13 @@ def test_ipv4_mapped assert_equal(Socket::AF_INET6, b.family) end + def test_ipv4_mapped_with_error_message + e = assert_raise(IPAddr::InvalidAddressError) do + IPAddr.new('2001:db8::').ipv4_mapped + end + assert_equal('not an IPv4 address: 2001:db8::', e.message) + end + def test_reverse assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").reverse) assert_equal("1.2.168.192.in-addr.arpa", IPAddr.new("192.168.2.1").reverse) @@ -231,16 +247,18 @@ def test_reverse def test_ip6_arpa assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").ip6_arpa) - assert_raise(IPAddr::InvalidAddressError) { + e = assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.2.1").ip6_arpa } + assert_equal('not an IPv6 address: 192.168.2.1', e.message) end def test_ip6_int assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int", IPAddr.new("3ffe:505:2::f").ip6_int) - assert_raise(IPAddr::InvalidAddressError) { + e = assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.2.1").ip6_int } + assert_equal('not an IPv6 address: 192.168.2.1', e.message) end def test_prefix_writer @@ -631,5 +649,10 @@ def test_raises_invalid_address_error_with_error_message IPAddr.new('192.168.01.100') end assert_equal('zero-filled number in IPv4 address is ambiguous: 192.168.01.100', e.message) + + e = assert_raise(IPAddr::InvalidAddressError) do + IPAddr.new('INVALID') + end + assert_equal('invalid address: INVALID', e.message) end end From 8ccfb375b9ade4504c4012ce8c31adc7e12dc49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=85=E6=88=91=E5=B1=B1=E8=8F=9C=E3=80=85?= Date: Fri, 19 Dec 2025 01:30:37 +0900 Subject: [PATCH 2207/2435] [ruby/json] Update `fpconv_dtoa` definition to use `dest[32]` https://github.com/ruby/json/commit/4808fee9af --- ext/json/vendor/fpconv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c index 4888ab4b8bcefe..6c9bc2c103396d 100644 --- a/ext/json/vendor/fpconv.c +++ b/ext/json/vendor/fpconv.c @@ -449,7 +449,7 @@ static int filter_special(double fp, char* dest) * } * */ -static int fpconv_dtoa(double d, char dest[28]) +static int fpconv_dtoa(double d, char dest[32]) { char digits[18]; From 4d7db86a794581f1be405dcc70bb06549b8cf28f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 22 Dec 2025 09:16:09 +0100 Subject: [PATCH 2208/2435] [ruby/json] Add missing documentation for `allow_control_characters` parsing option https://github.com/ruby/json/commit/a5c160f372 --- ext/json/lib/json.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb index 43d96afa95835c..f619d93252d950 100644 --- a/ext/json/lib/json.rb +++ b/ext/json/lib/json.rb @@ -173,6 +173,18 @@ # When enabled: # JSON.parse('[1,]', allow_trailing_comma: true) # => [1] # +# --- +# +# Option +allow_control_characters+ (boolean) specifies whether to allow +# unescaped ASCII control characters, such as newlines, in strings; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string (JSON::ParserError) +# +# When enabled: +# JSON.parse(%{"Hello\nWorld"}, allow_control_characters: true) # => "Hello\nWorld" +# # ====== Output Options # # Option +freeze+ (boolean) specifies whether the returned objects will be frozen; From fda7019c80c1026ee89ba772fb7db547c89e541a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 8 Dec 2025 13:06:08 +0100 Subject: [PATCH 2209/2435] [ruby/net-protocol] Add Net::Protocol::TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT * To find out efficiently if TCPSocket#initialize supports the open_timeout keyword argument. https://github.com/ruby/net-protocol/commit/738c06f950 --- lib/net/protocol.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 1443f3e8b71966..8c81298c0e420a 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -54,6 +54,16 @@ def ssl_socket_connect(s, timeout) s.connect end end + + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) + end + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT end From 70c7f3ad77c09e379b9e7ffb8009ccc2cb47f234 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Wed, 17 Dec 2025 14:28:48 +0900 Subject: [PATCH 2210/2435] [ruby/strscan] Bump version https://github.com/ruby/strscan/commit/747a3b5def --- ext/strscan/strscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 11e3d309a81757..c44e77ba91056c 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.6" +#define STRSCAN_VERSION "3.1.7" #ifdef HAVE_RB_DEPRECATE_CONSTANT From 93df96684860ea437de982d6778fc9d845d0505a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 09:37:17 +0900 Subject: [PATCH 2211/2435] Mark development version for unreleased gems --- ext/stringio/stringio.c | 2 +- ext/strscan/strscan.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 93a419ff3172fa..cc2294a795a378 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -13,7 +13,7 @@ **********************************************************************/ static const char *const -STRINGIO_VERSION = "3.2.1"; +STRINGIO_VERSION = "3.2.1.dev"; #include diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index c44e77ba91056c..935fce19df94e9 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.7" +#define STRSCAN_VERSION "3.1.7.dev" #ifdef HAVE_RB_DEPRECATE_CONSTANT From 89af235435a911d86e04abdb1a54f4fe25dcaa6a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 09:44:55 +0900 Subject: [PATCH 2212/2435] Added ruby_41? platform --- lib/bundler/current_ruby.rb | 2 +- spec/bundler/bundler/current_ruby_spec.rb | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index ab6520a106a40f..17c7655adb588f 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -11,7 +11,7 @@ def self.current_ruby end class CurrentRuby - ALL_RUBY_VERSIONS = [*18..27, *30..34, 40].freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze PLATFORM_MAP = { diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb index aa19a414076508..79eb802aa52215 100644 --- a/spec/bundler/bundler/current_ruby_spec.rb +++ b/spec/bundler/bundler/current_ruby_spec.rb @@ -23,6 +23,7 @@ ruby_33: Gem::Platform::RUBY, ruby_34: Gem::Platform::RUBY, ruby_40: Gem::Platform::RUBY, + ruby_41: Gem::Platform::RUBY, mri: Gem::Platform::RUBY, mri_18: Gem::Platform::RUBY, mri_19: Gem::Platform::RUBY, @@ -40,6 +41,7 @@ mri_33: Gem::Platform::RUBY, mri_34: Gem::Platform::RUBY, mri_40: Gem::Platform::RUBY, + mri_41: Gem::Platform::RUBY, rbx: Gem::Platform::RUBY, truffleruby: Gem::Platform::RUBY, jruby: Gem::Platform::JAVA, @@ -61,7 +63,8 @@ windows_32: Gem::Platform::WINDOWS, windows_33: Gem::Platform::WINDOWS, windows_34: Gem::Platform::WINDOWS, - windows_40: Gem::Platform::WINDOWS } + windows_40: Gem::Platform::WINDOWS, + windows_41: Gem::Platform::WINDOWS } end let(:deprecated) do @@ -82,6 +85,7 @@ mswin_33: Gem::Platform::MSWIN, mswin_34: Gem::Platform::MSWIN, mswin_40: Gem::Platform::MSWIN, + mswin_41: Gem::Platform::MSWIN, mswin64: Gem::Platform::MSWIN64, mswin64_19: Gem::Platform::MSWIN64, mswin64_20: Gem::Platform::MSWIN64, @@ -98,6 +102,7 @@ mswin64_33: Gem::Platform::MSWIN64, mswin64_34: Gem::Platform::MSWIN64, mswin64_40: Gem::Platform::MSWIN64, + mswin64_41: Gem::Platform::MSWIN64, mingw: Gem::Platform::UNIVERSAL_MINGW, mingw_18: Gem::Platform::UNIVERSAL_MINGW, mingw_19: Gem::Platform::UNIVERSAL_MINGW, @@ -115,6 +120,7 @@ mingw_33: Gem::Platform::UNIVERSAL_MINGW, mingw_34: Gem::Platform::UNIVERSAL_MINGW, mingw_40: Gem::Platform::UNIVERSAL_MINGW, + mingw_41: Gem::Platform::UNIVERSAL_MINGW, x64_mingw: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_20: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_21: Gem::Platform::UNIVERSAL_MINGW, @@ -129,7 +135,8 @@ x64_mingw_32: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_33: Gem::Platform::UNIVERSAL_MINGW, x64_mingw_34: Gem::Platform::UNIVERSAL_MINGW, - x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW } + x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_41: Gem::Platform::UNIVERSAL_MINGW } end # rubocop:enable Naming/VariableNumber From 565ea26ad10ea8c3c6ce9bdae6cbb78353a9ad36 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 10:14:16 +0900 Subject: [PATCH 2213/2435] Disabled to run lobsters benchmark because it didn't work with Ruby 4.1 yet --- .github/workflows/ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 46ad7c82128e23..bf34baaced0fd2 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -243,7 +243,7 @@ jobs: path: ruby-bench - name: Run ruby-bench - run: ruby run_benchmarks.rb -e "ruby::../build/install/bin/ruby" ${{ matrix.bench_opts }} + run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "ruby::../build/install/bin/ruby" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index ed559d64e10d05..55bfcb30f22364 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -198,7 +198,7 @@ jobs: path: ruby-bench - name: Run ruby-bench - run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 37a9000d704c5a..29b7aaad592c7e 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -251,7 +251,7 @@ jobs: path: ruby-bench - name: Run ruby-bench - run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack From e95a9942bb77ab17a6743a2703cc9c7d5293cc5c Mon Sep 17 00:00:00 2001 From: git Date: Fri, 26 Dec 2025 02:01:49 +0000 Subject: [PATCH 2214/2435] Update default gems list at 565ea26ad10ea8c3c6ce9bdae6cbb7 [ci skip] --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index b9b5fb3ed247ca..2cc6f599f056fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,10 @@ The following default gem is added. The following default gems are updated. +* RubyGems 4.1.0.dev +* bundler 4.1.0.dev +* stringio 3.2.1.dev +* strscan 3.1.7.dev The following bundled gems are updated. From 02275b1e53339f34e46b7e69e1512895ea105042 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 11:11:56 +0900 Subject: [PATCH 2215/2435] uutils-coreutils 0.5.0 has been removed uutils wrapper --- .github/workflows/windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index c19d9e4aa78577..7f3e60349642e0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -112,7 +112,7 @@ jobs: # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 run: | ::- Set up VC ${{ matrix.vc }} - set | uutils sort > old.env + set | sort > old.env call ..\src\win32\vssetup.cmd ^ -arch=${{ matrix.target || 'amd64' }} ^ ${{ matrix.vcvars && '-vcvars_ver=' || '' }}${{ matrix.vcvars }} @@ -122,8 +122,8 @@ jobs: set MAKEFLAGS=l set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul set RUBY_OPT_DIR=%GITHUB_WORKSPACE:\=/%/src/vcpkg_installed/%VCPKG_DEFAULT_TRIPLET% - set | uutils sort > new.env - uutils comm -13 old.env new.env >> %GITHUB_ENV% + set | sort > new.env + comm -13 old.env new.env >> %GITHUB_ENV% del *.env - name: baseruby version From 9824724b2ffe583302e9318c6eff7a440478125f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 26 Dec 2025 11:39:43 +0900 Subject: [PATCH 2216/2435] Skip test_write_binary(GemSingletonTest) at rbs tests ``` Errno::EACCES: Permission denied @ rb_file_s_rename ... D:/a/ruby/ruby/src/lib/rubygems/util/atomic_file_writer.rb:42:in 'File.rename' ``` It may caused with atomic_file_writer.rb --- tool/rbs_skip_tests_windows | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/rbs_skip_tests_windows b/tool/rbs_skip_tests_windows index 1b9f930e846624..35e78e237f1668 100644 --- a/tool/rbs_skip_tests_windows +++ b/tool/rbs_skip_tests_windows @@ -107,3 +107,7 @@ test_new(TempfileSingletonTest) test_open(ZlibGzipReaderSingletonTest) test_reachable_objects_from_root(ObjectSpaceTest) To avoid NoMemoryError with test-unit 3.7.5 + +# Errno::EACCES: Permission denied @ rb_file_s_rename +# D:/a/ruby/ruby/src/lib/rubygems/util/atomic_file_writer.rb:42:in 'File.rename' +test_write_binary(GemSingletonTest) From 594dd8bfd4c2b380dc7185d421d71b29c379356b Mon Sep 17 00:00:00 2001 From: Takashi Sakaguchi Date: Fri, 26 Dec 2025 12:37:46 +0900 Subject: [PATCH 2217/2435] [ruby/pp] Support private instance_variables_to_inspect (https://github.com/ruby/pp/pull/70) * Support private instance_variables_to_inspect in pp Ruby supports calling instance_variables_to_inspect even when it is defined as a private method (ruby/ruby#13555). This change aligns pp with Ruby's behavior. https://github.com/ruby/pp/commit/8450e76db6 --- lib/pp.rb | 2 +- test/test_pp.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pp.rb b/lib/pp.rb index fcd33ba80e9e2e..5fd29a373aa3e2 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -388,7 +388,7 @@ def pretty_print_cycle(q) # This method should return an array of names of instance variables as symbols or strings as: # +[:@a, :@b]+. def pretty_print_instance_variables - ivars = respond_to?(:instance_variables_to_inspect) ? instance_variables_to_inspect : instance_variables + ivars = respond_to?(:instance_variables_to_inspect, true) ? instance_variables_to_inspect || instance_variables : instance_variables ivars.sort end diff --git a/test/test_pp.rb b/test/test_pp.rb index 4a273e6edd0aec..922ed371af3e8f 100644 --- a/test/test_pp.rb +++ b/test/test_pp.rb @@ -146,7 +146,9 @@ def a.pretty_print_instance_variables() [:@b] end def test_iv_hiding_via_ruby a = Object.new - def a.instance_variables_to_inspect() [:@b] end + a.singleton_class.class_eval do + private def instance_variables_to_inspect() [:@b] end + end a.instance_eval { @a = "aaa"; @b = "bbb" } assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) end From bad7dd5d74e9d14e84faf8cc8907dcdfdb8751e8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 26 Dec 2025 09:16:36 +0900 Subject: [PATCH 2218/2435] [DOC] Separate updated gems lists into sections in NEWS.md --- NEWS.md | 8 ++++---- tool/update-NEWS-gemlist.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2cc6f599f056fd..59a2b4b49d4c44 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,18 +19,18 @@ Other changes are listed in the following sections. We also listed release history from the previous bundled version that is Ruby 3.4.0 if it has GitHub releases. -The following bundled gems are promoted from default gems. +### The following bundled gems are promoted from default gems. -The following default gem is added. +### The following default gem is added. -The following default gems are updated. +### The following default gems are updated. * RubyGems 4.1.0.dev * bundler 4.1.0.dev * stringio 3.2.1.dev * strscan 3.1.7.dev -The following bundled gems are updated. +### The following bundled gems are updated. ### RubyGems and Bundler diff --git a/tool/update-NEWS-gemlist.rb b/tool/update-NEWS-gemlist.rb index e1535eb400bc7e..0b5503580d3de9 100755 --- a/tool/update-NEWS-gemlist.rb +++ b/tool/update-NEWS-gemlist.rb @@ -6,10 +6,10 @@ update = ->(list, type, desc = "updated") do item = ->(mark = "* ") do - "The following #{type} gem#{list.size == 1 ? ' is' : 's are'} #{desc}.\n\n" + + "### The following #{type} gem#{list.size == 1 ? ' is' : 's are'} #{desc}.\n\n" + list.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") + "\n" end - news.sub!(/^(?:\*( +))?The following #{type} gems? (?:are|is) #{desc}\.\n+(?:(?(1) \1)\*( *).*\n)*\n*/) do + news.sub!(/^(?:\*( +)|#+ *)?The following #{type} gems? (?:are|is) #{desc}\.\n+(?:(?(1) \1)\*( *).*\n)*\n*/) do item["#{$1&.<< " "}*#{$2 || ' '}"] end or news.sub!(/^## Stdlib updates(?:\n+The following.*(?:\n+( *\* *).*)*)*\n+\K/) do item[$1 || "* "] From b01fd2d8c3195cc9936f6be6f89df0de12394a28 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Thu, 25 Dec 2025 12:05:06 +0200 Subject: [PATCH 2219/2435] Fix RSET_IS_MEMBER macro parameter mismatch The RSET_IS_MEMBER macro had a parameter named 'sobj' but the macro body used 'set' instead, causing the first argument to be ignored. This worked by accident because all current callers use a variable named 'set', but would cause compilation failure if called with a differently named variable: error: use of undeclared identifier 'set' Changed the parameter name from 'sobj' to 'set' to match the macro body and be consistent with other RSET_* macros. --- set.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/set.c b/set.c index 469078ee9968af..d7704e9cf38a4f 100644 --- a/set.c +++ b/set.c @@ -116,7 +116,7 @@ static ID id_class_methods; #define RSET_SIZE(set) set_table_size(RSET_TABLE(set)) #define RSET_EMPTY(set) (RSET_SIZE(set) == 0) #define RSET_SIZE_NUM(set) SIZET2NUM(RSET_SIZE(set)) -#define RSET_IS_MEMBER(sobj, item) set_table_lookup(RSET_TABLE(set), (st_data_t)(item)) +#define RSET_IS_MEMBER(set, item) set_table_lookup(RSET_TABLE(set), (st_data_t)(item)) #define RSET_COMPARE_BY_IDENTITY(set) (RSET_TABLE(set)->type == &identhash) struct set_object { From 704ac72fb67bc431c7d77d0d0aaa7e5b5331b096 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 25 Dec 2025 00:08:38 +0900 Subject: [PATCH 2220/2435] Clarify the intent of the test for "ruby -h" to fit in 80x25 --- test/ruby/test_rubyoptions.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 735d2681fb6a13..cd2dd5d3ff7412 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -47,10 +47,15 @@ def test_source_file assert_in_out_err([], "", [], []) end + # This constant enforces the traditional 80x25 terminal size standard + TRADITIONAL_TERM_COLS = 80 # DO NOT MODIFY! + TRADITIONAL_TERM_ROWS = 25 # DO NOT MODIFY! + def test_usage + # This test checks if the output of `ruby -h` fits in 80x25 assert_in_out_err(%w(-h)) do |r, e| - assert_operator(r.size, :<=, 25) - longer = r[1..-1].select {|x| x.size >= 80} + assert_operator(r.size, :<=, TRADITIONAL_TERM_ROWS) + longer = r[1..-1].select {|x| x.size >= TRADITIONAL_TERM_COLS} assert_equal([], longer) assert_equal([], e) end From 0b65ac6daff08dd30ee66e85f9bcfe756d27b742 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 24 Oct 2024 18:39:51 +0900 Subject: [PATCH 2221/2435] Remove a no longer used prototype declaration in re.c Include internal/error.h instead. --- depend | 1 + re.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depend b/depend index 11397bf647adf1..bf001f45ca8169 100644 --- a/depend +++ b/depend @@ -13378,6 +13378,7 @@ re.$(OBJEXT): $(top_srcdir)/internal/box.h re.$(OBJEXT): $(top_srcdir)/internal/class.h re.$(OBJEXT): $(top_srcdir)/internal/compilers.h re.$(OBJEXT): $(top_srcdir)/internal/encoding.h +re.$(OBJEXT): $(top_srcdir)/internal/error.h re.$(OBJEXT): $(top_srcdir)/internal/gc.h re.$(OBJEXT): $(top_srcdir)/internal/hash.h re.$(OBJEXT): $(top_srcdir)/internal/imemo.h diff --git a/re.c b/re.c index e4d30d2939596e..19bf674c268665 100644 --- a/re.c +++ b/re.c @@ -17,6 +17,7 @@ #include "hrtime.h" #include "internal.h" #include "internal/encoding.h" +#include "internal/error.h" #include "internal/hash.h" #include "internal/imemo.h" #include "internal/re.h" @@ -3970,7 +3971,6 @@ struct reg_init_args { static VALUE reg_extract_args(int argc, VALUE *argv, struct reg_init_args *args); static VALUE reg_init_args(VALUE self, VALUE str, rb_encoding *enc, int flags); -void rb_warn_deprecated_to_remove(const char *removal, const char *fmt, const char *suggest, ...); /* * call-seq: From 4c07e61bc937b39774862e33bbac8c9c234f3dbf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 24 Oct 2024 19:35:05 +0900 Subject: [PATCH 2222/2435] Deprecate old VC --- include/ruby/atomic.h | 44 ++---------------------- include/ruby/internal/attr/deprecated.h | 2 +- include/ruby/internal/attr/forceinline.h | 2 +- include/ruby/internal/attr/noexcept.h | 2 +- include/ruby/internal/attr/restrict.h | 2 +- include/ruby/internal/config.h | 2 +- include/ruby/internal/static_assert.h | 2 +- include/ruby/internal/warning_push.h | 2 +- internal/error.h | 2 +- win32/setup.mak | 6 +++- 10 files changed, 16 insertions(+), 50 deletions(-) diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index c7043b047692a6..fcc48f532c89ba 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -34,7 +34,7 @@ # include /* ssize_t */ #endif -#if RBIMPL_COMPILER_SINCE(MSVC, 13, 0, 0) +#if RBIMPL_COMPILER_IS(MSVC) # pragma intrinsic(_InterlockedOr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) # include @@ -790,22 +790,9 @@ rbimpl_atomic_or(volatile rb_atomic_t *ptr, rb_atomic_t val, int memory_order) #elif defined(HAVE_GCC_SYNC_BUILTINS) __sync_or_and_fetch(ptr, val); -#elif RBIMPL_COMPILER_SINCE(MSVC, 13, 0, 0) +#elif RBIMPL_COMPILER_IS(MSVC) _InterlockedOr(ptr, val); -#elif defined(_WIN32) && defined(__GNUC__) - /* This was for old MinGW. Maybe not needed any longer? */ - __asm__( - "lock\n\t" - "orl\t%1, %0" - : "=m"(ptr) - : "Ir"(val)); - -#elif defined(_WIN32) && defined(_M_IX86) - __asm mov eax, ptr; - __asm mov ecx, val; - __asm lock or [eax], ecx; - #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_or_uint(ptr, val); @@ -817,15 +804,6 @@ rbimpl_atomic_or(volatile rb_atomic_t *ptr, rb_atomic_t val, int memory_order) #endif } -/* Nobody uses this but for theoretical backwards compatibility... */ -#if RBIMPL_COMPILER_BEFORE(MSVC, 13, 0, 0) -static inline rb_atomic_t -rb_w32_atomic_or(volatile rb_atomic_t *var, rb_atomic_t val) -{ - return rbimpl_atomic_or(var, val); -} -#endif - RBIMPL_ATTR_ARTIFICIAL() RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_NONNULL((1)) @@ -1031,16 +1009,9 @@ rbimpl_atomic_cas(volatile rb_atomic_t *ptr, rb_atomic_t oldval, rb_atomic_t new #elif defined(HAVE_GCC_SYNC_BUILTINS) return __sync_val_compare_and_swap(ptr, oldval, newval); -#elif RBIMPL_COMPILER_SINCE(MSVC, 13, 0, 0) +#elif RBIMPL_COMPILER_IS(MSVC) return InterlockedCompareExchange(ptr, newval, oldval); -#elif defined(_WIN32) - PVOID *pptr = RBIMPL_CAST((PVOID *)ptr); - PVOID pold = RBIMPL_CAST((PVOID)oldval); - PVOID pnew = RBIMPL_CAST((PVOID)newval); - PVOID pret = InterlockedCompareExchange(pptr, pnew, pold); - return RBIMPL_CAST((rb_atomic_t)pret); - #elif defined(__sun) && defined(HAVE_ATOMIC_H) return atomic_cas_uint(ptr, oldval, newval); @@ -1054,15 +1025,6 @@ rbimpl_atomic_cas(volatile rb_atomic_t *ptr, rb_atomic_t oldval, rb_atomic_t new #endif } -/* Nobody uses this but for theoretical backwards compatibility... */ -#if RBIMPL_COMPILER_BEFORE(MSVC, 13, 0, 0) -static inline rb_atomic_t -rb_w32_atomic_cas(volatile rb_atomic_t *var, rb_atomic_t oldval, rb_atomic_t newval) -{ - return rbimpl_atomic_cas(var, oldval, newval); -} -#endif - RBIMPL_ATTR_ARTIFICIAL() RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_NONNULL((1)) diff --git a/include/ruby/internal/attr/deprecated.h b/include/ruby/internal/attr/deprecated.h index 9c9dea1df2794a..a374ace868adad 100644 --- a/include/ruby/internal/attr/deprecated.h +++ b/include/ruby/internal/attr/deprecated.h @@ -48,7 +48,7 @@ #elif RBIMPL_HAS_ATTRIBUTE(deprecated) /* but not with message. */ # define RBIMPL_ATTR_DEPRECATED(msg) __attribute__((__deprecated__)) -#elif RBIMPL_COMPILER_SINCE(MSVC, 14, 0, 0) +#elif RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_ATTR_DEPRECATED(msg) __declspec(deprecated msg) #elif RBIMPL_HAS_DECLSPEC_ATTRIBUTE(deprecated) diff --git a/include/ruby/internal/attr/forceinline.h b/include/ruby/internal/attr/forceinline.h index b7daafede7c42c..5b9ae794aff629 100644 --- a/include/ruby/internal/attr/forceinline.h +++ b/include/ruby/internal/attr/forceinline.h @@ -29,7 +29,7 @@ * `__forceinline` are mutually exclusive. We have to mimic that behaviour for * non-MSVC compilers. */ -#if RBIMPL_COMPILER_SINCE(MSVC, 12, 0, 0) +#if RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_ATTR_FORCEINLINE() __forceinline #elif RBIMPL_HAS_ATTRIBUTE(always_inline) # define RBIMPL_ATTR_FORCEINLINE() __attribute__((__always_inline__)) inline diff --git a/include/ruby/internal/attr/noexcept.h b/include/ruby/internal/attr/noexcept.h index 7c3f92f1e79e91..dd4c66740765af 100644 --- a/include/ruby/internal/attr/noexcept.h +++ b/include/ruby/internal/attr/noexcept.h @@ -78,7 +78,7 @@ #elif defined(__INTEL_CXX11_MODE__) # define RBIMPL_ATTR_NOEXCEPT(_) noexcept(noexcept(_)) -#elif RBIMPL_COMPILER_SINCE(MSVC, 19, 0, 0) +#elif RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_ATTR_NOEXCEPT(_) noexcept(noexcept(_)) #elif __cplusplus >= 201103L diff --git a/include/ruby/internal/attr/restrict.h b/include/ruby/internal/attr/restrict.h index e39104138c6fc2..b12fdc9dbc03dd 100644 --- a/include/ruby/internal/attr/restrict.h +++ b/include/ruby/internal/attr/restrict.h @@ -28,7 +28,7 @@ * `__has_declspec_attribute()` which involves macro substitution. */ /** Wraps (or simulates) `__declspec(restrict)` */ -#if RBIMPL_COMPILER_SINCE(MSVC, 14, 0, 0) +#if RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_ATTR_RESTRICT() __declspec(re ## strict) #elif RBIMPL_HAS_ATTRIBUTE(malloc) diff --git a/include/ruby/internal/config.h b/include/ruby/internal/config.h index da070f0979c74f..34862ded6e55c2 100644 --- a/include/ruby/internal/config.h +++ b/include/ruby/internal/config.h @@ -50,7 +50,7 @@ # define HAVE_VA_ARGS_MACRO # elif defined(__INTEL_CXX11_MODE__) # define HAVE_VA_ARGS_MACRO -# elif RBIMPL_COMPILER_SINCE(MSVC, 16, 0, 0) +# elif RBIMPL_COMPILER_IS(MSVC) # define HAVE_VA_ARGS_MACRO # else # /* NG, not known. */ diff --git a/include/ruby/internal/static_assert.h b/include/ruby/internal/static_assert.h index 7f00bc21ebec14..30bfd3bb793378 100644 --- a/include/ruby/internal/static_assert.h +++ b/include/ruby/internal/static_assert.h @@ -30,7 +30,7 @@ # /* https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations */ # define RBIMPL_STATIC_ASSERT0 static_assert -#elif defined(__cplusplus) && RBIMPL_COMPILER_SINCE(MSVC, 16, 0, 0) +#elif defined(__cplusplus) && RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_STATIC_ASSERT0 static_assert #elif defined(__INTEL_CXX11_MODE__) diff --git a/include/ruby/internal/warning_push.h b/include/ruby/internal/warning_push.h index f5981633f837b7..91d62cb00d98f3 100644 --- a/include/ruby/internal/warning_push.h +++ b/include/ruby/internal/warning_push.h @@ -79,7 +79,7 @@ */ #define RBIMPL_WARNING_IGNORED(flag) __pragma(warning(disable: flag)) -#elif RBIMPL_COMPILER_SINCE(MSVC, 12, 0, 0) +#elif RBIMPL_COMPILER_IS(MSVC) # /* Not sure exactly when but it seems VC++ 6.0 is a version with it.*/ # define RBIMPL_WARNING_PUSH() __pragma(warning(push)) # define RBIMPL_WARNING_POP() __pragma(warning(pop)) diff --git a/internal/error.h b/internal/error.h index de189698b8f2e5..cecaa5c4a8d69e 100644 --- a/internal/error.h +++ b/internal/error.h @@ -79,7 +79,7 @@ PRINTF_ARGS(void rb_warn_reserved_name(const char *removal, const char *fmt, ... # define RUBY_VERSION_BEFORE(major, minor) (RUBY_API_VERSION_CODE < (major * 10000) + (minor) * 100) # if defined(RBIMPL_WARNING_PRAGMA0) # define RBIMPL_TODO0(x) RBIMPL_WARNING_PRAGMA0(message(x)) -# elif RBIMPL_COMPILER_SINCE(MSVC, 12, 0, 0) +# elif RBIMPL_COMPILER_IS(MSVC) # define RBIMPL_TODO0(x) __pragma(message(x)) # endif diff --git a/win32/setup.mak b/win32/setup.mak index 6eeaa325aea45e..50090094552811 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -188,12 +188,16 @@ echo TEENY = RUBY_VERSION_TEENY echo ABI_VERSION = RUBY_ABI_VERSION #endif set /a MSC_VER = _MSC_VER -#if _MSC_VER >= 1920 +#ifndef _MSC_VER +# error _MSC_VER not defined +#elif _MSC_VER >= 1920 set /a MSC_VER_LOWER = MSC_VER/20*20+0 set /a MSC_VER_UPPER = MSC_VER/20*20+19 #elif _MSC_VER >= 1900 set /a MSC_VER_LOWER = MSC_VER/10*10+0 set /a MSC_VER_UPPER = MSC_VER/10*10+9 +#elif _MSC_VER < 1400 +# error Unsupported VC++ compiler #endif set MSC_VER del %0 & exit From ccc8610b660ef225b304aae7b820bad036621a87 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Jun 2024 00:21:33 +0900 Subject: [PATCH 2223/2435] Remove `RUBY_GC_HEAP_INIT_SLOTS` environment variable --- ruby.c | 7 ------- test/ruby/test_gc.rb | 7 ------- 2 files changed, 14 deletions(-) diff --git a/ruby.c b/ruby.c index b00fc1502dec1c..28f43176d61d70 100644 --- a/ruby.c +++ b/ruby.c @@ -1811,13 +1811,6 @@ ruby_opt_init(ruby_cmdline_options_t *opt) } } - /* [Feature #19785] Warning for removed GC environment variable. - * Remove this in Ruby 3.4. */ - if (getenv("RUBY_GC_HEAP_INIT_SLOTS")) { - rb_warn_deprecated("The environment variable RUBY_GC_HEAP_INIT_SLOTS", - "environment variables RUBY_GC_HEAP_%d_INIT_SLOTS"); - } - Init_ext(); /* load statically linked extensions before rubygems */ Init_extra_exts(); diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 594e2b8aa8a4ca..09199c34b1c3cc 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -453,13 +453,6 @@ class << b; end end def test_gc_parameter - env = { - "RUBY_GC_HEAP_INIT_SLOTS" => "100" - } - assert_in_out_err([env, "-W0", "-e", "exit"], "", [], []) - assert_in_out_err([env, "-W:deprecated", "-e", "exit"], "", [], - /The environment variable RUBY_GC_HEAP_INIT_SLOTS is deprecated; use environment variables RUBY_GC_HEAP_%d_INIT_SLOTS instead/) - env = {} GC.stat_heap.keys.each do |heap| env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000" From 1ca464347a526ec6539df37146a0005f95f6e3db Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 4 Jun 2024 09:27:35 +0900 Subject: [PATCH 2224/2435] CI: Warn deprecated features to be removed at this version --- .github/workflows/check_misc.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index e4d2b2c68961e5..2a2bd1df53288e 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -53,6 +53,22 @@ jobs: exit $fail working-directory: include + - id: now + run: | + date +"mon=%-m"%n"day=%-d" >> $GITHUB_OUTPUT + env: + TZ: Tokyo/Asia + + - id: deprecation + run: | + eval $(sed -n 's/^#define RUBY_API_VERSION_\(MAJOR\|MINOR\) /\1=/p' include/ruby/version.h) + if git --no-pager grep --color -o 'rb_warn_deprecated_to_remove_at('$MAJOR'\.'$MINOR',.*' -- '*.c' >&2; then + false + else + true + fi + continue-on-error: ${{ steps.now.outputs.mon < 12 }} + - name: Check if to generate documents id: rdoc run: | From 5c2f6639c53c2dcde60544b1b53ab8f5d10cd12f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:05:02 +0900 Subject: [PATCH 2225/2435] Remove `rb_clear_constant_cache` deprecated for 3 years --- include/ruby/backward.h | 2 -- internal/vm.h | 2 -- vm_method.c | 3 --- 3 files changed, 7 deletions(-) diff --git a/include/ruby/backward.h b/include/ruby/backward.h index 3a0fda9ec56dda..67261021584e66 100644 --- a/include/ruby/backward.h +++ b/include/ruby/backward.h @@ -11,8 +11,6 @@ #include "ruby/internal/interpreter.h" #include "ruby/backward/2/attributes.h" -RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() void rb_clear_constant_cache(void); - /* from version.c */ #if defined(RUBY_SHOW_COPYRIGHT_TO_DIE) && !!(RUBY_SHOW_COPYRIGHT_TO_DIE+0) # error RUBY_SHOW_COPYRIGHT_TO_DIE is deprecated diff --git a/internal/vm.h b/internal/vm.h index 09dfaf182e9a92..029b19d55561c2 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -101,8 +101,6 @@ const struct rb_callcache *rb_vm_search_method_slowpath(const struct rb_callinfo /* vm_method.c */ int rb_ec_obj_respond_to(struct rb_execution_context_struct *ec, VALUE obj, ID id, int priv); -void rb_clear_constant_cache(void); - /* vm_dump.c */ void rb_print_backtrace(FILE *); diff --git a/vm_method.c b/vm_method.c index 2b3ac74d573434..26dbe4cae8b416 100644 --- a/vm_method.c +++ b/vm_method.c @@ -325,9 +325,6 @@ rb_clear_constant_cache_for_id_i(st_data_t ic, st_data_t arg) return ST_CONTINUE; } -// Here for backward compat. -void rb_clear_constant_cache(void) {} - void rb_clear_constant_cache_for_id(ID id) { From f84110e601399c389f6d0c4b3c59be97e6dff935 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:19:20 +0900 Subject: [PATCH 2226/2435] Remove old APIs to allocate a data object deprecated for 5 years --- gc.c | 3 --- include/ruby/internal/core/rdata.h | 8 -------- include/ruby/internal/core/rtypeddata.h | 8 -------- 3 files changed, 19 deletions(-) diff --git a/gc.c b/gc.c index 87db89d04956e6..60860d1e5fe398 100644 --- a/gc.c +++ b/gc.c @@ -11,9 +11,6 @@ **********************************************************************/ -#define rb_data_object_alloc rb_data_object_alloc -#define rb_data_typed_object_alloc rb_data_typed_object_alloc - #include "ruby/internal/config.h" #ifdef _WIN32 # include "ruby/ruby.h" diff --git a/include/ruby/internal/core/rdata.h b/include/ruby/internal/core/rdata.h index f9ff3acc5febcb..6c58ddedf1be25 100644 --- a/include/ruby/internal/core/rdata.h +++ b/include/ruby/internal/core/rdata.h @@ -351,14 +351,6 @@ rb_data_object_make(VALUE klass, RUBY_DATA_FUNC mark_func, RUBY_DATA_FUNC free_f return result; } -RBIMPL_ATTR_DEPRECATED(("by: rb_data_object_wrap")) -/** @deprecated This function was renamed to rb_data_object_wrap(). */ -static inline VALUE -rb_data_object_alloc(VALUE klass, void *data, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree) -{ - return rb_data_object_wrap(klass, data, dmark, dfree); -} - /** @cond INTERNAL_MACRO */ #define rb_data_object_wrap_0 rb_data_object_wrap #define rb_data_object_wrap_1 rb_data_object_wrap_warning diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 72044562df8e3b..7d7df6c01a2d3b 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -670,12 +670,4 @@ rb_data_typed_object_make(VALUE klass, const rb_data_type_t *type, void **datap, return result; } -RBIMPL_ATTR_DEPRECATED(("by: rb_data_typed_object_wrap")) -/** @deprecated This function was renamed to rb_data_typed_object_wrap(). */ -static inline VALUE -rb_data_typed_object_alloc(VALUE klass, void *datap, const rb_data_type_t *type) -{ - return rb_data_typed_object_wrap(klass, datap, type); -} - #endif /* RBIMPL_RTYPEDDATA_H */ From a41b1540291d1469caae58d2271f3671aee7431c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:25:52 +0900 Subject: [PATCH 2227/2435] Remove `rb_complex_polar` deprecated for 7 years --- complex.c | 6 ------ include/ruby/internal/intern/complex.h | 4 ---- 2 files changed, 10 deletions(-) diff --git a/complex.c b/complex.c index bb54d4f61f31ca..a764557990e8dc 100644 --- a/complex.c +++ b/complex.c @@ -1778,12 +1778,6 @@ rb_complex_new_polar(VALUE x, VALUE y) return f_complex_polar(rb_cComplex, x, y); } -VALUE -rb_complex_polar(VALUE x, VALUE y) -{ - return rb_complex_new_polar(x, y); -} - VALUE rb_Complex(VALUE x, VALUE y) { diff --git a/include/ruby/internal/intern/complex.h b/include/ruby/internal/intern/complex.h index e111bd8ced7b6b..1efc093631f3a6 100644 --- a/include/ruby/internal/intern/complex.h +++ b/include/ruby/internal/intern/complex.h @@ -87,10 +87,6 @@ VALUE rb_complex_new(VALUE real, VALUE imag); */ VALUE rb_complex_new_polar(VALUE abs, VALUE arg); -RBIMPL_ATTR_DEPRECATED(("by: rb_complex_new_polar")) -/** @old{rb_complex_new_polar} */ -VALUE rb_complex_polar(VALUE abs, VALUE arg); - RBIMPL_ATTR_PURE() /** * Queries the real part of the passed Complex. From cf60dc2642b6c84794e7b6a1a2a1eff4b2e0f1cf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:44:46 +0900 Subject: [PATCH 2228/2435] Remove `rb_clone_setup` and `rb_dup_setup` deprecated for 4 years --- include/ruby/internal/newobj.h | 38 ---------------------------------- 1 file changed, 38 deletions(-) diff --git a/include/ruby/internal/newobj.h b/include/ruby/internal/newobj.h index 6eee2fa5fa7643..13030ae279b3cf 100644 --- a/include/ruby/internal/newobj.h +++ b/include/ruby/internal/newobj.h @@ -109,42 +109,4 @@ void rb_singleton_class_attached(VALUE klass, VALUE obj); void rb_copy_generic_ivar(VALUE clone, VALUE obj); RBIMPL_SYMBOL_EXPORT_END() -RBIMPL_ATTR_DEPRECATED(("This is no longer how Object#clone works.")) -/** - * @deprecated Not sure exactly when but at some time, the implementation of - * `Object#clone` stopped using this function. It remained - * untouched for a while, and then @shyouhei realised that they - * are no longer doing the same thing. It seems nobody seriously - * uses this function any longer. Let's just abandon it. - * - * @param[out] clone The destination object. - * @param[in] obj The source object. - */ -static inline void -rb_clone_setup(VALUE clone, VALUE obj) -{ - (void)clone; - (void)obj; - return; -} - -RBIMPL_ATTR_DEPRECATED(("This is no longer how Object#dup works.")) -/** - * @deprecated Not sure exactly when but at some time, the implementation of - * `Object#dup` stopped using this function. It remained - * untouched for a while, and then @shyouhei realised that they - * are no longer the same thing. It seems nobody seriously uses - * this function any longer. Let's just abandon it. - * - * @param[out] dup The destination object. - * @param[in] obj The source object. - */ -static inline void -rb_dup_setup(VALUE dup, VALUE obj) -{ - (void)dup; - (void)obj; - return; -} - #endif /* RBIMPL_NEWOBJ_H */ From a447d39da267cff10a4e8d07e74d65b6c4e64207 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:47:51 +0900 Subject: [PATCH 2229/2435] Remove `rb_gc_force_recycle` deprecated as "removed soon" --- include/ruby/internal/gc.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index 5ab3bb266e242f..21c2b670b3ed72 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -823,7 +823,4 @@ rb_obj_write( return a; } -RBIMPL_ATTR_DEPRECATED(("Will be removed soon")) -static inline void rb_gc_force_recycle(VALUE obj){} - #endif /* RBIMPL_GC_H */ From a3ac83b7b86f61f3bb9f82bba69e06e7cbc6a592 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:59:50 +0900 Subject: [PATCH 2230/2435] Remove taintedness/trustedness enums/macros deprecated for 4 years --- include/ruby/internal/fl_type.h | 153 -------------------------------- yjit/src/cruby_bindings.inc.rs | 2 - zjit/src/cruby_bindings.inc.rs | 2 - 3 files changed, 157 deletions(-) diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 456ec77b87490a..0f6bb26cacdc08 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -59,7 +59,6 @@ #define FL_WB_PROTECTED RBIMPL_CAST((VALUE)RUBY_FL_WB_PROTECTED) /**< @old{RUBY_FL_WB_PROTECTED} */ #define FL_PROMOTED RBIMPL_CAST((VALUE)RUBY_FL_PROMOTED) /**< @old{RUBY_FL_PROMOTED} */ #define FL_FINALIZE RBIMPL_CAST((VALUE)RUBY_FL_FINALIZE) /**< @old{RUBY_FL_FINALIZE} */ -#define FL_TAINT RBIMPL_CAST((VALUE)RUBY_FL_TAINT) /**< @old{RUBY_FL_TAINT} */ #define FL_SHAREABLE RBIMPL_CAST((VALUE)RUBY_FL_SHAREABLE) /**< @old{RUBY_FL_SHAREABLE} */ #define FL_UNTRUSTED RBIMPL_CAST((VALUE)RUBY_FL_UNTRUSTED) /**< @old{RUBY_FL_UNTRUSTED} */ #define FL_EXIVAR RBIMPL_CAST((VALUE)RUBY_FL_EXIVAR) /**< @old{RUBY_FL_EXIVAR} */ @@ -237,21 +236,6 @@ ruby_fl_type { */ RUBY_FL_FINALIZE = (1<<7), - /** - * @deprecated This flag once was a thing back in the old days, but makes - * no sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - */ - RUBY_FL_TAINT - -#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) - RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -#elif defined(_MSC_VER) -# pragma deprecated(RUBY_FL_TAINT) -#endif - - = 0, - /** * @deprecated This flag was an implementation detail that should never have * no been exposed. Exists here for backwards @@ -279,21 +263,6 @@ ruby_fl_type { */ RUBY_FL_SHAREABLE = (1<<8), - /** - * @deprecated This flag once was a thing back in the old days, but makes - * no sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - */ - RUBY_FL_UNTRUSTED - -#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) - RBIMPL_ATTR_DEPRECATED(("trustedness turned out to be a wrong idea.")) -#elif defined(_MSC_VER) -# pragma deprecated(RUBY_FL_UNTRUSTED) -#endif - - = 0, - /** * This object weakly refers to other objects. * @@ -735,128 +704,6 @@ RB_FL_REVERSE(VALUE obj, VALUE flags) } } -RBIMPL_ATTR_PURE_UNLESS_DEBUG() -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] obj Object in question. - * @return false always. - */ -static inline bool -RB_OBJ_TAINTABLE(VALUE obj) -{ - (void)obj; - return false; -} - -RBIMPL_ATTR_PURE_UNLESS_DEBUG() -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] obj Object in question. - * @return false always. - */ -static inline VALUE -RB_OBJ_TAINTED_RAW(VALUE obj) -{ - (void)obj; - return false; -} - -RBIMPL_ATTR_PURE_UNLESS_DEBUG() -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] obj Object in question. - * @return false always. - */ -static inline bool -RB_OBJ_TAINTED(VALUE obj) -{ - (void)obj; - return false; -} - -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] obj Object in question. - */ -static inline void -RB_OBJ_TAINT_RAW(VALUE obj) -{ - (void)obj; - return; -} - -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] obj Object in question. - */ -static inline void -RB_OBJ_TAINT(VALUE obj) -{ - (void)obj; - return; -} - -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] dst Victim object. - * @param[in] src Infectant object. - */ -static inline void -RB_OBJ_INFECT_RAW(VALUE dst, VALUE src) -{ - (void)dst; - (void)src; - return; -} - -RBIMPL_ATTR_ARTIFICIAL() -RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) -/** - * @deprecated This function once was a thing in the old days, but makes no - * sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - * - * @param[in] dst Victim object. - * @param[in] src Infectant object. - */ -static inline void -RB_OBJ_INFECT(VALUE dst, VALUE src) -{ - (void)dst; - (void)src; - return; -} - RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index c081bc9e22abab..61dbf9b5c35929 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -225,10 +225,8 @@ pub const RUBY_FL_WB_PROTECTED: ruby_fl_type = 32; pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; pub const RUBY_FL_USERPRIV0: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; -pub const RUBY_FL_TAINT: ruby_fl_type = 0; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; -pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; pub const RUBY_FL_WEAK_REFERENCE: ruby_fl_type = 512; pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 8f5ea5b8a837cd..5bb62e89fa8d3e 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -286,10 +286,8 @@ pub const RUBY_FL_WB_PROTECTED: ruby_fl_type = 32; pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; pub const RUBY_FL_USERPRIV0: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; -pub const RUBY_FL_TAINT: ruby_fl_type = 0; pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; -pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; pub const RUBY_FL_WEAK_REFERENCE: ruby_fl_type = 512; pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; From 2ac4cc0a1bcd55be456aa12c1b6d7f52a5725fed Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 14:01:39 +0900 Subject: [PATCH 2231/2435] Remove `RUBY_FL_DUPPED` deprecated for 4 years --- include/ruby/internal/fl_type.h | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 0f6bb26cacdc08..2afb3f1fa348f2 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -363,23 +363,6 @@ ruby_fl_type { RUBY_FL_SINGLETON = RUBY_FL_USER1, }; -enum { - /** - * @deprecated This flag once was a thing back in the old days, but makes - * no sense any longer today. Exists here for backwards - * compatibility only. You can safely forget about it. - */ - RUBY_FL_DUPPED - -#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) - RBIMPL_ATTR_DEPRECATED(("It seems there is no actual usage of this enum.")) -#elif defined(_MSC_VER) -# pragma deprecated(RUBY_FL_DUPPED) -#endif - - = (int)RUBY_T_MASK -}; - #undef RBIMPL_HAVE_ENUM_ATTRIBUTE RBIMPL_SYMBOL_EXPORT_BEGIN() From 8c7b1401a50687dfd1d0c253c03eefdb8815154a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 14:16:11 +0900 Subject: [PATCH 2232/2435] Remove `rb_iterate` deprecated since 1.9 --- ext/-test-/cxxanyargs/cxxanyargs.cpp | 26 ------------- include/ruby/backward/cxxanyargs.hpp | 27 ------------- include/ruby/internal/iterator.h | 42 --------------------- spec/ruby/optional/capi/array_spec.rb | 48 ++++++++++++------------ spec/ruby/optional/capi/ext/array_spec.c | 8 ++++ vm_eval.c | 7 ---- 6 files changed, 33 insertions(+), 125 deletions(-) diff --git a/ext/-test-/cxxanyargs/cxxanyargs.cpp b/ext/-test-/cxxanyargs/cxxanyargs.cpp index eded13e2eec00a..c7df7f9038272b 100644 --- a/ext/-test-/cxxanyargs/cxxanyargs.cpp +++ b/ext/-test-/cxxanyargs/cxxanyargs.cpp @@ -97,31 +97,6 @@ struct test_rb_define_hooked_variable { }; VALUE test_rb_define_hooked_variable::v = Qundef; -namespace test_rb_iterate { - VALUE - iter(VALUE self) - { - return rb_funcall(self, rb_intern("yield"), 0); - } - - VALUE - block(RB_BLOCK_CALL_FUNC_ARGLIST(arg, param)) - { - return rb_funcall(arg, rb_intern("=="), 1, param); - } - - VALUE - test(VALUE self) - { -#ifdef HAVE_NULLPTR - rb_iterate(iter, self, nullptr, self); -#endif - - rb_iterate(iter, self, RUBY_METHOD_FUNC(block), self); // old - return rb_iterate(iter, self, block, self); // new - } -} - namespace test_rb_block_call { VALUE block(RB_BLOCK_CALL_FUNC_ARGLIST(arg, param)) @@ -936,7 +911,6 @@ Init_cxxanyargs(void) test(rb_define_virtual_variable); test(rb_define_hooked_variable); - test(rb_iterate); test(rb_block_call); test(rb_rescue); test(rb_rescue2); diff --git a/include/ruby/backward/cxxanyargs.hpp b/include/ruby/backward/cxxanyargs.hpp index 2414b7ae6d2461..d37495dd942790 100644 --- a/include/ruby/backward/cxxanyargs.hpp +++ b/include/ruby/backward/cxxanyargs.hpp @@ -190,33 +190,6 @@ rb_define_hooked_variable(const char *q, VALUE *w, std::nullptr_t e, void_type * /// @name Exceptions and tag jumps /// @{ -// RUBY_CXX_DEPRECATED("by rb_block_call since 1.9") -RUBY_CXX_DEPRECATED("Use of ANYARGS in this function is deprecated") -/// @brief Old way to implement iterators. -/// @param[in] q A function that can yield. -/// @param[in] w Passed to `q`. -/// @param[in] e What is to be yielded. -/// @param[in] r Passed to `e`. -/// @return The return value of `q`. -/// @note `e` can be nullptr. -/// @deprecated This function is obsoleted since long before 2.x era. Do not -/// use it any longer. rb_block_call() is provided instead. -inline VALUE -rb_iterate(onearg_type *q, VALUE w, type *e, VALUE r) -{ - rb_block_call_func_t t = reinterpret_cast(e); - return backward::rb_iterate_deprecated(q, w, t, r); -} - -#ifdef HAVE_NULLPTR -RUBY_CXX_DEPRECATED("by rb_block_call since 1.9") -inline VALUE -rb_iterate(onearg_type *q, VALUE w, std::nullptr_t e, VALUE r) -{ - return backward::rb_iterate_deprecated(q, w, e, r); -} -#endif - RUBY_CXX_DEPRECATED("Use of ANYARGS in this function is deprecated") /// @brief Call a method with a block. /// @param[in] q The self. diff --git a/include/ruby/internal/iterator.h b/include/ruby/internal/iterator.h index 60e3535fd97a24..891045363ef5ba 100644 --- a/include/ruby/internal/iterator.h +++ b/include/ruby/internal/iterator.h @@ -265,48 +265,6 @@ int rb_block_given_p(void); */ void rb_need_block(void); -#ifndef __cplusplus -RBIMPL_ATTR_DEPRECATED(("by: rb_block_call since 1.9")) -#endif -/** - * Old way to iterate a block. - * - * @deprecated This is an old API. Use rb_block_call() instead. - * @warning The passed function must at least once call a ruby method - * (to handle interrupts etc.) - * @param[in] func1 A function that could yield a value. - * @param[in,out] data1 Passed to `func1` - * @param[in] proc A function acts as a block. - * @param[in,out] data2 Passed to `proc` as the data2 parameter. - * @return What `func1` returns. - */ -VALUE rb_iterate(VALUE (*func1)(VALUE), VALUE data1, rb_block_call_func_t proc, VALUE data2); - -#ifdef __cplusplus -namespace ruby { -namespace backward { -/** - * Old way to iterate a block. - * - * @deprecated This is an old API. Use rb_block_call() instead. - * @warning The passed function must at least once call a ruby method - * (to handle interrupts etc.) - * @param[in] iter A function that could yield a value. - * @param[in,out] data1 Passed to `func1` - * @param[in] bl A function acts as a block. - * @param[in,out] data2 Passed to `proc` as the data2 parameter. - * @return What `func1` returns. - */ -static inline VALUE -rb_iterate_deprecated(VALUE (*iter)(VALUE), VALUE data1, rb_block_call_func_t bl, VALUE data2) -{ - return ::rb_iterate(iter, data1, bl, data2); -}}} - -RBIMPL_ATTR_DEPRECATED(("by: rb_block_call since 1.9")) -VALUE rb_iterate(VALUE (*func1)(VALUE), VALUE data1, rb_block_call_func_t proc, VALUE data2); -#endif - /** * Identical to rb_funcallv(), except it additionally passes a function as a * block. When the method yields, `proc` is called with the yielded value as diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb index 9c35017e211ed6..7e878598566659 100644 --- a/spec/ruby/optional/capi/array_spec.rb +++ b/spec/ruby/optional/capi/array_spec.rb @@ -343,37 +343,39 @@ end end - describe "rb_iterate" do - it "calls an callback function as a block passed to an method" do - s = [1,2,3,4] - s2 = @s.rb_iterate(s) + ruby_version_is ""..."4.0" do + describe "rb_iterate" do + it "calls an callback function as a block passed to an method" do + s = [1,2,3,4] + s2 = @s.rb_iterate(s) - s2.should == s + s2.should == s - # Make sure they're different objects - s2.equal?(s).should be_false - end + # Make sure they're different objects + s2.equal?(s).should be_false + end - it "calls a function with the other function available as a block" do - h = {a: 1, b: 2} + it "calls a function with the other function available as a block" do + h = {a: 1, b: 2} - @s.rb_iterate_each_pair(h).sort.should == [1,2] - end + @s.rb_iterate_each_pair(h).sort.should == [1,2] + end - it "calls a function which can yield into the original block" do - s2 = [] + it "calls a function which can yield into the original block" do + s2 = [] - o = Object.new - def o.each - yield 1 - yield 2 - yield 3 - yield 4 - end + o = Object.new + def o.each + yield 1 + yield 2 + yield 3 + yield 4 + end - @s.rb_iterate_then_yield(o) { |x| s2 << x } + @s.rb_iterate_then_yield(o) { |x| s2 << x } - s2.should == [1,2,3,4] + s2.should == [1,2,3,4] + end end end diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 2347798bb47c64..628c4df9d74a60 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -196,6 +196,7 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { return rb_ary_push(new_ary, el); } +#ifndef RUBY_VERSION_IS_4_0 static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -203,6 +204,7 @@ static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { return new_ary; } +#endif static VALUE array_spec_rb_block_call(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -216,6 +218,7 @@ static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { return rb_ary_push(holder, rb_ary_entry(el, 1)); } +#ifndef RUBY_VERSION_IS_4_0 static VALUE each_pair(VALUE obj) { return rb_funcall(obj, rb_intern("each_pair"), 0); } @@ -227,6 +230,7 @@ static VALUE array_spec_rb_iterate_each_pair(VALUE self, VALUE obj) { return new_ary; } +#endif static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { VALUE new_ary = rb_ary_new(); @@ -241,10 +245,12 @@ static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { return Qnil; } +#ifndef RUBY_VERSION_IS_4_0 static VALUE array_spec_rb_iterate_then_yield(VALUE self, VALUE obj) { rb_iterate(rb_each, obj, iter_yield, obj); return Qnil; } +#endif static VALUE array_spec_rb_block_call_then_yield(VALUE self, VALUE obj) { rb_block_call(obj, rb_intern("each"), 0, 0, iter_yield, obj); @@ -308,9 +314,11 @@ void Init_array_spec(void) { rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2); rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2); rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2); +#ifndef RUBY_VERSION_IS_4_0 rb_define_method(cls, "rb_iterate", array_spec_rb_iterate, 1); rb_define_method(cls, "rb_iterate_each_pair", array_spec_rb_iterate_each_pair, 1); rb_define_method(cls, "rb_iterate_then_yield", array_spec_rb_iterate_then_yield, 1); +#endif rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1); rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1); rb_define_method(cls, "rb_block_call_then_yield", array_spec_rb_block_call_then_yield, 1); diff --git a/vm_eval.c b/vm_eval.c index 34560d704a15a5..707344718bced0 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1533,13 +1533,6 @@ rb_iterate_internal(VALUE (* it_proc)(VALUE), VALUE data1, GET_EC()); } -VALUE -rb_iterate(VALUE (* it_proc)(VALUE), VALUE data1, - rb_block_call_func_t bl_proc, VALUE data2) -{ - return rb_iterate_internal(it_proc, data1, bl_proc, data2); -} - struct iter_method_arg { VALUE obj; ID mid; From c3424615821192488f644d41eff21fa349a1eb2f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 26 Dec 2025 19:33:03 +0900 Subject: [PATCH 2233/2435] Remove an useless macro definition `RSTRUCT_PTR` The underlying definition, `rb_struct_ptr`, was removed four years ago. --- include/ruby/internal/core/rstruct.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/ruby/internal/core/rstruct.h b/include/ruby/internal/core/rstruct.h index 69be487b592c7f..0028a1bdcdbdc0 100644 --- a/include/ruby/internal/core/rstruct.h +++ b/include/ruby/internal/core/rstruct.h @@ -31,18 +31,6 @@ # include "ruby/backward.h" #endif -/** - * @private - * - * @deprecated This macro once was a thing in the old days, but makes no sense - * any longer today. Exists here for backwards compatibility - * only. You can safely forget about it. - * - * @internal - * - * Declaration of rb_struct_ptr() is at include/ruby/backward.h. - */ -#define RSTRUCT_PTR(st) rb_struct_ptr(st) /** @cond INTERNAL_MACRO */ #define RSTRUCT_LEN RSTRUCT_LEN #define RSTRUCT_SET RSTRUCT_SET From b304c149aa2fe845872ccdf6ea88e7fe155c61f3 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 26 Dec 2025 09:14:57 +0100 Subject: [PATCH 2234/2435] [DOC] Remove outdated documentation about command injection [Feature #19630] This dangerous behavior was removed in 4.0 (996cae65f3cc8fed60c6bb758b00882cac49389d) but the documentation wasn't updated. --- doc/security/command_injection.rdoc | 22 ---------------------- io.c | 27 --------------------------- 2 files changed, 49 deletions(-) diff --git a/doc/security/command_injection.rdoc b/doc/security/command_injection.rdoc index ee33d4a04e6629..d46e42f7be73fb 100644 --- a/doc/security/command_injection.rdoc +++ b/doc/security/command_injection.rdoc @@ -13,25 +13,3 @@ These methods include: - {\`command` (backtick method)}[rdoc-ref:Kernel#`] (also called by the expression %x[command]). - IO.popen (when called with other than "-"). - -Some methods execute a system command only if the given path name starts -with a |: - -- Kernel.open(command). -- IO.read(command). -- IO.write(command). -- IO.binread(command). -- IO.binwrite(command). -- IO.readlines(command). -- IO.foreach(command). -- URI.open(command). - -Note that some of these methods do not execute commands when called -from subclass +File+: - -- File.read(path). -- File.write(path). -- File.binread(path). -- File.binwrite(path). -- File.readlines(path). -- File.foreach(path). diff --git a/io.c b/io.c index 42017b1c253035..7088f036c564cb 100644 --- a/io.c +++ b/io.c @@ -8256,9 +8256,6 @@ rb_io_s_sysopen(int argc, VALUE *argv, VALUE _) * * Creates an IO object connected to the given file. * - * This method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * * With no block given, file stream is returned: * * open('t.txt') # => # @@ -12054,10 +12051,6 @@ io_s_foreach(VALUE v) * * Calls the block with each successive line read from the stream. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * * The first argument must be a string that is the path to a file. * * With only argument +path+ given, parses lines from the file at the given +path+, @@ -12157,10 +12150,6 @@ io_s_readlines(VALUE v) * * Returns an array of all lines read from the stream. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * * The first argument must be a string that is the path to a file. * * With only argument +path+ given, parses lines from the file at the given +path+, @@ -12246,10 +12235,6 @@ seek_before_access(VALUE argp) * Opens the stream, reads and returns some or all of its content, * and closes the stream; returns +nil+ if no bytes were read. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * * The first argument must be a string that is the path to a file. * * With only argument +path+ given, reads in text mode and returns the entire content @@ -12317,10 +12302,6 @@ rb_io_s_read(int argc, VALUE *argv, VALUE io) * Behaves like IO.read, except that the stream is opened in binary mode * with ASCII-8BIT encoding. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * */ static VALUE @@ -12421,10 +12402,6 @@ io_s_write(int argc, VALUE *argv, VALUE klass, int binary) * Opens the stream, writes the given +data+ to it, * and closes the stream; returns the number of bytes written. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * * The first argument must be a string that is the path to a file. * * With only argument +path+ given, writes the given +data+ to the file at that path: @@ -12471,10 +12448,6 @@ rb_io_s_write(int argc, VALUE *argv, VALUE io) * Behaves like IO.write, except that the stream is opened in binary mode * with ASCII-8BIT encoding. * - * When called from class \IO (but not subclasses of \IO), - * this method has potential security vulnerabilities if called with untrusted input; - * see {Command Injection}[rdoc-ref:security/command_injection.rdoc]. - * */ static VALUE From 6ae69e9ac1db89c0ee2e8ced251cc31e7f0c96d2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 26 Dec 2025 10:03:49 -0500 Subject: [PATCH 2235/2435] [DOC] Remove args from call-seq of Method#call --- proc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proc.c b/proc.c index 4f775c899228b5..5cfcfb08ad05ab 100644 --- a/proc.c +++ b/proc.c @@ -2644,8 +2644,8 @@ method_dup(VALUE self) /* * call-seq: - * meth.call(args, ...) -> obj - * meth[args, ...] -> obj + * meth.call(...) -> obj + * meth[...] -> obj * method === obj -> result_of_method * * Invokes the meth with the specified arguments, returning the From 7b3b1a1442a6665ab300207bf064ae360272525a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 26 Dec 2025 10:04:51 -0500 Subject: [PATCH 2236/2435] [DOC] Use self in call-seq for Method#call --- proc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proc.c b/proc.c index 5cfcfb08ad05ab..d72f8d952e9465 100644 --- a/proc.c +++ b/proc.c @@ -2644,11 +2644,11 @@ method_dup(VALUE self) /* * call-seq: - * meth.call(...) -> obj - * meth[...] -> obj - * method === obj -> result_of_method + * call(...) -> obj + * self[...] -> obj + * self === obj -> result_of_method * - * Invokes the meth with the specified arguments, returning the + * Invokes +self+ with the specified arguments, returning the * method's return value. * * m = 12.method("+") From dedde996762dbed72d3f1e11a02d69a9d080edc4 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 26 Dec 2025 14:06:21 -0600 Subject: [PATCH 2237/2435] [DOC] Use Japanese for multi-byte characters (#15745) --- doc/examples/files.rdoc | 8 +++---- io.c | 50 ++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/doc/examples/files.rdoc b/doc/examples/files.rdoc index f7361327709812..cb400c81be17ca 100644 --- a/doc/examples/files.rdoc +++ b/doc/examples/files.rdoc @@ -7,8 +7,8 @@ text = <<~EOT Fifth line EOT -# Russian text. -russian = "\u{442 435 441 442}" # => "тест" +# Japanese text. +japanese = 'こんにちは' # Binary data. data = "\u9990\u9991\u9992\u9993\u9994" @@ -16,8 +16,8 @@ data = "\u9990\u9991\u9992\u9993\u9994" # Text file. File.write('t.txt', text) -# File with Russian text. -File.write('t.rus', russian) +# File with Japanese text. +File.write('t.ja', japanese) # File with binary data. f = File.new('t.dat', 'wb:UTF-16') diff --git a/io.c b/io.c index 7088f036c564cb..95664c023098b4 100644 --- a/io.c +++ b/io.c @@ -4720,10 +4720,11 @@ rb_io_each_line(int argc, VALUE *argv, VALUE io) * Calls the given block with each byte (0..255) in the stream; returns +self+. * See {Byte IO}[rdoc-ref:IO@Byte+IO]. * - * f = File.new('t.rus') + * File.read('t.ja') # => "こんにちは" + * f = File.new('t.ja') * a = [] * f.each_byte {|b| a << b } - * a # => [209, 130, 208, 181, 209, 129, 209, 130] + * a # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] * f.close * * Returns an Enumerator if no block is given. @@ -4868,10 +4869,11 @@ io_getc(rb_io_t *fptr, rb_encoding *enc) * Calls the given block with each character in the stream; returns +self+. * See {Character IO}[rdoc-ref:IO@Character+IO]. * - * f = File.new('t.rus') + * File.read('t.ja') # => "こんにちは" + * f = File.new('t.ja') * a = [] * f.each_char {|c| a << c.ord } - * a # => [1090, 1077, 1089, 1090] + * a # => [12371, 12435, 12395, 12385, 12399] * f.close * * Returns an Enumerator if no block is given. @@ -4906,10 +4908,11 @@ rb_io_each_char(VALUE io) * * Calls the given block with each codepoint in the stream; returns +self+: * - * f = File.new('t.rus') + * File.read('t.ja') # => "こんにちは" + * f = File.new('t.ja') * a = [] * f.each_codepoint {|c| a << c } - * a # => [1090, 1077, 1089, 1090] + * a # => [12371, 12435, 12395, 12385, 12399] * f.close * * Returns an Enumerator if no block is given. @@ -5023,8 +5026,9 @@ rb_io_each_codepoint(VALUE io) * f = File.open('t.txt') * f.getc # => "F" * f.close - * f = File.open('t.rus') - * f.getc.ord # => 1090 + * File.read('t.ja') # => "こんにちは" + * f = File.open('t.ja') + * f.getc.ord # => 12371 * f.close * * Related: IO#readchar (may raise EOFError). @@ -5056,8 +5060,9 @@ rb_io_getc(VALUE io) * f = File.open('t.txt') * f.readchar # => "F" * f.close - * f = File.open('t.rus') - * f.readchar.ord # => 1090 + * File.read('t.ja') # => "こんにちは" + * f = File.open('t.ja') + * f.readchar.ord # => 12371 * f.close * * Related: IO#getc (will not raise EOFError). @@ -5086,8 +5091,9 @@ rb_io_readchar(VALUE io) * f = File.open('t.txt') * f.getbyte # => 70 * f.close - * f = File.open('t.rus') - * f.getbyte # => 209 + * File.read('t.ja') # => "こんにちは" + * f = File.open('t.ja') + * f.getbyte # => 227 * f.close * * Related: IO#readbyte (may raise EOFError). @@ -5130,8 +5136,9 @@ rb_io_getbyte(VALUE io) * f = File.open('t.txt') * f.readbyte # => 70 * f.close - * f = File.open('t.rus') - * f.readbyte # => 209 + * File.read('t.ja') # => "こんにちは" + * f = File.open('t.ja') + * f.readbyte # => 227 * f.close * * Related: IO#getbyte (will not raise EOFError). @@ -9492,7 +9499,8 @@ static VALUE io_initialize(VALUE io, VALUE fnum, VALUE vmode, VALUE opt); * The new \IO object does not inherit encoding * (because the integer file descriptor does not have an encoding): * - * fd = IO.sysopen('t.rus', 'rb') + * File.read('t.ja') # => "こんにちは" + * fd = IO.sysopen('t.ja', 'rb') * io = IO.new(fd) * io.external_encoding # => # # Not ASCII-8BIT. * @@ -15304,11 +15312,13 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * File.open('t.txt') {|f| f.gets(11) } # => "First line\n" * File.open('t.txt') {|f| f.gets(12) } # => "First line\n" * - * # Text with 2-byte characters, which will not be split. - * File.open('t.rus') {|f| f.gets(1).size } # => 1 - * File.open('t.rus') {|f| f.gets(2).size } # => 1 - * File.open('t.rus') {|f| f.gets(3).size } # => 2 - * File.open('t.rus') {|f| f.gets(4).size } # => 2 + * # Text with 3-byte characters, which will not be split. + * File.read('t.ja') # => "こんにちは" + * File.open('t.ja') {|f| f.gets(1).size } # => 1 + * File.open('t.ja') {|f| f.gets(2).size } # => 1 + * File.open('t.ja') {|f| f.gets(3).size } # => 1 + * File.open('t.ja') {|f| f.gets(4).size } # => 2 + * File.open('t.ja') {|f| f.gets(5).size } # => 2 * * ===== Line Separator and Line Limit * From b8201a09e026b3f2948a19eddf1341eb4b9668cf Mon Sep 17 00:00:00 2001 From: git Date: Sat, 27 Dec 2025 06:53:51 +0000 Subject: [PATCH 2238/2435] Update bundled gems list as of 2025-12-27 --- NEWS.md | 9 +++++++++ gems/bundled_gems | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 59a2b4b49d4c44..9712152da6a7b9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,15 @@ releases. ### The following bundled gems are updated. +* minitest 6.0.1 +* test-unit 3.7.6 +* rss 0.3.2 +* net-imap 0.6.2 +* typeprof 0.31.1 +* debug 1.11.1 +* mutex_m 0.3.0 +* rdoc 7.0.3 + ### RubyGems and Bundler Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for details. diff --git a/gems/bundled_gems b/gems/bundled_gems index 9461122e62dd73..f4afd8e9c27ac9 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,10 +6,10 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 6.0.0 https://github.com/minitest/minitest +minitest 6.0.1 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.5 https://github.com/test-unit/test-unit +test-unit 3.7.6 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From 3c9e61f5ef8190e082b36e056f59be49692ae8aa Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 27 Dec 2025 16:18:58 +0900 Subject: [PATCH 2239/2435] [ruby/openssl] cipher: remove incorrect assertion in Cipher#update Commit https://github.com/ruby/openssl/commit/1de3b80a46c2 (cipher: make output buffer String independent, 2024-12-10) ensures the output buffer String has sufficient capacity, bu the length can be shorter. The assert() is simply incorrect and should be removed. Also remove a similar assert() in Cipher#final. While not incorrect, it is not useful either. https://github.com/ruby/openssl/commit/0ce6ab97dd --- ext/openssl/ossl_cipher.c | 13 ++++++------- test/openssl/test_cipher.rb | 13 +++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index db65e99888d3bf..f3cd247c8fdfbc 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -401,9 +401,9 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) } out_len = in_len + EVP_MAX_BLOCK_LENGTH; - if (NIL_P(str)) { - str = rb_str_new(0, out_len); - } else { + if (NIL_P(str)) + str = rb_str_buf_new(out_len); + else { StringValue(str); if ((long)rb_str_capacity(str) >= out_len) rb_str_modify(str); @@ -411,9 +411,9 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) rb_str_modify_expand(str, out_len - RSTRING_LEN(str)); } - if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len)) - ossl_raise(eCipherError, NULL); - assert(out_len <= RSTRING_LEN(str)); + if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), + &out_len, in, in_len)) + ossl_raise(eCipherError, "EVP_CipherUpdate"); rb_str_set_len(str, out_len); return str; @@ -456,7 +456,6 @@ ossl_cipher_final(VALUE self) ossl_raise(eCipherError, "cipher final failed"); } } - assert(out_len <= RSTRING_LEN(str)); rb_str_set_len(str, out_len); return str; diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 93766cfc88ce17..5b867671d312ad 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -134,13 +134,14 @@ def test_ctr_if_exists def test_update_with_buffer cipher = OpenSSL::Cipher.new("aes-128-ecb").encrypt cipher.random_key - expected = cipher.update("data") << cipher.final - assert_equal 16, expected.bytesize + expected = cipher.update("data" * 10) << cipher.final + assert_equal 48, expected.bytesize # Buffer is supplied cipher.reset buf = String.new - assert_same buf, cipher.update("data", buf) + assert_same buf, cipher.update("data" * 10, buf) + assert_equal 32, buf.bytesize assert_equal expected, buf + cipher.final # Buffer is frozen @@ -149,9 +150,9 @@ def test_update_with_buffer # Buffer is a shared string [ruby-core:120141] [Bug #20937] cipher.reset - buf = "x" * 1024 - shared = buf[-("data".bytesize + 32)..-1] - assert_same shared, cipher.update("data", shared) + buf = "x".b * 1024 + shared = buf[-("data".bytesize * 10 + 32)..-1] + assert_same shared, cipher.update("data" * 10, shared) assert_equal expected, shared + cipher.final end From a8c3d5e127776d74eb068c95610277feb99adcf0 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:26:56 +0900 Subject: [PATCH 2240/2435] Fix: Do not fast_fallback if local_port is explicitly specified (#15732) `fast fallback` cannot be used with explicitly specified local port, because concurrent binds to the same `local_host:local_port` can raise `Errno::EADDRINUSE`. This issue is more likely to occur on hosts with `IPV6_V6ONLY` disabled, because IPv6 binds can also occupy IPv4-mapped IPv6 address space. --- ext/socket/ipsocket.c | 24 +++++++++++++++++++++--- ext/socket/lib/socket.rb | 5 ++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index e952b7871b3f6f..758d37293ffc2c 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -258,6 +258,22 @@ is_specified_ip_address(const char *hostname) inet_pton(AF_INET, hostname, &ipv4addr) == 1); } +static int +is_local_port_fixed(const char *portp) +{ + if (!portp) return 0; + + char *endp; + errno = 0; + long port = strtol(portp, &endp, 10); + + if (endp == portp) return 0; + if (errno == ERANGE) return 0; + if (port <= 0) return 0; + + return port != 0; +} + struct fast_fallback_inetsock_arg { VALUE self; @@ -1314,13 +1330,15 @@ rsock_init_inetsock( if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) { struct rb_addrinfo *local_res = NULL; - char *hostp, *portp; - char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; + char *hostp, *portp, *local_portp; + char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV], local_pbuf[NI_MAXSERV]; int additional_flags = 0; + int local_flags = 0; hostp = raddrinfo_host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags); portp = raddrinfo_port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags); + local_portp = raddrinfo_port_str(local_serv, local_pbuf, sizeof(local_pbuf), &local_flags); - if (!is_specified_ip_address(hostp)) { + if (!is_specified_ip_address(hostp) && !is_local_port_fixed(local_portp)) { int target_families[2] = { 0, 0 }; int resolving_family_size = 0; diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index e74eaec43a3318..36fcceaee96300 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -660,12 +660,11 @@ def accept_nonblock(exception: true) # puts sock.read # } def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket - if open_timeout && (connect_timeout || resolv_timeout) raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" end - sock = if fast_fallback && !(host && ip_address?(host)) + sock = if fast_fallback && !(host && ip_address?(host)) && !(local_port && local_port.to_i != 0) tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) else tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) @@ -736,7 +735,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if local_addrinfos.any? local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily } - if local_addrinfo.nil? # Connecting addrinfoと同じアドレスファミリのLocal addrinfoがない + if local_addrinfo.nil? if resolution_store.any_addrinfos? # Try other Addrinfo in next "while" next From 38d24294acdf2fba8ab54fc05483e881b16dc7f1 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 27 Dec 2025 13:50:32 +0000 Subject: [PATCH 2241/2435] [DOC] Multibyte chars Japanese only --- doc/string/aref.rdoc | 2 -- doc/string/aset.rdoc | 4 ---- doc/string/bytes.rdoc | 1 - doc/string/bytesize.rdoc | 3 --- doc/string/capitalize.rdoc | 2 -- doc/string/center.rdoc | 1 - doc/string/chars.rdoc | 1 - doc/string/swapcase.rdoc | 1 - 8 files changed, 15 deletions(-) diff --git a/doc/string/aref.rdoc b/doc/string/aref.rdoc index ee4c3d33d4e655..59c6ae97ace01e 100644 --- a/doc/string/aref.rdoc +++ b/doc/string/aref.rdoc @@ -8,7 +8,6 @@ returns the 1-character substring found in self at character offset index: 'hello'[0] # => "h" 'hello'[4] # => "o" 'hello'[5] # => nil - 'Привет'[2] # => "и" 'こんにちは'[4] # => "は" With negative integer argument +index+ given, @@ -92,7 +91,6 @@ returns the matching substring of +self+, if found: 'hello'['ell'] # => "ell" 'hello'[''] # => "" 'hello'['nosuch'] # => nil - 'Привет'['ив'] # => "ив" 'こんにちは'['んにち'] # => "んにち" Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/aset.rdoc b/doc/string/aset.rdoc index db9079ebfb8188..98c58b59cc43a1 100644 --- a/doc/string/aset.rdoc +++ b/doc/string/aset.rdoc @@ -170,10 +170,6 @@ With string argument +substring+ given: s['ll'] = 'foo' # => "foo" s # => "hefooo" - s = 'Привет' - s['ив'] = 'foo' # => "foo" - s # => "Прfooет" - s = 'こんにちは' s['んにち'] = 'foo' # => "foo" s # => "こfooは" diff --git a/doc/string/bytes.rdoc b/doc/string/bytes.rdoc index 3815f13276fa11..6dde0a745d9b84 100644 --- a/doc/string/bytes.rdoc +++ b/doc/string/bytes.rdoc @@ -1,7 +1,6 @@ Returns an array of the bytes in +self+: 'hello'.bytes # => [104, 101, 108, 108, 111] - 'Привет'.bytes # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] 'こんにちは'.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] diff --git a/doc/string/bytesize.rdoc b/doc/string/bytesize.rdoc index cbb7f439fcb448..8d12a0d454fbac 100644 --- a/doc/string/bytesize.rdoc +++ b/doc/string/bytesize.rdoc @@ -5,9 +5,6 @@ Note that the byte count may be different from the character count (returned by s = 'foo' s.bytesize # => 3 s.size # => 3 - s = 'Привет' - s.bytesize # => 12 - s.size # => 6 s = 'こんにちは' s.bytesize # => 15 s.size # => 5 diff --git a/doc/string/capitalize.rdoc b/doc/string/capitalize.rdoc index 9b26c0215342db..3a1a2dcb8b7c90 100644 --- a/doc/string/capitalize.rdoc +++ b/doc/string/capitalize.rdoc @@ -10,8 +10,6 @@ Examples: 'HELLO'.capitalize # => "Hello" 'straße'.capitalize # => "Straße" # Lowercase 'ß' not changed. 'STRAẞE'.capitalize # => "Straße" # Uppercase 'ẞ' downcased to 'ß'. - 'привет'.capitalize # => "Привет" - 'ПРИВЕТ'.capitalize # => "Привет" Some characters (and some character sets) do not have upcase and downcase versions; see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: diff --git a/doc/string/center.rdoc b/doc/string/center.rdoc index 3116d211174286..b86c8b59169933 100644 --- a/doc/string/center.rdoc +++ b/doc/string/center.rdoc @@ -9,7 +9,6 @@ centered and padded on one or both ends with +pad_string+: 'hello'.center(20, '-|') # => "-|-|-|-hello-|-|-|-|" # Some padding repeated. 'hello'.center(10, 'abcdefg') # => "abhelloabc" # Some padding not used. ' hello '.center(13) # => " hello " - 'Привет'.center(10) # => " Привет " 'こんにちは'.center(10) # => " こんにちは " # Multi-byte characters. If +size+ is less than or equal to the size of +self+, returns an unpadded copy of +self+: diff --git a/doc/string/chars.rdoc b/doc/string/chars.rdoc index 97ea07331f4c46..d4d15bf2ad541d 100644 --- a/doc/string/chars.rdoc +++ b/doc/string/chars.rdoc @@ -1,7 +1,6 @@ Returns an array of the characters in +self+: 'hello'.chars # => ["h", "e", "l", "l", "o"] - 'Привет'.chars # => ["П", "р", "и", "в", "е", "т"] 'こんにちは'.chars # => ["こ", "ん", "に", "ち", "は"] ''.chars # => [] diff --git a/doc/string/swapcase.rdoc b/doc/string/swapcase.rdoc index 916e711b7ec7b2..4353c8528a26b4 100644 --- a/doc/string/swapcase.rdoc +++ b/doc/string/swapcase.rdoc @@ -7,7 +7,6 @@ Examples: 'Hello'.swapcase # => "hELLO" 'Straße'.swapcase # => "sTRASSE" - 'Привет'.swapcase # => "пРИВЕТ" 'RubyGems.org'.swapcase # => "rUBYgEMS.ORG" The sizes of +self+ and the upcased result may differ: From a92c0342dd35efac8c08845b23412e5f70ecd769 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 27 Dec 2025 14:12:02 +0000 Subject: [PATCH 2242/2435] [DOC] Japanese only for multi-byte chars examples --- doc/string/chomp.rdoc | 1 - doc/string/chop.rdoc | 2 -- doc/string/chr.rdoc | 1 - doc/string/codepoints.rdoc | 1 - doc/string/concat.rdoc | 1 - doc/string/count.rdoc | 4 ---- doc/string/delete.rdoc | 4 ---- doc/string/delete_prefix.rdoc | 1 - doc/string/delete_suffix.rdoc | 1 - doc/string/dump.rdoc | 8 -------- doc/string/each_byte.rdoc | 3 --- doc/string/each_char.rdoc | 5 ----- doc/string/each_codepoint.rdoc | 5 ----- doc/string/each_grapheme_cluster.rdoc | 6 ------ doc/string/end_with_p.rdoc | 1 - doc/string/getbyte.rdoc | 3 --- doc/string/index.rdoc | 4 ---- doc/string/insert.rdoc | 1 - doc/string/inspect.rdoc | 1 - doc/string/intern.rdoc | 1 - doc/string/length.rdoc | 2 -- doc/string/ljust.rdoc | 1 - doc/string/ord.rdoc | 1 - doc/string/partition.rdoc | 1 - doc/string/rindex.rdoc | 1 - doc/string/rjust.rdoc | 1 - doc/string/rpartition.rdoc | 2 -- doc/string/scan.rdoc | 1 - doc/string/split.rdoc | 2 -- doc/string/start_with_p.rdoc | 1 - doc/string/succ.rdoc | 1 - doc/string/sum.rdoc | 1 - 32 files changed, 69 deletions(-) diff --git a/doc/string/chomp.rdoc b/doc/string/chomp.rdoc index 6ec7664f6b6832..4efff5c2917e9c 100644 --- a/doc/string/chomp.rdoc +++ b/doc/string/chomp.rdoc @@ -9,7 +9,6 @@ if they are "\r", "\n", or "\r\n" "abc\n".chomp # => "abc" "abc\r\n".chomp # => "abc" "abc\n\r".chomp # => "abc\n" - "тест\r\n".chomp # => "тест" "こんにちは\r\n".chomp # => "こんにちは" When +line_sep+ is '' (an empty string), diff --git a/doc/string/chop.rdoc b/doc/string/chop.rdoc index 2c48e911292eff..d818ba467ac062 100644 --- a/doc/string/chop.rdoc +++ b/doc/string/chop.rdoc @@ -3,13 +3,11 @@ Returns a new string copied from +self+, with trailing characters possibly remov Removes "\r\n" if those are the last two characters. "abc\r\n".chop # => "abc" - "тест\r\n".chop # => "тест" "こんにちは\r\n".chop # => "こんにちは" Otherwise removes the last character if it exists. 'abcd'.chop # => "abc" - 'тест'.chop # => "тес" 'こんにちは'.chop # => "こんにち" ''.chop # => "" diff --git a/doc/string/chr.rdoc b/doc/string/chr.rdoc index 1ada3854cbeb27..153d5d71c325ba 100644 --- a/doc/string/chr.rdoc +++ b/doc/string/chr.rdoc @@ -1,7 +1,6 @@ Returns a string containing the first character of +self+: 'hello'.chr # => "h" - 'тест'.chr # => "т" 'こんにちは'.chr # => "こ" ''.chr # => "" diff --git a/doc/string/codepoints.rdoc b/doc/string/codepoints.rdoc index d9586d2e0bc2a5..22cb22c8890df1 100644 --- a/doc/string/codepoints.rdoc +++ b/doc/string/codepoints.rdoc @@ -2,7 +2,6 @@ Returns an array of the codepoints in +self+; each codepoint is the integer value for a character: 'hello'.codepoints # => [104, 101, 108, 108, 111] - 'тест'.codepoints # => [1090, 1077, 1089, 1090] 'こんにちは'.codepoints # => [12371, 12435, 12395, 12385, 12399] ''.codepoints # => [] diff --git a/doc/string/concat.rdoc b/doc/string/concat.rdoc index 2ba0c714af57d3..92ba664b8cb825 100644 --- a/doc/string/concat.rdoc +++ b/doc/string/concat.rdoc @@ -6,7 +6,6 @@ For each given object +object+ that is an integer, the value is considered a codepoint and converted to a character before concatenation: 'foo'.concat(32, 'bar', 32, 'baz') # => "foo bar baz" # Embeds spaces. - 'те'.concat(1089, 1090) # => "тест" 'こん'.concat(12395, 12385, 12399) # => "こんにちは" Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/count.rdoc b/doc/string/count.rdoc index 092c672d7d113b..7a3b9f1e211ebf 100644 --- a/doc/string/count.rdoc +++ b/doc/string/count.rdoc @@ -9,10 +9,6 @@ returns the count of instances of that character: s.count('x') # => 0 s.count('') # => 0 - s = 'тест' - s.count('т') # => 2 - s.count('е') # => 1 - s = 'よろしくお願いします' s.count('よ') # => 1 s.count('し') # => 2 diff --git a/doc/string/delete.rdoc b/doc/string/delete.rdoc index e8ff4c0ae400af..1827f177e6cd87 100644 --- a/doc/string/delete.rdoc +++ b/doc/string/delete.rdoc @@ -10,10 +10,6 @@ removes all instances of that character: s.delete('x') # => "abracadabra" s.delete('') # => "abracadabra" - s = 'тест' - s.delete('т') # => "ес" - s.delete('е') # => "тст" - s = 'よろしくお願いします' s.delete('よ') # => "ろしくお願いします" s.delete('し') # => "よろくお願います" diff --git a/doc/string/delete_prefix.rdoc b/doc/string/delete_prefix.rdoc index 1135f3d19d26ed..6255e300e350d4 100644 --- a/doc/string/delete_prefix.rdoc +++ b/doc/string/delete_prefix.rdoc @@ -4,7 +4,6 @@ Returns a copy of +self+ with leading substring +prefix+ removed: 'oof'.delete_prefix('oo') # => "f" 'oof'.delete_prefix('oof') # => "" 'oof'.delete_prefix('x') # => "oof" - 'тест'.delete_prefix('те') # => "ст" 'こんにちは'.delete_prefix('こん') # => "にちは" Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/delete_suffix.rdoc b/doc/string/delete_suffix.rdoc index 2fb70ce012aabc..a4d9a80f85fb4d 100644 --- a/doc/string/delete_suffix.rdoc +++ b/doc/string/delete_suffix.rdoc @@ -5,7 +5,6 @@ Returns a copy of +self+ with trailing substring suffix removed: 'foo'.delete_suffix('foo') # => "" 'foo'.delete_suffix('f') # => "foo" 'foo'.delete_suffix('x') # => "foo" - 'тест'.delete_suffix('ст') # => "те" 'こんにちは'.delete_suffix('ちは') # => "こんに" Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/dump.rdoc b/doc/string/dump.rdoc index 2ab9521540dcda..add3c356623b15 100644 --- a/doc/string/dump.rdoc +++ b/doc/string/dump.rdoc @@ -69,10 +69,6 @@ and so contains both outer double-quotes and escaped inner double-quotes: If +self+ is encoded in UTF-8 and contains Unicode characters, each Unicode character is dumped as a Unicode escape sequence: - String: тест - Dumped: "\u0442\u0435\u0441\u0442" - Undumped: тест - String: こんにちは Dumped: "\u3053\u3093\u306B\u3061\u306F" Undumped: こんにちは @@ -88,10 +84,6 @@ where is self.encoding.name: Dumped: "\xFE\xFF\x00h\x00e\x00l\x00l\x00o".dup.force_encoding("UTF-16") Undumped: hello - String: тест - Dumped: "\xFE\xFF\x04B\x045\x04A\x04B".dup.force_encoding("UTF-16") - Undumped: тест - String: こんにちは Dumped: "\xFE\xFF0S0\x930k0a0o".dup.force_encoding("UTF-16") Undumped: こんにちは diff --git a/doc/string/each_byte.rdoc b/doc/string/each_byte.rdoc index 1f1069863b264b..642d71e84b5408 100644 --- a/doc/string/each_byte.rdoc +++ b/doc/string/each_byte.rdoc @@ -5,9 +5,6 @@ returns +self+: 'hello'.each_byte {|byte| a.push(byte) } # Five 1-byte characters. a # => [104, 101, 108, 108, 111] a = [] - 'тест'.each_byte {|byte| a.push(byte) } # Four 2-byte characters. - a # => [209, 130, 208, 181, 209, 129, 209, 130] - a = [] 'こんにちは'.each_byte {|byte| a.push(byte) } # Five 3-byte characters. a # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] diff --git a/doc/string/each_char.rdoc b/doc/string/each_char.rdoc index 5aa85b28ad9dd1..2dd56711d3b076 100644 --- a/doc/string/each_char.rdoc +++ b/doc/string/each_char.rdoc @@ -7,11 +7,6 @@ returns +self+: end a # => ["h", "e", "l", "l", "o"] a = [] - 'тест'.each_char do |char| - a.push(char) - end - a # => ["т", "е", "с", "т"] - a = [] 'こんにちは'.each_char do |char| a.push(char) end diff --git a/doc/string/each_codepoint.rdoc b/doc/string/each_codepoint.rdoc index 0e687082d3ed4c..8e4e7545e62a76 100644 --- a/doc/string/each_codepoint.rdoc +++ b/doc/string/each_codepoint.rdoc @@ -8,11 +8,6 @@ returns +self+: end a # => [104, 101, 108, 108, 111] a = [] - 'тест'.each_codepoint do |codepoint| - a.push(codepoint) - end - a # => [1090, 1077, 1089, 1090] - a = [] 'こんにちは'.each_codepoint do |codepoint| a.push(codepoint) end diff --git a/doc/string/each_grapheme_cluster.rdoc b/doc/string/each_grapheme_cluster.rdoc index 8bc6f78aaa374d..384cd6967d5db3 100644 --- a/doc/string/each_grapheme_cluster.rdoc +++ b/doc/string/each_grapheme_cluster.rdoc @@ -8,12 +8,6 @@ returns +self+: end a # => ["h", "e", "l", "l", "o"] - a = [] - 'тест'.each_grapheme_cluster do |grapheme_cluster| - a.push(grapheme_cluster) - end - a # => ["т", "е", "с", "т"] - a = [] 'こんにちは'.each_grapheme_cluster do |grapheme_cluster| a.push(grapheme_cluster) diff --git a/doc/string/end_with_p.rdoc b/doc/string/end_with_p.rdoc index fcd92421225ca8..9a95d74fde5ba0 100644 --- a/doc/string/end_with_p.rdoc +++ b/doc/string/end_with_p.rdoc @@ -4,7 +4,6 @@ Returns whether +self+ ends with any of the given +strings+: 'foo'.end_with?('bar', 'oo') # => true 'foo'.end_with?('bar', 'baz') # => false 'foo'.end_with?('') # => true - 'тест'.end_with?('т') # => true 'こんにちは'.end_with?('は') # => true Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/doc/string/getbyte.rdoc b/doc/string/getbyte.rdoc index ba1c06fd27cb67..1d0ed2a5a488ad 100644 --- a/doc/string/getbyte.rdoc +++ b/doc/string/getbyte.rdoc @@ -16,9 +16,6 @@ Returns +nil+ if +index+ is out of range: More examples: - s = 'тест' - s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] - s.getbyte(2) # => 208 s = 'こんにちは' s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] s.getbyte(2) # => 147 diff --git a/doc/string/index.rdoc b/doc/string/index.rdoc index 6045fac0f6fb5a..c3cff24dac5a48 100644 --- a/doc/string/index.rdoc +++ b/doc/string/index.rdoc @@ -8,7 +8,6 @@ returns the index of the first matching substring in +self+: 'foo'.index('o') # => 1 'foo'.index('oo') # => 1 'foo'.index('ooo') # => nil - 'тест'.index('с') # => 2 # Characters, not bytes. 'こんにちは'.index('ち') # => 3 When +pattern+ is a Regexp, returns the index of the first match in +self+: @@ -24,9 +23,6 @@ the returned index is relative to the beginning of +self+: 'bar'.index('r', 2) # => 2 'bar'.index('r', 3) # => nil 'bar'.index(/[r-z]/, 0) # => 2 - 'тест'.index('с', 1) # => 2 - 'тест'.index('с', 2) # => 2 - 'тест'.index('с', 3) # => nil # Offset in characters, not bytes. 'こんにちは'.index('ち', 2) # => 3 With negative integer argument +offset+, selects the search position by counting backward diff --git a/doc/string/insert.rdoc b/doc/string/insert.rdoc index d8252d5ec5fd81..73205f20693cc7 100644 --- a/doc/string/insert.rdoc +++ b/doc/string/insert.rdoc @@ -5,7 +5,6 @@ If the given +index+ is non-negative, inserts +other_string+ at offset +index+: 'foo'.insert(0, 'bar') # => "barfoo" 'foo'.insert(1, 'bar') # => "fbaroo" 'foo'.insert(3, 'bar') # => "foobar" - 'тест'.insert(2, 'bar') # => "теbarст" # Characters, not bytes. 'こんにちは'.insert(2, 'bar') # => "こんbarにちは" If the +index+ is negative, counts backward from the end of +self+ diff --git a/doc/string/inspect.rdoc b/doc/string/inspect.rdoc index 828ecf966dd26f..907828c2afc22c 100644 --- a/doc/string/inspect.rdoc +++ b/doc/string/inspect.rdoc @@ -6,7 +6,6 @@ Most printable characters are rendered simply as themselves: '012'.inspect # => "\"012\"" ''.inspect # => "\"\"" "\u000012".inspect # => "\"\\u000012\"" - 'тест'.inspect # => "\"тест\"" 'こんにちは'.inspect # => "\"こんにちは\"" But printable characters double-quote ('"') and backslash and ('\\') are escaped: diff --git a/doc/string/intern.rdoc b/doc/string/intern.rdoc index 1336e4688f7a2e..eded6ac3d727c9 100644 --- a/doc/string/intern.rdoc +++ b/doc/string/intern.rdoc @@ -2,7 +2,6 @@ Returns the Symbol object derived from +self+, creating it if it did not already exist: 'foo'.intern # => :foo - 'тест'.intern # => :тест 'こんにちは'.intern # => :こんにちは Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/length.rdoc b/doc/string/length.rdoc index 5b302380b59fdb..eb68edb10c449f 100644 --- a/doc/string/length.rdoc +++ b/doc/string/length.rdoc @@ -1,13 +1,11 @@ Returns the count of characters (not bytes) in +self+: 'foo'.length # => 3 - 'тест'.length # => 4 'こんにちは'.length # => 5 Contrast with String#bytesize: 'foo'.bytesize # => 3 - 'тест'.bytesize # => 8 'こんにちは'.bytesize # => 15 Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/doc/string/ljust.rdoc b/doc/string/ljust.rdoc index f37c0b3151176d..a8ca62ee764c6d 100644 --- a/doc/string/ljust.rdoc +++ b/doc/string/ljust.rdoc @@ -3,7 +3,6 @@ Returns a copy of +self+, left-justified and, if necessary, right-padded with th 'hello'.ljust(10) # => "hello " ' hello'.ljust(10) # => " hello " 'hello'.ljust(10, 'ab') # => "helloababa" - 'тест'.ljust(10) # => "тест " 'こんにちは'.ljust(10) # => "こんにちは " If width <= self.length, returns a copy of +self+: diff --git a/doc/string/ord.rdoc b/doc/string/ord.rdoc index a5ddbb4e2fb82a..87b469db0205ce 100644 --- a/doc/string/ord.rdoc +++ b/doc/string/ord.rdoc @@ -2,7 +2,6 @@ Returns the integer ordinal of the first character of +self+: 'h'.ord # => 104 'hello'.ord # => 104 - 'тест'.ord # => 1090 'こんにちは'.ord # => 12371 Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/partition.rdoc b/doc/string/partition.rdoc index d822f8ec0e0738..614ad029d49885 100644 --- a/doc/string/partition.rdoc +++ b/doc/string/partition.rdoc @@ -38,7 +38,6 @@ then performs the equivalent of self.index(pattern) 'hello'.partition('o') # => ["hell", "o", ""] 'hello'.partition('') # => ["", "", "hello"] 'hello'.partition('x') # => ["hello", "", ""] - 'тест'.partition('т') # => ["", "т", "ест"] 'こんにちは'.partition('に') # => ["こん", "に", "ちは"] Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/rindex.rdoc b/doc/string/rindex.rdoc index 8a1cc0106f59fb..2b81c3716d2986 100644 --- a/doc/string/rindex.rdoc +++ b/doc/string/rindex.rdoc @@ -7,7 +7,6 @@ When +pattern+ is a string, returns the index of the last matching substring in 'foo'.rindex('o') # => 2 'foo'.rindex('oo' # => 1 'foo'.rindex('ooo') # => nil - 'тест'.rindex('т') # => 3 'こんにちは'.rindex('ち') # => 3 When +pattern+ is a Regexp, returns the index of the last match in self: diff --git a/doc/string/rjust.rdoc b/doc/string/rjust.rdoc index 864cfcce7a0ec0..acd3f198d46fe0 100644 --- a/doc/string/rjust.rdoc +++ b/doc/string/rjust.rdoc @@ -7,7 +7,6 @@ right justified and padded on the left with +pad_string+: 'hello'.rjust(10) # => " hello" 'hello '.rjust(10) # => " hello " 'hello'.rjust(10, 'ab') # => "ababahello" - 'тест'.rjust(10) # => " тест" 'こんにちは'.rjust(10) # => " こんにちは" If width <= self.size, returns a copy of +self+: diff --git a/doc/string/rpartition.rdoc b/doc/string/rpartition.rdoc index 6a17b5e944bffc..eed03949a56e52 100644 --- a/doc/string/rpartition.rdoc +++ b/doc/string/rpartition.rdoc @@ -31,7 +31,6 @@ If +pattern+ is a Regexp, searches for the last matching substring 'hello'.rpartition(/o/) # => ["hell", "o", ""] 'hello'.rpartition(//) # => ["hello", "", ""] 'hello'.rpartition(/x/) # => ["", "", "hello"] - 'тест'.rpartition(/т/) # => ["тес", "т", ""] 'こんにちは'.rpartition(/に/) # => ["こん", "に", "ちは"] If +pattern+ is not a Regexp, converts it to a string (if it is not already one), @@ -43,7 +42,6 @@ then searches for the last matching substring 'hello'.rpartition('h') # => ["", "h", "ello"] 'hello'.rpartition('o') # => ["hell", "o", ""] 'hello'.rpartition('') # => ["hello", "", ""] - 'тест'.rpartition('т') # => ["тес", "т", ""] 'こんにちは'.rpartition('に') # => ["こん", "に", "ちは"] Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/scan.rdoc b/doc/string/scan.rdoc index cbede5280f5c49..04a2b02ff4ec1b 100644 --- a/doc/string/scan.rdoc +++ b/doc/string/scan.rdoc @@ -16,7 +16,6 @@ With no block given, returns an array of the results: 'cruel world'.scan(/.../) # => ["cru", "el ", "wor"] 'cruel world'.scan(/(...)/) # => [["cru"], ["el "], ["wor"]] 'cruel world'.scan(/(..)(..)/) # => [["cr", "ue"], ["l ", "wo"]] - 'тест'.scan(/../) # => ["те", "ст"] 'こんにちは'.scan(/../) # => ["こん", "にち"] 'abracadabra'.scan('ab') # => ["ab", "ab"] 'abracadabra'.scan('nosuch') # => [] diff --git a/doc/string/split.rdoc b/doc/string/split.rdoc index 9e61bc5bab3751..1aee1de0a4cca7 100644 --- a/doc/string/split.rdoc +++ b/doc/string/split.rdoc @@ -23,7 +23,6 @@ splits at every character: 'abracadabra'.split('') # => ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"] ''.split('') # => [] - 'тест'.split('') # => ["т", "е", "с", "т"] 'こんにちは'.split('') # => ["こ", "ん", "に", "ち", "は"] When +field_sep+ is a non-empty string and different from ' ' (a single space), @@ -32,7 +31,6 @@ uses that string as the separator: 'abracadabra'.split('a') # => ["", "br", "c", "d", "br"] 'abracadabra'.split('ab') # => ["", "racad", "ra"] ''.split('a') # => [] - 'тест'.split('т') # => ["", "ес"] 'こんにちは'.split('に') # => ["こん", "ちは"] When +field_sep+ is a Regexp, diff --git a/doc/string/start_with_p.rdoc b/doc/string/start_with_p.rdoc index 298a5572769ea8..f78edc7fa3b63f 100644 --- a/doc/string/start_with_p.rdoc +++ b/doc/string/start_with_p.rdoc @@ -11,7 +11,6 @@ Returns +true+ if any pattern matches the beginning, +false+ otherwise: 'hello'.start_with?(/H/i) # => true 'hello'.start_with?('heaven', 'hell') # => true 'hello'.start_with?('heaven', 'paradise') # => false - 'тест'.start_with?('т') # => true 'こんにちは'.start_with?('こ') # => true Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/doc/string/succ.rdoc b/doc/string/succ.rdoc index 3653112b837e3c..1b4b936a8e8351 100644 --- a/doc/string/succ.rdoc +++ b/doc/string/succ.rdoc @@ -7,7 +7,6 @@ or, if no alphanumerics, the rightmost character: 'THX1138'.succ # => "THX1139" '<>'.succ # => "<>" '***'.succ # => '**+' - 'тест'.succ # => "тесу" 'こんにちは'.succ # => "こんにちば" The successor to a digit is another digit, "carrying" to the next-left diff --git a/doc/string/sum.rdoc b/doc/string/sum.rdoc index 125611411e34ab..22045e5f4ddc5a 100644 --- a/doc/string/sum.rdoc +++ b/doc/string/sum.rdoc @@ -5,7 +5,6 @@ modulo 2**n - 1: 'hello'.sum # => 532 'hello'.sum(4) # => 4 'hello'.sum(64) # => 532 - 'тест'.sum # => 1405 'こんにちは'.sum # => 2582 This is not a particularly strong checksum. From 8415f8faccffd426ed9c1168abddc2b3e727b659 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 26 Dec 2025 21:24:52 +0000 Subject: [PATCH 2243/2435] [DOC] Use Japanese for multi-byte characters --- doc/language/packed_data.rdoc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index 088e6d86536db7..597db5139f87b2 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -457,15 +457,15 @@ for one byte in the input or output string. "foo ".unpack('A4') # => ["foo"] "foo".unpack('A4') # => ["foo"] - russian = "\u{442 435 441 442}" # => "тест" - russian.size # => 4 - russian.bytesize # => 8 - [russian].pack('A') # => "\xD1" - [russian].pack('A*') # => "\xD1\x82\xD0\xB5\xD1\x81\xD1\x82" - russian.unpack('A') # => ["\xD1"] - russian.unpack('A2') # => ["\xD1\x82"] - russian.unpack('A4') # => ["\xD1\x82\xD0\xB5"] - russian.unpack('A*') # => ["\xD1\x82\xD0\xB5\xD1\x81\xD1\x82"] + japanese = 'こんにちは' + japanese.size # => 5 + japanese.bytesize # => 15 + [japanese].pack('A') # => "\xE3" + [japanese].pack('A*') # => "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF" + japanese.unpack('A') # => ["\xE3"] + japanese.unpack('A2') # => ["\xE3\x81"] + japanese.unpack('A4') # => ["\xE3\x81\x93\xE3"] + japanese.unpack('A*') # => ["\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF"] - 'a' - Arbitrary binary string (null padded; count is width): From 6544c89708d94b894012e0587f770f33bbce4a3b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 27 Dec 2025 18:32:07 +0900 Subject: [PATCH 2244/2435] Skip the hang-up test on mingw --- test/ruby/test_thread.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index c08d41cb863a2c..3796e12f083feb 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1480,6 +1480,8 @@ def test_thread_native_thread_id_across_fork_on_linux end def test_thread_interrupt_for_killed_thread + pend "hang-up" if RUBY_PLATFORM.include?("mingw") + opts = { timeout: 5, timeout_error: nil } assert_normal_exit(<<-_end, '[Bug #8996]', **opts) From 3fe2ebf8e4127bca0a57d4ed8eb6035792420a26 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:23:50 +0900 Subject: [PATCH 2245/2435] Remove unnecessary comparison from is_local_port_fixed (#15757) Simplify the conditions: - Return false if the port is 0 or a negative number - Return true if the port is a positive number --- ext/socket/ipsocket.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 758d37293ffc2c..931a1a629c87f6 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -269,9 +269,8 @@ is_local_port_fixed(const char *portp) if (endp == portp) return 0; if (errno == ERANGE) return 0; - if (port <= 0) return 0; - return port != 0; + return port > 0; } struct fast_fallback_inetsock_arg From eaa83e505fdcddd1d354f1d9a375b22f33748060 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 27 Dec 2025 13:19:56 -0500 Subject: [PATCH 2246/2435] Always allocate Fiber objects in Thread Currently, root fibers of threads do not have a corresponding Ruby object backing it by default (it does have one when an object is required, such as when Fiber.current is called). This is a problem for the new GC weak references design in #12606 since Thread is not declared as having weak references but it does hold weak references (the generic ivar cache). This commit changes it to always allocate a Fiber object for the root fiber. --- cont.c | 65 +++++++++++++++++++++++----------------------------------- vm.c | 21 +++++++++++-------- 2 files changed, 38 insertions(+), 48 deletions(-) diff --git a/cont.c b/cont.c index dbac05b05a0d5c..463be7400f174c 100644 --- a/cont.c +++ b/cont.c @@ -953,7 +953,9 @@ fiber_verify(const rb_fiber_t *fiber) switch (fiber->status) { case FIBER_RESUMED: - VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL); + if (fiber->cont.saved_ec.thread_ptr->self == 0) { + VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL); + } break; case FIBER_SUSPENDED: VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL); @@ -1141,12 +1143,7 @@ rb_fiber_update_self(rb_fiber_t *fiber) void rb_fiber_mark_self(const rb_fiber_t *fiber) { - if (fiber->cont.self) { - rb_gc_mark_movable(fiber->cont.self); - } - else { - rb_execution_context_mark(&fiber->cont.saved_ec); - } + rb_gc_mark_movable(fiber->cont.self); } static void @@ -2067,32 +2064,10 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking) return fiber; } -static rb_fiber_t * -root_fiber_alloc(rb_thread_t *th) -{ - VALUE fiber_value = fiber_alloc(rb_cFiber); - rb_fiber_t *fiber = th->ec->fiber_ptr; - - VM_ASSERT(DATA_PTR(fiber_value) == NULL); - VM_ASSERT(fiber->cont.type == FIBER_CONTEXT); - VM_ASSERT(FIBER_RESUMED_P(fiber)); - - th->root_fiber = fiber; - DATA_PTR(fiber_value) = fiber; - fiber->cont.self = fiber_value; - - coroutine_initialize_main(&fiber->context); - - return fiber; -} - static inline rb_fiber_t* fiber_current(void) { rb_execution_context_t *ec = GET_EC(); - if (ec->fiber_ptr->cont.self == 0) { - root_fiber_alloc(rb_ec_thread_ptr(ec)); - } return ec->fiber_ptr; } @@ -2598,6 +2573,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) if (!fiber) { rb_bug("%s", strerror(errno)); /* ... is it possible to call rb_bug here? */ } + fiber->cont.type = FIBER_CONTEXT; fiber->cont.saved_ec.fiber_ptr = fiber; fiber->cont.saved_ec.serial = next_ec_serial(th->ractor); @@ -2605,10 +2581,23 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th) fiber->blocking = 1; fiber->killed = 0; fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */ + + coroutine_initialize_main(&fiber->context); + th->ec = &fiber->cont.saved_ec; + cont_init_jit_cont(&fiber->cont); } +void +rb_root_fiber_obj_setup(rb_thread_t *th) +{ + rb_fiber_t *fiber = th->ec->fiber_ptr; + VALUE fiber_value = fiber_alloc(rb_cFiber); + DATA_PTR(fiber_value) = fiber; + fiber->cont.self = fiber_value; +} + void rb_threadptr_root_fiber_release(rb_thread_t *th) { @@ -2679,15 +2668,7 @@ rb_fiber_current(void) static inline void fiber_store(rb_fiber_t *next_fiber, rb_thread_t *th) { - rb_fiber_t *fiber; - - if (th->ec->fiber_ptr != NULL) { - fiber = th->ec->fiber_ptr; - } - else { - /* create root fiber */ - fiber = root_fiber_alloc(th); - } + rb_fiber_t *fiber = th->ec->fiber_ptr; if (FIBER_CREATED_P(next_fiber)) { fiber_prepare_stack(next_fiber); @@ -2723,7 +2704,9 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi rb_thread_t *th = GET_THREAD(); /* make sure the root_fiber object is available */ - if (th->root_fiber == NULL) root_fiber_alloc(th); + if (th->root_fiber == NULL) { + th->root_fiber = th->ec->fiber_ptr; + } if (th->ec->fiber_ptr == fiber) { /* ignore fiber context switch @@ -3558,6 +3541,10 @@ Init_Cont(void) rb_define_singleton_method(rb_cFiber, "schedule", rb_fiber_s_schedule, -1); + rb_thread_t *current_thread = rb_current_thread(); + RUBY_ASSERT(CLASS_OF(current_thread->ec->fiber_ptr->cont.self) == 0); + *(VALUE *)&((struct RBasic *)current_thread->ec->fiber_ptr->cont.self)->klass = rb_cFiber; + #ifdef RB_EXPERIMENTAL_FIBER_POOL /* * Document-class: Fiber::Pool diff --git a/vm.c b/vm.c index 0c3a2d01a515d4..7c86c0ff54d1b6 100644 --- a/vm.c +++ b/vm.c @@ -3738,6 +3738,7 @@ rb_execution_context_mark(const rb_execution_context_t *ec) void rb_fiber_mark_self(rb_fiber_t *fib); void rb_fiber_update_self(rb_fiber_t *fib); void rb_threadptr_root_fiber_setup(rb_thread_t *th); +void rb_root_fiber_obj_setup(rb_thread_t *th); void rb_threadptr_root_fiber_release(rb_thread_t *th); static void @@ -3746,10 +3747,6 @@ thread_compact(void *ptr) rb_thread_t *th = ptr; th->self = rb_gc_location(th->self); - - if (!th->root_fiber) { - rb_execution_context_update(th->ec); - } } static void @@ -3757,7 +3754,11 @@ thread_mark(void *ptr) { rb_thread_t *th = ptr; RUBY_MARK_ENTER("thread"); - rb_fiber_mark_self(th->ec->fiber_ptr); + + // ec is null when setting up the thread in rb_threadptr_root_fiber_setup + if (th->ec) { + rb_fiber_mark_self(th->ec->fiber_ptr); + } /* mark ruby objects */ switch (th->invoke_type) { @@ -3813,8 +3814,6 @@ thread_free(void *ptr) ruby_xfree(th->specific_storage); - rb_threadptr_root_fiber_release(th); - if (th->vm && th->vm->ractor.main_thread == th) { RUBY_GC_INFO("MRI main thread\n"); } @@ -3917,6 +3916,8 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->self = self; + ccan_list_head_init(&th->interrupt_exec_tasks); + rb_threadptr_root_fiber_setup(th); /* All threads are blocking until a non-blocking fiber is scheduled */ @@ -3961,8 +3962,6 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->report_on_exception = vm->thread_report_on_exception; th->ext_config.ractor_safe = true; - ccan_list_head_init(&th->interrupt_exec_tasks); - #if USE_RUBY_DEBUG_LOG static rb_atomic_t thread_serial = 1; th->serial = RUBY_ATOMIC_FETCH_ADD(thread_serial, 1); @@ -3978,6 +3977,7 @@ rb_thread_alloc(VALUE klass) rb_thread_t *target_th = rb_thread_ptr(self); target_th->ractor = GET_RACTOR(); th_init(target_th, self, target_th->vm = GET_VM()); + rb_root_fiber_obj_setup(target_th); return self; } @@ -4525,6 +4525,8 @@ Init_VM(void) th->top_wrapper = 0; th->top_self = rb_vm_top_self(); + rb_root_fiber_obj_setup(th); + rb_vm_register_global_object((VALUE)iseq); th->ec->cfp->iseq = iseq; th->ec->cfp->pc = ISEQ_BODY(iseq)->iseq_encoded; @@ -4599,6 +4601,7 @@ Init_BareVM(void) th_init(th, 0, vm); rb_ractor_set_current_ec(th->ractor, th->ec); + /* n.b. native_main_thread_stack_top is set by the INIT_STACK macro */ ruby_thread_init_stack(th, native_main_thread_stack_top); From 68cd46353c34cd87917930f80925b2ac8ddaaa9b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 28 Dec 2025 13:38:39 +0900 Subject: [PATCH 2247/2435] Ensure `T_DATA` before `RTYPEDDATA_P` For the precondition of `RTYPEDDATA_P` that the argument must be a Ruby object of `::RUBY_T_DATA`. --- include/ruby/random.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/ruby/random.h b/include/ruby/random.h index f3df0d96fba6e1..740be6bdad9137 100644 --- a/include/ruby/random.h +++ b/include/ruby/random.h @@ -332,7 +332,9 @@ RBIMPL_ATTR_PURE_UNLESS_DEBUG() static inline const rb_random_interface_t * rb_rand_if(VALUE obj) { + RBIMPL_ASSERT_OR_ASSUME(RB_TYPE_P(obj, T_DATA)); RBIMPL_ASSERT_OR_ASSUME(RTYPEDDATA_P(obj)); + RUBY_ASSERT(rb_typeddata_is_kind_of(obj, &rb_random_data_type)); const struct rb_data_type_struct *t = RTYPEDDATA_TYPE(obj); const void *ret = t->data; return RBIMPL_CAST((const rb_random_interface_t *)ret); From 44e762a99c2234756594382f36fc64db1d6c31d0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 28 Dec 2025 13:49:02 +0900 Subject: [PATCH 2248/2435] Extract `rb_random_interface_t` alongside `rb_random_t` as well --- random.c | 107 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/random.c b/random.c index 0fd953b81e9eed..d902603e98345a 100644 --- a/random.c +++ b/random.c @@ -315,31 +315,24 @@ get_rnd_mt(VALUE obj) } static rb_random_t * -try_get_rnd(VALUE obj) +try_get_rnd(VALUE obj, const rb_random_interface_t **rng_p) { if (obj == rb_cRandom) { + *rng_p = &random_mt_if; return default_rand_start(); } if (!rb_typeddata_is_kind_of(obj, &rb_random_data_type)) return NULL; - if (RTYPEDDATA_TYPE(obj) == &random_mt_type) - return rand_start(DATA_PTR(obj), obj); - rb_random_t *rnd = DATA_PTR(obj); + const struct rb_data_type_struct *type = RTYPEDDATA_TYPE(obj); + *rng_p = type->data; + void *rnd = DATA_PTR(obj); if (!rnd) { rb_raise(rb_eArgError, "uninitialized random: %s", RTYPEDDATA_TYPE(obj)->wrap_struct_name); } + if (type == &random_mt_type) rnd = rand_start(rnd, obj); return rnd; } -static const rb_random_interface_t * -try_rand_if(VALUE obj, rb_random_t *rnd) -{ - if (rnd == &default_rand()->base) { - return &random_mt_if; - } - return rb_rand_if(obj); -} - /* :nodoc: */ void rb_random_base_init(rb_random_t *rnd) @@ -412,8 +405,8 @@ rand_init(const rb_random_interface_t *rng, rb_random_t *rnd, VALUE seed) static VALUE random_init(int argc, VALUE *argv, VALUE obj) { - rb_random_t *rnd = try_get_rnd(obj); - const rb_random_interface_t *rng = rb_rand_if(obj); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rng) { rb_raise(rb_eTypeError, "undefined random interface: %s", @@ -1130,17 +1123,18 @@ random_int32(const rb_random_interface_t *rng, rb_random_t *rnd) unsigned int rb_random_int32(VALUE obj) { - rb_random_t *rnd = try_get_rnd(obj); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rnd) { uint32_t x; obj_random_bytes(obj, &x, sizeof(x)); return (unsigned int)x; } - return random_int32(try_rand_if(obj, rnd), rnd); + return random_int32(rng, rnd); } static double -random_real(VALUE obj, rb_random_t *rnd, int excl) +random_real(VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd, int excl) { uint32_t a, b; @@ -1151,7 +1145,6 @@ random_real(VALUE obj, rb_random_t *rnd, int excl) b = x[1]; } else { - const rb_random_interface_t *rng = try_rand_if(obj, rnd); if (rng->get_real) return rng->get_real(rnd, excl); a = random_int32(rng, rnd); b = random_int32(rng, rnd); @@ -1173,7 +1166,8 @@ rb_int_pair_to_real(uint32_t a, uint32_t b, int excl) double rb_random_real(VALUE obj) { - rb_random_t *rnd = try_get_rnd(obj); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rnd) { VALUE v = rb_funcallv(obj, id_rand, 0, 0); double d = NUM2DBL(v); @@ -1185,7 +1179,7 @@ rb_random_real(VALUE obj) } return d; } - return random_real(obj, rnd, TRUE); + return random_real(obj, rng, rnd, TRUE); } static inline VALUE @@ -1202,7 +1196,7 @@ ulong_to_num_plus_1(unsigned long n) } static unsigned long -random_ulong_limited(VALUE obj, rb_random_t *rnd, unsigned long limit) +random_ulong_limited(VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd, unsigned long limit) { if (!limit) return 0; if (!rnd) { @@ -1227,13 +1221,14 @@ random_ulong_limited(VALUE obj, rb_random_t *rnd, unsigned long limit) } while (limit < val); return val; } - return limited_rand(try_rand_if(obj, rnd), rnd, limit); + return limited_rand(rng, rnd, limit); } unsigned long rb_random_ulong_limited(VALUE obj, unsigned long limit) { - rb_random_t *rnd = try_get_rnd(obj); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rnd) { VALUE lim = ulong_to_num_plus_1(limit); VALUE v = rb_to_int(rb_funcallv_public(obj, id_rand, 1, &lim)); @@ -1246,11 +1241,11 @@ rb_random_ulong_limited(VALUE obj, unsigned long limit) } return r; } - return limited_rand(try_rand_if(obj, rnd), rnd, limit); + return limited_rand(rng, rnd, limit); } static VALUE -random_ulong_limited_big(VALUE obj, rb_random_t *rnd, VALUE vmax) +random_ulong_limited_big(VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd, VALUE vmax) { if (!rnd) { VALUE v, vtmp; @@ -1275,7 +1270,7 @@ random_ulong_limited_big(VALUE obj, rb_random_t *rnd, VALUE vmax) ALLOCV_END(vtmp); return v; } - return limited_big_rand(try_rand_if(obj, rnd), rnd, vmax); + return limited_big_rand(rng, rnd, vmax); } static VALUE @@ -1301,8 +1296,9 @@ rand_bytes(const rb_random_interface_t *rng, rb_random_t *rnd, long n) static VALUE random_bytes(VALUE obj, VALUE len) { - rb_random_t *rnd = try_get_rnd(obj); - return rand_bytes(rb_rand_if(obj), rnd, NUM2LONG(rb_to_int(len))); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); + return rand_bytes(rng, rnd, NUM2LONG(rb_to_int(len))); } void @@ -1331,11 +1327,12 @@ rb_rand_bytes_int32(rb_random_get_int32_func *get_int32, VALUE rb_random_bytes(VALUE obj, long n) { - rb_random_t *rnd = try_get_rnd(obj); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rnd) { return obj_random_bytes(obj, NULL, n); } - return rand_bytes(try_rand_if(obj, rnd), rnd, n); + return rand_bytes(rng, rnd, n); } /* @@ -1387,7 +1384,7 @@ range_values(VALUE vmax, VALUE *begp, VALUE *endp, int *exclp) } static VALUE -rand_int(VALUE obj, rb_random_t *rnd, VALUE vmax, int restrictive) +rand_int(VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd, VALUE vmax, int restrictive) { /* mt must be initialized */ unsigned long r; @@ -1399,7 +1396,7 @@ rand_int(VALUE obj, rb_random_t *rnd, VALUE vmax, int restrictive) if (restrictive) return Qnil; max = -max; } - r = random_ulong_limited(obj, rnd, (unsigned long)max - 1); + r = random_ulong_limited(obj, rng, rnd, (unsigned long)max - 1); return ULONG2NUM(r); } else { @@ -1413,10 +1410,10 @@ rand_int(VALUE obj, rb_random_t *rnd, VALUE vmax, int restrictive) if (FIXNUM_P(vmax)) { long max = FIX2LONG(vmax); if (max == -1) return Qnil; - r = random_ulong_limited(obj, rnd, max); + r = random_ulong_limited(obj, rng, rnd, max); return LONG2NUM(r); } - ret = random_ulong_limited_big(obj, rnd, vmax); + ret = random_ulong_limited_big(obj, rng, rnd, vmax); RB_GC_GUARD(vmax); return ret; } @@ -1460,7 +1457,7 @@ float_value(VALUE v) } static inline VALUE -rand_range(VALUE obj, rb_random_t* rnd, VALUE range) +rand_range(VALUE obj, const rb_random_interface_t *rng, rb_random_t* rnd, VALUE range) { VALUE beg = Qundef, end = Qundef, vmax, v; int excl = 0; @@ -1475,7 +1472,7 @@ rand_range(VALUE obj, rb_random_t* rnd, VALUE range) fixnum: if (FIXNUM_P(vmax)) { if ((max = FIX2LONG(vmax) - excl) >= 0) { - unsigned long r = random_ulong_limited(obj, rnd, (unsigned long)max); + unsigned long r = random_ulong_limited(obj, rng, rnd, (unsigned long)max); v = ULONG2NUM(r); } } @@ -1485,7 +1482,7 @@ rand_range(VALUE obj, rb_random_t* rnd, VALUE range) excl = 0; goto fixnum; } - v = random_ulong_limited_big(obj, rnd, vmax); + v = random_ulong_limited_big(obj, rng, rnd, vmax); } } else if (v = rb_check_to_float(vmax), !NIL_P(v)) { @@ -1503,7 +1500,7 @@ rand_range(VALUE obj, rb_random_t* rnd, VALUE range) } v = Qnil; if (max > 0.0) { - r = random_real(obj, rnd, excl); + r = random_real(obj, rng, rnd, excl); if (scale > 1) { return rb_float_new(+(+(+(r - 0.5) * max) * scale) + mid); } @@ -1536,7 +1533,7 @@ rand_range(VALUE obj, rb_random_t* rnd, VALUE range) return v; } -static VALUE rand_random(int argc, VALUE *argv, VALUE obj, rb_random_t *rnd); +static VALUE rand_random(int argc, VALUE *argv, VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd); /* * call-seq: @@ -1573,24 +1570,26 @@ static VALUE rand_random(int argc, VALUE *argv, VALUE obj, rb_random_t *rnd); static VALUE random_rand(int argc, VALUE *argv, VALUE obj) { - VALUE v = rand_random(argc, argv, obj, try_get_rnd(obj)); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); + VALUE v = rand_random(argc, argv, obj, rng, rnd); check_random_number(v, argv); return v; } static VALUE -rand_random(int argc, VALUE *argv, VALUE obj, rb_random_t *rnd) +rand_random(int argc, VALUE *argv, VALUE obj, const rb_random_interface_t *rng, rb_random_t *rnd) { VALUE vmax, v; if (rb_check_arity(argc, 0, 1) == 0) { - return rb_float_new(random_real(obj, rnd, TRUE)); + return rb_float_new(random_real(obj, rng, rnd, TRUE)); } vmax = argv[0]; if (NIL_P(vmax)) return Qnil; if (!RB_FLOAT_TYPE_P(vmax)) { v = rb_check_to_int(vmax); - if (!NIL_P(v)) return rand_int(obj, rnd, v, 1); + if (!NIL_P(v)) return rand_int(obj, rng, rnd, v, 1); } v = rb_check_to_float(vmax); if (!NIL_P(v)) { @@ -1599,12 +1598,12 @@ rand_random(int argc, VALUE *argv, VALUE obj, rb_random_t *rnd) return Qnil; } else { - double r = random_real(obj, rnd, TRUE); + double r = random_real(obj, rng, rnd, TRUE); if (max > 0.0) r *= max; return rb_float_new(r); } } - return rand_range(obj, rnd, vmax); + return rand_range(obj, rng, rnd, vmax); } /* @@ -1622,9 +1621,10 @@ rand_random(int argc, VALUE *argv, VALUE obj, rb_random_t *rnd) static VALUE rand_random_number(int argc, VALUE *argv, VALUE obj) { - rb_random_t *rnd = try_get_rnd(obj); - VALUE v = rand_random(argc, argv, obj, rnd); - if (NIL_P(v)) v = rand_random(0, 0, obj, rnd); + const rb_random_interface_t *rng; + rb_random_t *rnd = try_get_rnd(obj, &rng); + VALUE v = rand_random(argc, argv, obj, rng, rnd); + if (NIL_P(v)) v = rand_random(0, 0, obj, rng, rnd); else if (!v) invalid_argument(argv[0]); return v; } @@ -1703,18 +1703,19 @@ static VALUE rb_f_rand(int argc, VALUE *argv, VALUE obj) { VALUE vmax; + const rb_random_interface_t *rng = &random_mt_if; rb_random_t *rnd = default_rand_start(); if (rb_check_arity(argc, 0, 1) && !NIL_P(vmax = argv[0])) { - VALUE v = rand_range(obj, rnd, vmax); + VALUE v = rand_range(obj, rng, rnd, vmax); if (v != Qfalse) return v; vmax = rb_to_int(vmax); if (vmax != INT2FIX(0)) { - v = rand_int(obj, rnd, vmax, 0); + v = rand_int(obj, rng, rnd, vmax, 0); if (!NIL_P(v)) return v; } } - return DBL2NUM(random_real(obj, rnd, TRUE)); + return DBL2NUM(random_real(obj, rng, rnd, TRUE)); } /* @@ -1730,7 +1731,7 @@ rb_f_rand(int argc, VALUE *argv, VALUE obj) static VALUE random_s_rand(int argc, VALUE *argv, VALUE obj) { - VALUE v = rand_random(argc, argv, Qnil, default_rand_start()); + VALUE v = rand_random(argc, argv, Qnil, &random_mt_if, default_rand_start()); check_random_number(v, argv); return v; } From d615dbf4e28d26c3fd7a54ad58d7365521378599 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 28 Dec 2025 14:51:10 -0600 Subject: [PATCH 2249/2435] [DOC] Japanese for multi-byte characters --- doc/language/character_selectors.rdoc | 1 - doc/matchdata/begin.rdoc | 12 ++++++------ doc/matchdata/bytebegin.rdoc | 12 ++++++------ doc/matchdata/byteend.rdoc | 12 ++++++------ doc/matchdata/end.rdoc | 12 ++++++------ doc/matchdata/offset.rdoc | 12 ++++++------ 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/doc/language/character_selectors.rdoc b/doc/language/character_selectors.rdoc index 20685b8392b655..8bfc9b719b75bf 100644 --- a/doc/language/character_selectors.rdoc +++ b/doc/language/character_selectors.rdoc @@ -31,7 +31,6 @@ contained in the selector itself: 'abracadabra'.delete('abc') # => "rdr" '0123456789'.delete('258') # => "0134679" '!@#$%&*()_+'.delete('+&#') # => "!@$%*()_" - 'тест'.delete('т') # => "ес" 'こんにちは'.delete('に') # => "こんちは" Note that order and repetitions do not matter: diff --git a/doc/matchdata/begin.rdoc b/doc/matchdata/begin.rdoc index 8046dd9d55840c..6100617e19cf0c 100644 --- a/doc/matchdata/begin.rdoc +++ b/doc/matchdata/begin.rdoc @@ -10,12 +10,12 @@ returns the offset of the beginning of the nth match: m[3] # => "113" m.begin(3) # => 3 - m = /(т)(е)(с)/.match('тест') - # => # - m[0] # => "тес" - m.begin(0) # => 0 - m[3] # => "с" - m.begin(3) # => 2 + m = /(ん)(に)(ち)/.match('こんにちは') + # => # + m[0] # => "んにち" + m.begin(0) # => 1 + m[3] # => "ち" + m.begin(3) # => 3 When string or symbol argument +name+ is given, returns the offset of the beginning for the named match: diff --git a/doc/matchdata/bytebegin.rdoc b/doc/matchdata/bytebegin.rdoc index 5b40a7ef73ce4a..54e417a7fca510 100644 --- a/doc/matchdata/bytebegin.rdoc +++ b/doc/matchdata/bytebegin.rdoc @@ -10,12 +10,12 @@ returns the offset of the beginning of the nth match: m[3] # => "113" m.bytebegin(3) # => 3 - m = /(т)(е)(с)/.match('тест') - # => # - m[0] # => "тес" - m.bytebegin(0) # => 0 - m[3] # => "с" - m.bytebegin(3) # => 4 + m = /(ん)(に)(ち)/.match('こんにちは') + # => # + m[0] # => "んにち" + m.bytebegin(0) # => 3 + m[3] # => "ち" + m.bytebegin(3) # => 9 When string or symbol argument +name+ is given, returns the offset of the beginning for the named match: diff --git a/doc/matchdata/byteend.rdoc b/doc/matchdata/byteend.rdoc index eb576640220b05..0a03f762088d22 100644 --- a/doc/matchdata/byteend.rdoc +++ b/doc/matchdata/byteend.rdoc @@ -10,12 +10,12 @@ returns the offset of the end of the nth match: m[3] # => "113" m.byteend(3) # => 6 - m = /(т)(е)(с)/.match('тест') - # => # - m[0] # => "тес" - m.byteend(0) # => 6 - m[3] # => "с" - m.byteend(3) # => 6 + m = /(ん)(に)(ち)/.match('こんにちは') + # => # + m[0] # => "んにち" + m.byteend(0) # => 12 + m[3] # => "ち" + m.byteend(3) # => 12 When string or symbol argument +name+ is given, returns the offset of the end for the named match: diff --git a/doc/matchdata/end.rdoc b/doc/matchdata/end.rdoc index 0209b2d2fc097f..c43a5428f3d1ad 100644 --- a/doc/matchdata/end.rdoc +++ b/doc/matchdata/end.rdoc @@ -10,12 +10,12 @@ returns the offset of the end of the nth match: m[3] # => "113" m.end(3) # => 6 - m = /(т)(е)(с)/.match('тест') - # => # - m[0] # => "тес" - m.end(0) # => 3 - m[3] # => "с" - m.end(3) # => 3 + m = /(ん)(に)(ち)/.match('こんにちは') + # => # + m[0] # => "んにち" + m.end(0) # => 4 + m[3] # => "ち" + m.end(3) # => 4 When string or symbol argument +name+ is given, returns the offset of the end for the named match: diff --git a/doc/matchdata/offset.rdoc b/doc/matchdata/offset.rdoc index 0985316d763de2..4194ef7ef9e13a 100644 --- a/doc/matchdata/offset.rdoc +++ b/doc/matchdata/offset.rdoc @@ -11,12 +11,12 @@ returns the starting and ending offsets of the nth match: m[3] # => "113" m.offset(3) # => [3, 6] - m = /(т)(е)(с)/.match('тест') - # => # - m[0] # => "тес" - m.offset(0) # => [0, 3] - m[3] # => "с" - m.offset(3) # => [2, 3] + m = /(ん)(に)(ち)/.match('こんにちは') + # => # + m[0] # => "んにち" + m.offset(0) # => [1, 4] + m[3] # => "ち" + m.offset(3) # => [3, 4] When string or symbol argument +name+ is given, returns the starting and ending offsets for the named match: From 9e78353c0f840bf4aa7cceb4d1676f4d090d0096 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 28 Dec 2025 15:43:36 -0500 Subject: [PATCH 2250/2435] Fix maybe uninitialized warnings in random.c Fixes the following compiler warnings: random.c: In function `random_init`: random.c:416:38: warning: `rng` may be used uninitialized in this function [-Wmaybe-uninitialized] 416 | unsigned int major = rng->version.major; | ~~~~~~~~~~~~^~~~~~ random.c: In function `random_bytes`: random.c:1284:8: warning: `rng` may be used uninitialized in this function [-Wmaybe-uninitialized] 1284 | rng->get_bytes(rnd, ptr, n); | ~~~^~~~~~~~~~~ random.c:1299:34: note: `rng` was declared here 1299 | const rb_random_interface_t *rng; | ^~~ random.c: In function `rand_random_number`: random.c:1606:12: warning: `rng` may be used uninitialized in this function [-Wmaybe-uninitialized] 1606 | return rand_range(obj, rng, rnd, vmax); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ random.c:1624:34: note: `rng` was declared here 1624 | const rb_random_interface_t *rng; | ^~~ random.c: In function `random_rand`: random.c:1120:15: warning: `rng` may be used uninitialized in this function [-Wmaybe-uninitialized] 1120 | return rng->get_int32(rnd); | ~~~^~~~~~~~~~~ random.c:1573:34: note: `rng` was declared here 1573 | const rb_random_interface_t *rng; | ^~~ --- random.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/random.c b/random.c index d902603e98345a..ad7cbca426361c 100644 --- a/random.c +++ b/random.c @@ -405,7 +405,7 @@ rand_init(const rb_random_interface_t *rng, rb_random_t *rnd, VALUE seed) static VALUE random_init(int argc, VALUE *argv, VALUE obj) { - const rb_random_interface_t *rng; + const rb_random_interface_t *rng = NULL; rb_random_t *rnd = try_get_rnd(obj, &rng); if (!rng) { @@ -1296,7 +1296,7 @@ rand_bytes(const rb_random_interface_t *rng, rb_random_t *rnd, long n) static VALUE random_bytes(VALUE obj, VALUE len) { - const rb_random_interface_t *rng; + const rb_random_interface_t *rng = NULL; rb_random_t *rnd = try_get_rnd(obj, &rng); return rand_bytes(rng, rnd, NUM2LONG(rb_to_int(len))); } @@ -1570,7 +1570,7 @@ static VALUE rand_random(int argc, VALUE *argv, VALUE obj, const rb_random_inter static VALUE random_rand(int argc, VALUE *argv, VALUE obj) { - const rb_random_interface_t *rng; + const rb_random_interface_t *rng = NULL; rb_random_t *rnd = try_get_rnd(obj, &rng); VALUE v = rand_random(argc, argv, obj, rng, rnd); check_random_number(v, argv); @@ -1621,7 +1621,7 @@ rand_random(int argc, VALUE *argv, VALUE obj, const rb_random_interface_t *rng, static VALUE rand_random_number(int argc, VALUE *argv, VALUE obj) { - const rb_random_interface_t *rng; + const rb_random_interface_t *rng = NULL; rb_random_t *rnd = try_get_rnd(obj, &rng); VALUE v = rand_random(argc, argv, obj, rng, rnd); if (NIL_P(v)) v = rand_random(0, 0, obj, rng, rnd); From cb01b9023ec2007c03bddc992416c33f2c59a0e1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 03:15:03 +0900 Subject: [PATCH 2251/2435] rtypeddata.h: Add missing `RBIMPL_CAST` In public headers, casts should be enclosed in `RBIMPL_CAST` for compilation in C++. --- include/ruby/internal/core/rtypeddata.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 7d7df6c01a2d3b..55b7d2b47dc27f 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -476,7 +476,7 @@ RBIMPL_SYMBOL_EXPORT_END() */ #define TypedData_Make_Struct0(result, klass, type, size, data_type, sval) \ VALUE result = rb_data_typed_object_zalloc(klass, size, data_type); \ - (sval) = (type *)RTYPEDDATA_GET_DATA(result); \ + (sval) = RBIMPL_CAST((type *)RTYPEDDATA_GET_DATA(result)); \ RBIMPL_CAST(/*suppress unused variable warnings*/(void)(sval)) /** @@ -594,7 +594,7 @@ RBIMPL_ATTR_ARTIFICIAL() * @return Data type struct that corresponds to `obj`. * @pre `obj` must be an instance of ::RTypedData. */ -static inline const struct rb_data_type_struct * +static inline const rb_data_type_t * RTYPEDDATA_TYPE(VALUE obj) { #if RUBY_DEBUG @@ -604,7 +604,8 @@ RTYPEDDATA_TYPE(VALUE obj) } #endif - return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK); + VALUE type = RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK; + return RBIMPL_CAST((const rb_data_type_t *)type); } RBIMPL_ATTR_ARTIFICIAL() From 38701a4de83c72d855ce79f898526d5f079c96c5 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 28 Dec 2025 18:48:45 -0800 Subject: [PATCH 2252/2435] Remove deprecated support for to_set taking arguments --- NEWS.md | 6 +++++ benchmark/set.yml | 4 ---- lib/set/subclass_compatible.rb | 12 +++------- prelude.rb | 11 ++-------- range.c | 6 ++--- set.c | 28 ++++++------------------ spec/ruby/core/enumerable/to_set_spec.rb | 2 +- 7 files changed, 22 insertions(+), 47 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9712152da6a7b9..8ac808c2e7b6a4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,11 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Set + + * A deprecated behavior, `Set#to_set`, `Range#to_set`, and + `Enumerable#to_set` accepting arguments, was removed. [[Feature #21390]] + ## Stdlib updates We only list stdlib changes that are notable feature changes. @@ -61,3 +66,4 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT +[Feature #21390]: https://bugs.ruby-lang.org/issues/21390 diff --git a/benchmark/set.yml b/benchmark/set.yml index 43217036e25ba5..061509cb1f72a9 100644 --- a/benchmark/set.yml +++ b/benchmark/set.yml @@ -259,7 +259,3 @@ benchmark: to_set_10: s1.to_set to_set_100: s2.to_set to_set_1000: s3.to_set - to_set_arg_0: s0.to_set set_subclass - to_set_arg_10: s1.to_set set_subclass - to_set_arg_100: s2.to_set set_subclass - to_set_arg_1000: s3.to_set set_subclass diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb index ab0aedc0e5bafd..f43c34f6a2bd35 100644 --- a/lib/set/subclass_compatible.rb +++ b/lib/set/subclass_compatible.rb @@ -69,15 +69,9 @@ def replace(enum) end end - def to_set(*args, &block) - klass = if args.empty? - Set - else - warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 - args.shift - end - return self if instance_of?(Set) && klass == Set && block.nil? && args.empty? - klass.new(self, *args, &block) + def to_set(&block) + return self if instance_of?(Set) && block.nil? + Set.new(self, &block) end def flatten_merge(set, seen = {}) # :nodoc: diff --git a/prelude.rb b/prelude.rb index 36da381804e6a7..7b5a7668982a93 100644 --- a/prelude.rb +++ b/prelude.rb @@ -30,14 +30,7 @@ def pp(*objs) module Enumerable # Makes a set from the enumerable object with given arguments. - # Passing arguments to this method is deprecated. - def to_set(*args, &block) - klass = if args.empty? - Set - else - warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 - args.shift - end - klass.new(self, *args, &block) + def to_set(&block) + Set.new(self, &block) end end diff --git a/range.c b/range.c index 82c252e3ef50cd..7aa917bc067b68 100644 --- a/range.c +++ b/range.c @@ -1033,12 +1033,12 @@ range_to_a(VALUE range) * */ static VALUE -range_to_set(int argc, VALUE *argv, VALUE range) +range_to_set(VALUE range) { if (NIL_P(RANGE_END(range))) { rb_raise(rb_eRangeError, "cannot convert endless range to a set"); } - return rb_call_super(argc, argv); + return rb_call_super(0, NULL); } static VALUE @@ -2868,7 +2868,7 @@ Init_Range(void) rb_define_method(rb_cRange, "minmax", range_minmax, 0); rb_define_method(rb_cRange, "size", range_size, 0); rb_define_method(rb_cRange, "to_a", range_to_a, 0); - rb_define_method(rb_cRange, "to_set", range_to_set, -1); + rb_define_method(rb_cRange, "to_set", range_to_set, 0); rb_define_method(rb_cRange, "entries", range_to_a, 0); rb_define_method(rb_cRange, "to_s", range_to_s, 0); rb_define_method(rb_cRange, "inspect", range_inspect, 0); diff --git a/set.c b/set.c index d7704e9cf38a4f..4d8178ffc080de 100644 --- a/set.c +++ b/set.c @@ -648,36 +648,22 @@ set_i_to_a(VALUE set) /* * call-seq: - * to_set(klass = Set, *args, &block) -> self or new_set + * to_set(&block) -> self or new_set * - * Without arguments, returns +self+ (for duck-typing in methods that - * accept "set, or set-convertible" arguments). + * Without a block, if +self+ is an instance of +Set+, returns +self+. + * Otherwise, calls Set.new(self, &block). * * A form with arguments is _deprecated_. It converts the set to another * with klass.new(self, *args, &block). */ static VALUE -set_i_to_set(int argc, VALUE *argv, VALUE set) +set_i_to_set(VALUE set) { - VALUE klass; - - if (argc == 0) { - klass = rb_cSet; - argv = &set; - argc = 1; - } - else { - rb_warn_deprecated("passing arguments to Set#to_set", NULL); - klass = argv[0]; - argv[0] = set; - } - - if (klass == rb_cSet && rb_obj_is_instance_of(set, rb_cSet) && - argc == 1 && !rb_block_given_p()) { + if (rb_obj_is_instance_of(set, rb_cSet) && !rb_block_given_p()) { return set; } - return rb_funcall_passing_block(klass, id_new, argc, argv); + return rb_funcall_passing_block(rb_cSet, id_new, 0, NULL); } /* @@ -2292,7 +2278,7 @@ Init_Set(void) rb_define_method(rb_cSet, "superset?", set_i_superset, 1); rb_define_alias(rb_cSet, ">=", "superset?"); rb_define_method(rb_cSet, "to_a", set_i_to_a, 0); - rb_define_method(rb_cSet, "to_set", set_i_to_set, -1); + rb_define_method(rb_cSet, "to_set", set_i_to_set, 0); /* :nodoc: */ VALUE compat = rb_define_class_under(rb_cSet, "compatible", rb_cObject); diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb index e1fcd3a20d0273..91499aeb5b180c 100644 --- a/spec/ruby/core/enumerable/to_set_spec.rb +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -11,7 +11,7 @@ [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9] end - ruby_version_is "4.0" do + ruby_version_is "4.0"..."4.1" do it "instantiates an object of provided as the first argument set class" do set = nil proc{set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)}.should complain(/Enumerable#to_set/) From 4e0bb58a0a374b40b7691e7b7aa88e759a0fc9f2 Mon Sep 17 00:00:00 2001 From: Luke Jahnke Date: Mon, 29 Dec 2025 15:51:17 +1000 Subject: [PATCH 2253/2435] fix underflow --- pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pack.c b/pack.c index 3a5c1bfb9677cf..87a5c6787134c3 100644 --- a/pack.c +++ b/pack.c @@ -302,7 +302,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) else if (ISDIGIT(*p)) { errno = 0; len = STRTOUL(p, (char**)&p, 10); - if (errno) { + if (len < 0 || errno) { rb_raise(rb_eRangeError, "pack length too big"); } } From 72627d85e337e5d8c7fa5738dc4ec7f253f0738e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 12:52:29 +0900 Subject: [PATCH 2254/2435] Declare `rb_data_typed_t` parameters and return values as nonull --- include/ruby/internal/core/rtypeddata.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 55b7d2b47dc27f..0f430f0c6aaf5d 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -396,6 +396,7 @@ RBIMPL_ATTR_NONNULL((3)) */ VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type); +RBIMPL_ATTR_NONNULL((3)) /** * Identical to rb_data_typed_object_wrap(), except it allocates a new data * region internally instead of taking an existing one. The allocation is done @@ -411,6 +412,7 @@ VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t * */ VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t *type); +RBIMPL_ATTR_NONNULL(()) /** * Checks for the domestic relationship between the two. * @@ -425,6 +427,7 @@ VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t */ int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent); +RBIMPL_ATTR_NONNULL((2)) /** * Checks if the given object is of given kind. * @@ -435,6 +438,7 @@ int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t * */ int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type); +RBIMPL_ATTR_NONNULL((2)) /** * Identical to rb_typeddata_is_kind_of(), except it raises exceptions instead * of returning false. @@ -586,7 +590,7 @@ RTYPEDDATA_P(VALUE obj) RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() -/* :TODO: can this function be __attribute__((returns_nonnull)) or not? */ +RBIMPL_ATTR_RETURNS_NONNULL() /** * Queries for the type of given object. * @@ -605,7 +609,9 @@ RTYPEDDATA_TYPE(VALUE obj) #endif VALUE type = RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK; - return RBIMPL_CAST((const rb_data_type_t *)type); + const rb_data_type_t *ptr = RBIMPL_CAST((const rb_data_type_t *)type); + RBIMPL_ASSERT_OR_ASSUME(ptr); + return ptr; } RBIMPL_ATTR_ARTIFICIAL() @@ -650,6 +656,7 @@ rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) #define TypedData_Get_Struct(obj,type,data_type,sval) \ ((sval) = RBIMPL_CAST((type *)rbimpl_check_typeddata((obj), (data_type)))) +RBIMPL_ATTR_NONNULL((2)) /** * While we don't stop you from using this function, it seems to be an * implementation detail of #TypedData_Make_Struct, which is preferred over From 0f64da9672d88921439f6fdb306d16fece9b9c90 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:14:28 +0900 Subject: [PATCH 2255/2435] Make `rb_check_typeddata` and `rbimpl_check_typeddata` identical --- error.c | 53 ++++++-------- include/ruby/internal/core/rtypeddata.h | 96 +++++++++++++++++++++---- internal/error.h | 3 +- 3 files changed, 104 insertions(+), 48 deletions(-) diff --git a/error.c b/error.c index e1a01b985aae16..f21a682d65d352 100644 --- a/error.c +++ b/error.c @@ -1313,6 +1313,20 @@ rb_builtin_class_name(VALUE x) COLDFUNC NORETURN(static void unexpected_type(VALUE, int, int)); #define UNDEF_LEAKED "undef leaked to the Ruby space" +void +rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected) +{ + rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", + actual->wrap_struct_name, expected->wrap_struct_name); +} + +void +rb_unexpected_object_type(VALUE obj, const char *expected) +{ + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)", + displaying_class_of(obj), expected); +} + static void unexpected_type(VALUE x, int xt, int t) { @@ -1320,9 +1334,7 @@ unexpected_type(VALUE x, int xt, int t) VALUE mesg, exc = rb_eFatal; if (tname) { - mesg = rb_sprintf("wrong argument type %"PRIsVALUE" (expected %s)", - displaying_class_of(x), tname); - exc = rb_eTypeError; + rb_unexpected_object_type(x, tname); } else if (xt > T_MASK && xt <= 0x3f) { mesg = rb_sprintf("unknown type 0x%x (0x%x given, probably comes" @@ -1367,24 +1379,18 @@ rb_unexpected_type(VALUE x, int t) unexpected_type(x, TYPE(x), t); } +#undef rb_typeddata_inherited_p int rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent) { - while (child) { - if (child == parent) return 1; - child = child->parent; - } - return 0; + return rb_typeddata_inherited_p_inline(child, parent); } +#undef rb_typeddata_is_kind_of int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type) { - if (!RB_TYPE_P(obj, T_DATA) || - !RTYPEDDATA_P(obj) || !rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type)) { - return 0; - } - return 1; + return rb_typeddata_is_kind_of_inline(obj, data_type); } #undef rb_typeddata_is_instance_of @@ -1397,26 +1403,7 @@ rb_typeddata_is_instance_of(VALUE obj, const rb_data_type_t *data_type) void * rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type) { - VALUE actual; - - if (!RB_TYPE_P(obj, T_DATA)) { - actual = displaying_class_of(obj); - } - else if (!RTYPEDDATA_P(obj)) { - actual = displaying_class_of(obj); - } - else if (!rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type)) { - const char *name = RTYPEDDATA_TYPE(obj)->wrap_struct_name; - actual = rb_str_new_cstr(name); /* or rb_fstring_cstr? not sure... */ - } - else { - return RTYPEDDATA_GET_DATA(obj); - } - - const char *expected = data_type->wrap_struct_name; - rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected %s)", - actual, expected); - UNREACHABLE_RETURN(NULL); + return rbimpl_check_typeddata(obj, data_type); } /* exception classes */ diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 0f430f0c6aaf5d..8952ba91810aac 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -450,6 +450,36 @@ RBIMPL_ATTR_NONNULL((2)) * @post Upon successful return `obj`'s type is guaranteed `data_type`. */ void *rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_NONNULL((2)) +/** + * @private + * + * Fails with the given object's type incompatibility to the type. + * + * This is an implementation detail of Check_Type. People don't use it + * directly. + * + * @param[in] obj The object in question. + * @param[in] expected Name of expected data type of `obj`. + */ +void rb_unexpected_object_type(VALUE obj, const char *expected); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_NONNULL(()) +/** + * @private + * + * Fails with the given object's type incompatibility to the type. + * + * This is an implementation detail of #TypedData_Make_Struct. People don't + * use it directly. + * + * @param[in] actual Actual data type. + * @param[in] expected Expected data type. + */ +void rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected); RBIMPL_SYMBOL_EXPORT_END() /** @@ -565,6 +595,27 @@ rbimpl_rtypeddata_p(VALUE obj) return FL_TEST_RAW(obj, RUBY_TYPED_FL_IS_TYPED_DATA); } +RBIMPL_ATTR_PURE() +RBIMPL_ATTR_ARTIFICIAL() +/** + * @private + * + * Identical to rbimpl_rtypeddata_p(), except it is allowed to call on non-data + * objects. + * + * This is an implementation detail of inline functions defined in this file. + * People don't use it directly. + * + * @param[in] obj Object in question + * @retval true `obj` is an instance of ::RTypedData. + * @retval false `obj` is not an instance of ::RTypedData + */ +static inline bool +rbimpl_obj_typeddata_p(VALUE obj) +{ + return RB_TYPE_P(obj, RUBY_T_DATA) && rbimpl_rtypeddata_p(obj); +} + RBIMPL_ATTR_PURE_UNLESS_DEBUG() RBIMPL_ATTR_ARTIFICIAL() /** @@ -615,6 +666,29 @@ RTYPEDDATA_TYPE(VALUE obj) } RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL(()) +static inline bool +rb_typeddata_inherited_p_inline(const rb_data_type_t *child, const rb_data_type_t *parent) +{ + do { + if (RB_LIKELY(child == parent)) return true; + } while ((child = child->parent) != NULL); + return false; +} +#define rb_typeddata_inherited_p rb_typeddata_inherited_p_inline + +RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL((2)) +static inline bool +rb_typeddata_is_kind_of_inline(VALUE obj, const rb_data_type_t *data_type) +{ + if (RB_UNLIKELY(!rbimpl_obj_typeddata_p(obj))) return false; + return rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), data_type); +} +#define rb_typeddata_is_kind_of rb_typeddata_is_kind_of_inline + +RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NONNULL((2)) /** * @private * @@ -624,22 +698,16 @@ RBIMPL_ATTR_ARTIFICIAL() static inline void * rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) { - if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj))) { - const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); - void *data = RTYPEDDATA_GET_DATA(obj); - if (RB_LIKELY(actual_type == expected_type)) { - return data; - } - - while (actual_type) { - actual_type = actual_type->parent; - if (actual_type == expected_type) { - return data; - } - } + if (RB_UNLIKELY(!rbimpl_obj_typeddata_p(obj))) { + rb_unexpected_object_type(obj, expected_type->wrap_struct_name); + } + + const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); + if (RB_UNLIKELY(!rb_typeddata_inherited_p(actual_type, expected_type))){ + rb_unexpected_typeddata(actual_type, expected_type); } - return rb_check_typeddata(obj, expected_type); + return RTYPEDDATA_GET_DATA(obj); } diff --git a/internal/error.h b/internal/error.h index cecaa5c4a8d69e..4b41aee77b00ec 100644 --- a/internal/error.h +++ b/internal/error.h @@ -235,10 +235,11 @@ rb_key_err_raise(VALUE mesg, VALUE recv, VALUE name) rb_exc_raise(exc); } +RBIMPL_ATTR_NONNULL((2)) static inline bool rb_typeddata_is_instance_of_inline(VALUE obj, const rb_data_type_t *data_type) { - return RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && (RTYPEDDATA_TYPE(obj) == data_type); + return rbimpl_obj_typeddata_p(obj) && (RTYPEDDATA_TYPE(obj) == data_type); } typedef enum { From 56a6a21f28bd2b47bc96c58ae276b272933e7f62 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:19:28 +0900 Subject: [PATCH 2256/2435] Return `NULL` in a `void *` function --- include/ruby/internal/core/rtypeddata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 8952ba91810aac..1dd7397f7d335c 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -566,7 +566,7 @@ RTYPEDDATA_GET_DATA(VALUE obj) #if RUBY_DEBUG if (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { Check_Type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(false); + RBIMPL_UNREACHABLE_RETURN(NULL); } #endif From 26088dcd4a1f1784ae24387ce6a98fcad48749c5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 18:41:58 +0900 Subject: [PATCH 2257/2435] [DOC] State that `rb_unexpected_type` is private --- include/ruby/internal/error.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ruby/internal/error.h b/include/ruby/internal/error.h index 3ff885b2b151b7..5bf82bfe7d632e 100644 --- a/include/ruby/internal/error.h +++ b/include/ruby/internal/error.h @@ -421,11 +421,12 @@ void rb_readwrite_syserr_fail(enum rb_io_wait_readwrite waiting, int err, const RBIMPL_ATTR_COLD() RBIMPL_ATTR_NORETURN() /** + * @private + * * Fails with the given object's type incompatibility to the type. * - * It seems this function is visible from extension libraries only because - * RTYPEDDATA_TYPE() uses it on RUBY_DEBUG. So you can basically ignore it; - * use some other fine-grained method instead. + * This is an implementation detail of Check_Type. People don't use it + * directly. * * @param[in] self The object in question. * @param[in] t Expected type of the object. From da89f7f58d6e0a662dfdeaf7df7aea9e3e7c02b5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 29 Dec 2025 20:26:00 +0900 Subject: [PATCH 2258/2435] Prefer `ALLOCV` over `ALLOCA` for unknown size `ALLOCA` with too large size may result in stack overflow. Incidentally, this suppresses the GCC false maybe-uninitialized warning in `product_each`. Also shrink `struct product_state` when `sizeof(int) < sizeof(VALUE)`. --- enumerator.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/enumerator.c b/enumerator.c index 89ec5035305753..eb241a3b58dab7 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3581,9 +3581,9 @@ enum_product_enum_size(VALUE obj, VALUE args, VALUE eobj) struct product_state { VALUE obj; VALUE block; + int index; int argc; VALUE *argv; - int index; }; static VALUE product_each(VALUE, struct product_state *); @@ -3622,15 +3622,23 @@ enum_product_run(VALUE obj, VALUE block) { struct enum_product *ptr = enum_product_ptr(obj); int argc = RARRAY_LENINT(ptr->enums); + if (argc == 0) { /* no need to allocate state.argv */ + rb_funcall(block, id_call, 1, rb_ary_new()); + return obj; + } + + VALUE argsbuf = 0; struct product_state state = { .obj = obj, .block = block, .index = 0, .argc = argc, - .argv = ALLOCA_N(VALUE, argc), + .argv = ALLOCV_N(VALUE, argsbuf, argc), }; - return product_each(obj, &state); + VALUE ret = product_each(obj, &state); + ALLOCV_END(argsbuf); + return ret; } /* From 56147001ec439a7d6b887402c8a66d3ee625e598 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 22 Dec 2025 19:38:47 -0500 Subject: [PATCH 2259/2435] Move MEMO_NEW to imemo.c and rename to rb_imemo_memo_new --- enum.c | 36 ++++++++++++++++++------------------ enumerator.c | 4 ++-- imemo.c | 11 +++++++++++ internal/imemo.h | 12 +----------- re.c | 4 ++-- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/enum.c b/enum.c index ba0dc94744cc3b..765c093da79ae6 100644 --- a/enum.c +++ b/enum.c @@ -127,7 +127,7 @@ static VALUE enum_grep0(VALUE obj, VALUE pat, VALUE test) { VALUE ary = rb_ary_new(); - struct MEMO *memo = MEMO_NEW(pat, ary, test); + struct MEMO *memo = rb_imemo_memo_new(pat, ary, test); rb_block_call_func_t fn; if (rb_block_given_p()) { fn = grep_iter_i; @@ -317,7 +317,7 @@ enum_count(int argc, VALUE *argv, VALUE obj) func = count_i; } - memo = MEMO_NEW(item, 0, 0); + memo = rb_imemo_memo_new(item, 0, 0); rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo); return imemo_count_value(memo); } @@ -382,7 +382,7 @@ enum_find(int argc, VALUE *argv, VALUE obj) if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; RETURN_ENUMERATOR(obj, argc, argv); - memo = MEMO_NEW(Qundef, 0, 0); + memo = rb_imemo_memo_new(Qundef, 0, 0); if (rb_block_pair_yield_optimizable()) rb_block_call2(obj, id_each, 0, 0, find_i_fast, (VALUE)memo, RB_BLOCK_NO_USE_PACKED_ARGS); else @@ -467,7 +467,7 @@ enum_find_index(int argc, VALUE *argv, VALUE obj) func = find_index_i; } - memo = MEMO_NEW(Qnil, condition_value, 0); + memo = rb_imemo_memo_new(Qnil, condition_value, 0); rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo); return memo->v1; } @@ -1084,7 +1084,7 @@ enum_inject(int argc, VALUE *argv, VALUE obj) return ary_inject_op(obj, init, op); } - memo = MEMO_NEW(init, Qnil, op); + memo = rb_imemo_memo_new(init, Qnil, op); rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo); if (UNDEF_P(memo->v1)) return Qnil; return memo->v1; @@ -1142,7 +1142,7 @@ enum_partition(VALUE obj) RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size); - memo = MEMO_NEW(rb_ary_new(), rb_ary_new(), 0); + memo = rb_imemo_memo_new(rb_ary_new(), rb_ary_new(), 0); rb_block_call(obj, id_each, 0, 0, partition_i, (VALUE)memo); return rb_assoc_new(memo->v1, memo->v2); @@ -1345,7 +1345,7 @@ enum_first(int argc, VALUE *argv, VALUE obj) return enum_take(obj, argv[0]); } else { - memo = MEMO_NEW(Qnil, 0, 0); + memo = rb_imemo_memo_new(Qnil, 0, 0); rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo); return memo->v1; } @@ -1722,7 +1722,7 @@ enum_sort_by(VALUE obj) RBASIC_CLEAR_CLASS(ary); buf = rb_ary_hidden_new(SORT_BY_BUFSIZE*2); rb_ary_store(buf, SORT_BY_BUFSIZE*2-1, Qnil); - memo = MEMO_NEW(0, 0, 0); + memo = rb_imemo_memo_new(0, 0, 0); data = (struct sort_by_data *)&memo->v1; RB_OBJ_WRITE(memo, &data->ary, ary); RB_OBJ_WRITE(memo, &data->buf, buf); @@ -1766,7 +1766,7 @@ enum_sort_by(VALUE obj) #define ENUM_BLOCK_CALL(name) \ rb_block_call2(obj, id_each, 0, 0, ENUMFUNC(name), (VALUE)memo, rb_block_given_p() && rb_block_pair_yield_optimizable() ? RB_BLOCK_NO_USE_PACKED_ARGS : 0); -#define MEMO_ENUM_NEW(v1) (rb_check_arity(argc, 0, 1), MEMO_NEW((v1), (argc ? *argv : 0), 0)) +#define MEMO_ENUM_NEW(v1) (rb_check_arity(argc, 0, 1), rb_imemo_memo_new((v1), (argc ? *argv : 0), 0)) #define DEFINE_ENUMFUNCS(name) \ static VALUE enum_##name##_func(VALUE result, struct MEMO *memo); \ @@ -2754,7 +2754,7 @@ enum_min_by(int argc, VALUE *argv, VALUE obj) if (argc && !NIL_P(num = argv[0])) return rb_nmin_run(obj, num, 1, 0, 0); - memo = MEMO_NEW(Qundef, Qnil, 0); + memo = rb_imemo_memo_new(Qundef, Qnil, 0); rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo); return memo->v2; } @@ -2828,7 +2828,7 @@ enum_max_by(int argc, VALUE *argv, VALUE obj) if (argc && !NIL_P(num = argv[0])) return rb_nmin_run(obj, num, 1, 1, 0); - memo = MEMO_NEW(Qundef, Qnil, 0); + memo = rb_imemo_memo_new(Qundef, Qnil, 0); rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo); return memo->v2; } @@ -2979,7 +2979,7 @@ member_i(RB_BLOCK_CALL_FUNC_ARGLIST(iter, args)) static VALUE enum_member(VALUE obj, VALUE val) { - struct MEMO *memo = MEMO_NEW(val, Qfalse, 0); + struct MEMO *memo = rb_imemo_memo_new(val, Qfalse, 0); rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo); return memo->v2; @@ -3231,7 +3231,7 @@ enum_each_slice(VALUE obj, VALUE n) size = limit_by_enum_size(obj, size); ary = rb_ary_new2(size); arity = rb_block_arity(); - memo = MEMO_NEW(ary, dont_recycle_block_arg(arity), size); + memo = rb_imemo_memo_new(ary, dont_recycle_block_arg(arity), size); rb_block_call(obj, id_each, 0, 0, each_slice_i, (VALUE)memo); ary = memo->v1; if (RARRAY_LEN(ary) > 0) rb_yield(ary); @@ -3307,7 +3307,7 @@ enum_each_cons(VALUE obj, VALUE n) RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_cons_size); arity = rb_block_arity(); if (enum_size_over_p(obj, size)) return obj; - memo = MEMO_NEW(rb_ary_new2(size), dont_recycle_block_arg(arity), size); + memo = rb_imemo_memo_new(rb_ary_new2(size), dont_recycle_block_arg(arity), size); rb_block_call(obj, id_each, 0, 0, each_cons_i, (VALUE)memo); return obj; @@ -3536,7 +3536,7 @@ enum_zip(int argc, VALUE *argv, VALUE obj) } /* TODO: use NODE_DOT2 as memo(v, v, -) */ - memo = MEMO_NEW(result, args, 0); + memo = rb_imemo_memo_new(result, args, 0); rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo); return result; @@ -3579,7 +3579,7 @@ enum_take(VALUE obj, VALUE n) if (len == 0) return rb_ary_new2(0); result = rb_ary_new2(len); - memo = MEMO_NEW(result, 0, len); + memo = rb_imemo_memo_new(result, 0, len); rb_block_call(obj, id_each, 0, 0, take_i, (VALUE)memo); return result; } @@ -3667,7 +3667,7 @@ enum_drop(VALUE obj, VALUE n) } result = rb_ary_new(); - memo = MEMO_NEW(result, 0, len); + memo = rb_imemo_memo_new(result, 0, len); rb_block_call(obj, id_each, 0, 0, drop_i, (VALUE)memo); return result; } @@ -3726,7 +3726,7 @@ enum_drop_while(VALUE obj) RETURN_ENUMERATOR(obj, 0, 0); result = rb_ary_new(); - memo = MEMO_NEW(result, 0, FALSE); + memo = rb_imemo_memo_new(result, 0, FALSE); rb_block_call(obj, id_each, 0, 0, drop_while_i, (VALUE)memo); return result; } diff --git a/enumerator.c b/enumerator.c index eb241a3b58dab7..8580c605dffc16 100644 --- a/enumerator.c +++ b/enumerator.c @@ -671,7 +671,7 @@ enumerator_with_index(int argc, VALUE *argv, VALUE obj) rb_check_arity(argc, 0, 1); RETURN_SIZED_ENUMERATOR(obj, argc, argv, enumerator_enum_size); memo = (!argc || NIL_P(memo = argv[0])) ? INT2FIX(0) : rb_to_int(memo); - return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)MEMO_NEW(memo, 0, 0)); + return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)rb_imemo_memo_new(memo, 0, 0)); } /* @@ -1613,7 +1613,7 @@ lazy_init_yielder(RB_BLOCK_CALL_FUNC_ARGLIST(_, m)) VALUE memos = rb_attr_get(yielder, id_memo); struct MEMO *result; - result = MEMO_NEW(m, rb_enum_values_pack(argc, argv), + result = rb_imemo_memo_new(m, rb_enum_values_pack(argc, argv), argc > 1 ? LAZY_MEMO_PACKED : 0); return lazy_yielder_result(result, yielder, procs_array, memos, 0); } diff --git a/imemo.c b/imemo.c index 2d63b7cd99549a..58029293764aef 100644 --- a/imemo.c +++ b/imemo.c @@ -97,6 +97,17 @@ rb_free_tmp_buffer(volatile VALUE *store) } } +struct MEMO * +rb_imemo_memo_new(VALUE a, VALUE b, VALUE c) +{ + struct MEMO *memo = IMEMO_NEW(struct MEMO, imemo_memo, 0); + *((VALUE *)&memo->v1) = a; + *((VALUE *)&memo->v2) = b; + *((VALUE *)&memo->u3.value) = c; + + return memo; +} + static VALUE imemo_fields_new(VALUE owner, size_t capa, bool shareable) { diff --git a/internal/imemo.h b/internal/imemo.h index f8bda26f0b50f9..7192631e92a5fc 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -134,6 +134,7 @@ typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size, bool is_shareable); VALUE rb_imemo_tmpbuf_new(void); +struct MEMO *rb_imemo_memo_new(VALUE a, VALUE b, VALUE c); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); @@ -152,17 +153,6 @@ RUBY_SYMBOL_EXPORT_BEGIN const char *rb_imemo_name(enum imemo_type type); RUBY_SYMBOL_EXPORT_END -static inline struct MEMO * -MEMO_NEW(VALUE a, VALUE b, VALUE c) -{ - struct MEMO *memo = IMEMO_NEW(struct MEMO, imemo_memo, 0); - *((VALUE *)&memo->v1) = a; - *((VALUE *)&memo->v2) = b; - *((VALUE *)&memo->u3.value) = c; - - return memo; -} - static inline enum imemo_type imemo_type(VALUE imemo) { diff --git a/re.c b/re.c index 19bf674c268665..621af13cd73f16 100644 --- a/re.c +++ b/re.c @@ -2469,7 +2469,7 @@ match_named_captures(int argc, VALUE *argv, VALUE match) } hash = rb_hash_new(); - memo = MEMO_NEW(hash, match, symbolize_names); + memo = rb_imemo_memo_new(hash, match, symbolize_names); onig_foreach_name(RREGEXP(RMATCH(match)->regexp)->ptr, match_named_captures_iter, (void*)memo); @@ -2508,7 +2508,7 @@ match_deconstruct_keys(VALUE match, VALUE keys) h = rb_hash_new_with_size(onig_number_of_names(RREGEXP_PTR(RMATCH(match)->regexp))); struct MEMO *memo; - memo = MEMO_NEW(h, match, 1); + memo = rb_imemo_memo_new(h, match, 1); onig_foreach_name(RREGEXP_PTR(RMATCH(match)->regexp), match_named_captures_iter, (void*)memo); From 01cd9c9fade0c1c6f13f11c86e86c0caeefd38bc Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 23 Dec 2025 11:45:16 -0500 Subject: [PATCH 2260/2435] Add rb_gc_register_pinning_obj --- gc.c | 13 +++++++++++++ gc/default/default.c | 6 ++++++ gc/gc_impl.h | 1 + hash.c | 4 ++++ imemo.c | 5 +++++ internal/gc.h | 1 + proc.c | 3 +++ string.c | 1 + 8 files changed, 34 insertions(+) diff --git a/gc.c b/gc.c index 60860d1e5fe398..a1dc878dc6fafe 100644 --- a/gc.c +++ b/gc.c @@ -638,6 +638,7 @@ typedef struct gc_function_map { void (*declare_weak_references)(void *objspace_ptr, VALUE obj); bool (*handle_weak_references_alive_p)(void *objspace_ptr, VALUE obj); // Compaction + void (*register_pinning_obj)(void *objspace_ptr, VALUE obj); bool (*object_moved_p)(void *objspace_ptr, VALUE obj); VALUE (*location)(void *objspace_ptr, VALUE value); // Write barriers @@ -813,6 +814,7 @@ ruby_modular_gc_init(void) load_modular_gc_func(declare_weak_references); load_modular_gc_func(handle_weak_references_alive_p); // Compaction + load_modular_gc_func(register_pinning_obj); load_modular_gc_func(object_moved_p); load_modular_gc_func(location); // Write barriers @@ -894,6 +896,7 @@ ruby_modular_gc_init(void) # define rb_gc_impl_declare_weak_references rb_gc_functions.declare_weak_references # define rb_gc_impl_handle_weak_references_alive_p rb_gc_functions.handle_weak_references_alive_p // Compaction +# define rb_gc_impl_register_pinning_obj rb_gc_functions.register_pinning_obj # define rb_gc_impl_object_moved_p rb_gc_functions.object_moved_p # define rb_gc_impl_location rb_gc_functions.location // Write barriers @@ -1049,6 +1052,12 @@ rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, shape_id, TRUE, size); } +void +rb_gc_register_pinning_obj(VALUE obj) +{ + rb_gc_impl_register_pinning_obj(rb_gc_get_objspace(), obj); +} + #define UNEXPECTED_NODE(func) \ rb_bug(#func"(): GC does not handle T_NODE 0x%x(%p) 0x%"PRIxVALUE, \ BUILTIN_TYPE(obj), (void*)(obj), RBASIC(obj)->flags) @@ -1069,6 +1078,8 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU if (klass) rb_data_object_check(klass); VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA, ROOT_SHAPE_ID, !dmark, sizeof(struct RTypedData)); + rb_gc_register_pinning_obj(obj); + struct RData *data = (struct RData *)obj; data->dmark = dmark; data->dfree = dfree; @@ -1093,6 +1104,8 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark; VALUE obj = newobj_of(GET_RACTOR(), klass, T_DATA | RUBY_TYPED_FL_IS_TYPED_DATA, ROOT_SHAPE_ID, wb_protected, size); + rb_gc_register_pinning_obj(obj); + struct RTypedData *data = (struct RTypedData *)obj; data->fields_obj = 0; *(VALUE *)&data->type = ((VALUE)type) | typed_flag; diff --git a/gc/default/default.c b/gc/default/default.c index 0e92c35598f09f..c33570f920f647 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -7059,6 +7059,12 @@ gc_sort_heap_by_compare_func(rb_objspace_t *objspace, gc_compact_compare_func co } #endif +void +rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj) +{ + /* no-op */ +} + bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj) { diff --git a/gc/gc_impl.h b/gc/gc_impl.h index bb97fa4c09a4be..7898316a75e536 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -86,6 +86,7 @@ GC_IMPL_FN void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj); GC_IMPL_FN void rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj); GC_IMPL_FN bool rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj); // Compaction +GC_IMPL_FN void rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj); GC_IMPL_FN bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj); GC_IMPL_FN VALUE rb_gc_impl_location(void *objspace_ptr, VALUE value); // Write barriers diff --git a/hash.c b/hash.c index 3669f55d5024d0..c4e8512f64c458 100644 --- a/hash.c +++ b/hash.c @@ -4673,6 +4673,8 @@ rb_hash_compare_by_id(VALUE hash) RHASH_ST_CLEAR(tmp); } + rb_gc_register_pinning_obj(hash); + return hash; } @@ -4702,6 +4704,7 @@ rb_ident_hash_new(void) { VALUE hash = rb_hash_new(); hash_st_table_init(hash, &identhash, 0); + rb_gc_register_pinning_obj(hash); return hash; } @@ -4710,6 +4713,7 @@ rb_ident_hash_new_with_size(st_index_t size) { VALUE hash = rb_hash_new(); hash_st_table_init(hash, &identhash, size); + rb_gc_register_pinning_obj(hash); return hash; } diff --git a/imemo.c b/imemo.c index 58029293764aef..8b3018523f0155 100644 --- a/imemo.c +++ b/imemo.c @@ -54,6 +54,8 @@ rb_imemo_tmpbuf_new(void) VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); NEWOBJ_OF(obj, rb_imemo_tmpbuf_t, 0, flags, sizeof(rb_imemo_tmpbuf_t), NULL); + rb_gc_register_pinning_obj((VALUE)obj); + obj->ptr = NULL; obj->cnt = 0; @@ -101,6 +103,9 @@ struct MEMO * rb_imemo_memo_new(VALUE a, VALUE b, VALUE c) { struct MEMO *memo = IMEMO_NEW(struct MEMO, imemo_memo, 0); + + rb_gc_register_pinning_obj((VALUE)memo); + *((VALUE *)&memo->v1) = a; *((VALUE *)&memo->v2) = b; *((VALUE *)&memo->u3.value) = c; diff --git a/internal/gc.h b/internal/gc.h index 8fff2e83361192..ee1f390e104cff 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -204,6 +204,7 @@ static inline void *ruby_sized_xrealloc_inlined(void *ptr, size_t new_size, size static inline void *ruby_sized_xrealloc2_inlined(void *ptr, size_t new_count, size_t elemsiz, size_t old_count) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2, 3)); static inline void ruby_sized_xfree_inlined(void *ptr, size_t size); void rb_gc_obj_id_moved(VALUE obj); +void rb_gc_register_pinning_obj(VALUE obj); void *rb_gc_ractor_cache_alloc(rb_ractor_t *ractor); void rb_gc_ractor_cache_free(void *cache); diff --git a/proc.c b/proc.c index d72f8d952e9465..a0fc3a7e0837f2 100644 --- a/proc.c +++ b/proc.c @@ -898,6 +898,9 @@ rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int m rb_execution_context_t *ec = GET_EC(); struct vm_ifunc *ifunc = IMEMO_NEW(struct vm_ifunc, imemo_ifunc, (VALUE)rb_vm_svar_lep(ec, ec->cfp)); + + rb_gc_register_pinning_obj((VALUE)ifunc); + ifunc->func = func; ifunc->data = data; ifunc->argc.min = min_argc; diff --git a/string.c b/string.c index b70cee020de6bd..b6c6d3f6268caf 100644 --- a/string.c +++ b/string.c @@ -205,6 +205,7 @@ str_enc_fastpath(VALUE str) RUBY_ASSERT(RSTRING_PTR(str) <= RSTRING_PTR(shared_str) + RSTRING_LEN(shared_str)); \ RB_OBJ_WRITE((str), &RSTRING(str)->as.heap.aux.shared, (shared_str)); \ FL_SET((str), STR_SHARED); \ + rb_gc_register_pinning_obj(str); \ FL_SET((shared_str), STR_SHARED_ROOT); \ if (RBASIC_CLASS((shared_str)) == 0) /* for CoW-friendliness */ \ FL_SET_RAW((shared_str), STR_BORROWED); \ From 7902ae34d01a142fcbc0f669d93b1af5664ece42 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 24 Dec 2025 20:38:22 -0500 Subject: [PATCH 2261/2435] Add rb_gc_move_obj_during_marking --- gc.c | 8 ++++++++ gc/gc.h | 1 + 2 files changed, 9 insertions(+) diff --git a/gc.c b/gc.c index a1dc878dc6fafe..8a18a129786f33 100644 --- a/gc.c +++ b/gc.c @@ -3173,6 +3173,14 @@ gc_mark_classext_iclass(rb_classext_t *ext, bool prime, VALUE box_value, void *a #define TYPED_DATA_REFS_OFFSET_LIST(d) (size_t *)(uintptr_t)RTYPEDDATA_TYPE(d)->function.dmark +void +rb_gc_move_obj_during_marking(VALUE from, VALUE to) +{ + if (rb_obj_gen_fields_p(to)) { + rb_mark_generic_ivar(from); + } +} + void rb_gc_mark_children(void *objspace, VALUE obj) { diff --git a/gc/gc.h b/gc/gc.h index 69dacf488f7db1..a5edc266e75b1a 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -107,6 +107,7 @@ MODULAR_GC_FN void *rb_gc_get_ractor_newobj_cache(void); MODULAR_GC_FN void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context); MODULAR_GC_FN void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context); MODULAR_GC_FN void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context); +MODULAR_GC_FN void rb_gc_move_obj_during_marking(VALUE from, VALUE to); #endif #if USE_MODULAR_GC From 782d959f674b5088377191a4b34ed2f5bbb7c022 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 26 Dec 2025 11:55:42 -0500 Subject: [PATCH 2262/2435] Implement moving Immix in MMTk This commit implements moving Immix in MMTk, which allows objects to move in the GC. The performance of this implementation is not yet amazing. It is very similar to non-moving Immix in many of them and slightly slower in others. The benchmark results is shown below. -------------- ----------------- ---------- --------- bench Moving Immix (ms) stddev (%) RSS (MiB) activerecord 241.9 0.5 86.6 chunky-png 447.8 0.8 74.9 erubi-rails 1183.9 0.8 136.1 hexapdf 1607.9 2.6 402.3 liquid-c 45.4 6.7 44.9 liquid-compile 44.1 9.3 53.0 liquid-render 105.4 4.5 55.9 lobsters 650.1 9.7 418.4 mail 115.4 2.1 64.4 psych-load 1656.8 0.8 43.6 railsbench 1653.5 1.3 149.8 rubocop 127.0 15.6 142.1 ruby-lsp 130.7 10.5 99.4 sequel 52.8 7.2 45.6 shipit 1187.0 3.9 311.0 -------------- ----------------- ---------- --------- -------------- --------------------- ---------- --------- bench Non-moving Immix (ms) stddev (%) RSS (MiB) activerecord 218.9 2.7 86.1 chunky-png 464.6 0.8 66.7 erubi-rails 1119.0 4.3 132.7 hexapdf 1539.8 1.8 425.2 liquid-c 40.6 6.9 45.2 liquid-compile 40.6 8.1 52.9 liquid-render 99.3 2.3 48.3 mail 107.4 5.3 65.4 psych-load 1535.6 1.0 39.5 railsbench 1565.6 1.1 149.6 rubocop 122.5 14.3 146.7 ruby-lsp 128.4 10.7 106.4 sequel 44.1 4.0 45.7 shipit 1154.5 2.7 358.5 -------------- --------------------- ---------- --------- --- gc/mmtk/Cargo.toml | 2 +- gc/mmtk/mmtk.c | 112 ++++++++++++++++---- gc/mmtk/mmtk.h | 9 +- gc/mmtk/src/abi.rs | 7 +- gc/mmtk/src/api.rs | 7 ++ gc/mmtk/src/binding.rs | 3 + gc/mmtk/src/collection.rs | 19 +++- gc/mmtk/src/lib.rs | 1 + gc/mmtk/src/object_model.rs | 40 ++++++- gc/mmtk/src/pinning_registry.rs | 179 ++++++++++++++++++++++++++++++++ gc/mmtk/src/scanning.rs | 13 ++- gc/mmtk/src/weak_proc.rs | 39 ++++++- 12 files changed, 393 insertions(+), 38 deletions(-) create mode 100644 gc/mmtk/src/pinning_registry.rs diff --git a/gc/mmtk/Cargo.toml b/gc/mmtk/Cargo.toml index d0bc5b3ff5d9f8..d856122900333d 100644 --- a/gc/mmtk/Cargo.toml +++ b/gc/mmtk/Cargo.toml @@ -21,7 +21,7 @@ probe = "0.5" sysinfo = "0.32.0" [dependencies.mmtk] -features = ["is_mmtk_object", "object_pinning", "sticky_immix_non_moving_nursery", "immix_non_moving"] +features = ["is_mmtk_object", "object_pinning", "sticky_immix_non_moving_nursery"] # Uncomment the following lines to use mmtk-core from the official repository. git = "https://github.com/mmtk/mmtk-core.git" diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 634abc068a44f5..b507e337b8a132 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -183,6 +183,18 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) RB_GC_VM_UNLOCK(lock_lev); } +static void +rb_mmtk_before_updating_jit_code(void) +{ + rb_gc_before_updating_jit_code(); +} + +static void +rb_mmtk_after_updating_jit_code(void) +{ + rb_gc_after_updating_jit_code(); +} + static size_t rb_mmtk_number_of_mutators(void) { @@ -247,9 +259,19 @@ rb_mmtk_scan_objspace(void) } static void -rb_mmtk_scan_object_ruby_style(MMTk_ObjectReference object) +rb_mmtk_move_obj_during_marking(MMTk_ObjectReference from, MMTk_ObjectReference to) { - rb_gc_mark_children(rb_gc_get_objspace(), (VALUE)object); + rb_gc_move_obj_during_marking((VALUE)from, (VALUE)to); +} + +static void +rb_mmtk_update_object_references(MMTk_ObjectReference mmtk_object) +{ + VALUE object = (VALUE)mmtk_object; + + if (!RB_FL_TEST(object, RUBY_FL_WEAK_REFERENCE)) { + rb_gc_update_object_references(rb_gc_get_objspace(), object); + } } static void @@ -259,9 +281,15 @@ rb_mmtk_call_gc_mark_children(MMTk_ObjectReference object) } static void -rb_mmtk_handle_weak_references(MMTk_ObjectReference object) +rb_mmtk_handle_weak_references(MMTk_ObjectReference mmtk_object, bool moving) { - rb_gc_handle_weak_references((VALUE)object); + VALUE object = (VALUE)mmtk_object; + + rb_gc_handle_weak_references(object); + + if (moving) { + rb_gc_update_object_references(rb_gc_get_objspace(), object); + } } static void @@ -332,19 +360,35 @@ rb_mmtk_update_finalizer_table(void) } static int -rb_mmtk_update_table_i(VALUE val, void *data) +rb_mmtk_global_tables_count(void) +{ + return RB_GC_VM_WEAK_TABLE_COUNT; +} + +static inline VALUE rb_mmtk_call_object_closure(VALUE obj, bool pin); + +static int +rb_mmtk_update_global_tables_i(VALUE val, void *data) { if (!mmtk_is_reachable((MMTk_ObjectReference)val)) { return ST_DELETE; } + // TODO: check only if in moving GC + if (rb_mmtk_call_object_closure(val, false) != val) { + return ST_REPLACE; + } + return ST_CONTINUE; } static int -rb_mmtk_global_tables_count(void) +rb_mmtk_update_global_tables_replace_i(VALUE *ptr, void *data) { - return RB_GC_VM_WEAK_TABLE_COUNT; + // TODO: cache the new location so we don't call rb_mmtk_call_object_closure twice + *ptr = rb_mmtk_call_object_closure(*ptr, false); + + return ST_CONTINUE; } static void @@ -352,7 +396,14 @@ rb_mmtk_update_global_tables(int table) { RUBY_ASSERT(table < RB_GC_VM_WEAK_TABLE_COUNT); - rb_gc_vm_weak_table_foreach(rb_mmtk_update_table_i, NULL, NULL, true, (enum rb_gc_vm_weak_tables)table); + // TODO: set weak_only to true for non-moving GC + rb_gc_vm_weak_table_foreach( + rb_mmtk_update_global_tables_i, + rb_mmtk_update_global_tables_replace_i, + NULL, + false, + (enum rb_gc_vm_weak_tables)table + ); } static bool @@ -376,11 +427,14 @@ MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_stop_the_world, rb_mmtk_resume_mutators, rb_mmtk_block_for_gc, + rb_mmtk_before_updating_jit_code, + rb_mmtk_after_updating_jit_code, rb_mmtk_number_of_mutators, rb_mmtk_get_mutators, rb_mmtk_scan_gc_roots, rb_mmtk_scan_objspace, - rb_mmtk_scan_object_ruby_style, + rb_mmtk_move_obj_during_marking, + rb_mmtk_update_object_references, rb_mmtk_call_gc_mark_children, rb_mmtk_handle_weak_references, rb_mmtk_call_obj_free, @@ -738,15 +792,23 @@ rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size) void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff) { } // Marking +static inline VALUE +rb_mmtk_call_object_closure(VALUE obj, bool pin) +{ + return (VALUE)rb_mmtk_gc_thread_tls->object_closure.c_function( + rb_mmtk_gc_thread_tls->object_closure.rust_closure, + rb_mmtk_gc_thread_tls->gc_context, + (MMTk_ObjectReference)obj, + pin + ); +} + void rb_gc_impl_mark(void *objspace_ptr, VALUE obj) { if (RB_SPECIAL_CONST_P(obj)) return; - rb_mmtk_gc_thread_tls->object_closure.c_function(rb_mmtk_gc_thread_tls->object_closure.rust_closure, - rb_mmtk_gc_thread_tls->gc_context, - (MMTk_ObjectReference)obj, - false); + rb_mmtk_call_object_closure(obj, false); } void @@ -754,8 +816,10 @@ rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr) { if (RB_SPECIAL_CONST_P(*ptr)) return; - // TODO: make it movable - rb_gc_impl_mark(objspace_ptr, *ptr); + VALUE new_obj = rb_mmtk_call_object_closure(*ptr, false); + if (new_obj != *ptr) { + *ptr = new_obj; + } } void @@ -763,8 +827,7 @@ rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj) { if (RB_SPECIAL_CONST_P(obj)) return; - // TODO: also pin - rb_gc_impl_mark(objspace_ptr, obj); + rb_mmtk_call_object_closure(obj, true); } void @@ -775,11 +838,10 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) } } -// Weak references - void rb_gc_impl_declare_weak_references(void *objspace_ptr, VALUE obj) { + RB_FL_SET(obj, RUBY_FL_WEAK_REFERENCE); mmtk_declare_weak_references((MMTk_ObjectReference)obj); } @@ -790,16 +852,22 @@ rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj) } // Compaction +void +rb_gc_impl_register_pinning_obj(void *objspace_ptr, VALUE obj) +{ + mmtk_register_pinning_obj((MMTk_ObjectReference)obj); +} + bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj) { - rb_bug("unimplemented"); + return rb_mmtk_call_object_closure(obj, false) != obj; } VALUE -rb_gc_impl_location(void *objspace_ptr, VALUE value) +rb_gc_impl_location(void *objspace_ptr, VALUE obj) { - rb_bug("unimplemented"); + return rb_mmtk_call_object_closure(obj, false); } // Write barriers diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index 77e82cf06e5614..f7da0f95f0214d 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -62,13 +62,16 @@ typedef struct MMTk_RubyUpcalls { void (*stop_the_world)(void); void (*resume_mutators)(void); void (*block_for_gc)(MMTk_VMMutatorThread tls); + void (*before_updating_jit_code)(void); + void (*after_updating_jit_code)(void); size_t (*number_of_mutators)(void); void (*get_mutators)(void (*visit_mutator)(MMTk_Mutator*, void*), void *data); void (*scan_gc_roots)(void); void (*scan_objspace)(void); - void (*scan_object_ruby_style)(MMTk_ObjectReference object); + void (*move_obj_during_marking)(MMTk_ObjectReference from, MMTk_ObjectReference to); + void (*update_object_references)(MMTk_ObjectReference object); void (*call_gc_mark_children)(MMTk_ObjectReference object); - void (*handle_weak_references)(MMTk_ObjectReference object); + void (*handle_weak_references)(MMTk_ObjectReference object, bool moving); void (*call_obj_free)(MMTk_ObjectReference object); size_t (*vm_live_bytes)(void); void (*update_global_tables)(int tbl_idx); @@ -125,6 +128,8 @@ void mmtk_declare_weak_references(MMTk_ObjectReference object); bool mmtk_weak_references_alive_p(MMTk_ObjectReference object); +void mmtk_register_pinning_obj(MMTk_ObjectReference obj); + void mmtk_object_reference_write_post(MMTk_Mutator *mutator, MMTk_ObjectReference object); void mmtk_register_wb_unprotected_object(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index fed4d82f4e31e6..1d7dc62a8753b4 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -299,6 +299,8 @@ pub struct RubyUpcalls { pub stop_the_world: extern "C" fn(), pub resume_mutators: extern "C" fn(), pub block_for_gc: extern "C" fn(tls: VMMutatorThread), + pub before_updating_jit_code: extern "C" fn(), + pub after_updating_jit_code: extern "C" fn(), pub number_of_mutators: extern "C" fn() -> usize, pub get_mutators: extern "C" fn( visit_mutator: extern "C" fn(*mut RubyMutator, *mut libc::c_void), @@ -306,9 +308,10 @@ pub struct RubyUpcalls { ), pub scan_gc_roots: extern "C" fn(), pub scan_objspace: extern "C" fn(), - pub scan_object_ruby_style: extern "C" fn(object: ObjectReference), + pub move_obj_during_marking: extern "C" fn(from: ObjectReference, to: ObjectReference), + pub update_object_references: extern "C" fn(object: ObjectReference), pub call_gc_mark_children: extern "C" fn(object: ObjectReference), - pub handle_weak_references: extern "C" fn(object: ObjectReference), + pub handle_weak_references: extern "C" fn(object: ObjectReference, moving: bool), pub call_obj_free: extern "C" fn(object: ObjectReference), pub vm_live_bytes: extern "C" fn() -> usize, pub update_global_tables: extern "C" fn(tbl_idx: c_int), diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 0449d3959d7c1e..ec0e5bafe2289a 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -312,6 +312,13 @@ pub extern "C" fn mmtk_weak_references_alive_p(object: ObjectReference) -> bool object.is_reachable() } +// =============== Compaction =============== + +#[no_mangle] +pub extern "C" fn mmtk_register_pinning_obj(obj: ObjectReference) { + crate::binding().pinning_registry.register(obj); +} + // =============== Write barriers =============== #[no_mangle] diff --git a/gc/mmtk/src/binding.rs b/gc/mmtk/src/binding.rs index ddbaafb0767b1f..36d4a992fda79b 100644 --- a/gc/mmtk/src/binding.rs +++ b/gc/mmtk/src/binding.rs @@ -9,6 +9,7 @@ use mmtk::MMTK; use crate::abi; use crate::abi::RubyBindingOptions; +use crate::pinning_registry::PinningRegistry; use crate::weak_proc::WeakProcessor; use crate::Ruby; @@ -54,6 +55,7 @@ pub struct RubyBinding { pub upcalls: *const abi::RubyUpcalls, pub plan_name: Mutex>, pub weak_proc: WeakProcessor, + pub pinning_registry: PinningRegistry, pub gc_thread_join_handles: Mutex>>, pub wb_unprotected_objects: Mutex>, } @@ -77,6 +79,7 @@ impl RubyBinding { upcalls, plan_name: Mutex::new(None), weak_proc: WeakProcessor::new(), + pinning_registry: PinningRegistry::new(), gc_thread_join_handles: Default::default(), wb_unprotected_objects: Default::default(), } diff --git a/gc/mmtk/src/collection.rs b/gc/mmtk/src/collection.rs index 824747b04c4051..0b1221204c1468 100644 --- a/gc/mmtk/src/collection.rs +++ b/gc/mmtk/src/collection.rs @@ -8,9 +8,12 @@ use mmtk::scheduler::*; use mmtk::util::heap::GCTriggerPolicy; use mmtk::util::{VMMutatorThread, VMThread, VMWorkerThread}; use mmtk::vm::{Collection, GCThreadContext}; +use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::thread; +static CURRENT_GC_MAY_MOVE: AtomicBool = AtomicBool::new(false); + pub struct VMCollection {} impl Collection for VMCollection { @@ -18,11 +21,21 @@ impl Collection for VMCollection { crate::CONFIGURATION.gc_enabled.load(Ordering::Relaxed) } - fn stop_all_mutators(_tls: VMWorkerThread, mut mutator_visitor: F) + fn stop_all_mutators(tls: VMWorkerThread, mut mutator_visitor: F) where F: FnMut(&'static mut mmtk::Mutator), { (upcalls().stop_the_world)(); + + if crate::mmtk().get_plan().current_gc_may_move_object() { + CURRENT_GC_MAY_MOVE.store(true, Ordering::Relaxed); + (upcalls().before_updating_jit_code)(); + } else { + CURRENT_GC_MAY_MOVE.store(false, Ordering::Relaxed); + } + + crate::binding().pinning_registry.pin_children(tls); + (upcalls().get_mutators)( Self::notify_mutator_ready::, &mut mutator_visitor as *mut F as *mut _, @@ -30,6 +43,10 @@ impl Collection for VMCollection { } fn resume_mutators(_tls: VMWorkerThread) { + if CURRENT_GC_MAY_MOVE.load(Ordering::Relaxed) { + (upcalls().after_updating_jit_code)(); + } + (upcalls().resume_mutators)(); } diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index 8647793522ed82..0da6121883747e 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -27,6 +27,7 @@ pub mod binding; pub mod collection; pub mod heap; pub mod object_model; +pub mod pinning_registry; pub mod reference_glue; pub mod scanning; pub mod utils; diff --git a/gc/mmtk/src/object_model.rs b/gc/mmtk/src/object_model.rs index 93b6063a05d506..dd27f83612cb9a 100644 --- a/gc/mmtk/src/object_model.rs +++ b/gc/mmtk/src/object_model.rs @@ -1,4 +1,6 @@ -use crate::abi::{RubyObjectAccess, OBJREF_OFFSET}; +use std::ptr::copy_nonoverlapping; + +use crate::abi::{RubyObjectAccess, MIN_OBJ_ALIGN, OBJREF_OFFSET}; use crate::{abi, Ruby}; use mmtk::util::constants::BITS_IN_BYTE; use mmtk::util::copy::{CopySemantics, GCWorkerCopyContext}; @@ -36,11 +38,39 @@ impl ObjectModel for VMObjectModel { const NEED_VO_BITS_DURING_TRACING: bool = true; fn copy( - _from: ObjectReference, - _semantics: CopySemantics, - _copy_context: &mut GCWorkerCopyContext, + from: ObjectReference, + semantics: CopySemantics, + copy_context: &mut GCWorkerCopyContext, ) -> ObjectReference { - unimplemented!("Copying GC not currently supported") + let from_acc = RubyObjectAccess::from_objref(from); + let from_start = from_acc.obj_start(); + let object_size = from_acc.object_size(); + let to_start = copy_context.alloc_copy(from, object_size, MIN_OBJ_ALIGN, 0, semantics); + debug_assert!(!to_start.is_zero()); + let to_payload = to_start.add(OBJREF_OFFSET); + unsafe { + copy_nonoverlapping::(from_start.to_ptr(), to_start.to_mut_ptr(), object_size); + } + let to_obj = unsafe { ObjectReference::from_raw_address_unchecked(to_payload) }; + copy_context.post_copy(to_obj, object_size, semantics); + trace!("Copied object from {} to {}", from, to_obj); + + (crate::binding().upcalls().move_obj_during_marking)(from, to_obj); + + #[cfg(feature = "clear_old_copy")] + { + trace!( + "Clearing old copy {} ({}-{})", + from, + from_start, + from_start + object_size + ); + // For debug purpose, we clear the old copy so that if the Ruby VM reads from the old + // copy again, it will likely result in an error. + unsafe { std::ptr::write_bytes::(from_start.to_mut_ptr(), 0, object_size) } + } + + to_obj } fn copy_to(_from: ObjectReference, _to: ObjectReference, _region: Address) -> Address { diff --git a/gc/mmtk/src/pinning_registry.rs b/gc/mmtk/src/pinning_registry.rs new file mode 100644 index 00000000000000..7f67bbeef5e4a3 --- /dev/null +++ b/gc/mmtk/src/pinning_registry.rs @@ -0,0 +1,179 @@ +use std::sync::Mutex; + +use mmtk::{ + memory_manager, + scheduler::{GCWork, GCWorker, WorkBucketStage}, + util::{ObjectReference, VMWorkerThread}, + MMTK, +}; + +use crate::{abi::GCThreadTLS, upcalls, Ruby}; + +pub struct PinningRegistry { + pinning_objs: Mutex>, + pinned_objs: Mutex>, +} + +impl PinningRegistry { + pub fn new() -> Self { + Self { + pinning_objs: Default::default(), + pinned_objs: Default::default(), + } + } + + pub fn register(&self, object: ObjectReference) { + let mut pinning_objs = self.pinning_objs.lock().unwrap(); + pinning_objs.push(object); + } + + pub fn pin_children(&self, tls: VMWorkerThread) { + if !crate::mmtk().get_plan().current_gc_may_move_object() { + log::debug!("The current GC is non-moving, skipping pinning children."); + return; + } + + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) }; + let worker = gc_tls.worker(); + + let pinning_objs = self + .pinning_objs + .try_lock() + .expect("PinningRegistry should not have races during GC."); + + let packet_size = 512; + let work_packets = pinning_objs + .chunks(packet_size) + .map(|chunk| { + Box::new(PinPinningChildren { + pinning_objs: chunk.to_vec(), + }) as _ + }) + .collect(); + + worker.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets); + } + + pub fn cleanup(&self, worker: &mut GCWorker) { + worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(RemoveDeadPinnings); + if crate::mmtk().get_plan().current_gc_may_move_object() { + let packet = { + let mut pinned_objs = self + .pinned_objs + .try_lock() + .expect("Unexpected contention on pinned_objs"); + UnpinPinnedObjects { + objs: std::mem::take(&mut pinned_objs), + } + }; + + worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(packet); + } else { + debug!("The current GC is non-moving, skipping unpinning objects."); + debug_assert_eq!( + { + let pinned_objs = self + .pinned_objs + .try_lock() + .expect("Unexpected contention on pinned_objs"); + pinned_objs.len() + }, + 0 + ); + } + } +} + +impl Default for PinningRegistry { + fn default() -> Self { + Self::new() + } +} + +struct PinPinningChildren { + pinning_objs: Vec, +} + +impl GCWork for PinPinningChildren { + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static MMTK) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + let mut pinned_objs = vec![]; + let mut newly_pinned_objs = vec![]; + + let visit_object = |_worker, target_object: ObjectReference, pin| { + log::trace!( + " -> {} {}", + if pin { "(pin)" } else { " " }, + target_object + ); + if pin { + pinned_objs.push(target_object); + } + target_object + }; + + gc_tls + .object_closure + .set_temporarily_and_run_code(visit_object, || { + for obj in self.pinning_objs.iter().cloned() { + log::trace!(" Pinning: {}", obj); + (upcalls().call_gc_mark_children)(obj); + } + }); + + for target_object in pinned_objs { + if memory_manager::pin_object(target_object) { + newly_pinned_objs.push(target_object); + } + } + + let mut pinned_objs = crate::binding() + .pinning_registry + .pinned_objs + .lock() + .unwrap(); + pinned_objs.append(&mut newly_pinned_objs); + } +} + +struct RemoveDeadPinnings; + +impl GCWork for RemoveDeadPinnings { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static MMTK) { + log::debug!("Removing dead Pinnings..."); + + let registry = &crate::binding().pinning_registry; + { + let mut pinning_objs = registry + .pinning_objs + .try_lock() + .expect("PinningRegistry should not have races during GC."); + + pinning_objs.retain_mut(|obj| { + if obj.is_live() { + let new_obj = obj.get_forwarded_object().unwrap_or(*obj); + *obj = new_obj; + true + } else { + log::trace!(" Dead Pinning removed: {}", *obj); + false + } + }); + } + } +} + +struct UnpinPinnedObjects { + objs: Vec, +} + +impl GCWork for UnpinPinnedObjects { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static MMTK) { + log::debug!("Unpinning pinned objects..."); + + for obj in self.objs.iter() { + let unpinned = memory_manager::unpin_object(*obj); + debug_assert!(unpinned); + } + } +} diff --git a/gc/mmtk/src/scanning.rs b/gc/mmtk/src/scanning.rs index af435813770a8a..eca4769e2f9d15 100644 --- a/gc/mmtk/src/scanning.rs +++ b/gc/mmtk/src/scanning.rs @@ -54,7 +54,11 @@ impl Scanning for VMScanning { gc_tls .object_closure .set_temporarily_and_run_code(visit_object, || { - (upcalls().scan_object_ruby_style)(object); + (upcalls().call_gc_mark_children)(object); + + if crate::mmtk().get_plan().current_gc_may_move_object() { + (upcalls().update_object_references)(object); + } }); } @@ -131,6 +135,7 @@ impl Scanning for VMScanning { crate::binding() .weak_proc .process_weak_stuff(worker, tracer_context); + crate::binding().pinning_registry.cleanup(worker); false } @@ -248,7 +253,11 @@ impl> GCWork for ScanWbUnprotectedRoots { for object in self.objects.iter().copied() { if object.is_reachable() { debug!("[wb_unprot_roots] Visiting WB-unprotected object (parent): {object}"); - (upcalls().scan_object_ruby_style)(object); + (upcalls().call_gc_mark_children)(object); + + if crate::mmtk().get_plan().current_gc_may_move_object() { + (upcalls().update_object_references)(object); + } } else { debug!( "[wb_unprot_roots] Skipping young WB-unprotected object (parent): {object}" diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index ce80d323495e73..3184c4f6d183f2 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -116,16 +116,49 @@ impl GCWork for ProcessObjFreeCandidates { struct ProcessWeakReferences; impl GCWork for ProcessWeakReferences { - fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + if crate::mmtk().get_plan().current_gc_may_move_object() { + let gc_tls: &mut GCThreadTLS = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + + let visit_object = |_worker, target_object: ObjectReference, _pin| { + debug_assert!( + mmtk::memory_manager::is_mmtk_object(target_object.to_raw_address()).is_some(), + "Destination is not an MMTk object" + ); + + target_object + .get_forwarded_object() + .unwrap_or(target_object) + }; + + gc_tls + .object_closure + .set_temporarily_and_run_code(visit_object, || { + self.process_weak_references(true); + }) + } else { + self.process_weak_references(false); + } + } +} + +impl ProcessWeakReferences { + fn process_weak_references(&mut self, moving_gc: bool) { let mut weak_references = crate::binding() .weak_proc .weak_references .try_lock() .expect("Mutators should not be holding the lock."); - weak_references.retain(|&object| { + weak_references.retain_mut(|object_ptr| { + let object = object_ptr.get_forwarded_object().unwrap_or(*object_ptr); + + if object != *object_ptr { + *object_ptr = object; + } + if object.is_reachable() { - (upcalls().handle_weak_references)(object); + (upcalls().handle_weak_references)(object, moving_gc); true } else { From 8afd4fade69b3a53a5e309499595f7b192f87726 Mon Sep 17 00:00:00 2001 From: Thomas Marshall Date: Mon, 29 Dec 2025 12:54:28 +0000 Subject: [PATCH 2263/2435] [ruby/prism] Add unterminated construct tests https://github.com/ruby/prism/commit/166764f794 --- test/prism/errors/unterminated_begin.txt | 4 ++++ test/prism/errors/unterminated_begin_upcase.txt | 4 ++++ test/prism/errors/unterminated_block_do_end.txt | 4 ++++ test/prism/errors/unterminated_class.txt | 4 ++++ test/prism/errors/unterminated_def.txt | 5 +++++ test/prism/errors/unterminated_end_upcase.txt | 4 ++++ test/prism/errors/unterminated_for.txt | 5 +++++ test/prism/errors/unterminated_if.txt | 5 +++++ test/prism/errors/unterminated_if_else.txt | 5 +++++ test/prism/errors/unterminated_lambda_brace.txt | 4 ++++ test/prism/errors/unterminated_module.txt | 4 ++++ test/prism/errors/unterminated_pattern_bracket.txt | 7 +++++++ test/prism/errors/unterminated_pattern_paren.txt | 7 +++++++ test/prism/errors/unterminated_until.txt | 5 +++++ 14 files changed, 67 insertions(+) create mode 100644 test/prism/errors/unterminated_begin.txt create mode 100644 test/prism/errors/unterminated_begin_upcase.txt create mode 100644 test/prism/errors/unterminated_block_do_end.txt create mode 100644 test/prism/errors/unterminated_class.txt create mode 100644 test/prism/errors/unterminated_def.txt create mode 100644 test/prism/errors/unterminated_end_upcase.txt create mode 100644 test/prism/errors/unterminated_for.txt create mode 100644 test/prism/errors/unterminated_if.txt create mode 100644 test/prism/errors/unterminated_if_else.txt create mode 100644 test/prism/errors/unterminated_lambda_brace.txt create mode 100644 test/prism/errors/unterminated_module.txt create mode 100644 test/prism/errors/unterminated_pattern_bracket.txt create mode 100644 test/prism/errors/unterminated_pattern_paren.txt create mode 100644 test/prism/errors/unterminated_until.txt diff --git a/test/prism/errors/unterminated_begin.txt b/test/prism/errors/unterminated_begin.txt new file mode 100644 index 00000000000000..6217f80a0b5e97 --- /dev/null +++ b/test/prism/errors/unterminated_begin.txt @@ -0,0 +1,4 @@ +begin + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `begin` statement + diff --git a/test/prism/errors/unterminated_begin_upcase.txt b/test/prism/errors/unterminated_begin_upcase.txt new file mode 100644 index 00000000000000..92b975bf766d61 --- /dev/null +++ b/test/prism/errors/unterminated_begin_upcase.txt @@ -0,0 +1,4 @@ +BEGIN { + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected a `}` to close the `BEGIN` statement + diff --git a/test/prism/errors/unterminated_block_do_end.txt b/test/prism/errors/unterminated_block_do_end.txt new file mode 100644 index 00000000000000..fb7ca53d6aff5f --- /dev/null +++ b/test/prism/errors/unterminated_block_do_end.txt @@ -0,0 +1,4 @@ +foo do + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected a block beginning with `do` to end with `end` + diff --git a/test/prism/errors/unterminated_class.txt b/test/prism/errors/unterminated_class.txt new file mode 100644 index 00000000000000..ea80ab8fc71a33 --- /dev/null +++ b/test/prism/errors/unterminated_class.txt @@ -0,0 +1,4 @@ +class Foo + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `class` statement + diff --git a/test/prism/errors/unterminated_def.txt b/test/prism/errors/unterminated_def.txt new file mode 100644 index 00000000000000..83ec939feaec63 --- /dev/null +++ b/test/prism/errors/unterminated_def.txt @@ -0,0 +1,5 @@ +def foo + ^ expected a delimiter to close the parameters + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `def` statement + diff --git a/test/prism/errors/unterminated_end_upcase.txt b/test/prism/errors/unterminated_end_upcase.txt new file mode 100644 index 00000000000000..42ccd22bd5d3aa --- /dev/null +++ b/test/prism/errors/unterminated_end_upcase.txt @@ -0,0 +1,4 @@ +END { + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected a `}` to close the `END` statement + diff --git a/test/prism/errors/unterminated_for.txt b/test/prism/errors/unterminated_for.txt new file mode 100644 index 00000000000000..cbd17f0b84d97b --- /dev/null +++ b/test/prism/errors/unterminated_for.txt @@ -0,0 +1,5 @@ +for x in y + ^ unexpected end-of-input; expected a 'do', newline, or ';' after the 'for' loop collection + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `for` loop + diff --git a/test/prism/errors/unterminated_if.txt b/test/prism/errors/unterminated_if.txt new file mode 100644 index 00000000000000..559a00602210f6 --- /dev/null +++ b/test/prism/errors/unterminated_if.txt @@ -0,0 +1,5 @@ +if true + ^ expected `then` or `;` or '\n' + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the conditional clause + diff --git a/test/prism/errors/unterminated_if_else.txt b/test/prism/errors/unterminated_if_else.txt new file mode 100644 index 00000000000000..35a181e844b4ac --- /dev/null +++ b/test/prism/errors/unterminated_if_else.txt @@ -0,0 +1,5 @@ +if true +else + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `else` clause + diff --git a/test/prism/errors/unterminated_lambda_brace.txt b/test/prism/errors/unterminated_lambda_brace.txt new file mode 100644 index 00000000000000..bb8c1090abdd24 --- /dev/null +++ b/test/prism/errors/unterminated_lambda_brace.txt @@ -0,0 +1,4 @@ +-> { + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected a lambda block beginning with `{` to end with `}` + diff --git a/test/prism/errors/unterminated_module.txt b/test/prism/errors/unterminated_module.txt new file mode 100644 index 00000000000000..cf207c06a72dac --- /dev/null +++ b/test/prism/errors/unterminated_module.txt @@ -0,0 +1,4 @@ +module Foo + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `module` statement + diff --git a/test/prism/errors/unterminated_pattern_bracket.txt b/test/prism/errors/unterminated_pattern_bracket.txt new file mode 100644 index 00000000000000..cc2630f8e98850 --- /dev/null +++ b/test/prism/errors/unterminated_pattern_bracket.txt @@ -0,0 +1,7 @@ +case x +in [1 + ^ expected a `]` to close the pattern expression + ^ expected a delimiter after the patterns of an `in` clause + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `case` statement + diff --git a/test/prism/errors/unterminated_pattern_paren.txt b/test/prism/errors/unterminated_pattern_paren.txt new file mode 100644 index 00000000000000..162a12854675da --- /dev/null +++ b/test/prism/errors/unterminated_pattern_paren.txt @@ -0,0 +1,7 @@ +case x +in (1 + ^ expected a `)` to close the pattern expression + ^ expected a delimiter after the patterns of an `in` clause + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `case` statement + diff --git a/test/prism/errors/unterminated_until.txt b/test/prism/errors/unterminated_until.txt new file mode 100644 index 00000000000000..b9d7eee40ff062 --- /dev/null +++ b/test/prism/errors/unterminated_until.txt @@ -0,0 +1,5 @@ +until true + ^ expected a predicate expression for the `until` statement + ^ unexpected end-of-input, assuming it is closing the parent top level context + ^ expected an `end` to close the `until` statement + From 14fbcf0e6ed37c4a0d15fd3f016778465f774f2c Mon Sep 17 00:00:00 2001 From: Thomas Marshall Date: Mon, 29 Dec 2025 12:55:18 +0000 Subject: [PATCH 2264/2435] [ruby/prism] Report missing end errors at opening token This commit adds an `expect1_opening` function that expects a token and attaches the error to the opening token location rather than the current position. This is useful for errors about missing closing tokens, where we want to point to the line with the opening token rather than the end of the file. For example: ```ruby def foo def bar def baz ^ expected an `end` to close the `def` statement ^ expected an `end` to close the `def` statement ^ expected an `end` to close the `def` statement ``` This would previously produce three identical errors at the end of the file. After this commit, they would be reported at the opening token location: ```ruby def foo ^~~ expected an `end` to close the `def` statement def bar ^~~ expected an `end` to close the `def` statement def baz ^~~ expected an `end` to close the `def` statement ``` I considered using the end of the line where the opening token is located, but in some cases that would be less useful than the opening token location itself. For example: ```ruby def foo def bar def baz ``` Here the end of the line where the opening token is located would be the same for each of the unclosed `def` nodes. https://github.com/ruby/prism/commit/2d7829f060 --- prism/prism.c | 67 ++++++++++++------- ...ginning_with_brace_and_ending_with_end.txt | 2 +- test/prism/errors/command_calls_2.txt | 2 +- test/prism/errors/command_calls_24.txt | 2 +- test/prism/errors/command_calls_25.txt | 2 +- test/prism/errors/heredoc_unterminated.txt | 2 +- test/prism/errors/infix_after_label.txt | 2 +- .../errors/label_in_interpolated_string.txt | 2 +- test/prism/errors/pattern_string_key.txt | 2 +- test/prism/errors/shadow_args_in_lambda.txt | 2 +- test/prism/errors/unterminated_begin.txt | 2 +- .../errors/unterminated_begin_upcase.txt | 2 +- test/prism/errors/unterminated_block.txt | 2 +- .../errors/unterminated_block_do_end.txt | 2 +- test/prism/errors/unterminated_class.txt | 2 +- test/prism/errors/unterminated_def.txt | 2 +- test/prism/errors/unterminated_end_upcase.txt | 2 +- test/prism/errors/unterminated_for.txt | 2 +- test/prism/errors/unterminated_if.txt | 2 +- test/prism/errors/unterminated_if_else.txt | 2 +- .../errors/unterminated_lambda_brace.txt | 2 +- test/prism/errors/unterminated_module.txt | 2 +- .../errors/unterminated_pattern_bracket.txt | 4 +- .../errors/unterminated_pattern_paren.txt | 4 +- test/prism/errors/unterminated_until.txt | 2 +- test/prism/errors/while_endless_method.txt | 2 +- 26 files changed, 69 insertions(+), 52 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 4c8ab91f0ef4e6..1a8cdf7568fe98 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -12422,6 +12422,22 @@ expect1_heredoc_term(pm_parser_t *parser, const uint8_t *ident_start, size_t ide } } +/** + * A special expect1 that attaches the error to the opening token location + * rather than the current position. This is useful for errors about missing + * closing tokens, where we want to point to the line with the opening token + * (e.g., `def`, `class`, `if`, `{`) rather than the end of the file. + */ +static void +expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t diag_id, const pm_token_t *opening) { + if (accept1(parser, type)) return; + + pm_parser_err(parser, opening->start, opening->end, diag_id); + + parser->previous.start = opening->end; + parser->previous.type = PM_TOKEN_MISSING; +} + static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, bool accepts_label, pm_diagnostic_id_t diag_id, uint16_t depth); @@ -14764,7 +14780,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) { statements = UP(parse_statements(parser, PM_CONTEXT_BLOCK_BRACES, (uint16_t) (depth + 1))); } - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_BLOCK_TERM_BRACE); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_BLOCK_TERM_BRACE, &opening); } else { if (!match1(parser, PM_TOKEN_KEYWORD_END)) { if (!match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_ENSURE)) { @@ -14779,7 +14795,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) { } } - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BLOCK_TERM_END); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BLOCK_TERM_END, &opening); } pm_constant_id_list_t locals; @@ -15204,7 +15220,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); parser_warn_indentation_mismatch(parser, opening_newline_index, &else_keyword, false, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM_ELSE); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM_ELSE, &keyword); pm_else_node_t *else_node = pm_else_node_create(parser, &else_keyword, else_statements, &parser->previous); @@ -15221,7 +15237,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl } } else { parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, if_after_else, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM, &keyword); } // Set the appropriate end location for all of the nodes in the subtree. @@ -16202,7 +16218,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures if (!accept1(parser, PM_TOKEN_BRACKET_RIGHT)) { inner = parse_pattern(parser, captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET); + expect1_opening(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET, &opening); } closing = parser->previous; @@ -16214,7 +16230,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { inner = parse_pattern(parser, captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); + expect1_opening(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN, &opening); } closing = parser->previous; @@ -16594,7 +16610,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pm_node_t *inner = parse_pattern(parser, captures, PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET); + expect1_opening(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET, &opening); pm_token_t closing = parser->previous; switch (PM_NODE_TYPE(inner)) { @@ -16672,7 +16688,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm node = parse_pattern_hash(parser, captures, first_node, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_PATTERN_TERM_BRACE); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_PATTERN_TERM_BRACE, &opening); pm_token_t closing = parser->previous; node->base.location.start = opening.start; @@ -16798,7 +16814,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm parser->pattern_matching_newlines = previous_pattern_matching_newlines; accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); + expect1_opening(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN, &lparen); return UP(pm_pinned_expression_node_create(parser, expression, &operator, &lparen, &parser->previous)); } default: { @@ -16896,7 +16912,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, p pm_node_t *body = parse_pattern(parser, captures, PM_PARSE_PATTERN_SINGLE, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN, (uint16_t) (depth + 1)); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); + expect1_opening(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN, &opening); pm_node_t *right = UP(pm_parentheses_node_create(parser, &opening, body, &parser->previous, 0)); if (!alternation) { @@ -17748,7 +17764,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_accepts_block_stack_push(parser, true); parser_lex(parser); - pm_hash_node_t *node = pm_hash_node_create(parser, &parser->previous); + pm_token_t opening = parser->previous; + pm_hash_node_t *node = pm_hash_node_create(parser, &opening); if (!match2(parser, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_EOF)) { if (current_hash_keys != NULL) { @@ -17763,7 +17780,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_accepts_block_stack_pop(parser); - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM, &opening); pm_hash_node_closing_loc_set(node, &parser->previous); return UP(node); @@ -18380,7 +18397,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CASE_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CASE_TERM, &case_keyword); if (PM_NODE_TYPE_P(node, PM_CASE_NODE)) { pm_case_node_end_keyword_loc_set((pm_case_node_t *) node, &parser->previous); @@ -18413,7 +18430,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_begin_node_t *begin_node = pm_begin_node_create(parser, &begin_keyword, begin_statements); parse_rescues(parser, opening_newline_index, &begin_keyword, begin_node, PM_RESCUES_BEGIN, (uint16_t) (depth + 1)); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BEGIN_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BEGIN_TERM, &begin_keyword); begin_node->base.location.end = parser->previous.end; pm_begin_node_end_keyword_set(begin_node, &parser->previous); @@ -18438,7 +18455,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = parser->previous; pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_PREEXE, (uint16_t) (depth + 1)); - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_BEGIN_UPCASE_TERM); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_BEGIN_UPCASE_TERM, &opening); pm_context_t context = parser->current_context->context; if ((context != PM_CONTEXT_MAIN) && (context != PM_CONTEXT_PREEXE)) { pm_parser_err_token(parser, &keyword, PM_ERR_BEGIN_UPCASE_TOPLEVEL); @@ -18568,7 +18585,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM, &class_keyword); pm_constant_id_list_t locals; pm_locals_order(parser, &parser->current_scope->locals, &locals, false); @@ -18626,7 +18643,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM, &class_keyword); if (context_def_p(parser)) { pm_parser_err_token(parser, &class_keyword, PM_ERR_CLASS_IN_METHOD); @@ -18936,7 +18953,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_accepts_block_stack_pop(parser); pm_do_loop_stack_pop(parser); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_DEF_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_DEF_TERM, &def_keyword); end_keyword = parser->previous; } @@ -19030,7 +19047,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = parser->previous; pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_POSTEXE, (uint16_t) (depth + 1)); - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_END_UPCASE_TERM); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_END_UPCASE_TERM, &opening); return UP(pm_post_execution_node_create(parser, &keyword, &opening, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_FALSE: @@ -19094,7 +19111,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } parser_warn_indentation_mismatch(parser, opening_newline_index, &for_keyword, false, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_FOR_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_FOR_TERM, &for_keyword); return UP(pm_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous)); } @@ -19245,7 +19262,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM, &module_keyword); if (context_def_p(parser)) { pm_parser_err_token(parser, &module_keyword, PM_ERR_MODULE_IN_METHOD); @@ -19311,7 +19328,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_UNTIL_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_UNTIL_TERM, &keyword); return UP(pm_until_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0)); } @@ -19345,7 +19362,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_WHILE_TERM); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_WHILE_TERM, &keyword); return UP(pm_while_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0)); } @@ -20091,7 +20108,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); - expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_LAMBDA_TERM_BRACE); + expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_LAMBDA_TERM_BRACE, &opening); } else { expect1(parser, PM_TOKEN_KEYWORD_DO, PM_ERR_LAMBDA_OPEN); opening = parser->previous; @@ -20109,7 +20126,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); } - expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END); + expect1_opening(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END, &operator); } pm_constant_id_list_t locals; diff --git a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt index 16af8200ec8930..1184b38ce840df 100644 --- a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt +++ b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt @@ -1,5 +1,5 @@ x.each { x end ^~~ unexpected 'end', expecting end-of-input ^~~ unexpected 'end', ignoring it - ^ expected a block beginning with `{` to end with `}` + ^ expected a block beginning with `{` to end with `}` diff --git a/test/prism/errors/command_calls_2.txt b/test/prism/errors/command_calls_2.txt index b0983c015bb49d..13e10f7ebfd2c7 100644 --- a/test/prism/errors/command_calls_2.txt +++ b/test/prism/errors/command_calls_2.txt @@ -1,5 +1,5 @@ {a: b c} - ^ expected a `}` to close the hash literal +^ expected a `}` to close the hash literal ^ unexpected local variable or method, expecting end-of-input ^ unexpected '}', expecting end-of-input ^ unexpected '}', ignoring it diff --git a/test/prism/errors/command_calls_24.txt b/test/prism/errors/command_calls_24.txt index 3046b36dc1256f..27a32ea3bf1235 100644 --- a/test/prism/errors/command_calls_24.txt +++ b/test/prism/errors/command_calls_24.txt @@ -1,5 +1,5 @@ ->a=b c{} ^ expected a `do` keyword or a `{` to open the lambda block ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a lambda block beginning with `do` to end with `end` +^~ expected a lambda block beginning with `do` to end with `end` diff --git a/test/prism/errors/command_calls_25.txt b/test/prism/errors/command_calls_25.txt index 5fddd90fdd09ae..cf04508f87d8a7 100644 --- a/test/prism/errors/command_calls_25.txt +++ b/test/prism/errors/command_calls_25.txt @@ -4,5 +4,5 @@ ^ unexpected ')', expecting end-of-input ^ unexpected ')', ignoring it ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a lambda block beginning with `do` to end with `end` +^~ expected a lambda block beginning with `do` to end with `end` diff --git a/test/prism/errors/heredoc_unterminated.txt b/test/prism/errors/heredoc_unterminated.txt index 3c6aeaeb81fdb5..56bd1629987ce8 100644 --- a/test/prism/errors/heredoc_unterminated.txt +++ b/test/prism/errors/heredoc_unterminated.txt @@ -3,7 +3,7 @@ a=>{< 1 } ^ unexpected '.'; expected a value in the hash literal - ^ expected a `}` to close the hash literal +^ expected a `}` to close the hash literal ^ unexpected '}', expecting end-of-input ^ unexpected '}', ignoring it diff --git a/test/prism/errors/label_in_interpolated_string.txt b/test/prism/errors/label_in_interpolated_string.txt index e8f40dd2a8aa69..29af5310a15b1a 100644 --- a/test/prism/errors/label_in_interpolated_string.txt +++ b/test/prism/errors/label_in_interpolated_string.txt @@ -2,6 +2,7 @@ case in el""Q ^~~~ expected a predicate for a case matching statement ^ expected a delimiter after the patterns of an `in` clause ^ unexpected constant, expecting end-of-input +^~~~ expected an `end` to close the `case` statement !"""#{in el"":Q ^~ unexpected 'in', assuming it is closing the parent 'in' clause ^ expected a `}` to close the embedded expression @@ -10,5 +11,4 @@ case in el""Q ^ cannot parse the string part ^~~~~~~~~~~ unexpected label ^~~~~~~~~~~ expected a string for concatenation - ^ expected an `end` to close the `case` statement diff --git a/test/prism/errors/pattern_string_key.txt b/test/prism/errors/pattern_string_key.txt index 9f28feddb96dea..41bc1fa57b95f4 100644 --- a/test/prism/errors/pattern_string_key.txt +++ b/test/prism/errors/pattern_string_key.txt @@ -1,8 +1,8 @@ case:a +^~~~ expected an `end` to close the `case` statement in b:"","#{}" ^~~~~ expected a label after the `,` in the hash pattern ^ expected a pattern expression after the key ^ expected a delimiter after the patterns of an `in` clause ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `case` statement diff --git a/test/prism/errors/shadow_args_in_lambda.txt b/test/prism/errors/shadow_args_in_lambda.txt index 2399a0ebd5443c..7fc78d7d8f5102 100644 --- a/test/prism/errors/shadow_args_in_lambda.txt +++ b/test/prism/errors/shadow_args_in_lambda.txt @@ -1,5 +1,5 @@ ->a;b{} ^ expected a `do` keyword or a `{` to open the lambda block ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a lambda block beginning with `do` to end with `end` +^~ expected a lambda block beginning with `do` to end with `end` diff --git a/test/prism/errors/unterminated_begin.txt b/test/prism/errors/unterminated_begin.txt index 6217f80a0b5e97..2733f830c9b0fe 100644 --- a/test/prism/errors/unterminated_begin.txt +++ b/test/prism/errors/unterminated_begin.txt @@ -1,4 +1,4 @@ begin ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `begin` statement +^~~~~ expected an `end` to close the `begin` statement diff --git a/test/prism/errors/unterminated_begin_upcase.txt b/test/prism/errors/unterminated_begin_upcase.txt index 92b975bf766d61..5512f2089e9cc9 100644 --- a/test/prism/errors/unterminated_begin_upcase.txt +++ b/test/prism/errors/unterminated_begin_upcase.txt @@ -1,4 +1,4 @@ BEGIN { ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a `}` to close the `BEGIN` statement + ^ expected a `}` to close the `BEGIN` statement diff --git a/test/prism/errors/unterminated_block.txt b/test/prism/errors/unterminated_block.txt index 8cc772db162206..db6a4aa56c05f4 100644 --- a/test/prism/errors/unterminated_block.txt +++ b/test/prism/errors/unterminated_block.txt @@ -1,4 +1,4 @@ foo { ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a block beginning with `{` to end with `}` + ^ expected a block beginning with `{` to end with `}` diff --git a/test/prism/errors/unterminated_block_do_end.txt b/test/prism/errors/unterminated_block_do_end.txt index fb7ca53d6aff5f..0b7c64965fd162 100644 --- a/test/prism/errors/unterminated_block_do_end.txt +++ b/test/prism/errors/unterminated_block_do_end.txt @@ -1,4 +1,4 @@ foo do ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a block beginning with `do` to end with `end` + ^~ expected a block beginning with `do` to end with `end` diff --git a/test/prism/errors/unterminated_class.txt b/test/prism/errors/unterminated_class.txt index ea80ab8fc71a33..f47a3aa7dfe918 100644 --- a/test/prism/errors/unterminated_class.txt +++ b/test/prism/errors/unterminated_class.txt @@ -1,4 +1,4 @@ class Foo ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `class` statement +^~~~~ expected an `end` to close the `class` statement diff --git a/test/prism/errors/unterminated_def.txt b/test/prism/errors/unterminated_def.txt index 83ec939feaec63..a6212e3a21cd55 100644 --- a/test/prism/errors/unterminated_def.txt +++ b/test/prism/errors/unterminated_def.txt @@ -1,5 +1,5 @@ def foo ^ expected a delimiter to close the parameters ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `def` statement +^~~ expected an `end` to close the `def` statement diff --git a/test/prism/errors/unterminated_end_upcase.txt b/test/prism/errors/unterminated_end_upcase.txt index 42ccd22bd5d3aa..ef01caa0ca6068 100644 --- a/test/prism/errors/unterminated_end_upcase.txt +++ b/test/prism/errors/unterminated_end_upcase.txt @@ -1,4 +1,4 @@ END { ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a `}` to close the `END` statement + ^ expected a `}` to close the `END` statement diff --git a/test/prism/errors/unterminated_for.txt b/test/prism/errors/unterminated_for.txt index cbd17f0b84d97b..75978a7cae8e53 100644 --- a/test/prism/errors/unterminated_for.txt +++ b/test/prism/errors/unterminated_for.txt @@ -1,5 +1,5 @@ for x in y ^ unexpected end-of-input; expected a 'do', newline, or ';' after the 'for' loop collection ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `for` loop +^~~ expected an `end` to close the `for` loop diff --git a/test/prism/errors/unterminated_if.txt b/test/prism/errors/unterminated_if.txt index 559a00602210f6..1697931773e1fa 100644 --- a/test/prism/errors/unterminated_if.txt +++ b/test/prism/errors/unterminated_if.txt @@ -1,5 +1,5 @@ if true ^ expected `then` or `;` or '\n' ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the conditional clause +^~ expected an `end` to close the conditional clause diff --git a/test/prism/errors/unterminated_if_else.txt b/test/prism/errors/unterminated_if_else.txt index 35a181e844b4ac..db7828cce8565e 100644 --- a/test/prism/errors/unterminated_if_else.txt +++ b/test/prism/errors/unterminated_if_else.txt @@ -1,5 +1,5 @@ if true +^~ expected an `end` to close the `else` clause else ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `else` clause diff --git a/test/prism/errors/unterminated_lambda_brace.txt b/test/prism/errors/unterminated_lambda_brace.txt index bb8c1090abdd24..75474c7534f59e 100644 --- a/test/prism/errors/unterminated_lambda_brace.txt +++ b/test/prism/errors/unterminated_lambda_brace.txt @@ -1,4 +1,4 @@ -> { ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected a lambda block beginning with `{` to end with `}` + ^ expected a lambda block beginning with `{` to end with `}` diff --git a/test/prism/errors/unterminated_module.txt b/test/prism/errors/unterminated_module.txt index cf207c06a72dac..4c50ba5f63ccab 100644 --- a/test/prism/errors/unterminated_module.txt +++ b/test/prism/errors/unterminated_module.txt @@ -1,4 +1,4 @@ module Foo ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `module` statement +^~~~~~ expected an `end` to close the `module` statement diff --git a/test/prism/errors/unterminated_pattern_bracket.txt b/test/prism/errors/unterminated_pattern_bracket.txt index cc2630f8e98850..4f35cd84af66f4 100644 --- a/test/prism/errors/unterminated_pattern_bracket.txt +++ b/test/prism/errors/unterminated_pattern_bracket.txt @@ -1,7 +1,7 @@ case x +^~~~ expected an `end` to close the `case` statement in [1 - ^ expected a `]` to close the pattern expression + ^ expected a `]` to close the pattern expression ^ expected a delimiter after the patterns of an `in` clause ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `case` statement diff --git a/test/prism/errors/unterminated_pattern_paren.txt b/test/prism/errors/unterminated_pattern_paren.txt index 162a12854675da..426d614e61719f 100644 --- a/test/prism/errors/unterminated_pattern_paren.txt +++ b/test/prism/errors/unterminated_pattern_paren.txt @@ -1,7 +1,7 @@ case x +^~~~ expected an `end` to close the `case` statement in (1 - ^ expected a `)` to close the pattern expression + ^ expected a `)` to close the pattern expression ^ expected a delimiter after the patterns of an `in` clause ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `case` statement diff --git a/test/prism/errors/unterminated_until.txt b/test/prism/errors/unterminated_until.txt index b9d7eee40ff062..42a0545200109b 100644 --- a/test/prism/errors/unterminated_until.txt +++ b/test/prism/errors/unterminated_until.txt @@ -1,5 +1,5 @@ until true ^ expected a predicate expression for the `until` statement ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `until` statement +^~~~~ expected an `end` to close the `until` statement diff --git a/test/prism/errors/while_endless_method.txt b/test/prism/errors/while_endless_method.txt index 6f062d89d0f850..cdd7ba9abab3aa 100644 --- a/test/prism/errors/while_endless_method.txt +++ b/test/prism/errors/while_endless_method.txt @@ -1,5 +1,5 @@ while def f = g do end ^ expected a predicate expression for the `while` statement ^ unexpected end-of-input, assuming it is closing the parent top level context - ^ expected an `end` to close the `while` statement +^~~~~ expected an `end` to close the `while` statement From 65634d8df57ea1636efffb5f040fa31c156d307f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:41:09 +0100 Subject: [PATCH 2265/2435] [ruby/prism] Optimize ruby visitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `compact_child_nodes` allocates an array. We can skip that step by simply yielding the nodes. Benchmark for visiting the rails codebase: ```rb require "prism" require "benchmark/ips" files = Dir.glob("../rails/**/*.rb") results = files.map { Prism.parse_file(it) } visitor = Prism::Visitor.new Benchmark.ips do |x| x.config(warmup: 3, time: 10) x.report do results.each do visitor.visit(it.value) end end end RubyVM::YJIT.enable Benchmark.ips do |x| x.config(warmup: 3, time: 10) x.report do results.each do visitor.visit(it.value) end end end ``` Before: ``` ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +PRISM [x86_64-linux] Warming up -------------------------------------- 1.000 i/100ms Calculating ------------------------------------- 2.691 (± 0.0%) i/s (371.55 ms/i) - 27.000 in 10.089422s ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +YJIT +PRISM [x86_64-linux] Warming up -------------------------------------- 1.000 i/100ms Calculating ------------------------------------- 7.278 (±13.7%) i/s (137.39 ms/i) - 70.000 in 10.071568s ``` After: ``` ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +PRISM [x86_64-linux] Warming up -------------------------------------- 1.000 i/100ms Calculating ------------------------------------- 3.429 (± 0.0%) i/s (291.65 ms/i) - 35.000 in 10.208580s ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +YJIT +PRISM [x86_64-linux] Warming up -------------------------------------- 1.000 i/100ms Calculating ------------------------------------- 16.815 (± 0.0%) i/s (59.47 ms/i) - 169.000 in 10.054668s ``` ~21% faster on the interpreter, ~56% with YJIT https://github.com/ruby/prism/commit/bf631750cf --- lib/prism/translation/ruby_parser.rb | 2 +- prism/templates/lib/prism/compiler.rb.erb | 4 ++-- prism/templates/lib/prism/node.rb.erb | 25 ++++++++++++++++++++++- prism/templates/lib/prism/visitor.rb.erb | 4 ++-- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 1fb0e278462eeb..c026c4ad9c57af 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -1422,7 +1422,7 @@ def visit_or_node(node) # ``` def visit_parameters_node(node) children = - node.compact_child_nodes.map do |element| + node.each_child_node.map do |element| if element.is_a?(MultiTargetNode) visit_destructured_parameter(element) else diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 9102025c20bdbe..66dbe666b90934 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -29,14 +29,14 @@ module Prism # Visit the child nodes of the given node. def visit_child_nodes(node) - node.compact_child_nodes.map { |node| node.accept(self) } + node.each_child_node.map { |node| node.accept(self) } end <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Compile a <%= node.name %> node def visit_<%= node.human %>(node) - node.compact_child_nodes.map { |node| node.accept(self) } + node.each_child_node.map { |node| node.accept(self) } end <%- end -%> end diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index ceee2b0ffec820..c97c029d3bf410 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -187,7 +187,7 @@ module Prism while (node = queue.shift) result << node - node.compact_child_nodes.each do |child_node| + node.each_child_node do |child_node| child_location = child_node.location start_line = child_location.start_line @@ -259,6 +259,13 @@ module Prism alias deconstruct child_nodes + # With a block given, yields each child node. Without a block, returns + # an enumerator that contains each child node. Excludes any `nil`s in + # the place of optional nodes that were not present. + def each_child_node + raise NoMethodError, "undefined method `each_child_node' for #{inspect}" + end + # Returns an array of child nodes, excluding any `nil`s in the place of # optional nodes that were not present. def compact_child_nodes @@ -335,6 +342,22 @@ module Prism }.compact.join(", ") %>] end + # def each_child_node: () { (Prism::node) -> void } -> void | () -> Enumerator[Prism::node] + def each_child_node + return to_enum(:each_child_node) unless block_given? + + <%- node.fields.each do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + yield <%= field.name %> + <%- when Prism::Template::OptionalNodeField -%> + yield <%= field.name %> if <%= field.name %> + <%- when Prism::Template::NodeListField -%> + <%= field.name %>.each {|node| yield node } + <%- end -%> + <%- end -%> + end + # def compact_child_nodes: () -> Array[Node] def compact_child_nodes <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index b1a03c3f1ac9e3..76f907724fd5da 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -20,7 +20,7 @@ module Prism # Visits the child nodes of `node` by calling `accept` on each one. def visit_child_nodes(node) # @type self: _Visitor - node.compact_child_nodes.each { |node| node.accept(self) } + node.each_child_node { |node| node.accept(self) } end end @@ -48,7 +48,7 @@ module Prism <%= "\n" if index != 0 -%> # Visit a <%= node.name %> node def visit_<%= node.human %>(node) - node.compact_child_nodes.each { |node| node.accept(self) } + node.each_child_node { |node| node.accept(self) } end <%- end -%> end From d5af8d785888f3af5efb844be5948df71d777b22 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 30 Dec 2025 14:46:19 +0900 Subject: [PATCH 2266/2435] Box: allocate classes as boxable when it happens in the root box Without this change, classes (including iclass) are allocated as un-boxable classes after initializing user boxes (after starting script evaluation). Under this situation, iclasses are created as un-boxabled class when core modules are included by a class in the root box, then it causes problems because it's in the root box but it can't have multiple classexts. This change makes it possible to allocate boxable classes even after initializing user boxes. Classes create in the root box will be boxable, and those can have 2 or more classexts. --- class.c | 4 +++- test/ruby/test_box.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/class.c b/class.c index 840bdeb0c2f9e6..8c773125e19b00 100644 --- a/class.c +++ b/class.c @@ -491,6 +491,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *bo while (subclass_entry) { if (subclass_entry->klass && RB_TYPE_P(subclass_entry->klass, T_ICLASS)) { iclass = subclass_entry->klass; + VM_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); if (RBASIC_CLASS(iclass) == klass) { // Is the subclass an ICLASS including this module into another class // If so we need to re-associate it under our box with the new ext @@ -819,7 +820,8 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) static VALUE class_alloc(enum ruby_value_type type, VALUE klass) { - return class_alloc0(type, klass, false); + bool boxable = BOX_ROOT_P(rb_current_box()); + return class_alloc0(type, klass, boxable); } static VALUE diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 28c9dd02b03d09..763f6148cb5483 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -855,4 +855,20 @@ def test_loaded_extension_deleted_in_user_box assert_empty(Dir.children(tmpdir)) end end + + def test_root_box_iclasses_should_be_boxable + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + Ruby::Box.root.eval("class IMath; include Math; end") # (*) + module Math + def foo = :foo + end + # This test crashes here if iclasses (created at the line (*) is not boxable) + class IMath2; include Math; end + assert_equal :foo, IMath2.new.foo + assert_raise NoMethodError do + Ruby::Box.root.eval("IMath.new.foo") + end + end; + end end From 3f616d5701592360cf07dae885aa706596ad07fd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 30 Dec 2025 18:33:03 +0900 Subject: [PATCH 2267/2435] Update ruby/setup-ruby action to v1.276.0 --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index a2958fc00971dc..042748389ea87b 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -72,7 +72,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 1351e78b98abef..4fc54902315d31 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v6.0.1 - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 9ad8978c3e6e51..ee4f8c9a2a78a0 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 218bef28186e2d..7d5b5e79efe4c4 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -40,7 +40,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 7851005e5133e1..fe01b3dcbefbbb 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -61,7 +61,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index a1035ef451dd82..c06d7b8fa93f74 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -58,7 +58,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 8ccb954d84548d..88b69e888e7a80 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -47,7 +47,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 3947001ccdf71a..8e44b96908738f 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index bf34baaced0fd2..6489454c57c74b 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -69,7 +69,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 9b9ea1841029d4..6022da8c3b0585 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -98,7 +98,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 7f3e60349642e0..e0bd6893a6f7e7 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 7dce69cda4ea78..bd90a57c2a0d87 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -128,7 +128,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 29b7aaad592c7e..13b1add5b09568 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -111,7 +111,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ab177d40ee5483edb974554986f56b33477e21d0 # v1.265.0 + - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 with: ruby-version: '3.1' bundler: none From 27d6c966583c65c9ffd02f931d9c4efe8d7232e0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 30 Dec 2025 17:58:44 +0900 Subject: [PATCH 2268/2435] Add 4.0 to the spec_guards workflow --- .github/workflows/spec_guards.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 88b69e888e7a80..971547351de67e 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -42,6 +42,7 @@ jobs: - ruby-3.2 - ruby-3.3 - ruby-3.4 + - ruby-4.0 fail-fast: false steps: From 966dbba8db970f13065a35d893662a981d0abae5 Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Tue, 30 Dec 2025 22:01:54 +0900 Subject: [PATCH 2269/2435] Box: skip checking the current box is the root box Because checking the current box is not a cheap process. --- class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class.c b/class.c index 8c773125e19b00..9c1bd86dc3e405 100644 --- a/class.c +++ b/class.c @@ -820,7 +820,7 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable) static VALUE class_alloc(enum ruby_value_type type, VALUE klass) { - bool boxable = BOX_ROOT_P(rb_current_box()); + bool boxable = rb_box_available() && BOX_ROOT_P(rb_current_box()); return class_alloc0(type, klass, boxable); } From 19e539c9ee1701b34189fa0c1feb942adeb0e326 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 30 Dec 2025 23:00:18 +0900 Subject: [PATCH 2270/2435] [Bug #21814] Fix negative bignum modulo If modulo is zero, do not apply bias even if the divisor is zero. `BIGNUM_POSITIVE_P` is true even on bignum zero. --- bignum.c | 9 +++++++-- test/ruby/test_numeric.rb | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bignum.c b/bignum.c index ed53d75149085d..ee2fa1ed30a2a6 100644 --- a/bignum.c +++ b/bignum.c @@ -7070,7 +7070,7 @@ int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg) zn = mn; z = bignew(zn, 1); bary_powm_gmp(BDIGITS(z), zn, BDIGITS(x), xn, BDIGITS(y), yn, BDIGITS(m), mn); - if (nega_flg & BIGNUM_POSITIVE_P(z)) { + if (nega_flg && BIGNUM_POSITIVE_P(z) && !BIGZEROP(z)) { z = rb_big_minus(z, m); } RB_GC_GUARD(x); @@ -7098,7 +7098,7 @@ int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg) x = rb_int_modulo(x, m); } - if (nega_flg && rb_int_positive_p(tmp)) { + if (nega_flg && rb_int_positive_p(tmp) && !rb_int_zero_p(tmp)) { tmp = rb_int_minus(tmp, m); } return tmp; @@ -7210,6 +7210,11 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) rb_raise(rb_eTypeError, "Integer#pow() 2nd argument not allowed unless all arguments are integers"); } + if (rb_int_zero_p(a) && !rb_int_zero_p(b)) { + /* shortcut; 0**x => 0 except for x == 0 */ + return INT2FIX(0); + } + if (rb_int_negative_p(m)) { m = rb_int_uminus(m); nega_flg = 1; diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 3bf93ef20dc0bd..35496ac875815a 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -489,6 +489,10 @@ def test_pow assert_equal(0, 0.pow(3, 1)) assert_equal(0, 2.pow(3, 1)) assert_equal(0, -2.pow(3, 1)) + + min, max = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX") + assert_equal(0, 0.pow(2, min)) + assert_equal(0, Integer.sqrt(max+1).pow(2, min)) end end From d82fc3360d7cfa7e1e1a4dddb668b4c38808538a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 09:48:41 +0100 Subject: [PATCH 2271/2435] Reapply "[Feature #6012] Extend `source_location` for end position * This reverts commit 065c48cdf11a1c4cece84db44ed8624d294f8fd5. * This functionality is very valuable and has already taken 14 years to agree on the API. * Let's just document it's byte columns (in the next commit). * See https://bugs.ruby-lang.org/issues/21783#note-9 --- NEWS.md | 10 ++++ proc.c | 16 ++++-- spec/ruby/core/method/source_location_spec.rb | 16 ++++-- spec/ruby/core/proc/source_location_spec.rb | 51 ++++++++++++------- .../unboundmethod/source_location_spec.rb | 18 ++++--- test/ruby/test_lambda.rb | 12 ++--- test/ruby/test_proc.rb | 18 ++++--- 7 files changed, 93 insertions(+), 48 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8ac808c2e7b6a4..e37e4ebf5b435a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,15 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Method + + * `Method#source_location`, `Proc#source_location`, and + `UnboundMethod#source_location` now return extended location + information with 5 elements: `[path, start_line, start_column, + end_line, end_column]`. The previous 2-element format `[path, + line]` can still be obtained by calling `.take(2)` on the result. + [[Feature #6012]] + * Set * A deprecated behavior, `Set#to_set`, `Range#to_set`, and @@ -66,4 +75,5 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT +[Feature #6012]: https://bugs.ruby-lang.org/issues/6012 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 diff --git a/proc.c b/proc.c index a0fc3a7e0837f2..715f346853b8aa 100644 --- a/proc.c +++ b/proc.c @@ -1513,14 +1513,20 @@ proc_eq(VALUE self, VALUE other) static VALUE iseq_location(const rb_iseq_t *iseq) { - VALUE loc[2]; + VALUE loc[5]; + int i = 0; if (!iseq) return Qnil; rb_iseq_check(iseq); - loc[0] = rb_iseq_path(iseq); - loc[1] = RB_INT2NUM(ISEQ_BODY(iseq)->location.first_lineno); - - return rb_ary_new4(2, loc); + loc[i++] = rb_iseq_path(iseq); + const rb_code_location_t *cl = &ISEQ_BODY(iseq)->location.code_location; + loc[i++] = RB_INT2NUM(cl->beg_pos.lineno); + loc[i++] = RB_INT2NUM(cl->beg_pos.column); + loc[i++] = RB_INT2NUM(cl->end_pos.lineno); + loc[i++] = RB_INT2NUM(cl->end_pos.column); + RUBY_ASSERT_ALWAYS(i == numberof(loc)); + + return rb_ary_new_from_values(i, loc); } VALUE diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index c5b296f6e2a7b0..1b175ebabac044 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -11,23 +11,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - MethodSpecs::SourceLocation.method(:redefined).source_location.last.should == 13 + MethodSpecs::SourceLocation.method(:redefined).source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - MethodSpecs::SourceLocation.new.method(:aka).source_location.last.should == 17 + MethodSpecs::SourceLocation.new.method(:aka).source_location[1].should == 17 end it "works for methods defined with a block" do @@ -108,7 +108,13 @@ def f c = Class.new do eval('def self.m; end', nil, "foo", 100) end - c.method(:m).source_location.should == ["foo", 100] + location = c.method(:m).source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 0, 100, 15] + end end describe "for a Method generated by respond_to_missing?" do diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index a8b99287d5c380..69b4e2fd8273b6 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -17,57 +17,64 @@ end it "sets the first value to the path of the file in which the proc was defined" do - file = @proc.source_location.first + file = @proc.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @proc_new.source_location.first + file = @proc_new.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @lambda.source_location.first + file = @lambda.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/source_location.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the proc was defined" do - line = @proc.source_location.last + it "sets the second value to an Integer representing the line on which the proc was defined" do + line = @proc.source_location[1] line.should be_an_instance_of(Integer) line.should == 4 - line = @proc_new.source_location.last + line = @proc_new.source_location[1] line.should be_an_instance_of(Integer) line.should == 12 - line = @lambda.source_location.last + line = @lambda.source_location[1] line.should be_an_instance_of(Integer) line.should == 8 - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 15 end it "works even if the proc was created on the same line" do - proc { true }.source_location.should == [__FILE__, __LINE__] - Proc.new { true }.source_location.should == [__FILE__, __LINE__] - -> { true }.source_location.should == [__FILE__, __LINE__] + ruby_version_is(""..."4.0") do + proc { true }.source_location.should == [__FILE__, __LINE__] + Proc.new { true }.source_location.should == [__FILE__, __LINE__] + -> { true }.source_location.should == [__FILE__, __LINE__] + end + ruby_version_is("4.0") do + proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] + Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] + -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] + end end it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do - ProcSpecs::SourceLocation.my_multiline_proc.source_location.last.should == 20 - ProcSpecs::SourceLocation.my_multiline_proc_new.source_location.last.should == 34 - ProcSpecs::SourceLocation.my_multiline_lambda.source_location.last.should == 27 + ProcSpecs::SourceLocation.my_multiline_proc.source_location[1].should == 20 + ProcSpecs::SourceLocation.my_multiline_proc_new.source_location[1].should == 34 + ProcSpecs::SourceLocation.my_multiline_lambda.source_location[1].should == 27 end it "returns the location of the proc's body; not necessarily the proc itself" do - ProcSpecs::SourceLocation.my_detached_proc.source_location.last.should == 41 - ProcSpecs::SourceLocation.my_detached_proc_new.source_location.last.should == 51 - ProcSpecs::SourceLocation.my_detached_lambda.source_location.last.should == 46 + ProcSpecs::SourceLocation.my_detached_proc.source_location[1].should == 41 + ProcSpecs::SourceLocation.my_detached_proc_new.source_location[1].should == 51 + ProcSpecs::SourceLocation.my_detached_lambda.source_location[1].should == 46 end it "returns the same value for a proc-ified method as the method reports" do @@ -86,6 +93,12 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) - proc.source_location.should == ["foo", 100] + location = proc.source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 2, 100, 5] + end end end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 5c2f14362c40b4..85078ff34e8cd5 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -7,23 +7,23 @@ end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) file.should == File.realpath('fixtures/classes.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + it "sets the second value to an Integer representing the line on which the method was defined" do + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13 + UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17 + UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location[1].should == 17 end it "works for define_method methods" do @@ -54,6 +54,12 @@ c = Class.new do eval('def m; end', nil, "foo", 100) end - c.instance_method(:m).source_location.should == ["foo", 100] + location = c.instance_method(:m).source_location + ruby_version_is(""..."4.0") do + location.should == ["foo", 100] + end + ruby_version_is("4.0") do + location.should == ["foo", 100, 0, 100, 10] + end end end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 7738034240497d..3cbb54306c8afa 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -276,27 +276,27 @@ def test_break end def test_do_lambda_source_location - exp_lineno = __LINE__ + 3 + exp = [__LINE__ + 1, 12, __LINE__ + 5, 7] lmd = ->(x, y, z) do # end - file, lineno = lmd.source_location + file, *loc = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + assert_equal(exp, loc) end def test_brace_lambda_source_location - exp_lineno = __LINE__ + 3 + exp = [__LINE__ + 1, 12, __LINE__ + 5, 5] lmd = ->(x, y, z) { # } - file, lineno = lmd.source_location + file, *loc = lmd.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + assert_equal(exp, loc) end def test_not_orphan_return diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index f74342322f5b78..959ea87f25d667 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -513,7 +513,7 @@ def test_binding_source_location file, lineno = method(:source_location_test).to_proc.binding.source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + assert_equal(@@line_of_source_location_test[0], lineno, 'Bug #2427') end def test_binding_error_unless_ruby_frame @@ -1499,15 +1499,19 @@ def test_to_s assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name) end - @@line_of_source_location_test = __LINE__ + 1 + @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5] def source_location_test a=1, b=2 end def test_source_location - file, lineno = method(:source_location_test).source_location + file, *loc = method(:source_location_test).source_location assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') + + file, *loc = self.class.instance_method(:source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') end @@line_of_attr_reader_source_location_test = __LINE__ + 3 @@ -1540,13 +1544,13 @@ def block_source_location_test(*args, &block) end def test_block_source_location - exp_lineno = __LINE__ + 3 - file, lineno = block_source_location_test(1, + exp_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49] + file, *loc = block_source_location_test(1, 2, 3) do end assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) - assert_equal(exp_lineno, lineno) + assert_equal(exp_loc, loc) end def test_splat_without_respond_to From cd66d15858a06406d1de854f3e9690d3557a9864 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 16 Dec 2025 09:52:31 +0100 Subject: [PATCH 2272/2435] [Bug #21783] Fix documentation of {Method,UnboundMethod,Proc}#source_location --- proc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proc.c b/proc.c index 715f346853b8aa..4fa48196caccc2 100644 --- a/proc.c +++ b/proc.c @@ -1543,9 +1543,9 @@ rb_iseq_location(const rb_iseq_t *iseq) * The returned Array contains: * (1) the Ruby source filename * (2) the line number where the definition starts - * (3) the column number where the definition starts + * (3) the position where the definition starts, in number of bytes from the start of the line * (4) the line number where the definition ends - * (5) the column number where the definitions ends + * (5) the position where the definitions ends, in number of bytes from the start of the line * * This method will return +nil+ if the Proc was not defined in Ruby (i.e. native). */ @@ -3203,9 +3203,9 @@ rb_method_entry_location(const rb_method_entry_t *me) * The returned Array contains: * (1) the Ruby source filename * (2) the line number where the definition starts - * (3) the column number where the definition starts + * (3) the position where the definition starts, in number of bytes from the start of the line * (4) the line number where the definition ends - * (5) the column number where the definitions ends + * (5) the position where the definitions ends, in number of bytes from the start of the line * * This method will return +nil+ if the method was not defined in Ruby (i.e. native). */ From c970d2941d56a862bb9bb3b808cb588c2982f436 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 15 Dec 2025 21:46:17 +0100 Subject: [PATCH 2273/2435] [Bug #21784] Fix the Proc#source_location start_column for stabby lambdas * Consistent with plain `blocks` and `for` blocks and methods where the source_location covers their entire definition. * Matches the documentation which mentions "where the definition starts/ends". * Partially reverts d357d50f0a74409446f4cccec78593373f5adf2f which was a workaround to be compatible with parse.y. --- parse.y | 2 +- prism_compile.c | 7 ------- spec/ruby/core/proc/source_location_spec.rb | 4 ++-- test/ruby/test_ast.rb | 2 +- test/ruby/test_lambda.rb | 4 ++-- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/parse.y b/parse.y index 067f45e6d6044a..03dd1c6f926067 100644 --- a/parse.y +++ b/parse.y @@ -5149,7 +5149,7 @@ lambda : tLAMBDA[lpar] CMDARG_POP(); $args = args_with_numbered(p, $args, max_numparam, it_id); { - YYLTYPE loc = code_loc_gen(&@args, &@body); + YYLTYPE loc = code_loc_gen(&@lpar, &@body); $$ = NEW_LAMBDA($args, $body->node, &loc, &@lpar, &$body->opening_loc, &$body->closing_loc); nd_set_line(RNODE_LAMBDA($$)->nd_body, @body.end_pos.lineno); nd_set_line($$, @args.end_pos.lineno); diff --git a/prism_compile.c b/prism_compile.c index 77b940ce6c6008..36a0b6ce50a77b 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3284,13 +3284,6 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ scope->parameters = cast->parameters; scope->body = cast->body; scope->locals = cast->locals; - - if (cast->parameters != NULL) { - scope->base.location.start = cast->parameters->location.start; - } - else { - scope->base.location.start = cast->operator_loc.end; - } break; } case PM_MODULE_NODE: { diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 69b4e2fd8273b6..18f1ca274c5488 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -61,7 +61,7 @@ ruby_version_is("4.0") do proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] - -> { true }.source_location.should == [__FILE__, __LINE__, 8, __LINE__, 17] + -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] end end @@ -98,7 +98,7 @@ location.should == ["foo", 100] end ruby_version_is("4.0") do - location.should == ["foo", 100, 2, 100, 5] + location.should == ["foo", 100, 0, 100, 5] end end end diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index c7a946dec868bb..22ccbfb604d0e4 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1230,7 +1230,7 @@ def test_error_tolerant_end_is_short_for_do_LAMBDA args: nil body: (LAMBDA@1:0-2:3 - (SCOPE@1:2-2:3 + (SCOPE@1:0-2:3 tbl: [] args: (ARGS@1:2-1:2 diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 3cbb54306c8afa..6a0dd9915e32a6 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -276,7 +276,7 @@ def test_break end def test_do_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 7] + exp = [__LINE__ + 1, 10, __LINE__ + 5, 7] lmd = ->(x, y, z) do @@ -288,7 +288,7 @@ def test_do_lambda_source_location end def test_brace_lambda_source_location - exp = [__LINE__ + 1, 12, __LINE__ + 5, 5] + exp = [__LINE__ + 1, 10, __LINE__ + 5, 5] lmd = ->(x, y, z) { From a7fec4d6619384b0b0277e751a56f7b91e5d8d5b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 30 Dec 2025 12:47:01 +0100 Subject: [PATCH 2274/2435] Update version guards in ruby/spec --- spec/ruby/core/method/source_location_spec.rb | 4 ++-- spec/ruby/core/proc/source_location_spec.rb | 8 ++++---- spec/ruby/core/unboundmethod/source_location_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index 1b175ebabac044..87413a2ab6780d 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -109,10 +109,10 @@ def f eval('def self.m; end', nil, "foo", 100) end location = c.method(:m).source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 15] end end diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index 18f1ca274c5488..fd33f21a26e8b3 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -53,12 +53,12 @@ end it "works even if the proc was created on the same line" do - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do proc { true }.source_location.should == [__FILE__, __LINE__] Proc.new { true }.source_location.should == [__FILE__, __LINE__] -> { true }.source_location.should == [__FILE__, __LINE__] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] @@ -94,10 +94,10 @@ it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) location = proc.source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 5] end end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index 85078ff34e8cd5..9cc219801738d7 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -55,10 +55,10 @@ eval('def m; end', nil, "foo", 100) end location = c.instance_method(:m).source_location - ruby_version_is(""..."4.0") do + ruby_version_is(""..."4.1") do location.should == ["foo", 100] end - ruby_version_is("4.0") do + ruby_version_is("4.1") do location.should == ["foo", 100, 0, 100, 10] end end From c05e10605e46106397fb4af4ea0f322c4d6d68ea Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 30 Dec 2025 15:30:31 +0100 Subject: [PATCH 2275/2435] Exclude rbs tests which need updates for {Method,UnboundMethod,Proc}#source_location * See https://github.com/ruby/ruby/pull/15580 --- tool/rbs_skip_tests | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index d5139705b3c4d3..e03eecbfedcc4d 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -45,3 +45,6 @@ test_linear_time?(RegexpSingletonTest) test_new(RegexpSingletonTest) ## Failed tests caused by unreleased version of Ruby +test_source_location(MethodInstanceTest) +test_source_location(ProcInstanceTest) +test_source_location(UnboundMethodInstanceTest) From f2833e358cf58c3e69038cab80e87e40b7694541 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 09:46:42 -0500 Subject: [PATCH 2276/2435] Fix generational GC for weak references Fixes issue pointed out in https://bugs.ruby-lang.org/issues/21084#note-7. The following script crashes: wmap = ObjectSpace::WeakMap.new GC.disable # only manual GCs GC.start GC.start retain = [] 50.times do k = Object.new wmap[k] = true retain << k end GC.start # wmap promoted, other objects still young retain.clear GC.start(full_mark: false) wmap.keys.each(&:itself) # call method on keys to cause crash --- gc/default/default.c | 10 +++++++++- test/ruby/test_weakmap.rb | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/gc/default/default.c b/gc/default/default.c index c33570f920f647..f6628fe9fd7104 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -5329,7 +5329,13 @@ rb_gc_impl_handle_weak_references_alive_p(void *objspace_ptr, VALUE obj) { rb_objspace_t *objspace = objspace_ptr; - return RVALUE_MARKED(objspace, obj); + bool marked = RVALUE_MARKED(objspace, obj); + + if (marked) { + rgengc_check_relation(objspace, obj); + } + + return marked; } static void @@ -5337,7 +5343,9 @@ gc_update_weak_references(rb_objspace_t *objspace) { VALUE *obj_ptr; rb_darray_foreach(objspace->weak_references, i, obj_ptr) { + gc_mark_set_parent(objspace, *obj_ptr); rb_gc_handle_weak_references(*obj_ptr); + gc_mark_set_parent_invalid(objspace); } size_t capa = rb_darray_capa(objspace->weak_references); diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index a2904776bc905f..1050c74b3dc4cd 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -265,4 +265,27 @@ def test_use_after_free_bug_20688 10_000.times { weakmap[Object.new] = Object.new } RUBY end + + def test_generational_gc + EnvUtil.without_gc do + wmap = ObjectSpace::WeakMap.new + + (GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] - 1).times { GC.start } + + retain = [] + 50.times do + k = Object.new + wmap[k] = true + retain << k + end + + GC.start # WeakMap promoted, other objects still young + + retain.clear + + GC.start(full_mark: false) + + wmap.keys.each(&:itself) # call method on keys to cause crash + end + end end From b7bf8c20b045c4dce5daa894d95d48c0983b9481 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 09:48:20 -0500 Subject: [PATCH 2277/2435] Add RVALUE_OLD_AGE to GC::INTERNAL_CONSTANTS for MMTk --- gc/mmtk/mmtk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index b507e337b8a132..d69a5cda2df2ae 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -550,6 +550,8 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); + // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0)); OBJ_FREEZE(gc_constants); rb_define_const(rb_mGC, "INTERNAL_CONSTANTS", gc_constants); From 3086d58263b36d364a1187cd7678e6020806e6f3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 01:34:43 +0900 Subject: [PATCH 2278/2435] Run also test-tool on mingw --- .github/workflows/mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 8c3d16fbc9c120..5c639ad48bdf38 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -207,7 +207,7 @@ jobs: - name: test timeout-minutes: 30 - run: make test + run: make test test-tool env: GNUMAKEFLAGS: '' RUBY_TESTOPTS: '-v --tty=no' From d40e056cc8cb5c21fb3f7c25bf9463548dd14873 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 02:24:21 +0900 Subject: [PATCH 2279/2435] Skip the hang-up test on Windows --- test/ruby/test_thread.rb | 2 +- tool/test/testunit/test_assertion.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 3796e12f083feb..8edebfb5c219da 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1480,7 +1480,7 @@ def test_thread_native_thread_id_across_fork_on_linux end def test_thread_interrupt_for_killed_thread - pend "hang-up" if RUBY_PLATFORM.include?("mingw") + pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM opts = { timeout: 5, timeout_error: nil } diff --git a/tool/test/testunit/test_assertion.rb b/tool/test/testunit/test_assertion.rb index b0c2267b31ade3..7b1f28a8698b15 100644 --- a/tool/test/testunit/test_assertion.rb +++ b/tool/test/testunit/test_assertion.rb @@ -8,6 +8,8 @@ def test_wrong_assertion end def test_timeout_separately + pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM + assert_raise(Timeout::Error) do assert_separately([], <<~"end;", timeout: 0.1) sleep From 9d37155cfc74258374134cbcc64f796b01f807d5 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 13:01:06 -0500 Subject: [PATCH 2280/2435] [ruby/mmtk] Use MMTK_HEAP_COUNT for SIZE_POOL_COUNT https://github.com/ruby/mmtk/commit/290a2aec4e --- gc/mmtk/mmtk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index d69a5cda2df2ae..b532d3a774cca1 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -549,7 +549,7 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools - rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(MMTK_HEAP_COUNT)); // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0)); OBJ_FREEZE(gc_constants); From c352808fa9eb650ce8c1b268a348ad553df55caa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 11:27:46 +0900 Subject: [PATCH 2281/2435] Introduce typed-data embeddable predicate macros The combination of `&` and `&&` is confusing. --- gc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gc.c b/gc.c index 8a18a129786f33..d149c3e22ec223 100644 --- a/gc.c +++ b/gc.c @@ -1096,6 +1096,9 @@ rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_ return obj; } +#define RB_DATA_TYPE_EMBEDDABLE_P(type) ((type)->flags & RUBY_TYPED_EMBEDDABLE) +#define RTYPEDDATA_EMBEDDABLE_P(obj) RB_DATA_TYPE_EMBEDDABLE_P(RTYPEDDATA_TYPE(obj)) + static VALUE typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_t *type, size_t size) { @@ -1117,7 +1120,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_ VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) { - if (UNLIKELY(type->flags & RUBY_TYPED_EMBEDDABLE)) { + if (UNLIKELY(RB_DATA_TYPE_EMBEDDABLE_P(type))) { rb_raise(rb_eTypeError, "Cannot wrap an embeddable TypedData"); } @@ -1127,7 +1130,7 @@ rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t *type) { - if (type->flags & RUBY_TYPED_EMBEDDABLE) { + if (RB_DATA_TYPE_EMBEDDABLE_P(type)) { if (!(type->flags & RUBY_TYPED_FREE_IMMEDIATELY)) { rb_raise(rb_eTypeError, "Embeddable TypedData must be freed immediately"); } @@ -1153,7 +1156,7 @@ rb_objspace_data_type_memsize(VALUE obj) const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); const void *ptr = RTYPEDDATA_GET_DATA(obj); - if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_EMBEDDABLE && !RTYPEDDATA_EMBEDDED_P(obj)) { + if (RTYPEDDATA_EMBEDDABLE_P(obj) && !RTYPEDDATA_EMBEDDED_P(obj)) { #ifdef HAVE_MALLOC_USABLE_SIZE size += malloc_usable_size((void *)ptr); #endif @@ -1285,7 +1288,7 @@ rb_data_free(void *objspace, VALUE obj) } else if (free_immediately) { (*dfree)(data); - if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_EMBEDDABLE && !RTYPEDDATA_EMBEDDED_P(obj)) { + if (RTYPEDDATA_EMBEDDABLE_P(obj) && !RTYPEDDATA_EMBEDDED_P(obj)) { xfree(data); } From d95bebe06c076cdf6951b81665e2b2e779937123 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 11:29:29 +0900 Subject: [PATCH 2282/2435] Make `RTYPEDDATA_EMBEDDABLE_P` internal-use only It should not be exposed because it is so implementation specific that it is only used in gc.c even within the entire Ruby source tree. --- gc.c | 1 + include/ruby/internal/core/rtypeddata.h | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/gc.c b/gc.c index d149c3e22ec223..f77ee417c52dea 100644 --- a/gc.c +++ b/gc.c @@ -1096,6 +1096,7 @@ rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_ return obj; } +#define RTYPEDDATA_EMBEDDED_P rbimpl_typeddata_embedded_p #define RB_DATA_TYPE_EMBEDDABLE_P(type) ((type)->flags & RUBY_TYPED_EMBEDDABLE) #define RTYPEDDATA_EMBEDDABLE_P(obj) RB_DATA_TYPE_EMBEDDABLE_P(RTYPEDDATA_TYPE(obj)) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 1dd7397f7d335c..505303cefaa5aa 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -548,7 +548,7 @@ RBIMPL_SYMBOL_EXPORT_END() #endif static inline bool -RTYPEDDATA_EMBEDDED_P(VALUE obj) +rbimpl_typeddata_embedded_p(VALUE obj) { #if RUBY_DEBUG if (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { @@ -560,6 +560,13 @@ RTYPEDDATA_EMBEDDED_P(VALUE obj) return (RTYPEDDATA(obj)->type) & TYPED_DATA_EMBEDDED; } +RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() +static inline bool +RTYPEDDATA_EMBEDDED_P(VALUE obj) +{ + return rbimpl_typeddata_embedded_p(obj); +} + static inline void * RTYPEDDATA_GET_DATA(VALUE obj) { @@ -571,9 +578,9 @@ RTYPEDDATA_GET_DATA(VALUE obj) #endif /* We reuse the data pointer in embedded TypedData. */ - return RTYPEDDATA_EMBEDDED_P(obj) ? - RBIMPL_CAST((void *)&(RTYPEDDATA(obj)->data)) : - RTYPEDDATA(obj)->data; + return rbimpl_typeddata_embedded_p(obj) ? + RBIMPL_CAST((void *)&RTYPEDDATA_DATA(obj)) : + RTYPEDDATA_DATA(obj); } RBIMPL_ATTR_PURE() From 094145fbc11afc444c8cd641e0715172f1c8a3db Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 17:54:42 +0900 Subject: [PATCH 2283/2435] [DOC] Move typed-data related macros The flags for `rb_data_type_t::flags` are public constants for defining `rb_data_type_t`. The embedded data flag and mask are internal implementation detail. --- include/ruby/internal/core/rtypeddata.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 505303cefaa5aa..add78b5ed57de7 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -109,14 +109,17 @@ /** @cond INTERNAL_MACRO */ #define RTYPEDDATA_P RTYPEDDATA_P #define RTYPEDDATA_TYPE RTYPEDDATA_TYPE +#define TYPED_DATA_EMBEDDED ((VALUE)1) +#define TYPED_DATA_PTR_MASK (~(TYPED_DATA_EMBEDDED)) +/** @endcond */ + +/** + * Macros to see if each corresponding flag is defined. + */ #define RUBY_TYPED_FREE_IMMEDIATELY RUBY_TYPED_FREE_IMMEDIATELY #define RUBY_TYPED_FROZEN_SHAREABLE RUBY_TYPED_FROZEN_SHAREABLE #define RUBY_TYPED_WB_PROTECTED RUBY_TYPED_WB_PROTECTED #define RUBY_TYPED_PROMOTED1 RUBY_TYPED_PROMOTED1 -/** @endcond */ - -#define TYPED_DATA_EMBEDDED ((VALUE)1) -#define TYPED_DATA_PTR_MASK (~(TYPED_DATA_EMBEDDED)) /** * @private From 61d45c857b6f92ef0df853cdeba02aefd0e6ffc4 Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Tue, 30 Dec 2025 20:48:18 -0600 Subject: [PATCH 2284/2435] [ruby/json] Keep track of the the number of additional backslashes to avoid an extra memchr searching the remaining characters when no more backslashes exist. https://github.com/ruby/json/commit/d21d9362fa --- ext/json/parser/parser.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 8f9729ef28a7fb..2c95cc97e8442e 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -643,7 +643,7 @@ static inline VALUE json_string_fastpath(JSON_ParserState *state, JSON_ParserCon typedef struct _json_unescape_positions { long size; const char **positions; - bool has_more; + unsigned long additional_backslashes; } JSON_UnescapePositions; static inline const char *json_next_backslash(const char *pe, const char *stringEnd, JSON_UnescapePositions *positions) @@ -657,7 +657,8 @@ static inline const char *json_next_backslash(const char *pe, const char *string } } - if (positions->has_more) { + if (positions->additional_backslashes) { + positions->additional_backslashes--; return memchr(pe, '\\', stringEnd - pe); } @@ -992,7 +993,7 @@ static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfi JSON_UnescapePositions positions = { .size = 0, .positions = backslashes, - .has_more = false, + .additional_backslashes = 0, }; do { @@ -1007,7 +1008,7 @@ static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfi backslashes[positions.size] = state->cursor; positions.size++; } else { - positions.has_more = true; + positions.additional_backslashes++; } state->cursor++; break; From c97f5d591b17b8194b8ddfccc6fb9d94b66c6bce Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Tue, 30 Dec 2025 21:32:03 -0600 Subject: [PATCH 2285/2435] [ruby/json] Simplify unescape_unicode https://github.com/ruby/json/commit/976ba36629 Co-Authored-By: Jean Boussier --- ext/json/parser/parser.c | 106 ++++++++++++++++------------------ test/json/json_parser_test.rb | 1 + 2 files changed, 51 insertions(+), 56 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 2c95cc97e8442e..60ed689fde4b4a 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -477,23 +477,24 @@ static const signed char digit_values[256] = { -1, -1, -1, -1, -1, -1, -1 }; -static uint32_t unescape_unicode(JSON_ParserState *state, const unsigned char *p) +static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe) { - signed char b; - uint32_t result = 0; - b = digit_values[p[0]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[1]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[2]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - b = digit_values[p[3]]; - if (b < 0) raise_parse_error_at("incomplete unicode character escape sequence at %s", state, (char *)p - 2); - result = (result << 4) | (unsigned char)b; - return result; + if (RB_UNLIKELY(sp > spe - 4)) { + raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + const unsigned char *p = (const unsigned char *)sp; + + const char b0 = digit_values[p[0]]; + const char b1 = digit_values[p[1]]; + const char b2 = digit_values[p[2]]; + const char b3 = digit_values[p[3]]; + + if (RB_UNLIKELY((b0 | b1 | b2 | b3) < 0)) { + raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + return ((uint32_t)b0 << 12) | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 4) | (uint32_t)b3; } #define GET_PARSER_CONFIG \ @@ -708,50 +709,43 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser case 'f': APPEND_CHAR('\f'); break; - case 'u': - if (pe > stringEnd - 5) { - raise_parse_error_at("incomplete unicode character escape sequence at %s", state, p); - } else { - uint32_t ch = unescape_unicode(state, (unsigned char *) ++pe); - pe += 3; - /* To handle values above U+FFFF, we take a sequence of - * \uXXXX escapes in the U+D800..U+DBFF then - * U+DC00..U+DFFF ranges, take the low 10 bits from each - * to make a 20-bit number, then add 0x10000 to get the - * final codepoint. - * - * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling - * Surrogate Pairs in UTF-16", and 23.6 "Surrogates - * Area". - */ - if ((ch & 0xFC00) == 0xD800) { - pe++; - if (pe > stringEnd - 6) { - raise_parse_error_at("incomplete surrogate pair at %s", state, p); - } - if (pe[0] == '\\' && pe[1] == 'u') { - uint32_t sur = unescape_unicode(state, (unsigned char *) pe + 2); - - if ((sur & 0xFC00) != 0xDC00) { - raise_parse_error_at("invalid surrogate pair at %s", state, p); - } - - ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) - | (sur & 0x3FF)); - pe += 5; - } else { - raise_parse_error_at("incomplete surrogate pair at %s", state, p); - break; + case 'u': { + uint32_t ch = unescape_unicode(state, ++pe, stringEnd); + pe += 3; + /* To handle values above U+FFFF, we take a sequence of + * \uXXXX escapes in the U+D800..U+DBFF then + * U+DC00..U+DFFF ranges, take the low 10 bits from each + * to make a 20-bit number, then add 0x10000 to get the + * final codepoint. + * + * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling + * Surrogate Pairs in UTF-16", and 23.6 "Surrogates + * Area". + */ + if ((ch & 0xFC00) == 0xD800) { + pe++; + if (RB_LIKELY((pe <= stringEnd - 6) && memcmp(pe, "\\u", 2) == 0)) { + uint32_t sur = unescape_unicode(state, pe + 2, stringEnd); + + if (RB_UNLIKELY((sur & 0xFC00) != 0xDC00)) { + raise_parse_error_at("invalid surrogate pair at %s", state, p); } - } - char buf[4]; - int unescape_len = convert_UTF32_to_UTF8(buf, ch); - MEMCPY(buffer, buf, char, unescape_len); - buffer += unescape_len; - p = ++pe; + ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF)); + pe += 5; + } else { + raise_parse_error_at("incomplete surrogate pair at %s", state, p); + break; + } } + + char buf[4]; + int unescape_len = convert_UTF32_to_UTF8(buf, ch); + MEMCPY(buffer, buf, char, unescape_len); + buffer += unescape_len; + p = ++pe; break; + } default: if ((unsigned char)*pe < 0x20) { if (!config->allow_control_characters) { diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index ec9391909d779d..ac53ba9f0c872f 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -350,6 +350,7 @@ def test_invalid_surogates assert_raise(JSON::ParserError) { parse('"\\uD800"') } assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') } assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') } + assert_raise(JSON::ParserError) { parse('"\\uD800\\u004') } end def test_parse_big_integers From 99249cc582177f05baf2a9c45e3fa21b891c70e9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 21:40:16 +0900 Subject: [PATCH 2286/2435] [ruby/json] Fix non-portable code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plain `char` may be `signed` or `unsigned` depending on the implementation. Also, bitwise ORing of `signed` values ​​is not guaranteed to be `signed`. To ensure portability, should logical-OR each comparison, but casting to `signed char` is usually sufficient. https://github.com/ruby/json/commit/8ad744c532 --- ext/json/parser/parser.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 60ed689fde4b4a..0ac16918f876b9 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -485,12 +485,12 @@ static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const const unsigned char *p = (const unsigned char *)sp; - const char b0 = digit_values[p[0]]; - const char b1 = digit_values[p[1]]; - const char b2 = digit_values[p[2]]; - const char b3 = digit_values[p[3]]; + const signed char b0 = digit_values[p[0]]; + const signed char b1 = digit_values[p[1]]; + const signed char b2 = digit_values[p[2]]; + const signed char b3 = digit_values[p[3]]; - if (RB_UNLIKELY((b0 | b1 | b2 | b3) < 0)) { + if (RB_UNLIKELY((signed char)(b0 | b1 | b2 | b3) < 0)) { raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); } From 544770d566ae6b9cbdb79b24a555f10f60b3e5e3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 13:42:21 -0500 Subject: [PATCH 2287/2435] [ruby/mmtk] Process obj_free candidates in parallel This commit allows objects that are safe to be freed in parallel to do so. A decrease in object freeing time can be seen in profiles. The benchmarks don't show much difference. Before: -------------- -------------------- ---------- --------- bench sequential free (ms) stddev (%) RSS (MiB) activerecord 242.3 7.4 84.3 chunky-png 439.1 0.6 75.6 erubi-rails 1221.2 4.2 132.7 hexapdf 1544.8 1.8 429.1 liquid-c 42.7 7.4 48.5 liquid-compile 41.4 8.3 52.2 liquid-render 100.6 3.0 56.8 mail 108.9 2.1 65.1 psych-load 1536.9 0.6 43.4 railsbench 1633.5 2.6 146.2 rubocop 126.5 15.8 142.1 ruby-lsp 129.6 9.7 112.2 sequel 47.9 6.5 44.6 shipit 1152.0 2.7 315.2 -------------- -------------------- ---------- --------- After: -------------- ------------------ ---------- --------- bench parallel free (ms) stddev (%) RSS (MiB) activerecord 235.1 5.5 87.4 chunky-png 440.8 0.8 68.1 erubi-rails 1105.3 0.8 128.0 hexapdf 1578.3 4.1 405.1 liquid-c 42.6 7.1 48.4 liquid-compile 41.5 8.1 52.1 liquid-render 101.2 2.8 53.3 mail 109.7 2.7 64.8 psych-load 1567.7 1.1 44.4 railsbench 1644.9 1.9 150.9 rubocop 125.6 15.4 148.5 ruby-lsp 127.9 5.8 104.6 sequel 48.2 6.1 44.1 shipit 1215.3 4.7 320.5 -------------- ------------------ ---------- --------- https://github.com/ruby/mmtk/commit/4f0b5fd2eb --- gc/mmtk/mmtk.c | 23 ++++++++- gc/mmtk/mmtk.h | 2 +- gc/mmtk/src/api.rs | 11 ++-- gc/mmtk/src/weak_proc.rs | 106 ++++++++++++++++++++++++++++++--------- 4 files changed, 114 insertions(+), 28 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index b532d3a774cca1..f61130b4f62350 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -696,6 +696,27 @@ rb_mmtk_alloc_fast_path(struct objspace *objspace, struct MMTk_ractor_cache *rac } } +static bool +obj_can_parallel_free_p(VALUE obj) +{ + switch (RB_BUILTIN_TYPE(obj)) { + case T_ARRAY: + case T_BIGNUM: + case T_COMPLEX: + case T_FLOAT: + case T_HASH: + case T_OBJECT: + case T_RATIONAL: + case T_REGEXP: + case T_STRING: + case T_STRUCT: + case T_SYMBOL: + return true; + default: + return false; + } +} + VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -732,7 +753,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called - mmtk_add_obj_free_candidate(alloc_obj); + mmtk_add_obj_free_candidate(alloc_obj, obj_can_parallel_free_p((VALUE)alloc_obj)); objspace->total_allocated_objects++; diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index f7da0f95f0214d..51798c4240f1d4 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -122,7 +122,7 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, size_t bytes, MMTk_AllocationSemantics semantics); -void mmtk_add_obj_free_candidate(MMTk_ObjectReference object); +void mmtk_add_obj_free_candidate(MMTk_ObjectReference object, bool can_parallel_free); void mmtk_declare_weak_references(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index ec0e5bafe2289a..3515a2408b3714 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -198,7 +198,10 @@ pub unsafe extern "C" fn mmtk_init_binding( let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); - let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); + let mut binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); + binding + .weak_proc + .init_parallel_obj_free_candidates(memory_manager::num_of_workers(binding.mmtk)); crate::BINDING .set(binding) @@ -296,8 +299,10 @@ pub unsafe extern "C" fn mmtk_post_alloc( // TODO: Replace with buffered mmtk_add_obj_free_candidates #[no_mangle] -pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference) { - binding().weak_proc.add_obj_free_candidate(object) +pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference, can_parallel_free: bool) { + binding() + .weak_proc + .add_obj_free_candidate(object, can_parallel_free) } // =============== Weak references =============== diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 3184c4f6d183f2..8bb82625446c85 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use std::sync::Mutex; use mmtk::{ @@ -9,10 +11,13 @@ use mmtk::{ use crate::{abi::GCThreadTLS, upcalls, Ruby}; pub struct WeakProcessor { + non_parallel_obj_free_candidates: Mutex>, + parallel_obj_free_candidates: Vec>>, + parallel_obj_free_candidates_counter: AtomicUsize, + /// Objects that needs `obj_free` called when dying. /// If it is a bottleneck, replace it with a lock-free data structure, /// or add candidates in batch. - obj_free_candidates: Mutex>, weak_references: Mutex>, } @@ -25,32 +30,59 @@ impl Default for WeakProcessor { impl WeakProcessor { pub fn new() -> Self { Self { - obj_free_candidates: Mutex::new(Vec::new()), + non_parallel_obj_free_candidates: Mutex::new(Vec::new()), + parallel_obj_free_candidates: vec![Mutex::new(Vec::new())], + parallel_obj_free_candidates_counter: AtomicUsize::new(0), weak_references: Mutex::new(Vec::new()), } } - /// Add an object as a candidate for `obj_free`. - /// - /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidate(&self, object: ObjectReference) { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - obj_free_candidates.push(object); + pub fn init_parallel_obj_free_candidates(&mut self, num_workers: usize) { + debug_assert_eq!(self.parallel_obj_free_candidates.len(), 1); + + for _ in 1..num_workers { + self.parallel_obj_free_candidates + .push(Mutex::new(Vec::new())); + } } - /// Add many objects as candidates for `obj_free`. + /// Add an object as a candidate for `obj_free`. /// /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidates(&self, objects: &[ObjectReference]) { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - for object in objects.iter().copied() { - obj_free_candidates.push(object); + pub fn add_obj_free_candidate(&self, object: ObjectReference, can_parallel_free: bool) { + if can_parallel_free { + // Newly allocated objects are placed in parallel_obj_free_candidates using + // round-robin. This may not be ideal for load balancing. + let idx = self + .parallel_obj_free_candidates_counter + .fetch_add(1, Ordering::Relaxed) + % self.parallel_obj_free_candidates.len(); + + self.parallel_obj_free_candidates[idx] + .lock() + .unwrap() + .push(object); + } else { + self.non_parallel_obj_free_candidates + .lock() + .unwrap() + .push(object); } } pub fn get_all_obj_free_candidates(&self) -> Vec { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - std::mem::take(obj_free_candidates.as_mut()) + // let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + let mut all_obj_free_candidates = self + .non_parallel_obj_free_candidates + .lock() + .unwrap() + .to_vec(); + + for candidates_mutex in &self.parallel_obj_free_candidates { + all_obj_free_candidates.extend(candidates_mutex.lock().unwrap().to_vec()); + } + + std::mem::take(all_obj_free_candidates.as_mut()) } pub fn add_weak_reference(&self, object: ObjectReference) { @@ -63,7 +95,22 @@ impl WeakProcessor { worker: &mut GCWorker, _tracer_context: impl ObjectTracerContext, ) { - worker.add_work(WorkBucketStage::VMRefClosure, ProcessObjFreeCandidates); + worker.add_work( + WorkBucketStage::VMRefClosure, + ProcessObjFreeCandidates { + process_type: ProcessObjFreeCandidatesType::NonParallel, + }, + ); + + for i in 0..self.parallel_obj_free_candidates.len() { + worker.add_work( + WorkBucketStage::VMRefClosure, + ProcessObjFreeCandidates { + process_type: ProcessObjFreeCandidatesType::Parallel(i), + }, + ); + } + worker.add_work(WorkBucketStage::VMRefClosure, ProcessWeakReferences); worker.add_work(WorkBucketStage::Prepare, UpdateFinalizerObjIdTables); @@ -80,16 +127,29 @@ impl WeakProcessor { } } -struct ProcessObjFreeCandidates; +enum ProcessObjFreeCandidatesType { + NonParallel, + Parallel(usize), +} + +struct ProcessObjFreeCandidates { + process_type: ProcessObjFreeCandidatesType, +} impl GCWork for ProcessObjFreeCandidates { fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { - // If it blocks, it is a bug. - let mut obj_free_candidates = crate::binding() - .weak_proc - .obj_free_candidates - .try_lock() - .expect("It's GC time. No mutators should hold this lock at this time."); + let mut obj_free_candidates = match self.process_type { + ProcessObjFreeCandidatesType::NonParallel => crate::binding() + .weak_proc + .non_parallel_obj_free_candidates + .try_lock() + .expect("Lock for non_parallel_obj_free_candidates should not be held"), + ProcessObjFreeCandidatesType::Parallel(idx) => { + crate::binding().weak_proc.parallel_obj_free_candidates[idx] + .try_lock() + .expect("Lock for parallel_obj_free_candidates should not be held") + } + }; let n_cands = obj_free_candidates.len(); From dbfedeb3a30be711fa17bf6ab9e1f4a83338f7d9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 14:03:40 -0500 Subject: [PATCH 2288/2435] [ruby/mmtk] Split ProcessObjFreeCandidates to parallel and non-parallel This makes it easier to visualize in profilers which one is non-parallel. https://github.com/ruby/mmtk/commit/ba68b2ef3b --- gc/mmtk/src/weak_proc.rs | 79 +++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 8bb82625446c85..41b133a4945506 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -97,17 +97,13 @@ impl WeakProcessor { ) { worker.add_work( WorkBucketStage::VMRefClosure, - ProcessObjFreeCandidates { - process_type: ProcessObjFreeCandidatesType::NonParallel, - }, + ProcessNonParallelObjFreeCanadidates {}, ); - for i in 0..self.parallel_obj_free_candidates.len() { + for index in 0..self.parallel_obj_free_candidates.len() { worker.add_work( WorkBucketStage::VMRefClosure, - ProcessObjFreeCandidates { - process_type: ProcessObjFreeCandidatesType::Parallel(i), - }, + ProcessParallelObjFreeCandidates { index }, ); } @@ -127,49 +123,50 @@ impl WeakProcessor { } } -enum ProcessObjFreeCandidatesType { - NonParallel, - Parallel(usize), +fn process_obj_free_candidates(obj_free_candidates: &mut Vec) { + // Process obj_free + let mut new_candidates = Vec::new(); + + for object in obj_free_candidates.iter().copied() { + if object.is_reachable() { + // Forward and add back to the candidate list. + let new_object = object.forward(); + trace!("Forwarding obj_free candidate: {object} -> {new_object}"); + new_candidates.push(new_object); + } else { + (upcalls().call_obj_free)(object); + } + } + + *obj_free_candidates = new_candidates; } -struct ProcessObjFreeCandidates { - process_type: ProcessObjFreeCandidatesType, +struct ProcessParallelObjFreeCandidates { + index: usize, } -impl GCWork for ProcessObjFreeCandidates { +impl GCWork for ProcessParallelObjFreeCandidates { fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { - let mut obj_free_candidates = match self.process_type { - ProcessObjFreeCandidatesType::NonParallel => crate::binding() - .weak_proc - .non_parallel_obj_free_candidates - .try_lock() - .expect("Lock for non_parallel_obj_free_candidates should not be held"), - ProcessObjFreeCandidatesType::Parallel(idx) => { - crate::binding().weak_proc.parallel_obj_free_candidates[idx] - .try_lock() - .expect("Lock for parallel_obj_free_candidates should not be held") - } - }; - - let n_cands = obj_free_candidates.len(); + let mut obj_free_candidates = crate::binding().weak_proc.parallel_obj_free_candidates + [self.index] + .try_lock() + .expect("Lock for parallel_obj_free_candidates should not be held"); - debug!("Total: {n_cands} candidates"); + process_obj_free_candidates(&mut obj_free_candidates); + } +} - // Process obj_free - let mut new_candidates = Vec::new(); +struct ProcessNonParallelObjFreeCanadidates; - for object in obj_free_candidates.iter().copied() { - if object.is_reachable() { - // Forward and add back to the candidate list. - let new_object = object.forward(); - trace!("Forwarding obj_free candidate: {object} -> {new_object}"); - new_candidates.push(new_object); - } else { - (upcalls().call_obj_free)(object); - } - } +impl GCWork for ProcessNonParallelObjFreeCanadidates { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let mut obj_free_candidates = crate::binding() + .weak_proc + .non_parallel_obj_free_candidates + .try_lock() + .expect("Lock for non_parallel_obj_free_candidates should not be held"); - *obj_free_candidates = new_candidates; + process_obj_free_candidates(&mut obj_free_candidates); } } From b27d9353a73179f1f8e582d651382b1b4404cab2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Dec 2025 23:56:58 +0900 Subject: [PATCH 2289/2435] Use `is_obj_encoding` instead of `is_data_encoding` The argument to `is_data_encoding` is assumed to be `T_DATA`. --- encoding.c | 7 +- gc/mmtk/mmtk.c | 23 +------ gc/mmtk/mmtk.h | 2 +- gc/mmtk/src/api.rs | 11 +--- gc/mmtk/src/weak_proc.rs | 135 +++++++++++---------------------------- 5 files changed, 48 insertions(+), 130 deletions(-) diff --git a/encoding.c b/encoding.c index 749cbd586d00be..8bb393b471ed54 100644 --- a/encoding.c +++ b/encoding.c @@ -125,8 +125,9 @@ static const rb_data_type_t encoding_data_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; -#define is_data_encoding(obj) (RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == &encoding_data_type) -#define is_obj_encoding(obj) (RB_TYPE_P((obj), T_DATA) && is_data_encoding(obj)) +#define is_encoding_type(obj) (RTYPEDDATA_TYPE(obj) == &encoding_data_type) +#define is_data_encoding(obj) (rbimpl_rtypeddata_p(obj) && is_encoding_type(obj)) +#define is_obj_encoding(obj) (rbimpl_obj_typeddata_p(obj) && is_encoding_type(obj)) int rb_data_is_encoding(VALUE obj) @@ -1345,7 +1346,7 @@ enc_inspect(VALUE self) { rb_encoding *enc; - if (!is_data_encoding(self)) { + if (!is_obj_encoding(self)) { /* do not resolve autoload */ not_encoding(self); } if (!(enc = RTYPEDDATA_GET_DATA(self)) || rb_enc_from_index(rb_enc_to_index(enc)) != enc) { diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index f61130b4f62350..b532d3a774cca1 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -696,27 +696,6 @@ rb_mmtk_alloc_fast_path(struct objspace *objspace, struct MMTk_ractor_cache *rac } } -static bool -obj_can_parallel_free_p(VALUE obj) -{ - switch (RB_BUILTIN_TYPE(obj)) { - case T_ARRAY: - case T_BIGNUM: - case T_COMPLEX: - case T_FLOAT: - case T_HASH: - case T_OBJECT: - case T_RATIONAL: - case T_REGEXP: - case T_STRING: - case T_STRUCT: - case T_SYMBOL: - return true; - default: - return false; - } -} - VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -753,7 +732,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called - mmtk_add_obj_free_candidate(alloc_obj, obj_can_parallel_free_p((VALUE)alloc_obj)); + mmtk_add_obj_free_candidate(alloc_obj); objspace->total_allocated_objects++; diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index 51798c4240f1d4..f7da0f95f0214d 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -122,7 +122,7 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, size_t bytes, MMTk_AllocationSemantics semantics); -void mmtk_add_obj_free_candidate(MMTk_ObjectReference object, bool can_parallel_free); +void mmtk_add_obj_free_candidate(MMTk_ObjectReference object); void mmtk_declare_weak_references(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 3515a2408b3714..ec0e5bafe2289a 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -198,10 +198,7 @@ pub unsafe extern "C" fn mmtk_init_binding( let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); - let mut binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); - binding - .weak_proc - .init_parallel_obj_free_candidates(memory_manager::num_of_workers(binding.mmtk)); + let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); crate::BINDING .set(binding) @@ -299,10 +296,8 @@ pub unsafe extern "C" fn mmtk_post_alloc( // TODO: Replace with buffered mmtk_add_obj_free_candidates #[no_mangle] -pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference, can_parallel_free: bool) { - binding() - .weak_proc - .add_obj_free_candidate(object, can_parallel_free) +pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference) { + binding().weak_proc.add_obj_free_candidate(object) } // =============== Weak references =============== diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 41b133a4945506..3184c4f6d183f2 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; use std::sync::Mutex; use mmtk::{ @@ -11,13 +9,10 @@ use mmtk::{ use crate::{abi::GCThreadTLS, upcalls, Ruby}; pub struct WeakProcessor { - non_parallel_obj_free_candidates: Mutex>, - parallel_obj_free_candidates: Vec>>, - parallel_obj_free_candidates_counter: AtomicUsize, - /// Objects that needs `obj_free` called when dying. /// If it is a bottleneck, replace it with a lock-free data structure, /// or add candidates in batch. + obj_free_candidates: Mutex>, weak_references: Mutex>, } @@ -30,59 +25,32 @@ impl Default for WeakProcessor { impl WeakProcessor { pub fn new() -> Self { Self { - non_parallel_obj_free_candidates: Mutex::new(Vec::new()), - parallel_obj_free_candidates: vec![Mutex::new(Vec::new())], - parallel_obj_free_candidates_counter: AtomicUsize::new(0), + obj_free_candidates: Mutex::new(Vec::new()), weak_references: Mutex::new(Vec::new()), } } - pub fn init_parallel_obj_free_candidates(&mut self, num_workers: usize) { - debug_assert_eq!(self.parallel_obj_free_candidates.len(), 1); - - for _ in 1..num_workers { - self.parallel_obj_free_candidates - .push(Mutex::new(Vec::new())); - } + /// Add an object as a candidate for `obj_free`. + /// + /// Multiple mutators can call it concurrently, so it has `&self`. + pub fn add_obj_free_candidate(&self, object: ObjectReference) { + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + obj_free_candidates.push(object); } - /// Add an object as a candidate for `obj_free`. + /// Add many objects as candidates for `obj_free`. /// /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidate(&self, object: ObjectReference, can_parallel_free: bool) { - if can_parallel_free { - // Newly allocated objects are placed in parallel_obj_free_candidates using - // round-robin. This may not be ideal for load balancing. - let idx = self - .parallel_obj_free_candidates_counter - .fetch_add(1, Ordering::Relaxed) - % self.parallel_obj_free_candidates.len(); - - self.parallel_obj_free_candidates[idx] - .lock() - .unwrap() - .push(object); - } else { - self.non_parallel_obj_free_candidates - .lock() - .unwrap() - .push(object); + pub fn add_obj_free_candidates(&self, objects: &[ObjectReference]) { + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + for object in objects.iter().copied() { + obj_free_candidates.push(object); } } pub fn get_all_obj_free_candidates(&self) -> Vec { - // let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - let mut all_obj_free_candidates = self - .non_parallel_obj_free_candidates - .lock() - .unwrap() - .to_vec(); - - for candidates_mutex in &self.parallel_obj_free_candidates { - all_obj_free_candidates.extend(candidates_mutex.lock().unwrap().to_vec()); - } - - std::mem::take(all_obj_free_candidates.as_mut()) + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + std::mem::take(obj_free_candidates.as_mut()) } pub fn add_weak_reference(&self, object: ObjectReference) { @@ -95,18 +63,7 @@ impl WeakProcessor { worker: &mut GCWorker, _tracer_context: impl ObjectTracerContext, ) { - worker.add_work( - WorkBucketStage::VMRefClosure, - ProcessNonParallelObjFreeCanadidates {}, - ); - - for index in 0..self.parallel_obj_free_candidates.len() { - worker.add_work( - WorkBucketStage::VMRefClosure, - ProcessParallelObjFreeCandidates { index }, - ); - } - + worker.add_work(WorkBucketStage::VMRefClosure, ProcessObjFreeCandidates); worker.add_work(WorkBucketStage::VMRefClosure, ProcessWeakReferences); worker.add_work(WorkBucketStage::Prepare, UpdateFinalizerObjIdTables); @@ -123,50 +80,36 @@ impl WeakProcessor { } } -fn process_obj_free_candidates(obj_free_candidates: &mut Vec) { - // Process obj_free - let mut new_candidates = Vec::new(); - - for object in obj_free_candidates.iter().copied() { - if object.is_reachable() { - // Forward and add back to the candidate list. - let new_object = object.forward(); - trace!("Forwarding obj_free candidate: {object} -> {new_object}"); - new_candidates.push(new_object); - } else { - (upcalls().call_obj_free)(object); - } - } - - *obj_free_candidates = new_candidates; -} - -struct ProcessParallelObjFreeCandidates { - index: usize, -} +struct ProcessObjFreeCandidates; -impl GCWork for ProcessParallelObjFreeCandidates { +impl GCWork for ProcessObjFreeCandidates { fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { - let mut obj_free_candidates = crate::binding().weak_proc.parallel_obj_free_candidates - [self.index] + // If it blocks, it is a bug. + let mut obj_free_candidates = crate::binding() + .weak_proc + .obj_free_candidates .try_lock() - .expect("Lock for parallel_obj_free_candidates should not be held"); + .expect("It's GC time. No mutators should hold this lock at this time."); - process_obj_free_candidates(&mut obj_free_candidates); - } -} + let n_cands = obj_free_candidates.len(); -struct ProcessNonParallelObjFreeCanadidates; + debug!("Total: {n_cands} candidates"); -impl GCWork for ProcessNonParallelObjFreeCanadidates { - fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { - let mut obj_free_candidates = crate::binding() - .weak_proc - .non_parallel_obj_free_candidates - .try_lock() - .expect("Lock for non_parallel_obj_free_candidates should not be held"); + // Process obj_free + let mut new_candidates = Vec::new(); + + for object in obj_free_candidates.iter().copied() { + if object.is_reachable() { + // Forward and add back to the candidate list. + let new_object = object.forward(); + trace!("Forwarding obj_free candidate: {object} -> {new_object}"); + new_candidates.push(new_object); + } else { + (upcalls().call_obj_free)(object); + } + } - process_obj_free_candidates(&mut obj_free_candidates); + *obj_free_candidates = new_candidates; } } From 7cf6cc83f3d34dbff6a30457fb008d87106f5e0c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 31 Dec 2025 11:36:19 -0500 Subject: [PATCH 2290/2435] Register imemo_ment as a pinning object It sometimes pins itself when it is in the overloaded_cme table. --- vm_method.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vm_method.c b/vm_method.c index 26dbe4cae8b416..dcf35527f7b956 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1119,6 +1119,10 @@ rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, rb_method_ VM_ASSERT_TYPE2(defined_class, T_CLASS, T_ICLASS); } rb_method_entry_t *me = SHAREABLE_IMEMO_NEW(rb_method_entry_t, imemo_ment, defined_class); + + // mark_and_move_method_entry pins itself when it is in the overloaded_cme table + rb_gc_register_pinning_obj((VALUE)me); + *((rb_method_definition_t **)&me->def) = def; me->called_id = called_id; me->owner = owner; From ea05c23ee8f258463156c3e3a276e2e16d777238 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 1 Jan 2026 09:24:32 +0900 Subject: [PATCH 2291/2435] Extract `RBIMPL_TYPEDDATA_PRECONDITION` --- include/ruby/internal/core/rtypeddata.h | 53 ++++++++++++------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index add78b5ed57de7..aadccc238ba1fc 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -485,6 +485,17 @@ RBIMPL_ATTR_NONNULL(()) void rb_unexpected_typeddata(const rb_data_type_t *actual, const rb_data_type_t *expected); RBIMPL_SYMBOL_EXPORT_END() +#if RUBY_DEBUG +# define RBIMPL_TYPEDDATA_PRECONDITION(obj, unreachable) \ + while (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { \ + rb_unexpected_object_type(obj, "Data"); \ + unreachable; \ + } +#else +# define RBIMPL_TYPEDDATA_PRECONDITION(obj, unreachable) \ + RBIMPL_ASSERT_NOTHING +#endif + /** * Converts sval, a pointer to your struct, into a Ruby object. * @@ -513,7 +524,7 @@ RBIMPL_SYMBOL_EXPORT_END() */ #define TypedData_Make_Struct0(result, klass, type, size, data_type, sval) \ VALUE result = rb_data_typed_object_zalloc(klass, size, data_type); \ - (sval) = RBIMPL_CAST((type *)RTYPEDDATA_GET_DATA(result)); \ + (sval) = RBIMPL_CAST((type *)rbimpl_typeddata_get_data(result)); \ RBIMPL_CAST(/*suppress unused variable warnings*/(void)(sval)) /** @@ -553,13 +564,6 @@ RBIMPL_SYMBOL_EXPORT_END() static inline bool rbimpl_typeddata_embedded_p(VALUE obj) { -#if RUBY_DEBUG - if (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { - Check_Type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(false); - } -#endif - return (RTYPEDDATA(obj)->type) & TYPED_DATA_EMBEDDED; } @@ -567,25 +571,28 @@ RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() static inline bool RTYPEDDATA_EMBEDDED_P(VALUE obj) { + RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(false)); + return rbimpl_typeddata_embedded_p(obj); } static inline void * -RTYPEDDATA_GET_DATA(VALUE obj) +rbimpl_typeddata_get_data(VALUE obj) { -#if RUBY_DEBUG - if (RB_UNLIKELY(!RB_TYPE_P(obj, RUBY_T_DATA))) { - Check_Type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(NULL); - } -#endif - /* We reuse the data pointer in embedded TypedData. */ return rbimpl_typeddata_embedded_p(obj) ? RBIMPL_CAST((void *)&RTYPEDDATA_DATA(obj)) : RTYPEDDATA_DATA(obj); } +static inline void * +RTYPEDDATA_GET_DATA(VALUE obj) +{ + RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(NULL)); + + return rbimpl_typeddata_get_data(obj); +} + RBIMPL_ATTR_PURE() RBIMPL_ATTR_ARTIFICIAL() /** @@ -639,12 +646,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool RTYPEDDATA_P(VALUE obj) { -#if RUBY_DEBUG - if (RB_UNLIKELY(! RB_TYPE_P(obj, RUBY_T_DATA))) { - Check_Type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(false); - } -#endif + RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(false)); return rbimpl_rtypeddata_p(obj); } @@ -662,12 +664,7 @@ RBIMPL_ATTR_RETURNS_NONNULL() static inline const rb_data_type_t * RTYPEDDATA_TYPE(VALUE obj) { -#if RUBY_DEBUG - if (RB_UNLIKELY(! RTYPEDDATA_P(obj))) { - rb_unexpected_type(obj, RUBY_T_DATA); - RBIMPL_UNREACHABLE_RETURN(NULL); - } -#endif + RBIMPL_TYPEDDATA_PRECONDITION(obj, RBIMPL_UNREACHABLE_RETURN(NULL)); VALUE type = RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK; const rb_data_type_t *ptr = RBIMPL_CAST((const rb_data_type_t *)type); From 41292562141118147c6a4eff5552097b0ecc79f0 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 31 Dec 2025 15:41:47 -0500 Subject: [PATCH 2292/2435] Use STR_SET_SHARED in str_duplicate_setup_heap str_duplicate_setup_heap is missing a call to rb_gc_register_pinning_obj that STR_SET_SHARED correctly calls. --- string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index b6c6d3f6268caf..1f2a14f681e6d2 100644 --- a/string.c +++ b/string.c @@ -1960,8 +1960,8 @@ str_duplicate_setup_heap(VALUE klass, VALUE str, VALUE dup) RUBY_ASSERT(RB_OBJ_FROZEN_RAW(root)); RSTRING(dup)->as.heap.ptr = RSTRING_PTR(str); - FL_SET(root, STR_SHARED_ROOT); - RB_OBJ_WRITE(dup, &RSTRING(dup)->as.heap.aux.shared, root); + FL_SET_RAW(dup, RSTRING_NOEMBED); + STR_SET_SHARED(dup, root); flags |= RSTRING_NOEMBED | STR_SHARED; STR_SET_LEN(dup, RSTRING_LEN(str)); From 8ce61f90ba4aea3d52e92e258c3803b8b885726e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 31 Dec 2025 13:54:00 +0100 Subject: [PATCH 2293/2435] Thread::Queue use a ring buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread::Queue spends a significant amount of time in array functions, checking for invariants we know aren't a problem, and whether the backing array need to reordered. By using a ring buffer we can remove a lot of overhead (~23% faster). ``` $ hyperfine './miniruby --yjit /tmp/q.rb' './miniruby-qrb --yjit /tmp/q.rb' Benchmark 1: ./miniruby --yjit /tmp/q.rb Time (mean ± σ): 1.050 s ± 0.191 s [User: 0.988 s, System: 0.004 s] Range (min … max): 0.984 s … 1.595 s 10 runs Benchmark 2: ./miniruby-qrb --yjit /tmp/q.rb Time (mean ± σ): 844.2 ms ± 3.1 ms [User: 840.4 ms, System: 2.8 ms] Range (min … max): 838.6 ms … 848.9 ms 10 runs Summary ./miniruby-qrb --yjit /tmp/q.rb ran 1.24 ± 0.23 times faster than ./miniruby --yjit /tmp/q.rb ``` ``` q = Queue.new([1, 2, 3, 4, 5, 6, 7, 8]) i = 2_000_000 while i > 0 i -= 1 q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) q.push(q.pop) end ``` --- thread_sync.c | 224 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 157 insertions(+), 67 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index a93888fad02ae6..72341762399a8f 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -691,47 +691,57 @@ rb_mutex_allow_trap(VALUE self, int val) /* Queue */ -#define queue_waitq(q) UNALIGNED_MEMBER_PTR(q, waitq) -#define queue_list(q) UNALIGNED_MEMBER_PTR(q, que) -RBIMPL_ATTR_PACKED_STRUCT_UNALIGNED_BEGIN() struct rb_queue { struct ccan_list_head waitq; rb_serial_t fork_gen; - const VALUE que; + long capa; + long len; + long offset; + VALUE *buffer; int num_waiting; -} RBIMPL_ATTR_PACKED_STRUCT_UNALIGNED_END(); +}; + +#define szqueue_waitq(sq) &sq->q.waitq +#define szqueue_pushq(sq) &sq->pushq -#define szqueue_waitq(sq) UNALIGNED_MEMBER_PTR(sq, q.waitq) -#define szqueue_list(sq) UNALIGNED_MEMBER_PTR(sq, q.que) -#define szqueue_pushq(sq) UNALIGNED_MEMBER_PTR(sq, pushq) -RBIMPL_ATTR_PACKED_STRUCT_UNALIGNED_BEGIN() struct rb_szqueue { struct rb_queue q; int num_waiting_push; struct ccan_list_head pushq; long max; -} RBIMPL_ATTR_PACKED_STRUCT_UNALIGNED_END(); +}; static void queue_mark_and_move(void *ptr) { struct rb_queue *q = ptr; - /* no need to mark threads in waitq, they are on stack */ - rb_gc_mark_and_move((VALUE *)UNALIGNED_MEMBER_PTR(q, que)); + for (long index = 0; index < q->len; index++) { + rb_gc_mark_and_move(&q->buffer[((q->offset + index) % q->capa)]); + } +} + +static void +queue_free(void *ptr) +{ + struct rb_queue *q = ptr; + if (q->buffer) { + ruby_sized_xfree(q->buffer, q->capa * sizeof(VALUE)); + } } static size_t queue_memsize(const void *ptr) { - return sizeof(struct rb_queue); + const struct rb_queue *q = ptr; + return sizeof(struct rb_queue) + (q->capa * sizeof(VALUE)); } static const rb_data_type_t queue_data_type = { .wrap_struct_name = "Thread::Queue", .function = { .dmark = queue_mark_and_move, - .dfree = RUBY_TYPED_DEFAULT_FREE, + .dfree = queue_free, .dsize = queue_memsize, .dcompact = queue_mark_and_move, }, @@ -745,7 +755,7 @@ queue_alloc(VALUE klass) struct rb_queue *q; obj = TypedData_Make_Struct(klass, struct rb_queue, &queue_data_type, q); - ccan_list_head_init(queue_waitq(q)); + ccan_list_head_init(&q->waitq); return obj; } @@ -759,13 +769,13 @@ queue_fork_check(struct rb_queue *q) } /* forked children can't reach into parent thread stacks */ q->fork_gen = fork_gen; - ccan_list_head_init(queue_waitq(q)); + ccan_list_head_init(&q->waitq); q->num_waiting = 0; return 1; } -static struct rb_queue * -queue_ptr(VALUE obj) +static inline struct rb_queue * +raw_queue_ptr(VALUE obj) { struct rb_queue *q; @@ -775,6 +785,22 @@ queue_ptr(VALUE obj) return q; } +static inline void +check_queue(VALUE obj, struct rb_queue *q) +{ + if (RB_UNLIKELY(q->buffer == NULL)) { + rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); + } +} + +static inline struct rb_queue * +queue_ptr(VALUE obj) +{ + struct rb_queue *q = raw_queue_ptr(obj); + check_queue(obj, q); + return q; +} + #define QUEUE_CLOSED FL_USER5 static rb_hrtime_t @@ -801,17 +827,25 @@ szqueue_mark_and_move(void *ptr) queue_mark_and_move(&sq->q); } +static void +szqueue_free(void *ptr) +{ + struct rb_szqueue *sq = ptr; + queue_free(&sq->q); +} + static size_t szqueue_memsize(const void *ptr) { - return sizeof(struct rb_szqueue); + const struct rb_szqueue *sq = ptr; + return sizeof(struct rb_szqueue) + (sq->q.capa * sizeof(VALUE)); } static const rb_data_type_t szqueue_data_type = { .wrap_struct_name = "Thread::SizedQueue", .function = { .dmark = szqueue_mark_and_move, - .dfree = RUBY_TYPED_DEFAULT_FREE, + .dfree = szqueue_free, .dsize = szqueue_memsize, .dcompact = szqueue_mark_and_move, }, @@ -830,8 +864,8 @@ szqueue_alloc(VALUE klass) return obj; } -static struct rb_szqueue * -szqueue_ptr(VALUE obj) +static inline struct rb_szqueue * +raw_szqueue_ptr(VALUE obj) { struct rb_szqueue *sq; @@ -844,25 +878,12 @@ szqueue_ptr(VALUE obj) return sq; } -static VALUE -ary_buf_new(void) -{ - return rb_ary_hidden_new(1); -} - -static inline VALUE -check_array(VALUE obj, VALUE ary) -{ - if (RB_LIKELY(ary)) { - return ary; - } - rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); -} - -static long -queue_length(VALUE self, struct rb_queue *q) +static inline struct rb_szqueue * +szqueue_ptr(VALUE obj) { - return RARRAY_LEN(check_array(self, q->que)); + struct rb_szqueue *sq = raw_szqueue_ptr(obj); + check_queue(obj, &sq->q); + return sq; } static int @@ -889,10 +910,63 @@ raise_closed_queue_error(VALUE self) static VALUE queue_closed_result(VALUE self, struct rb_queue *q) { - RUBY_ASSERT(queue_length(self, q) == 0); + RUBY_ASSERT(q->len == 0); return Qnil; } +#define QUEUE_INITIAL_CAPA 8 + +static inline void +ring_buffer_init(struct rb_queue *q, long initial_capa) +{ + q->buffer = ALLOC_N(VALUE, initial_capa); + q->capa = initial_capa; +} + +static inline void +ring_buffer_expand(struct rb_queue *q) +{ + RUBY_ASSERT(q->capa > 0); + VALUE *new_buffer = ALLOC_N(VALUE, q->capa * 2); + MEMCPY(new_buffer, q->buffer + q->offset, VALUE, q->capa - q->offset); + MEMCPY(new_buffer + (q->capa - q->offset), q->buffer, VALUE, q->offset); + VALUE *old_buffer = q->buffer; + q->buffer = new_buffer; + q->offset = 0; + ruby_sized_xfree(old_buffer, q->capa * sizeof(VALUE)); + q->capa *= 2; +} + +static void +ring_buffer_push(VALUE self, struct rb_queue *q, VALUE obj) +{ + if (RB_UNLIKELY(q->len >= q->capa)) { + ring_buffer_expand(q); + } + RUBY_ASSERT(q->capa > q->len); + long index = (q->offset + q->len) % q->capa; + q->len++; + RB_OBJ_WRITE(self, &q->buffer[index], obj); +} + +static VALUE +ring_buffer_shift(struct rb_queue *q) +{ + if (!q->len) { + return Qnil; + } + + VALUE obj = q->buffer[q->offset]; + q->len--; + if (q->len == 0) { + q->offset = 0; + } + else { + q->offset = (q->offset + 1) % q->capa; + } + return obj; +} + /* * Document-class: Thread::Queue * @@ -957,14 +1031,27 @@ static VALUE rb_queue_initialize(int argc, VALUE *argv, VALUE self) { VALUE initial; - struct rb_queue *q = queue_ptr(self); + struct rb_queue *q = raw_queue_ptr(self); if ((argc = rb_scan_args(argc, argv, "01", &initial)) == 1) { initial = rb_to_array(initial); } - RB_OBJ_WRITE(self, queue_list(q), ary_buf_new()); - ccan_list_head_init(queue_waitq(q)); + ccan_list_head_init(&q->waitq); if (argc == 1) { - rb_ary_concat(q->que, initial); + long len = RARRAY_LEN(initial); + long initial_capa = QUEUE_INITIAL_CAPA; + while (initial_capa < len) { + initial_capa *= 2; + } + ring_buffer_init(q, initial_capa); + + const VALUE *initial_ptr = RARRAY_CONST_PTR(initial); + + for (long index = 0; index < len; index++) { + ring_buffer_push(self, q, initial_ptr[index]); + } + } + else { + ring_buffer_init(q, QUEUE_INITIAL_CAPA); } return self; } @@ -972,11 +1059,12 @@ rb_queue_initialize(int argc, VALUE *argv, VALUE self) static VALUE queue_do_push(VALUE self, struct rb_queue *q, VALUE obj) { + check_queue(self, q); if (queue_closed_p(self)) { raise_closed_queue_error(self); } - rb_ary_push(check_array(self, q->que), obj); - wakeup_one(queue_waitq(q)); + ring_buffer_push(self, q, obj); + wakeup_one(&q->waitq); return self; } @@ -1021,7 +1109,7 @@ rb_queue_close(VALUE self) if (!queue_closed_p(self)) { FL_SET(self, QUEUE_CLOSED); - wakeup_all(queue_waitq(q)); + wakeup_all(&q->waitq); } return self; @@ -1097,8 +1185,7 @@ szqueue_sleep_done(VALUE p) static inline VALUE queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE non_block, VALUE timeout) { - check_array(self, q->que); - if (RARRAY_LEN(q->que) == 0) { + if (q->len == 0) { if (RTEST(non_block)) { rb_raise(rb_eThreadError, "queue empty"); } @@ -1109,12 +1196,12 @@ queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE n } rb_hrtime_t end = queue_timeout2hrtime(timeout); - while (RARRAY_LEN(q->que) == 0) { + while (q->len == 0) { if (queue_closed_p(self)) { return queue_closed_result(self, q); } else { - RUBY_ASSERT(RARRAY_LEN(q->que) == 0); + RUBY_ASSERT(q->len == 0); RUBY_ASSERT(queue_closed_p(self) == 0); struct queue_waiter queue_waiter = { @@ -1122,7 +1209,7 @@ queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE n .as = {.q = q} }; - struct ccan_list_head *waitq = queue_waitq(q); + struct ccan_list_head *waitq = &q->waitq; ccan_list_add_tail(waitq, &queue_waiter.w.node); queue_waiter.as.q->num_waiting++; @@ -1139,7 +1226,7 @@ queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE n } } - return rb_ary_shift(q->que); + return ring_buffer_shift(q); } static VALUE @@ -1158,7 +1245,14 @@ rb_queue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE time static VALUE rb_queue_empty_p(VALUE self) { - return RBOOL(queue_length(self, queue_ptr(self)) == 0); + return RBOOL(queue_ptr(self)->len == 0); +} + +static void +queue_clear(struct rb_queue *q) +{ + q->len = 0; + q->offset = 0; } /* @@ -1170,9 +1264,7 @@ rb_queue_empty_p(VALUE self) static VALUE rb_queue_clear(VALUE self) { - struct rb_queue *q = queue_ptr(self); - - rb_ary_clear(check_array(self, q->que)); + queue_clear(queue_ptr(self)); return self; } @@ -1188,7 +1280,7 @@ rb_queue_clear(VALUE self) static VALUE rb_queue_length(VALUE self) { - return LONG2NUM(queue_length(self, queue_ptr(self))); + return LONG2NUM(queue_ptr(self)->len); } NORETURN(static VALUE rb_queue_freeze(VALUE self)); @@ -1241,14 +1333,13 @@ static VALUE rb_szqueue_initialize(VALUE self, VALUE vmax) { long max; - struct rb_szqueue *sq = szqueue_ptr(self); + struct rb_szqueue *sq = raw_szqueue_ptr(self); max = NUM2LONG(vmax); if (max <= 0) { rb_raise(rb_eArgError, "queue size must be positive"); } - - RB_OBJ_WRITE(self, szqueue_list(sq), ary_buf_new()); + ring_buffer_init(&sq->q, QUEUE_INITIAL_CAPA); ccan_list_head_init(szqueue_waitq(sq)); ccan_list_head_init(szqueue_pushq(sq)); sq->max = max; @@ -1323,7 +1414,7 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ { struct rb_szqueue *sq = szqueue_ptr(self); - if (queue_length(self, &sq->q) >= sq->max) { + if (sq->q.len >= sq->max) { if (RTEST(non_block)) { rb_raise(rb_eThreadError, "queue full"); } @@ -1334,7 +1425,7 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ } rb_hrtime_t end = queue_timeout2hrtime(timeout); - while (queue_length(self, &sq->q) >= sq->max) { + while (sq->q.len >= sq->max) { if (queue_closed_p(self)) { raise_closed_queue_error(self); } @@ -1370,7 +1461,7 @@ rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE ti struct rb_szqueue *sq = szqueue_ptr(self); VALUE retval = queue_do_pop(ec, self, &sq->q, non_block, timeout); - if (queue_length(self, &sq->q) < sq->max) { + if (sq->q.len < sq->max) { wakeup_one(szqueue_pushq(sq)); } @@ -1387,8 +1478,7 @@ static VALUE rb_szqueue_clear(VALUE self) { struct rb_szqueue *sq = szqueue_ptr(self); - - rb_ary_clear(check_array(self, sq->q.que)); + queue_clear(&sq->q); wakeup_all(szqueue_pushq(sq)); return self; } From 26a5bcd6de806fa460cafd0906651a66cac33e7e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 31 Dec 2025 18:29:01 +0100 Subject: [PATCH 2294/2435] [ruby/prism] Fix spacing in the generated #each_child_node https://github.com/ruby/prism/commit/91f60cb736 --- prism/templates/lib/prism/node.rb.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index c97c029d3bf410..066a0cea1b508f 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -353,7 +353,7 @@ module Prism <%- when Prism::Template::OptionalNodeField -%> yield <%= field.name %> if <%= field.name %> <%- when Prism::Template::NodeListField -%> - <%= field.name %>.each {|node| yield node } + <%= field.name %>.each { |node| yield node } <%- end -%> <%- end -%> end From acda63debc1e7d056d2fe934caa05a930f8f3f2d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 31 Dec 2025 15:02:25 -0500 Subject: [PATCH 2295/2435] [ruby/mmtk] Format imports to be each on a new line https://github.com/ruby/mmtk/commit/42adba630e --- gc/mmtk/src/abi.rs | 8 ++++++-- gc/mmtk/src/collection.rs | 11 ++++++++--- gc/mmtk/src/heap/ruby_heap_trigger.rs | 3 ++- gc/mmtk/src/lib.rs | 7 +++++-- gc/mmtk/src/object_model.rs | 13 +++++++++---- gc/mmtk/src/pinning_registry.rs | 19 +++++++++++-------- gc/mmtk/src/scanning.rs | 16 ++++++++++++---- gc/mmtk/src/utils.rs | 7 +++++-- gc/mmtk/src/weak_proc.rs | 16 +++++++++------- 9 files changed, 67 insertions(+), 33 deletions(-) diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 1d7dc62a8753b4..1e03dbf2f98809 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -1,8 +1,12 @@ use crate::api::RubyMutator; -use crate::{extra_assert, Ruby}; +use crate::extra_assert; +use crate::Ruby; use libc::c_int; use mmtk::scheduler::GCWorker; -use mmtk::util::{Address, ObjectReference, VMMutatorThread, VMWorkerThread}; +use mmtk::util::Address; +use mmtk::util::ObjectReference; +use mmtk::util::VMMutatorThread; +use mmtk::util::VMWorkerThread; // For the C binding pub const OBJREF_OFFSET: usize = 8; diff --git a/gc/mmtk/src/collection.rs b/gc/mmtk/src/collection.rs index 0b1221204c1468..83d046aef43092 100644 --- a/gc/mmtk/src/collection.rs +++ b/gc/mmtk/src/collection.rs @@ -2,12 +2,17 @@ use crate::abi::GCThreadTLS; use crate::api::RubyMutator; use crate::heap::RubyHeapTrigger; -use crate::{mmtk, upcalls, Ruby}; +use crate::mmtk; +use crate::upcalls; +use crate::Ruby; use mmtk::memory_manager; use mmtk::scheduler::*; use mmtk::util::heap::GCTriggerPolicy; -use mmtk::util::{VMMutatorThread, VMThread, VMWorkerThread}; -use mmtk::vm::{Collection, GCThreadContext}; +use mmtk::util::VMMutatorThread; +use mmtk::util::VMThread; +use mmtk::util::VMWorkerThread; +use mmtk::vm::Collection; +use mmtk::vm::GCThreadContext; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::thread; diff --git a/gc/mmtk/src/heap/ruby_heap_trigger.rs b/gc/mmtk/src/heap/ruby_heap_trigger.rs index 9215e2ebb0ec04..fe1130043d55f7 100644 --- a/gc/mmtk/src/heap/ruby_heap_trigger.rs +++ b/gc/mmtk/src/heap/ruby_heap_trigger.rs @@ -1,4 +1,5 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use mmtk::util::heap::GCTriggerPolicy; use mmtk::util::heap::SpaceStats; diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index 0da6121883747e..0ee8f6752e5778 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -14,8 +14,11 @@ use std::sync::Mutex; use std::thread::ThreadId; use abi::RubyUpcalls; -use binding::{RubyBinding, RubyBindingFast, RubyConfiguration}; -use mmtk::vm::slot::{SimpleSlot, UnimplementedMemorySlice}; +use binding::RubyBinding; +use binding::RubyBindingFast; +use binding::RubyConfiguration; +use mmtk::vm::slot::SimpleSlot; +use mmtk::vm::slot::UnimplementedMemorySlice; use mmtk::vm::VMBinding; use mmtk::MMTK; use once_cell::sync::OnceCell; diff --git a/gc/mmtk/src/object_model.rs b/gc/mmtk/src/object_model.rs index dd27f83612cb9a..d673ca11a0a31d 100644 --- a/gc/mmtk/src/object_model.rs +++ b/gc/mmtk/src/object_model.rs @@ -1,10 +1,15 @@ use std::ptr::copy_nonoverlapping; -use crate::abi::{RubyObjectAccess, MIN_OBJ_ALIGN, OBJREF_OFFSET}; -use crate::{abi, Ruby}; +use crate::abi; +use crate::abi::RubyObjectAccess; +use crate::abi::MIN_OBJ_ALIGN; +use crate::abi::OBJREF_OFFSET; +use crate::Ruby; use mmtk::util::constants::BITS_IN_BYTE; -use mmtk::util::copy::{CopySemantics, GCWorkerCopyContext}; -use mmtk::util::{Address, ObjectReference}; +use mmtk::util::copy::CopySemantics; +use mmtk::util::copy::GCWorkerCopyContext; +use mmtk::util::Address; +use mmtk::util::ObjectReference; use mmtk::vm::*; pub struct VMObjectModel {} diff --git a/gc/mmtk/src/pinning_registry.rs b/gc/mmtk/src/pinning_registry.rs index 7f67bbeef5e4a3..71cf73eaf913b4 100644 --- a/gc/mmtk/src/pinning_registry.rs +++ b/gc/mmtk/src/pinning_registry.rs @@ -1,13 +1,16 @@ use std::sync::Mutex; -use mmtk::{ - memory_manager, - scheduler::{GCWork, GCWorker, WorkBucketStage}, - util::{ObjectReference, VMWorkerThread}, - MMTK, -}; - -use crate::{abi::GCThreadTLS, upcalls, Ruby}; +use mmtk::memory_manager; +use mmtk::scheduler::GCWork; +use mmtk::scheduler::GCWorker; +use mmtk::scheduler::WorkBucketStage; +use mmtk::util::ObjectReference; +use mmtk::util::VMWorkerThread; +use mmtk::MMTK; + +use crate::abi::GCThreadTLS; +use crate::upcalls; +use crate::Ruby; pub struct PinningRegistry { pinning_objs: Mutex>, diff --git a/gc/mmtk/src/scanning.rs b/gc/mmtk/src/scanning.rs index eca4769e2f9d15..be834bdd0bde95 100644 --- a/gc/mmtk/src/scanning.rs +++ b/gc/mmtk/src/scanning.rs @@ -1,10 +1,18 @@ use crate::abi::GCThreadTLS; +use crate::upcalls; use crate::utils::ChunkedVecCollector; -use crate::{upcalls, Ruby, RubySlot}; -use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage}; -use mmtk::util::{ObjectReference, VMWorkerThread}; -use mmtk::vm::{ObjectTracer, RootsWorkFactory, Scanning, SlotVisitor}; +use crate::Ruby; +use crate::RubySlot; +use mmtk::scheduler::GCWork; +use mmtk::scheduler::GCWorker; +use mmtk::scheduler::WorkBucketStage; +use mmtk::util::ObjectReference; +use mmtk::util::VMWorkerThread; +use mmtk::vm::ObjectTracer; +use mmtk::vm::RootsWorkFactory; +use mmtk::vm::Scanning; +use mmtk::vm::SlotVisitor; use mmtk::Mutator; pub struct VMScanning {} diff --git a/gc/mmtk/src/utils.rs b/gc/mmtk/src/utils.rs index 71a7ae8dd25f60..d1979eaf58da4e 100644 --- a/gc/mmtk/src/utils.rs +++ b/gc/mmtk/src/utils.rs @@ -1,7 +1,10 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use atomic_refcell::AtomicRefCell; -use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage}; +use mmtk::scheduler::GCWork; +use mmtk::scheduler::GCWorker; +use mmtk::scheduler::WorkBucketStage; use crate::Ruby; use sysinfo::System; diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 3184c4f6d183f2..c8ad944633bd3a 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,12 +1,14 @@ use std::sync::Mutex; -use mmtk::{ - scheduler::{GCWork, GCWorker, WorkBucketStage}, - util::ObjectReference, - vm::ObjectTracerContext, -}; - -use crate::{abi::GCThreadTLS, upcalls, Ruby}; +use mmtk::scheduler::GCWork; +use mmtk::scheduler::GCWorker; +use mmtk::scheduler::WorkBucketStage; +use mmtk::util::ObjectReference; +use mmtk::vm::ObjectTracerContext; + +use crate::abi::GCThreadTLS; +use crate::upcalls; +use crate::Ruby; pub struct WeakProcessor { /// Objects that needs `obj_free` called when dying. From 0e9d69893007b1a17372fbc2fbcc7f0ee92be5fb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Jul 2025 21:12:32 +0900 Subject: [PATCH 2296/2435] Prefer dedicated assertions --- test/ruby/test_box.rb | 42 ++++++++++++++--------------- test/ruby/test_encoding.rb | 4 +-- test/ruby/test_env.rb | 15 ++++++----- test/ruby/test_exception.rb | 2 +- test/ruby/test_gc_compact.rb | 6 ++--- test/ruby/test_io_buffer.rb | 24 ++++++++--------- test/ruby/test_module.rb | 46 ++++++++++++++++---------------- test/ruby/test_nomethod_error.rb | 2 +- test/ruby/test_parse.rb | 2 +- test/ruby/test_signal.rb | 6 ++--- test/ruby/test_thread_queue.rb | 6 ++--- test/ruby/test_transcode.rb | 8 +++--- 12 files changed, 82 insertions(+), 81 deletions(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 763f6148cb5483..bb98a2efbe57ca 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -28,15 +28,15 @@ def test_box_availability_in_default assert_separately(['RUBY_BOX'=>nil], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; assert_nil ENV['RUBY_BOX'] - assert !Ruby::Box.enabled? + assert_not_predicate Ruby::Box, :enabled? end; end def test_box_availability_when_enabled assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; - assert '1', ENV['RUBY_BOX'] - assert Ruby::Box.enabled? + assert_equal '1', ENV['RUBY_BOX'] + assert_predicate Ruby::Box, :enabled? end; end @@ -44,7 +44,7 @@ def test_current_box_in_main assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; assert_equal Ruby::Box.main, Ruby::Box.current - assert Ruby::Box.main.main? + assert_predicate Ruby::Box.main, :main? end; end @@ -152,7 +152,7 @@ def test_raising_errors_in_require setup_box assert_raise(RuntimeError, "Yay!") { @box.require(File.join(__dir__, 'box', 'raise')) } - assert Ruby::Box.current.inspect.include?("main") + assert_include Ruby::Box.current.inspect, "main" end def test_autoload_in_box @@ -514,7 +514,7 @@ def test_global_variables assert_equal nil, $, # used only in box - assert !global_variables.include?(:$used_only_in_box) + assert_not_include? global_variables, :$used_only_in_box @box::UniqueGvar.write(123) assert_equal 123, @box::UniqueGvar.read assert_nil $used_only_in_box @@ -535,7 +535,7 @@ def test_global_variables def test_load_path_and_loaded_features setup_box - assert $LOAD_PATH.respond_to?(:resolve_feature_path) + assert_respond_to $LOAD_PATH, :resolve_feature_path @box.require_relative('box/load_path') @@ -545,13 +545,13 @@ def test_load_path_and_loaded_features box_dir = File.join(__dir__, 'box') # TODO: $LOADED_FEATURES in method calls should refer the current box in addition to the loading box. - # assert @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank1.rb')) - # assert !@box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) - # assert @box::LoadPathCheck.require_blank2 - # assert @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) + # assert_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank1.rb') + # assert_not_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb') + # assert_predicate @box::LoadPathCheck, :require_blank2 + # assert_include(@box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb')) - assert !$LOADED_FEATURES.include?(File.join(box_dir, 'blank1.rb')) - assert !$LOADED_FEATURES.include?(File.join(box_dir, 'blank2.rb')) + assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank1.rb') + assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank2.rb') end def test_eval_basic @@ -690,23 +690,23 @@ def test_root_and_main_methods begin; pend unless Ruby::Box.respond_to?(:root) and Ruby::Box.respond_to?(:main) # for RUBY_DEBUG > 0 - assert Ruby::Box.root.respond_to?(:root?) - assert Ruby::Box.main.respond_to?(:main?) + assert_respond_to Ruby::Box.root, :root? + assert_respond_to Ruby::Box.main, :main? - assert Ruby::Box.root.root? - assert Ruby::Box.main.main? + assert_predicate Ruby::Box.root, :root? + assert_predicate Ruby::Box.main, :main? assert_equal Ruby::Box.main, Ruby::Box.current $a = 1 $LOADED_FEATURES.push("/tmp/foobar") assert_equal 2, Ruby::Box.root.eval('$a = 2; $a') - assert !Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') - assert "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + assert_not_include Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES'), "/tmp/foobar" + assert_equal "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') assert_equal 1, $a - assert !$LOADED_FEATURES.include?("/tmp/barbaz") - assert !Object.const_defined?(:FooClass) + assert_not_include $LOADED_FEATURES, "/tmp/barbaz" + assert_not_operator Object, :const_defined?, :FooClass end; end diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 5c1eb50bb13786..0cd5bf49dc19f7 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -154,7 +154,7 @@ def test_ractor_lazy_load_encoding_concurrently r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end @@ -173,7 +173,7 @@ def test_ractor_set_default_external_string r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 2727620c198522..d17e300bceb2f1 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -1379,23 +1379,24 @@ def test_frozen_in_ractor Ractor.new port = Ractor::Port.new do |port| ENV["#{PATH_ENV}"] = "/" ENV.each do |k, v| - port.send [k.frozen?] - port.send [v.frozen?] + port.send [k] + port.send [v] end ENV.each_key do |k| - port.send [k.frozen?] + port.send [k] end ENV.each_value do |v| - port.send [v.frozen?] + port.send [v] end ENV.each_key do |k| - port.send [ENV[k].frozen?, "[\#{k.dump}]"] - port.send [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"] + port.send [ENV[k], "[\#{k.dump}]"] + port.send [ENV.fetch(k), "fetch(\#{k.dump})"] end port.send "finished" end while((params=port.receive) != "finished") - assert(*params) + value, *params = params + assert_predicate(value, :frozen?, *params) end end; end diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 17ff5a2e82996b..31e5aa9f6b9505 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -992,7 +992,7 @@ def test_output_string_encoding assert_equal 1, outs.size assert_equal 0, errs.size err = outs.first.force_encoding('utf-8') - assert err.valid_encoding?, 'must be valid encoding' + assert_predicate err, :valid_encoding? assert_match %r/\u3042/, err end end diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index a03535171c42e8..bc7692d644fba9 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -30,7 +30,7 @@ class AutoCompact < Test::Unit::TestCase def test_enable_autocompact before = GC.auto_compact GC.auto_compact = true - assert GC.auto_compact + assert_predicate GC, :auto_compact ensure GC.auto_compact = before end @@ -151,12 +151,12 @@ def test_ast_compacts def walk_ast ast children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node) children.each do |child| - assert child.type + assert_predicate child, :type walk_ast child end end ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump} - assert GC.compact + assert_predicate GC, :compact walk_ast ast end; end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index e996fc39b88eb2..706ce16c42a547 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -46,22 +46,22 @@ def test_default_size def test_new_internal buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL) assert_equal 1024, buffer.size - refute buffer.external? - assert buffer.internal? - refute buffer.mapped? + refute_predicate buffer, :external? + assert_predicate buffer, :internal? + refute_predicate buffer, :mapped? end def test_new_mapped buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED) assert_equal 1024, buffer.size - refute buffer.external? - refute buffer.internal? - assert buffer.mapped? + refute_predicate buffer, :external? + refute_predicate buffer, :internal? + assert_predicate buffer, :mapped? end def test_new_readonly buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY) - assert buffer.readonly? + assert_predicate buffer, :readonly? assert_raise IO::Buffer::AccessError do buffer.set_string("") @@ -141,19 +141,19 @@ def test_file_mapped_invalid def test_string_mapped string = "Hello World" buffer = IO::Buffer.for(string) - assert buffer.readonly? + assert_predicate buffer, :readonly? end def test_string_mapped_frozen string = "Hello World".freeze buffer = IO::Buffer.for(string) - assert buffer.readonly? + assert_predicate buffer, :readonly? end def test_string_mapped_mutable string = "Hello World" IO::Buffer.for(string) do |buffer| - refute buffer.readonly? + refute_predicate buffer, :readonly? buffer.set_value(:U8, 0, "h".ord) @@ -715,8 +715,8 @@ def test_private buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) begin - assert buffer.private? - refute buffer.readonly? + assert_predicate buffer, :private? + refute_predicate buffer, :readonly? buffer.set_string("J") diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 30a7c5d9bc1c22..3db60dec8f3b43 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -9,18 +9,18 @@ def _wrap_assertion yield end - def assert_method_defined?(klass, mid, message="") + def assert_method_defined?(klass, (mid, *args), message="") message = build_message(message, "#{klass}\##{mid} expected to be defined.") _wrap_assertion do - klass.method_defined?(mid) or + klass.method_defined?(mid, *args) or raise Test::Unit::AssertionFailedError, message, caller(3) end end - def assert_method_not_defined?(klass, mid, message="") + def assert_method_not_defined?(klass, (mid, *args), message="") message = build_message(message, "#{klass}\##{mid} expected to not be defined.") _wrap_assertion do - klass.method_defined?(mid) and + klass.method_defined?(mid, *args) and raise Test::Unit::AssertionFailedError, message, caller(3) end end @@ -813,40 +813,40 @@ def test_instance_methods def test_method_defined? [User, Class.new{include User}, Class.new{prepend User}].each do |klass| [[], [true]].each do |args| - assert !klass.method_defined?(:wombat, *args) - assert klass.method_defined?(:mixin, *args) - assert klass.method_defined?(:user, *args) - assert klass.method_defined?(:user2, *args) - assert !klass.method_defined?(:user3, *args) + assert_method_not_defined?(klass, [:wombat, *args]) + assert_method_defined?(klass, [:mixin, *args]) + assert_method_defined?(klass, [:user, *args]) + assert_method_defined?(klass, [:user2, *args]) + assert_method_not_defined?(klass, [:user3, *args]) - assert !klass.method_defined?("wombat", *args) - assert klass.method_defined?("mixin", *args) - assert klass.method_defined?("user", *args) - assert klass.method_defined?("user2", *args) - assert !klass.method_defined?("user3", *args) + assert_method_not_defined?(klass, ["wombat", *args]) + assert_method_defined?(klass, ["mixin", *args]) + assert_method_defined?(klass, ["user", *args]) + assert_method_defined?(klass, ["user2", *args]) + assert_method_not_defined?(klass, ["user3", *args]) end end end def test_method_defined_without_include_super - assert User.method_defined?(:user, false) - assert !User.method_defined?(:mixin, false) - assert Mixin.method_defined?(:mixin, false) + assert_method_defined?(User, [:user, false]) + assert_method_not_defined?(User, [:mixin, false]) + assert_method_defined?(Mixin, [:mixin, false]) User.const_set(:FOO, c = Class.new) c.prepend(User) - assert !c.method_defined?(:user, false) + assert_method_not_defined?(c, [:user, false]) c.define_method(:user){} - assert c.method_defined?(:user, false) + assert_method_defined?(c, [:user, false]) - assert !c.method_defined?(:mixin, false) + assert_method_not_defined?(c, [:mixin, false]) c.define_method(:mixin){} - assert c.method_defined?(:mixin, false) + assert_method_defined?(c, [:mixin, false]) - assert !c.method_defined?(:userx, false) + assert_method_not_defined?(c, [:userx, false]) c.define_method(:userx){} - assert c.method_defined?(:userx, false) + assert_method_defined?(c, [:userx, false]) # cleanup User.class_eval do diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb index aa2a88b2d8f3fb..6abd20cc81bae5 100644 --- a/test/ruby/test_nomethod_error.rb +++ b/test/ruby/test_nomethod_error.rb @@ -78,7 +78,7 @@ def test_new_receiver assert_equal :foo, error.name assert_equal [1, 2], error.args assert_equal receiver, error.receiver - assert error.private_call?, "private_call? was false." + assert_predicate error, :private_call? end def test_message_encoding diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 9fa4dad41e2a86..def41d60175eb5 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1588,7 +1588,7 @@ def test_shareable_constant_value_simple assert_ractor_shareable(a) assert_not_ractor_shareable(obj) assert_equal obj, a - assert !obj.equal?(a) + assert_not_same obj, a bug_20339 = '[ruby-core:117186] [Bug #20339]' bug_20341 = '[ruby-core:117197] [Bug #20341]' diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index a2bdf02b88522f..661ba031413ba8 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -320,15 +320,15 @@ def test_self_stop # The parent should be notified about the stop _, status = Process.waitpid2(child_pid, Process::WUNTRACED) - assert status.stopped? + assert_predicate status, :stopped? # It can be continued Process.kill(:CONT, child_pid) # And the child then runs to completion _, status = Process.waitpid2(child_pid) - assert status.exited? - assert status.success? + assert_predicate status, :exited? + assert_predicate status, :success? end def test_sigwait_fd_unused diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb index 9485528977e599..9a41be8b1ac1a3 100644 --- a/test/ruby/test_thread_queue.rb +++ b/test/ruby/test_thread_queue.rb @@ -379,7 +379,7 @@ def test_close assert_equal false, q.closed? q << :something assert_equal q, q.close - assert q.closed? + assert_predicate q, :closed? assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing} assert_equal q.pop, :something assert_nil q.pop @@ -433,7 +433,7 @@ def test_sized_queue_one_closed_interrupt assert_equal 1, q.size assert_equal :one, q.pop - assert q.empty?, "queue not empty" + assert_empty q end # make sure that shutdown state is handled properly by empty? for the non-blocking case @@ -567,7 +567,7 @@ def test_blocked_pushers_empty assert_equal 0, q.size assert_equal 3, ary.size - ary.each{|e| assert [0,1,2,3,4,5].include?(e)} + ary.each{|e| assert_include [0,1,2,3,4,5], e} assert_nil q.pop prod_threads.each{|t| diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index 15e290728beadc..99b5ee8d43da94 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -2338,7 +2338,7 @@ def test_ractor_lazy_load_encoding r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end @@ -2357,7 +2357,7 @@ def test_ractor_lazy_load_encoding_random r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end @@ -2380,7 +2380,7 @@ def test_ractor_asciicompat_encoding_exists r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end @@ -2403,7 +2403,7 @@ def test_ractor_asciicompat_encoding_doesnt_exist r, _obj = Ractor.select(*rs) rs.delete(r) end - assert rs.empty? + assert_empty rs end; end From dd34d6273a08b821d049fd653db43b032674c7d8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 2 Jan 2026 09:26:54 +0900 Subject: [PATCH 2297/2435] Extract git version only The version message may contain other info such as the distribution. e.g.: ```console $ /usr/bin/git --version git version 2.50.1 (Apple Git-155) ``` --- tool/test/test_sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index f9ab0aae4def92..252687f3f35a58 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -324,7 +324,7 @@ def test_squash_merge # We don't know which exact version fixed it, but we know git 2.52.0 works. stdout, status = Open3.capture2('git', '--version', err: File::NULL) omit 'git version check failed' unless status.success? - git_version = stdout.rstrip.delete_prefix('git version ') + git_version = stdout[/\Agit version \K\S+/] omit "git #{git_version} is too old" if Gem::Version.new(git_version) < Gem::Version.new('2.44.0') # 2---. <- branch From 0ec5678cd55aa0f2d900d028497bb444c4ce4b7b Mon Sep 17 00:00:00 2001 From: git Date: Fri, 2 Jan 2026 06:55:45 +0000 Subject: [PATCH 2298/2435] Update bundled gems list as of 2026-01-02 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index e37e4ebf5b435a..02fef64f4cb5e5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -47,7 +47,7 @@ releases. ### The following bundled gems are updated. * minitest 6.0.1 -* test-unit 3.7.6 +* test-unit 3.7.7 * rss 0.3.2 * net-imap 0.6.2 * typeprof 0.31.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index f4afd8e9c27ac9..fcf63ab603ac6b 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 6.0.1 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.6 https://github.com/test-unit/test-unit +test-unit 3.7.7 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp From 11165495f4853752ee5e61dd941b9307c6dcf354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:05:33 +0000 Subject: [PATCH 2299/2435] [ruby/rubygems] Bump the rb-sys group across 2 directories with 1 update Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Updates `rb-sys` from 0.9.117 to 0.9.123 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.117...v0.9.123) Updates `rb-sys` from 0.9.117 to 0.9.123 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.117...v0.9.123) --- updated-dependencies: - dependency-name: rb-sys dependency-version: 0.9.123 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys - dependency-name: rb-sys dependency-version: 0.9.123 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys ... Signed-off-by: dependabot[bot] https://github.com/ruby/rubygems/commit/551c665b6b --- .../custom_name/ext/custom_name_lib/Cargo.lock | 8 ++++---- .../custom_name/ext/custom_name_lib/Cargo.toml | 2 +- .../rust_ruby_example/Cargo.lock | 8 ++++---- .../rust_ruby_example/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index 6302e9ee37b60a..a5051607c15957 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.117" +version = "0.9.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" +checksum = "45fb1a185af97ee456f1c9e56dbe6e2e662bec4fdeaf83c4c28e0e6adfb18816" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.117" +version = "0.9.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" +checksum = "a58ebd02d7a6033e6a5f6f8d150c1e9f16506039092b84a73e6bedce6d3adf41" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index e26fa352a81156..42ac3497a66b14 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.117" +rb-sys = "0.9.123" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 4bbe8932a2b8a4..efc85ffd5fcbf0 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.117" +version = "0.9.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" +checksum = "45fb1a185af97ee456f1c9e56dbe6e2e662bec4fdeaf83c4c28e0e6adfb18816" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.117" +version = "0.9.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" +checksum = "a58ebd02d7a6033e6a5f6f8d150c1e9f16506039092b84a73e6bedce6d3adf41" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index 55d5c5bde7f645..6972bb51750cdf 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.117" +rb-sys = "0.9.123" From 177949c8b28587a0e0c0709d726cef846bdc51aa Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 2 Jan 2026 12:57:00 +0100 Subject: [PATCH 2300/2435] Speedup Queue initialization Rather than to push items one by one we can directly memcpy. --- thread_sync.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 72341762399a8f..3227bab8435b1a 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -1043,12 +1043,8 @@ rb_queue_initialize(int argc, VALUE *argv, VALUE self) initial_capa *= 2; } ring_buffer_init(q, initial_capa); - - const VALUE *initial_ptr = RARRAY_CONST_PTR(initial); - - for (long index = 0; index < len; index++) { - ring_buffer_push(self, q, initial_ptr[index]); - } + MEMCPY(q->buffer, RARRAY_CONST_PTR(initial), VALUE, len); + q->len = len; } else { ring_buffer_init(q, QUEUE_INITIAL_CAPA); From b9819ad06c883e6ea4f9e903c5bab06f175efb62 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 1 Jan 2026 16:36:17 -0500 Subject: [PATCH 2301/2435] Register a dupped identity hash as pinning --- hash.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hash.c b/hash.c index c4e8512f64c458..b0de8e943355af 100644 --- a/hash.c +++ b/hash.c @@ -1497,6 +1497,10 @@ rb_hash_new_capa(long capa) static VALUE hash_copy(VALUE ret, VALUE hash) { + if (rb_hash_compare_by_id_p(hash)) { + rb_gc_register_pinning_obj(ret); + } + if (RHASH_AR_TABLE_P(hash)) { if (RHASH_AR_TABLE_P(ret)) { ar_copy(ret, hash); From 1596853428393136ee9964ad4c11b0120ed648d1 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 31 Dec 2025 14:52:16 +0100 Subject: [PATCH 2302/2435] Skip initializing optional arguments to `nil` They are optional because they have a default value, so I don't understand why we'd need to initialize them to nil. --- vm_args.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vm_args.c b/vm_args.c index 8d4042e35566ae..458710e59e3688 100644 --- a/vm_args.c +++ b/vm_args.c @@ -245,7 +245,6 @@ args_setup_opt_parameters(struct args_info *args, int opt_max, VALUE *locals) i = opt_max; } else { - int j; i = args->argc; args->argc = 0; @@ -257,11 +256,6 @@ args_setup_opt_parameters(struct args_info *args, int opt_max, VALUE *locals) locals[i] = argv[args->rest_index]; } } - - /* initialize by nil */ - for (j=i; j Date: Fri, 2 Jan 2026 10:47:55 +0100 Subject: [PATCH 2303/2435] Add a test case for complex argument forward reference Using `eval` it's possible to reference a later argument, and this requires careful initialization of the stack. --- test/ruby/test_call.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 1b30ed34d8826f..dd1936c4e2689e 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -123,6 +123,25 @@ def self.f(*a); a end assert_equal([1, 2, {kw: 3}], f(*a, kw: 3)) end + def test_forward_argument_init + o = Object.new + def o.simple_forward_argument_init(a=eval('b'), b=1) + [a, b] + end + + def o.complex_forward_argument_init(a=eval('b'), b=eval('kw'), kw: eval('kw2'), kw2: 3) + [a, b, kw, kw2] + end + + def o.keyword_forward_argument_init(a: eval('b'), b: eval('kw'), kw: eval('kw2'), kw2: 3) + [a, b, kw, kw2] + end + + assert_equal [nil, 1], o.simple_forward_argument_init + assert_equal [nil, nil, 3, 3], o.complex_forward_argument_init + assert_equal [nil, nil, 3, 3], o.keyword_forward_argument_init + end + def test_call_bmethod_proc pr = proc{|sym| sym} define_singleton_method(:a, &pr) From 31fb970c18734455c2a2e6eb93bdc67db1edb88c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 1 Jan 2026 09:04:15 -0500 Subject: [PATCH 2304/2435] [ruby/mmtk] Assert target is not pinned during normal tracing https://github.com/ruby/mmtk/commit/58210c88ed --- gc/mmtk/src/scanning.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gc/mmtk/src/scanning.rs b/gc/mmtk/src/scanning.rs index be834bdd0bde95..355a2e7759584a 100644 --- a/gc/mmtk/src/scanning.rs +++ b/gc/mmtk/src/scanning.rs @@ -4,6 +4,7 @@ use crate::upcalls; use crate::utils::ChunkedVecCollector; use crate::Ruby; use crate::RubySlot; +use mmtk::memory_manager; use mmtk::scheduler::GCWork; use mmtk::scheduler::GCWorker; use mmtk::scheduler::WorkBucketStage; @@ -53,6 +54,19 @@ impl Scanning for VMScanning { mmtk::memory_manager::is_mmtk_object(target_object.to_raw_address()).is_some(), "Destination is not an MMTk object. Src: {object} dst: {target_object}" ); + + debug_assert!( + // If we are in a moving GC, all objects should be pinned by PinningRegistry. + // If it is requested that target_object be pinned but it is not pinned, then + // it is a bug because it could be moved. + if crate::mmtk().get_plan().current_gc_may_move_object() && pin { + memory_manager::is_pinned(target_object) + } else { + true + }, + "Object {object} is trying to pin {target_object}" + ); + let forwarded_target = object_tracer.trace_object(target_object); if forwarded_target != target_object { trace!(" Forwarded target {target_object} -> {forwarded_target}"); From e7695ba3d9f0e8ee17025af4d42ecaf2dad47f29 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 2 Jan 2026 11:41:24 -0500 Subject: [PATCH 2305/2435] [ruby/mmtk] Check for T_NONE during marking https://github.com/ruby/mmtk/commit/c3e338bb25 --- gc/mmtk/mmtk.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index b532d3a774cca1..6da17fdb23b1e7 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -72,6 +72,8 @@ struct MMTk_final_job { #ifdef RB_THREAD_LOCAL_SPECIFIER RB_THREAD_LOCAL_SPECIFIER struct MMTk_GCThreadTLS *rb_mmtk_gc_thread_tls; + +RB_THREAD_LOCAL_SPECIFIER VALUE marking_parent_object; #else # error We currently need language-supported TLS #endif @@ -270,14 +272,18 @@ rb_mmtk_update_object_references(MMTk_ObjectReference mmtk_object) VALUE object = (VALUE)mmtk_object; if (!RB_FL_TEST(object, RUBY_FL_WEAK_REFERENCE)) { + marking_parent_object = object; rb_gc_update_object_references(rb_gc_get_objspace(), object); + marking_parent_object = 0; } } static void rb_mmtk_call_gc_mark_children(MMTk_ObjectReference object) { + marking_parent_object = (VALUE)object; rb_gc_mark_children(rb_gc_get_objspace(), (VALUE)object); + marking_parent_object = 0; } static void @@ -285,11 +291,15 @@ rb_mmtk_handle_weak_references(MMTk_ObjectReference mmtk_object, bool moving) { VALUE object = (VALUE)mmtk_object; + marking_parent_object = object; + rb_gc_handle_weak_references(object); if (moving) { rb_gc_update_object_references(rb_gc_get_objspace(), object); } + + marking_parent_object = 0; } static void @@ -797,6 +807,17 @@ void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff) { } static inline VALUE rb_mmtk_call_object_closure(VALUE obj, bool pin) { + if (RB_UNLIKELY(RB_BUILTIN_TYPE(obj) == T_NONE)) { + const size_t info_size = 256; + char obj_info_buf[info_size]; + rb_raw_obj_info(obj_info_buf, info_size, obj); + + char parent_obj_info_buf[info_size]; + rb_raw_obj_info(parent_obj_info_buf, info_size, marking_parent_object); + + rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf); + } + return (VALUE)rb_mmtk_gc_thread_tls->object_closure.c_function( rb_mmtk_gc_thread_tls->object_closure.rust_closure, rb_mmtk_gc_thread_tls->gc_context, From 16feb46fa27fdbdec4f7a0914787300b77fa232a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 2 Jan 2026 12:33:34 +0100 Subject: [PATCH 2306/2435] Convert Queue and SizedQueue to rb builtin A large part of `thread_sync.c` was migrated already, might as well go all the way. It also allow to remove a bunch of Rdoc commands. --- test/ruby/test_settracefunc.rb | 2 +- thread_sync.c | 515 ++------------------------------- thread_sync.rb | 344 +++++++++++++++++++++- 3 files changed, 363 insertions(+), 498 deletions(-) diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 776534a2b54770..d3b2441e21e7c3 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -2226,7 +2226,7 @@ def method_for_test_thread_add_trace_func def test_thread_add_trace_func events = [] base_line = __LINE__ - q = Thread::Queue.new + q = [] t = Thread.new{ Thread.current.add_trace_func proc{|ev, file, line, *args| events << [ev, line] if file == __FILE__ diff --git a/thread_sync.c b/thread_sync.c index 3227bab8435b1a..e3916c97cbd0a6 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -2,8 +2,7 @@ #include "ccan/list/list.h" #include "builtin.h" -static VALUE rb_cMutex, rb_cQueue, rb_cSizedQueue, rb_cConditionVariable; -static VALUE rb_eClosedQueueError; +static VALUE rb_cMutex, rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { @@ -83,30 +82,6 @@ static void rb_mutex_abandon_locking_mutex(rb_thread_t *th); #endif static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial); -/* - * Document-class: Thread::Mutex - * - * Thread::Mutex implements a simple semaphore that can be used to - * coordinate access to shared data from multiple concurrent threads. - * - * Example: - * - * semaphore = Thread::Mutex.new - * - * a = Thread.new { - * semaphore.synchronize { - * # access shared resource - * } - * } - * - * b = Thread.new { - * semaphore.synchronize { - * # access shared resource - * } - * } - * - */ - static size_t rb_mutex_num_waiting(rb_mutex_t *mutex) { @@ -759,19 +734,19 @@ queue_alloc(VALUE klass) return obj; } -static int +static inline bool queue_fork_check(struct rb_queue *q) { rb_serial_t fork_gen = GET_VM()->fork_gen; - if (q->fork_gen == fork_gen) { - return 0; + if (RB_LIKELY(q->fork_gen == fork_gen)) { + return false; } /* forked children can't reach into parent thread stacks */ q->fork_gen = fork_gen; ccan_list_head_init(&q->waitq); q->num_waiting = 0; - return 1; + return true; } static inline struct rb_queue * @@ -870,7 +845,7 @@ raw_szqueue_ptr(VALUE obj) struct rb_szqueue *sq; TypedData_Get_Struct(obj, struct rb_szqueue, &szqueue_data_type, sq); - if (queue_fork_check(&sq->q)) { + if (RB_UNLIKELY(queue_fork_check(&sq->q))) { ccan_list_head_init(szqueue_pushq(sq)); sq->num_waiting_push = 0; } @@ -886,7 +861,7 @@ szqueue_ptr(VALUE obj) return sq; } -static int +static inline bool queue_closed_p(VALUE self) { return FL_TEST_RAW(self, QUEUE_CLOSED) != 0; @@ -967,76 +942,16 @@ ring_buffer_shift(struct rb_queue *q) return obj; } -/* - * Document-class: Thread::Queue - * - * The Thread::Queue class implements multi-producer, multi-consumer - * queues. It is especially useful in threaded programming when - * information must be exchanged safely between multiple threads. The - * Thread::Queue class implements all the required locking semantics. - * - * The class implements FIFO (first in, first out) type of queue. - * In a FIFO queue, the first tasks added are the first retrieved. - * - * Example: - * - * queue = Thread::Queue.new - * - * producer = Thread.new do - * 5.times do |i| - * sleep rand(i) # simulate expense - * queue << i - * puts "#{i} produced" - * end - * end - * - * consumer = Thread.new do - * 5.times do |i| - * value = queue.pop - * sleep rand(i/2) # simulate expense - * puts "consumed #{value}" - * end - * end - * - * consumer.join - * - */ - -/* - * Document-method: Queue::new - * - * call-seq: - * Thread::Queue.new -> empty_queue - * Thread::Queue.new(enumerable) -> queue - * - * Creates a new queue instance, optionally using the contents of an +enumerable+ - * for its initial state. - * - * Example: - * - * q = Thread::Queue.new - * #=> # - * q.empty? - * #=> true - * - * q = Thread::Queue.new([1, 2, 3]) - * #=> # - * q.empty? - * #=> false - * q.pop - * #=> 1 - */ - static VALUE -rb_queue_initialize(int argc, VALUE *argv, VALUE self) +queue_initialize(rb_execution_context_t *ec, VALUE self, VALUE initial) { - VALUE initial; struct rb_queue *q = raw_queue_ptr(self); - if ((argc = rb_scan_args(argc, argv, "01", &initial)) == 1) { - initial = rb_to_array(initial); - } ccan_list_head_init(&q->waitq); - if (argc == 1) { + if (NIL_P(initial)) { + ring_buffer_init(q, QUEUE_INITIAL_CAPA); + } + else { + initial = rb_to_array(initial); long len = RARRAY_LEN(initial); long initial_capa = QUEUE_INITIAL_CAPA; while (initial_capa < len) { @@ -1046,9 +961,6 @@ rb_queue_initialize(int argc, VALUE *argv, VALUE self) MEMCPY(q->buffer, RARRAY_CONST_PTR(initial), VALUE, len); q->len = len; } - else { - ring_buffer_init(q, QUEUE_INITIAL_CAPA); - } return self; } @@ -1064,82 +976,6 @@ queue_do_push(VALUE self, struct rb_queue *q, VALUE obj) return self; } -/* - * Document-method: Thread::Queue#close - * call-seq: - * close - * - * Closes the queue. A closed queue cannot be re-opened. - * - * After the call to close completes, the following are true: - * - * - +closed?+ will return true - * - * - +close+ will be ignored. - * - * - calling enq/push/<< will raise a +ClosedQueueError+. - * - * - when +empty?+ is false, calling deq/pop/shift will return an object - * from the queue as usual. - * - when +empty?+ is true, deq(false) will not suspend the thread and will return nil. - * deq(true) will raise a +ThreadError+. - * - * ClosedQueueError is inherited from StopIteration, so that you can break loop block. - * - * Example: - * - * q = Thread::Queue.new - * Thread.new{ - * while e = q.deq # wait for nil to break loop - * # ... - * end - * } - * q.close - */ - -static VALUE -rb_queue_close(VALUE self) -{ - struct rb_queue *q = queue_ptr(self); - - if (!queue_closed_p(self)) { - FL_SET(self, QUEUE_CLOSED); - - wakeup_all(&q->waitq); - } - - return self; -} - -/* - * Document-method: Thread::Queue#closed? - * call-seq: closed? - * - * Returns +true+ if the queue is closed. - */ - -static VALUE -rb_queue_closed_p(VALUE self) -{ - return RBOOL(queue_closed_p(self)); -} - -/* - * Document-method: Thread::Queue#push - * call-seq: - * push(object) - * enq(object) - * <<(object) - * - * Pushes the given +object+ to the queue. - */ - -static VALUE -rb_queue_push(VALUE self, VALUE obj) -{ - return queue_do_push(self, queue_ptr(self), obj); -} - static VALUE queue_sleep(VALUE _args) { @@ -1231,19 +1067,6 @@ rb_queue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE time return queue_do_pop(ec, self, queue_ptr(self), non_block, timeout); } -/* - * Document-method: Thread::Queue#empty? - * call-seq: empty? - * - * Returns +true+ if the queue is empty. - */ - -static VALUE -rb_queue_empty_p(VALUE self) -{ - return RBOOL(queue_ptr(self)->len == 0); -} - static void queue_clear(struct rb_queue *q) { @@ -1251,87 +1074,12 @@ queue_clear(struct rb_queue *q) q->offset = 0; } -/* - * Document-method: Thread::Queue#clear - * - * Removes all objects from the queue. - */ - -static VALUE -rb_queue_clear(VALUE self) -{ - queue_clear(queue_ptr(self)); - return self; -} - -/* - * Document-method: Thread::Queue#length - * call-seq: - * length - * size - * - * Returns the length of the queue. - */ - -static VALUE -rb_queue_length(VALUE self) -{ - return LONG2NUM(queue_ptr(self)->len); -} - -NORETURN(static VALUE rb_queue_freeze(VALUE self)); -/* - * call-seq: - * freeze - * - * The queue can't be frozen, so this method raises an exception: - * Thread::Queue.new.freeze # Raises TypeError (cannot freeze #) - * - */ -static VALUE -rb_queue_freeze(VALUE self) -{ - rb_raise(rb_eTypeError, "cannot freeze " "%+"PRIsVALUE, self); - UNREACHABLE_RETURN(self); -} - -/* - * Document-method: Thread::Queue#num_waiting - * - * Returns the number of threads waiting on the queue. - */ - -static VALUE -rb_queue_num_waiting(VALUE self) -{ - struct rb_queue *q = queue_ptr(self); - - return INT2NUM(q->num_waiting); -} - -/* - * Document-class: Thread::SizedQueue - * - * This class represents queues of specified size capacity. The push operation - * may be blocked if the capacity is full. - * - * See Thread::Queue for an example of how a Thread::SizedQueue works. - */ - -/* - * Document-method: SizedQueue::new - * call-seq: new(max) - * - * Creates a fixed-length queue with a maximum size of +max+. - */ - static VALUE -rb_szqueue_initialize(VALUE self, VALUE vmax) +szqueue_initialize(rb_execution_context_t *ec, VALUE self, VALUE vmax) { - long max; + long max = NUM2LONG(vmax); struct rb_szqueue *sq = raw_szqueue_ptr(self); - max = NUM2LONG(vmax); if (max <= 0) { rb_raise(rb_eArgError, "queue size must be positive"); } @@ -1343,68 +1091,6 @@ rb_szqueue_initialize(VALUE self, VALUE vmax) return self; } -/* - * Document-method: Thread::SizedQueue#close - * call-seq: - * close - * - * Similar to Thread::Queue#close. - * - * The difference is behavior with waiting enqueuing threads. - * - * If there are waiting enqueuing threads, they are interrupted by - * raising ClosedQueueError('queue closed'). - */ -static VALUE -rb_szqueue_close(VALUE self) -{ - if (!queue_closed_p(self)) { - struct rb_szqueue *sq = szqueue_ptr(self); - - FL_SET(self, QUEUE_CLOSED); - wakeup_all(szqueue_waitq(sq)); - wakeup_all(szqueue_pushq(sq)); - } - return self; -} - -/* - * Document-method: Thread::SizedQueue#max - * - * Returns the maximum size of the queue. - */ - -static VALUE -rb_szqueue_max_get(VALUE self) -{ - return LONG2NUM(szqueue_ptr(self)->max); -} - -/* - * Document-method: Thread::SizedQueue#max= - * call-seq: max=(number) - * - * Sets the maximum size of the queue to the given +number+. - */ - -static VALUE -rb_szqueue_max_set(VALUE self, VALUE vmax) -{ - long max = NUM2LONG(vmax); - long diff = 0; - struct rb_szqueue *sq = szqueue_ptr(self); - - if (max <= 0) { - rb_raise(rb_eArgError, "queue size must be positive"); - } - if (max > sq->max) { - diff = max - sq->max; - } - sq->max = max; - sync_wakeup(szqueue_pushq(sq), diff); - return vmax; -} - static VALUE rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_block, VALUE timeout) { @@ -1464,124 +1150,12 @@ rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE ti return retval; } -/* - * Document-method: Thread::SizedQueue#clear - * - * Removes all objects from the queue. - */ - -static VALUE -rb_szqueue_clear(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - queue_clear(&sq->q); - wakeup_all(szqueue_pushq(sq)); - return self; -} - -/* - * Document-method: Thread::SizedQueue#num_waiting - * - * Returns the number of threads waiting on the queue. - */ - -static VALUE -rb_szqueue_num_waiting(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - - return INT2NUM(sq->q.num_waiting + sq->num_waiting_push); -} - - /* ConditionalVariable */ struct rb_condvar { struct ccan_list_head waitq; rb_serial_t fork_gen; }; -/* - * Document-class: Thread::ConditionVariable - * - * ConditionVariable objects augment class Mutex. Using condition variables, - * it is possible to suspend while in the middle of a critical section until a - * condition is met, such as a resource becomes available. - * - * Due to non-deterministic scheduling and spurious wake-ups, users of - * condition variables should always use a separate boolean predicate (such as - * reading from a boolean variable) to check if the condition is actually met - * before starting to wait, and should wait in a loop, re-checking the - * condition every time the ConditionVariable is waken up. The idiomatic way - * of using condition variables is calling the +wait+ method in an +until+ - * loop with the predicate as the loop condition. - * - * condvar.wait(mutex) until condition_is_met - * - * In the example below, we use the boolean variable +resource_available+ - * (which is protected by +mutex+) to indicate the availability of the - * resource, and use +condvar+ to wait for that variable to become true. Note - * that: - * - * 1. Thread +b+ may be scheduled before thread +a1+ and +a2+, and may run so - * fast that it have already made the resource available before either - * +a1+ or +a2+ starts. Therefore, +a1+ and +a2+ should check if - * +resource_available+ is already true before starting to wait. - * 2. The +wait+ method may spuriously wake up without signalling. Therefore, - * thread +a1+ and +a2+ should recheck +resource_available+ after the - * +wait+ method returns, and go back to wait if the condition is not - * actually met. - * 3. It is possible that thread +a2+ starts right after thread +a1+ is waken - * up by +b+. Thread +a2+ may have acquired the +mutex+ and consumed the - * resource before thread +a1+ acquires the +mutex+. This necessitates - * rechecking after +wait+, too. - * - * Example: - * - * mutex = Thread::Mutex.new - * - * resource_available = false - * condvar = Thread::ConditionVariable.new - * - * a1 = Thread.new { - * # Thread 'a1' waits for the resource to become available and consumes - * # the resource. - * mutex.synchronize { - * condvar.wait(mutex) until resource_available - * # After the loop, 'resource_available' is guaranteed to be true. - * - * resource_available = false - * puts "a1 consumed the resource" - * } - * } - * - * a2 = Thread.new { - * # Thread 'a2' behaves like 'a1'. - * mutex.synchronize { - * condvar.wait(mutex) until resource_available - * resource_available = false - * puts "a2 consumed the resource" - * } - * } - * - * b = Thread.new { - * # Thread 'b' periodically makes the resource available. - * loop { - * mutex.synchronize { - * resource_available = true - * - * # Notify one waiting thread if any. It is possible that neither - * # 'a1' nor 'a2 is waiting on 'condvar' at this moment. That's OK. - * condvar.signal - * } - * sleep 1 - * } - * } - * - * # Eventually both 'a1' and 'a2' will have their resources, albeit in an - * # unspecified order. - * [a1, a2].each {|th| th.join} - */ - static size_t condvar_memsize(const void *ptr) { @@ -1679,75 +1253,24 @@ rb_condvar_broadcast(rb_execution_context_t *ec, VALUE self) return self; } -NORETURN(static VALUE undumpable(VALUE obj)); -/* :nodoc: */ -static VALUE -undumpable(VALUE obj) -{ - rb_raise(rb_eTypeError, "can't dump %"PRIsVALUE, rb_obj_class(obj)); - UNREACHABLE_RETURN(Qnil); -} - -static VALUE -define_thread_class(VALUE outer, const ID name, VALUE super) -{ - VALUE klass = rb_define_class_id_under(outer, name, super); - rb_const_set(rb_cObject, name, klass); - return klass; -} - static void Init_thread_sync(void) { -#undef rb_intern -#if defined(TEACH_RDOC) && TEACH_RDOC == 42 - rb_cMutex = rb_define_class_under(rb_cThread, "Mutex", rb_cObject); - rb_cConditionVariable = rb_define_class_under(rb_cThread, "ConditionVariable", rb_cObject); - rb_cQueue = rb_define_class_under(rb_cThread, "Queue", rb_cObject); - rb_cSizedQueue = rb_define_class_under(rb_cThread, "SizedQueue", rb_cObject); -#endif - -#define DEFINE_CLASS(name, super) \ - rb_c##name = define_thread_class(rb_cThread, rb_intern(#name), rb_c##super) - /* Mutex */ - DEFINE_CLASS(Mutex, Object); + rb_cMutex = rb_define_class_id_under(rb_cThread, rb_intern("Mutex"), rb_cObject); rb_define_alloc_func(rb_cMutex, mutex_alloc); /* Queue */ - DEFINE_CLASS(Queue, Object); + VALUE rb_cQueue = rb_define_class_id_under_no_pin(rb_cThread, rb_intern("Queue"), rb_cObject); rb_define_alloc_func(rb_cQueue, queue_alloc); rb_eClosedQueueError = rb_define_class("ClosedQueueError", rb_eStopIteration); - rb_define_method(rb_cQueue, "initialize", rb_queue_initialize, -1); - rb_undef_method(rb_cQueue, "initialize_copy"); - rb_define_method(rb_cQueue, "marshal_dump", undumpable, 0); - rb_define_method(rb_cQueue, "close", rb_queue_close, 0); - rb_define_method(rb_cQueue, "closed?", rb_queue_closed_p, 0); - rb_define_method(rb_cQueue, "push", rb_queue_push, 1); - rb_define_method(rb_cQueue, "empty?", rb_queue_empty_p, 0); - rb_define_method(rb_cQueue, "clear", rb_queue_clear, 0); - rb_define_method(rb_cQueue, "length", rb_queue_length, 0); - rb_define_method(rb_cQueue, "num_waiting", rb_queue_num_waiting, 0); - rb_define_method(rb_cQueue, "freeze", rb_queue_freeze, 0); - - rb_define_alias(rb_cQueue, "enq", "push"); - rb_define_alias(rb_cQueue, "<<", "push"); - rb_define_alias(rb_cQueue, "size", "length"); - - DEFINE_CLASS(SizedQueue, Queue); + VALUE rb_cSizedQueue = rb_define_class_id_under_no_pin(rb_cThread, rb_intern("SizedQueue"), rb_cQueue); rb_define_alloc_func(rb_cSizedQueue, szqueue_alloc); - rb_define_method(rb_cSizedQueue, "initialize", rb_szqueue_initialize, 1); - rb_define_method(rb_cSizedQueue, "close", rb_szqueue_close, 0); - rb_define_method(rb_cSizedQueue, "max", rb_szqueue_max_get, 0); - rb_define_method(rb_cSizedQueue, "max=", rb_szqueue_max_set, 1); - rb_define_method(rb_cSizedQueue, "clear", rb_szqueue_clear, 0); - rb_define_method(rb_cSizedQueue, "num_waiting", rb_szqueue_num_waiting, 0); - /* CVar */ - DEFINE_CLASS(ConditionVariable, Object); + VALUE rb_cConditionVariable = rb_define_class_id_under_no_pin(rb_cThread, rb_intern("ConditionVariable"), rb_cObject); rb_define_alloc_func(rb_cConditionVariable, condvar_alloc); id_sleep = rb_intern("sleep"); diff --git a/thread_sync.rb b/thread_sync.rb index a722b7ec1a2595..398c0d02b7b75a 100644 --- a/thread_sync.rb +++ b/thread_sync.rb @@ -1,7 +1,62 @@ # frozen_string_literal: true class Thread + # The Thread::Queue class implements multi-producer, multi-consumer + # queues. It is especially useful in threaded programming when + # information must be exchanged safely between multiple threads. The + # Thread::Queue class implements all the required locking semantics. + # + # The class implements FIFO (first in, first out) type of queue. + # In a FIFO queue, the first tasks added are the first retrieved. + # + # Example: + # + # queue = Thread::Queue.new + # + # producer = Thread.new do + # 5.times do |i| + # sleep rand(i) # simulate expense + # queue << i + # puts "#{i} produced" + # end + # end + # + # consumer = Thread.new do + # 5.times do |i| + # value = queue.pop + # sleep rand(i/2) # simulate expense + # puts "consumed #{value}" + # end + # end + # + # consumer.join class Queue + # Document-method: Queue::new + # + # call-seq: + # Thread::Queue.new -> empty_queue + # Thread::Queue.new(enumerable) -> queue + # + # Creates a new queue instance, optionally using the contents of an +enumerable+ + # for its initial state. + # + # Example: + # + # q = Thread::Queue.new + # #=> # + # q.empty? + # #=> true + # + # q = Thread::Queue.new([1, 2, 3]) + # #=> # + # q.empty? + # #=> false + # q.pop + # #=> 1 + def initialize(enumerable = nil) + Primitive.queue_initialize(enumerable) + end + # call-seq: # pop(non_block=false, timeout: nil) # @@ -21,9 +76,129 @@ def pop(non_block = false, timeout: nil) end alias_method :deq, :pop alias_method :shift, :pop + + undef_method :initialize_copy + + # call-seq: + # push(object) + # enq(object) + # <<(object) + # + # Pushes the given +object+ to the queue. + def push(object) + Primitive.cexpr!('queue_do_push(self, queue_ptr(self), object)') + end + alias_method :enq, :push + alias_method :<<, :push + + # call-seq: + # close + # + # Closes the queue. A closed queue cannot be re-opened. + # + # After the call to close completes, the following are true: + # + # - +closed?+ will return true + # + # - +close+ will be ignored. + # + # - calling enq/push/<< will raise a +ClosedQueueError+. + # + # - when +empty?+ is false, calling deq/pop/shift will return an object + # from the queue as usual. + # - when +empty?+ is true, deq(false) will not suspend the thread and will return nil. + # deq(true) will raise a +ThreadError+. + # + # ClosedQueueError is inherited from StopIteration, so that you can break loop block. + # + # Example: + # + # q = Thread::Queue.new + # Thread.new{ + # while e = q.deq # wait for nil to break loop + # # ... + # end + # } + # q.close + def close + Primitive.cstmt! %{ + if (!queue_closed_p(self)) { + FL_SET_RAW(self, QUEUE_CLOSED); + + wakeup_all(&queue_ptr(self)->waitq); + } + + return self; + } + end + + # call-seq: closed? + # + # Returns +true+ if the queue is closed. + def closed? + Primitive.cexpr!('RBOOL(FL_TEST_RAW(self, QUEUE_CLOSED))') + end + + # call-seq: + # length + # size + # + # Returns the length of the queue. + def length + Primitive.cexpr!('LONG2NUM(queue_ptr(self)->len)') + end + alias_method :size, :length + + # call-seq: empty? + # + # Returns +true+ if the queue is empty. + def empty? + Primitive.cexpr!('RBOOL(queue_ptr(self)->len == 0)') + end + + # Removes all objects from the queue. + def clear + Primitive.cstmt! %{ + queue_clear(queue_ptr(self)); + return self; + } + end + + # call-seq: + # num_waiting + # + # Returns the number of threads waiting on the queue. + def num_waiting + Primitive.cexpr!('INT2NUM(queue_ptr(self)->num_waiting)') + end + + def marshal_dump # :nodoc: + raise TypeError, "can't dump #{self.class}" + end + + # call-seq: + # freeze + # + # The queue can't be frozen, so this method raises an exception: + # Thread::Queue.new.freeze # Raises TypeError (cannot freeze #) + def freeze + raise TypeError, "cannot freeze #{self}" + end end - class SizedQueue + # This class represents queues of specified size capacity. The push operation + # may be blocked if the capacity is full. + # + # See Thread::Queue for an example of how a Thread::SizedQueue works. + class SizedQueue < Queue + # Document-method: SizedQueue::new + # call-seq: new(max) + # + # Creates a fixed-length queue with a maximum size of +max+. + def initialize(vmax) + Primitive.szqueue_initialize(vmax) + end + # call-seq: # pop(non_block=false, timeout: nil) # @@ -66,8 +241,93 @@ def push(object, non_block = false, timeout: nil) end alias_method :enq, :push alias_method :<<, :push + + # call-seq: + # close + # + # Similar to Thread::Queue#close. + # + # The difference is behavior with waiting enqueuing threads. + # + # If there are waiting enqueuing threads, they are interrupted by + # raising ClosedQueueError('queue closed'). + def close + Primitive.cstmt! %{ + if (!queue_closed_p(self)) { + struct rb_szqueue *sq = szqueue_ptr(self); + + FL_SET(self, QUEUE_CLOSED); + wakeup_all(szqueue_waitq(sq)); + wakeup_all(szqueue_pushq(sq)); + } + return self; + } + end + + # Removes all objects from the queue. + def clear + Primitive.cstmt! %{ + struct rb_szqueue *sq = szqueue_ptr(self); + queue_clear(&sq->q); + wakeup_all(szqueue_pushq(sq)); + return self; + } + end + + # Returns the number of threads waiting on the queue. + def num_waiting + Primitive.cstmt! %{ + struct rb_szqueue *sq = szqueue_ptr(self); + return INT2NUM(sq->q.num_waiting + sq->num_waiting_push); + } + end + + # Returns the maximum size of the queue. + def max + Primitive.cexpr!('LONG2NUM(szqueue_ptr(self)->max)') + end + + # call-seq: max=(number) + # + # Sets the maximum size of the queue to the given +number+. + def max=(vmax) + Primitive.cstmt! %{ + long max = NUM2LONG(vmax); + if (max <= 0) { + rb_raise(rb_eArgError, "queue size must be positive"); + } + + long diff = 0; + struct rb_szqueue *sq = szqueue_ptr(self); + + if (max > sq->max) { + diff = max - sq->max; + } + sq->max = max; + sync_wakeup(szqueue_pushq(sq), diff); + return vmax; + } + end end + # Thread::Mutex implements a simple semaphore that can be used to + # coordinate access to shared data from multiple concurrent threads. + # + # Example: + # + # semaphore = Thread::Mutex.new + # + # a = Thread.new { + # semaphore.synchronize { + # # access shared resource + # } + # } + # + # b = Thread.new { + # semaphore.synchronize { + # # access shared resource + # } + # } class Mutex # call-seq: # Thread::Mutex.new -> mutex @@ -149,6 +409,83 @@ def sleep(timeout = nil) end end + # ConditionVariable objects augment class Mutex. Using condition variables, + # it is possible to suspend while in the middle of a critical section until a + # condition is met, such as a resource becomes available. + # + # Due to non-deterministic scheduling and spurious wake-ups, users of + # condition variables should always use a separate boolean predicate (such as + # reading from a boolean variable) to check if the condition is actually met + # before starting to wait, and should wait in a loop, re-checking the + # condition every time the ConditionVariable is waken up. The idiomatic way + # of using condition variables is calling the +wait+ method in an +until+ + # loop with the predicate as the loop condition. + # + # condvar.wait(mutex) until condition_is_met + # + # In the example below, we use the boolean variable +resource_available+ + # (which is protected by +mutex+) to indicate the availability of the + # resource, and use +condvar+ to wait for that variable to become true. Note + # that: + # + # 1. Thread +b+ may be scheduled before thread +a1+ and +a2+, and may run so + # fast that it have already made the resource available before either + # +a1+ or +a2+ starts. Therefore, +a1+ and +a2+ should check if + # +resource_available+ is already true before starting to wait. + # 2. The +wait+ method may spuriously wake up without signalling. Therefore, + # thread +a1+ and +a2+ should recheck +resource_available+ after the + # +wait+ method returns, and go back to wait if the condition is not + # actually met. + # 3. It is possible that thread +a2+ starts right after thread +a1+ is waken + # up by +b+. Thread +a2+ may have acquired the +mutex+ and consumed the + # resource before thread +a1+ acquires the +mutex+. This necessitates + # rechecking after +wait+, too. + # + # Example: + # + # mutex = Thread::Mutex.new + # + # resource_available = false + # condvar = Thread::ConditionVariable.new + # + # a1 = Thread.new { + # # Thread 'a1' waits for the resource to become available and consumes + # # the resource. + # mutex.synchronize { + # condvar.wait(mutex) until resource_available + # # After the loop, 'resource_available' is guaranteed to be true. + # + # resource_available = false + # puts "a1 consumed the resource" + # } + # } + # + # a2 = Thread.new { + # # Thread 'a2' behaves like 'a1'. + # mutex.synchronize { + # condvar.wait(mutex) until resource_available + # resource_available = false + # puts "a2 consumed the resource" + # } + # } + # + # b = Thread.new { + # # Thread 'b' periodically makes the resource available. + # loop { + # mutex.synchronize { + # resource_available = true + # + # # Notify one waiting thread if any. It is possible that neither + # # 'a1' nor 'a2 is waiting on 'condvar' at this moment. That's OK. + # condvar.signal + # } + # sleep 1 + # } + # } + # + # # Eventually both 'a1' and 'a2' will have their resources, albeit in an + # # unspecified order. + # [a1, a2].each {|th| th.join} class ConditionVariable # Document-method: ConditionVariable::new # @@ -193,3 +530,8 @@ def wait(mutex, timeout=nil) end end end + +Mutex = Thread::Mutex +ConditionVariable = Thread::ConditionVariable +Queue = Thread::Queue +SizedQueue = Thread::SizedQueue From 60d9b10dab9c9e92518f5579e6d36006c0dd359d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 2 Jan 2026 16:36:05 -0500 Subject: [PATCH 2307/2435] [ruby/mmtk] Propagate crash of GC thread to mutator thread This allows the mutator thread to dump its backtrace when a GC thread crashes. https://github.com/ruby/mmtk/commit/40ff9ffee7 --- gc/mmtk/mmtk.c | 36 ++++++++++++++++++++++++++++++++++++ gc/mmtk/mmtk.h | 1 + gc/mmtk/src/abi.rs | 1 + gc/mmtk/src/lib.rs | 4 ++-- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 6da17fdb23b1e7..fc1fa8bd2eb865 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -38,6 +38,11 @@ struct objspace { pthread_cond_t cond_world_started; size_t start_the_world_count; + struct { + bool gc_thread_crashed; + char crash_msg[256]; + } crash_context; + struct rb_gc_vm_context vm_context; unsigned int fork_hook_vm_lock_lev; @@ -169,6 +174,10 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) pthread_cond_wait(&objspace->cond_world_started, &objspace->mutex); } + if (RB_UNLIKELY(objspace->crash_context.gc_thread_crashed)) { + rb_bug("%s", objspace->crash_context.crash_msg); + } + if (objspace->measure_gc_time) { struct timespec gc_end_time; clock_gettime(CLOCK_MONOTONIC, &gc_end_time); @@ -424,6 +433,32 @@ rb_mmtk_special_const_p(MMTk_ObjectReference object) return RB_SPECIAL_CONST_P(obj); } +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 1, 2) +static void +rb_mmtk_gc_thread_bug(const char *msg, ...) +{ + struct objspace *objspace = rb_gc_get_objspace(); + + objspace->crash_context.gc_thread_crashed = true; + + va_list args; + va_start(args, msg); + vsnprintf(objspace->crash_context.crash_msg, sizeof(objspace->crash_context.crash_msg), msg, args); + va_end(args); + + rb_mmtk_resume_mutators(); + + sleep(5); + + rb_bug("rb_mmtk_gc_thread_bug"); +} + +static void +rb_mmtk_gc_thread_panic_handler(void) +{ + rb_mmtk_gc_thread_bug("MMTk GC thread panicked"); +} + static void rb_mmtk_mutator_thread_panic_handler(void) { @@ -454,6 +489,7 @@ MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_update_finalizer_table, rb_mmtk_special_const_p, rb_mmtk_mutator_thread_panic_handler, + rb_mmtk_gc_thread_panic_handler, }; // Use max 80% of the available memory by default for MMTk diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index f7da0f95f0214d..b7c8248718526a 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -79,6 +79,7 @@ typedef struct MMTk_RubyUpcalls { void (*update_finalizer_table)(void); bool (*special_const_p)(MMTk_ObjectReference object); void (*mutator_thread_panic_handler)(void); + void (*gc_thread_panic_handler)(void); } MMTk_RubyUpcalls; typedef struct MMTk_RawVecOfObjRef { diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 1e03dbf2f98809..255b2b1e5658be 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -323,6 +323,7 @@ pub struct RubyUpcalls { pub update_finalizer_table: extern "C" fn(), pub special_const_p: extern "C" fn(object: ObjectReference) -> bool, pub mutator_thread_panic_handler: extern "C" fn(), + pub gc_thread_panic_handler: extern "C" fn(), } unsafe impl Sync for RubyUpcalls {} diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index 0ee8f6752e5778..52dc782051f741 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -126,8 +126,6 @@ fn handle_gc_thread_panic(panic_info: &PanicHookInfo) { eprintln!("Unknown backtrace status: {s:?}"); } } - - std::process::abort(); } pub(crate) fn set_panic_hook() { @@ -140,6 +138,8 @@ pub(crate) fn set_panic_hook() { std::panic::set_hook(Box::new(move |panic_info| { if is_gc_thread(std::thread::current().id()) { handle_gc_thread_panic(panic_info); + + (crate::binding().upcalls().gc_thread_panic_handler)(); } else { old_hook(panic_info); (crate::MUTATOR_THREAD_PANIC_HANDLER From 2f4119eaea5416ae69c9256a72ab98237e221b91 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 2 Jan 2026 16:37:25 -0500 Subject: [PATCH 2308/2435] [ruby/mmtk] Use rb_mmtk_gc_thread_bug for rb_mmtk_call_object_closure https://github.com/ruby/mmtk/commit/308936296a --- gc/mmtk/mmtk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index fc1fa8bd2eb865..2c447b0d389f65 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -851,7 +851,7 @@ rb_mmtk_call_object_closure(VALUE obj, bool pin) char parent_obj_info_buf[info_size]; rb_raw_obj_info(parent_obj_info_buf, info_size, marking_parent_object); - rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf); + rb_mmtk_gc_thread_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf); } return (VALUE)rb_mmtk_gc_thread_tls->object_closure.c_function( From a8a989b6f651b878c690f5fd0a728e19a54dd2b9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 3 Jan 2026 12:28:00 +0900 Subject: [PATCH 2309/2435] Test net-imap with ruby/net-imap#593 Delete test/net/imap/test_data_lite.rb, because the target of this test file has been deleted by [ruby/net-imap#543]. [ruby/net-imap#543]: https://github.com/ruby/net-imap/pull/593 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index fcf63ab603ac6b..e709d57b793039 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,7 +13,7 @@ test-unit 3.7.7 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp -net-imap 0.6.2 https://github.com/ruby/net-imap +net-imap 0.6.2 https://github.com/ruby/net-imap d9ae35ef913a45f83387b8444cdce4fb1cbf01af net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix From d7a6ff8224519005d2deeb3f4e98689a8a0835ad Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 3 Jan 2026 15:03:57 +0900 Subject: [PATCH 2310/2435] [Bug #21819] Data objects without members should also be frozen --- struct.c | 1 + test/ruby/test_data.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/struct.c b/struct.c index 667d35424fb8d1..31df3798cbcb4b 100644 --- a/struct.c +++ b/struct.c @@ -1809,6 +1809,7 @@ rb_data_initialize_m(int argc, const VALUE *argv, VALUE self) if (num_members > 0) { rb_exc_raise(rb_keyword_error_new("missing", members)); } + OBJ_FREEZE(self); return Qnil; } if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) { diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb index dd698fdcc4a30e..5ac4c6b84b66aa 100644 --- a/test/ruby/test_data.rb +++ b/test/ruby/test_data.rb @@ -262,6 +262,7 @@ def test_memberless assert_equal('#', test.inspect) assert_equal([], test.members) assert_equal({}, test.to_h) + assert_predicate(test, :frozen?) end def test_dup From b6463d59e7c52dd4493796ff1cbd10b5e25caaca Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Fri, 2 Jan 2026 20:47:59 -0600 Subject: [PATCH 2311/2435] [ruby/json] Directly write to the output buffer when converting UTF32 to UTF8. https://github.com/ruby/json/commit/a51317c949 --- ext/json/parser/parser.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 0ac16918f876b9..f1ea1b6abbfdc3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -739,9 +739,7 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser } } - char buf[4]; - int unescape_len = convert_UTF32_to_UTF8(buf, ch); - MEMCPY(buffer, buf, char, unescape_len); + int unescape_len = convert_UTF32_to_UTF8(buffer, ch); buffer += unescape_len; p = ++pe; break; From 65f9c4a06a17368c452bcda7f0e6325ea1d51eba Mon Sep 17 00:00:00 2001 From: Shannon Skipper Date: Thu, 1 Jan 2026 18:15:11 -0800 Subject: [PATCH 2312/2435] Drop memberless Data/Struct#inspect trailing space Anonymous memberless Structs and Data were returning `#` and `#` with a trailing space. Now they return `#` and `#` to match attrless class behavior and look a bit more compact. --- struct.c | 7 ++++--- test/ruby/test_data.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/struct.c b/struct.c index 31df3798cbcb4b..a438ddd6136092 100644 --- a/struct.c +++ b/struct.c @@ -981,6 +981,7 @@ inspect_struct(VALUE s, VALUE prefix, int recur) char first = RSTRING_PTR(cname)[0]; if (recur || first != '#') { + rb_str_cat2(str, " "); rb_str_append(str, cname); } if (recur) { @@ -997,7 +998,7 @@ inspect_struct(VALUE s, VALUE prefix, int recur) if (i > 0) { rb_str_cat2(str, ", "); } - else if (first != '#') { + else { rb_str_cat2(str, " "); } slot = RARRAY_AREF(members, i); @@ -1031,7 +1032,7 @@ inspect_struct(VALUE s, VALUE prefix, int recur) static VALUE rb_struct_inspect(VALUE s) { - return rb_exec_recursive(inspect_struct, s, rb_str_new2("#', test.inspect) + assert_equal('#', test.inspect) assert_equal([], test.members) assert_equal({}, test.to_h) assert_predicate(test, :frozen?) From 5b87294d2fc84b7039a703cbbd02c7cf50c5b560 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 3 Jan 2026 09:27:15 -0500 Subject: [PATCH 2313/2435] Add rb_gc_print_backtrace --- gc.c | 8 ++++++++ gc/gc.h | 1 + 2 files changed, 9 insertions(+) diff --git a/gc.c b/gc.c index f77ee417c52dea..98e5712ca40214 100644 --- a/gc.c +++ b/gc.c @@ -129,6 +129,14 @@ #include "builtin.h" #include "shape.h" +// TODO: Don't export this function in modular GC, instead MMTk should figure out +// how to combine GC thread backtrace with mutator thread backtrace. +void +rb_gc_print_backtrace(void) +{ + rb_print_backtrace(stderr); +} + unsigned int rb_gc_vm_lock(const char *file, int line) { diff --git a/gc/gc.h b/gc/gc.h index a5edc266e75b1a..097ddb93949a0b 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -108,6 +108,7 @@ MODULAR_GC_FN void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context) MODULAR_GC_FN void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context); MODULAR_GC_FN void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context); MODULAR_GC_FN void rb_gc_move_obj_during_marking(VALUE from, VALUE to); +MODULAR_GC_FN void rb_gc_print_backtrace(); #endif #if USE_MODULAR_GC From a0c483fcfb9b8a2009cf21a8bce5fa2ad54d4fda Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 3 Jan 2026 09:51:00 -0500 Subject: [PATCH 2314/2435] Also output GC thread backtrace in rb_mmtk_gc_thread_bug --- gc/mmtk/mmtk.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 2c447b0d389f65..e9558f906f3242 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -446,6 +446,11 @@ rb_mmtk_gc_thread_bug(const char *msg, ...) vsnprintf(objspace->crash_context.crash_msg, sizeof(objspace->crash_context.crash_msg), msg, args); va_end(args); + fprintf(stderr, "-- GC thread backtrace " + "-------------------------------------------\n"); + rb_gc_print_backtrace(); + fprintf(stderr, "\n"); + rb_mmtk_resume_mutators(); sleep(5); From 5064af7ed120b1d14ad5020ea39a4a6ff2a4a4ec Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 3 Jan 2026 09:39:54 -0500 Subject: [PATCH 2315/2435] [ruby/mmtk] Process obj_free candidates in parallel Redos commit 544770d which seems to have accidentally been undone in b27d935. --- gc/mmtk/mmtk.c | 23 ++++++- gc/mmtk/mmtk.h | 2 +- gc/mmtk/src/api.rs | 11 +++- gc/mmtk/src/weak_proc.rs | 135 ++++++++++++++++++++++++++++----------- 4 files changed, 127 insertions(+), 44 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e9558f906f3242..042517e7d741b6 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -747,6 +747,27 @@ rb_mmtk_alloc_fast_path(struct objspace *objspace, struct MMTk_ractor_cache *rac } } +static bool +obj_can_parallel_free_p(VALUE obj) +{ + switch (RB_BUILTIN_TYPE(obj)) { + case T_ARRAY: + case T_BIGNUM: + case T_COMPLEX: + case T_FLOAT: + case T_HASH: + case T_OBJECT: + case T_RATIONAL: + case T_REGEXP: + case T_STRING: + case T_STRUCT: + case T_SYMBOL: + return true; + default: + return false; + } +} + VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -783,7 +804,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called - mmtk_add_obj_free_candidate(alloc_obj); + mmtk_add_obj_free_candidate(alloc_obj, obj_can_parallel_free_p((VALUE)alloc_obj)); objspace->total_allocated_objects++; diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index b7c8248718526a..21a5bf94156587 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -123,7 +123,7 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, size_t bytes, MMTk_AllocationSemantics semantics); -void mmtk_add_obj_free_candidate(MMTk_ObjectReference object); +void mmtk_add_obj_free_candidate(MMTk_ObjectReference object, bool can_parallel_free); void mmtk_declare_weak_references(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index ec0e5bafe2289a..3515a2408b3714 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -198,7 +198,10 @@ pub unsafe extern "C" fn mmtk_init_binding( let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); - let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); + let mut binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); + binding + .weak_proc + .init_parallel_obj_free_candidates(memory_manager::num_of_workers(binding.mmtk)); crate::BINDING .set(binding) @@ -296,8 +299,10 @@ pub unsafe extern "C" fn mmtk_post_alloc( // TODO: Replace with buffered mmtk_add_obj_free_candidates #[no_mangle] -pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference) { - binding().weak_proc.add_obj_free_candidate(object) +pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference, can_parallel_free: bool) { + binding() + .weak_proc + .add_obj_free_candidate(object, can_parallel_free) } // =============== Weak references =============== diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index c8ad944633bd3a..19dc6a0ee1501a 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use std::sync::Mutex; use mmtk::scheduler::GCWork; @@ -11,10 +13,13 @@ use crate::upcalls; use crate::Ruby; pub struct WeakProcessor { + non_parallel_obj_free_candidates: Mutex>, + parallel_obj_free_candidates: Vec>>, + parallel_obj_free_candidates_counter: AtomicUsize, + /// Objects that needs `obj_free` called when dying. /// If it is a bottleneck, replace it with a lock-free data structure, /// or add candidates in batch. - obj_free_candidates: Mutex>, weak_references: Mutex>, } @@ -27,32 +32,59 @@ impl Default for WeakProcessor { impl WeakProcessor { pub fn new() -> Self { Self { - obj_free_candidates: Mutex::new(Vec::new()), + non_parallel_obj_free_candidates: Mutex::new(Vec::new()), + parallel_obj_free_candidates: vec![Mutex::new(Vec::new())], + parallel_obj_free_candidates_counter: AtomicUsize::new(0), weak_references: Mutex::new(Vec::new()), } } - /// Add an object as a candidate for `obj_free`. - /// - /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidate(&self, object: ObjectReference) { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - obj_free_candidates.push(object); + pub fn init_parallel_obj_free_candidates(&mut self, num_workers: usize) { + debug_assert_eq!(self.parallel_obj_free_candidates.len(), 1); + + for _ in 1..num_workers { + self.parallel_obj_free_candidates + .push(Mutex::new(Vec::new())); + } } - /// Add many objects as candidates for `obj_free`. + /// Add an object as a candidate for `obj_free`. /// /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidates(&self, objects: &[ObjectReference]) { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - for object in objects.iter().copied() { - obj_free_candidates.push(object); + pub fn add_obj_free_candidate(&self, object: ObjectReference, can_parallel_free: bool) { + if can_parallel_free { + // Newly allocated objects are placed in parallel_obj_free_candidates using + // round-robin. This may not be ideal for load balancing. + let idx = self + .parallel_obj_free_candidates_counter + .fetch_add(1, Ordering::Relaxed) + % self.parallel_obj_free_candidates.len(); + + self.parallel_obj_free_candidates[idx] + .lock() + .unwrap() + .push(object); + } else { + self.non_parallel_obj_free_candidates + .lock() + .unwrap() + .push(object); } } pub fn get_all_obj_free_candidates(&self) -> Vec { - let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); - std::mem::take(obj_free_candidates.as_mut()) + // let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + let mut all_obj_free_candidates = self + .non_parallel_obj_free_candidates + .lock() + .unwrap() + .to_vec(); + + for candidates_mutex in &self.parallel_obj_free_candidates { + all_obj_free_candidates.extend(candidates_mutex.lock().unwrap().to_vec()); + } + + std::mem::take(all_obj_free_candidates.as_mut()) } pub fn add_weak_reference(&self, object: ObjectReference) { @@ -65,7 +97,18 @@ impl WeakProcessor { worker: &mut GCWorker, _tracer_context: impl ObjectTracerContext, ) { - worker.add_work(WorkBucketStage::VMRefClosure, ProcessObjFreeCandidates); + worker.add_work( + WorkBucketStage::VMRefClosure, + ProcessNonParallelObjFreeCanadidates {}, + ); + + for index in 0..self.parallel_obj_free_candidates.len() { + worker.add_work( + WorkBucketStage::VMRefClosure, + ProcessParallelObjFreeCandidates { index }, + ); + } + worker.add_work(WorkBucketStage::VMRefClosure, ProcessWeakReferences); worker.add_work(WorkBucketStage::Prepare, UpdateFinalizerObjIdTables); @@ -82,36 +125,50 @@ impl WeakProcessor { } } -struct ProcessObjFreeCandidates; +fn process_obj_free_candidates(obj_free_candidates: &mut Vec) { + // Process obj_free + let mut new_candidates = Vec::new(); + + for object in obj_free_candidates.iter().copied() { + if object.is_reachable() { + // Forward and add back to the candidate list. + let new_object = object.forward(); + trace!("Forwarding obj_free candidate: {object} -> {new_object}"); + new_candidates.push(new_object); + } else { + (upcalls().call_obj_free)(object); + } + } + + *obj_free_candidates = new_candidates; +} + +struct ProcessParallelObjFreeCandidates { + index: usize, +} -impl GCWork for ProcessObjFreeCandidates { +impl GCWork for ProcessParallelObjFreeCandidates { fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { - // If it blocks, it is a bug. - let mut obj_free_candidates = crate::binding() - .weak_proc - .obj_free_candidates + let mut obj_free_candidates = crate::binding().weak_proc.parallel_obj_free_candidates + [self.index] .try_lock() - .expect("It's GC time. No mutators should hold this lock at this time."); - - let n_cands = obj_free_candidates.len(); + .expect("Lock for parallel_obj_free_candidates should not be held"); - debug!("Total: {n_cands} candidates"); + process_obj_free_candidates(&mut obj_free_candidates); + } +} - // Process obj_free - let mut new_candidates = Vec::new(); +struct ProcessNonParallelObjFreeCanadidates; - for object in obj_free_candidates.iter().copied() { - if object.is_reachable() { - // Forward and add back to the candidate list. - let new_object = object.forward(); - trace!("Forwarding obj_free candidate: {object} -> {new_object}"); - new_candidates.push(new_object); - } else { - (upcalls().call_obj_free)(object); - } - } +impl GCWork for ProcessNonParallelObjFreeCanadidates { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let mut obj_free_candidates = crate::binding() + .weak_proc + .non_parallel_obj_free_candidates + .try_lock() + .expect("Lock for non_parallel_obj_free_candidates should not be held"); - *obj_free_candidates = new_candidates; + process_obj_free_candidates(&mut obj_free_candidates); } } From d8d41d7441aabae5e5948f89cd759bbdc3bf5532 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 21 Dec 2025 23:14:22 +0900 Subject: [PATCH 2316/2435] [DOC] Use `Ruby::Box#require_relative` in box.md examples Based on the example, it appears that `foo.rb` and `main.rb` are expected to be in the same directory. Since Ruby 1.9, the current directory is not included in `$LOAD_PATH` by default. As a result, running `box.require('foo')` as shown in the sample code raises a `LoadError`: ```console main.rb:2:in `Ruby::Box#require': cannot load such file -- foo (LoadError) from main.rb:2:in `
' ``` To avoid this, it seems simplest to show either `box.require('./foo')` or `box.require_relative('foo')`. In this PR, `box.require('foo')` is replaced with `box.require_relative('foo')` to make the intention of using a relative path explicit. This should reduce the chance that users trying Ruby Box will run into an unexpected error. --- doc/language/box.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/language/box.md b/doc/language/box.md index f2b59e5b39cafe..4bbb70e52914f1 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -117,7 +117,7 @@ Foo.foo.blank? #=> false # in main.rb box = Ruby::Box.new -box.require('foo') +box.require_relative('foo') box::Foo.foo_is_blank? #=> false (#blank? called in box) @@ -151,7 +151,7 @@ end # main.rb box = Ruby::Box.new -box.require('foo') +box.require_relative('foo') box::String.foo # NoMethodError ``` @@ -174,7 +174,7 @@ Array.const_get(:V) #=> "FOO" # main.rb box = Ruby::Box.new -box.require('foo') +box.require_relative('foo') Array.instance_variable_get(:@v) #=> nil Array.class_variable_get(:@@v) # NameError @@ -197,7 +197,7 @@ p $foo #=> nil p $VERBOSE #=> false box = Ruby::Box.new -box.require('foo') # "This appears: 'foo'" +box.require_relative('foo') # "This appears: 'foo'" p $foo #=> nil p $VERBOSE #=> false @@ -216,7 +216,7 @@ Object::FOO #=> 100 # main.rb box = Ruby::Box.new -box.require('foo') +box.require_relative('foo') box::FOO #=> 100 @@ -241,7 +241,7 @@ yay #=> "foo" # main.rb box = Ruby::Box.new -box.require('foo') +box.require_relative('foo') box::Foo.say #=> "foo" From ca0fece56a3ddfa6b9ec9cf1652e414929040614 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 21 Dec 2025 04:47:55 +0900 Subject: [PATCH 2317/2435] [DOC] Tweak an example in language/box.md Although the example code comments indicate that it returns `false`, a non-matching result for `=~` is actually `nil`. ```ruby Foo.foo.blank? #=> false "foo".blank? #=> false ``` https://github.com/ruby/ruby/blob/v4.0.0-preview3/doc/language/box.md?plain=1#L115-L122 This PR replaces `=~` with `match?` so that it returns the expected `false`. Since this makes the result a boolean, it also aligns with the expected behavior of a predicate method name like `blank?`. --- doc/language/box.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/language/box.md b/doc/language/box.md index 4bbb70e52914f1..05c0dad985cc5b 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -100,7 +100,7 @@ The changed definitions are visible only in the box. In other boxes, builtin cla class String BLANK_PATTERN = /\A\s*\z/ def blank? - self =~ BLANK_PATTERN + self.match?(BLANK_PATTERN) end end From 912cf819b96a7ada89db044add26e94746f3ebb5 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:54:42 +0100 Subject: [PATCH 2318/2435] [ruby/openssl] Improve Argument Error Message in EC:Group.new Before, passing the wrong number of arguments (e.g., 2) to OpenSSL::PKey::EC::Group.new raised a generic "wrong number of arguments" error. This change updates it to show the actual argument count and the expected options (1 or 4), making debugging easier for the user. Example: ArgumentError: wrong number of arguments (given 2, expected 1 or 4) I hope it helps! https://github.com/ruby/openssl/commit/783c99e6c7 --- ext/openssl/ossl_pkey_ec.c | 2 +- test/openssl/test_pkey_ec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index bb19533edf4744..35f031819dc6eb 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -702,7 +702,7 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) break; default: - ossl_raise(rb_eArgError, "wrong number of arguments"); + ossl_raise(rb_eArgError, "wrong number of arguments (given %d, expected 1 or 4)", argc); } ASSUME(group); diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index 88085bc68c7ef5..ec97a747a33b8b 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -345,6 +345,15 @@ def test_ec_group assert_equal group1.degree, group4.degree end + def test_ec_group_initialize_error_message + # Test that passing 2 arguments raises the helpful error + e = assert_raise(ArgumentError) do + OpenSSL::PKey::EC::Group.new(:GFp, 123) + end + + assert_equal("wrong number of arguments (given 2, expected 1 or 4)", e.message) + end + def test_ec_point group = OpenSSL::PKey::EC::Group.new("prime256v1") key = OpenSSL::PKey::EC.generate(group) From 18672b392b7164c783381979ad34d550955d0416 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 4 Jan 2026 16:00:23 +0900 Subject: [PATCH 2319/2435] [DOC] Add `base-url` to ChangeLog by default It is used to expand repository references to URL. --- tool/lib/vcs.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 602f6dd519c5b3..4f6dc0043283e8 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -453,7 +453,7 @@ def branch_beginning(url) #{url.to_str} -- version.h include/ruby/version.h]) end - def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, base_url: nil) + def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, base_url: true) from, to = [from, to].map do |rev| rev or next rev unless rev.empty? @@ -476,6 +476,7 @@ def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, arg = ["--since=25 Dec 00:00:00", to] end if base_url == true + env = CHANGELOG_ENV remote, = upstream if remote &&= cmd_read(env, %W[#{COMMAND} remote get-url --no-push #{remote}]) remote.chomp! @@ -494,9 +495,10 @@ def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, end LOG_FIX_REGEXP_SEPARATORS = '/!:;|,#%&' + CHANGELOG_ENV = {'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'} def changelog_formatter(path, arg, base_url = nil) - env = {'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'} + env = CHANGELOG_ENV cmd = %W[#{COMMAND} log --format=fuller --notes=commits --notes=log-fix --topo-order --no-merges --fixed-strings --invert-grep --grep=[ci\ skip] --grep=[skip\ ci] From 1b3382cbab83f0dbf21b0d3683d4ab5335f6c342 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 14:53:24 +0100 Subject: [PATCH 2320/2435] Update to ruby/spec@f54296d --- spec/ruby/.rubocop.yml | 1 + spec/ruby/command_line/frozen_strings_spec.rb | 24 +- spec/ruby/core/array/pack/shared/basic.rb | 10 +- .../builtin_constants_spec.rb | 74 ++++++ spec/ruby/core/comparable/clamp_spec.rb | 79 +++++++ spec/ruby/core/comparable/fixtures/classes.rb | 1 + spec/ruby/core/data/fixtures/classes.rb | 6 + spec/ruby/core/data/initialize_spec.rb | 10 + spec/ruby/core/encoding/find_spec.rb | 2 +- spec/ruby/core/enumerable/to_set_spec.rb | 8 - spec/ruby/core/fiber/fixtures/scheduler.rb | 35 +++ spec/ruby/core/fiber/scheduler_spec.rb | 8 + spec/ruby/core/fiber/set_scheduler_spec.rb | 8 + spec/ruby/core/fiber/shared/scheduler.rb | 51 +++++ spec/ruby/core/file/socket_spec.rb | 32 --- spec/ruby/core/filetest/socket_spec.rb | 4 + spec/ruby/core/gc/config_spec.rb | 83 +++++++ spec/ruby/core/integer/shared/modulo.rb | 46 +++- spec/ruby/core/io/shared/new.rb | 23 ++ spec/ruby/core/kernel/Float_spec.rb | 4 + spec/ruby/core/kernel/Rational_spec.rb | 98 +++++++- spec/ruby/core/kernel/raise_spec.rb | 4 +- spec/ruby/core/kernel/sleep_spec.rb | 35 +++ spec/ruby/core/math/expm1_spec.rb | 37 +++ spec/ruby/core/math/log1p_spec.rb | 49 ++++ spec/ruby/core/module/fixtures/classes.rb | 2 +- spec/ruby/core/proc/parameters_spec.rb | 14 ++ spec/ruby/core/range/to_set_spec.rb | 55 +++++ spec/ruby/core/regexp/linear_time_spec.rb | 6 + spec/ruby/core/string/unpack/shared/basic.rb | 17 +- spec/ruby/core/string/uplus_spec.rb | 44 +++- spec/ruby/core/symbol/shared/id2name.rb | 14 ++ .../core/unboundmethod/equal_value_spec.rb | 2 +- spec/ruby/core/warning/categories_spec.rb | 12 + spec/ruby/core/warning/warn_spec.rb | 8 +- spec/ruby/default.mspec | 5 +- spec/ruby/language/block_spec.rb | 64 +++++- spec/ruby/language/class_spec.rb | 42 +++- spec/ruby/language/fixtures/module.rb | 9 - spec/ruby/language/it_parameter_spec.rb | 66 ++++++ spec/ruby/language/magic_comment_spec.rb | 17 +- spec/ruby/language/method_spec.rb | 8 +- spec/ruby/language/module_spec.rb | 32 ++- spec/ruby/language/reserved_keywords.rb | 149 +++++++++++++ .../library/cgi/escapeURIComponent_spec.rb | 59 +++-- .../library/cgi/unescapeURIComponent_spec.rb | 128 +++++++++++ .../library/socket/addrinfo/afamily_spec.rb | 16 +- .../socket/addrinfo/family_addrinfo_spec.rb | 48 ++-- .../socket/addrinfo/getnameinfo_spec.rb | 20 +- .../socket/addrinfo/initialize_spec.rb | 58 +++-- .../socket/addrinfo/inspect_sockaddr_spec.rb | 18 +- .../library/socket/addrinfo/inspect_spec.rb | 26 +-- .../socket/addrinfo/ip_address_spec.rb | 14 +- .../library/socket/addrinfo/ip_port_spec.rb | 14 +- spec/ruby/library/socket/addrinfo/ip_spec.rb | 14 +- .../library/socket/addrinfo/ip_unpack_spec.rb | 14 +- .../socket/addrinfo/ipv4_loopback_spec.rb | 14 +- .../socket/addrinfo/ipv4_multicast_spec.rb | 14 +- .../socket/addrinfo/ipv4_private_spec.rb | 16 +- .../ruby/library/socket/addrinfo/ipv4_spec.rb | 14 +- .../socket/addrinfo/ipv6_loopback_spec.rb | 16 +- .../socket/addrinfo/ipv6_multicast_spec.rb | 16 +- .../ruby/library/socket/addrinfo/ipv6_spec.rb | 14 +- .../socket/addrinfo/ipv6_to_ipv4_spec.rb | 2 +- .../socket/addrinfo/marshal_dump_spec.rb | 50 ++--- .../socket/addrinfo/marshal_load_spec.rb | 20 +- .../library/socket/addrinfo/pfamily_spec.rb | 16 +- .../library/socket/addrinfo/protocol_spec.rb | 14 +- .../socket/addrinfo/shared/to_sockaddr.rb | 16 +- .../library/socket/addrinfo/socktype_spec.rb | 14 +- .../library/socket/addrinfo/unix_path_spec.rb | 46 ++-- .../ruby/library/socket/addrinfo/unix_spec.rb | 46 ++-- .../basicsocket/connect_address_spec.rb | 80 ++++--- .../socket/basicsocket/getpeereid_spec.rb | 2 +- .../socket/basicsocket/setsockopt_spec.rb | 26 +-- spec/ruby/library/socket/shared/address.rb | 86 +++---- .../library/socket/shared/pack_sockaddr.rb | 30 ++- .../socket/socket/unix_server_loop_spec.rb | 76 +++---- .../socket/socket/unix_server_socket_spec.rb | 56 +++-- spec/ruby/library/socket/socket/unix_spec.rb | 56 +++-- .../socket/socket/unpack_sockaddr_in_spec.rb | 16 +- .../socket/socket/unpack_sockaddr_un_spec.rb | 34 ++- spec/ruby/library/socket/spec_helper.rb | 1 - .../socket/unixserver/accept_nonblock_spec.rb | 114 +++++----- .../library/socket/unixserver/accept_spec.rb | 166 +++++++------- .../library/socket/unixserver/for_fd_spec.rb | 28 ++- .../socket/unixserver/initialize_spec.rb | 36 ++- .../library/socket/unixserver/listen_spec.rb | 24 +- .../library/socket/unixserver/new_spec.rb | 14 +- .../library/socket/unixserver/open_spec.rb | 30 ++- .../socket/unixserver/sysaccept_spec.rb | 64 +++--- .../library/socket/unixsocket/addr_spec.rb | 48 ++-- .../socket/unixsocket/initialize_spec.rb | 60 ++--- .../library/socket/unixsocket/inspect_spec.rb | 20 +- .../socket/unixsocket/local_address_spec.rb | 132 ++++++----- .../library/socket/unixsocket/new_spec.rb | 14 +- .../library/socket/unixsocket/open_spec.rb | 34 ++- .../library/socket/unixsocket/pair_spec.rb | 20 +- .../unixsocket/partially_closable_spec.rb | 30 ++- .../library/socket/unixsocket/path_spec.rb | 34 ++- .../socket/unixsocket/peeraddr_spec.rb | 38 ++-- .../library/socket/unixsocket/recv_io_spec.rb | 2 +- .../socket/unixsocket/recvfrom_spec.rb | 152 +++++++++---- .../socket/unixsocket/remote_address_spec.rb | 60 +++-- .../library/socket/unixsocket/send_io_spec.rb | 2 +- .../library/socket/unixsocket/shared/pair.rb | 39 +++- .../socket/unixsocket/socketpair_spec.rb | 20 +- .../ruby/library/stringio/readpartial_spec.rb | 8 + .../thread_safety/fixtures/classes.rb | 39 ++++ spec/ruby/optional/thread_safety/hash_spec.rb | 210 ++++++++++++++++++ spec/ruby/shared/file/socket.rb | 32 ++- 111 files changed, 2752 insertions(+), 1236 deletions(-) create mode 100644 spec/ruby/core/fiber/fixtures/scheduler.rb create mode 100644 spec/ruby/core/fiber/scheduler_spec.rb create mode 100644 spec/ruby/core/fiber/set_scheduler_spec.rb create mode 100644 spec/ruby/core/fiber/shared/scheduler.rb create mode 100644 spec/ruby/core/gc/config_spec.rb create mode 100644 spec/ruby/core/math/expm1_spec.rb create mode 100644 spec/ruby/core/math/log1p_spec.rb create mode 100644 spec/ruby/core/range/to_set_spec.rb create mode 100644 spec/ruby/core/warning/categories_spec.rb create mode 100644 spec/ruby/language/it_parameter_spec.rb create mode 100644 spec/ruby/language/reserved_keywords.rb create mode 100644 spec/ruby/library/cgi/unescapeURIComponent_spec.rb create mode 100644 spec/ruby/optional/thread_safety/fixtures/classes.rb create mode 100644 spec/ruby/optional/thread_safety/hash_spec.rb diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml index 68cce312808bce..0b59a11512f445 100644 --- a/spec/ruby/.rubocop.yml +++ b/spec/ruby/.rubocop.yml @@ -197,6 +197,7 @@ Style/Lambda: - 'language/lambda_spec.rb' - 'language/proc_spec.rb' - 'language/numbered_parameters_spec.rb' + - 'language/it_parameter_spec.rb' - 'core/kernel/lambda_spec.rb' Style/EmptyLambdaParameter: diff --git a/spec/ruby/command_line/frozen_strings_spec.rb b/spec/ruby/command_line/frozen_strings_spec.rb index 014153e0b4afbe..8acab5bc1d66ee 100644 --- a/spec/ruby/command_line/frozen_strings_spec.rb +++ b/spec/ruby/command_line/frozen_strings_spec.rb @@ -42,8 +42,28 @@ ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb")).chomp.should == "false" end - it "if file has no frozen_string_literal comment produce different mutable strings each time" do - ruby_exe(fixture(__FILE__, "string_literal_raw.rb")).chomp.should == "frozen:false interned:false" + context "if file has no frozen_string_literal comment" do + it "produce different mutable strings each time" do + ruby_exe(fixture(__FILE__, "string_literal_raw.rb")).chomp.should == "frozen:false interned:false" + end + + guard -> { ruby_version_is "3.4" and !"test".frozen? } do + it "complain about modification of produced mutable strings" do + -> { eval(<<~RUBY) }.should complain(/warning: literal string will be frozen in the future \(run with --debug-frozen-string-literal for more information\)/) + "test" << "!" + RUBY + end + + it "does not complain about modification if Warning[:deprecated] is false" do + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + -> { eval(<<~RUBY) }.should_not complain + "test" << "!" + RUBY + ensure + Warning[:deprecated] = deprecated + end + end end it "if file has frozen_string_literal:true comment produce same frozen strings each time" do diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb index ebd9f75d9dc66a..a63f64d296a312 100644 --- a/spec/ruby/core/array/pack/shared/basic.rb +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -33,19 +33,15 @@ end ruby_version_is ""..."3.3" do - # https://bugs.ruby-lang.org/issues/19150 - # NOTE: it's just a plan of the Ruby core team it "warns that a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/) - -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/) + -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K' in 'a K#{pack_format}'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0' in 'a 0#{pack_format}'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':' in 'a :#{pack_format}'/) end end ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19150 - # NOTE: Added this case just to not forget about the decision in the ticket it "raise ArgumentError when a directive is unknown" do # additional directive ('a') is required for the X directive -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) diff --git a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb index eaf311783a4a08..13e066cc7f1664 100644 --- a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb +++ b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb @@ -46,6 +46,16 @@ end end +describe "RUBY_ENGINE_VERSION" do + it "is a String" do + RUBY_ENGINE_VERSION.should be_kind_of(String) + end + + it "is frozen" do + RUBY_ENGINE_VERSION.should.frozen? + end +end + describe "RUBY_PLATFORM" do it "is a String" do RUBY_PLATFORM.should be_kind_of(String) @@ -75,3 +85,67 @@ RUBY_REVISION.should.frozen? end end + +ruby_version_is "4.0" do + context "The constant" do + describe "Ruby" do + it "is a Module" do + Ruby.should.instance_of?(Module) + end + end + + describe "Ruby::VERSION" do + it "is equal to RUBY_VERSION" do + Ruby::VERSION.should equal(RUBY_VERSION) + end + end + + describe "RUBY::PATCHLEVEL" do + it "is equal to RUBY_PATCHLEVEL" do + Ruby::PATCHLEVEL.should equal(RUBY_PATCHLEVEL) + end + end + + describe "Ruby::COPYRIGHT" do + it "is equal to RUBY_COPYRIGHT" do + Ruby::COPYRIGHT.should equal(RUBY_COPYRIGHT) + end + end + + describe "Ruby::DESCRIPTION" do + it "is equal to RUBY_DESCRIPTION" do + Ruby::DESCRIPTION.should equal(RUBY_DESCRIPTION) + end + end + + describe "Ruby::ENGINE" do + it "is equal to RUBY_ENGINE" do + Ruby::ENGINE.should equal(RUBY_ENGINE) + end + end + + describe "Ruby::ENGINE_VERSION" do + it "is equal to RUBY_ENGINE_VERSION" do + Ruby::ENGINE_VERSION.should equal(RUBY_ENGINE_VERSION) + end + end + + describe "Ruby::PLATFORM" do + it "is equal to RUBY_PLATFORM" do + Ruby::PLATFORM.should equal(RUBY_PLATFORM) + end + end + + describe "Ruby::RELEASE_DATE" do + it "is equal to RUBY_RELEASE_DATE" do + Ruby::RELEASE_DATE.should equal(RUBY_RELEASE_DATE) + end + end + + describe "Ruby::REVISION" do + it "is equal to RUBY_REVISION" do + Ruby::REVISION.should equal(RUBY_REVISION) + end + end + end +end diff --git a/spec/ruby/core/comparable/clamp_spec.rb b/spec/ruby/core/comparable/clamp_spec.rb index cc1df977e2ae09..18f616a997998d 100644 --- a/spec/ruby/core/comparable/clamp_spec.rb +++ b/spec/ruby/core/comparable/clamp_spec.rb @@ -40,6 +40,39 @@ c.clamp(one, two).should equal(two) end + context 'max is nil' do + it 'returns min if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + c.clamp(one, nil).should equal(one) + end + + it 'always returns self if greater than min' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + c.clamp(one, nil).should equal(c) + end + end + + context 'min is nil' do + it 'returns max if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + c.clamp(nil, one).should equal(one) + end + + it 'always returns self if less than max' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + c.clamp(nil, one).should equal(c) + end + end + + it 'always returns self when min is nil and max is nil' do + c = ComparableSpecs::Weird.new(1) + c.clamp(nil, nil).should equal(c) + end + it 'returns self if within the given range parameters' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) @@ -76,6 +109,26 @@ -> { c.clamp(one...two) }.should raise_error(ArgumentError) end + context 'with nil as the max argument' do + it 'returns min argument if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(one, nil).should equal(one) + c.clamp(zero, nil).should equal(c) + end + + it 'always returns self if greater than min argument' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + two = ComparableSpecs::WithOnlyCompareDefined.new(2) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one, nil).should equal(c) + c.clamp(two, nil).should equal(c) + end + end + context 'with endless range' do it 'returns minimum value of the range parameters if less than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) @@ -103,6 +156,24 @@ end end + context 'with nil as the min argument' do + it 'returns max argument if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(nil, one).should equal(one) + end + + it 'always returns self if less than max argument' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(nil, one).should equal(c) + c.clamp(nil, zero).should equal(c) + end + end + context 'with beginless range' do it 'returns maximum value of the range parameters if greater than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) @@ -128,6 +199,14 @@ end end + context 'with nil as the min and the max argument' do + it 'always returns self' do + c = ComparableSpecs::Weird.new(1) + + c.clamp(nil, nil).should equal(c) + end + end + context 'with beginless-and-endless range' do it 'always returns self' do c = ComparableSpecs::Weird.new(1) diff --git a/spec/ruby/core/comparable/fixtures/classes.rb b/spec/ruby/core/comparable/fixtures/classes.rb index 4239a47d2f5738..2bdabbf0140c60 100644 --- a/spec/ruby/core/comparable/fixtures/classes.rb +++ b/spec/ruby/core/comparable/fixtures/classes.rb @@ -7,6 +7,7 @@ def initialize(value) end def <=>(other) + return nil if other.nil? self.value <=> other.value end end diff --git a/spec/ruby/core/data/fixtures/classes.rb b/spec/ruby/core/data/fixtures/classes.rb index ffd361d781d7a0..650c0b2a623fdd 100644 --- a/spec/ruby/core/data/fixtures/classes.rb +++ b/spec/ruby/core/data/fixtures/classes.rb @@ -24,5 +24,11 @@ def initialize(*rest, **kw) ScratchPad.record [:initialize, rest, kw] end end + + Area = Data.define(:width, :height, :area) do + def initialize(width:, height:) + super(width: width, height: height, area: width * height) + end + end end end diff --git a/spec/ruby/core/data/initialize_spec.rb b/spec/ruby/core/data/initialize_spec.rb index 9d272780a85b23..010c73b91b8fea 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -110,5 +110,15 @@ def initialize(*, **) DataSpecs::DataWithOverriddenInitialize[amount: 42, unit: "m"] ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] end + + # See https://github.com/ruby/psych/pull/765 + it "can be deserialized by calling Data.instance_method(:initialize)" do + d1 = DataSpecs::Area.new(width: 2, height: 3) + d1.area.should == 6 + + d2 = DataSpecs::Area.allocate + Data.instance_method(:initialize).bind_call(d2, **d1.to_h) + d2.should == d1 + end end end diff --git a/spec/ruby/core/encoding/find_spec.rb b/spec/ruby/core/encoding/find_spec.rb index 8a0873070fc90b..9c34fe0e77ffee 100644 --- a/spec/ruby/core/encoding/find_spec.rb +++ b/spec/ruby/core/encoding/find_spec.rb @@ -50,7 +50,7 @@ def to_str; @encoding_name; end end it "raises an ArgumentError if the given encoding does not exist" do - -> { Encoding.find('dh2dh278d') }.should raise_error(ArgumentError) + -> { Encoding.find('dh2dh278d') }.should raise_error(ArgumentError, 'unknown encoding name - dh2dh278d') end # Not sure how to do a better test, since locale depends on weird platform-specific stuff diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb index 91499aeb5b180c..c02ead11fa4638 100644 --- a/spec/ruby/core/enumerable/to_set_spec.rb +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -27,12 +27,4 @@ set.to_a.sort.should == [1, 2, 3] end end - - it "does not need explicit `require 'set'`" do - output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') - puts [1, 2, 3].to_set.to_a.inspect - RUBY - - output.chomp.should == "[1, 2, 3]" - end end diff --git a/spec/ruby/core/fiber/fixtures/scheduler.rb b/spec/ruby/core/fiber/fixtures/scheduler.rb new file mode 100644 index 00000000000000..16bd2f6b443457 --- /dev/null +++ b/spec/ruby/core/fiber/fixtures/scheduler.rb @@ -0,0 +1,35 @@ +module FiberSpecs + + class LoggingScheduler + attr_reader :events + def initialize + @events = [] + end + + def block(*args) + @events << { event: :block, fiber: Fiber.current, args: args } + Fiber.yield + end + + def io_wait(*args) + @events << { event: :io_wait, fiber: Fiber.current, args: args } + Fiber.yield + end + + def kernel_sleep(*args) + @events << { event: :kernel_sleep, fiber: Fiber.current, args: args } + Fiber.yield + end + + def unblock(*args) + @events << { event: :unblock, fiber: Fiber.current, args: args } + Fiber.yield + end + + def fiber_interrupt(*args) + @events << { event: :fiber_interrupt, fiber: Fiber.current, args: args } + Fiber.yield + end + end + +end diff --git a/spec/ruby/core/fiber/scheduler_spec.rb b/spec/ruby/core/fiber/scheduler_spec.rb new file mode 100644 index 00000000000000..15a03c147921aa --- /dev/null +++ b/spec/ruby/core/fiber/scheduler_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/scheduler' + +require "fiber" + +describe "Fiber.scheduler" do + it_behaves_like :scheduler, :scheduler +end diff --git a/spec/ruby/core/fiber/set_scheduler_spec.rb b/spec/ruby/core/fiber/set_scheduler_spec.rb new file mode 100644 index 00000000000000..82f6acbe866f19 --- /dev/null +++ b/spec/ruby/core/fiber/set_scheduler_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/scheduler' + +require "fiber" + +describe "Fiber.scheduler" do + it_behaves_like :scheduler, :set_scheduler +end diff --git a/spec/ruby/core/fiber/shared/scheduler.rb b/spec/ruby/core/fiber/shared/scheduler.rb new file mode 100644 index 00000000000000..19bfb75e3e1313 --- /dev/null +++ b/spec/ruby/core/fiber/shared/scheduler.rb @@ -0,0 +1,51 @@ +describe :scheduler, shared: true do + it "validates the scheduler for required methods" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + required_methods.each do |missing_method| + scheduler = Object.new + required_methods.difference([missing_method]).each do |method| + scheduler.define_singleton_method(method) {} + end + -> { + suppress_warning { Fiber.set_scheduler(scheduler) } + }.should raise_error(ArgumentError, /Scheduler must implement ##{missing_method}/) + end + end + + it "can set and get the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.scheduler.should == scheduler + end + + it "returns the scheduler after setting it" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + result = suppress_warning { Fiber.set_scheduler(scheduler) } + result.should == scheduler + end + + it "can remove the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.set_scheduler(nil) + Fiber.scheduler.should be_nil + end + + it "can assign a nil scheduler multiple times" do + Fiber.set_scheduler(nil) + Fiber.set_scheduler(nil) + Fiber.scheduler.should be_nil + end +end diff --git a/spec/ruby/core/file/socket_spec.rb b/spec/ruby/core/file/socket_spec.rb index 5d12e21f5596b6..d3f4eb013a3513 100644 --- a/spec/ruby/core/file/socket_spec.rb +++ b/spec/ruby/core/file/socket_spec.rb @@ -1,42 +1,10 @@ require_relative '../../spec_helper' require_relative '../../shared/file/socket' -require 'socket' describe "File.socket?" do it_behaves_like :file_socket, :socket?, File -end -describe "File.socket?" do it "returns false if file does not exist" do File.socket?("I_am_a_bogus_file").should == false end - - it "returns false if the file is not a socket" do - filename = tmp("i_exist") - touch(filename) - - File.socket?(filename).should == false - - rm_r filename - end -end - -platform_is_not :windows do - describe "File.socket?" do - before :each do - # We need a really short name here. - # On Linux the path length is limited to 107, see unix(7). - @name = tmp("s") - @server = UNIXServer.new @name - end - - after :each do - @server.close - rm_r @name - end - - it "returns true if the file is a socket" do - File.socket?(@name).should == true - end - end end diff --git a/spec/ruby/core/filetest/socket_spec.rb b/spec/ruby/core/filetest/socket_spec.rb index 63a6a31ecbb3fe..f274be6318c203 100644 --- a/spec/ruby/core/filetest/socket_spec.rb +++ b/spec/ruby/core/filetest/socket_spec.rb @@ -3,4 +3,8 @@ describe "FileTest.socket?" do it_behaves_like :file_socket, :socket?, FileTest + + it "returns false if file does not exist" do + FileTest.socket?("I_am_a_bogus_file").should == false + end end diff --git a/spec/ruby/core/gc/config_spec.rb b/spec/ruby/core/gc/config_spec.rb new file mode 100644 index 00000000000000..e20e8e4a16a97f --- /dev/null +++ b/spec/ruby/core/gc/config_spec.rb @@ -0,0 +1,83 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "GC.config" do + context "without arguments" do + it "returns a hash of current settings" do + GC.config.should be_kind_of(Hash) + end + + it "includes the name of currently loaded GC implementation as a global key" do + GC.config.should include(:implementation) + GC.config[:implementation].should be_kind_of(String) + end + end + + context "with a hash of options" do + it "allows to set GC implementation's options, returning the new config" do + config = GC.config({}) + # Try to find a boolean setting to reliably test changing it. + key, _value = config.find { |_k, v| v == true } + skip unless key + + GC.config(key => false).should == config.merge(key => false) + GC.config[key].should == false + GC.config(key => true).should == config + GC.config[key].should == true + ensure + GC.config(config.except(:implementation)) + end + + it "does not change settings that aren't present in the hash" do + previous = GC.config + GC.config({}) + GC.config.should == previous + end + + it "ignores unknown keys" do + previous = GC.config + GC.config(foo: "bar") + GC.config.should == previous + end + + it "raises an ArgumentError if options include global keys" do + -> { GC.config(implementation: "default") }.should raise_error(ArgumentError, 'Attempting to set read-only key "Implementation"') + end + end + + context "with a non-hash argument" do + it "returns current settings if argument is nil" do + GC.config(nil).should == GC.config + end + + it "raises ArgumentError for all other arguments" do + -> { GC.config([]) }.should raise_error(ArgumentError) + -> { GC.config("default") }.should raise_error(ArgumentError) + -> { GC.config(1) }.should raise_error(ArgumentError) + end + end + + guard -> { PlatformGuard.standard? && GC.config[:implementation] == "default" } do + context "with default GC implementation on MRI" do + before do + @default_config = GC.config({}) + end + + after do + GC.config(@default_config.except(:implementation)) + end + + it "includes :rgengc_allow_full_mark option, true by default" do + GC.config.should include(:rgengc_allow_full_mark) + GC.config[:rgengc_allow_full_mark].should be_true + end + + it "allows to set :rgengc_allow_full_mark" do + # This key maps truthy and falsey values to true and false. + GC.config(rgengc_allow_full_mark: nil).should == @default_config.merge(rgengc_allow_full_mark: false) + GC.config(rgengc_allow_full_mark: 1.23).should == @default_config.merge(rgengc_allow_full_mark: true) + end + end + end + end +end diff --git a/spec/ruby/core/integer/shared/modulo.rb b/spec/ruby/core/integer/shared/modulo.rb index f678a10806d93e..d91af1e924176d 100644 --- a/spec/ruby/core/integer/shared/modulo.rb +++ b/spec/ruby/core/integer/shared/modulo.rb @@ -1,6 +1,12 @@ describe :integer_modulo, shared: true do context "fixnum" do it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + 13.send(@method, 4).should == 1 4.send(@method, 13).should == 4 @@ -16,8 +22,22 @@ (200).send(@method, -256).should == -56 (1000).send(@method, -512).should == -24 + 13.send(@method, -4.0).should == -3.0 + 4.send(@method, -13.0).should == -9.0 + + -13.send(@method, -4.0).should == -1.0 + -4.send(@method, -13.0).should == -4.0 + + -13.send(@method, 4.0).should == 3.0 + -4.send(@method, 13.0).should == 9.0 + 1.send(@method, 2.0).should == 1.0 200.send(@method, bignum_value).should == 200 + + 4.send(@method, bignum_value(10)).should == 4 + 4.send(@method, -bignum_value(10)).should == -18446744073709551622 + -4.send(@method, bignum_value(10)).should == 18446744073709551622 + -4.send(@method, -bignum_value(10)).should == -4 end it "raises a ZeroDivisionError when the given argument is 0" do @@ -44,15 +64,35 @@ context "bignum" do before :each do - @bignum = bignum_value + @bignum = bignum_value(10) end it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + @bignum.send(@method, 5).should == 1 @bignum.send(@method, -5).should == -4 - @bignum.send(@method, -100).should == -84 + (-@bignum).send(@method, 5).should == 4 + (-@bignum).send(@method, -5).should == -1 + @bignum.send(@method, 2.22).should be_close(1.5603603603605034, TOLERANCE) - @bignum.send(@method, bignum_value(10)).should == 18446744073709551616 + @bignum.send(@method, -2.22).should be_close(-0.6596396396394968, TOLERANCE) + (-@bignum).send(@method, 2.22).should be_close(0.6596396396394968, TOLERANCE) + (-@bignum).send(@method, -2.22).should be_close(-1.5603603603605034, TOLERANCE) + + @bignum.send(@method, @bignum + 10).should == 18446744073709551626 + @bignum.send(@method, -(@bignum + 10)).should == -10 + (-@bignum).send(@method, @bignum + 10).should == 10 + (-@bignum).send(@method, -(@bignum + 10)).should == -18446744073709551626 + + (@bignum + 10).send(@method, @bignum).should == 10 + (@bignum + 10).send(@method, -@bignum).should == -18446744073709551616 + (-(@bignum + 10)).send(@method, @bignum).should == 18446744073709551616 + (-(@bignum + 10)).send(@method, -@bignum).should == -10 end it "raises a ZeroDivisionError when the given argument is 0" do diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb index cba5f33ebfc1f2..e84133493c8c63 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -208,6 +208,26 @@ @io.internal_encoding.to_s.should == 'IBM866' end + it "does not use binary encoding when mode encoding is specified along with binmode: true option" do + @io = IO.send(@method, @fd, 'w:iso-8859-1', binmode: true) + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when mode encoding is specified" do + @io = IO.send(@method, @fd, 'w:ascii-8bit', textmode: true) + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + + it "does not use binmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', binmode: true, external_encoding: 'iso-8859-1') + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', textmode: true, external_encoding: 'ascii-8bit') + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + it "raises ArgumentError for nil options" do -> { IO.send(@method, @fd, 'w', nil) @@ -324,6 +344,9 @@ -> { @io = IO.send(@method, @fd, 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) + -> { + @io = IO.send(@method, @fd, 'w:ISO-8859-1', internal_encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) -> { @io = IO.send(@method, @fd, 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 74f6f1b0bdbcce..9c436b05f73012 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -261,6 +261,10 @@ def to_f() 1.2 end @object.send(:Float, "0x_10", exception: false).should be_nil end + it "parses negative hexadecimal string as negative float" do + @object.send(:Float, "-0x7b").should == -123.0 + end + ruby_version_is "3.4" do it "accepts a fractional part" do @object.send(:Float, "0x0.8").should == 0.5 diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb index 841c8e8c64d147..cc11a354515c98 100644 --- a/spec/ruby/core/kernel/Rational_spec.rb +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -76,12 +76,62 @@ end describe "when passed a Complex" do - it "returns a Rational from the real part if the imaginary part is 0" do - Rational(Complex(1, 0)).should == Rational(1) + context "[Complex]" do + it "returns a Rational from the real part if the imaginary part is 0" do + Rational(Complex(1, 0)).should == Rational(1) + end + + it "raises a RangeError if the imaginary part is not 0" do + -> { Rational(Complex(1, 2)) }.should raise_error(RangeError, "can't convert 1+2i into Rational") + end end - it "raises a RangeError if the imaginary part is not 0" do - -> { Rational(Complex(1, 2)) }.should raise_error(RangeError) + context "[Numeric, Complex]" do + it "uses the real part if the imaginary part is 0" do + Rational(1, Complex(2, 0)).should == Rational(1, 2) + end + + it "divides a numerator by the Complex denominator if the imaginary part is not 0" do + Rational(1, Complex(2, 1)).should == Complex(2/5r, -1/5r) + end + end + end + + context "when passed neither a Numeric nor a String" do + it "converts to Rational with #to_r method" do + obj = Object.new + def obj.to_r; 1/2r; end + + Rational(obj).should == 1/2r + end + + it "tries to convert to Integer with #to_int method if it does not respond to #to_r" do + obj = Object.new + def obj.to_int; 1; end + + Rational(obj).should == 1r + end + + it "raises TypeError if it neither responds to #to_r nor #to_int method" do + -> { Rational([]) }.should raise_error(TypeError, "can't convert Array into Rational") + -> { Rational({}) }.should raise_error(TypeError, "can't convert Hash into Rational") + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "swallows exception raised in #to_int method" do + object = Object.new + def object.to_int() raise NoMethodError; end + + -> { Rational(object) }.should raise_error(TypeError) + -> { Rational(object, 1) }.should raise_error(TypeError) + -> { Rational(1, object) }.should raise_error(TypeError) + end + + it "raises TypeError if #to_r does not return Rational" do + obj = Object.new + def obj.to_r; []; end + + -> { Rational(obj) }.should raise_error(TypeError, "can't convert Object to Rational (Object#to_r gives Array)") end end @@ -91,11 +141,11 @@ end it "raises a TypeError if the first argument is nil" do - -> { Rational(nil) }.should raise_error(TypeError) + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") end it "raises a TypeError if the second argument is nil" do - -> { Rational(1, nil) }.should raise_error(TypeError) + -> { Rational(1, nil) }.should raise_error(TypeError, "can't convert nil into Rational") end it "raises a TypeError if the first argument is a Symbol" do @@ -112,6 +162,18 @@ Rational(:sym, exception: false).should == nil Rational("abc", exception: false).should == nil end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, exception: false).should == nil + end end describe "and [non-Numeric, Numeric]" do @@ -119,6 +181,18 @@ Rational(:sym, 1, exception: false).should == nil Rational("abc", 1, exception: false).should == nil end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, 1, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, 1, exception: false).should == nil + end end describe "and [anything, non-Numeric]" do @@ -126,6 +200,18 @@ Rational(:sym, :sym, exception: false).should == nil Rational("abc", :sym, exception: false).should == nil end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, obj, exception: false).should == nil + end end describe "and non-Numeric String arguments" do diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index a4ab963fa35d12..fcd011d4e62e88 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -51,11 +51,11 @@ -> { raise(cause: nil) }.should raise_error(ArgumentError, "only cause is given with no arguments") end - it "raises an ArgumentError when given cause is not an instance of Exception" do + it "raises a TypeError when given cause is not an instance of Exception" do -> { raise "message", cause: Object.new }.should raise_error(TypeError, "exception object expected") end - it "doesn't raise an ArgumentError when given cause is nil" do + it "doesn't raise a TypeError when given cause is nil" do -> { raise "message", cause: nil }.should raise_error(RuntimeError, "message") end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index 4401e542563a9d..e9c600aac41107 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative '../fiber/fixtures/scheduler' describe "Kernel#sleep" do it "is a private method" do @@ -84,6 +85,40 @@ def o.divmod(*); [0, 0.001]; end t.value.should == 5 end end + + context "Kernel.sleep with Fiber scheduler" do + before :each do + Fiber.set_scheduler(FiberSpecs::LoggingScheduler.new) + end + + after :each do + Fiber.set_scheduler(nil) + end + + it "calls the scheduler without arguments when no duration is given" do + sleeper = Fiber.new(blocking: false) do + sleep + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [] }] + end + + it "calls the scheduler with the given duration" do + sleeper = Fiber.new(blocking: false) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [0.01] }] + end + + it "does not call the scheduler if the fiber is blocking" do + sleeper = Fiber.new(blocking: true) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [] + end + end end describe "Kernel.sleep" do diff --git a/spec/ruby/core/math/expm1_spec.rb b/spec/ruby/core/math/expm1_spec.rb new file mode 100644 index 00000000000000..5725319abbb458 --- /dev/null +++ b/spec/ruby/core/math/expm1_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + describe "Math.expm1" do + it "calculates Math.exp(arg) - 1" do + Math.expm1(3).should == Math.exp(3) - 1 + end + + it "preserves precision that can be lost otherwise" do + Math.expm1(1.0e-16).should be_close(1.0e-16, TOLERANCE) + Math.expm1(1.0e-16).should != 0.0 + end + + it "raises a TypeError if the argument cannot be coerced with Float()" do + -> { Math.expm1("test") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "returns NaN given NaN" do + Math.expm1(nan_value).nan?.should be_true + end + + it "raises a TypeError if the argument is nil" do + -> { Math.expm1(nil) }.should raise_error(TypeError, "can't convert nil into Float") + end + + it "accepts any argument that can be coerced with Float()" do + Math.expm1(MathSpecs::Float.new).should be_close(Math::E - 1, TOLERANCE) + end + end + + describe "Math#expm1" do + it "is accessible as a private instance method" do + IncludesMath.new.send(:expm1, 23.1415).should be_close(11226018483.0012, TOLERANCE) + end + end +end diff --git a/spec/ruby/core/math/log1p_spec.rb b/spec/ruby/core/math/log1p_spec.rb new file mode 100644 index 00000000000000..216358a3c47802 --- /dev/null +++ b/spec/ruby/core/math/log1p_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + describe "Math.log1p" do + it "calculates Math.log(1 + arg)" do + Math.log1p(3).should == Math.log(1 + 3) + end + + it "preserves precision that can be lost otherwise" do + Math.log1p(1e-16).should be_close(1.0e-16, TOLERANCE) + Math.log1p(1e-16).should != 0.0 + end + + it "raises an Math::DomainError if the argument is less than 1" do + -> { Math.log1p(-1-1e-15) }.should raise_error(Math::DomainError, "Numerical argument is out of domain - log1p") + end + + it "raises a TypeError if the argument cannot be coerced with Float()" do + -> { Math.log1p("test") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "raises a TypeError for numerical values passed as string" do + -> { Math.log1p("10") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "does not accept a second argument for the base" do + -> { Math.log1p(9, 3) }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + + it "returns NaN given NaN" do + Math.log1p(nan_value).nan?.should be_true + end + + it "raises a TypeError if the argument is nil" do + -> { Math.log1p(nil) }.should raise_error(TypeError, "can't convert nil into Float") + end + + it "accepts any argument that can be coerced with Float()" do + Math.log1p(MathSpecs::Float.new).should be_close(0.6931471805599453, TOLERANCE) + end + end + + describe "Math#log1p" do + it "is accessible as a private instance method" do + IncludesMath.new.send(:log1p, 4.21).should be_close(1.65057985576528, TOLERANCE) + end + end +end diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb index a434e7b0b8fc2a..964f64c593b8da 100644 --- a/spec/ruby/core/module/fixtures/classes.rb +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -1,6 +1,6 @@ module ModuleSpecs def self.without_test_modules(modules) - ignore = %w[MSpecRSpecAdapter PP::ObjectMixin ModuleSpecs::IncludedInObject MainSpecs::Module ConstantSpecs::ModuleA] + ignore = %w[MSpecRSpecAdapter PP::ObjectMixin MainSpecs::Module ConstantSpecs::ModuleA] modules.reject { |k| ignore.include?(k.name) } end diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index e9bc9a1c5798af..cf8a8f5b12b942 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -158,4 +158,18 @@ it "returns :nokey for **nil parameter" do proc { |**nil| }.parameters.should == [[:nokey]] end + + ruby_version_is "3.4"..."4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt, nil]] + eval("lambda { it }").parameters.should == [[:req]] + end + end + + ruby_version_is "4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt]] + eval("lambda { it }").parameters.should == [[:req]] + end + end end diff --git a/spec/ruby/core/range/to_set_spec.rb b/spec/ruby/core/range/to_set_spec.rb new file mode 100644 index 00000000000000..589c0e9aedec26 --- /dev/null +++ b/spec/ruby/core/range/to_set_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative '../enumerable/fixtures/classes' + +describe "Enumerable#to_set" do + it "returns a new Set created from self" do + (1..4).to_set.should == Set[1, 2, 3, 4] + (1...4).to_set.should == Set[1, 2, 3] + end + + it "passes down passed blocks" do + (1..3).to_set { |x| x * x }.should == Set[1, 4, 9] + end + + ruby_version_is "4.0" do + it "raises a RangeError if the range is infinite" do + -> { (1..).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") + -> { (1...).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") + end + end + + ruby_version_is ""..."4.0" do + it "instantiates an object of provided as the first argument set class" do + set = (1..3).to_set(EnumerableSpecs::SetSubclass) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is "4.0"..."4.1" do + it "instantiates an object of provided as the first argument set class and warns" do + set = nil + proc { + set = (1..3).to_set(EnumerableSpecs::SetSubclass) + }.should complain(/Enumerable#to_set/) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is "4.1" do + it "does not accept any positional argument" do + -> { + (1..3).to_set(EnumerableSpecs::SetSubclass) + }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 0)') + end + end + + it "does not need explicit `require 'set'`" do + output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') + puts (1..3).to_set.to_a.inspect + RUBY + + output.chomp.should == "[1, 2, 3]" + end +end diff --git a/spec/ruby/core/regexp/linear_time_spec.rb b/spec/ruby/core/regexp/linear_time_spec.rb index c3b3500549b2e6..cf9e73c37c2b64 100644 --- a/spec/ruby/core/regexp/linear_time_spec.rb +++ b/spec/ruby/core/regexp/linear_time_spec.rb @@ -24,4 +24,10 @@ Regexp.linear_time?(/a/, Regexp::IGNORECASE) }.should complain(/warning: flags ignored/) end + + ruby_version_is "3.3" do + it "returns true for positive lookarounds" do + Regexp.linear_time?(/(?:(?=a*)a)*/).should == true + end + end end diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb index b37a447683a95c..734630bda0a620 100644 --- a/spec/ruby/core/string/unpack/shared/basic.rb +++ b/spec/ruby/core/string/unpack/shared/basic.rb @@ -9,12 +9,19 @@ "abc".unpack(d).should be_an_instance_of(Array) end + ruby_version_is ""..."3.3" do + it "warns about using an unknown directive" do + -> { "abcdefgh".unpack("a R" + unpack_format) }.should complain(/unknown unpack directive 'R' in 'a R#{unpack_format}'/) + -> { "abcdefgh".unpack("a 0" + unpack_format) }.should complain(/unknown unpack directive '0' in 'a 0#{unpack_format}'/) + -> { "abcdefgh".unpack("a :" + unpack_format) }.should complain(/unknown unpack directive ':' in 'a :#{unpack_format}'/) + end + end + ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19150 - it 'raise ArgumentError when a directive is unknown' do - -> { "abcdefgh".unpack("a R" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive 'R'/) - -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive '0'/) - -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, /unknown unpack directive ':'/) + it "raises ArgumentError when a directive is unknown" do + -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive 'K' in 'a K#{unpack_format}'") + -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive '0' in 'a 0#{unpack_format}'") + -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive ':' in 'a :#{unpack_format}'") end end end diff --git a/spec/ruby/core/string/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb index c0b0c49edeef22..20767bcc015e74 100644 --- a/spec/ruby/core/string/uplus_spec.rb +++ b/spec/ruby/core/string/uplus_spec.rb @@ -13,14 +13,48 @@ output.should == 'foobar' end - it 'returns self if the String is not frozen' do - input = 'foo' + it 'returns a mutable String itself' do + input = String.new("foo") output = +input - output.equal?(input).should == true + output.should.equal?(input) + + input << "bar" + output.should == "foobar" + end + + context 'if file has "frozen_string_literal: true" magic comment' do + it 'returns mutable copy of a literal' do + ruby_exe(fixture(__FILE__, "freeze_magic_comment.rb")).should == 'mutable' + end end - it 'returns mutable copy despite freeze-magic-comment in file' do - ruby_exe(fixture(__FILE__, "freeze_magic_comment.rb")).should == 'mutable' + context 'if file has "frozen_string_literal: false" magic comment' do + it 'returns literal string itself' do + input = 'foo' + output = +input + + output.equal?(input).should == true + end + end + + context 'if file has no frozen_string_literal magic comment' do + ruby_version_is ''...'3.4' do + it 'returns literal string itself' do + eval(<<~RUBY).should == true + s = "foo" + s.equal?(+s) + RUBY + end + end + + ruby_version_is '3.4' do + it 'returns mutable copy of a literal' do + eval(<<~RUBY).should == false + s = "foo" + s.equal?(+s) + RUBY + end + end end end diff --git a/spec/ruby/core/symbol/shared/id2name.rb b/spec/ruby/core/symbol/shared/id2name.rb index d012b7634e5510..00a9c7d7dc69a6 100644 --- a/spec/ruby/core/symbol/shared/id2name.rb +++ b/spec/ruby/core/symbol/shared/id2name.rb @@ -13,4 +13,18 @@ symbol.send(@method).encoding.should == Encoding::US_ASCII end + + ruby_version_is "3.4" do + it "warns about mutating returned string" do + -> { :bad!.send(@method).upcase! }.should complain(/warning: string returned by :bad!.to_s will be frozen in the future/) + end + + it "does not warn about mutation when Warning[:deprecated] is false" do + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + -> { :bad!.send(@method).upcase! }.should_not complain + ensure + Warning[:deprecated] = deprecated + end + end end diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb index 4d4fc66504c7a2..b2d78c50afb359 100644 --- a/spec/ruby/core/unboundmethod/equal_value_spec.rb +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -81,7 +81,7 @@ (@child1 == @parent).should == true end - it "returns false if same method but extracted from two different subclasses" do + it "returns true if same method but extracted from two different subclasses" do (@child2 == @child1).should == true (@child1 == @child2).should == true end diff --git a/spec/ruby/core/warning/categories_spec.rb b/spec/ruby/core/warning/categories_spec.rb new file mode 100644 index 00000000000000..1e310ef38badcc --- /dev/null +++ b/spec/ruby/core/warning/categories_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "Warning.categories" do + # There might be more, but these are standard across Ruby implementations + it "returns the list of possible warning categories" do + Warning.categories.should.include? :deprecated + Warning.categories.should.include? :experimental + Warning.categories.should.include? :performance + end + end +end diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb index 12677349874e5b..2e4a822e026d72 100644 --- a/spec/ruby/core/warning/warn_spec.rb +++ b/spec/ruby/core/warning/warn_spec.rb @@ -99,14 +99,14 @@ def Warning.warn(msg) ruby_version_is "3.4" do it "warns when category is :strict_unused_block but Warning[:strict_unused_block] is false" do - warn_experimental = Warning[:strict_unused_block] + warn_strict_unused_block = Warning[:strict_unused_block] Warning[:strict_unused_block] = true begin -> { Warning.warn("foo", category: :strict_unused_block) }.should complain("foo") ensure - Warning[:strict_unused_block] = warn_experimental + Warning[:strict_unused_block] = warn_strict_unused_block end end end @@ -137,14 +137,14 @@ def Warning.warn(msg) ruby_version_is "3.4" do it "doesn't print message when category is :strict_unused_block but Warning[:strict_unused_block] is false" do - warn_experimental = Warning[:strict_unused_block] + warn_strict_unused_block = Warning[:strict_unused_block] Warning[:strict_unused_block] = false begin -> { Warning.warn("foo", category: :strict_unused_block) }.should_not complain ensure - Warning[:strict_unused_block] = warn_experimental + Warning[:strict_unused_block] = warn_strict_unused_block end end end diff --git a/spec/ruby/default.mspec b/spec/ruby/default.mspec index 1e8f8893aa1f9c..c8b1215f561d9c 100644 --- a/spec/ruby/default.mspec +++ b/spec/ruby/default.mspec @@ -20,8 +20,11 @@ class MSpecScript # C extension API specs set :capi, [ 'optional/capi' ] + # Thread safety specs + set :thread_safety, [ 'optional/thread_safety' ] + # A list of _all_ optional specs - set :optional, get(:capi) + set :optional, get(:capi) + get(:thread_safety) # An ordered list of the directories containing specs to run set :files, get(:command_line) + get(:language) + get(:core) + get(:library) + get(:security) + get(:optional) diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index e1e4a363c82b74..cc003b8946270e 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -1049,6 +1049,12 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) end + it "emits a deprecation warning if numbered parameters are used" do + -> { + eval "proc { it; _1 }" + }.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/) + end + it "does not emit a deprecation warning when a block has parameters" do -> { eval "proc { |a, b| it }" }.should_not complain -> { eval "proc { |*rest| it }" }.should_not complain @@ -1058,21 +1064,75 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** -> { eval "proc { |**| it }" }.should_not complain -> { eval "proc { |&block| it }" }.should_not complain -> { eval "proc { |&| it }" }.should_not complain + -> { eval "proc { || it }" }.should_not complain end it "does not emit a deprecation warning when `it` calls with arguments" do -> { eval "proc { it(42) }" }.should_not complain + -> { eval "proc { it 42 }" }.should_not complain + end + + it "does not emit a deprecation warning when `it` calls with a block" do + -> { eval "proc { it {} }" }.should_not complain + end + + it "does not emit a deprecation warning when a local variable inside the block named `it` exists" do + -> { eval "proc { it = 42; it }" }.should_not complain end it "does not emit a deprecation warning when `it` calls with explicit empty arguments list" do -> { eval "proc { it() }" }.should_not complain end + + it "calls the method `it` if defined" do + o = Object.new + def o.it + 21 + end + suppress_warning do + o.instance_eval("proc { it * 2 }").call(1).should == 42 + end + end + end + + ruby_version_is "3.4" do + it "does not emit a deprecation warning" do + -> { + eval "proc { it }" + }.should_not complain + end + + it "acts as the first argument if no local variables exist" do + eval("proc { it * 2 }").call(5).should == 10 + end + + it "can be reassigned to act as a local variable" do + eval("proc { tmp = it; it = tmp * 2; it }").call(21).should == 42 + end + + it "can be used in nested calls" do + eval("proc { it.map { it * 2 } }").call([1, 2, 3]).should == [2, 4, 6] + end + + it "cannot be mixed with numbered parameters" do + -> { + eval "proc { it + _1 }" + }.should raise_error(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/) + + -> { + eval "proc { _1 + it }" + }.should raise_error(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/) + end end end -describe "if `it` is defined outside of a block" do - it "treats `it` as a captured variable" do +describe "if `it` is defined as a variable" do + it "treats `it` as a captured variable if defined outside of a block" do it = 5 proc { it }.call(0).should == 5 end + + it "treats `it` as a local variable if defined inside of a block" do + proc { it = 5; it }.call(0).should == 5 + end end diff --git a/spec/ruby/language/class_spec.rb b/spec/ruby/language/class_spec.rb index 0b770d69b58ea7..6fb785fd568d6a 100644 --- a/spec/ruby/language/class_spec.rb +++ b/spec/ruby/language/class_spec.rb @@ -46,7 +46,14 @@ class ClassSpecsKeywordWithSemicolon; end -> { class ClassSpecsNumber end - }.should raise_error(TypeError) + }.should raise_error(TypeError, /\AClassSpecsNumber is not a class/) + end + + it "raises TypeError if constant given as class name exists and is a Module but not a Class" do + -> { + class ClassSpecs + end + }.should raise_error(TypeError, /\AClassSpecs is not a class/) end # test case known to be detecting bugs (JRuby, MRI) @@ -346,6 +353,39 @@ def self.m ClassSpecs::M.m.should == 1 ClassSpecs::L.singleton_class.send(:remove_method, :m) end + + it "does not reopen a class included in Object" do + ruby_exe(<<~RUBY).should == "false" + module IncludedInObject + class IncludedClass + end + end + class Object + include IncludedInObject + end + class IncludedClass + end + print IncludedInObject::IncludedClass == Object::IncludedClass + RUBY + end + + it "does not reopen a class included in non-Object modules" do + ruby_exe(<<~RUBY).should == "false/false" + module Included + module IncludedClass; end + end + module M + include Included + module IncludedClass; end + end + class C + include Included + module IncludedClass; end + end + print Included::IncludedClass == M::IncludedClass, "/", + Included::IncludedClass == C::IncludedClass + RUBY + end end describe "class provides hooks" do diff --git a/spec/ruby/language/fixtures/module.rb b/spec/ruby/language/fixtures/module.rb index 33d323846e2d8d..75eee7779172a6 100644 --- a/spec/ruby/language/fixtures/module.rb +++ b/spec/ruby/language/fixtures/module.rb @@ -12,13 +12,4 @@ class Klass module Anonymous end - - module IncludedInObject - module IncludedModuleSpecs - end - end -end - -class Object - include ModuleSpecs::IncludedInObject end diff --git a/spec/ruby/language/it_parameter_spec.rb b/spec/ruby/language/it_parameter_spec.rb new file mode 100644 index 00000000000000..72023180d91d54 --- /dev/null +++ b/spec/ruby/language/it_parameter_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' + +ruby_version_is "3.4" do + describe "The `it` parameter" do + it "provides it in a block" do + -> { it }.call("a").should == "a" + proc { it }.call("a").should == "a" + lambda { it }.call("a").should == "a" + ["a"].map { it }.should == ["a"] + end + + it "assigns nil to not passed parameters" do + proc { it }.call().should == nil + end + + it "can be used in both outer and nested blocks at the same time" do + -> { it + -> { it * it }.call(2) }.call(3).should == 7 + end + + it "is a regular local variable if there is already a 'it' local variable" do + it = 0 + proc { it }.call("a").should == 0 + end + + it "raises SyntaxError when block parameters are specified explicitly" do + -> { eval("-> () { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("-> (x) { it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("proc { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("proc { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("lambda { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("lambda { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + + -> { eval("['a'].map { || it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + -> { eval("['a'].map { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) + end + + it "affects block arity" do + -> {}.arity.should == 0 + -> { it }.arity.should == 1 + end + + it "affects block parameters" do + -> { it }.parameters.should == [[:req]] + + ruby_version_is ""..."4.0" do + proc { it }.parameters.should == [[:opt, nil]] + end + ruby_version_is "4.0" do + proc { it }.parameters.should == [[:opt]] + end + end + + it "does not affect binding local variables" do + -> { it; binding.local_variables }.call("a").should == [] + end + + it "does not work in methods" do + obj = Object.new + def obj.foo; it; end + + -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) + end + end +end diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb index f2bf3a08e53ab2..90bb83e279be8a 100644 --- a/spec/ruby/language/magic_comment_spec.rb +++ b/spec/ruby/language/magic_comment_spec.rb @@ -45,10 +45,19 @@ describe "Magic comments" do describe "in stdin" do - it_behaves_like :magic_comments, :locale, -> file { - print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") - ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") - } + ruby_version_is ""..."4.0" do + it_behaves_like :magic_comments, :locale, -> file { + print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") + ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") + } + end + + ruby_version_is "4.0" do + it_behaves_like :magic_comments, :UTF8, -> file { + print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") + ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") + } + end end platform_is_not :windows do diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 39b245fe2a9de2..8f72bd45ed8cbe 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1621,12 +1621,12 @@ def m_that_does_not_use_block # ensure that warning is emitted -> { m_that_does_not_use_block { } }.should complain(verbose: true) - warn_experimental = Warning[:strict_unused_block] + warn_strict_unused_block = Warning[:strict_unused_block] Warning[:strict_unused_block] = false begin -> { m_that_does_not_use_block { } }.should_not complain(verbose: true) ensure - Warning[:strict_unused_block] = warn_experimental + Warning[:strict_unused_block] = warn_strict_unused_block end end @@ -1635,14 +1635,14 @@ def m_that_does_not_use_block 42 end - warn_experimental = Warning[:strict_unused_block] + warn_strict_unused_block = Warning[:strict_unused_block] Warning[:strict_unused_block] = true begin -> { m_that_does_not_use_block { } }.should complain(/the block passed to 'm_that_does_not_use_block' defined at .+ may be ignored/) ensure - Warning[:strict_unused_block] = warn_experimental + Warning[:strict_unused_block] = warn_strict_unused_block end end end diff --git a/spec/ruby/language/module_spec.rb b/spec/ruby/language/module_spec.rb index 4db00bd7fb9e66..fba4aa8c6e68b7 100644 --- a/spec/ruby/language/module_spec.rb +++ b/spec/ruby/language/module_spec.rb @@ -31,10 +31,34 @@ module ModuleSpecs; Reopened = true; end end it "does not reopen a module included in Object" do - module IncludedModuleSpecs; Reopened = true; end - ModuleSpecs::IncludedInObject::IncludedModuleSpecs.should_not == Object::IncludedModuleSpecs - ensure - IncludedModuleSpecs.send(:remove_const, :Reopened) + ruby_exe(<<~RUBY).should == "false" + module IncludedInObject + module IncludedModule; end + end + class Object + include IncludedInObject + end + module IncludedModule; end + print IncludedInObject::IncludedModule == Object::IncludedModule + RUBY + end + + it "does not reopen a module included in non-Object modules" do + ruby_exe(<<~RUBY).should == "false/false" + module Included + module IncludedModule; end + end + module M + include Included + module IncludedModule; end + end + class C + include Included + module IncludedModule; end + end + print Included::IncludedModule == M::IncludedModule, "/", + Included::IncludedModule == C::IncludedModule + RUBY end it "raises a TypeError if the constant is a Class" do diff --git a/spec/ruby/language/reserved_keywords.rb b/spec/ruby/language/reserved_keywords.rb new file mode 100644 index 00000000000000..6c40e34cccc92e --- /dev/null +++ b/spec/ruby/language/reserved_keywords.rb @@ -0,0 +1,149 @@ +require_relative '../spec_helper' + +describe "Ruby's reserved keywords" do + # Copied from https://github.com/ruby/ruby/blob/master/defs/keywords + keywords = %w[ + alias + and + begin + BEGIN + break + case + class + def + defined? + do + else + elsif + end + END + ensure + false + for + if + in + module + next + nil + not + or + redo + rescue + retry + return + self + super + then + true + undef + unless + until + when + while + yield + __ENCODING__ + __FILE__ + __LINE__ + ] + + keywords.each do |name| + describe "keyword '#{name}'" do + it "can't be used as local variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + #{name} = :local_variable + RUBY + end + + if name == "defined?" + it "can't be used as an instance variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + @#{name} = :instance_variable + RUBY + end + + it "can't be used as a class variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + class C + @@#{name} = :class_variable + end + RUBY + end + + it "can't be used as a global variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + $#{name} = :global_variable + RUBY + end + else + it "can be used as an instance variable name" do + result = eval <<~RUBY + @#{name} = :instance_variable + @#{name} + RUBY + + result.should == :instance_variable + end + + it "can be used as a class variable name" do + result = eval <<~RUBY + class C + @@#{name} = :class_variable + @@#{name} + end + RUBY + + result.should == :class_variable + end + + it "can be used as a global variable name" do + result = eval <<~RUBY + $#{name} = :global_variable + $#{name} + RUBY + + result.should == :global_variable + end + end + + it "can't be used as a positional parameter name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + def x(#{name}); end + RUBY + end + + invalid_kw_param_names = ["BEGIN","END","defined?"] + + if invalid_kw_param_names.include?(name) + it "can't be used a keyword parameter name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + def m(#{name}:); end + RUBY + end + else + it "can be used a keyword parameter name" do + result = instance_eval <<~RUBY + def m(#{name}:) + binding.local_variable_get(:#{name}) + end + + m(#{name}: :argument) + RUBY + + result.should == :argument + end + end + + it "can be used as a method name" do + result = instance_eval <<~RUBY + def #{name} + :method_return_value + end + + send(:#{name}) + RUBY + + result.should == :method_return_value + end + end + end +end diff --git a/spec/ruby/library/cgi/escapeURIComponent_spec.rb b/spec/ruby/library/cgi/escapeURIComponent_spec.rb index 02921ab8bf43a8..1cea2b786a626b 100644 --- a/spec/ruby/library/cgi/escapeURIComponent_spec.rb +++ b/spec/ruby/library/cgi/escapeURIComponent_spec.rb @@ -6,48 +6,67 @@ end describe "CGI.escapeURIComponent" do - it "escapes whitespace" do - string = "&<>\" \xE3\x82\x86\xE3\x82\x93\xE3\x82\x86\xE3\x82\x93" - CGI.escapeURIComponent(string).should == '%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93' + it "percent-encodes characters reserved according to RFC 3986" do + # https://www.rfc-editor.org/rfc/rfc3986#section-2.2 + string = ":/?#[]@!$&'()*+,;=" + CGI.escapeURIComponent(string).should == "%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D" end - it "does not escape with unreserved characters" do + it "does not percent-encode unreserved characters according to RFC 3986" do string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" CGI.escapeURIComponent(string).should == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" end - it "supports String with invalid encoding" do - string = "\xC0\<\<".dup.force_encoding("UTF-8") - CGI.escapeURIComponent(string).should == "%C0%3C%3C" + it "encodes % character as %25" do + CGI.escapeURIComponent("%").should == "%25" end - it "processes String bytes one by one, not characters" do - CGI.escapeURIComponent("β").should == "%CE%B2" # "β" bytes representation is CE B2 + # Compare to .escape which uses "+". + it "percent-encodes single whitespace" do + CGI.escapeURIComponent(" ").should == "%20" end - it "raises a TypeError with nil" do - -> { - CGI.escapeURIComponent(nil) - }.should raise_error(TypeError, 'no implicit conversion of nil into String') + it "percent-encodes all non-reserved and non-unreserved ASCII characters" do + special_set = ":/?#[]@!$&'()*+,;=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + all_other = (0x00..0x7F).filter_map { |i| i.chr unless special_set.include?(i.chr) }.join + encoded = CGI.escapeURIComponent(all_other) + encoded.should.match?(/\A(?:%[0-9A-F]{2}){#{all_other.length}}\z/) end - it "encodes empty string" do - CGI.escapeURIComponent("").should == "" + it "percent-encodes non-ASCII bytes" do + bytes = (0x80..0xFF).map(&:chr).join + encoded = CGI.escapeURIComponent(bytes) + encoded.should.match?(/\A(?:%[0-9A-F]{2}){#{bytes.length}}\z/) end - it "encodes single whitespace" do - CGI.escapeURIComponent(" ").should == "%20" + it "processes multi-byte characters as separate bytes, percent-encoding each one" do + CGI.escapeURIComponent("β").should == "%CE%B2" # "β" bytes representation is CE B2 end - it "encodes double whitespace" do - CGI.escapeURIComponent(" ").should == "%20%20" + it "produces a copy of an empty string" do + string = "".encode(Encoding::BINARY) + encoded = CGI.escapeURIComponent(string) + encoded.should == "" + encoded.encoding.should == Encoding::BINARY + string.should_not.equal?(encoded) end - it "preserves encoding" do + it "preserves string's encoding" do string = "whatever".encode("ASCII-8BIT") CGI.escapeURIComponent(string).encoding.should == Encoding::ASCII_8BIT end + it "processes even strings with invalid encoding, percent-encoding octets as-is" do + string = "\xC0<<".dup.force_encoding("UTF-8") + CGI.escapeURIComponent(string).should == "%C0%3C%3C" + end + + it "raises a TypeError with nil" do + -> { + CGI.escapeURIComponent(nil) + }.should raise_error(TypeError, "no implicit conversion of nil into String") + end + it "uses implicit type conversion to String" do object = Object.new def object.to_str diff --git a/spec/ruby/library/cgi/unescapeURIComponent_spec.rb b/spec/ruby/library/cgi/unescapeURIComponent_spec.rb new file mode 100644 index 00000000000000..f80eb1626b4454 --- /dev/null +++ b/spec/ruby/library/cgi/unescapeURIComponent_spec.rb @@ -0,0 +1,128 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' +end +ruby_version_is "4.0" do + require 'cgi/escape' +end + +describe "CGI.unescapeURIComponent" do + it "decodes any percent-encoded octets to their corresponding bytes according to RFC 3986" do + string = (0x00..0xff).map { |i| "%%%02x" % i }.join + expected = (0x00..0xff).map { |i| i.chr }.join.force_encoding(Encoding::UTF_8) + CGI.unescapeURIComponent(string).should == expected + end + + it "disregards case of characters in a percent-encoding triplet" do + CGI.unescapeURIComponent("%CE%B2abc").should == "βabc" + CGI.unescapeURIComponent("%ce%b2ABC").should == "βABC" + end + + it "leaves any non-percent-encoded characters as-is" do + string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ:/?#[]@!$&'()*+,;=\t\x0D\xFFβᛉ▒90%" + decoded = CGI.unescapeURIComponent(string) + decoded.should == string + string.should_not.equal?(decoded) + end + + it "leaves sequences which can't be a percent-encoded octet as-is" do + string = "%AZ%B" + decoded = CGI.unescapeURIComponent(string) + decoded.should == string + string.should_not.equal?(decoded) + end + + it "creates a String with the specified target Encoding" do + string = CGI.unescapeURIComponent("%D2%3C%3CABC", Encoding::ISO_8859_1) + string.encoding.should == Encoding::ISO_8859_1 + string.should == "Ò< { CGI.unescapeURIComponent("ABC", "ISO-JOKE-1") }.should raise_error(ArgumentError, "unknown encoding name - ISO-JOKE-1") + end + + ruby_version_is ""..."4.0" do + it "uses CGI.accept_charset as the default target encoding" do + original_charset = CGI.accept_charset + CGI.accept_charset = "ISO-8859-1" + decoded = CGI.unescapeURIComponent("%D2%3C%3CABC") + decoded.should == "Ò< { + CGI.unescapeURIComponent(nil) + }.should raise_error(TypeError, "no implicit conversion of nil into String") + end + + it "uses implicit type conversion to String" do + object = Object.new + def object.to_str + "a%20b" + end + + CGI.unescapeURIComponent(object).should == "a b" + end +end diff --git a/spec/ruby/library/socket/addrinfo/afamily_spec.rb b/spec/ruby/library/socket/addrinfo/afamily_spec.rb index 7229dab9de12c2..5d075be05758f0 100644 --- a/spec/ruby/library/socket/addrinfo/afamily_spec.rb +++ b/spec/ruby/library/socket/addrinfo/afamily_spec.rb @@ -23,15 +23,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns Socket::AF_UNIX" do - @addrinfo.afamily.should == Socket::AF_UNIX - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::AF_UNIX" do + @addrinfo.afamily.should == Socket::AF_UNIX end end end diff --git a/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb index 2bc3b6a2e34e7b..3c2f9f73d8a0a9 100644 --- a/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb +++ b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb @@ -50,38 +50,36 @@ end end - with_feature :unix_socket do - describe 'with a UNIX Addrinfo' do - before do - @source = Addrinfo.unix('cats') - end + describe 'with a UNIX Addrinfo' do + before do + @source = Addrinfo.unix('cats') + end - it 'raises ArgumentError if more than 1 argument is given' do - -> { @source.family_addrinfo('foo', 'bar') }.should raise_error(ArgumentError) - end + it 'raises ArgumentError if more than 1 argument is given' do + -> { @source.family_addrinfo('foo', 'bar') }.should raise_error(ArgumentError) + end - it 'returns an Addrinfo when a UNIX socket path is given' do - addr = @source.family_addrinfo('dogs') + it 'returns an Addrinfo when a UNIX socket path is given' do + addr = @source.family_addrinfo('dogs') - addr.should be_an_instance_of(Addrinfo) - end + addr.should be_an_instance_of(Addrinfo) + end - describe 'the returned Addrinfo' do - before do - @addr = @source.family_addrinfo('dogs') - end + describe 'the returned Addrinfo' do + before do + @addr = @source.family_addrinfo('dogs') + end - it 'uses AF_UNIX as the address family' do - @addr.afamily.should == Socket::AF_UNIX - end + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end - it 'uses PF_UNIX as the protocol family' do - @addr.pfamily.should == Socket::PF_UNIX - end + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end - it 'uses the given socket path' do - @addr.unix_path.should == 'dogs' - end + it 'uses the given socket path' do + @addr.unix_path.should == 'dogs' end end end diff --git a/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb index 76579de74c0c67..43b5a2000a8091 100644 --- a/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb +++ b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb @@ -22,19 +22,17 @@ platform_is :linux do platform_is_not :android do - with_feature :unix_socket do - describe 'using a UNIX Addrinfo' do - before do - @addr = Addrinfo.unix('cats') - @host = Socket.gethostname - end + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('cats') + @host = Socket.gethostname + end - it 'returns the hostname and UNIX socket path' do - host, path = @addr.getnameinfo + it 'returns the hostname and UNIX socket path' do + host, path = @addr.getnameinfo - host.should == @host - path.should == 'cats' - end + host.should == @host + path.should == 'cats' end end end diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb index b7477efc7959f7..1f16531aaa4dea 100644 --- a/spec/ruby/library/socket/addrinfo/initialize_spec.rb +++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb @@ -17,7 +17,7 @@ @addrinfo.ip_port.should == 25 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the UNSPEC pfamily" do @addrinfo.pfamily.should == Socket::PF_UNSPEC end @@ -53,7 +53,7 @@ @addrinfo.ip_port.should == 25 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET6 pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end @@ -83,7 +83,7 @@ @addrinfo.ip_port.should == 25 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET6 pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end @@ -113,7 +113,7 @@ @addrinfo.ip_port.should == 25 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET6 pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end @@ -147,11 +147,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the Socket::PF_INET pfamily" do + it "returns the INET pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET6 afamily" do + it "returns the INET afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -217,11 +217,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET6 afamily" do + it "returns the INET afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -247,11 +247,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET6 afamily" do + it "returns the INET afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -311,11 +311,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the Socket::UNSPEC pfamily" do + it "returns the INET pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET6 afamily" do + it "returns the INET afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -514,13 +514,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with :PF_INET family' do + it 'returns an Addrinfo with :PF_INET family' do addr = Addrinfo.new(@sockaddr, :PF_INET) addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with :INET family' do + it 'returns an Addrinfo with :INET family' do addr = Addrinfo.new(@sockaddr, :INET) addr.pfamily.should == Socket::PF_INET @@ -544,13 +544,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with "PF_INET" family' do + it 'returns an Addrinfo with "PF_INET" family' do addr = Addrinfo.new(@sockaddr, 'PF_INET') addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with "INET" family' do + it 'returns an Addrinfo with "INET" family' do addr = Addrinfo.new(@sockaddr, 'INET') addr.pfamily.should == Socket::PF_INET @@ -569,23 +569,21 @@ end end - with_feature :unix_socket do - describe 'using separate arguments for a Unix socket' do - before do - @sockaddr = Socket.pack_sockaddr_un('socket') - end + describe 'using separate arguments for a Unix socket' do + before do + @sockaddr = Socket.pack_sockaddr_un('socket') + end - it 'returns an Addrinfo with the correct unix path' do - Addrinfo.new(@sockaddr).unix_path.should == 'socket' - end + it 'returns an Addrinfo with the correct unix path' do + Addrinfo.new(@sockaddr).unix_path.should == 'socket' + end - it 'returns an Addrinfo with the correct protocol family' do - Addrinfo.new(@sockaddr).pfamily.should == Socket::PF_UNSPEC - end + it 'returns an Addrinfo with the correct protocol family' do + Addrinfo.new(@sockaddr).pfamily.should == Socket::PF_UNSPEC + end - it 'returns an Addrinfo with the correct address family' do - Addrinfo.new(@sockaddr).afamily.should == Socket::AF_UNIX - end + it 'returns an Addrinfo with the correct address family' do + Addrinfo.new(@sockaddr).afamily.should == Socket::AF_UNIX end end end diff --git a/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb index 70ca4dd4d76388..6b18c794694499 100644 --- a/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb +++ b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb @@ -32,19 +32,17 @@ end end - with_feature :unix_socket do - describe 'using a UNIX path' do - it 'returns a String containing the UNIX path' do - addr = Addrinfo.unix('/foo/bar') + describe 'using a UNIX path' do + it 'returns a String containing the UNIX path' do + addr = Addrinfo.unix('/foo/bar') - addr.inspect_sockaddr.should == '/foo/bar' - end + addr.inspect_sockaddr.should == '/foo/bar' + end - it 'returns a String containing the UNIX path when using a relative path' do - addr = Addrinfo.unix('foo') + it 'returns a String containing the UNIX path when using a relative path' do + addr = Addrinfo.unix('foo') - addr.inspect_sockaddr.should == 'UNIX foo' - end + addr.inspect_sockaddr.should == 'UNIX foo' end end end diff --git a/spec/ruby/library/socket/addrinfo/inspect_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_spec.rb index 98e1e83ffa921f..1442af61627302 100644 --- a/spec/ruby/library/socket/addrinfo/inspect_spec.rb +++ b/spec/ruby/library/socket/addrinfo/inspect_spec.rb @@ -41,25 +41,23 @@ end end - with_feature :unix_socket do - describe 'using a UNIX Addrinfo' do - it 'returns a String' do - addr = Addrinfo.unix('/foo') + describe 'using a UNIX Addrinfo' do + it 'returns a String' do + addr = Addrinfo.unix('/foo') - addr.inspect.should == '#' - end + addr.inspect.should == '#' + end - it 'returns a String when using a relative UNIX path' do - addr = Addrinfo.unix('foo') + it 'returns a String when using a relative UNIX path' do + addr = Addrinfo.unix('foo') - addr.inspect.should == '#' - end + addr.inspect.should == '#' + end - it 'returns a String when using a DGRAM socket' do - addr = Addrinfo.unix('/foo', Socket::SOCK_DGRAM) + it 'returns a String when using a DGRAM socket' do + addr = Addrinfo.unix('/foo', Socket::SOCK_DGRAM) - addr.inspect.should == '#' - end + addr.inspect.should == '#' end end end diff --git a/spec/ruby/library/socket/addrinfo/ip_address_spec.rb b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb index 4522cf5cfd4f44..193432e861cf0f 100644 --- a/spec/ruby/library/socket/addrinfo/ip_address_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb @@ -21,15 +21,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "raises an exception" do - -> { @addrinfo.ip_address }.should raise_error(SocketError) - end + it "raises an exception" do + -> { @addrinfo.ip_address }.should raise_error(SocketError) end end diff --git a/spec/ruby/library/socket/addrinfo/ip_port_spec.rb b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb index 4118607db0dab7..f10ce35143d822 100644 --- a/spec/ruby/library/socket/addrinfo/ip_port_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb @@ -21,15 +21,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "raises an exception" do - -> { @addrinfo.ip_port }.should raise_error(SocketError) - end + it "raises an exception" do + -> { @addrinfo.ip_port }.should raise_error(SocketError) end end end diff --git a/spec/ruby/library/socket/addrinfo/ip_spec.rb b/spec/ruby/library/socket/addrinfo/ip_spec.rb index 80e7a62df7c902..09b93416055933 100644 --- a/spec/ruby/library/socket/addrinfo/ip_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ip_spec.rb @@ -22,15 +22,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns false" do - @addrinfo.ip?.should be_false - end + it "returns false" do + @addrinfo.ip?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb index 6c81c48d1ca066..58260c45578972 100644 --- a/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb @@ -21,15 +21,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "raises an exception" do - -> { @addrinfo.ip_unpack }.should raise_error(SocketError) - end + it "raises an exception" do + -> { @addrinfo.ip_unpack }.should raise_error(SocketError) end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb index 10ad084fc93bb4..3a584d4f52fbcd 100644 --- a/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb @@ -29,15 +29,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns false" do - @addrinfo.ipv4_loopback?.should be_false - end + it "returns false" do + @addrinfo.ipv4_loopback?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb index f7fead86409680..e4b4cfcc84a773 100644 --- a/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb @@ -15,15 +15,13 @@ Addrinfo.ip('::1').should_not.ipv4_multicast? end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns false" do - @addrinfo.ipv4_multicast?.should be_false - end + it "returns false" do + @addrinfo.ipv4_multicast?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb index e5a33b4953f38f..97218b5ba327dd 100644 --- a/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb @@ -33,15 +33,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns false" do - @addrinfo.ipv4_private?.should be_false - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_private?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb index 7cba8209b6b1b1..61f7759b108952 100644 --- a/spec/ruby/library/socket/addrinfo/ipv4_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb @@ -21,15 +21,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns false" do - @addrinfo.ipv4?.should be_false - end + it "returns false" do + @addrinfo.ipv4?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb index 9ff8f107bf32e5..ffc75185ead603 100644 --- a/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb @@ -31,15 +31,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns false" do - @addrinfo.ipv6_loopback?.should be_false - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_loopback?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb index 2c987b59210acf..99d4e8cf4d8577 100644 --- a/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb @@ -34,15 +34,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns false" do - @addrinfo.ipv6_multicast?.should be_false - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_multicast?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb index 131e38849c3abe..436d5e930b415e 100644 --- a/spec/ruby/library/socket/addrinfo/ipv6_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb @@ -21,15 +21,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns false" do - @addrinfo.ipv6?.should be_false - end + it "returns false" do + @addrinfo.ipv6?.should be_false end end end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb index 6dfaf531aecc7a..29050bec20e9cd 100644 --- a/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb +++ b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb @@ -62,7 +62,7 @@ Addrinfo.ip('192.168.1.1').ipv6_to_ipv4.should be_nil end - with_feature :unix_socket do + describe 'for a unix socket' do it 'returns nil for a UNIX Addrinfo' do Addrinfo.unix('foo').ipv6_to_ipv4.should be_nil end diff --git a/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb index 2d69a33b53b73f..e2c3497f7fc513 100644 --- a/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb +++ b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb @@ -42,40 +42,38 @@ end end - with_feature :unix_socket do - describe 'using a UNIX Addrinfo' do + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('foo') + end + + it 'returns an Array' do + @addr.marshal_dump.should be_an_instance_of(Array) + end + + describe 'the returned Array' do before do - @addr = Addrinfo.unix('foo') + @array = @addr.marshal_dump end - it 'returns an Array' do - @addr.marshal_dump.should be_an_instance_of(Array) + it 'includes the address family as the 1st value' do + @array[0].should == 'AF_UNIX' end - describe 'the returned Array' do - before do - @array = @addr.marshal_dump - end - - it 'includes the address family as the 1st value' do - @array[0].should == 'AF_UNIX' - end - - it 'includes the UNIX path as the 2nd value' do - @array[1].should == @addr.unix_path - end + it 'includes the UNIX path as the 2nd value' do + @array[1].should == @addr.unix_path + end - it 'includes the protocol family as the 3rd value' do - @array[2].should == 'PF_UNIX' - end + it 'includes the protocol family as the 3rd value' do + @array[2].should == 'PF_UNIX' + end - it 'includes the socket type as the 4th value' do - @array[3].should == 'SOCK_STREAM' - end + it 'includes the socket type as the 4th value' do + @array[3].should == 'SOCK_STREAM' + end - it 'includes the protocol as the 5th value' do - @array[4].should == 0 - end + it 'includes the protocol as the 5th value' do + @array[4].should == 0 end end end diff --git a/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb index aa208652241ae6..02cef901158188 100644 --- a/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb +++ b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb @@ -18,18 +18,16 @@ end end - with_feature :unix_socket do - describe 'using a UNIX socket' do - it 'returns a new Addrinfo' do - source = Addrinfo.unix('foo') - addr = Marshal.load(Marshal.dump(source)) + describe 'using a UNIX socket' do + it 'returns a new Addrinfo' do + source = Addrinfo.unix('foo') + addr = Marshal.load(Marshal.dump(source)) - addr.afamily.should == source.afamily - addr.pfamily.should == source.pfamily - addr.socktype.should == source.socktype - addr.protocol.should == source.protocol - addr.unix_path.should == source.unix_path - end + addr.afamily.should == source.afamily + addr.pfamily.should == source.pfamily + addr.socktype.should == source.socktype + addr.protocol.should == source.protocol + addr.unix_path.should == source.unix_path end end end diff --git a/spec/ruby/library/socket/addrinfo/pfamily_spec.rb b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb index 984744a964a805..da530b7fdc19c1 100644 --- a/spec/ruby/library/socket/addrinfo/pfamily_spec.rb +++ b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb @@ -29,15 +29,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns Socket::PF_UNIX" do - @addrinfo.pfamily.should == Socket::PF_UNIX - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::PF_UNIX" do + @addrinfo.pfamily.should == Socket::PF_UNIX end end end diff --git a/spec/ruby/library/socket/addrinfo/protocol_spec.rb b/spec/ruby/library/socket/addrinfo/protocol_spec.rb index ea143fc4a80c19..f6ffc9acf9e893 100644 --- a/spec/ruby/library/socket/addrinfo/protocol_spec.rb +++ b/spec/ruby/library/socket/addrinfo/protocol_spec.rb @@ -10,15 +10,13 @@ Addrinfo.tcp('::1', 80).protocol.should == Socket::IPPROTO_TCP end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns 0" do - @addrinfo.protocol.should == 0 - end + it "returns 0" do + @addrinfo.protocol.should == 0 end end end diff --git a/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb index 4f7cf439a07ac1..70d6bfbbfed7a7 100644 --- a/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb +++ b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb @@ -19,15 +19,13 @@ end end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns a sockaddr packed structure" do - @addrinfo.send(@method).should == Socket.sockaddr_un('/tmp/sock') - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_un('/tmp/sock') end end diff --git a/spec/ruby/library/socket/addrinfo/socktype_spec.rb b/spec/ruby/library/socket/addrinfo/socktype_spec.rb index b994bea140d63f..e5f02cd7593cf3 100644 --- a/spec/ruby/library/socket/addrinfo/socktype_spec.rb +++ b/spec/ruby/library/socket/addrinfo/socktype_spec.rb @@ -9,15 +9,13 @@ Addrinfo.tcp('127.0.0.1', 80).socktype.should == Socket::SOCK_STREAM end - with_feature :unix_socket do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns Socket::SOCK_STREAM" do - @addrinfo.socktype.should == Socket::SOCK_STREAM - end + it "returns Socket::SOCK_STREAM" do + @addrinfo.socktype.should == Socket::SOCK_STREAM end end end diff --git a/spec/ruby/library/socket/addrinfo/unix_path_spec.rb b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb index 6bfb56a4ac9cc3..2a9076a3544271 100644 --- a/spec/ruby/library/socket/addrinfo/unix_path_spec.rb +++ b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb @@ -1,37 +1,35 @@ require_relative '../spec_helper' -with_feature :unix_socket do - describe "Addrinfo#unix_path" do - describe "for an ipv4 socket" do +describe "Addrinfo#unix_path" do + describe "for an ipv4 socket" do - before :each do - @addrinfo = Addrinfo.tcp("127.0.0.1", 80) - end - - it "raises an exception" do - -> { @addrinfo.unix_path }.should raise_error(SocketError) - end + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) end - describe "for an ipv6 socket" do - before :each do - @addrinfo = Addrinfo.tcp("::1", 80) - end + end - it "raises an exception" do - -> { @addrinfo.unix_path }.should raise_error(SocketError) - end + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) end - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end - it "returns the socket path" do - @addrinfo.unix_path.should == "/tmp/sock" - end + it "returns the socket path" do + @addrinfo.unix_path.should == "/tmp/sock" end end end diff --git a/spec/ruby/library/socket/addrinfo/unix_spec.rb b/spec/ruby/library/socket/addrinfo/unix_spec.rb index 4596ece17e5d9a..7597533a769c7d 100644 --- a/spec/ruby/library/socket/addrinfo/unix_spec.rb +++ b/spec/ruby/library/socket/addrinfo/unix_spec.rb @@ -1,36 +1,34 @@ require_relative '../spec_helper' -with_feature :unix_socket do - describe 'Addrinfo.unix' do - it 'returns an Addrinfo instance' do - Addrinfo.unix('socket').should be_an_instance_of(Addrinfo) - end +describe 'Addrinfo.unix' do + it 'returns an Addrinfo instance' do + Addrinfo.unix('socket').should be_an_instance_of(Addrinfo) + end - it 'sets the IP address' do - Addrinfo.unix('socket').unix_path.should == 'socket' - end + it 'sets the IP address' do + Addrinfo.unix('socket').unix_path.should == 'socket' + end - it 'sets the address family' do - Addrinfo.unix('socket').afamily.should == Socket::AF_UNIX - end + it 'sets the address family' do + Addrinfo.unix('socket').afamily.should == Socket::AF_UNIX + end - it 'sets the protocol family' do - Addrinfo.unix('socket').pfamily.should == Socket::PF_UNIX - end + it 'sets the protocol family' do + Addrinfo.unix('socket').pfamily.should == Socket::PF_UNIX + end - it 'sets the socket type' do - Addrinfo.unix('socket').socktype.should == Socket::SOCK_STREAM - end + it 'sets the socket type' do + Addrinfo.unix('socket').socktype.should == Socket::SOCK_STREAM + end - it 'sets a custom socket type' do - addr = Addrinfo.unix('socket', Socket::SOCK_DGRAM) + it 'sets a custom socket type' do + addr = Addrinfo.unix('socket', Socket::SOCK_DGRAM) - addr.socktype.should == Socket::SOCK_DGRAM - end + addr.socktype.should == Socket::SOCK_DGRAM + end - it 'sets the socket protocol to 0' do - Addrinfo.unix('socket').protocol.should == 0 - end + it 'sets the socket protocol to 0' do + Addrinfo.unix('socket').protocol.should == 0 end end diff --git a/spec/ruby/library/socket/basicsocket/connect_address_spec.rb b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb index 1a1c9982d942ca..2e318fcb851391 100644 --- a/spec/ruby/library/socket/basicsocket/connect_address_spec.rb +++ b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb @@ -94,61 +94,59 @@ end end - with_feature :unix_socket do - platform_is_not :aix do - describe 'using an unbound UNIX socket' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - @client = UNIXSocket.new(@path) - end - - after do - @client.close - @server.close - rm_r(@path) - end - - it 'raises SocketError' do - -> { @client.connect_address }.should raise_error(SocketError) - end - end - end - - describe 'using a bound UNIX socket' do + platform_is_not :aix do + describe 'using an unbound UNIX socket' do before do @path = SocketSpecs.socket_path - @sock = UNIXServer.new(@path) + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) end after do - @sock.close + @client.close + @server.close rm_r(@path) end - it 'returns an Addrinfo' do - @sock.connect_address.should be_an_instance_of(Addrinfo) + it 'raises SocketError' do + -> { @client.connect_address }.should raise_error(SocketError) end + end + end - it 'uses the correct socket path' do - @sock.connect_address.unix_path.should == @path - end + describe 'using a bound UNIX socket' do + before do + @path = SocketSpecs.socket_path + @sock = UNIXServer.new(@path) + end - it 'uses AF_UNIX as the address family' do - @sock.connect_address.afamily.should == Socket::AF_UNIX - end + after do + @sock.close + rm_r(@path) + end - it 'uses PF_UNIX as the protocol family' do - @sock.connect_address.pfamily.should == Socket::PF_UNIX - end + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end - it 'uses SOCK_STREAM as the socket type' do - @sock.connect_address.socktype.should == Socket::SOCK_STREAM - end + it 'uses the correct socket path' do + @sock.connect_address.unix_path.should == @path + end - it 'uses 0 as the protocol' do - @sock.connect_address.protocol.should == 0 - end + it 'uses AF_UNIX as the address family' do + @sock.connect_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @sock.connect_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 end end end diff --git a/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb index 6179211d9617d3..2e03cd3684bd99 100644 --- a/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb +++ b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb @@ -2,7 +2,7 @@ require_relative '../fixtures/classes' describe 'BasicSocket#getpeereid' do - with_feature :unix_socket do + platform_is_not :windows do describe 'using a UNIXSocket' do before do @path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb index 1e8d84e1c9ecd0..f686e673263789 100644 --- a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb +++ b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb @@ -315,22 +315,20 @@ end end - with_feature :unix_socket do - describe 'using a UNIX socket' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - end + describe 'using a UNIX socket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end - after do - @server.close - rm_r @path - end + after do + @server.close + rm_r @path + end - it 'sets a boolean option' do - @server.setsockopt(:SOCKET, :REUSEADDR, true) - @server.getsockopt(:SOCKET, :REUSEADDR).bool.should == true - end + it 'sets a boolean option' do + @server.setsockopt(:SOCKET, :REUSEADDR, true) + @server.getsockopt(:SOCKET, :REUSEADDR).bool.should == true end end end diff --git a/spec/ruby/library/socket/shared/address.rb b/spec/ruby/library/socket/shared/address.rb index f3be9cfb9985e0..b548f1eac06464 100644 --- a/spec/ruby/library/socket/shared/address.rb +++ b/spec/ruby/library/socket/shared/address.rb @@ -129,41 +129,51 @@ end end - with_feature :unix_socket do - describe 'using UNIXSocket' do - before :each do - @path = SocketSpecs.socket_path - @s = UNIXServer.new(@path) - @a = UNIXSocket.new(@path) - @b = @s.accept - @addr = @object.call(@a) - end + describe 'using UNIXSocket' do + before :each do + @path = SocketSpecs.socket_path + @s = UNIXServer.new(@path) + @a = UNIXSocket.new(@path) + @b = @s.accept + @addr = @object.call(@a) + end - after :each do - [@b, @a, @s].each(&:close) - rm_r(@path) - end + after :each do + [@b, @a, @s].each(&:close) + rm_r(@path) + end - it 'uses AF_UNIX as the address family' do - @addr.afamily.should == Socket::AF_UNIX - end + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end - it 'uses PF_UNIX as the protocol family' do - @addr.pfamily.should == Socket::PF_UNIX - end + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end - it 'uses SOCK_STREAM as the socket type' do - @addr.socktype.should == Socket::SOCK_STREAM + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct socket path' do + if @method == :local_address + @addr.unix_path.should == "" + else + @addr.unix_path.should == @path end + end - it 'uses the correct socket path' do + platform_is_not :windows do + it 'equals address of peer socket' do if @method == :local_address - @addr.unix_path.should == "" + @addr.to_s.should == @b.remote_address.to_s else - @addr.unix_path.should == @path + @addr.to_s.should == @b.local_address.to_s end end + end + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do it 'equals address of peer socket' do if @method == :local_address @addr.to_s.should == @b.remote_address.to_s @@ -171,23 +181,23 @@ @addr.to_s.should == @b.local_address.to_s end end + end - it 'returns an Addrinfo' do - @addr.should be_an_instance_of(Addrinfo) - end + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end - it 'uses 0 as the protocol' do - @addr.protocol.should == 0 - end + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end - it 'can be used to connect to the server' do - skip if @method == :local_address - b = @addr.connect - begin - b.remote_address.to_s.should == @addr.to_s - ensure - b.close - end + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close end end end diff --git a/spec/ruby/library/socket/shared/pack_sockaddr.rb b/spec/ruby/library/socket/shared/pack_sockaddr.rb index f309aa02c7ad03..4bfcf4edb9d908 100644 --- a/spec/ruby/library/socket/shared/pack_sockaddr.rb +++ b/spec/ruby/library/socket/shared/pack_sockaddr.rb @@ -47,23 +47,21 @@ end describe :socket_pack_sockaddr_un, shared: true do - with_feature :unix_socket do - it 'should be idempotent' do - bytes = Socket.public_send(@method, '/tmp/foo').bytes - bytes[2..9].should == [47, 116, 109, 112, 47, 102, 111, 111] - bytes[10..-1].all?(&:zero?).should == true - end + it 'should be idempotent' do + bytes = Socket.public_send(@method, '/tmp/foo').bytes + bytes[2..9].should == [47, 116, 109, 112, 47, 102, 111, 111] + bytes[10..-1].all?(&:zero?).should == true + end - it "packs and unpacks" do - sockaddr_un = Socket.public_send(@method, '/tmp/s') - Socket.unpack_sockaddr_un(sockaddr_un).should == '/tmp/s' - end + it "packs and unpacks" do + sockaddr_un = Socket.public_send(@method, '/tmp/s') + Socket.unpack_sockaddr_un(sockaddr_un).should == '/tmp/s' + end - it "handles correctly paths with multibyte chars" do - sockaddr_un = Socket.public_send(@method, '/home/вася/sock') - path = Socket.unpack_sockaddr_un(sockaddr_un).encode('UTF-8', 'UTF-8') - path.should == '/home/вася/sock' - end + it "handles correctly paths with multibyte chars" do + sockaddr_un = Socket.public_send(@method, '/home/вася/sock') + path = Socket.unpack_sockaddr_un(sockaddr_un).encode('UTF-8', 'UTF-8') + path.should == '/home/вася/sock' end platform_is :linux do @@ -84,7 +82,7 @@ end end - platform_is_not :windows, :aix do + platform_is_not :aix do it "raises ArgumentError for paths that are too long" do # AIX doesn't raise error long_path = 'a' * 110 diff --git a/spec/ruby/library/socket/socket/unix_server_loop_spec.rb b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb index 0f34d4a50baa89..6192bc8bf6d166 100644 --- a/spec/ruby/library/socket/socket/unix_server_loop_spec.rb +++ b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb @@ -1,58 +1,56 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'Socket.unix_server_loop' do - before do - @path = SocketSpecs.socket_path - end +describe 'Socket.unix_server_loop' do + before do + @path = SocketSpecs.socket_path + end - after do - rm_r(@path) if File.file?(@path) - end + after do + rm_r(@path) if File.file?(@path) + end - describe 'when no connections are available' do - it 'blocks the caller' do - -> { Socket.unix_server_loop(@path) }.should block_caller - end + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.unix_server_loop(@path) }.should block_caller end + end - describe 'when a connection is available' do - before do - @client = nil - end + describe 'when a connection is available' do + before do + @client = nil + end - after do - @sock.close if @sock - @client.close if @client - end + after do + @sock.close if @sock + @client.close if @client + end - it 'yields a Socket and an Addrinfo' do - @sock, addr = nil + it 'yields a Socket and an Addrinfo' do + @sock, addr = nil - thread = Thread.new do - Socket.unix_server_loop(@path) do |socket, addrinfo| - @sock = socket - addr = addrinfo + thread = Thread.new do + Socket.unix_server_loop(@path) do |socket, addrinfo| + @sock = socket + addr = addrinfo - break - end + break end + end - SocketSpecs.loop_with_timeout do - begin - @client = Socket.unix(@path) - rescue SystemCallError - sleep 0.01 - :retry - end + SocketSpecs.loop_with_timeout do + begin + @client = Socket.unix(@path) + rescue SystemCallError + sleep 0.01 + :retry end + end - thread.join + thread.join - @sock.should be_an_instance_of(Socket) - addr.should be_an_instance_of(Addrinfo) - end + @sock.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) end end end diff --git a/spec/ruby/library/socket/socket/unix_server_socket_spec.rb b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb index fc357740fa79bd..34c3b96d077e7f 100644 --- a/spec/ruby/library/socket/socket/unix_server_socket_spec.rb +++ b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb @@ -1,48 +1,46 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'Socket.unix_server_socket' do +describe 'Socket.unix_server_socket' do + before do + @path = SocketSpecs.socket_path + end + + after do + rm_r(@path) + end + + describe 'when no block is given' do before do - @path = SocketSpecs.socket_path + @socket = nil end after do - rm_r(@path) + @socket.close end - describe 'when no block is given' do - before do - @socket = nil - end - - after do - @socket.close - end + it 'returns a Socket' do + @socket = Socket.unix_server_socket(@path) - it 'returns a Socket' do - @socket = Socket.unix_server_socket(@path) - - @socket.should be_an_instance_of(Socket) - end + @socket.should be_an_instance_of(Socket) end + end - describe 'when a block is given' do - it 'yields a Socket' do - Socket.unix_server_socket(@path) do |sock| - sock.should be_an_instance_of(Socket) - end + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix_server_socket(@path) do |sock| + sock.should be_an_instance_of(Socket) end + end - it 'closes the Socket when the block returns' do - socket = nil - - Socket.unix_server_socket(@path) do |sock| - socket = sock - end + it 'closes the Socket when the block returns' do + socket = nil - socket.should be_an_instance_of(Socket) + Socket.unix_server_socket(@path) do |sock| + socket = sock end + + socket.should be_an_instance_of(Socket) end end end diff --git a/spec/ruby/library/socket/socket/unix_spec.rb b/spec/ruby/library/socket/socket/unix_spec.rb index 4bff59bd4b3d44..2a5d77f96fd34e 100644 --- a/spec/ruby/library/socket/socket/unix_spec.rb +++ b/spec/ruby/library/socket/socket/unix_spec.rb @@ -1,45 +1,43 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'Socket.unix' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - @socket = nil - end +describe 'Socket.unix' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = nil + end - after do - @server.close - @socket.close if @socket + after do + @server.close + @socket.close if @socket - rm_r(@path) - end + rm_r(@path) + end - describe 'when no block is given' do - it 'returns a Socket' do - @socket = Socket.unix(@path) + describe 'when no block is given' do + it 'returns a Socket' do + @socket = Socket.unix(@path) - @socket.should be_an_instance_of(Socket) - end + @socket.should be_an_instance_of(Socket) end + end - describe 'when a block is given' do - it 'yields a Socket' do - Socket.unix(@path) do |sock| - sock.should be_an_instance_of(Socket) - end + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix(@path) do |sock| + sock.should be_an_instance_of(Socket) end + end - it 'closes the Socket when the block returns' do - socket = nil - - Socket.unix(@path) do |sock| - socket = sock - end + it 'closes the Socket when the block returns' do + socket = nil - socket.should.closed? + Socket.unix(@path) do |sock| + socket = sock end + + socket.should.closed? end end end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb index 79ec68cd184391..935b5cb5439c32 100644 --- a/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb @@ -32,15 +32,13 @@ end end - with_feature :unix_socket do - it "raises an ArgumentError when the sin_family is not AF_INET" do - sockaddr = Socket.sockaddr_un '/tmp/x' - -> { Socket.unpack_sockaddr_in sockaddr }.should raise_error(ArgumentError) - end + it "raises an ArgumentError when the sin_family is not AF_INET" do + sockaddr = Socket.sockaddr_un '/tmp/x' + -> { Socket.unpack_sockaddr_in sockaddr }.should raise_error(ArgumentError) + end - it "raises an ArgumentError when passed addrinfo is not AF_INET/AF_INET6" do - addrinfo = Addrinfo.unix('/tmp/sock') - -> { Socket.unpack_sockaddr_in(addrinfo) }.should raise_error(ArgumentError) - end + it "raises an ArgumentError when passed addrinfo is not AF_INET/AF_INET6" do + addrinfo = Addrinfo.unix('/tmp/sock') + -> { Socket.unpack_sockaddr_in(addrinfo) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb index 12f970f89b4b02..6e0f11de3d2db3 100644 --- a/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb @@ -1,26 +1,24 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'Socket.unpack_sockaddr_un' do - it 'decodes sockaddr to unix path' do - sockaddr = Socket.sockaddr_un('/tmp/sock') - Socket.unpack_sockaddr_un(sockaddr).should == '/tmp/sock' - end +describe 'Socket.unpack_sockaddr_un' do + it 'decodes sockaddr to unix path' do + sockaddr = Socket.sockaddr_un('/tmp/sock') + Socket.unpack_sockaddr_un(sockaddr).should == '/tmp/sock' + end - it 'returns unix path from a passed Addrinfo' do - addrinfo = Addrinfo.unix('/tmp/sock') - Socket.unpack_sockaddr_un(addrinfo).should == '/tmp/sock' - end + it 'returns unix path from a passed Addrinfo' do + addrinfo = Addrinfo.unix('/tmp/sock') + Socket.unpack_sockaddr_un(addrinfo).should == '/tmp/sock' + end - it 'raises an ArgumentError when the sa_family is not AF_UNIX' do - sockaddr = Socket.sockaddr_in(0, '127.0.0.1') - -> { Socket.unpack_sockaddr_un(sockaddr) }.should raise_error(ArgumentError) - end + it 'raises an ArgumentError when the sa_family is not AF_UNIX' do + sockaddr = Socket.sockaddr_in(0, '127.0.0.1') + -> { Socket.unpack_sockaddr_un(sockaddr) }.should raise_error(ArgumentError) + end - it 'raises an ArgumentError when passed addrinfo is not AF_UNIX' do - addrinfo = Addrinfo.tcp('127.0.0.1', 0) - -> { Socket.unpack_sockaddr_un(addrinfo) }.should raise_error(ArgumentError) - end + it 'raises an ArgumentError when passed addrinfo is not AF_UNIX' do + addrinfo = Addrinfo.tcp('127.0.0.1', 0) + -> { Socket.unpack_sockaddr_un(addrinfo) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/socket/spec_helper.rb b/spec/ruby/library/socket/spec_helper.rb index 1121542dd50848..b33663e02da119 100644 --- a/spec/ruby/library/socket/spec_helper.rb +++ b/spec/ruby/library/socket/spec_helper.rb @@ -2,7 +2,6 @@ require 'socket' MSpec.enable_feature :sock_packet if Socket.const_defined?(:SOCK_PACKET) -MSpec.enable_feature :unix_socket unless PlatformGuard.windows? MSpec.enable_feature :udp_cork if Socket.const_defined?(:UDP_CORK) MSpec.enable_feature :tcp_cork if Socket.const_defined?(:TCP_CORK) MSpec.enable_feature :pktinfo if Socket.const_defined?(:IP_PKTINFO) diff --git a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb index dba3de7359d258..f67941b296dca1 100644 --- a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb +++ b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb @@ -1,87 +1,85 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXServer#accept_nonblock" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @client = UNIXSocket.open(@path) +describe "UNIXServer#accept_nonblock" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) - @socket = @server.accept_nonblock - @client.send("foobar", 0) - end + @socket = @server.accept_nonblock + @client.send("foobar", 0) + end - after :each do - @socket.close - @client.close - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @socket.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end - it "accepts a connection in a non-blocking way" do - data = @socket.recvfrom(6).first - data.should == "foobar" - end + it "accepts a connection in a non-blocking way" do + data = @socket.recvfrom(6).first + data.should == "foobar" + end - it "returns a UNIXSocket" do - @socket.should be_kind_of(UNIXSocket) - end + it "returns a UNIXSocket" do + @socket.should be_kind_of(UNIXSocket) + end + + it 'returns :wait_readable in exceptionless mode' do + @server.accept_nonblock(exception: false).should == :wait_readable + end +end - it 'returns :wait_readable in exceptionless mode' do - @server.accept_nonblock(exception: false).should == :wait_readable +describe 'UNIXServer#accept_nonblock' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) end end - describe 'UNIXServer#accept_nonblock' do + describe 'with a client' do before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) end after do - @server.close - rm_r(@path) + @client.close + @socket.close if @socket end - describe 'without a client' do - it 'raises IO::WaitReadable' do - -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) end end - describe 'with a client' do + describe 'with data available' do before do - @client = UNIXSocket.new(@path) - end - - after do - @client.close - @socket.close if @socket + @client.write('hello') end - describe 'without any data' do - it 'returns a UNIXSocket' do - @socket = @server.accept_nonblock - @socket.should be_an_instance_of(UNIXSocket) - end + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) end - describe 'with data available' do - before do - @client.write('hello') - end - - it 'returns a UNIXSocket' do + describe 'the returned UNIXSocket' do + it 'can read the data written' do @socket = @server.accept_nonblock - @socket.should be_an_instance_of(UNIXSocket) - end - - describe 'the returned UNIXSocket' do - it 'can read the data written' do - @socket = @server.accept_nonblock - @socket.recv(5).should == 'hello' - end + @socket.recv(5).should == 'hello' end end end diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb index 1305bc6220d380..cc2c922b6fca23 100644 --- a/spec/ruby/library/socket/unixserver/accept_spec.rb +++ b/spec/ruby/library/socket/unixserver/accept_spec.rb @@ -1,126 +1,124 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXServer#accept" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - end +describe "UNIXServer#accept" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end - after :each do - @server.close if @server - SocketSpecs.rm_socket @path - end + after :each do + @server.close if @server + SocketSpecs.rm_socket @path + end - it "accepts what is written by the client" do - client = UNIXSocket.open(@path) + it "accepts what is written by the client" do + client = UNIXSocket.open(@path) - client.send('hello', 0) + client.send('hello', 0) - sock = @server.accept - begin - data, info = sock.recvfrom(5) + sock = @server.accept + begin + data, info = sock.recvfrom(5) - data.should == 'hello' - info.should_not be_empty - ensure - sock.close - client.close - end + data.should == 'hello' + info.should_not be_empty + ensure + sock.close + client.close end + end - it "can be interrupted by Thread#kill" do - t = Thread.new { - @server.accept - } - Thread.pass while t.status and t.status != "sleep" - - # kill thread, ensure it dies in a reasonable amount of time - t.kill - a = 0 - while t.alive? and a < 5000 - sleep 0.001 - a += 1 - end - a.should < 5000 + it "can be interrupted by Thread#kill" do + t = Thread.new { + @server.accept + } + Thread.pass while t.status and t.status != "sleep" + + # kill thread, ensure it dies in a reasonable amount of time + t.kill + a = 0 + while t.alive? and a < 5000 + sleep 0.001 + a += 1 end + a.should < 5000 + end - it "can be interrupted by Thread#raise" do - t = Thread.new { - -> { - @server.accept - }.should raise_error(Exception, "interrupted") - } + it "can be interrupted by Thread#raise" do + t = Thread.new { + -> { + @server.accept + }.should raise_error(Exception, "interrupted") + } - Thread.pass while t.status and t.status != "sleep" - t.raise Exception, "interrupted" - t.join - end + Thread.pass while t.status and t.status != "sleep" + t.raise Exception, "interrupted" + t.join end end -with_feature :unix_socket do - describe 'UNIXServer#accept' do +describe 'UNIXServer#accept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.accept }.should block_caller + end + end + + describe 'with a client' do before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) end after do - @server.close - rm_r(@path) + @client.close + @socket.close if @socket end - describe 'without a client' do - it 'blocks the calling thread' do - -> { @server.accept }.should block_caller + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) end end - describe 'with a client' do + describe 'with data available' do before do - @client = UNIXSocket.new(@path) - end - - after do - @client.close - @socket.close if @socket + @client.write('hello') end - describe 'without any data' do - it 'returns a UNIXSocket' do - @socket = @server.accept - @socket.should be_an_instance_of(UNIXSocket) - end + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) end - describe 'with data available' do - before do - @client.write('hello') - end - - it 'returns a UNIXSocket' do + describe 'the returned UNIXSocket' do + it 'can read the data written' do @socket = @server.accept - @socket.should be_an_instance_of(UNIXSocket) + @socket.recv(5).should == 'hello' end - describe 'the returned UNIXSocket' do - it 'can read the data written' do - @socket = @server.accept - @socket.recv(5).should == 'hello' - end - + platform_is_not :windows do it "is set to nonblocking" do require 'io/nonblock' @socket = @server.accept @socket.should.nonblock? end + end - it "is set to close on exec" do - @socket = @server.accept - @socket.should.close_on_exec? - end + it "is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? end end end diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb index 8cc55ef391cc98..be1c2df4d72063 100644 --- a/spec/ruby/library/socket/unixserver/for_fd_spec.rb +++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb @@ -1,23 +1,21 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXServer.for_fd" do - before :each do - @unix_path = SocketSpecs.socket_path - @unix = UNIXServer.new(@unix_path) - end +describe "UNIXServer.for_fd" do + before :each do + @unix_path = SocketSpecs.socket_path + @unix = UNIXServer.new(@unix_path) + end - after :each do - @unix.close if @unix - SocketSpecs.rm_socket @unix_path - end + after :each do + @unix.close if @unix + SocketSpecs.rm_socket @unix_path + end - it "can calculate the path" do - b = UNIXServer.for_fd(@unix.fileno) - b.autoclose = false + it "can calculate the path" do + b = UNIXServer.for_fd(@unix.fileno) + b.autoclose = false - b.path.should == @unix_path - end + b.path.should == @unix_path end end diff --git a/spec/ruby/library/socket/unixserver/initialize_spec.rb b/spec/ruby/library/socket/unixserver/initialize_spec.rb index 0cc49ef1ebc768..3728a307b05c2d 100644 --- a/spec/ruby/library/socket/unixserver/initialize_spec.rb +++ b/spec/ruby/library/socket/unixserver/initialize_spec.rb @@ -1,28 +1,26 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXServer#initialize' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - end +describe 'UNIXServer#initialize' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end - after do - @server.close if @server - rm_r @path - end + after do + @server.close if @server + rm_r @path + end - it 'returns a new UNIXServer' do - @server.should be_an_instance_of(UNIXServer) - end + it 'returns a new UNIXServer' do + @server.should be_an_instance_of(UNIXServer) + end - it 'sets the socket to binmode' do - @server.binmode?.should be_true - end + it 'sets the socket to binmode' do + @server.binmode?.should be_true + end - it 'raises Errno::EADDRINUSE when the socket is already in use' do - -> { UNIXServer.new(@path) }.should raise_error(Errno::EADDRINUSE) - end + it 'raises Errno::EADDRINUSE when the socket is already in use' do + -> { UNIXServer.new(@path) }.should raise_error(Errno::EADDRINUSE) end end diff --git a/spec/ruby/library/socket/unixserver/listen_spec.rb b/spec/ruby/library/socket/unixserver/listen_spec.rb index b90b3bbb09f061..7938d648c4016e 100644 --- a/spec/ruby/library/socket/unixserver/listen_spec.rb +++ b/spec/ruby/library/socket/unixserver/listen_spec.rb @@ -1,21 +1,19 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXServer#listen' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - end +describe 'UNIXServer#listen' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end - after do - @server.close + after do + @server.close - rm_r(@path) - end + rm_r(@path) + end - it 'returns 0' do - @server.listen(1).should == 0 - end + it 'returns 0' do + @server.listen(1).should == 0 end end diff --git a/spec/ruby/library/socket/unixserver/new_spec.rb b/spec/ruby/library/socket/unixserver/new_spec.rb index a160e3ce5c8050..7d0c7bf76ee194 100644 --- a/spec/ruby/library/socket/unixserver/new_spec.rb +++ b/spec/ruby/library/socket/unixserver/new_spec.rb @@ -1,14 +1,12 @@ require_relative '../spec_helper' require_relative 'shared/new' -with_feature :unix_socket do - describe "UNIXServer.new" do - it_behaves_like :unixserver_new, :new +describe "UNIXServer.new" do + it_behaves_like :unixserver_new, :new - it "does not use the given block and warns to use UNIXServer::open" do - -> { - @server = UNIXServer.new(@path) { raise } - }.should complain(/warning: UNIXServer::new\(\) does not take block; use UNIXServer::open\(\) instead/) - end + it "does not use the given block and warns to use UNIXServer::open" do + -> { + @server = UNIXServer.new(@path) { raise } + }.should complain(/warning: UNIXServer::new\(\) does not take block; use UNIXServer::open\(\) instead/) end end diff --git a/spec/ruby/library/socket/unixserver/open_spec.rb b/spec/ruby/library/socket/unixserver/open_spec.rb index 16453dd3bd877f..c49df802d067e7 100644 --- a/spec/ruby/library/socket/unixserver/open_spec.rb +++ b/spec/ruby/library/socket/unixserver/open_spec.rb @@ -2,25 +2,23 @@ require_relative '../fixtures/classes' require_relative 'shared/new' -with_feature :unix_socket do - describe "UNIXServer.open" do - it_behaves_like :unixserver_new, :open +describe "UNIXServer.open" do + it_behaves_like :unixserver_new, :open - before :each do - @path = SocketSpecs.socket_path - end + before :each do + @path = SocketSpecs.socket_path + end - after :each do - @server.close if @server - @server = nil - SocketSpecs.rm_socket @path - end + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end - it "yields the new UNIXServer object to the block, if given" do - UNIXServer.open(@path) do |unix| - unix.path.should == @path - unix.addr.should == ["AF_UNIX", @path] - end + it "yields the new UNIXServer object to the block, if given" do + UNIXServer.open(@path) do |unix| + unix.path.should == @path + unix.addr.should == ["AF_UNIX", @path] end end end diff --git a/spec/ruby/library/socket/unixserver/sysaccept_spec.rb b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb index e59731878aa419..c4a4ecc824b618 100644 --- a/spec/ruby/library/socket/unixserver/sysaccept_spec.rb +++ b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb @@ -1,51 +1,49 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXServer#sysaccept' do +describe 'UNIXServer#sysaccept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.sysaccept }.should block_caller + end + end + + describe 'with a client' do before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) end after do - @server.close - - rm_r(@path) + Socket.for_fd(@fd).close if @fd + @client.close end - describe 'without a client' do - it 'blocks the calling thread' do - -> { @server.sysaccept }.should block_caller + describe 'without any data' do + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) end end - describe 'with a client' do + describe 'with data available' do before do - @client = UNIXSocket.new(@path) - end - - after do - Socket.for_fd(@fd).close if @fd - @client.close + @client.write('hello') end - describe 'without any data' do - it 'returns an Integer' do - @fd = @server.sysaccept - @fd.should be_kind_of(Integer) - end - end - - describe 'with data available' do - before do - @client.write('hello') - end - - it 'returns an Integer' do - @fd = @server.sysaccept - @fd.should be_kind_of(Integer) - end + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) end end end diff --git a/spec/ruby/library/socket/unixsocket/addr_spec.rb b/spec/ruby/library/socket/unixsocket/addr_spec.rb index d93e06131236ea..1afe9b12dc4477 100644 --- a/spec/ruby/library/socket/unixsocket/addr_spec.rb +++ b/spec/ruby/library/socket/unixsocket/addr_spec.rb @@ -1,35 +1,33 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXSocket#addr" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @client = UNIXSocket.open(@path) - end +describe "UNIXSocket#addr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end - after :each do - @client.close - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end - it "returns an array" do - @client.addr.should be_kind_of(Array) - end + it "returns an array" do + @client.addr.should be_kind_of(Array) + end - it "returns the address family of this socket in an array" do - @client.addr[0].should == "AF_UNIX" - @server.addr[0].should == "AF_UNIX" - end + it "returns the address family of this socket in an array" do + @client.addr[0].should == "AF_UNIX" + @server.addr[0].should == "AF_UNIX" + end - it "returns the path of the socket in an array if it's a server" do - @server.addr[1].should == @path - end + it "returns the path of the socket in an array if it's a server" do + @server.addr[1].should == @path + end - it "returns an empty string for path if it's a client" do - @client.addr[1].should == "" - end + it "returns an empty string for path if it's a client" do + @client.addr[1].should == "" end end diff --git a/spec/ruby/library/socket/unixsocket/initialize_spec.rb b/spec/ruby/library/socket/unixsocket/initialize_spec.rb index bf7896ab0eb590..5dccfcc7456d24 100644 --- a/spec/ruby/library/socket/unixsocket/initialize_spec.rb +++ b/spec/ruby/library/socket/unixsocket/initialize_spec.rb @@ -1,48 +1,56 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXSocket#initialize' do - describe 'using a non existing path' do +describe 'UNIXSocket#initialize' do + describe 'using a non existing path' do + platform_is_not :windows do it 'raises Errno::ENOENT' do -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ENOENT) end end - describe 'using an existing socket path' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - @socket = UNIXSocket.new(@path) + platform_is :windows do + # Why, Windows, why? + it 'raises Errno::ECONNREFUSED' do + -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ECONNREFUSED) end + end + end - after do - @socket.close - @server.close - rm_r(@path) - end + describe 'using an existing socket path' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = UNIXSocket.new(@path) + end - it 'returns a new UNIXSocket' do - @socket.should be_an_instance_of(UNIXSocket) - end + after do + @socket.close + @server.close + rm_r(@path) + end - it 'sets the socket path to an empty String' do - @socket.path.should == '' - end + it 'returns a new UNIXSocket' do + @socket.should be_an_instance_of(UNIXSocket) + end - it 'sets the socket to binmode' do - @socket.binmode?.should be_true - end + it 'sets the socket path to an empty String' do + @socket.path.should == '' + end + + it 'sets the socket to binmode' do + @socket.binmode?.should be_true + end + platform_is_not :windows do it 'sets the socket to nonblock' do require 'io/nonblock' @socket.should.nonblock? end + end - it 'sets the socket to close on exec' do - @socket.should.close_on_exec? - end - + it 'sets the socket to close on exec' do + @socket.should.close_on_exec? end end end diff --git a/spec/ruby/library/socket/unixsocket/inspect_spec.rb b/spec/ruby/library/socket/unixsocket/inspect_spec.rb index a542ba6db540f1..77bb521069c19d 100644 --- a/spec/ruby/library/socket/unixsocket/inspect_spec.rb +++ b/spec/ruby/library/socket/unixsocket/inspect_spec.rb @@ -1,17 +1,15 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXSocket#inspect" do - it "returns sockets fd for unnamed sockets" do - begin - s1, s2 = UNIXSocket.socketpair - s1.inspect.should == "#" - s2.inspect.should == "#" - ensure - s1.close - s2.close - end +describe "UNIXSocket#inspect" do + it "returns sockets fd for unnamed sockets" do + begin + s1, s2 = UNIXSocket.socketpair + s1.inspect.should == "#" + s2.inspect.should == "#" + ensure + s1.close + s2.close end end end diff --git a/spec/ruby/library/socket/unixsocket/local_address_spec.rb b/spec/ruby/library/socket/unixsocket/local_address_spec.rb index 734253e7f5ccbd..0fdec3829305ad 100644 --- a/spec/ruby/library/socket/unixsocket/local_address_spec.rb +++ b/spec/ruby/library/socket/unixsocket/local_address_spec.rb @@ -1,94 +1,92 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXSocket#local_address' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - @client = UNIXSocket.new(@path) - end +describe 'UNIXSocket#local_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end - after do - @client.close - @server.close + after do + @client.close + @server.close - rm_r(@path) - end - - it 'returns an Addrinfo' do - @client.local_address.should be_an_instance_of(Addrinfo) - end + rm_r(@path) + end - describe 'the returned Addrinfo' do - platform_is_not :aix do - it 'uses AF_UNIX as the address family' do - @client.local_address.afamily.should == Socket::AF_UNIX - end + it 'returns an Addrinfo' do + @client.local_address.should be_an_instance_of(Addrinfo) + end - it 'uses PF_UNIX as the protocol family' do - @client.local_address.pfamily.should == Socket::PF_UNIX - end + describe 'the returned Addrinfo' do + platform_is_not :aix do + it 'uses AF_UNIX as the address family' do + @client.local_address.afamily.should == Socket::AF_UNIX end - it 'uses SOCK_STREAM as the socket type' do - @client.local_address.socktype.should == Socket::SOCK_STREAM + it 'uses PF_UNIX as the protocol family' do + @client.local_address.pfamily.should == Socket::PF_UNIX end + end - platform_is_not :aix do - it 'uses an empty socket path' do - @client.local_address.unix_path.should == '' - end - end + it 'uses SOCK_STREAM as the socket type' do + @client.local_address.socktype.should == Socket::SOCK_STREAM + end - it 'uses 0 as the protocol' do - @client.local_address.protocol.should == 0 + platform_is_not :aix do + it 'uses an empty socket path' do + @client.local_address.unix_path.should == '' end end - end - describe 'UNIXSocket#local_address with a UNIX socket pair' do - before :each do - @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) + it 'uses 0 as the protocol' do + @client.local_address.protocol.should == 0 end + end +end - after :each do - @sock.close - @sock2.close - end +describe 'UNIXSocket#local_address with a UNIX socket pair' do + before :each do + @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) + end - it 'returns an Addrinfo' do - @sock.local_address.should be_an_instance_of(Addrinfo) - end + after :each do + @sock.close + @sock2.close + end - describe 'the returned Addrinfo' do - it 'uses AF_UNIX as the address family' do - @sock.local_address.afamily.should == Socket::AF_UNIX - end + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end - it 'uses PF_UNIX as the protocol family' do - @sock.local_address.pfamily.should == Socket::PF_UNIX - end + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @sock.local_address.afamily.should == Socket::AF_UNIX + end - it 'uses SOCK_STREAM as the socket type' do - @sock.local_address.socktype.should == Socket::SOCK_STREAM - end + it 'uses PF_UNIX as the protocol family' do + @sock.local_address.pfamily.should == Socket::PF_UNIX + end - it 'raises SocketError for #ip_address' do - -> { - @sock.local_address.ip_address - }.should raise_error(SocketError, "need IPv4 or IPv6 address") - end + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end - it 'raises SocketError for #ip_port' do - -> { - @sock.local_address.ip_port - }.should raise_error(SocketError, "need IPv4 or IPv6 address") - end + it 'raises SocketError for #ip_address' do + -> { + @sock.local_address.ip_address + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end - it 'uses 0 as the protocol' do - @sock.local_address.protocol.should == 0 - end + it 'raises SocketError for #ip_port' do + -> { + @sock.local_address.ip_port + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 end end end diff --git a/spec/ruby/library/socket/unixsocket/new_spec.rb b/spec/ruby/library/socket/unixsocket/new_spec.rb index 6d8ea6dcfe6c3a..fea2c1e2b79a10 100644 --- a/spec/ruby/library/socket/unixsocket/new_spec.rb +++ b/spec/ruby/library/socket/unixsocket/new_spec.rb @@ -1,14 +1,12 @@ require_relative '../spec_helper' require_relative 'shared/new' -with_feature :unix_socket do - describe "UNIXSocket.new" do - it_behaves_like :unixsocket_new, :new +describe "UNIXSocket.new" do + it_behaves_like :unixsocket_new, :new - it "does not use the given block and warns to use UNIXSocket::open" do - -> { - @client = UNIXSocket.new(@path) { raise } - }.should complain(/warning: UNIXSocket::new\(\) does not take block; use UNIXSocket::open\(\) instead/) - end + it "does not use the given block and warns to use UNIXSocket::open" do + -> { + @client = UNIXSocket.new(@path) { raise } + }.should complain(/warning: UNIXSocket::new\(\) does not take block; use UNIXSocket::open\(\) instead/) end end diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb index 61def30abbb7ad..b5e8c6c23a45be 100644 --- a/spec/ruby/library/socket/unixsocket/open_spec.rb +++ b/spec/ruby/library/socket/unixsocket/open_spec.rb @@ -2,27 +2,25 @@ require_relative '../fixtures/classes' require_relative 'shared/new' -with_feature :unix_socket do - describe "UNIXSocket.open" do - it_behaves_like :unixsocket_new, :open - end +describe "UNIXSocket.open" do + it_behaves_like :unixsocket_new, :open +end - describe "UNIXSocket.open" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - end +describe "UNIXSocket.open" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end - after :each do - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @server.close + SocketSpecs.rm_socket @path + end - it "opens a unix socket on the specified file and yields it to the block" do - UNIXSocket.open(@path) do |client| - client.addr[0].should == "AF_UNIX" - client.should_not.closed? - end + it "opens a unix socket on the specified file and yields it to the block" do + UNIXSocket.open(@path) do |client| + client.addr[0].should == "AF_UNIX" + client.should_not.closed? end end end diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb index b0a3b2af99da64..9690142668e498 100644 --- a/spec/ruby/library/socket/unixsocket/pair_spec.rb +++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb @@ -3,18 +3,16 @@ require_relative '../shared/partially_closable_sockets' require_relative 'shared/pair' -with_feature :unix_socket do - describe "UNIXSocket.pair" do - it_should_behave_like :unixsocket_pair - it_should_behave_like :partially_closable_sockets +describe "UNIXSocket.pair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets - before :each do - @s1, @s2 = UNIXSocket.pair - end + before :each do + @s1, @s2 = UNIXSocket.pair + end - after :each do - @s1.close - @s2.close - end + after :each do + @s1.close + @s2.close end end diff --git a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb index ef7d0f0b2aad5e..108a6c30637219 100644 --- a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb +++ b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb @@ -2,22 +2,20 @@ require_relative '../fixtures/classes' require_relative '../shared/partially_closable_sockets' -with_feature :unix_socket do - describe "UNIXSocket partial closability" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @s1 = UNIXSocket.new(@path) - @s2 = @server.accept - end - - after :each do - @server.close - @s1.close - @s2.close - SocketSpecs.rm_socket @path - end +describe "UNIXSocket partial closability" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @s1 = UNIXSocket.new(@path) + @s2 = @server.accept + end - it_should_behave_like :partially_closable_sockets + after :each do + @server.close + @s1.close + @s2.close + SocketSpecs.rm_socket @path end + + it_should_behave_like :partially_closable_sockets end diff --git a/spec/ruby/library/socket/unixsocket/path_spec.rb b/spec/ruby/library/socket/unixsocket/path_spec.rb index a608378e4f97a6..ffe7e4bea2cb03 100644 --- a/spec/ruby/library/socket/unixsocket/path_spec.rb +++ b/spec/ruby/library/socket/unixsocket/path_spec.rb @@ -1,26 +1,24 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXSocket#path" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @client = UNIXSocket.open(@path) - end +describe "UNIXSocket#path" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end - after :each do - @client.close - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end - it "returns the path of the socket if it's a server" do - @server.path.should == @path - end + it "returns the path of the socket if it's a server" do + @server.path.should == @path + end - it "returns an empty string for path if it's a client" do - @client.path.should == "" - end + it "returns an empty string for path if it's a client" do + @client.path.should == "" end end diff --git a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb index 72bc96b1fe978f..10cab13b422a73 100644 --- a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb +++ b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb @@ -1,28 +1,26 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXSocket#peeraddr" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @client = UNIXSocket.open(@path) - end +describe "UNIXSocket#peeraddr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end - after :each do - @client.close - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end - it "returns the address family and path of the server end of the connection" do - @client.peeraddr.should == ["AF_UNIX", @path] - end + it "returns the address family and path of the server end of the connection" do + @client.peeraddr.should == ["AF_UNIX", @path] + end - it "raises an error in server sockets" do - -> { - @server.peeraddr - }.should raise_error(Errno::ENOTCONN) - end + it "raises an error in server sockets" do + -> { + @server.peeraddr + }.should raise_error(Errno::ENOTCONN) end end diff --git a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb index 1dbc4538e379e6..f0b408f30945fd 100644 --- a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do +platform_is_not :windows do describe "UNIXSocket#recv_io" do before :each do @path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb index d849fdc302646b..9bb18047c49a80 100644 --- a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -1,59 +1,105 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe "UNIXSocket#recvfrom" do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - @client = UNIXSocket.open(@path) +describe "UNIXSocket#recvfrom" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "receives len bytes from sock, returning an array containing sent data as first element" do + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6).first.should == "foobar" + sock.close + end + + context "when called on a server's socket" do + platform_is_not :windows do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end end - after :each do - @client.close - @server.close - SocketSpecs.rm_socket @path + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end end + end - it "receives len bytes from sock" do - @client.send("foobar", 0) - sock = @server.accept - sock.recvfrom(6).first.should == "foobar" - sock.close + context "when called on a client's socket" do + platform_is_not :windows, :darwin do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end end - it "returns an array with data and information on the sender" do - @client.send("foobar", 0) - sock = @server.accept - data = sock.recvfrom(6) - data.first.should == "foobar" - data.last.should == ["AF_UNIX", ""] - sock.close + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + # This may not be correct, depends on what underlying recvfrom actually returns. + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end end - it "allows an output buffer as third argument" do - buffer = +'' + platform_is :darwin do + it "returns an array containing basic information on the server as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", ""] + sock.close + end + end + end - @client.send("foobar", 0) - sock = @server.accept - message, = sock.recvfrom(6, 0, buffer) - sock.close + it "allows an output buffer as third argument" do + buffer = +'' - message.should.equal?(buffer) - buffer.should == "foobar" - end + @client.send("foobar", 0) + sock = @server.accept + message, = sock.recvfrom(6, 0, buffer) + sock.close - it "preserves the encoding of the given buffer" do - buffer = ''.encode(Encoding::ISO_8859_1) + message.should.equal?(buffer) + buffer.should == "foobar" + end - @client.send("foobar", 0) - sock = @server.accept - sock.recvfrom(6, 0, buffer) - sock.close + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) - buffer.encoding.should == Encoding::ISO_8859_1 - end + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6, 0, buffer) + sock.close + buffer.encoding.should == Encoding::ISO_8859_1 + end + + platform_is_not :windows do it "uses different message options" do @client.send("foobar", Socket::MSG_PEEK) sock = @server.accept @@ -65,24 +111,34 @@ sock.close end end +end - describe 'UNIXSocket#recvfrom' do - describe 'using a socket pair' do - before do - @client, @server = UNIXSocket.socketpair - @client.write('hello') - end +describe 'UNIXSocket#recvfrom' do + describe 'using a socket pair' do + before do + @client, @server = UNIXSocket.socketpair + @client.write('hello') + end - after do - @client.close - @server.close + after do + @client.close + @server.close + end + + platform_is_not :windows do + it 'returns an Array containing the data and address information' do + @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] end + end + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do it 'returns an Array containing the data and address information' do @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] end end + end + platform_is_not :windows do # These specs are taken from the rdoc examples on UNIXSocket#recvfrom. describe 'using a UNIX socket constructed using UNIXSocket.for_fd' do before do diff --git a/spec/ruby/library/socket/unixsocket/remote_address_spec.rb b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb index 0b416254d06bae..84bdff0a6a7dc5 100644 --- a/spec/ruby/library/socket/unixsocket/remote_address_spec.rb +++ b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb @@ -1,45 +1,43 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do - describe 'UNIXSocket#remote_address' do - before do - @path = SocketSpecs.socket_path - @server = UNIXServer.new(@path) - @client = UNIXSocket.new(@path) - end +describe 'UNIXSocket#remote_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end - after do - @client.close - @server.close + after do + @client.close + @server.close - rm_r(@path) - end + rm_r(@path) + end - it 'returns an Addrinfo' do - @client.remote_address.should be_an_instance_of(Addrinfo) - end + it 'returns an Addrinfo' do + @client.remote_address.should be_an_instance_of(Addrinfo) + end - describe 'the returned Addrinfo' do - it 'uses AF_UNIX as the address family' do - @client.remote_address.afamily.should == Socket::AF_UNIX - end + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @client.remote_address.afamily.should == Socket::AF_UNIX + end - it 'uses PF_UNIX as the protocol family' do - @client.remote_address.pfamily.should == Socket::PF_UNIX - end + it 'uses PF_UNIX as the protocol family' do + @client.remote_address.pfamily.should == Socket::PF_UNIX + end - it 'uses SOCK_STREAM as the socket type' do - @client.remote_address.socktype.should == Socket::SOCK_STREAM - end + it 'uses SOCK_STREAM as the socket type' do + @client.remote_address.socktype.should == Socket::SOCK_STREAM + end - it 'uses the correct socket path' do - @client.remote_address.unix_path.should == @path - end + it 'uses the correct socket path' do + @client.remote_address.unix_path.should == @path + end - it 'uses 0 as the protocol' do - @client.remote_address.protocol.should == 0 - end + it 'uses 0 as the protocol' do + @client.remote_address.protocol.should == 0 end end end diff --git a/spec/ruby/library/socket/unixsocket/send_io_spec.rb b/spec/ruby/library/socket/unixsocket/send_io_spec.rb index 80f3550c6d323e..52186ec3cf60f1 100644 --- a/spec/ruby/library/socket/unixsocket/send_io_spec.rb +++ b/spec/ruby/library/socket/unixsocket/send_io_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -with_feature :unix_socket do +platform_is_not :windows do describe "UNIXSocket#send_io" do before :each do @path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/unixsocket/shared/pair.rb b/spec/ruby/library/socket/unixsocket/shared/pair.rb index ade7fc9852388d..0f1af43b251323 100644 --- a/spec/ruby/library/socket/unixsocket/shared/pair.rb +++ b/spec/ruby/library/socket/unixsocket/shared/pair.rb @@ -12,18 +12,37 @@ @s2.gets.should == "foo\n" end - it "sets the socket paths to empty Strings" do - @s1.path.should == "" - @s2.path.should == "" - end + platform_is_not :windows do + it "sets the socket paths to empty Strings" do + @s1.path.should == "" + @s2.path.should == "" + end + + it "sets the socket addresses to empty Strings" do + @s1.addr.should == ["AF_UNIX", ""] + @s2.addr.should == ["AF_UNIX", ""] + end - it "sets the socket addresses to empty Strings" do - @s1.addr.should == ["AF_UNIX", ""] - @s2.addr.should == ["AF_UNIX", ""] + it "sets the socket peer addresses to empty Strings" do + @s1.peeraddr.should == ["AF_UNIX", ""] + @s2.peeraddr.should == ["AF_UNIX", ""] + end end - it "sets the socket peer addresses to empty Strings" do - @s1.peeraddr.should == ["AF_UNIX", ""] - @s2.peeraddr.should == ["AF_UNIX", ""] + platform_is :windows do + it "emulates unnamed sockets with a temporary file with a path" do + @s1.path.match?(/\\AppData\\Local\\Temp\\\d+-\d+\.\(\$\)\z/).should be_true + @s1.addr.should == ["AF_UNIX", @s1.path] + @s2.peeraddr.should == ["AF_UNIX", @s1.path] + end + + it "sets the peer address of first socket to an empty string" do + @s1.peeraddr.should == ["AF_UNIX", ""] + end + + it "sets the address and path of second socket to an empty string" do + @s2.addr.should == ["AF_UNIX", ""] + @s2.path.should == "" + end end end diff --git a/spec/ruby/library/socket/unixsocket/socketpair_spec.rb b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb index 5f34008df3e942..c61fc00be4cce7 100644 --- a/spec/ruby/library/socket/unixsocket/socketpair_spec.rb +++ b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb @@ -3,18 +3,16 @@ require_relative '../shared/partially_closable_sockets' require_relative 'shared/pair' -with_feature :unix_socket do - describe "UNIXSocket.socketpair" do - it_should_behave_like :unixsocket_pair - it_should_behave_like :partially_closable_sockets +describe "UNIXSocket.socketpair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets - before :each do - @s1, @s2 = UNIXSocket.socketpair - end + before :each do + @s1, @s2 = UNIXSocket.socketpair + end - after :each do - @s1.close - @s2.close - end + after :each do + @s1.close + @s2.close end end diff --git a/spec/ruby/library/stringio/readpartial_spec.rb b/spec/ruby/library/stringio/readpartial_spec.rb index 988c552235d5e6..dadefb783732a1 100644 --- a/spec/ruby/library/stringio/readpartial_spec.rb +++ b/spec/ruby/library/stringio/readpartial_spec.rb @@ -24,6 +24,14 @@ @string.readpartial(3).should == ", l" end + it "reads after ungetc with multibyte characters in the buffer" do + @string = StringIO.new(+"∂φ/∂x = gaîté") + c = @string.getc + @string.ungetc(c) + @string.readpartial(3).should == "\xE2\x88\x82".b + @string.readpartial(3).should == "\xCF\x86/".b + end + it "reads after ungetc without data in the buffer" do @string = StringIO.new @string.write("f").should == 1 diff --git a/spec/ruby/optional/thread_safety/fixtures/classes.rb b/spec/ruby/optional/thread_safety/fixtures/classes.rb new file mode 100644 index 00000000000000..4f0ad030e5c688 --- /dev/null +++ b/spec/ruby/optional/thread_safety/fixtures/classes.rb @@ -0,0 +1,39 @@ +module ThreadSafetySpecs + # Returns the number of processors, rounded up so it's always a multiple of 2 + def self.processors + require 'etc' + n = Etc.nprocessors + raise "expected at least 1 processor" if n < 1 + n += 1 if n.odd? # ensure it's a multiple of 2 + n + end + + class Counter + def initialize + @value = 0 + @mutex = Mutex.new + end + + def get + @mutex.synchronize { @value } + end + + def increment + @mutex.synchronize do + @value += 1 + end + end + end + + class Barrier + def initialize(parties) + @parties = parties + @counter = Counter.new + end + + def wait + @counter.increment + Thread.pass until @counter.get == @parties + end + end +end diff --git a/spec/ruby/optional/thread_safety/hash_spec.rb b/spec/ruby/optional/thread_safety/hash_spec.rb new file mode 100644 index 00000000000000..53127fc97342b2 --- /dev/null +++ b/spec/ruby/optional/thread_safety/hash_spec.rb @@ -0,0 +1,210 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Hash thread safety" do + it "supports concurrent #[]=" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = t * n + n.times do |j| + h[base+j] = t + end + } + } + + barrier.wait + threads.each(&:join) + + h.size.should == operations + h.each_pair { |key, value| + (key / n).should == value + } + end + + # can't add a new key into hash during iteration (RuntimeError) on CRuby. + # Yet it's good to test this for implementations that support it. + guard_not -> { PlatformGuard.standard? } do + it "supports concurrent #[]= and #delete and iteration" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier1 = ThreadSafetySpecs::Barrier.new(n_threads + 2) + barrier2 = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier1.wait + base = t * n + n.times do |j| + h[base+j] = t + end + + barrier2.wait + n.times do |j| + # delete only even keys + h.delete(base+j).should == t if (base+j).even? + end + } + } + + read = true + reader = Thread.new { + barrier1.wait + while read + h.each_pair { |k,v| + k.should.is_a?(Integer) + v.should.is_a?(Integer) + (k / n).should == v + } + end + } + + barrier1.wait + barrier2.wait + threads.each(&:join) + read = false + reader.join + + # odd keys are left + h.size.should == operations + h.each_pair { |key, value| + key.should.odd? + (key / n).should == value + } + end + end + + it "supports concurrent #[]= and #[]" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = (t / 2) * n + + if t.even? + n.times do |j| + k = base + j + h[k] = k + end + else + n.times do |j| + k = base + j + Thread.pass until v = h[k] + v.should == k + end + end + } + } + + barrier.wait + threads.each(&:join) + + h.size.should == operations + h.each_pair { |key, value| + key.should == value + } + end + + it "supports concurrent #[]= and #[] with change to #compare_by_identity in the middle" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = (t / 2) * n + + if t.even? + n.times do |j| + k = base + j + h[k] = k + end + else + n.times do |j| + k = base + j + Thread.pass until v = h[k] + v.should == k + end + end + } + } + + changer = Thread.new { + Thread.pass until h.size >= operations / 2 + h.should_not.compare_by_identity? + h.compare_by_identity + h.should.compare_by_identity? + } + + barrier.wait + threads.each(&:join) + changer.join + + h.size.should == operations + h.each_pair { |key, value| + key.should == value + } + end + + it "supports repeated concurrent #[]= and #delete and always returns a #size >= 0" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + deleted = ThreadSafetySpecs::Counter.new + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + key = t / 2 + + if t.even? + n.times { + Thread.pass until h.delete(key) + h.size.should >= 0 + deleted.increment + } + else + n.times { + h[key] = key + Thread.pass while h.key?(key) + } + end + } + } + + barrier.wait + threads.each(&:join) + + deleted.get.should == operations + h.size.should == 0 + h.should.empty? + end +end diff --git a/spec/ruby/shared/file/socket.rb b/spec/ruby/shared/file/socket.rb index 55a1cfd284a743..ef6c482d1cf297 100644 --- a/spec/ruby/shared/file/socket.rb +++ b/spec/ruby/shared/file/socket.rb @@ -1,3 +1,33 @@ describe :file_socket, shared: true do - it "accepts an object that has a #to_path method" + it "returns false if the file is not a socket" do + filename = tmp("i_exist") + touch(filename) + + @object.send(@method, filename).should == false + + rm_r filename + end + + it "returns true if the file is a socket" do + require 'socket' + + # We need a really short name here. + # On Linux the path length is limited to 107, see unix(7). + name = tmp("s") + server = UNIXServer.new(name) + + @object.send(@method, name).should == true + + server.close + rm_r name + end + + it "accepts an object that has a #to_path method" do + obj = Object.new + def obj.to_path + __FILE__ + end + + @object.send(@method, obj).should == false + end end From 6939f03f4cec6c50cbc1d214a38cdcdcf1d3f705 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 30 Dec 2025 16:03:35 -0500 Subject: [PATCH 2321/2435] Add field handle_weak_references to TypedData This commit adds a field handle_weak_references to rb_data_type_struct for the callback when handling weak references. This avoids TypedData objects from needing to expose their rb_data_type_struct and weak references function. --- cont.c | 41 ++++++------ gc.c | 29 ++------- include/ruby/internal/core/rtypeddata.h | 6 +- weakmap.c | 84 ++++++++++++------------- 4 files changed, 70 insertions(+), 90 deletions(-) diff --git a/cont.c b/cont.c index 463be7400f174c..8af093a3163479 100644 --- a/cont.c +++ b/cont.c @@ -48,8 +48,8 @@ static const int DEBUG = 0; #define RB_PAGE_MASK (~(RB_PAGE_SIZE - 1)) static long pagesize; -const rb_data_type_t rb_cont_data_type; -const rb_data_type_t rb_fiber_data_type; +static const rb_data_type_t rb_cont_data_type; +static const rb_data_type_t rb_fiber_data_type; static VALUE rb_cContinuation; static VALUE rb_cFiber; static VALUE rb_eFiberError; @@ -1239,17 +1239,11 @@ cont_save_machine_stack(rb_thread_t *th, rb_context_t *cont) asan_unpoison_memory_region(cont->machine.stack_src, size, false); MEMCPY(cont->machine.stack, cont->machine.stack_src, VALUE, size); } -const rb_data_type_t rb_cont_data_type = { - "continuation", - {cont_mark, cont_free, cont_memsize, cont_compact}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; -void -rb_cont_handle_weak_references(VALUE obj) +static void +cont_handle_weak_references(void *ptr) { - rb_context_t *cont; - TypedData_Get_Struct(obj, rb_context_t, &rb_cont_data_type, cont); + rb_context_t *cont = ptr; if (!cont) return; @@ -1260,6 +1254,12 @@ rb_cont_handle_weak_references(VALUE obj) } } +static const rb_data_type_t rb_cont_data_type = { + "continuation", + {cont_mark, cont_free, cont_memsize, cont_compact, cont_handle_weak_references}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + static inline void cont_save_thread(rb_context_t *cont, rb_thread_t *th) { @@ -1996,17 +1996,10 @@ rb_cont_call(int argc, VALUE *argv, VALUE contval) * */ -const rb_data_type_t rb_fiber_data_type = { - "fiber", - {fiber_mark, fiber_free, fiber_memsize, fiber_compact,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; - -void -rb_fiber_handle_weak_references(VALUE obj) +static void +fiber_handle_weak_references(void *ptr) { - rb_fiber_t *fiber; - TypedData_Get_Struct(obj, rb_fiber_t, &rb_fiber_data_type, fiber); + rb_fiber_t *fiber = ptr; if (!fiber) return; @@ -2017,6 +2010,12 @@ rb_fiber_handle_weak_references(VALUE obj) } } +static const rb_data_type_t rb_fiber_data_type = { + "fiber", + {fiber_mark, fiber_free, fiber_memsize, fiber_compact, fiber_handle_weak_references}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + static VALUE fiber_alloc(VALUE klass) { diff --git a/gc.c b/gc.c index 98e5712ca40214..c47f68c50c0047 100644 --- a/gc.c +++ b/gc.c @@ -1204,17 +1204,6 @@ rb_gc_handle_weak_references_alive_p(VALUE obj) return rb_gc_impl_handle_weak_references_alive_p(rb_gc_get_objspace(), obj); } -extern const rb_data_type_t rb_weakmap_type; -void rb_wmap_handle_weak_references(VALUE obj); -extern const rb_data_type_t rb_weakkeymap_type; -void rb_wkmap_handle_weak_references(VALUE obj); - -extern const rb_data_type_t rb_fiber_data_type; -void rb_fiber_handle_weak_references(VALUE obj); - -extern const rb_data_type_t rb_cont_data_type; -void rb_cont_handle_weak_references(VALUE obj); - void rb_gc_handle_weak_references(VALUE obj) { @@ -1223,20 +1212,14 @@ rb_gc_handle_weak_references(VALUE obj) if (RTYPEDDATA_P(obj)) { const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); - if (type == &rb_fiber_data_type) { - rb_fiber_handle_weak_references(obj); - } - else if (type == &rb_cont_data_type) { - rb_cont_handle_weak_references(obj); - } - else if (type == &rb_weakmap_type) { - rb_wmap_handle_weak_references(obj); - } - else if (type == &rb_weakkeymap_type) { - rb_wkmap_handle_weak_references(obj); + if (type->function.handle_weak_references) { + (type->function.handle_weak_references)(RTYPEDDATA_GET_DATA(obj)); } else { - rb_bug("rb_gc_handle_weak_references: unknown TypedData %s", RTYPEDDATA_TYPE(obj)->wrap_struct_name); + rb_bug( + "rb_gc_handle_weak_references: TypedData %s does not implement handle_weak_references", + RTYPEDDATA_TYPE(obj)->wrap_struct_name + ); } } else { diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index aadccc238ba1fc..bf6423f8d230f0 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -262,11 +262,9 @@ struct rb_data_type_struct { RUBY_DATA_FUNC dcompact; /** - * This field is reserved for future extension. For now, it must be - * filled with zeros. + * @internal */ - void *reserved[1]; /* For future extension. - This array *must* be filled with ZERO. */ + void (*handle_weak_references)(void *); } function; /** diff --git a/weakmap.c b/weakmap.c index 7b6f27ce2bbbed..b027604f5e83fe 100644 --- a/weakmap.c +++ b/weakmap.c @@ -112,6 +112,26 @@ wmap_compact(void *ptr) } } +static int +rb_wmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (rb_gc_handle_weak_references_alive_p(key) && + rb_gc_handle_weak_references_alive_p(val)) { + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +static void +wmap_handle_weak_references(void *ptr) +{ + struct weakmap *w = ptr; + + st_foreach(w->table, rb_wmap_handle_weak_references_i, (st_data_t)0); +} + const rb_data_type_t rb_weakmap_type = { "weakmap", { @@ -119,6 +139,7 @@ const rb_data_type_t rb_weakmap_type = { wmap_free, wmap_memsize, wmap_compact, + wmap_handle_weak_references, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; @@ -140,27 +161,6 @@ static const struct st_hash_type wmap_hash_type = { wmap_hash, }; -static int -rb_wmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) -{ - if (rb_gc_handle_weak_references_alive_p(key) && - rb_gc_handle_weak_references_alive_p(val)) { - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -void -rb_wmap_handle_weak_references(VALUE self) -{ - struct weakmap *w; - TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w); - - st_foreach(w->table, rb_wmap_handle_weak_references_i, (st_data_t)0); -} - static VALUE wmap_allocate(VALUE klass) { @@ -588,13 +588,33 @@ wkmap_compact(void *ptr) } } -const rb_data_type_t rb_weakkeymap_type = { +static int +rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (rb_gc_handle_weak_references_alive_p(key)) { + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +static void +wkmap_handle_weak_references(void *ptr) +{ + struct weakkeymap *w = ptr; + + st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0); +} + +static const rb_data_type_t rb_weakkeymap_type = { "weakkeymap", { wkmap_mark, wkmap_free, wkmap_memsize, wkmap_compact, + wkmap_handle_weak_references, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; @@ -621,26 +641,6 @@ static const struct st_hash_type wkmap_hash_type = { wkmap_hash, }; -static int -rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg) -{ - if (rb_gc_handle_weak_references_alive_p(key)) { - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -void -rb_wkmap_handle_weak_references(VALUE self) -{ - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w); - - st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0); -} - static VALUE wkmap_allocate(VALUE klass) { From 29e7973e055cc82622cdd4511d78d74ad784c80e Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 15:36:02 +0100 Subject: [PATCH 2322/2435] Update to ruby/spec@94dbd55 --- spec/ruby/library/socket/shared/address.rb | 2 +- spec/ruby/library/socket/unixsocket/recvfrom_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/ruby/library/socket/shared/address.rb b/spec/ruby/library/socket/shared/address.rb index b548f1eac06464..49ba17c400061e 100644 --- a/spec/ruby/library/socket/shared/address.rb +++ b/spec/ruby/library/socket/shared/address.rb @@ -173,7 +173,7 @@ end end - guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do it 'equals address of peer socket' do if @method == :local_address @addr.to_s.should == @b.remote_address.to_s diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb index 9bb18047c49a80..5e61cd9a90f998 100644 --- a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -32,7 +32,7 @@ end end - guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do it "returns an array containing basic information on the client as second element" do @client.send("foobar", 0) sock = @server.accept @@ -54,7 +54,7 @@ end end - guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do it "returns an array containing server's address as second element" do @client.send("", 0) sock = @server.accept @@ -131,7 +131,7 @@ end end - guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.1" } do + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do it 'returns an Array containing the data and address information' do @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] end From e79f8974b55de2ef8135c3fad03860e8ad36dfb0 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 15:39:31 +0100 Subject: [PATCH 2323/2435] Get better error if UNIXSocket.socketpair spec fails --- spec/ruby/library/socket/unixsocket/shared/pair.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ruby/library/socket/unixsocket/shared/pair.rb b/spec/ruby/library/socket/unixsocket/shared/pair.rb index 0f1af43b251323..006863777a1733 100644 --- a/spec/ruby/library/socket/unixsocket/shared/pair.rb +++ b/spec/ruby/library/socket/unixsocket/shared/pair.rb @@ -31,7 +31,7 @@ platform_is :windows do it "emulates unnamed sockets with a temporary file with a path" do - @s1.path.match?(/\\AppData\\Local\\Temp\\\d+-\d+\.\(\$\)\z/).should be_true + @s1.path.should.match?(/\\AppData\\Local\\Temp\\\d+-\d+\.\(\$\)\z/) @s1.addr.should == ["AF_UNIX", @s1.path] @s2.peeraddr.should == ["AF_UNIX", @s1.path] end From 2bf954305323857418b917f4a987c300245ee0eb Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 16:04:02 +0100 Subject: [PATCH 2324/2435] Remove assertion which does not hold * https://github.com/ruby/ruby/actions/runs/20694508956/job/59407571754 1) UNIXSocket.pair emulates unnamed sockets with a temporary file with a path FAILED Expected "C:\\a\\_temp\\102424668889-2384.($)".match? /\\AppData\\Local\\Temp\\\d+-\d+\.\(\$\)\z/ to be truthy but was false --- spec/ruby/library/socket/unixsocket/shared/pair.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/ruby/library/socket/unixsocket/shared/pair.rb b/spec/ruby/library/socket/unixsocket/shared/pair.rb index 006863777a1733..d68ee466bf1d61 100644 --- a/spec/ruby/library/socket/unixsocket/shared/pair.rb +++ b/spec/ruby/library/socket/unixsocket/shared/pair.rb @@ -31,7 +31,6 @@ platform_is :windows do it "emulates unnamed sockets with a temporary file with a path" do - @s1.path.should.match?(/\\AppData\\Local\\Temp\\\d+-\d+\.\(\$\)\z/) @s1.addr.should == ["AF_UNIX", @s1.path] @s2.peeraddr.should == ["AF_UNIX", @s1.path] end From 9888a3e8bda75a417f15e3936d5d9a16051c9256 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 18:10:42 +0100 Subject: [PATCH 2325/2435] UNIXSocket#recvfrom only returns the path on Linux * Notably it does not on BSD, macOS and Windows. --- spec/ruby/library/socket/unixsocket/recvfrom_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb index 5e61cd9a90f998..9ae37779616b41 100644 --- a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -44,7 +44,7 @@ end context "when called on a client's socket" do - platform_is_not :windows, :darwin do + platform_is :linux do it "returns an array containing server's address as second element" do @client.send("", 0) sock = @server.accept From 6eadc01cdaa70895d464843de5b6c4d35cab2e27 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 4 Jan 2026 18:13:08 +0100 Subject: [PATCH 2326/2435] Fix condition for UTF-8 default in magic_comment_spec.rb --- spec/ruby/language/magic_comment_spec.rb | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/spec/ruby/language/magic_comment_spec.rb b/spec/ruby/language/magic_comment_spec.rb index 90bb83e279be8a..af9c9dbfd0c8d6 100644 --- a/spec/ruby/language/magic_comment_spec.rb +++ b/spec/ruby/language/magic_comment_spec.rb @@ -45,19 +45,11 @@ describe "Magic comments" do describe "in stdin" do - ruby_version_is ""..."4.0" do - it_behaves_like :magic_comments, :locale, -> file { - print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") - ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") - } - end - - ruby_version_is "4.0" do - it_behaves_like :magic_comments, :UTF8, -> file { - print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") - ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") - } - end + default = (platform_is :windows and ruby_version_is "4.0") ? :UTF8 : :locale + it_behaves_like :magic_comments, default, -> file { + print_at_exit = fixture(__FILE__, "print_magic_comment_result_at_exit.rb") + ruby_exe(nil, args: "< #{fixture(__FILE__, file)}", options: "-r#{print_at_exit}") + } end platform_is_not :windows do From 7d5c0247eb2fb67bb0ac2fd6060bf2996c8c4c46 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 4 Jan 2026 09:33:39 -0500 Subject: [PATCH 2327/2435] Dump fstr and frozen status in rb_raw_obj_info_buitin_type --- gc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gc.c b/gc.c index c47f68c50c0047..9ac68ccb40e09d 100644 --- a/gc.c +++ b/gc.c @@ -4844,6 +4844,10 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU } break; case T_STRING: { + APPEND_F("[%s%s] ", + C(FL_TEST(obj, RSTRING_FSTR), "F"), + C(RB_OBJ_FROZEN(obj), "R")); + if (STR_SHARED_P(obj)) { APPEND_F(" [shared] len: %ld", RSTRING_LEN(obj)); } From 699813b65f8504c6bb787ac3c8b7fe6419e76cdf Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sat, 3 Jan 2026 10:12:19 -0800 Subject: [PATCH 2328/2435] [ruby/rubygems] Remove date require from rebuild command As far as I can see, this isn't used. https://github.com/ruby/rubygems/commit/67c97e7180 --- lib/rubygems/commands/rebuild_command.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb index 77a474ef1ddf32..23b9d7b3ba925b 100644 --- a/lib/rubygems/commands/rebuild_command.rb +++ b/lib/rubygems/commands/rebuild_command.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "date" require "digest" require "fileutils" require "tmpdir" From 01e8b38f601c4866738a76df4361f2b4df0fc300 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Jan 2026 10:55:47 +0900 Subject: [PATCH 2329/2435] Use RUBY_VERSION in specs instead of hardcoded 'Ruby 4.0.0' to make tests version-independent --- spec/bundled_gems_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index c2a655929ab7bf..e86b5ce6e8c0a7 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -113,7 +113,7 @@ def script(code, options = {}) require "active_support/all" RUBY - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) expect(err).to include(/lib\/active_support\/all\.rb:1/) end @@ -159,7 +159,7 @@ def script(code, options = {}) bundle "exec ruby script.rb" - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) expect(err).to include(/script\.rb:8/) end @@ -177,7 +177,7 @@ def script(code, options = {}) bundle "exec ./script.rb" - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) expect(err).to include(/script\.rb:9/) end @@ -186,7 +186,7 @@ def script(code, options = {}) create_file("Gemfile", "source 'https://rubygems.org'") bundle "exec ruby -r./stub -ropenssl -e ''" - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) end it "Show warning when warn is not the standard one in the current scope" do @@ -209,7 +209,7 @@ def my My.my RUBY - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) expect(err).to include(/-e:19/) end @@ -251,7 +251,7 @@ def my require Gem::BUNDLED_GEMS::ARCHDIR + 'openssl' RUBY - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) # TODO: We should assert caller location like below: # test_warn_bootsnap.rb:14: warning: ... end @@ -320,7 +320,7 @@ def my create_file("Gemfile", "source 'https://rubygems.org'") bundle "exec ruby script.rb" - expect(err).to include(/openssl used to be loaded from (.*) since Ruby 4.0.0/) + expect(err).to include(/openssl used to be loaded from (.*) since Ruby #{RUBY_VERSION}/) expect(err).to include(/script\.rb:13/) end From e6762d23cb14411ec026b324c5c15c339158ddc4 Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Mon, 5 Jan 2026 11:49:48 +0900 Subject: [PATCH 2330/2435] [DOC] Fix a typo and trim trailing whitespace per .editorconfig --- LEGAL | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/LEGAL b/LEGAL index cb1b867c23bd81..2777aa2c148bb8 100644 --- a/LEGAL +++ b/LEGAL @@ -713,9 +713,8 @@ mentioned below. Copyright (c) 2022 James Edward Anhalt III - https://github.com/jeaiii/itoa {MIT License}[rdoc-ref:@MIT+License] - -[ext/json/ext/vendor/ryu.h] +[ext/json/vendor/ryu.h] This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu. It is dual-licensed under {Apache License 2.0}[rdoc-ref:@Apache+License+2.0] OR {Boost Software License 1.0}[rdoc-ref:@Boost+Software+License+1.0]. @@ -1071,21 +1070,21 @@ mentioned below. >>> Boost Software License - Version 1.0 - August 17th, 2003 - + Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: - + The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT From 99bd18b5b8071659ac9f035eea173b445326f8ad Mon Sep 17 00:00:00 2001 From: Tsutomu Katsube Date: Fri, 2 Jan 2026 20:10:16 +0900 Subject: [PATCH 2331/2435] Unskip RBS test on Windows for NoMemoryError workaround test-unit 3.7.7 has been fixed this problem. See also: https://github.com/test-unit/test-unit/releases/tag/3.7.7 --- tool/rbs_skip_tests_windows | 2 -- 1 file changed, 2 deletions(-) diff --git a/tool/rbs_skip_tests_windows b/tool/rbs_skip_tests_windows index 35e78e237f1668..db12c69419e88e 100644 --- a/tool/rbs_skip_tests_windows +++ b/tool/rbs_skip_tests_windows @@ -106,8 +106,6 @@ test_new(TempfileSingletonTest) # Errno::EACCES: Permission denied @ apply2files - C:/a/_temp/d20250813-10156-f8z9pn/test.gz test_open(ZlibGzipReaderSingletonTest) -test_reachable_objects_from_root(ObjectSpaceTest) To avoid NoMemoryError with test-unit 3.7.5 - # Errno::EACCES: Permission denied @ rb_file_s_rename # D:/a/ruby/ruby/src/lib/rubygems/util/atomic_file_writer.rb:42:in 'File.rename' test_write_binary(GemSingletonTest) From 05f791bf41d9a14cb4db2d529413928db539affc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Jan 2026 13:59:47 +0900 Subject: [PATCH 2332/2435] [ruby/rubygems] rake update https://github.com/ruby/rubygems/commit/aa7632161e --- tool/bundler/dev_gems.rb.lock | 52 ++++++------- tool/bundler/rubocop_gems.rb.lock | 95 ++++++++++++------------ tool/bundler/standard_gems.rb.lock | 113 +++++++++++++++-------------- tool/bundler/test_gems.rb.lock | 34 ++++----- 4 files changed, 148 insertions(+), 146 deletions(-) diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 72abb0efcbe1f6..832127fb4cc59d 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -9,22 +9,22 @@ GEM kramdown (~> 2.0) mini_portile2 (2.8.9) mustache (1.1.1) - nokogiri (1.18.10) + nokogiri (1.19.0) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-gnu) + nokogiri (1.19.0-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.10-arm-linux-gnu) + nokogiri (1.19.0-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) + nokogiri (1.19.0-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.10-java) + nokogiri (1.19.0-java) racc (~> 1.4) - nokogiri (1.18.10-x64-mingw-ucrt) + nokogiri (1.19.0-x64-mingw-ucrt) racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) + nokogiri (1.19.0-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) + nokogiri (1.19.0-x86_64-linux-gnu) racc (~> 1.4) parallel (1.27.0) parallel_tests (4.10.1) @@ -33,9 +33,9 @@ GEM racc (1.8.1) racc (1.8.1-java) rake (13.3.1) - rake-compiler-dock (1.9.1) - rb_sys (0.9.117) - rake-compiler-dock (= 1.9.1) + rake-compiler-dock (1.10.0) + rb_sys (0.9.123) + rake-compiler-dock (= 1.10.0) rexml (3.4.4) ronn-ng (0.10.1) kramdown (~> 2, >= 2.1) @@ -57,10 +57,10 @@ GEM rspec-support (3.13.6) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) - test-unit (3.7.1) + test-unit (3.7.7) power_assert - test-unit-ruby-core (1.0.13) - test-unit + test-unit-ruby-core (1.0.14) + test-unit (>= 3.7.2) turbo_tests (2.2.5) parallel_tests (>= 3.3.0, < 5) rspec (>= 3.10) @@ -100,22 +100,22 @@ CHECKSUMS kramdown-parser-gfm (1.1.0) sha256=fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729 mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 mustache (1.1.1) sha256=90891fdd50b53919ca334c8c1031eada1215e78d226d5795e523d6123a2717d0 - nokogiri (1.18.10) sha256=d5cc0731008aa3b3a87b361203ea3d19b2069628cb55e46ac7d84a0445e69cc1 - nokogiri (1.18.10-aarch64-linux-gnu) sha256=7fb87235d729c74a2be635376d82b1d459230cc17c50300f8e4fcaabc6195344 - nokogiri (1.18.10-arm-linux-gnu) sha256=51f4f25ab5d5ba1012d6b16aad96b840a10b067b93f35af6a55a2c104a7ee322 - nokogiri (1.18.10-arm64-darwin) sha256=c2b0de30770f50b92c9323fa34a4e1cf5a0af322afcacd239cd66ee1c1b22c85 - nokogiri (1.18.10-java) sha256=cd431a09c45d84a2f870ba0b7e8f571199b3727d530f2b4888a73639f76510b5 - nokogiri (1.18.10-x64-mingw-ucrt) sha256=64f40d4a41af9f7f83a4e236ad0cf8cca621b97e31f727b1bebdae565a653104 - nokogiri (1.18.10-x86_64-darwin) sha256=536e74bed6db2b5076769cab5e5f5af0cd1dccbbd75f1b3e1fa69d1f5c2d79e2 - nokogiri (1.18.10-x86_64-linux-gnu) sha256=ff5ba26ba2dbce5c04b9ea200777fd225061d7a3930548806f31db907e500f72 + nokogiri (1.19.0) sha256=e304d21865f62518e04f2bf59f93bd3a97ca7b07e7f03952946d8e1c05f45695 + nokogiri (1.19.0-aarch64-linux-gnu) sha256=11a97ecc3c0e7e5edcf395720b10860ef493b768f6aa80c539573530bc933767 + nokogiri (1.19.0-arm-linux-gnu) sha256=572a259026b2c8b7c161fdb6469fa2d0edd2b61cd599db4bbda93289abefbfe5 + nokogiri (1.19.0-arm64-darwin) sha256=0811dfd936d5f6dd3f6d32ef790568bf29b2b7bead9ba68866847b33c9cf5810 + nokogiri (1.19.0-java) sha256=5f3a70e252be641d8a4099f7fb4cc25c81c632cb594eec9b4b8f2ca8be4374f3 + nokogiri (1.19.0-x64-mingw-ucrt) sha256=05d7ed2d95731edc9bef2811522dc396df3e476ef0d9c76793a9fca81cab056b + nokogiri (1.19.0-x86_64-darwin) sha256=1dad56220b603a8edb9750cd95798bffa2b8dd9dd9aa47f664009ee5b43e3067 + nokogiri (1.19.0-x86_64-linux-gnu) sha256=f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70 power_assert (3.0.1) sha256=8ce9876716cc74e863fcd4cdcdc52d792bd983598d1af3447083a3a9a4d34103 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 - rb_sys (0.9.117) sha256=755feaf6c640baceca7a9362dfb0ae87ff4ff16e3566d9ef86533896eb85cb59 + rake-compiler-dock (1.10.0) sha256=dd62ee19df2a185a3315697e560cfa8cc9129901332152851e023fab0e94bf11 + rb_sys (0.9.123) sha256=c22ae84d1bca3eec0f13a45ae4ca9ba6eace93d5be270a40a9c0a9a5b92a34e5 rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 ronn-ng (0.10.1) sha256=4eeb0185c0fbfa889efed923b5b50e949cd869e7d82ac74138acd0c9c7165ec0 rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 @@ -124,8 +124,8 @@ CHECKSUMS rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c - test-unit (3.7.1) sha256=be595279c3427c00f57600db3dd59b6a9596e1f1e447864b158608d8e0d91570 - test-unit-ruby-core (1.0.13) sha256=0c08e61b3b97060028a47d8fc4827de077fdbfc26ef80b4afd9035b9e15ecc5b + test-unit (3.7.7) sha256=3c89d5ff0690a16bef9946156c4624390402b9d54dfcf4ce9cbd5b06bead1e45 + test-unit-ruby-core (1.0.14) sha256=d2e997796c9c5c5e8e31ac014f83a473ff5c2523a67cfa491b08893e12d43d22 turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 251abc37dd0f23..defc0afdf42fac 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -2,23 +2,24 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.3) - date (3.5.0) - date (3.5.0-java) + date (3.5.1) + date (3.5.1-java) diff-lcs (1.6.2) - erb (6.0.0) - erb (6.0.0-java) - io-console (0.8.1) - io-console (0.8.1-java) - irb (1.15.3) + erb (6.0.1) + erb (6.0.1-java) + io-console (0.8.2) + io-console (0.8.2-java) + irb (1.16.0) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jar-dependencies (0.5.5) - json (2.16.0) - json (2.16.0-java) + json (2.18.0) + json (2.18.0-java) language_server-protocol (3.17.0.5) lint_roller (1.1.0) - minitest (5.26.1) + minitest (6.0.1) + prism (~> 1.5) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -27,23 +28,23 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) - psych (5.2.6) + prism (1.7.0) + psych (5.3.1) date stringio - psych (5.2.6-java) + psych (5.3.1-java) date jar-dependencies (>= 0.1.7) racc (1.8.1) racc (1.8.1-java) rainbow (3.1.1) rake (13.3.1) - rake-compiler (1.3.0) + rake-compiler (1.3.1) rake - rake-compiler-dock (1.9.1) - rb_sys (0.9.117) - rake-compiler-dock (= 1.9.1) - rdoc (6.15.1) + rake-compiler-dock (1.10.0) + rb_sys (0.9.123) + rake-compiler-dock (= 1.10.0) + rdoc (7.0.3) erb psych (>= 4.0.0) tsort @@ -63,7 +64,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) - rubocop (1.81.7) + rubocop (1.82.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -71,20 +72,20 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) + rubocop-ast (1.49.0) parser (>= 3.3.7.2) - prism (~> 1.4) + prism (~> 1.7) ruby-progressbar (1.13.0) - stringio (3.1.8) - test-unit (3.7.1) + stringio (3.2.0) + test-unit (3.7.7) power_assert tsort (0.2.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) PLATFORMS aarch64-darwin @@ -109,36 +110,36 @@ DEPENDENCIES CHECKSUMS ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 - date (3.5.0) sha256=5e74fd6c04b0e65d97ad4f3bb5cb2d8efb37f386cc848f46310b4593ffc46ee5 - date (3.5.0-java) sha256=d6876651299185b935e1b834a353e3a1d1db054be478967e8104e30a9a8f1127 + date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 + date (3.5.1-java) sha256=12e09477dc932afe45bf768cd362bf73026804e0db1e6c314186d6cd0bee3344 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 - erb (6.0.0) sha256=2730893f9d8c9733f16cab315a4e4b71c1afa9cabc1a1e7ad1403feba8f52579 - erb (6.0.0-java) sha256=6537c84b596d889c4e20d87da41b38664e79bfe0af812ba7ea2a82a7ebf0ed62 - io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb - io-console (0.8.1-java) sha256=9457a61a7b23aab11e9e9ff67f71ae81d7f1a6a2e582bb5d65d754cbb546c06f - irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa + erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5 + erb (6.0.1-java) sha256=5c6b8d885fb0220d4a8ad158f70430d805845939dd44827e5130ef7fdbaed8ba + io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + io-console (0.8.2-java) sha256=837efefe96084c13ae91114917986ae6c6d1cf063b27b8419cc564a722a38af8 + irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806 jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 - json (2.16.0) sha256=ca5630320bb5ca23ebfd0bac84532fab56eb357575653b815b9df42c051e1525 - json (2.16.0-java) sha256=ee4c6fcc8f4efd156414b3e253e51675ec95bdfc79230b0bd5ec3a6139ed6d22 + json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 + json (2.18.0-java) sha256=74706f684baeb1a40351ed26fc8fe6e958afa861320d1c28ff4eb7073b29c7aa language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 - minitest (5.26.1) sha256=f16a63d4278e230bba342c3bda3006a69c5216d46461b77dd57f7c7c529b5a96 + minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 power_assert (3.0.1) sha256=8ce9876716cc74e863fcd4cdcdc52d792bd983598d1af3447083a3a9a4d34103 pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prism (1.6.0) sha256=bfc0281a81718c4872346bc858dc84abd3a60cae78336c65ad35c8fbff641c6b - psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e - psych (5.2.6-java) sha256=0a5f65d47ed1ae3475d062b254e7b2035a259eac578038016d62172dd4cfbd91 + prism (1.7.0) sha256=10062f734bf7985c8424c44fac382ac04a58124ea3d220ec3ba9fe4f2da65103 + psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 + psych (5.3.1-java) sha256=20a4a81ad01479ef060f604ed75ba42fe673169e67d923b1bae5aa4e13cc5820 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - rake-compiler (1.3.0) sha256=eec272ef6d4dad27b36f5cdcf5b9ee4df2193751f4082b095f981ebf9cdf4127 - rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 - rb_sys (0.9.117) sha256=755feaf6c640baceca7a9362dfb0ae87ff4ff16e3566d9ef86533896eb85cb59 - rdoc (6.15.1) sha256=28bfac73cd8b226a8079f53a564ceaccdb5b4480e34335100001570ddb1a481a + rake-compiler (1.3.1) sha256=6b351612b6e2d73ddd5563ee799bb58685176e05363db6758504bd11573d670a + rake-compiler-dock (1.10.0) sha256=dd62ee19df2a185a3315697e560cfa8cc9129901332152851e023fab0e94bf11 + rb_sys (0.9.123) sha256=c22ae84d1bca3eec0f13a45ae4ca9ba6eace93d5be270a40a9c0a9a5b92a34e5 + rdoc (7.0.3) sha256=dfe3d0981d19b7bba71d9dbaeb57c9f4e3a7a4103162148a559c4fc687ea81f9 regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 @@ -146,14 +147,14 @@ CHECKSUMS rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 - rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094 - rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76 + rubocop (1.82.1) sha256=09f1a6a654a960eda767aebea33e47603080f8e9c9a3f019bf9b94c9cab5e273 + rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 - stringio (3.1.8) sha256=99c43c3a9302843cca223fd985bfc503dd50a4b1723d3e4a9eb1d9c37d99e4ec - test-unit (3.7.1) sha256=be595279c3427c00f57600db3dd59b6a9596e1f1e447864b158608d8e0d91570 + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + test-unit (3.7.7) sha256=3c89d5ff0690a16bef9946156c4624390402b9d54dfcf4ce9cbd5b06bead1e45 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 - unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH 4.1.0.dev diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 1b48fe058257f4..199d86d3cb6253 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -2,23 +2,24 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.3) - date (3.5.0) - date (3.5.0-java) + date (3.5.1) + date (3.5.1-java) diff-lcs (1.6.2) - erb (6.0.0) - erb (6.0.0-java) - io-console (0.8.1) - io-console (0.8.1-java) - irb (1.15.3) + erb (6.0.1) + erb (6.0.1-java) + io-console (0.8.2) + io-console (0.8.2-java) + irb (1.16.0) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jar-dependencies (0.5.5) - json (2.16.0) - json (2.16.0-java) + json (2.18.0) + json (2.18.0-java) language_server-protocol (3.17.0.5) lint_roller (1.1.0) - minitest (5.26.1) + minitest (6.0.1) + prism (~> 1.5) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -27,23 +28,23 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) - psych (5.2.6) + prism (1.7.0) + psych (5.3.1) date stringio - psych (5.2.6-java) + psych (5.3.1-java) date jar-dependencies (>= 0.1.7) racc (1.8.1) racc (1.8.1-java) rainbow (3.1.1) rake (13.3.1) - rake-compiler (1.3.0) + rake-compiler (1.3.1) rake - rake-compiler-dock (1.9.1) - rb_sys (0.9.117) - rake-compiler-dock (= 1.9.1) - rdoc (6.15.1) + rake-compiler-dock (1.10.0) + rb_sys (0.9.123) + rake-compiler-dock (= 1.10.0) + rdoc (7.0.3) erb psych (>= 4.0.0) tsort @@ -63,7 +64,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) - rubocop (1.80.2) + rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -71,36 +72,36 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) + rubocop-ast (1.49.0) parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-performance (1.25.0) + prism (~> 1.7) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) - standard (1.51.1) + standard (1.52.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.80.2) + rubocop (~> 1.81.7) standard-custom (~> 1.0.0) standard-performance (~> 1.8) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.8.0) + standard-performance (1.9.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.25.0) - stringio (3.1.8) - test-unit (3.7.1) + rubocop-performance (~> 1.26.0) + stringio (3.2.0) + test-unit (3.7.7) power_assert tsort (0.2.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) PLATFORMS aarch64-darwin @@ -125,36 +126,36 @@ DEPENDENCIES CHECKSUMS ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 - date (3.5.0) sha256=5e74fd6c04b0e65d97ad4f3bb5cb2d8efb37f386cc848f46310b4593ffc46ee5 - date (3.5.0-java) sha256=d6876651299185b935e1b834a353e3a1d1db054be478967e8104e30a9a8f1127 + date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 + date (3.5.1-java) sha256=12e09477dc932afe45bf768cd362bf73026804e0db1e6c314186d6cd0bee3344 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 - erb (6.0.0) sha256=2730893f9d8c9733f16cab315a4e4b71c1afa9cabc1a1e7ad1403feba8f52579 - erb (6.0.0-java) sha256=6537c84b596d889c4e20d87da41b38664e79bfe0af812ba7ea2a82a7ebf0ed62 - io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb - io-console (0.8.1-java) sha256=9457a61a7b23aab11e9e9ff67f71ae81d7f1a6a2e582bb5d65d754cbb546c06f - irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa + erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5 + erb (6.0.1-java) sha256=5c6b8d885fb0220d4a8ad158f70430d805845939dd44827e5130ef7fdbaed8ba + io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + io-console (0.8.2-java) sha256=837efefe96084c13ae91114917986ae6c6d1cf063b27b8419cc564a722a38af8 + irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806 jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 - json (2.16.0) sha256=ca5630320bb5ca23ebfd0bac84532fab56eb357575653b815b9df42c051e1525 - json (2.16.0-java) sha256=ee4c6fcc8f4efd156414b3e253e51675ec95bdfc79230b0bd5ec3a6139ed6d22 + json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 + json (2.18.0-java) sha256=74706f684baeb1a40351ed26fc8fe6e958afa861320d1c28ff4eb7073b29c7aa language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 - minitest (5.26.1) sha256=f16a63d4278e230bba342c3bda3006a69c5216d46461b77dd57f7c7c529b5a96 + minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 power_assert (3.0.1) sha256=8ce9876716cc74e863fcd4cdcdc52d792bd983598d1af3447083a3a9a4d34103 pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prism (1.6.0) sha256=bfc0281a81718c4872346bc858dc84abd3a60cae78336c65ad35c8fbff641c6b - psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e - psych (5.2.6-java) sha256=0a5f65d47ed1ae3475d062b254e7b2035a259eac578038016d62172dd4cfbd91 + prism (1.7.0) sha256=10062f734bf7985c8424c44fac382ac04a58124ea3d220ec3ba9fe4f2da65103 + psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 + psych (5.3.1-java) sha256=20a4a81ad01479ef060f604ed75ba42fe673169e67d923b1bae5aa4e13cc5820 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - rake-compiler (1.3.0) sha256=eec272ef6d4dad27b36f5cdcf5b9ee4df2193751f4082b095f981ebf9cdf4127 - rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 - rb_sys (0.9.117) sha256=755feaf6c640baceca7a9362dfb0ae87ff4ff16e3566d9ef86533896eb85cb59 - rdoc (6.15.1) sha256=28bfac73cd8b226a8079f53a564ceaccdb5b4480e34335100001570ddb1a481a + rake-compiler (1.3.1) sha256=6b351612b6e2d73ddd5563ee799bb58685176e05363db6758504bd11573d670a + rake-compiler-dock (1.10.0) sha256=dd62ee19df2a185a3315697e560cfa8cc9129901332152851e023fab0e94bf11 + rb_sys (0.9.123) sha256=c22ae84d1bca3eec0f13a45ae4ca9ba6eace93d5be270a40a9c0a9a5b92a34e5 + rdoc (7.0.3) sha256=dfe3d0981d19b7bba71d9dbaeb57c9f4e3a7a4103162148a559c4fc687ea81f9 regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 @@ -162,18 +163,18 @@ CHECKSUMS rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 - rubocop (1.80.2) sha256=6485f30fefcf5c199db3b91e5e253b1ef43f7e564784e2315255809a3dd9abf4 - rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76 - rubocop-performance (1.25.0) sha256=6f7d03568a770054117a78d0a8e191cefeffb703b382871ca7743831b1a52ec1 + rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094 + rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd + rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 - standard (1.51.1) sha256=6d0d98a1fac26d660393f37b3d9c864632bb934b17abfa23811996b20f87faf2 + standard (1.52.0) sha256=ec050e63228e31fabe40da3ef96da7edda476f7acdf3e7c2ad47b6e153f6a076 standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b - standard-performance (1.8.0) sha256=ed17b7d0e061b2a19a91dd434bef629439e2f32310f22f26acb451addc92b788 - stringio (3.1.8) sha256=99c43c3a9302843cca223fd985bfc503dd50a4b1723d3e4a9eb1d9c37d99e4ec - test-unit (3.7.1) sha256=be595279c3427c00f57600db3dd59b6a9596e1f1e447864b158608d8e0d91570 + standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2 + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + test-unit (3.7.7) sha256=3c89d5ff0690a16bef9946156c4624390402b9d54dfcf4ce9cbd5b06bead1e45 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 - unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH 4.1.0.dev diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 6fb9d1c0c5eb77..fdffc1f09deffb 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -4,9 +4,9 @@ GEM base64 (0.3.0) builder (3.3.0) compact_index (0.15.0) - concurrent-ruby (1.3.5) - date (3.5.0) - date (3.5.0-java) + concurrent-ruby (1.3.6) + date (3.5.1) + date (3.5.1-java) etc (1.4.6) fiddle (1.1.8) jar-dependencies (0.5.5) @@ -14,10 +14,10 @@ GEM mustermann (3.0.4) ruby2_keywords (~> 0.0.1) open3 (0.2.1) - psych (5.2.6) + psych (5.3.1) date stringio - psych (5.2.6-java) + psych (5.3.1-java) date jar-dependencies (>= 0.1.7) rack (3.2.4) @@ -31,9 +31,9 @@ GEM rack-test (2.2.0) rack (>= 1.3) rake (13.3.1) - rake-compiler-dock (1.9.1) - rb_sys (0.9.117) - rake-compiler-dock (= 1.9.1) + rake-compiler-dock (1.10.0) + rb_sys (0.9.123) + rake-compiler-dock (= 1.10.0) ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) @@ -45,7 +45,7 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - stringio (3.1.8) + stringio (3.2.0) tilt (2.6.1) PLATFORMS @@ -77,29 +77,29 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b - concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 - date (3.5.0) sha256=5e74fd6c04b0e65d97ad4f3bb5cb2d8efb37f386cc848f46310b4593ffc46ee5 - date (3.5.0-java) sha256=d6876651299185b935e1b834a353e3a1d1db054be478967e8104e30a9a8f1127 + concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab + date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 + date (3.5.1-java) sha256=12e09477dc932afe45bf768cd362bf73026804e0db1e6c314186d6cd0bee3344 etc (1.4.6) sha256=0f7e9e7842ea5e3c3bd9bc81746ebb8c65ea29e4c42a93520a0d638129c7de01 fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 mustermann (3.0.4) sha256=85fadcb6b3c6493a8b511b42426f904b7f27b282835502233dd154daab13aa22 open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952 - psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e - psych (5.2.6-java) sha256=0a5f65d47ed1ae3475d062b254e7b2035a259eac578038016d62172dd4cfbd91 + psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 + psych (5.3.1-java) sha256=20a4a81ad01479ef060f604ed75ba42fe673169e67d923b1bae5aa4e13cc5820 rack (3.2.4) sha256=5d74b6f75082a643f43c1e76b419c40f0e5527fcfee1e669ac1e6b73c0ccb6f6 rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 - rb_sys (0.9.117) sha256=755feaf6c640baceca7a9362dfb0ae87ff4ff16e3566d9ef86533896eb85cb59 + rake-compiler-dock (1.10.0) sha256=dd62ee19df2a185a3315697e560cfa8cc9129901332152851e023fab0e94bf11 + rb_sys (0.9.123) sha256=c22ae84d1bca3eec0f13a45ae4ca9ba6eace93d5be270a40a9c0a9a5b92a34e5 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c shellwords (0.2.2) sha256=b8695a791de2f71472de5abdc3f4332f6535a4177f55d8f99e7e44266cd32f94 sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 - stringio (3.1.8) sha256=99c43c3a9302843cca223fd985bfc503dd50a4b1723d3e4a9eb1d9c37d99e4ec + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH From 8c8561adbc3d8761ef70983c01d6ade0004d0fb8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Jan 2026 14:15:05 +0900 Subject: [PATCH 2333/2435] [ruby/rubygems] Update vendored connection_pool to 2.5.5 https://github.com/ruby/rubygems/commit/4c1eb2b274 --- .../connection_pool/lib/connection_pool.rb | 3 +++ .../lib/connection_pool/timed_stack.rb | 18 ++++++++++++++---- .../lib/connection_pool/version.rb | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index 8cb81be7301ac6..e8aaf70016bdd1 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -104,6 +104,9 @@ def initialize(options = {}, &block) end def with(options = {}) + # We need to manage exception handling manually here in order + # to work correctly with `Gem::Timeout.timeout` and `Thread#raise`. + # Otherwise an interrupted Thread can leak connections. Thread.handle_interrupt(Exception => :never) do conn = checkout(options) begin diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index 364662d801b49a..026d2c5be27a4b 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -54,9 +54,12 @@ def push(obj, options = {}) # immediately returned. If no connection is available within the given # timeout a Bundler::ConnectionPool::TimeoutError is raised. # - # +:timeout+ is the only checked entry in +options+ and is preferred over - # the +timeout+ argument (which will be removed in a future release). Other - # options may be used by subclasses that extend TimedStack. + # @option options [Float] :timeout (0.5) Wait this many seconds for an available entry + # @option options [Class] :exception (Bundler::ConnectionPool::TimeoutError) Exception class to raise + # if an entry was not available within the timeout period. Use `exception: false` to return nil. + # + # The +timeout+ argument will be removed in 3.0. + # Other options may be used by subclasses that extend TimedStack. def pop(timeout = 0.5, options = {}) options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout @@ -73,7 +76,14 @@ def pop(timeout = 0.5, options = {}) return connection if connection to_wait = deadline - current_time - raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 + if to_wait <= 0 + exc = options.fetch(:exception, Bundler::ConnectionPool::TimeoutError) + if exc + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" + else + return nil + end + end @resource.wait(@mutex, to_wait) end end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb index cbac68b3dc83c9..2e9eebdbb6d2dc 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -1,3 +1,3 @@ class Bundler::ConnectionPool - VERSION = "2.5.4" + VERSION = "2.5.5" end From b87b6edf2e557db7b2787d7a213a6aa03f93a146 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Jan 2026 14:15:46 +0900 Subject: [PATCH 2334/2435] [ruby/rubygems] Update vendored net-http to 0.9.1 https://github.com/ruby/rubygems/commit/12072e8d23 --- lib/rubygems/vendor/net-http/lib/net/http.rb | 83 +++++++++++++------ .../net-http/lib/net/http/exceptions.rb | 3 +- .../net-http/lib/net/http/generic_request.rb | 11 ++- .../vendor/net-http/lib/net/http/header.rb | 12 ++- .../vendor/net-http/lib/net/http/requests.rb | 16 +++- .../vendor/net-http/lib/net/http/response.rb | 3 +- .../vendor/net-http/lib/net/http/responses.rb | 68 +++++++++++++++ 7 files changed, 158 insertions(+), 38 deletions(-) diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index b5b93befffd4f3..4800cd25f13c21 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -724,7 +724,7 @@ class HTTPHeaderSyntaxError < StandardError; end class HTTP < Protocol # :stopdoc: - VERSION = "0.7.0" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -1179,6 +1179,7 @@ def initialize(address, port = nil) # :nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1321,6 +1322,9 @@ def response_body_encoding=(value) # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1527,7 +1531,7 @@ def use_ssl=(flag) :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: @@ -1632,6 +1636,21 @@ def start # :yield: http self end + # Finishes the \HTTP session: + # + # http = Gem::Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1654,14 +1673,15 @@ def connect end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Gem::Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1754,23 +1774,30 @@ def connect end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Gem::Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1821,6 +1848,8 @@ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ss } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1915,6 +1944,7 @@ def proxy_pass alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) require 'cgi/escape' @@ -1943,6 +1973,7 @@ def edit_path(path) path end end + # :startdoc: # # HTTP operations @@ -2397,7 +2428,9 @@ def send_entity(path, data, initheader, dest, type, &block) res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2554,7 +2587,7 @@ def debug(msg) alias_method :D, :debug end - # for backward compatibility until Ruby 3.5 + # for backward compatibility until Ruby 4.0 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb index c629c0113b1a91..218df9a8bd1524 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ def initialize(msg, res) #:nodoc: alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb index 0737b3f02afa95..d6496d4ac191e3 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -245,7 +242,7 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only end if host = self['host'] - host.sub!(/:.*/m, '') + host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -264,6 +261,8 @@ def update_uri(addr, port, ssl) # :nodoc: internal use only private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb index 5cb1da01ec07f6..bc68cd2eefb130 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ def add_field(key, val) end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ def add_field(key, val) ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ def each_capitalized alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ def connection_close? false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb index 45727e7f61fc0f..f990761042dad8 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest # - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb index cbbd191d879ba7..dc164f1504488a 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb @@ -153,6 +153,7 @@ def read_new(sock) #:nodoc: internal use only end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ def body_encoding=(value) # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb index 32738adc14429f..62ce1cba1b2917 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Gem::Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ class HTTPUnknownResponse < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ class HTTPInformation < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ class HTTPSuccess < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ class HTTPRedirection < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ class HTTPClientError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ class HTTPServerError < HTTPResponse # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ class HTTPContinue < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ class HTTPSwitchProtocol < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ class HTTPProcessing < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ class HTTPEarlyHints < HTTPInformation # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ class HTTPOK < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ class HTTPCreated < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ class HTTPAccepted < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ class HTTPNonAuthoritativeInformation < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ class HTTPNoContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ class HTTPResetContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ class HTTPPartialContent < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ class HTTPMultiStatus < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ class HTTPAlreadyReported < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ class HTTPIMUsed < HTTPSuccess # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ class HTTPMultipleChoices < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ class HTTPMovedPermanently < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ class HTTPFound < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ class HTTPSeeOther < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ class HTTPNotModified < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ class HTTPUseProxy < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ class HTTPTemporaryRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ class HTTPPermanentRedirect < HTTPRedirection # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ class HTTPBadRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ class HTTPUnauthorized < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ class HTTPPaymentRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ class HTTPForbidden < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ class HTTPNotFound < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ class HTTPMethodNotAllowed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ class HTTPNotAcceptable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ class HTTPProxyAuthenticationRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ class HTTPRequestTimeout < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ class HTTPConflict < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ class HTTPGone < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ class HTTPLengthRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ class HTTPPreconditionFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ class HTTPPayloadTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ class HTTPURITooLong < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ class HTTPUnsupportedMediaType < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ class HTTPRangeNotSatisfiable < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ class HTTPExpectationFailed < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ class HTTPMisdirectedRequest < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ class HTTPUnprocessableEntity < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ class HTTPLocked < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ class HTTPFailedDependency < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ class HTTPUpgradeRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ class HTTPPreconditionRequired < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ class HTTPTooManyRequests < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ class HTTPUnavailableForLegalReasons < HTTPClientError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ class HTTPInternalServerError < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ class HTTPNotImplemented < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ class HTTPBadGateway < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ class HTTPServiceUnavailable < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ class HTTPGatewayTimeout < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ class HTTPVersionNotSupported < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ class HTTPVariantAlsoNegotiates < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ class HTTPInsufficientStorage < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ class HTTPLoopDetected < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,12 +1158,14 @@ class HTTPNotExtended < HTTPServerError # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Gem::Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, From d3ecd9d2ab4478ca29db20c1ab6c425a24bee83d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 5 Jan 2026 15:36:06 +0900 Subject: [PATCH 2335/2435] [ruby/rubygems] Lock minitest ~> 5.1 https://github.com/ruby/rubygems/commit/7b9bfb4e18 --- tool/bundler/rubocop_gems.rb | 2 +- tool/bundler/rubocop_gems.rb.lock | 7 +++---- tool/bundler/standard_gems.rb | 2 +- tool/bundler/standard_gems.rb.lock | 7 +++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tool/bundler/rubocop_gems.rb b/tool/bundler/rubocop_gems.rb index ab13b1b2c374f8..a9b6fda11b30bf 100644 --- a/tool/bundler/rubocop_gems.rb +++ b/tool/bundler/rubocop_gems.rb @@ -4,7 +4,7 @@ gem "rubocop", ">= 1.52.1", "< 2" -gem "minitest" +gem "minitest", "~> 5.1" gem "irb" gem "rake" gem "rake-compiler" diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index defc0afdf42fac..d0c3120e119d10 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -18,8 +18,7 @@ GEM json (2.18.0-java) language_server-protocol (3.17.0.5) lint_roller (1.1.0) - minitest (6.0.1) - prism (~> 1.5) + minitest (5.27.0) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -100,7 +99,7 @@ PLATFORMS DEPENDENCIES irb - minitest + minitest (~> 5.1) rake rake-compiler rb_sys @@ -123,7 +122,7 @@ CHECKSUMS json (2.18.0-java) sha256=74706f684baeb1a40351ed26fc8fe6e958afa861320d1c28ff4eb7073b29c7aa language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 - minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb + minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 power_assert (3.0.1) sha256=8ce9876716cc74e863fcd4cdcdc52d792bd983598d1af3447083a3a9a4d34103 diff --git a/tool/bundler/standard_gems.rb b/tool/bundler/standard_gems.rb index fab93e6d6d8938..f7bc34cf5ee1fc 100644 --- a/tool/bundler/standard_gems.rb +++ b/tool/bundler/standard_gems.rb @@ -4,7 +4,7 @@ gem "standard", "~> 1.0" -gem "minitest" +gem "minitest", "~> 5.1" gem "irb" gem "rake" gem "rake-compiler" diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 199d86d3cb6253..f3792f8611e36a 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -18,8 +18,7 @@ GEM json (2.18.0-java) language_server-protocol (3.17.0.5) lint_roller (1.1.0) - minitest (6.0.1) - prism (~> 1.5) + minitest (5.27.0) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -116,7 +115,7 @@ PLATFORMS DEPENDENCIES irb - minitest + minitest (~> 5.1) rake rake-compiler rb_sys @@ -139,7 +138,7 @@ CHECKSUMS json (2.18.0-java) sha256=74706f684baeb1a40351ed26fc8fe6e958afa861320d1c28ff4eb7073b29c7aa language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 - minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb + minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 power_assert (3.0.1) sha256=8ce9876716cc74e863fcd4cdcdc52d792bd983598d1af3447083a3a9a4d34103 From 28b0e5125f1dd62b202a28aaa1e79190dad7eb02 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 5 Jan 2026 15:47:40 +0900 Subject: [PATCH 2336/2435] [Bug #18433] Remove unneeded declaration This `rb_cObject` declaration was only for `rb_cData()` that was removed at 7c738ce5e649b82bdc1305d5c347e81886ee759a. --- include/ruby/internal/core/rdata.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/ruby/internal/core/rdata.h b/include/ruby/internal/core/rdata.h index 6c58ddedf1be25..cee5e7b5ea3640 100644 --- a/include/ruby/internal/core/rdata.h +++ b/include/ruby/internal/core/rdata.h @@ -180,11 +180,6 @@ VALUE rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_D */ VALUE rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree); -/** - * @private - * Documented in include/ruby/internal/globals.h - */ -RUBY_EXTERN VALUE rb_cObject; RBIMPL_SYMBOL_EXPORT_END() /** From 32674606102d21ec56635ff4b496544dd01775a6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 5 Jan 2026 16:16:29 +0900 Subject: [PATCH 2337/2435] Update bindgen --- zjit/src/cruby_bindings.inc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5bb62e89fa8d3e..efb1559fb75512 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1855,7 +1855,6 @@ unsafe extern "C" { pub fn rb_gc_disable() -> VALUE; pub fn rb_gc_writebarrier(old: VALUE, young: VALUE); pub fn rb_class_get_superclass(klass: VALUE) -> VALUE; - pub static mut rb_cObject: VALUE; pub fn rb_funcallv( recv: VALUE, mid: ID, @@ -1864,6 +1863,7 @@ unsafe extern "C" { ) -> VALUE; pub static mut rb_mKernel: VALUE; pub static mut rb_cBasicObject: VALUE; + pub static mut rb_cObject: VALUE; pub static mut rb_cArray: VALUE; pub static mut rb_cClass: VALUE; pub static mut rb_cFalseClass: VALUE; From c65a5548a80f955ad71decabf0f29183fef8d6d7 Mon Sep 17 00:00:00 2001 From: Brandon Zylstra <9854+brandonzylstra@users.noreply.github.com> Date: Sun, 4 Jan 2026 22:26:21 -0500 Subject: [PATCH 2338/2435] Update box.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Code" (when used to refer to what we create in Ruby or any other programming language) is an abstract non-count noun, so it cannot be pluralized. ("Codes" would be used when referring to specific countable things like PIN codes, which is a different use of the word "code".) This is somewhat confusing because English allows converting count nouns into non-count nouns, and converting non-count nouns into count nouns, and because many words have both forms. For an example of converting a non-count noun to a count noun, "water" is normally a non-count noun: > The world is covered with water. but people who work in restaurants often use the word as a count noun, as a shorthand for "cup of water": > I need 7 waters on the big table by the window. For an example of the opposite conversion, "worm" is normally a count noun: > There are lots of worms in the puddle. but someone might use it as a non-count noun when talking about non-distinct remains of worms: > You have worm all over the bottom of your shoe! So although a given noun can be flexible enough to be used in either way—even when it is unconventional—there is a definite change of meaning when using a word as a count noun or a non-count noun. --- doc/language/box.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/language/box.md b/doc/language/box.md index 05c0dad985cc5b..8c7fd20b202f09 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -1,6 +1,6 @@ # Ruby Box - Ruby's in-process separation of Classes and Modules -Ruby Box is designed to provide separated spaces in a Ruby process, to isolate application codes, libraries and monkey patches. +Ruby Box is designed to provide separated spaces in a Ruby process, to isolate application code, libraries and monkey patches. ## Known issues From 7e81bf5c0c8f43602e6d901f4253dca2f3d71745 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 5 Jan 2026 12:18:47 -0500 Subject: [PATCH 2339/2435] Fix sleep spurious wakeup from sigchld (#15802) When sleeping with `sleep`, currently the main thread can get woken up from sigchld from any thread (subprocess exited). The timer thread wakes up the main thread when this happens, as it checks for signals. The main thread then executes the ruby sigchld handler if one is registered and is supposed to go back to sleep immediately. This is not ideal but it's the way it's worked for a while. In commit 8d8159e7d8 I added writes to `th->status` before and after `wait_running_turn` in `thread_sched_to_waiting_until_wakeup`, which is called from `sleep`. This is usually the right way to set the thread's status, but `sleep` is an exception because the writes to `th->status` are done in `sleep_forever`. There's a loop that checks `th->status` in `sleep_forever`. When the main thread got woken up from sigchld it saw the changed `th->status` and continued to run the main thread instead of going back to sleep. The following script shows the error. It was returning instead of sleeping forever. ```ruby t = Thread.new do sleep 0.3 `echo hello` # Spawns subprocess puts "Subprocess exited" end puts "Main thread sleeping..." result = sleep # Should block forever puts "sleep returned: #{result.inspect}" ``` Fixes [Bug #21812] --- test/ruby/test_sleep.rb | 18 ++++++++++++++++++ thread_pthread.c | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb index 991b73ebd50757..7ef962db4a3b6b 100644 --- a/test/ruby/test_sleep.rb +++ b/test/ruby/test_sleep.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require 'test/unit' require 'etc' +require 'timeout' class TestSleep < Test::Unit::TestCase def test_sleep_5sec @@ -13,4 +14,21 @@ def test_sleep_5sec assert_operator(slept, :<=, 6.0, "[ruby-core:18015]: longer than expected") end end + + def test_sleep_forever_not_woken_by_sigchld + begin + t = Thread.new do + sleep 0.5 + `echo hello` + end + + assert_raise Timeout::Error do + Timeout.timeout 2 do + sleep # Should block forever + end + end + ensure + t.join + end + end end diff --git a/thread_pthread.c b/thread_pthread.c index a2e42da13e70b1..9c7754067bcdf9 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1109,10 +1109,9 @@ thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t { if (!RUBY_VM_INTERRUPTED(th->ec)) { bool can_direct_transfer = !th_has_dedicated_nt(th); - th->status = THREAD_STOPPED_FOREVER; + // NOTE: th->status is set before and after this sleep outside of this function in `sleep_forever` thread_sched_wakeup_next_thread(sched, th, can_direct_transfer); thread_sched_wait_running_turn(sched, th, can_direct_transfer); - th->status = THREAD_RUNNABLE; } else { RUBY_DEBUG_LOG("th:%u interrupted", rb_th_serial(th)); From 23765a5e094f61b11bddce98f7fdd425d1c8ed87 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 5 Jan 2026 15:16:48 -0500 Subject: [PATCH 2340/2435] ZJIT: Update Iongraph (#15806) Fix some rendering bugs and remove React. --- tool/zjit_iongraph.html | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tool/zjit_iongraph.html b/tool/zjit_iongraph.html index e97ac74e4ac411..993cce90455260 100644 --- a/tool/zjit_iongraph.html +++ b/tool/zjit_iongraph.html @@ -1,7 +1,7 @@ + 39b04fa18f23cbf3fd2ca7339a45341ff3351ba1 in tekknolagi/iongraph --> @@ -337,6 +337,10 @@ overflow-y: auto; } +.ig-hide-if-empty:empty { + display: none; +} + /* Non-utility styles */ .ig-graph { @@ -511,8 +515,7 @@ -
- From 7a1180afb665286556315bcf27188263854b213b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 5 Jan 2026 13:33:57 -0800 Subject: [PATCH 2341/2435] ruby-bench: Prefer --excludes over rm -rf to remind us of skipped benchmarks in the CI job names --- .github/workflows/ubuntu.yml | 6 ++++-- .github/workflows/zjit-macos.yml | 6 ++++-- .github/workflows/zjit-ubuntu.yml | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6489454c57c74b..81c6bff401780e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -208,7 +208,7 @@ jobs: matrix: include: # Using the same setup as ZJIT jobs - - bench_opts: '--warmup=1 --bench=1' + - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' runs-on: ubuntu-24.04 @@ -242,8 +242,10 @@ jobs: repository: ruby/ruby-bench path: ruby-bench + # If you want to skip failing benchmark, consider using `--excludes`. + # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` - name: Run ruby-bench - run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "ruby::../build/install/bin/ruby" ${{ matrix.bench_opts }} + run: ruby run_benchmarks.rb -e "ruby::../build/install/bin/ruby" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 55bfcb30f22364..a638907811c3f1 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 @@ -197,8 +197,10 @@ jobs: repository: ruby/ruby-bench path: ruby-bench + # If you want to skip failing benchmark, consider using `--excludes`. + # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` - name: Run ruby-bench - run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 13b1add5b09568..4d5ecb7280c04d 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 @@ -250,8 +250,10 @@ jobs: repository: ruby/ruby-bench path: ruby-bench + # If you want to skip failing benchmark, consider using `--excludes`. + # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` - name: Run ruby-bench - run: rm -rf benchmarks/lobsters && ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} working-directory: ruby-bench - uses: ./.github/actions/slack From a25f4689175b5b0f7da0831dbcedf86b62f67e19 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 5 Jan 2026 16:18:16 +0000 Subject: [PATCH 2342/2435] [DOC] Harmonize #>= methods --- compar.c | 11 ++++++++--- hash.c | 5 ++--- numeric.c | 9 +++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/compar.c b/compar.c index eda7e83824a4e0..0fb7e5f6584e73 100644 --- a/compar.c +++ b/compar.c @@ -109,10 +109,15 @@ cmp_gt(VALUE x, VALUE y) /* * call-seq: - * obj >= other -> true or false + * self >= other -> true or false + * + * Returns whether +self+ is "greater than or equal to" +other+; + * equivalent to (self <=> other) >= 0: + * + * 'food' >= 'foo' # => true + * 'foo' >= 'foo' # => true + * 'foo' >= 'food' # => false * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value greater than or equal to 0. */ static VALUE diff --git a/hash.c b/hash.c index b0de8e943355af..f264bfba4bc40f 100644 --- a/hash.c +++ b/hash.c @@ -4950,10 +4950,9 @@ rb_hash_lt(VALUE hash, VALUE other) /* * call-seq: - * self >= other_hash -> true or false + * self >= other -> true or false * - * Returns +true+ if the entries of +self+ are a superset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a superset of the entries of +other+: * * h0 = {foo: 0, bar: 1, baz: 2} * h1 = {foo: 0, bar: 1} diff --git a/numeric.c b/numeric.c index 87d50ae4a1b9e8..8f866d00bde628 100644 --- a/numeric.c +++ b/numeric.c @@ -1674,7 +1674,8 @@ rb_float_gt(VALUE x, VALUE y) * call-seq: * self >= other -> true or false * - * Returns +true+ if +self+ is numerically greater than or equal to +other+: + * Returns whether the value of +self+ is greater than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 >= 1 # => true * 2.0 >= 1.0 # => true @@ -5001,10 +5002,10 @@ fix_ge(VALUE x, VALUE y) /* * call-seq: - * self >= real -> true or false + * self >= other -> true or false * - * Returns +true+ if the value of +self+ is greater than or equal to - * that of +other+: + * Returns whether the value of +self+ is greater than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 >= 0 # => true * 1 >= 1 # => true From dd67874ad9ef1beb57e2c76528ff715c525163c6 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Mon, 5 Jan 2026 18:29:04 +0000 Subject: [PATCH 2343/2435] [DOC] Harmonize #=== methods --- object.c | 11 ++++++----- range.c | 5 ++--- re.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/object.c b/object.c index aaa3326ebf6df5..ed7e0ff224cf0b 100644 --- a/object.c +++ b/object.c @@ -162,7 +162,7 @@ rb_obj_setup(VALUE obj, VALUE klass, VALUE type) * * Returns +true+ or +false+. * - * Like Object#==, if +object+ is an instance of Object + * Like Object#==, if +other+ is an instance of \Object * (and not an instance of one of its many subclasses). * * This method is commonly overridden by those subclasses, @@ -1892,11 +1892,12 @@ rb_mod_freeze(VALUE mod) /* * call-seq: - * mod === obj -> true or false + * self === other -> true or false * - * Case Equality---Returns true if obj is an - * instance of mod or an instance of one of mod's descendants. - * Of limited use for modules, but can be used in case statements + * Returns whether +other+ is an instance of +self+, + * or is an instance of a subclass of +self+. + * + * Of limited use for modules, but can be used in +case+ statements * to classify objects by class. */ diff --git a/range.c b/range.c index 7aa917bc067b68..c8a4b9938bab8e 100644 --- a/range.c +++ b/range.c @@ -2054,10 +2054,9 @@ VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive); /* * call-seq: - * self === object -> true or false + * self === other -> true or false * - * Returns +true+ if +object+ is between self.begin and self.end. - * +false+ otherwise: + * Returns whether +other+ is between self.begin and self.end: * * (1..4) === 2 # => true * (1..4) === 5 # => false diff --git a/re.c b/re.c index 621af13cd73f16..f8672d1d7ce187 100644 --- a/re.c +++ b/re.c @@ -3728,9 +3728,9 @@ rb_reg_match(VALUE re, VALUE str) /* * call-seq: - * regexp === string -> true or false + * self === other -> true or false * - * Returns +true+ if +self+ finds a match in +string+: + * Returns whether +self+ finds a match in +other+: * * /^[a-z]*$/ === 'HELLO' # => false * /^[A-Z]*$/ === 'HELLO' # => true From 5d26a2aeea1368c5e37cb75ca511e62c5e21960f Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 4 Jan 2026 14:14:26 -0500 Subject: [PATCH 2344/2435] [ruby/mmtk] Assert pinning object is not moved https://github.com/ruby/mmtk/commit/8813e76bf8 --- gc/mmtk/src/pinning_registry.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gc/mmtk/src/pinning_registry.rs b/gc/mmtk/src/pinning_registry.rs index 71cf73eaf913b4..b498b508f1f97b 100644 --- a/gc/mmtk/src/pinning_registry.rs +++ b/gc/mmtk/src/pinning_registry.rs @@ -110,6 +110,11 @@ impl GCWork for PinPinningChildren { target_object ); if pin { + debug_assert!( + target_object.get_forwarded_object().is_none(), + "Trying to pin {target_object} but has been moved" + ); + pinned_objs.push(target_object); } target_object From 95f2c78fc2391cc8e1b5d224857aec89c6a491cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:04:32 +0000 Subject: [PATCH 2345/2435] Bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/08eff52bf64351f401fb50d4972fa95b9f2c2d1b...21025c705c08248db411dc16f3619e6b5f9ea21a) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot_automerge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index aae1b3d9a8015d..a95c7005c41054 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -13,7 +13,7 @@ jobs: if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'ruby/ruby' steps: - name: Dependabot metadata - uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0 + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 id: metadata - name: Wait for status checks From 1b476606f2294d0ce52a7429c597491ff68b11ce Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 6 Jan 2026 13:07:49 +0900 Subject: [PATCH 2346/2435] Update the latest versions of actions --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 042748389ea87b..899d601aef6dba 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -72,7 +72,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 4fc54902315d31..ad0e63ba126ac0 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v6.0.1 - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index ee4f8c9a2a78a0..d3e734f8858a3b 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 7d5b5e79efe4c4..c5dec65e487ef0 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -40,7 +40,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index fe01b3dcbefbbb..1d14934df8734e 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -61,7 +61,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index c06d7b8fa93f74..87facc8a558701 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -58,7 +58,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 971547351de67e..cf4661555c82b2 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -48,7 +48,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 8e44b96908738f..9ff97d5a4e6d9c 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 81c6bff401780e..88c19b6fe60ee3 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -69,7 +69,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 6022da8c3b0585..0d2a6f05454c6e 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -98,7 +98,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e0bd6893a6f7e7..d9421374613e1a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index bd90a57c2a0d87..150f0b3275e5cc 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -128,7 +128,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 4d5ecb7280c04d..28bfec963e57f5 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -111,7 +111,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0 + - uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 with: ruby-version: '3.1' bundler: none From 3143543f958d4fa90d0423f84c598286098f1704 Mon Sep 17 00:00:00 2001 From: lolwut Date: Thu, 25 Dec 2025 21:05:50 -0600 Subject: [PATCH 2347/2435] [ruby/rubygems] Compare like values in find_bundler The input to this method is not guaranteed to be a string, it could be a `Gem::Version` this normalizes the comparison. https://github.com/ruby/rubygems/commit/1f43c7a988 --- lib/bundler/rubygems_integration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index d8f95cffb8fcd6..e04ef232592a0c 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -432,7 +432,7 @@ def default_specs end def find_bundler(version) - find_name("bundler").find {|s| s.version.to_s == version } + find_name("bundler").find {|s| s.version.to_s == version.to_s } end def find_name(name) From 16bdfa1b2a6b4cb40417e7673ad9eaa80664e4f0 Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 26 Dec 2025 12:21:51 -0600 Subject: [PATCH 2348/2435] [ruby/rubygems] Split logic to two lines https://github.com/ruby/rubygems/commit/5a6eca4cf9 --- lib/bundler/runtime.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 5eb827dcb2a8ed..f632ce9144ef5e 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -174,7 +174,8 @@ def clean(dry_run = false) spec_cache_paths = [] spec_gemspec_paths = [] spec_extension_paths = [] - Bundler.rubygems.add_default_gems_to(specs).values.each do |spec| + specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values + specs_to_keep.each do |spec| spec_gem_paths << spec.full_gem_path # need to check here in case gems are nested like for the rails git repo md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path) From 4377249bbf1a27b3d860ab8948b508f1b024ba1c Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 26 Dec 2025 12:21:51 -0600 Subject: [PATCH 2349/2435] [ruby/rubygems] Test for removing current bundler version https://github.com/ruby/rubygems/commit/675342e6d0 --- spec/bundler/commands/clean_spec.rb | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 6b678d0aa5451b..793aacf5c2b5cd 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -898,4 +898,41 @@ def should_not_have_gems(*gems) expect(very_simple_binary_extensions_dir).to be_nil end + + it "does not remove the bundler version currently running" do + gemfile <<-G + source "https://gem.repo1" + + gem "myrack" + G + + bundle "config set path vendor/bundle" + bundle "install" + + version = Bundler.gem_version.to_s + # Simulate that the locked bundler version is installed in the bundle path + # by creating the gem directory and gemspec (as would happen after bundle install with that version) + Pathname(vendored_gems("cache/bundler-#{version}.gem")).tap do |path| + path.basename.mkpath + FileUtils.touch(path) + end + FileUtils.touch(vendored_gems("gems/bundler-#{version}")) + Pathname(vendored_gems("specifications/bundler-#{version}.gemspec")).tap do |path| + path.basename.mkpath + path.write(<<~GEMSPEC) + Gem::Specification.new do |s| + s.name = "bundler" + s.version = "#{version}" + s.authors = ["bundler team"] + s.summary = "The best way to manage your application's dependencies" + end + GEMSPEC + end + + should_have_gems "bundler-#{version}" + + bundle :clean + + should_have_gems "bundler-#{version}" + end end From ad6b85450db1b252660dae4b514f5be35ccd38b9 Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 26 Dec 2025 12:21:51 -0600 Subject: [PATCH 2350/2435] [ruby/rubygems] Retain current bundler version on `bundle clean` Previously: In #9218 a reproduction is shared where running `bundle clean` using a binstub (`bin/bundle`) results in bundler removing itself. This results in Ruby falling back to its default bundler version. This behavior seems to be present for as long as there has been a default version of bundler (Ruby 2.6+). Now: Bundler will explicitly add its current version number to the specs to be preserved. This prevents `bundle clean` from removing the current bundler version. close https://github.com/ruby/rubygems/pull/9218 https://github.com/ruby/rubygems/commit/e3f0167ae4 --- lib/bundler/runtime.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index f632ce9144ef5e..5280e72aa24b5e 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -175,6 +175,12 @@ def clean(dry_run = false) spec_gemspec_paths = [] spec_extension_paths = [] specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values + + current_bundler = Bundler.rubygems.find_bundler(Bundler.gem_version) + if current_bundler + specs_to_keep << current_bundler + end + specs_to_keep.each do |spec| spec_gem_paths << spec.full_gem_path # need to check here in case gems are nested like for the rails git repo From bff7c6d9e9a423d4a7158b81658b3e77733b9be3 Mon Sep 17 00:00:00 2001 From: Steve Savio Date: Tue, 6 Jan 2026 10:34:44 +0300 Subject: [PATCH 2351/2435] [DOC] Fix minor typo on shareable procs section --- ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ractor.rb b/ractor.rb index 6679ad15886d88..ac04237fc6e4f1 100644 --- a/ractor.rb +++ b/ractor.rb @@ -653,7 +653,7 @@ def unmonitor port # in the Proc will be replaced with the value passed via the `self:` keyword, # or +nil+ if not given. # - # In a shareable Proc, access to any outer variables if prohibited. + # In a shareable Proc, access to any outer variables is prohibited. # # a = 42 # Ractor.shareable_proc{ p a } From 925d04108b8fff7dfa14ccd8ac19fd1522cc99c3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 6 Jan 2026 15:16:40 +0900 Subject: [PATCH 2352/2435] Add `rake` target to run the bundled `rake` --- common.mk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common.mk b/common.mk index 08fee9119a1ef9..e2fd5c4b279f7e 100644 --- a/common.mk +++ b/common.mk @@ -1620,6 +1620,11 @@ test-syntax-suggest: check: $(DOT_WAIT) $(PREPARE_SYNTAX_SUGGEST) test-syntax-suggest +RAKER = $(XRUBY) -I$(srcdir)/gems/lib$(PATH_SEPARATOR)$(srcdir)/.bundle/lib \ + -rrubygems $(srcdir)/.bundle/bin/rake +rake: + $(RAKER) $(RAKE_OPTS) $(RAKE) + test-bundler-precheck: $(TEST_RUNNABLE)-test-bundler-precheck no-test-bundler-precheck: yes-test-bundler-precheck: main $(arch)-fake.rb From 49ca241d6d5f589dec8f42fecdc8ecb96690b859 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Mon, 5 Jan 2026 21:30:06 +0000 Subject: [PATCH 2353/2435] Show a more verbose message if BASERUBY is too old --- configure.ac | 2 +- tool/missing-baseruby.bat | 9 +++++++-- win32/setup.mak | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 2bbce78fd05c25..6787e9b3067572 100644 --- a/configure.ac +++ b/configure.ac @@ -84,7 +84,7 @@ AC_ARG_WITH(baseruby, AC_PATH_PROG([BASERUBY], [ruby], [false]) ]) AS_IF([test "$HAVE_BASERUBY" != no], [ - RUBYOPT=- $BASERUBY --disable=gems -rerb -rfileutils -rtempfile "${tooldir}/missing-baseruby.bat" || HAVE_BASERUBY=no + RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" --verbose || HAVE_BASERUBY=no ]) AS_IF([test "${HAVE_BASERUBY:=no}" != no], [ AS_CASE(["$build_os"], [mingw*], [ diff --git a/tool/missing-baseruby.bat b/tool/missing-baseruby.bat index 45cee8c8788ed0..d39568fe86a7b9 100755 --- a/tool/missing-baseruby.bat +++ b/tool/missing-baseruby.bat @@ -21,5 +21,10 @@ call :warn "executable host ruby is required. use --with-baseruby option." call :warn "Note that BASERUBY must be Ruby 3.1.0 or later." call :abort (goto :eof ^;) -abort unless defined?(RubyVM::InstructionSequence) -abort if RUBY_VERSION < s[%r"warn .*Ruby ([\d.]+)(?:\.0)?",1] +verbose = true if ARGV[0] == "--verbose" +case +when !defined?(RubyVM::InstructionSequence) + abort(*(["BASERUBY must be CRuby"] if verbose)) +when RUBY_VERSION < s[%r[warn .*\KBASERUBY .*Ruby ([\d.]+)(?:\.0)?.*(?=\")],1] + abort(*(["#{$&}. Found: #{RUBY_VERSION}"] if verbose)) +end diff --git a/win32/setup.mak b/win32/setup.mak index 50090094552811..b06081cab99d98 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -47,7 +47,7 @@ prefix = $(prefix:\=/) @type $(config_make) >>$(MAKEFILE) @del $(config_make) > nul !if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" != "" - $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" + $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" --verbose !endif !if "$(WITH_GMP)" != "no" @($(CC) $(XINCFLAGS) < nul && (echo USE_GMP = yes) || exit /b 0) >>$(MAKEFILE) From 58fb95af36c86b00b134db5275becf7455ba4790 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 6 Jan 2026 16:13:15 -0600 Subject: [PATCH 2354/2435] [DOC] Harmonize #== methods (#15805) --- complex.c | 6 +++--- error.c | 4 ++-- hash.c | 14 +++++--------- numeric.c | 4 ++-- object.c | 20 ++++++++++++-------- proc.c | 11 +++++------ range.c | 4 ++-- rational.c | 4 ++-- re.c | 12 ++++++------ string.c | 18 +++++++++--------- struct.c | 4 ++-- 11 files changed, 50 insertions(+), 51 deletions(-) diff --git a/complex.c b/complex.c index a764557990e8dc..7b6c6c2a996a03 100644 --- a/complex.c +++ b/complex.c @@ -1226,10 +1226,10 @@ rb_complex_pow(VALUE self, VALUE other) /* * call-seq: - * complex == object -> true or false + * self == other -> true or false * - * Returns +true+ if self.real == object.real - * and self.imag == object.imag: + * Returns whether both self.real == other.real + * and self.imag == other.imag: * * Complex.rect(2, 3) == Complex.rect(2.0, 3.0) # => true * diff --git a/error.c b/error.c index f21a682d65d352..69dc559c5d03ed 100644 --- a/error.c +++ b/error.c @@ -2159,9 +2159,9 @@ try_convert_to_exception(VALUE obj) /* * call-seq: - * self == object -> true or false + * self == other -> true or false * - * Returns whether +object+ is the same class as +self+ + * Returns whether +other+ is the same class as +self+ * and its #message and #backtrace are equal to those of +self+. * */ diff --git a/hash.c b/hash.c index f264bfba4bc40f..dc47eb6ab77736 100644 --- a/hash.c +++ b/hash.c @@ -3980,17 +3980,13 @@ hash_equal(VALUE hash1, VALUE hash2, int eql) /* * call-seq: - * self == object -> true or false + * self == other -> true or false * - * Returns whether +self+ and +object+ are equal. + * Returns whether all of the following are true: * - * Returns +true+ if all of the following are true: - * - * - +object+ is a +Hash+ object (or can be converted to one). - * - +self+ and +object+ have the same keys (regardless of order). - * - For each key +key+, self[key] == object[key]. - * - * Otherwise, returns +false+. + * - +other+ is a +Hash+ object (or can be converted to one). + * - +self+ and +other+ have the same keys (regardless of order). + * - For each key +key+, self[key] == other[key]. * * Examples: * diff --git a/numeric.c b/numeric.c index 8f866d00bde628..ef44febe1b29cd 100644 --- a/numeric.c +++ b/numeric.c @@ -1501,7 +1501,7 @@ num_equal(VALUE x, VALUE y) * call-seq: * self == other -> true or false * - * Returns +true+ if +other+ has the same value as +self+, +false+ otherwise: + * Returns whether +other+ is numerically equal to +self+: * * 2.0 == 2 # => true * 2.0 == 2.0 # => true @@ -4852,7 +4852,7 @@ fix_equal(VALUE x, VALUE y) * call-seq: * self == other -> true or false * - * Returns +true+ if +self+ is numerically equal to +other+; +false+ otherwise. + * Returns whether +self+ is numerically equal to +other+: * * 1 == 2 #=> false * 1 == 1.0 #=> true diff --git a/object.c b/object.c index ed7e0ff224cf0b..2b3535158fe305 100644 --- a/object.c +++ b/object.c @@ -200,14 +200,18 @@ rb_eql(VALUE obj1, VALUE obj2) /** * call-seq: - * obj == other -> true or false - * obj.equal?(other) -> true or false - * obj.eql?(other) -> true or false - * - * Equality --- At the Object level, #== returns true - * only if +obj+ and +other+ are the same object. Typically, this - * method is overridden in descendant classes to provide - * class-specific meaning. + * self == other -> true or false + * equal?(other) -> true or false + * eql?(other) -> true or false + * + * Returns whether +self+ and +other+ are the same object: + * + * object = Object.new + * object == object # => true + * object == Object.new # => false + * + * Here in class \Object, #==, #equal?, and #eql? are the same method. + * A subclass may override #== to provide class-specific meaning. * * Unlike #==, the #equal? method should never be overridden by * subclasses as it is used to determine object identity (that is, diff --git a/proc.c b/proc.c index 4fa48196caccc2..b3159e14b743d8 100644 --- a/proc.c +++ b/proc.c @@ -1423,10 +1423,10 @@ rb_proc_get_iseq(VALUE self, int *is_proc) } /* call-seq: - * prc == other -> true or false - * prc.eql?(other) -> true or false + * self == other -> true or false + * eql?(other) -> true or false * - * Two procs are the same if, and only if, they were created from the same code block. + * Returns whether +self+ and +other+ were created from the same code block: * * def return_block(&block) * block @@ -1980,10 +1980,9 @@ method_entry_defined_class(const rb_method_entry_t *me) /* * call-seq: - * meth.eql?(other_meth) -> true or false - * meth == other_meth -> true or false + * self == other -> true or false * - * Two method objects are equal if they are bound to the same + * Returns whether +self+ and +other+ are bound to the same * object and refer to the same method definition and the classes * defining the methods are the same class or module. */ diff --git a/range.c b/range.c index c8a4b9938bab8e..fd08a81de7b8b1 100644 --- a/range.c +++ b/range.c @@ -154,14 +154,14 @@ recursive_equal(VALUE range, VALUE obj, int recur) * call-seq: * self == other -> true or false * - * Returns +true+ if and only if: + * Returns whether all of the following are true: * * - +other+ is a range. * - other.begin == self.begin. * - other.end == self.end. * - other.exclude_end? == self.exclude_end?. * - * Otherwise returns +false+. + * Examples: * * r = (1..5) * r == (1..5) # => true diff --git a/rational.c b/rational.c index 5463395459c027..51078f81ad3cf2 100644 --- a/rational.c +++ b/rational.c @@ -1145,9 +1145,9 @@ rb_rational_cmp(VALUE self, VALUE other) /* * call-seq: - * rat == object -> true or false + * self == other -> true or false * - * Returns +true+ if +rat+ equals +object+ numerically. + * Returns whether +self+ and +other+ are numerically equal: * * Rational(2, 3) == Rational(2, 3) #=> true * Rational(5) == 5 #=> true diff --git a/re.c b/re.c index f8672d1d7ce187..fe8e93c6a6b96c 100644 --- a/re.c +++ b/re.c @@ -3544,10 +3544,10 @@ reg_hash(VALUE re) /* * call-seq: - * regexp == object -> true or false + * self == other -> true or false * - * Returns +true+ if +object+ is another \Regexp whose pattern, - * flags, and encoding are the same as +self+, +false+ otherwise: + * Returns whether +other+ is another \Regexp whose pattern, + * flags, and encoding are the same as +self+: * * /foo/ == Regexp.new('foo') # => true * /foo/ == /foo/i # => false @@ -3599,11 +3599,11 @@ match_hash(VALUE match) /* * call-seq: - * matchdata == object -> true or false + * self == other -> true or false * - * Returns +true+ if +object+ is another \MatchData object + * Returns whether +other+ is another \MatchData object * whose target string, regexp, match, and captures - * are the same as +self+, +false+ otherwise. + * are the same as +self+. */ static VALUE diff --git a/string.c b/string.c index 1f2a14f681e6d2..c8233be66fd06a 100644 --- a/string.c +++ b/string.c @@ -4239,11 +4239,11 @@ rb_str_cmp(VALUE str1, VALUE str2) /* * call-seq: - * self == object -> true or false + * self == other -> true or false * - * Returns whether +object+ is equal to +self+. + * Returns whether +other+ is equal to +self+. * - * When +object+ is a string, returns whether +object+ has the same length and content as +self+: + * When +other+ is a string, returns whether +other+ has the same length and content as +self+: * * s = 'foo' * s == 'foo' # => true @@ -4254,11 +4254,11 @@ rb_str_cmp(VALUE str1, VALUE str2) * * "\u{e4 f6 fc}".encode(Encoding::ISO_8859_1) == ("\u{c4 d6 dc}") # => false * - * When +object+ is not a string: + * When +other+ is not a string: * - * - If +object+ responds to method to_str, - * object == self is called and its return value is returned. - * - If +object+ does not respond to to_str, + * - If +other+ responds to method to_str, + * other == self is called and its return value is returned. + * - If +other+ does not respond to to_str, * +false+ is returned. * * Related: {Comparing}[rdoc-ref:String@Comparing]. @@ -12218,9 +12218,9 @@ rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str) /* * call-seq: - * symbol == object -> true or false + * self == other -> true or false * - * Returns +true+ if +object+ is the same object as +self+, +false+ otherwise. + * Returns whether +other+ is the same object as +self+. */ #define sym_equal rb_obj_equal diff --git a/struct.c b/struct.c index a438ddd6136092..145d65f389d6d0 100644 --- a/struct.c +++ b/struct.c @@ -1404,7 +1404,7 @@ recursive_equal(VALUE s, VALUE s2, int recur) * call-seq: * self == other -> true or false * - * Returns +true+ if and only if the following are true; otherwise returns +false+: + * Returns whether both the following are true: * * - other.class == self.class. * - For each member name +name+, other.name == self.name. @@ -1918,7 +1918,7 @@ rb_data_inspect(VALUE s) * call-seq: * self == other -> true or false * - * Returns +true+ if +other+ is the same class as +self+, and all members are + * Returns whether +other+ is the same class as +self+, and all members are * equal. * * Examples: From 5c24f4081d0d163ed91dd20692d09d0c88ac46b1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 5 Jan 2026 20:25:52 -0500 Subject: [PATCH 2355/2435] Add potential missing GC guard in iseq_data_to_ary The iseq object might be optimized away by the compiler in iseq_data_to_ary because we mainly only use the iseq_body. With MMTk, this crash occasionally happens: TestIseqLoad#test_stressful_roundtrip [test/-ext-/iseq_load/test_iseq_load.rb:20]: pid 106677 killed by SIGSEGV (signal 11) (core dumped) | -:10: [BUG] Segmentation fault at 0x0000000000f1410c | ruby 4.1.0dev (2026-01-05T23:31:16Z master 5d26a2aeea) +PRISM +GC[mmtk] [x86_64-linux] | | -- Control frame information ----------------------------------------------- | c:0005 p:---- s:0022 e:000021 l:y b:---- CFUNC :to_a | c:0004 p:0039 s:0018 e:000017 l:y b:0001 METHOD -:10 | c:0003 p:0013 s:0010 e:000009 l:y b:0001 METHOD -:16 | c:0002 p:0054 s:0006 e:000005 l:n b:---- EVAL -:26 [FINISH] | c:0001 p:0000 s:0003 E:0003a0 l:y b:---- DUMMY [FINISH] | | -- Ruby level backtrace information ---------------------------------------- | -:26:in '
' | -:16:in 'test_bug8543' | -:10:in 'assert_iseq_roundtrip' | -:10:in 'to_a' | | -- Threading information --------------------------------------------------- | Total ractor count: 1 | Ruby thread count for this ractor: 1 | | -- Machine register context ------------------------------------------------ | RIP: 0x000055b581f866f5 RBP: 0x0000000000000000 RSP: 0x00007ffccce2ffe0 | RAX: 0x00000200ffee2b08 RBX: 0x0000000000f1410c RCX: 0x0000000000000000 | RDX: 0x000000000010c7f2 RDI: 0x00000200ffee2b08 RSI: 0x00000200ffee2b08 | R8: 0x0000000000000004 R9: 0x00000c0803ffb8ac R10: 0x00007fe9074c0cc8 | R11: 0x0000000000000246 R12: 0x0000000000000000 R13: 0x0000000000000001 | R14: 0x0000000000000001 R15: 0x00000200ffee2208 EFL: 0x0000000000010246 | | -- C level backtrace information ------------------------------------------- | ruby(rb_print_backtrace+0x14) [0x55b582119a9f] vm_dump.c:1105 | ruby(rb_vm_bugreport) vm_dump.c:1450 | ruby(rb_bug_for_fatal_signal+0x102) [0x55b582409072] error.c:1131 | ruby(sigsegv+0x46) [0x55b582051bf6] signal.c:948 | /lib/x86_64-linux-gnu/libc.so.6(0x7fe907645330) [0x7fe907645330] | ruby(iseq_data_to_ary+0xe5) [0x55b581f866f5] iseq.c:3380 | ruby(iseq_data_to_ary+0x6b2) [0x55b581f86cc2] iseq.c:3470 | ruby(vm_call_cfunc_with_frame_+0x10d) [0x55b5820e4a0d] vm_insnhelper.c:3902 --- iseq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iseq.c b/iseq.c index 2e13928e920ed0..97047794b1e904 100644 --- a/iseq.c +++ b/iseq.c @@ -3345,6 +3345,7 @@ iseq_type_id(enum rb_iseq_type type) static VALUE iseq_data_to_ary(const rb_iseq_t *iseq) { + VALUE iseq_value = (VALUE)iseq; unsigned int i; long l; const struct rb_iseq_constant_body *const iseq_body = ISEQ_BODY(iseq); @@ -3677,6 +3678,9 @@ iseq_data_to_ary(const rb_iseq_t *iseq) rb_ary_push(val, params); rb_ary_push(val, exception); rb_ary_push(val, body); + + RB_GC_GUARD(iseq_value); + return val; } From b2ed4cdced26f8840e181960c776e950e8bb0396 Mon Sep 17 00:00:00 2001 From: Philip Hallstrom Date: Tue, 6 Jan 2026 14:29:58 -0800 Subject: [PATCH 2356/2435] [ruby/time] Add changelog URI to time.gemspec https://github.com/ruby/time/commit/08f896ca0d --- lib/time.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/time.gemspec b/lib/time.gemspec index 4b9f9e1218237d..73650ab12e2567 100644 --- a/lib/time.gemspec +++ b/lib/time.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "https://github.com/ruby/time/releases" srcdir, gemspec = File.split(__FILE__) spec.files = Dir.chdir(srcdir) do From a024dca391695d07b24598677ddd6509ae3febfb Mon Sep 17 00:00:00 2001 From: yui-knk Date: Tue, 6 Jan 2026 12:08:48 +0900 Subject: [PATCH 2357/2435] Remove `in_masgn` field from `struct iseq_compile_data` `in_masgn` has not been used since fb6e3a80009a744a4e0b75660f1ce6da65e20e6c. --- compile.c | 3 --- iseq.h | 1 - 2 files changed, 4 deletions(-) diff --git a/compile.c b/compile.c index bcf22243cfc7af..b7d6b07090ba90 100644 --- a/compile.c +++ b/compile.c @@ -10926,10 +10926,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_MASGN:{ - bool prev_in_masgn = ISEQ_COMPILE_DATA(iseq)->in_masgn; - ISEQ_COMPILE_DATA(iseq)->in_masgn = true; compile_massign(iseq, ret, node, popped); - ISEQ_COMPILE_DATA(iseq)->in_masgn = prev_in_masgn; break; } diff --git a/iseq.h b/iseq.h index fbb8180a496662..5221c8aeb4fdfc 100644 --- a/iseq.h +++ b/iseq.h @@ -129,7 +129,6 @@ struct iseq_compile_data { struct iseq_compile_data_storage *storage_current; } insn; bool in_rescue; - bool in_masgn; int loopval_popped; /* used by NODE_BREAK */ int last_line; int label_no; From 08f6b8673adea23d2e96ca983f5d843c17b9a0e7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 5 Jan 2026 18:31:38 -0500 Subject: [PATCH 2358/2435] [ruby/mmtk] Set MMTK_DEBUG in C compiler for debug builds https://github.com/ruby/mmtk/commit/12a3904b04 --- gc/mmtk/extconf.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gc/mmtk/extconf.rb b/gc/mmtk/extconf.rb index 5f4228972bbbb8..c0e788037ecfe4 100644 --- a/gc/mmtk/extconf.rb +++ b/gc/mmtk/extconf.rb @@ -15,6 +15,10 @@ MMTK_BUILD = debug LIBMMTK_RUBY = libmmtk_ruby.#$LIBEXT RUSTSRCS = #{rustsrcs.join(" \\\n\t ")} + + ifeq ($(MMTK_BUILD), debug) + CPPFLAGS += -DMMTK_DEBUG + endif MAKEFILE ] end From 1abb609d667d4e07fb30192ef9da376bb288e230 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 5 Jan 2026 18:32:02 -0500 Subject: [PATCH 2359/2435] [ruby/mmtk] Check that a and b are valid objects in write barrier https://github.com/ruby/mmtk/commit/350625ebb3 --- gc/mmtk/mmtk.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 042517e7d741b6..38e730a3761e4d 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -963,6 +963,18 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (SPECIAL_CONST_P(b)) return; +#ifdef MMTK_DEBUG + if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)a)) { + char buff[256]; + rb_bug("a: %s is not an object", rb_raw_obj_info(buff, 256, a)); + } + + if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)b)) { + char buff[256]; + rb_bug("b: %s is not an object", rb_raw_obj_info(buff, 256, b)); + } +#endif + mmtk_object_reference_write_post(cache->mutator, (MMTk_ObjectReference)a); } From 25c72b0e8e206e5baec71d4ece7551b7da7da445 Mon Sep 17 00:00:00 2001 From: Jarek Prokop Date: Mon, 22 Dec 2025 10:13:34 +0100 Subject: [PATCH 2360/2435] Support customizable rustc_flags for rustc builds. Add `rustc_flags` option for configure that appends to RUSTC_FLAGS flags used when compiling with rustc for customizable build flags. It appends to existing defaults in RUSTC_FLAGS. Co-authored-by: Alan Wu --- common.mk | 10 ++-------- configure.ac | 8 ++++++++ defs/jit.mk | 2 ++ template/Makefile.in | 1 + 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/common.mk b/common.mk index e2fd5c4b279f7e..6871ad003fc33b 100644 --- a/common.mk +++ b/common.mk @@ -269,21 +269,15 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \ # For release builds YJIT_RUSTC_ARGS = --crate-name=yjit \ $(JIT_RUST_FLAGS) \ + $(RUSTC_FLAGS) \ --edition=2021 \ - -g \ - -C lto=thin \ - -C opt-level=3 \ - -C overflow-checks=on \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ '$(top_srcdir)/yjit/src/lib.rs' ZJIT_RUSTC_ARGS = --crate-name=zjit \ $(JIT_RUST_FLAGS) \ + $(RUSTC_FLAGS) \ --edition=2024 \ - -g \ - -C lto=thin \ - -C opt-level=3 \ - -C overflow-checks=on \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ '$(top_srcdir)/zjit/src/lib.rs' diff --git a/configure.ac b/configure.ac index 6787e9b3067572..a276783b6c2f63 100644 --- a/configure.ac +++ b/configure.ac @@ -69,6 +69,7 @@ dnl 93(bright yellow) is copied from .github/workflows/mingw.yml AC_ARG_VAR([cflags], [additional CFLAGS (ignored when CFLAGS is given)])dnl AC_ARG_VAR([cppflags], [additional CPPFLAGS (ignored when CPPFLAGS is given)])dnl AC_ARG_VAR([cxxflags], [additional CXXFLAGS (ignored when CXXFLAGS is given)])dnl +AC_ARG_VAR([rustc_flags], [additional RUSTC_FLAGS])dnl [begin]_group "environment section" && { HAVE_BASERUBY=yes @@ -4049,6 +4050,11 @@ AS_CASE(["${ZJIT_SUPPORT}"], AC_DEFINE(USE_ZJIT, 0) ]) +RUSTC_FLAGS='-g -C lto=thin -C opt-level=3 -C overflow-checks=on' +AS_IF([test -n "${rustc_flags}"], [ + RUSTC_FLAGS="${RUSTC_FLAGS} ${rustc_flags}" +]) + JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"' RLIB_DIR= AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], @@ -4106,6 +4112,7 @@ AS_IF([test -n "$RUST_LIB"], [ dnl These variables end up in ::RbConfig::CONFIG AC_SUBST(RUSTC)dnl Rust compiler command AC_SUBST(JIT_RUST_FLAGS)dnl the common rustc flags for JIT crates such as zjit +AC_SUBST(RUSTC_FLAGS)dnl user-configurable rustc compiler flags AC_SUBST(CARGO)dnl Cargo command for Rust builds AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes @@ -4838,6 +4845,7 @@ config_summary "strip command" "$STRIP" config_summary "install doc" "$DOCTARGETS" config_summary "YJIT support" "$YJIT_SUPPORT" config_summary "ZJIT support" "$ZJIT_SUPPORT" +config_summary "RUSTC_FLAGS" "$RUSTC_FLAGS" config_summary "man page type" "$MANTYPE" config_summary "search path" "$search_path" config_summary "static-linked-ext" ${EXTSTATIC:+"yes"} diff --git a/defs/jit.mk b/defs/jit.mk index 42b56c4cd928b6..27b14e7a07255d 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -40,6 +40,7 @@ else ifneq ($(strip $(RLIB_DIR)),) # combo build $(RUST_LIB): $(srcdir)/ruby.rs $(ECHO) 'building $(@F)' $(gnumake_recursive)$(Q) $(RUSTC) --edition=2024 \ + $(RUSTC_FLAGS) \ '-L$(@D)' \ --extern=yjit \ --extern=zjit \ @@ -58,6 +59,7 @@ $(JIT_RLIB): $(gnumake_recursive)$(Q) $(RUSTC) --crate-name=jit \ --edition=2024 \ $(JIT_RUST_FLAGS) \ + $(RUSTC_FLAGS) \ '--out-dir=$(@D)' \ '$(top_srcdir)/jit/src/lib.rs' endif # ifneq ($(JIT_CARGO_SUPPORT),no) diff --git a/template/Makefile.in b/template/Makefile.in index 443c394cb4f84d..0b7b50e3aa3340 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -115,6 +115,7 @@ CARGO_TARGET_DIR=@abs_top_builddir@/target CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@ ZJIT_TEST_FEATURES=@ZJIT_TEST_FEATURES@ JIT_RUST_FLAGS=@JIT_RUST_FLAGS@ +RUSTC_FLAGS=@RUSTC_FLAGS@ RLIB_DIR=@RLIB_DIR@ RUST_LIB=@RUST_LIB@ RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@) From e1087c1226f0a98c83842613066ba9ff48710887 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Fri, 19 Dec 2025 11:27:28 -0500 Subject: [PATCH 2361/2435] [ruby/rubygems] Fix dependency source bug in bundler I stumbled across a bundler bug that had me scratching my head for awhile, because I hadn't experienced it before. In some cases when changing the source in a gemfile from a `Source::Gemspec` to either a `Source::Path` or `Source::Git` only the parent gem will have it's gem replaced and updated and the child components will retain the original version. This only happens if the gem version of the `Source::Gemspec` and `Source::Git` are the same. It also requires another gem to share a dependency with the one being updated. For example if I have the following gemfile: ``` gem "rails", "~> 8.1.1" gem "propshaft" ``` Rails has a component called `actionpack` which `propshaft` depends on. If I change `rails` to point at a git source (or path source), only the path for `rails` gets updated: ``` gem "rails", github: "rails/rails", branch: "8-1-stable" gem "propshaft" ``` Because `actionpack` is a dependency of `propshaft`, it will remain in the rubygems source in the lock file WHILE the other gems are correctly pointing to the git source. Gemfile.lock: ``` GIT remote: https://github.com/rails/rails.git revision: https://github.com/ruby/rubygems/commit/9439f463e0ef branch: 8-1-stable specs: actioncable (8.1.1) ... actionmailbox (8.1.1) ... actionmailer (8.1.1) ... actiontext (8.1.1) ... activejob (8.1.1) ... activemodel (8.1.1) ... activerecord (8.1.1) ... activestorage (8.1.1) ... rails (8.1.1) ... railties (8.1.1) ... GEM remote: https://rubygems.org/ specs: action_text-trix (2.1.15) railties actionpack (8.1.1) <===== incorrectly left in Rubygems source ... ``` The gemfile will contain `actionpack` in the rubygems source, but will be missing in the git source so the path will be incorrect. A bundle show on Rails will point to the correct place: ``` $ bundle show rails /Users/eileencodes/.gem/ruby/3.4.4/bundler/gems/rails-9439f463e0ef ``` but a bundle show on actionpack will be incorrect: ``` $ bundle show actionpack /Users/eileencodes/.gem/ruby/3.4.4/gems/actionpack-8.1.1 ``` This bug requires the following to reproduce: 1) A gem like Rails that contains components that are released as their own standalone gem is added to the gemfile pointing to rubygems 2) A second gem is added that depends on one of the gems in the first gem (like propshaft does on actionpack) 3) The Rails gem is updated to use a git source, pointing to the same version that is being used by rubygems (ie 8.1.1) 4) `bundle` will only update the path for Rails component gems if no other gem depends on it. This incorrectly leaves Rails (or any gem like it) using two different codepaths / gem source code. https://github.com/ruby/rubygems/commit/dff76ba4f6 --- lib/bundler/definition.rb | 17 ++- spec/bundler/install/gemfile/sources_spec.rb | 116 +++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 2fa7d0d277943b..5ab577f504c39d 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1066,7 +1066,22 @@ def converge_specs(specs) deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep) else - replacement_source = sources.get(lockfile_source) + parent_dep = @dependencies.find do |d| + next unless d.source && d.source != lockfile_source + next if d.source.is_a?(Source::Gemspec) + + parent_locked_specs = @originally_locked_specs[d.name] + + parent_locked_specs.any? do |parent_spec| + parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name } + end + end + + if parent_dep + replacement_source = parent_dep.source + else + replacement_source = sources.get(lockfile_source) + end end # Replace the locked dependency's source with the equivalent source from the Gemfile diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index c0b4d98f1c5cc6..90f87ed0c5daea 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -1079,4 +1079,120 @@ expect(lockfile).to eq original_lockfile.gsub("bigdecimal (1.0.0)", "bigdecimal (3.3.1)") end end + + context "when switching a gem with components from rubygems to git source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_git "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_git "actionpack", "7.0.0", path: lib_path("rails") + build_git "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the git source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", git: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting on GIT first, then GEM + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end + + context "when switching a gem with components from rubygems to path source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_lib "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_lib "actionpack", "7.0.0", path: lib_path("rails") + build_lib "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the path source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", path: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting appropriately + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end end From 88467996862a15f64ee05e49c9df9b18296c66d6 Mon Sep 17 00:00:00 2001 From: neimadTL Date: Thu, 13 Feb 2025 09:32:33 -0400 Subject: [PATCH 2362/2435] [ruby/rubygems] Update custom errors with Exception#full_message The use of `Exception#full_message` makes more sense as it shows the cause and the backstrace. https://github.com/ruby/rubygems/commit/62a92c3f5e --- lib/bundler/errors.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 8cd1336356079e..d8df4d6ec5c553 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -131,7 +131,8 @@ class GemRequireError < BundlerError attr_reader :orig_exception def initialize(orig_exception, msg) - full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\ + full_message = msg + "\nGem Load Error is: + #{orig_exception.full_message(highlight: false)}\n"\ "Backtrace for gem load error is:\n"\ "#{orig_exception.backtrace.join("\n")}\n"\ "Bundler Error Backtrace:\n" @@ -221,7 +222,9 @@ def initialize(underlying_error, message) class DirectoryRemovalError < BundlerError def initialize(orig_exception, msg) full_message = "#{msg}.\n" \ - "The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \ + "The underlying error was #{orig_exception.class}: + #{orig_exception.full_message(highlight: false)}, + with backtrace:\n" \ " #{orig_exception.backtrace.join("\n ")}\n\n" \ "Bundler Error Backtrace:" super(full_message) From 3657700c4069ffd5d9e9b900e75f484f80eb8e3f Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Tue, 6 Sep 2022 07:37:50 +0000 Subject: [PATCH 2363/2435] [ruby/rubygems] Bundler: validate more options for add sub-command Signed-off-by: Takuya Noguchi https://github.com/ruby/rubygems/commit/6ca2e28680 --- lib/bundler/cli/add.rb | 10 +++++++++ spec/bundler/commands/add_spec.rb | 34 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb index 12a681a816886c..9f1760409617d2 100644 --- a/lib/bundler/cli/add.rb +++ b/lib/bundler/cli/add.rb @@ -36,6 +36,16 @@ def inject_dependencies end def validate_options! + raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"] + + unless options["git"] || options["github"] + raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"] + + raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"] + end + + raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"] + raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic] # raise error when no gems are specified diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 00aa6415e11c97..79f036d3cd3f72 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -236,6 +236,40 @@ end end + describe "with mismatched pair in --git/--github, --branch/--ref" do + describe "with --git and --github" do + it "throws error" do + bundle "add 'foo' --git x --github y", :raise_on_error => false + + expect(err).to include("You cannot specify `--git` and `--github` at the same time.") + end + end + + describe "with --branch and --ref with --git" do + it "throws error" do + bundle "add 'foo' --branch x --ref y --git file://git", :raise_on_error => false + + expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end + end + + describe "with --branch but without --git or --github" do + it "throws error" do + bundle "add 'foo' --branch x", :raise_on_error => false + + expect(err).to include("You cannot specify `--branch` unless `--git` or `--github` is specified.") + end + end + + describe "with --ref but without --git or --github" do + it "throws error" do + bundle "add 'foo' --ref y", :raise_on_error => false + + expect(err).to include("You cannot specify `--ref` unless `--git` or `--github` is specified.") + end + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" From 7d4983803887a45c311ab954de4527333b976500 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Jan 2026 17:00:14 +0900 Subject: [PATCH 2364/2435] [ruby/rubygems] bin/rubocop -A https://github.com/ruby/rubygems/commit/e3f418aa11 --- spec/bundler/commands/add_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 79f036d3cd3f72..ed98a914f326c4 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -239,7 +239,7 @@ describe "with mismatched pair in --git/--github, --branch/--ref" do describe "with --git and --github" do it "throws error" do - bundle "add 'foo' --git x --github y", :raise_on_error => false + bundle "add 'foo' --git x --github y", raise_on_error: false expect(err).to include("You cannot specify `--git` and `--github` at the same time.") end @@ -247,7 +247,7 @@ describe "with --branch and --ref with --git" do it "throws error" do - bundle "add 'foo' --branch x --ref y --git file://git", :raise_on_error => false + bundle "add 'foo' --branch x --ref y --git file://git", raise_on_error: false expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") end @@ -255,7 +255,7 @@ describe "with --branch but without --git or --github" do it "throws error" do - bundle "add 'foo' --branch x", :raise_on_error => false + bundle "add 'foo' --branch x", raise_on_error: false expect(err).to include("You cannot specify `--branch` unless `--git` or `--github` is specified.") end @@ -263,7 +263,7 @@ describe "with --ref but without --git or --github" do it "throws error" do - bundle "add 'foo' --ref y", :raise_on_error => false + bundle "add 'foo' --ref y", raise_on_error: false expect(err).to include("You cannot specify `--ref` unless `--git` or `--github` is specified.") end From 5230f835e807b7b6935b758de58ee26da6cb6a60 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 7 Jan 2026 17:01:56 -0600 Subject: [PATCH 2365/2435] [DOC] Harmonize #[] methods --- array.c | 34 +++++++++++++++------------------- doc/string/aref.rdoc | 24 ++++++++++++------------ re.c | 10 +++++----- string.c | 14 +++++++------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/array.c b/array.c index e13239ad3daaa1..b4718238763bab 100644 --- a/array.c +++ b/array.c @@ -1789,14 +1789,10 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); /* * call-seq: - * self[index] -> object or nil - * self[start, length] -> object or nil + * self[offset] -> object or nil + * self[offset, size] -> object or nil * self[range] -> object or nil * self[aseq] -> object or nil - * slice(index) -> object or nil - * slice(start, length) -> object or nil - * slice(range) -> object or nil - * slice(aseq) -> object or nil * * Returns elements from +self+; does not modify +self+. * @@ -1804,27 +1800,27 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * a = [:foo, 'bar', 2] * - * # Single argument index: returns one element. + * # Single argument offset: returns one element. * a[0] # => :foo # Zero-based index. * a[-1] # => 2 # Negative index counts backwards from end. * - * # Arguments start and length: returns an array. + * # Arguments offset and size: returns an array. * a[1, 2] # => ["bar", 2] - * a[-2, 2] # => ["bar", 2] # Negative start counts backwards from end. + * a[-2, 2] # => ["bar", 2] # Negative offset counts backwards from end. * * # Single argument range: returns an array. * a[0..1] # => [:foo, "bar"] * a[0..-2] # => [:foo, "bar"] # Negative range-begin counts backwards from end. * a[-2..2] # => ["bar", 2] # Negative range-end counts backwards from end. * - * When a single integer argument +index+ is given, returns the element at offset +index+: + * When a single integer argument +offset+ is given, returns the element at offset +offset+: * * a = [:foo, 'bar', 2] * a[0] # => :foo * a[2] # => 2 * a # => [:foo, "bar", 2] * - * If +index+ is negative, counts backwards from the end of +self+: + * If +offset+ is negative, counts backwards from the end of +self+: * * a = [:foo, 'bar', 2] * a[-1] # => 2 @@ -1832,29 +1828,29 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * If +index+ is out of range, returns +nil+. * - * When two Integer arguments +start+ and +length+ are given, - * returns a new array of size +length+ containing successive elements beginning at offset +start+: + * When two Integer arguments +offset+ and +size+ are given, + * returns a new array of size +size+ containing successive elements beginning at offset +offset+: * * a = [:foo, 'bar', 2] * a[0, 2] # => [:foo, "bar"] * a[1, 2] # => ["bar", 2] * - * If start + length is greater than self.length, - * returns all elements from offset +start+ to the end: + * If offset + size is greater than self.size, + * returns all elements from offset +offset+ to the end: * * a = [:foo, 'bar', 2] * a[0, 4] # => [:foo, "bar", 2] * a[1, 3] # => ["bar", 2] * a[2, 2] # => [2] * - * If start == self.size and length >= 0, + * If offset == self.size and size >= 0, * returns a new empty array. * - * If +length+ is negative, returns +nil+. + * If +size+ is negative, returns +nil+. * * When a single Range argument +range+ is given, - * treats range.min as +start+ above - * and range.size as +length+ above: + * treats range.min as +offset+ above + * and range.size as +size+ above: * * a = [:foo, 'bar', 2] * a[0..1] # => [:foo, "bar"] diff --git a/doc/string/aref.rdoc b/doc/string/aref.rdoc index 59c6ae97ace01e..a9ab8857bc1fc8 100644 --- a/doc/string/aref.rdoc +++ b/doc/string/aref.rdoc @@ -1,30 +1,30 @@ Returns the substring of +self+ specified by the arguments. -Form self[index] +Form self[offset] -With non-negative integer argument +index+ given, -returns the 1-character substring found in self at character offset index: +With non-negative integer argument +offset+ given, +returns the 1-character substring found in self at character offset +offset+: 'hello'[0] # => "h" 'hello'[4] # => "o" 'hello'[5] # => nil 'こんにちは'[4] # => "は" -With negative integer argument +index+ given, +With negative integer argument +offset+ given, counts backward from the end of +self+: 'hello'[-1] # => "o" 'hello'[-5] # => "h" 'hello'[-6] # => nil -Form self[start, length] +Form self[offset, size] -With integer arguments +start+ and +length+ given, -returns a substring of size +length+ characters (as available) -beginning at character offset specified by +start+. +With integer arguments +offset+ and +size+ given, +returns a substring of size +size+ characters (as available) +beginning at character offset specified by +offset+. -If argument +start+ is non-negative, -the offset is +start+: +If argument +offset+ is non-negative, +the offset is +offset+: 'hello'[0, 1] # => "h" 'hello'[0, 5] # => "hello" @@ -33,7 +33,7 @@ the offset is +start+: 'hello'[2, 0] # => "" 'hello'[2, -1] # => nil -If argument +start+ is negative, +If argument +offset+ is negative, counts backward from the end of +self+: 'hello'[-1, 1] # => "o" @@ -41,7 +41,7 @@ counts backward from the end of +self+: 'hello'[-1, 0] # => "" 'hello'[-6, 5] # => nil -Special case: if +start+ equals the length of +self+, +Special case: if +offset+ equals the size of +self+, returns a new empty string: 'hello'[5, 3] # => "" diff --git a/re.c b/re.c index fe8e93c6a6b96c..027fa597f4da5b 100644 --- a/re.c +++ b/re.c @@ -2213,12 +2213,12 @@ match_ary_aref(VALUE match, VALUE idx, VALUE result) /* * call-seq: - * matchdata[index] -> string or nil - * matchdata[start, length] -> array - * matchdata[range] -> array - * matchdata[name] -> string or nil + * self[offset] -> string or nil + * self[offset, size] -> array + * self[range] -> array + * self[name] -> string or nil * - * When arguments +index+, +start and +length+, or +range+ are given, + * When arguments +offset+, +offset+ and +size+, or +range+ are given, * returns match and captures in the style of Array#[]: * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") diff --git a/string.c b/string.c index c8233be66fd06a..234ef1edc8c64b 100644 --- a/string.c +++ b/string.c @@ -5713,8 +5713,8 @@ rb_str_aref(VALUE str, VALUE indx) /* * call-seq: - * self[index] -> new_string or nil - * self[start, length] -> new_string or nil + * self[offset] -> new_string or nil + * self[offset, size] -> new_string or nil * self[range] -> new_string or nil * self[regexp, capture = 0] -> new_string or nil * self[substring] -> new_string or nil @@ -12493,11 +12493,11 @@ sym_match_m_p(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * symbol[index] -> string or nil - * symbol[start, length] -> string or nil - * symbol[range] -> string or nil - * symbol[regexp, capture = 0] -> string or nil - * symbol[substring] -> string or nil + * self[offset] -> string or nil + * self[offset, size] -> string or nil + * self[range] -> string or nil + * self[regexp, capture = 0] -> string or nil + * self[substring] -> string or nil * * Equivalent to symbol.to_s[]; see String#[]. * From 3ea6ec8344a07e2d10ba248c69039dd4d27fd8fb Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 7 Jan 2026 17:02:22 -0600 Subject: [PATCH 2366/2435] [DOC] Harmonize #=~ methods (#15814) --- re.c | 7 +++---- string.c | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/re.c b/re.c index 027fa597f4da5b..b2c1909c153895 100644 --- a/re.c +++ b/re.c @@ -3663,12 +3663,11 @@ reg_match_pos(VALUE re, VALUE *strp, long pos, VALUE* set_match) /* * call-seq: - * regexp =~ string -> integer or nil + * self =~ other -> integer or nil * * Returns the integer index (in characters) of the first match - * for +self+ and +string+, or +nil+ if none; - * also sets the - * {rdoc-ref:Regexp global variables}[rdoc-ref:Regexp@Global+Variables]: + * for +self+ and +other+, or +nil+ if none; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]. * * /at/ =~ 'input data' # => 7 * $~ # => # diff --git a/string.c b/string.c index 234ef1edc8c64b..6f4ea03fb37a41 100644 --- a/string.c +++ b/string.c @@ -5011,12 +5011,15 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str) /* * call-seq: - * self =~ object -> integer or nil + * self =~ other -> integer or nil * - * When +object+ is a Regexp, returns the index of the first substring in +self+ - * matched by +object+, - * or +nil+ if no match is found; - * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: + * When +other+ is a Regexp: + * + * - Returns the integer index (in characters) of the first match + * for +self+ and +other+, or +nil+ if none; + * - Updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]. + * + * Examples: * * 'foo' =~ /f/ # => 0 * $~ # => # @@ -5034,8 +5037,8 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str) * /(?\d+)/ =~ 'no. 9' # => 4 * number # => "9" # Assigned. * - * If +object+ is not a Regexp, returns the value - * returned by object =~ self. + * When +other+ is not a Regexp, returns the value + * returned by other =~ self. * * Related: see {Querying}[rdoc-ref:String@Querying]. */ @@ -12445,9 +12448,9 @@ sym_casecmp_p(VALUE sym, VALUE other) /* * call-seq: - * symbol =~ object -> integer or nil + * self =~ other -> integer or nil * - * Equivalent to symbol.to_s =~ object, + * Equivalent to self.to_s =~ other, * including possible updates to global variables; * see String#=~. * From 950ffa90b7939885cc35d376a2e10aabfdc7170d Mon Sep 17 00:00:00 2001 From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:24:29 +0900 Subject: [PATCH 2367/2435] ZJIT: Add ArrayAset instruction to HIR (#15747) Inline `Array#[]=` into `ArrayAset`. --- test/ruby/test_zjit.rb | 109 ++++++++++++++++++++++++++++++++++++ zjit/src/codegen.rs | 35 ++++++++++++ zjit/src/cruby_methods.rs | 26 +++++++++ zjit/src/hir.rs | 33 +++++++++-- zjit/src/hir/opt_tests.rs | 115 +++++++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 2 + 6 files changed, 313 insertions(+), 7 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 805ecb98b20ff3..43db676d5d1cd3 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1646,6 +1646,115 @@ def test(x) = [1,2,3][x] }, call_threshold: 2, insns: [:opt_aref] end + def test_array_fixnum_aset + assert_compiles '[1, 2, 7]', %q{ + def test(arr, idx) + arr[idx] = 7 + end + arr = [1,2,3] + test(arr, 2) + arr = [1,2,3] + test(arr, 2) + arr + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_returns_value + assert_compiles '7', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + test([1,2,3], 2) + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_out_of_bounds + assert_compiles '[1, 2, 3, nil, nil, 7]', %q{ + def test(arr) + arr[5] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_negative_index + assert_compiles '[1, 2, 7]', %q{ + def test(arr) + arr[-1] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_shared + assert_compiles '[10, 999, -1, -2]', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = (0..50).to_a + test(arr, 0, -1) + test(arr, 1, -2) + shared = arr[10, 20] + test(shared, 0, 999) + [arr[10], shared[0], arr[0], arr[1]] + }, call_threshold: 2 + end + + def test_array_fixnum_aset_frozen + assert_compiles 'FrozenError', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = [1,2,3] + test(arr, 1, 9) + test(arr, 1, 9) + arr.freeze + begin + test(arr, 1, 9) + rescue => e + e.class + end + }, call_threshold: 2 + end + + def test_array_fixnum_aset_array_subclass + assert_compiles '7', %q{ + class MyArray < Array; end + def test(arr, idx) + arr[idx] = 7 + end + arr = MyArray.new + test(arr, 0) + arr = MyArray.new + test(arr, 0) + arr[0] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_aset_non_fixnum_index + assert_compiles 'TypeError', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 0) + test([1,2,3], 0) + begin + test([1,2,3], "0") + rescue => e + e.class + end + }, call_threshold: 2 + end + def test_empty_array_pop assert_compiles 'nil', %q{ def test(arr) = arr.pop diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b05c0110909e62..c728df7255c8ba 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -375,6 +375,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), + Insn::ArrayAset { array, index, val } => { + no_output!(gen_array_aset(asm, opnd!(array), opnd!(index), opnd!(val))) + } Insn::ArrayPop { array, state } => gen_array_pop(asm, opnd!(array), &function.frame_state(*state)), Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), @@ -449,6 +452,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)), + Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), @@ -692,6 +696,15 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: recv } +fn gen_guard_not_shared(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { + let recv = asm.load(recv); + // It's a heap object, so check the shared flag + let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS)); + asm.test(flags, (RUBY_ELTS_SHARED as u64).into()); + asm.jnz(side_exit(jit, state, SideExitReason::GuardNotShared)); + recv +} + fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { asm.cmp(left, right); asm.jge(side_exit(jit, state, SideExitReason::GuardLess)); @@ -1529,6 +1542,20 @@ fn gen_aref_fixnum( asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) } +fn gen_array_aset( + asm: &mut Assembler, + array: Opnd, + index: Opnd, + val: Opnd, +) { + let unboxed_idx = asm.load(index); + let array = asm.load(array); + let array_ptr = gen_array_ptr(asm, array); + let elem_offset = asm.lshift(unboxed_idx, Opnd::UImm(SIZEOF_VALUE.trailing_zeros() as u64)); + let elem_ptr = asm.add(array_ptr, elem_offset); + asm.store(Opnd::mem(VALUE_BITS, elem_ptr, 0), val); +} + fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); asm_ccall!(asm, rb_ary_pop, array) @@ -1545,6 +1572,14 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm.csel_nz(embedded_len, heap_len) } +fn gen_array_ptr(asm: &mut Assembler, array: Opnd) -> lir::Opnd { + let flags = Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RBASIC_FLAGS); + asm.test(flags, (RARRAY_EMBED_FLAG as u64).into()); + let heap_ptr = Opnd::mem(usize::BITS as u8, array, RUBY_OFFSET_RARRAY_AS_HEAP_PTR); + let embedded_ptr = asm.lea(Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RARRAY_AS_ARY)); + asm.csel_nz(embedded_ptr, heap_ptr) +} + /// Compile opt_newarray_hash - create a hash from array elements fn gen_opt_newarray_hash( jit: &JITState, diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 60060f149c850d..4aa9068cb17c16 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); + annotate!(rb_cArray, "[]=", inline_array_aset); annotate!(rb_cArray, "<<", inline_array_push); annotate!(rb_cArray, "push", inline_array_push); annotate!(rb_cArray, "pop", inline_array_pop); @@ -332,6 +333,31 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if let &[index, val] = args { + if fun.likely_a(recv, types::ArrayExact, state) + && fun.likely_a(index, types::Fixnum, state) + { + let recv = fun.coerce_to(block, recv, types::ArrayExact, state); + let index = fun.coerce_to(block, index, types::Fixnum, state); + let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); + let recv = fun.push_insn(block, hir::Insn::GuardNotShared { recv, state }); + + // Bounds check: unbox Fixnum index and guard 0 <= idx < length. + let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); + let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, state }); + let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, state }); + + let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val }); + fun.push_insn(block, hir::Insn::WriteBarrier { recv, val }); + return Some(val); + } + } + None +} + fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { // Inline only the case of `<<` or `push` when called with a single argument. if let &[val] = args { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3f1ed83e4bd9b3..2ea6e94c960b6a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -493,6 +493,7 @@ pub enum SideExitReason { GuardShape(ShapeId), GuardBitEquals(Const), GuardNotFrozen, + GuardNotShared, GuardLess, GuardGreaterEq, PatchPoint(Invariant), @@ -580,6 +581,7 @@ impl std::fmt::Display for SideExitReason { SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"), SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"), SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())), + SideExitReason::GuardNotShared => write!(f, "GuardNotShared"), SideExitReason::PatchPoint(invariant) => write!(f, "PatchPoint({invariant})"), _ => write!(f, "{self:?}"), } @@ -728,6 +730,7 @@ pub enum Insn { /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + ArrayAset { array: InsnId, index: InsnId, val: InsnId }, ArrayPop { array: InsnId, state: InsnId }, /// Return the length of the array as a C `long` ([`types::CInt64`]) ArrayLength { array: InsnId }, @@ -960,6 +963,9 @@ pub enum Insn { /// Side-exit if val is frozen. Does *not* check if the val is an immediate; assumes that it is /// a heap object. GuardNotFrozen { recv: InsnId, state: InsnId }, + /// Side-exit if val is shared. Does *not* check if the val is an immediate; assumes + /// that it is a heap object. + GuardNotShared { recv: InsnId, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). @@ -992,8 +998,9 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } - | Insn::HashAset { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } + | Insn::ArrayAset { .. } => false, _ => true, } } @@ -1121,6 +1128,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayArefFixnum { array, index, .. } => { write!(f, "ArrayArefFixnum {array}, {index}") } + Insn::ArrayAset { array, index, val, ..} => { + write!(f, "ArrayAset {array}, {index}, {val}") + } Insn::ArrayPop { array, .. } => { write!(f, "ArrayPop {array}") } @@ -1332,6 +1342,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::GuardNotFrozen { recv, .. } => write!(f, "GuardNotFrozen {recv}"), + Insn::GuardNotShared { recv, .. } => write!(f, "GuardNotShared {recv}"), Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, @@ -1968,6 +1979,7 @@ impl Function { &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, &GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state }, + &GuardNotShared { recv, state } => GuardNotShared { recv: find!(recv), state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, @@ -2071,6 +2083,7 @@ impl Function { &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, + &ArrayAset { array, index, val } => ArrayAset { array: find!(array), index: find!(index), val: find!(val) }, &ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) }, &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, @@ -2143,7 +2156,7 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } - | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } => + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } | Insn::ArrayAset { .. } => panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -2197,7 +2210,7 @@ impl Function { Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), - Insn::GuardNotFrozen { recv, .. } => self.type_of(*recv), + Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => self.type_of(*recv), Insn::GuardLess { left, .. } => self.type_of(*left), Insn::GuardGreaterEq { left, .. } => self.type_of(*left), Insn::FixnumAdd { .. } => types::Fixnum, @@ -3940,6 +3953,7 @@ impl Function { | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardShape { val, state, .. } | &Insn::GuardNotFrozen { recv: val, state } + | &Insn::GuardNotShared { recv: val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } @@ -4004,6 +4018,11 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::ArrayAset { array, index, val } => { + worklist.push_back(array); + worklist.push_back(index); + worklist.push_back(val); + } &Insn::ArrayPop { array, state } => { worklist.push_back(array); worklist.push_back(state); @@ -4628,7 +4647,7 @@ impl Function { | Insn::DefinedIvar { self_val: val, .. } => { self.assert_subtype(insn_id, val, types::BasicObject) } - Insn::GuardNotFrozen { recv, .. } => { + Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => { self.assert_subtype(insn_id, recv, types::HeapBasicObject) } // Instructions with 2 Ruby object operands @@ -4716,6 +4735,10 @@ impl Function { self.assert_subtype(insn_id, array, types::Array)?; self.assert_subtype(insn_id, index, types::Fixnum) } + Insn::ArrayAset { array, index, .. } => { + self.assert_subtype(insn_id, array, types::ArrayExact)?; + self.assert_subtype(insn_id, index, types::CInt64) + } // Instructions with Hash operands Insn::HashAref { hash, .. } | Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::HashExact), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2a70314fbb7f38..afa97e48f1e4dc 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4772,6 +4772,36 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_optimize_array_aset_if_redefined() { + eval(r##" + class Array + def []=(*args); :redefined; end + end + + def test(arr) + arr[1] = 10 + end + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:Fixnum[1] = Const Value(1) + v18:Fixnum[10] = Const Value(10) + v22:BasicObject = SendWithoutBlock v9, :[]=, v16, v18 # SendFallbackReason: Uncategorized(opt_aset) + CheckInterrupts + Return v18 + "); + } + #[test] fn test_dont_optimize_array_max_if_redefined() { eval(r##" @@ -6901,7 +6931,7 @@ mod hir_opt_tests { } #[test] - fn test_optimize_array_aset() { + fn test_optimize_array_aset_literal() { eval(" def test(arr) arr[1] = 10 @@ -6924,12 +6954,93 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallVariadic v31, :Array#[]=@0x1038, v16, v18 + v32:ArrayExact = GuardNotFrozen v31 + v33:ArrayExact = GuardNotShared v32 + v34:CInt64 = UnboxFixnum v16 + v35:CInt64 = ArrayLength v33 + v36:CInt64 = GuardLess v34, v35 + v37:CInt64[0] = Const CInt64(0) + v38:CInt64 = GuardGreaterEq v36, v37 + ArrayAset v33, v38, v18 + WriteBarrier v33, v18 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v18 "); } + #[test] + fn test_optimize_array_aset_profiled() { + eval(" + def test(arr, index, val) + arr[index] = val + end + test([], 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@6 + v3:BasicObject = GetLocal :index, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v35:ArrayExact = GuardType v13, ArrayExact + v36:Fixnum = GuardType v14, Fixnum + v37:ArrayExact = GuardNotFrozen v35 + v38:ArrayExact = GuardNotShared v37 + v39:CInt64 = UnboxFixnum v36 + v40:CInt64 = ArrayLength v38 + v41:CInt64 = GuardLess v39, v40 + v42:CInt64[0] = Const CInt64(0) + v43:CInt64 = GuardGreaterEq v41, v42 + ArrayAset v38, v43, v15 + WriteBarrier v38, v15 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_optimize_array_aset_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, index, val) + arr[index] = val + end + a = MyArray.new + test(a, 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@6 + v3:BasicObject = GetLocal :index, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(MyArray@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyArray@0x1000) + v35:ArraySubclass[class_exact:MyArray] = GuardType v13, ArraySubclass[class_exact:MyArray] + v36:BasicObject = CCallVariadic v35, :Array#[]=@0x1038, v14, v15 + CheckInterrupts + Return v15 + "); + } + #[test] fn test_optimize_array_ltlt() { eval(" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 38e8df170d33fd..68eeac456b38a2 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -191,6 +191,7 @@ make_counters! { exit_guard_int_equals_failure, exit_guard_shape_failure, exit_guard_not_frozen_failure, + exit_guard_not_shared_failure, exit_guard_less_failure, exit_guard_greater_eq_failure, exit_patchpoint_bop_redefined, @@ -511,6 +512,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, GuardNotFrozen => exit_guard_not_frozen_failure, + GuardNotShared => exit_guard_not_shared_failure, GuardLess => exit_guard_less_failure, GuardGreaterEq => exit_guard_greater_eq_failure, CalleeSideExit => exit_callee_side_exit, From 080d66beca71d6cc290a8be4acd49e5a70594f9c Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:41:42 +0900 Subject: [PATCH 2368/2435] Avoid flaky test failures by retrying on local port conflicts (#15818) This test obtains an available port number by calling `TCPServer.new`, then closes it and passes the same port number as `local_port` to `TCPSocket.new`. However, `TCPSocket.new` could occasionally fail with `Errno::EADDRINUSE` at the bind(2) step. I believe this happens when tests are run in parallel and another process on the same host happens to bind the same port in the short window between closing the `TCPServer` and calling `TCPSocket.new`. To address this race condition, the test now retries with a newly selected available port when such a conflict occurs. --- .../library/socket/tcpsocket/shared/new.rb | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/spec/ruby/library/socket/tcpsocket/shared/new.rb b/spec/ruby/library/socket/tcpsocket/shared/new.rb index 5280eb790080f8..0e405253c84324 100644 --- a/spec/ruby/library/socket/tcpsocket/shared/new.rb +++ b/spec/ruby/library/socket/tcpsocket/shared/new.rb @@ -53,14 +53,23 @@ end it "connects to a server when passed local_host and local_port arguments" do - server = TCPServer.new(SocketSpecs.hostname, 0) + retries = 0 + max_retries = 3 + begin - available_port = server.addr[1] - ensure - server.close + retries += 1 + server = TCPServer.new(SocketSpecs.hostname, 0) + begin + available_port = server.addr[1] + ensure + server.close + end + @socket = TCPSocket.send(@method, @hostname, @server.port, + @hostname, available_port) + rescue Errno::EADDRINUSE + raise if retries >= max_retries + retry end - @socket = TCPSocket.send(@method, @hostname, @server.port, - @hostname, available_port) @socket.should be_an_instance_of(TCPSocket) end From 768862868472fb1800e556effb0e37be2fbaec52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Hasi=C5=84ski?= Date: Thu, 8 Jan 2026 01:13:38 +0100 Subject: [PATCH 2369/2435] Fix incorrect bundled gems warning for hyphenated gem names When requiring a file like "benchmark/ips", the warning system would incorrectly warn about the "benchmark" gem not being a default gem, even when the user has "benchmark-ips" (a separate third-party gem) in their Gemfile. The fix checks if a hyphenated version of the require path exists in the bundle specs before issuing a warning. For example, requiring "benchmark/ips" now checks for both "benchmark" and "benchmark-ips" in the Gemfile. [Bug #21828] --- lib/bundled_gems.rb | 10 ++++++++++ test/test_bundled_gems.rb | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index 49fb90249dd6ae..3ac10aa25606d3 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -124,6 +124,16 @@ def self.warning?(name, specs: nil) return if specs.include?(name) + # Don't warn if a hyphenated gem provides this feature + # (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem) + if subfeature + feature_parts = feature.split("/") + if feature_parts.size >= 2 + hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}" + return if specs.include?(hyphenated_gem) + end + end + return if WARNED[name] WARNED[name] = true diff --git a/test/test_bundled_gems.rb b/test/test_bundled_gems.rb index 19546dd29606af..6e25df9b01f82d 100644 --- a/test/test_bundled_gems.rb +++ b/test/test_bundled_gems.rb @@ -32,4 +32,17 @@ def test_warning_archdir assert Gem::BUNDLED_GEMS.warning?(path, specs: {}) assert_nil Gem::BUNDLED_GEMS.warning?(path, specs: {}) end + + def test_no_warning_for_hyphenated_gem + # When benchmark-ips gem is in specs, requiring "benchmark/ips" should not warn + # about the benchmark gem (Bug #21828) + assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {"benchmark-ips" => true}) + end + + def test_warning_without_hyphenated_gem + # When benchmark-ips is NOT in specs, requiring "benchmark/ips" should warn + warning = Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {}) + assert warning + assert_match(/benchmark/, warning) + end end From 725e3d0aa7ffe47765a973904b72eac65763834b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Jan 2026 12:26:27 +0900 Subject: [PATCH 2370/2435] Fluent and/or is supported by Prism too now --- test/ruby/test_syntax.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 94a2e03940bf5b..585e691765c66a 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1260,8 +1260,6 @@ def test_fluent_dot end def test_fluent_and - omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION - assert_valid_syntax("a\n" "&& foo") assert_valid_syntax("a\n" "and foo") @@ -1285,8 +1283,6 @@ def test_fluent_and end def test_fluent_or - omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION - assert_valid_syntax("a\n" "|| foo") assert_valid_syntax("a\n" "or foo") From 1852ef43778d59a64881b293b0b887e2bc5af37a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 7 Jan 2026 09:39:55 +0900 Subject: [PATCH 2371/2435] Fail if the Ruby specified with `--with-baseruby` is too old If the baseruby is explicitly specified, fail because the option is not accepted if it does not meet the requirements. If the option is not specified, just display the warning and continue, in the hope that it is not needed. Follow up GH-15809 --- configure.ac | 12 ++++++++++-- win32/configure.bat | 2 +- win32/setup.mak | 7 +++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index a276783b6c2f63..90326e2926fc33 100644 --- a/configure.ac +++ b/configure.ac @@ -83,9 +83,17 @@ AC_ARG_WITH(baseruby, ], [ AC_PATH_PROG([BASERUBY], [ruby], [false]) + HAVE_BASERUBY= ]) -AS_IF([test "$HAVE_BASERUBY" != no], [ - RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" --verbose || HAVE_BASERUBY=no +AS_IF([test "$HAVE_BASERUBY" = no], [ + # --without-baseruby +], [error=`RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" --verbose 2>&1`], [ + HAVE_BASERUBY=yes +], [test "$HAVE_BASERUBY" = ""], [ # no --with-baseruby option + AC_MSG_WARN($error) # just warn and continue + HAVE_BASERUBY=no +], [ # the ruby given by --with-baseruby is too old + AC_MSG_ERROR($error) # bail out ]) AS_IF([test "${HAVE_BASERUBY:=no}" != no], [ AS_CASE(["$build_os"], [mingw*], [ diff --git a/win32/configure.bat b/win32/configure.bat index 8f767ede73256a..181813f4ad1581 100755 --- a/win32/configure.bat +++ b/win32/configure.bat @@ -208,7 +208,7 @@ goto :loop ; shift goto :loop ; :baseruby - echo>> %config_make% HAVE_BASERUBY = + echo>> %config_make% HAVE_BASERUBY = yes echo>> %config_make% BASERUBY = %~2 echo>>%confargs% %1=%2 \ shift diff --git a/win32/setup.mak b/win32/setup.mak index b06081cab99d98..de8db870ba69eb 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -24,6 +24,9 @@ MAKEFILE = Makefile CPU = PROCESSOR_LEVEL CC = $(CC) -nologo -source-charset:utf-8 CPP = $(CC) -EP +!if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" == "" +BASERUBY = ruby +!endif all: -prologue- -generic- -epilogue- i386-mswin32: -prologue- -i386- -epilogue- @@ -46,8 +49,8 @@ prefix = $(prefix:\=/) << @type $(config_make) >>$(MAKEFILE) @del $(config_make) > nul -!if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" != "" - $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" --verbose +!if "$(HAVE_BASERUBY)" != "no" + @$(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" --verbose $(HAVE_BASERUBY:yes=|| exit )|| exit 0 !endif !if "$(WITH_GMP)" != "no" @($(CC) $(XINCFLAGS) < nul && (echo USE_GMP = yes) || exit /b 0) >>$(MAKEFILE) From 946b1c1ba19e708d54a3f9eee00d4ea06434876c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Jan 2026 13:41:07 +0900 Subject: [PATCH 2372/2435] Move parentheses around macro arguments --- internal/error.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/error.h b/internal/error.h index 4b41aee77b00ec..ae9a13fceced27 100644 --- a/internal/error.h +++ b/internal/error.h @@ -75,8 +75,8 @@ PRINTF_ARGS(void rb_warn_deprecated_to_remove(const char *removal, const char *f PRINTF_ARGS(void rb_warn_reserved_name(const char *removal, const char *fmt, ...), 2, 3); #if RUBY_DEBUG # include "ruby/version.h" -# define RUBY_VERSION_SINCE(major, minor) (RUBY_API_VERSION_CODE >= (major * 10000) + (minor) * 100) -# define RUBY_VERSION_BEFORE(major, minor) (RUBY_API_VERSION_CODE < (major * 10000) + (minor) * 100) +# define RUBY_VERSION_SINCE(major, minor) (RUBY_API_VERSION_CODE >= (major) * 10000 + (minor) * 100) +# define RUBY_VERSION_BEFORE(major, minor) (RUBY_API_VERSION_CODE < (major) * 10000 + (minor) * 100) # if defined(RBIMPL_WARNING_PRAGMA0) # define RBIMPL_TODO0(x) RBIMPL_WARNING_PRAGMA0(message(x)) # elif RBIMPL_COMPILER_IS(MSVC) From 9580c7d07bb048386f75be994ea33d2870d2e177 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Jan 2026 15:43:28 +0900 Subject: [PATCH 2373/2435] Migrate tsort as bundled gems --- gems/bundled_gems | 1 + lib/tsort.gemspec | 35 ---- lib/tsort.rb | 457 --------------------------------------------- test/test_tsort.rb | 115 ------------ 4 files changed, 1 insertion(+), 607 deletions(-) delete mode 100644 lib/tsort.gemspec delete mode 100644 lib/tsort.rb delete mode 100644 test/test_tsort.rb diff --git a/gems/bundled_gems b/gems/bundled_gems index e709d57b793039..e1add92a167339 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -45,3 +45,4 @@ irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline readline 0.0.4 https://github.com/ruby/readline fiddle 1.1.8 https://github.com/ruby/fiddle +tsort 0.2.0 https://github.com/ruby/tsort diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec deleted file mode 100644 index 4e0ef0507df008..00000000000000 --- a/lib/tsort.gemspec +++ /dev/null @@ -1,35 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Tanaka Akira"] - spec.email = ["akr@fsij.org"] - - spec.summary = %q{Topological sorting using Tarjan's algorithm} - spec.description = %q{Topological sorting using Tarjan's algorithm} - spec.homepage = "https://github.com/ruby/tsort" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" - - dir, gemspec = File.split(__FILE__) - excludes = %W[ - :^/.git* :^/bin/ :^/test/ :^/spec/ :^/features/ :^/Gemfile :^/Rakefile - :^/#{gemspec} - ] - spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir) do |f| - f.read.split("\x0") - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] -end diff --git a/lib/tsort.rb b/lib/tsort.rb deleted file mode 100644 index 3c9635baa31c23..00000000000000 --- a/lib/tsort.rb +++ /dev/null @@ -1,457 +0,0 @@ -# frozen_string_literal: true - -#-- -# tsort.rb - provides a module for topological sorting and strongly connected components. -#++ -# - -# -# TSort implements topological sorting using Tarjan's algorithm for -# strongly connected components. -# -# TSort is designed to be able to be used with any object which can be -# interpreted as a directed graph. -# -# TSort requires two methods to interpret an object as a graph, -# tsort_each_node and tsort_each_child. -# -# * tsort_each_node is used to iterate for all nodes over a graph. -# * tsort_each_child is used to iterate for child nodes of a given node. -# -# The equality of nodes are defined by eql? and hash since -# TSort uses Hash internally. -# -# == A Simple Example -# -# The following example demonstrates how to mix the TSort module into an -# existing class (in this case, Hash). Here, we're treating each key in -# the hash as a node in the graph, and so we simply alias the required -# #tsort_each_node method to Hash's #each_key method. For each key in the -# hash, the associated value is an array of the node's child nodes. This -# choice in turn leads to our implementation of the required #tsort_each_child -# method, which fetches the array of child nodes and then iterates over that -# array using the user-supplied block. -# -# require 'tsort' -# -# class Hash -# include TSort -# alias tsort_each_node each_key -# def tsort_each_child(node, &block) -# fetch(node).each(&block) -# end -# end -# -# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort -# #=> [3, 2, 1, 4] -# -# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components -# #=> [[4], [2, 3], [1]] -# -# == A More Realistic Example -# -# A very simple `make' like tool can be implemented as follows: -# -# require 'tsort' -# -# class Make -# def initialize -# @dep = {} -# @dep.default = [] -# end -# -# def rule(outputs, inputs=[], &block) -# triple = [outputs, inputs, block] -# outputs.each {|f| @dep[f] = [triple]} -# @dep[triple] = inputs -# end -# -# def build(target) -# each_strongly_connected_component_from(target) {|ns| -# if ns.length != 1 -# fs = ns.delete_if {|n| Array === n} -# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") -# end -# n = ns.first -# if Array === n -# outputs, inputs, block = n -# inputs_time = inputs.map {|f| File.mtime f}.max -# begin -# outputs_time = outputs.map {|f| File.mtime f}.min -# rescue Errno::ENOENT -# outputs_time = nil -# end -# if outputs_time == nil || -# inputs_time != nil && outputs_time <= inputs_time -# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i -# block.call -# end -# end -# } -# end -# -# def tsort_each_child(node, &block) -# @dep[node].each(&block) -# end -# include TSort -# end -# -# def command(arg) -# print arg, "\n" -# system arg -# end -# -# m = Make.new -# m.rule(%w[t1]) { command 'date > t1' } -# m.rule(%w[t2]) { command 'date > t2' } -# m.rule(%w[t3]) { command 'date > t3' } -# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } -# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } -# m.build('t5') -# -# == Bugs -# -# * 'tsort.rb' is wrong name because this library uses -# Tarjan's algorithm for strongly connected components. -# Although 'strongly_connected_components.rb' is correct but too long. -# -# == References -# -# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", -# SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972. -# - -module TSort - - # The version string. - VERSION = "0.2.0" - - # Exception class to be raised when a cycle is found. - class Cyclic < StandardError - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.tsort #=> [4, 2, 3, 1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.tsort # raises TSort::Cyclic - # - def tsort - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort(each_node, each_child) - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic - # - def self.tsort(each_node, each_child) - tsort_each(each_node, each_child).to_a - end - - # The iterator version of the #tsort method. - # obj.tsort_each is similar to obj.tsort.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #tsort_each returns +nil+. - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.tsort_each {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def tsort_each(&block) # :yields: node - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort_each(each_node, each_child, &block) - end - - # The iterator version of the TSort.tsort method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.tsort_each(each_node, each_child) {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def self.tsort_each(each_node, each_child) # :yields: node - return to_enum(__method__, each_node, each_child) unless block_given? - - each_strongly_connected_component(each_node, each_child) {|component| - if component.size == 1 - yield component.first - else - raise Cyclic.new("topological sort failed: #{component.inspect}") - end - } - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] - # - def strongly_connected_components - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.strongly_connected_components(each_node, each_child) - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2], [3], [1]] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2, 3], [1]] - # - def self.strongly_connected_components(each_node, each_child) - each_strongly_connected_component(each_node, each_child).to_a - end - - # The iterator version of the #strongly_connected_components method. - # obj.each_strongly_connected_component is similar to - # obj.strongly_connected_components.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #each_strongly_connected_component returns +nil+. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def each_strongly_connected_component(&block) # :yields: nodes - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.each_strongly_connected_component(each_node, each_child, &block) - end - - # The iterator version of the TSort.strongly_connected_components method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes - return to_enum(__method__, each_node, each_child) unless block_given? - - id_map = {} - stack = [] - each_node.call {|node| - unless id_map.include? node - each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| - yield c - } - end - } - nil - end - - # Iterates over strongly connected component in the subgraph reachable from - # _node_. - # - # Return value is unspecified. - # - # #each_strongly_connected_component_from doesn't call #tsort_each_node. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2, 3] - # - def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes - TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) - end - - # Iterates over strongly connected components in a graph. - # The graph is represented by _node_ and _each_child_. - # - # _node_ is the first node. - # _each_child_ should have +call+ method which takes a node argument - # and yields for each child node. - # - # Return value is unspecified. - # - # #TSort.each_strongly_connected_component_from is a class method and - # it doesn't need a class to represent a graph which includes TSort. - # - # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_child = lambda {|n, &b| graph[n].each(&b) } - # TSort.each_strongly_connected_component_from(1, each_child) {|scc| - # p scc - # } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes - return to_enum(__method__, node, each_child, id_map, stack) unless block_given? - - minimum_id = node_id = id_map[node] = id_map.size - stack_length = stack.length - stack << node - - each_child.call(node) {|child| - if id_map.include? child - child_id = id_map[child] - minimum_id = child_id if child_id && child_id < minimum_id - else - sub_minimum_id = - each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| - yield c - } - minimum_id = sub_minimum_id if sub_minimum_id < minimum_id - end - } - - if node_id == minimum_id - component = stack.slice!(stack_length .. -1) - component.each {|n| id_map[n] = nil} - yield component - end - - minimum_id - end - - # Should be implemented by a extended class. - # - # #tsort_each_node is used to iterate for all nodes over a graph. - # - def tsort_each_node # :yields: node - raise NotImplementedError.new - end - - # Should be implemented by a extended class. - # - # #tsort_each_child is used to iterate for child nodes of _node_. - # - def tsort_each_child(node) # :yields: child - raise NotImplementedError.new - end -end diff --git a/test/test_tsort.rb b/test/test_tsort.rb deleted file mode 100644 index 354d9289081110..00000000000000 --- a/test/test_tsort.rb +++ /dev/null @@ -1,115 +0,0 @@ -# frozen_string_literal: true - -require 'tsort' -require 'test/unit' - -class TSortHash < Hash # :nodoc: - include TSort - alias tsort_each_node each_key - def tsort_each_child(node, &block) - fetch(node).each(&block) - end -end - -class TSortArray < Array # :nodoc: - include TSort - alias tsort_each_node each_index - def tsort_each_child(node, &block) - fetch(node).each(&block) - end -end - -class TSortTest < Test::Unit::TestCase # :nodoc: - def test_dag - h = TSortHash[{1=>[2, 3], 2=>[3], 3=>[]}] - assert_equal([3, 2, 1], h.tsort) - assert_equal([[3], [2], [1]], h.strongly_connected_components) - end - - def test_cycle - h = TSortHash[{1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}] - assert_equal([[4], [2, 3], [1]], - h.strongly_connected_components.map {|nodes| nodes.sort}) - assert_raise(TSort::Cyclic) { h.tsort } - end - - def test_array - a = TSortArray[[1], [0], [0], [2]] - assert_equal([[0, 1], [2], [3]], - a.strongly_connected_components.map {|nodes| nodes.sort}) - - a = TSortArray[[], [0]] - assert_equal([[0], [1]], - a.strongly_connected_components.map {|nodes| nodes.sort}) - end - - def test_s_tsort - g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - each_node = lambda {|&b| g.each_key(&b) } - each_child = lambda {|n, &b| g[n].each(&b) } - assert_equal([4, 2, 3, 1], TSort.tsort(each_node, each_child)) - g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - assert_raise(TSort::Cyclic) { TSort.tsort(each_node, each_child) } - end - - def test_s_tsort_each - g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - each_node = lambda {|&b| g.each_key(&b) } - each_child = lambda {|n, &b| g[n].each(&b) } - r = [] - TSort.tsort_each(each_node, each_child) {|n| r << n } - assert_equal([4, 2, 3, 1], r) - - r = TSort.tsort_each(each_node, each_child).map {|n| n.to_s } - assert_equal(['4', '2', '3', '1'], r) - end - - def test_s_strongly_connected_components - g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - each_node = lambda {|&b| g.each_key(&b) } - each_child = lambda {|n, &b| g[n].each(&b) } - assert_equal([[4], [2], [3], [1]], - TSort.strongly_connected_components(each_node, each_child)) - g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - assert_equal([[4], [2, 3], [1]], - TSort.strongly_connected_components(each_node, each_child)) - end - - def test_s_each_strongly_connected_component - g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - each_node = lambda {|&b| g.each_key(&b) } - each_child = lambda {|n, &b| g[n].each(&b) } - r = [] - TSort.each_strongly_connected_component(each_node, each_child) {|scc| - r << scc - } - assert_equal([[4], [2], [3], [1]], r) - g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - r = [] - TSort.each_strongly_connected_component(each_node, each_child) {|scc| - r << scc - } - assert_equal([[4], [2, 3], [1]], r) - - r = TSort.each_strongly_connected_component(each_node, each_child).map {|scc| - scc.map(&:to_s) - } - assert_equal([['4'], ['2', '3'], ['1']], r) - end - - def test_s_each_strongly_connected_component_from - g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - each_child = lambda {|n, &b| g[n].each(&b) } - r = [] - TSort.each_strongly_connected_component_from(1, each_child) {|scc| - r << scc - } - assert_equal([[4], [2, 3], [1]], r) - - r = TSort.each_strongly_connected_component_from(1, each_child).map {|scc| - scc.map(&:to_s) - } - assert_equal([['4'], ['2', '3'], ['1']], r) - end -end - From 9c49084a203a4b8a3abca653dee6781b9e5ed5fb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Jan 2026 15:45:41 +0900 Subject: [PATCH 2374/2435] Update tsort entries under the doc --- doc/maintainers.md | 12 ++++++------ doc/standard_library.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index 244912cb20c9ca..46840343cabb80 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -269,12 +269,6 @@ consensus on ruby-core/ruby-dev. * https://github.com/ruby/tmpdir * https://rubygems.org/gems/tmpdir -#### lib/tsort.rb - -* Tanaka Akira ([akr]) -* https://github.com/ruby/tsort -* https://rubygems.org/gems/tsort - #### lib/un.rb * WATANABE Hirofumi ([eban]) @@ -628,6 +622,12 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes * https://github.com/ruby/repl_type_completor * https://rubygems.org/gems/repl_type_completor +#### tsort + +* Tanaka Akira ([akr]) +* https://github.com/ruby/tsort +* https://rubygems.org/gems/tsort + ## Platform Maintainers ### mswin64 (Microsoft Windows) diff --git a/doc/standard_library.md b/doc/standard_library.md index 0c48ac0cdd7e0a..7a477283a9d7bc 100644 --- a/doc/standard_library.md +++ b/doc/standard_library.md @@ -58,7 +58,6 @@ of each. - Time ([GitHub][time]): Extends the Time class with methods for parsing and conversion - Timeout ([GitHub][timeout]): Auto-terminate potentially long-running operations in Ruby - TmpDir ([GitHub][tmpdir]): Extends the Dir class to manage the OS temporary file path -- TSort ([GitHub][tsort]): Topological sorting using Tarjan's algorithm - UN ([GitHub][un]): Utilities to replace common UNIX commands - URI ([GitHub][uri]): A Ruby module providing support for Uniform Resource Identifiers - YAML ([GitHub][yaml]): The Ruby client library for the Psych YAML implementation @@ -126,6 +125,7 @@ of each. - [reline][reline-doc] ([GitHub][reline]): GNU Readline and Editline in a pure Ruby implementation - [readline]: Wrapper for the Readline extension and Reline - [fiddle]: A libffi wrapper for Ruby +- [tsort]: Topological sorting using Tarjan's algorithm ## Tools From 4a988b7f480d30fbc06e3e3399611172072bf58e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Jan 2026 16:35:34 +0900 Subject: [PATCH 2375/2435] Inject tsort path before rdoc --- tool/rdoc-srcdir | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tool/rdoc-srcdir b/tool/rdoc-srcdir index 275f12fe421cd3..ecc49b4b2cbb1d 100755 --- a/tool/rdoc-srcdir +++ b/tool/rdoc-srcdir @@ -1,7 +1,9 @@ #!ruby -W0 -rdoc_path = Dir.glob("#{File.dirname(__dir__)}/.bundle/gems/rdoc-*").first -$LOAD_PATH.unshift("#{rdoc_path}/lib") +%w[tsort rdoc].each do |lib| + path = Dir.glob("#{File.dirname(__dir__)}/.bundle/gems/#{lib}-*").first + $LOAD_PATH.unshift("#{path}/lib") +end require 'rdoc/rdoc' # Make only the output directory relative to the invoked directory. From eaa9902ca7d5752fddd2e6d31b6880d9f82de536 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Jan 2026 17:01:54 +0900 Subject: [PATCH 2376/2435] Exclude ruby-lsp benchmark because released version of rbs didn't have tsort dependency --- .github/workflows/ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 88c19b6fe60ee3..7405ca997b8f56 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -208,7 +208,7 @@ jobs: matrix: include: # Using the same setup as ZJIT jobs - - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' runs-on: ubuntu-24.04 diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index a638907811c3f1..7f0f6ebd4d0e8d 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 28bfec963e57f5..ba849d9bdf4e3d 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 From 19d3f2da668ad0bdb1c6077cf1d9a86432ea8889 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Jan 2026 18:06:45 +0900 Subject: [PATCH 2377/2435] Skip collection install test --- tool/rbs_skip_tests | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index e03eecbfedcc4d..6d9ecb40992e26 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -48,3 +48,7 @@ test_new(RegexpSingletonTest) test_source_location(MethodInstanceTest) test_source_location(ProcInstanceTest) test_source_location(UnboundMethodInstanceTest) + +# Errno::ENOENT: No such file or directory - bundle +test_collection_install__pathname_set(RBS::CliTest) +test_collection_install__set_pathname__manifest(RBS::CliTest) From dcfbbdc38c2c0502f2eeb9172d1a82721b5ca45b Mon Sep 17 00:00:00 2001 From: git Date: Thu, 8 Jan 2026 09:48:38 +0000 Subject: [PATCH 2378/2435] Update bundled gems list as of 2026-01-08 --- NEWS.md | 8 +++++++- gems/bundled_gems | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 02fef64f4cb5e5..7ec521481d4f54 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,13 +27,18 @@ Note: We're only listing outstanding class updates. ## Stdlib updates +### The following bundled gems are added. + + We only list stdlib changes that are notable feature changes. Other changes are listed in the following sections. We also listed release history from the previous bundled version that is Ruby 3.4.0 if it has GitHub releases. -### The following bundled gems are promoted from default gems. +### The following bundled gem is promoted from default gems. + +* tsort 0.2.0 ### The following default gem is added. @@ -50,6 +55,7 @@ releases. * test-unit 3.7.7 * rss 0.3.2 * net-imap 0.6.2 +* rbs 3.10.2 * typeprof 0.31.1 * debug 1.11.1 * mutex_m 0.3.0 diff --git a/gems/bundled_gems b/gems/bundled_gems index e1add92a167339..95dfa5b6ca5b11 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -18,7 +18,7 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.10.0 https://github.com/ruby/rbs +rbs 3.10.2 https://github.com/ruby/rbs typeprof 0.31.1 https://github.com/ruby/typeprof debug 1.11.1 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc From 3bfc86558b7a314417399470b5204a914f2ca3ff Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:53:42 +0100 Subject: [PATCH 2379/2435] [ruby/prism] Move `LexRipper` into its own file It has a hard dependency on ripper that can't be removed. This makes it so that ripper can be loaded only when the class is actually used. https://github.com/ruby/prism/commit/3b5b4a8a6d --- lib/prism.rb | 2 +- lib/prism/lex_compat.rb | 58 ------------------------------------- lib/prism/lex_ripper.rb | 64 +++++++++++++++++++++++++++++++++++++++++ lib/prism/prism.gemspec | 1 + 4 files changed, 66 insertions(+), 59 deletions(-) create mode 100644 lib/prism/lex_ripper.rb diff --git a/lib/prism.rb b/lib/prism.rb index f6ad0c1fd10155..d809557fce101f 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -20,7 +20,7 @@ module Prism autoload :DSL, "prism/dsl" autoload :InspectVisitor, "prism/inspect_visitor" autoload :LexCompat, "prism/lex_compat" - autoload :LexRipper, "prism/lex_compat" + autoload :LexRipper, "prism/lex_ripper" autoload :MutationCompiler, "prism/mutation_compiler" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 9b3f025ab6a5b4..48ac768b03df2f 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -867,62 +867,4 @@ def result end private_constant :LexCompat - - # This is a class that wraps the Ripper lexer to produce almost exactly the - # same tokens. - class LexRipper # :nodoc: - attr_reader :source - - def initialize(source) - @source = source - end - - def result - previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] - results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] - - lex(source).each do |token| - case token[1] - when :on_sp - # skip - when :on_tstring_content - if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) - previous[2] << token[2] - else - results << token - previous = token - end - when :on_words_sep - if previous[1] == :on_words_sep - previous[2] << token[2] - else - results << token - previous = token - end - else - results << token - previous = token - end - end - - results - end - - private - - if Ripper.method(:lex).parameters.assoc(:keyrest) - def lex(source) - Ripper.lex(source, raise_errors: true) - end - else - def lex(source) - ripper = Ripper::Lexer.new(source) - ripper.lex.tap do |result| - raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? - end - end - end - end - - private_constant :LexRipper end diff --git a/lib/prism/lex_ripper.rb b/lib/prism/lex_ripper.rb new file mode 100644 index 00000000000000..4b5c3b77fd6112 --- /dev/null +++ b/lib/prism/lex_ripper.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +# :markup: markdown + +require "ripper" + +module Prism + # This is a class that wraps the Ripper lexer to produce almost exactly the + # same tokens. + class LexRipper # :nodoc: + attr_reader :source + + def initialize(source) + @source = source + end + + def result + previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] + results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] + + lex(source).each do |token| + case token[1] + when :on_sp + # skip + when :on_tstring_content + if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) + previous[2] << token[2] + else + results << token + previous = token + end + when :on_words_sep + if previous[1] == :on_words_sep + previous[2] << token[2] + else + results << token + previous = token + end + else + results << token + previous = token + end + end + + results + end + + private + + if Ripper.method(:lex).parameters.assoc(:keyrest) + def lex(source) + Ripper.lex(source, raise_errors: true) + end + else + def lex(source) + ripper = Ripper::Lexer.new(source) + ripper.lex.tap do |result| + raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + end + end + end + end + + private_constant :LexRipper +end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 2fb5d1d0b308e4..a45e0d93e78cd5 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -77,6 +77,7 @@ Gem::Specification.new do |spec| "lib/prism/ffi.rb", "lib/prism/inspect_visitor.rb", "lib/prism/lex_compat.rb", + "lib/prism/lex_ripper.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node_ext.rb", "lib/prism/node.rb", From fc66de3e6b5e28c017c3cffac77a66d680d679a4 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:12:15 +0100 Subject: [PATCH 2380/2435] [ruby/prism] Remove unneeded `ripper` requires Ripper is either not used or loaded where it is actually needed https://github.com/ruby/prism/commit/a73a4fb00c --- lib/prism/translation/ripper.rb | 2 -- test/prism/magic_comment_test.rb | 1 + test/prism/ruby/ripper_test.rb | 1 + test/prism/test_helper.rb | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index e488b7c5cf0c72..00d5f80af4b2f8 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true # :markup: markdown -require "ripper" - module Prism module Translation # This class provides a compatibility layer between prism and Ripper. It diff --git a/test/prism/magic_comment_test.rb b/test/prism/magic_comment_test.rb index ab4b5f56e5169b..ccfe5a5d0a5bc2 100644 --- a/test/prism/magic_comment_test.rb +++ b/test/prism/magic_comment_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "test_helper" +require "ripper" module Prism class MagicCommentTest < TestCase diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index bd63302efcf908..defa95b6a84f8f 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -3,6 +3,7 @@ return if RUBY_VERSION < "3.3" || RUBY_ENGINE != "ruby" require_relative "../test_helper" +require "ripper" module Prism class RipperTest < TestCase diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index 43771110b4284f..406582c0a5b1ec 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -2,7 +2,6 @@ require "prism" require "pp" -require "ripper" require "stringio" require "test/unit" require "tempfile" From 16863f2ec1c8cefd852965e58acfcfd61b0194b9 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:47:35 +0100 Subject: [PATCH 2381/2435] [ruby/prism] Decouple ripper translator from ripper library Ripper exposes Ripper::Lexer:State in its output, which is a bit of a problem. To make this work, I basically copy-pasted the implementation. I'm unsure if that is acceptable and added a test to make sure that these values never go out of sync. I don't imagine them changing often, prism maps them 1:1 for its own usage. This also fixed the shim by accident. `Ripper.lex` went to `Translation::Ripper.lex` when it should have been the original. Removing the need for the original resolves that issue. https://github.com/ruby/prism/commit/2c0bea076d --- lib/prism/lex_compat.rb | 86 +++++++++++++++++++++++++++------- test/prism/ruby/ripper_test.rb | 12 +++++ 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 48ac768b03df2f..ebfb19e56d999d 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -2,7 +2,6 @@ # :markup: markdown require "delegate" -require "ripper" module Prism # This class is responsible for lexing the source using prism and then @@ -199,6 +198,58 @@ def deconstruct_keys(keys) "__END__": :on___end__ }.freeze + # Pretty much a 1:1 copy of Ripper::Lexer::State. We list all the available states + # to reimplement to_s without using Ripper. + class State + # Ripper-internal bitflags. + ALL = %i[ + BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM + ].map.with_index.to_h { |name, i| [2 ** i, name] } + ALL[0] = :NONE + ALL.freeze + ALL.each { |value, name| const_set(name, value) } + + # :stopdoc: + + attr_reader :to_int, :to_s + + def initialize(i) + @to_int = i + @to_s = state_name(i) + freeze + end + + def [](index) + case index + when 0, :to_int + @to_int + when 1, :to_s + @to_s + else + nil + end + end + + alias to_i to_int + alias inspect to_s + def pretty_print(q) q.text(to_s) end + def ==(i) super or to_int == i end + def &(i) self.class.new(to_int & i) end + def |(i) self.class.new(to_int | i) end + def allbits?(i) to_int.allbits?(i) end + def anybits?(i) to_int.anybits?(i) end + def nobits?(i) to_int.nobits?(i) end + + # :startdoc: + + private + + # Convert the state flags into the format exposed by ripper. + def state_name(bits) + ALL.filter_map { |flag, name| name if bits & flag != 0 }.join("|") + end + end + # When we produce tokens, we produce the same arrays that Ripper does. # However, we add a couple of convenience methods onto them to make them a # little easier to work with. We delegate all other methods to the array. @@ -249,8 +300,8 @@ def ==(other) # :nodoc: class IdentToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) || - (other[3] & Ripper::EXPR_ARG_ANY != 0) + (other[3] == State::LABEL | State::END) || + (other[3] & (State::ARG | State::CMDARG) != 0) ) end end @@ -261,8 +312,8 @@ class IgnoredNewlineToken < Token def ==(other) # :nodoc: return false unless self[0...-1] == other[0...-1] - if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED - other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0 + if self[3] == State::ARG | State::LABELED + other[3] & State::ARG | State::LABELED != 0 else self[3] == other[3] end @@ -280,8 +331,8 @@ def ==(other) # :nodoc: class ParamToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_END) || - (other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL) + (other[3] == State::END) || + (other[3] == State::END | State::LABEL) ) end end @@ -615,6 +666,11 @@ def self.build(opening) private_constant :Heredoc + # In previous versions of Ruby, Ripper wouldn't flush the bom before the + # first token, so we had to have a hack in place to account for that. + BOM_FLUSHED = RUBY_VERSION >= "3.3.0" + private_constant :BOM_FLUSHED + attr_reader :source, :options def initialize(source, **options) @@ -630,13 +686,9 @@ def result result = Prism.lex(source, **options) result_value = result.value - previous_state = nil #: Ripper::Lexer::State? + previous_state = nil #: State? last_heredoc_end = nil #: Integer? - # In previous versions of Ruby, Ripper wouldn't flush the bom before the - # first token, so we had to have a hack in place to account for that. This - # checks for that behavior. - bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0 bom = source.byteslice(0..2) == "\xEF\xBB\xBF" result_value.each_with_index do |(token, lex_state), index| @@ -651,7 +703,7 @@ def result if bom && lineno == 1 column -= 3 - if index == 0 && column == 0 && !bom_flushed + if index == 0 && column == 0 && !BOM_FLUSHED flushed = case token.type when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE, @@ -675,7 +727,7 @@ def result event = RIPPER.fetch(token.type) value = token.value - lex_state = Ripper::Lexer::State.new(lex_state) + lex_state = State.new(lex_state) token = case event @@ -689,7 +741,7 @@ def result last_heredoc_end = token.location.end_offset IgnoreStateToken.new([[lineno, column], event, value, lex_state]) when :on_ident - if lex_state == Ripper::EXPR_END + if lex_state == State::END # If we have an identifier that follows a method name like: # # def foo bar @@ -699,7 +751,7 @@ def result # yet. We do this more accurately, so we need to allow comparing # against both END and END|LABEL. ParamToken.new([[lineno, column], event, value, lex_state]) - elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL + elsif lex_state == State::END | State::LABEL # In the event that we're comparing identifiers, we're going to # allow a little divergence. Ripper doesn't account for local # variables introduced through named captures in regexes, and we @@ -739,7 +791,7 @@ def result counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0 end - Ripper::Lexer::State.new(result_value[current_index][1]) + State.new(result_value[current_index][1]) else previous_state end diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index defa95b6a84f8f..9d64c5c70ce5a5 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -63,6 +63,18 @@ class RipperTest < TestCase define_method(fixture.test_name) { assert_ripper(fixture.read) } end + # Check that the hardcoded values don't change without us noticing. + def test_internals + actual = LexCompat::State::ALL + expected = Ripper.constants.select { |name| name.start_with?("EXPR_") } + expected -= %i[EXPR_VALUE EXPR_BEG_ANY EXPR_ARG_ANY EXPR_END_ANY] + + assert_equal(expected.size, actual.size) + expected.each do |const_name| + assert_equal(const_name.to_s.delete_prefix("EXPR_").to_sym, actual[Ripper.const_get(const_name)]) + end + end + private def assert_ripper(source) From 523857bfcb0f0cdfd1ed7faa09b9c59a0266e7e2 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 8 Jan 2026 14:57:55 -0500 Subject: [PATCH 2382/2435] ZJIT: Replace GuardShape with LoadField+GuardBitEquals (#15821) GuardShape is just load+guard, so use the existing HIR instructions for load+guard. Probably makes future analysis slightly easier. --- zjit/src/codegen.rs | 13 ++- zjit/src/hir.rs | 36 ++++-- zjit/src/hir/opt_tests.rs | 237 +++++++++++++++++++++----------------- zjit/src/stats.rs | 3 +- 4 files changed, 170 insertions(+), 119 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index c728df7255c8ba..8d832c2e2569b3 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -449,7 +449,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), - Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), + &Insn::GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, &function.frame_state(state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)), Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)), @@ -498,7 +498,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadPC => gen_load_pc(asm), Insn::LoadEC => gen_load_ec(), Insn::LoadSelf => gen_load_self(), - &Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset), + &Insn::LoadField { recv, id, offset, return_type } => gen_load_field(asm, opnd!(recv), id, offset, return_type), &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))), &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven => gen_is_block_given(jit, asm), @@ -1130,10 +1130,10 @@ fn gen_load_self() -> Opnd { Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF) } -fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32) -> Opnd { +fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, return_type: Type) -> Opnd { asm_comment!(asm, "Load field id={} offset={}", id.contents_lossy(), offset); let recv = asm.load(recv); - asm.load(Opnd::mem(64, recv, offset)) + asm.load(Opnd::mem(return_type.num_bits(), recv, offset)) } fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) { @@ -2083,14 +2083,15 @@ fn gen_guard_type_not(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, g } /// Compile an identity check with a side exit -fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, expected: crate::hir::Const, state: &FrameState) -> lir::Opnd { +fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, expected: crate::hir::Const, reason: SideExitReason, state: &FrameState) -> lir::Opnd { let expected_opnd: Opnd = match expected { crate::hir::Const::Value(v) => { Opnd::Value(v) } crate::hir::Const::CInt64(v) => { v.into() } + crate::hir::Const::CShape(v) => { Opnd::UImm(v.0 as u64) } _ => panic!("gen_guard_bit_equals: unexpected hir::Const {expected:?}"), }; asm.cmp(val, expected_opnd); - asm.jnz(side_exit(jit, state, GuardBitEquals(expected))); + asm.jnz(side_exit(jit, state, reason)); val } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2ea6e94c960b6a..c6a104fbd176b0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -491,7 +491,7 @@ pub enum SideExitReason { GuardType(Type), GuardTypeNot(Type), GuardShape(ShapeId), - GuardBitEquals(Const), + ExpandArray, GuardNotFrozen, GuardNotShared, GuardLess, @@ -580,7 +580,6 @@ impl std::fmt::Display for SideExitReason { SideExitReason::UnhandledDuparraySend(method_id) => write!(f, "UnhandledDuparraySend({method_id})"), SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"), SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"), - SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())), SideExitReason::GuardNotShared => write!(f, "GuardNotShared"), SideExitReason::PatchPoint(invariant) => write!(f, "PatchPoint({invariant})"), _ => write!(f, "{self:?}"), @@ -954,7 +953,7 @@ pub enum Insn { GuardType { val: InsnId, guard_type: Type, state: InsnId }, GuardTypeNot { val: InsnId, guard_type: Type, state: InsnId }, /// Side-exit if val is not the expected Const. - GuardBitEquals { val: InsnId, expected: Const, state: InsnId }, + GuardBitEquals { val: InsnId, expected: Const, reason: SideExitReason, state: InsnId }, /// Side-exit if val doesn't have the expected shape. GuardShape { val: InsnId, shape: ShapeId, state: InsnId }, /// Side-exit if the block param has been modified or the block handler for the frame @@ -1975,7 +1974,7 @@ impl Function { &IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) }, &GuardType { val, guard_type, state } => GuardType { val: find!(val), guard_type, state }, &GuardTypeNot { val, guard_type, state } => GuardTypeNot { val: find!(val), guard_type, state }, - &GuardBitEquals { val, expected, state } => GuardBitEquals { val: find!(val), expected, state }, + &GuardBitEquals { val, expected, reason, state } => GuardBitEquals { val: find!(val), expected, reason, state }, &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, &GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state }, @@ -3069,6 +3068,24 @@ impl Function { self.infer_types(); } + fn load_shape(&mut self, block: BlockId, recv: InsnId) -> InsnId { + self.push_insn(block, Insn::LoadField { + recv, + id: ID!(_shape_id), + offset: unsafe { rb_shape_id_offset() } as i32, + return_type: types::CShape + }) + } + + fn guard_shape(&mut self, block: BlockId, val: InsnId, expected: ShapeId, state: InsnId) -> InsnId { + self.push_insn(block, Insn::GuardBitEquals { + val, + expected: Const::CShape(expected), + reason: SideExitReason::GuardShape(expected), + state + }) + } + fn optimize_getivar(&mut self) { for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); @@ -3094,7 +3111,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); - let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); + let shape = self.load_shape(block, self_val); + self.guard_shape(block, shape, recv_type.shape(), state); let mut ivar_index: u16 = 0; let replacement = if ! unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { // If there is no IVAR index, then the ivar was undefined when we @@ -3148,7 +3166,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); - let _ = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); + let shape = self.load_shape(block, self_val); + self.guard_shape(block, shape, recv_type.shape(), state); let mut ivar_index: u16 = 0; let replacement = if unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { self.push_insn(block, Insn::Const { val: Const::Value(pushval) }) @@ -3219,7 +3238,8 @@ impl Function { // Fall through to emitting the ivar write } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); - let self_val = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state }); + let shape = self.load_shape(block, self_val); + self.guard_shape(block, shape, recv_type.shape(), state); // Current shape contains this ivar let (ivar_storage, offset) = if recv_type.flags().is_embedded() { // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h @@ -6261,7 +6281,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let val = state.stack_pop()?; let array = fun.push_insn(block, Insn::GuardType { val, guard_type: types::ArrayExact, state: exit_id, }); let length = fun.push_insn(block, Insn::ArrayLength { array }); - fun.push_insn(block, Insn::GuardBitEquals { val: length, expected: Const::CInt64(num as i64), state: exit_id }); + fun.push_insn(block, Insn::GuardBitEquals { val: length, expected: Const::CInt64(num as i64), reason: SideExitReason::ExpandArray, state: exit_id }); for i in (0..num).rev() { // TODO(max): Add a short-cut path for long indices into an array where the // index is known to be in-bounds diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index afa97e48f1e4dc..3ad07596b71f58 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3677,10 +3677,11 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v15:HeapBasicObject = GuardType v6, HeapBasicObject - v16:HeapBasicObject = GuardShape v15, 0x1000 - v17:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v16:CShape = LoadField v15, :_shape_id@0x1000 + v17:CShape[0x1001] = GuardBitEquals v16, CShape(0x1001) + v18:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v17 + Return v18 "); } @@ -3701,10 +3702,11 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): v15:HeapBasicObject = GuardType v6, HeapBasicObject - v16:HeapBasicObject = GuardShape v15, 0x1000 - v17:NilClass = Const Value(nil) + v16:CShape = LoadField v15, :_shape_id@0x1000 + v17:CShape[0x1001] = GuardBitEquals v16, CShape(0x1001) + v18:NilClass = Const Value(nil) CheckInterrupts - Return v17 + Return v18 "); } @@ -3821,9 +3823,10 @@ mod hir_opt_tests { v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode v19:HeapBasicObject = GuardType v6, HeapBasicObject - v20:HeapBasicObject = GuardShape v19, 0x1000 - StoreField v20, :@foo@0x1001, v10 - WriteBarrier v20, v10 + v20:CShape = LoadField v19, :_shape_id@0x1000 + v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) + StoreField v19, :@foo@0x1002, v10 + WriteBarrier v19, v10 CheckInterrupts Return v10 "); @@ -3848,11 +3851,12 @@ mod hir_opt_tests { v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode v19:HeapBasicObject = GuardType v6, HeapBasicObject - v20:HeapBasicObject = GuardShape v19, 0x1000 - StoreField v20, :@foo@0x1001, v10 - WriteBarrier v20, v10 - v23:CShape[0x1002] = Const CShape(0x1002) - StoreField v20, :_shape_id@0x1003, v23 + v20:CShape = LoadField v19, :_shape_id@0x1000 + v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) + StoreField v19, :@foo@0x1002, v10 + WriteBarrier v19, v10 + v24:CShape[0x1003] = Const CShape(0x1003) + StoreField v19, :_shape_id@0x1000, v24 CheckInterrupts Return v10 "); @@ -3880,19 +3884,21 @@ mod hir_opt_tests { v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode v25:HeapBasicObject = GuardType v6, HeapBasicObject - v26:HeapBasicObject = GuardShape v25, 0x1000 - StoreField v26, :@foo@0x1001, v10 - WriteBarrier v26, v10 - v29:CShape[0x1002] = Const CShape(0x1002) - StoreField v26, :_shape_id@0x1003, v29 + v26:CShape = LoadField v25, :_shape_id@0x1000 + v27:CShape[0x1001] = GuardBitEquals v26, CShape(0x1001) + StoreField v25, :@foo@0x1002, v10 + WriteBarrier v25, v10 + v30:CShape[0x1003] = Const CShape(0x1003) + StoreField v25, :_shape_id@0x1000, v30 v16:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - v31:HeapBasicObject = GuardType v6, HeapBasicObject - v32:HeapBasicObject = GuardShape v31, 0x1002 + v32:HeapBasicObject = GuardType v6, HeapBasicObject + v33:CShape = LoadField v32, :_shape_id@0x1000 + v34:CShape[0x1003] = GuardBitEquals v33, CShape(0x1003) StoreField v32, :@bar@0x1004, v16 WriteBarrier v32, v16 - v35:CShape[0x1005] = Const CShape(0x1005) - StoreField v32, :_shape_id@0x1003, v35 + v37:CShape[0x1005] = Const CShape(0x1005) + StoreField v32, :_shape_id@0x1000, v37 CheckInterrupts Return v16 "); @@ -5611,10 +5617,11 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:BasicObject = LoadField v24, :@foo@0x1039 + v24:CShape = LoadField v21, :_shape_id@0x1038 + v25:CShape[0x1039] = GuardBitEquals v24, CShape(0x1039) + v26:BasicObject = LoadField v21, :@foo@0x103a CheckInterrupts - Return v25 + Return v26 "); } @@ -5650,11 +5657,12 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:CPtr = LoadField v24, :_as_heap@0x1039 - v26:BasicObject = LoadField v25, :@foo@0x103a + v24:CShape = LoadField v21, :_shape_id@0x1038 + v25:CShape[0x1039] = GuardBitEquals v24, CShape(0x1039) + v26:CPtr = LoadField v21, :_as_heap@0x103a + v27:BasicObject = LoadField v26, :@foo@0x103b CheckInterrupts - Return v26 + Return v27 "); } @@ -5679,11 +5687,12 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode v16:HeapBasicObject = GuardType v6, HeapBasicObject - v17:HeapBasicObject = GuardShape v16, 0x1000 - v18:CUInt16[0] = Const CUInt16(0) - v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + v17:CShape = LoadField v16, :_shape_id@0x1000 + v18:CShape[0x1001] = GuardBitEquals v17, CShape(0x1001) + v19:CUInt16[0] = Const CUInt16(0) + v20:BasicObject = CCall v16, :rb_ivar_get_at_no_ractor_check@0x1008, v19 CheckInterrupts - Return v19 + Return v20 "); } @@ -5708,11 +5717,12 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode v16:HeapBasicObject = GuardType v6, HeapBasicObject - v17:HeapBasicObject = GuardShape v16, 0x1000 - v18:CUInt16[0] = Const CUInt16(0) - v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + v17:CShape = LoadField v16, :_shape_id@0x1000 + v18:CShape[0x1001] = GuardBitEquals v17, CShape(0x1001) + v19:CUInt16[0] = Const CUInt16(0) + v20:BasicObject = CCall v16, :rb_ivar_get_at_no_ractor_check@0x1008, v19 CheckInterrupts - Return v19 + Return v20 "); } @@ -5739,11 +5749,12 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint SingleRactorMode v16:HeapBasicObject = GuardType v6, HeapBasicObject - v17:HeapBasicObject = GuardShape v16, 0x1000 - v18:CUInt16[0] = Const CUInt16(0) - v19:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v18 + v17:CShape = LoadField v16, :_shape_id@0x1000 + v18:CShape[0x1001] = GuardBitEquals v17, CShape(0x1001) + v19:CUInt16[0] = Const CUInt16(0) + v20:BasicObject = CCall v16, :rb_ivar_get_at_no_ractor_check@0x1008, v19 CheckInterrupts - Return v19 + Return v20 "); } @@ -6030,10 +6041,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) PatchPoint NoSingletonClass(C@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v26:NilClass = Const Value(nil) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v26 + Return v27 "); } @@ -6064,10 +6076,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) PatchPoint NoSingletonClass(C@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v26:NilClass = Const Value(nil) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v26 + Return v27 "); } @@ -6096,10 +6109,11 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:NilClass = Const Value(nil) + v24:CShape = LoadField v21, :_shape_id@0x1038 + v25:CShape[0x1039] = GuardBitEquals v24, CShape(0x1039) + v26:NilClass = Const Value(nil) CheckInterrupts - Return v25 + Return v26 "); } @@ -6128,10 +6142,11 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v24:HeapObject[class_exact:C] = GuardShape v21, 0x1038 - v25:NilClass = Const Value(nil) + v24:CShape = LoadField v21, :_shape_id@0x1038 + v25:CShape[0x1039] = GuardBitEquals v24, CShape(0x1039) + v26:NilClass = Const Value(nil) CheckInterrupts - Return v25 + Return v26 "); } @@ -6160,11 +6175,12 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 - StoreField v29, :@foo@0x1039, v16 - WriteBarrier v29, v16 - v32:CShape[0x103a] = Const CShape(0x103a) - StoreField v29, :_shape_id@0x103b, v32 + v29:CShape = LoadField v26, :_shape_id@0x1038 + v30:CShape[0x1039] = GuardBitEquals v29, CShape(0x1039) + StoreField v26, :@foo@0x103a, v16 + WriteBarrier v26, v16 + v33:CShape[0x103b] = Const CShape(0x103b) + StoreField v26, :_shape_id@0x1038, v33 CheckInterrupts Return v16 "); @@ -6195,11 +6211,12 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v29:HeapObject[class_exact:C] = GuardShape v26, 0x1038 - StoreField v29, :@foo@0x1039, v16 - WriteBarrier v29, v16 - v32:CShape[0x103a] = Const CShape(0x103a) - StoreField v29, :_shape_id@0x103b, v32 + v29:CShape = LoadField v26, :_shape_id@0x1038 + v30:CShape[0x1039] = GuardBitEquals v29, CShape(0x1039) + StoreField v26, :@foo@0x103a, v16 + WriteBarrier v26, v16 + v33:CShape[0x103b] = Const CShape(0x103b) + StoreField v26, :_shape_id@0x1038, v33 CheckInterrupts Return v16 "); @@ -9752,21 +9769,22 @@ mod hir_opt_tests { SetLocal :formatted, l0, EP@3, v15 PatchPoint SingleRactorMode v54:HeapBasicObject = GuardType v14, HeapBasicObject - v55:HeapBasicObject = GuardShape v54, 0x1000 - StoreField v55, :@formatted@0x1001, v15 - WriteBarrier v55, v15 - v58:CShape[0x1002] = Const CShape(0x1002) - StoreField v55, :_shape_id@0x1003, v58 + v55:CShape = LoadField v54, :_shape_id@0x1000 + v56:CShape[0x1001] = GuardBitEquals v55, CShape(0x1001) + StoreField v54, :@formatted@0x1002, v15 + WriteBarrier v54, v15 + v59:CShape[0x1003] = Const CShape(0x1003) + StoreField v54, :_shape_id@0x1000, v59 v43:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Class@0x1010) - v63:BasicObject = CCallWithFrame v43, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 + v64:BasicObject = CCallWithFrame v43, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 v46:BasicObject = GetLocal :a, l0, EP@6 v47:BasicObject = GetLocal :_b, l0, EP@5 v48:BasicObject = GetLocal :_c, l0, EP@4 v49:BasicObject = GetLocal :formatted, l0, EP@3 CheckInterrupts - Return v63 + Return v64 "); } @@ -9802,10 +9820,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestFrozen@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:Fixnum[1] = Const Value(1) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:Fixnum[1] = Const Value(1) CheckInterrupts - Return v27 + Return v28 "); } @@ -9843,10 +9862,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestMultiIvars@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:Fixnum[20] = Const Value(20) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:Fixnum[20] = Const Value(20) CheckInterrupts - Return v27 + Return v28 "); } @@ -9882,10 +9902,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestFrozenStr@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:StringExact[VALUE(0x1050)] = Const Value(VALUE(0x1050)) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:StringExact[VALUE(0x1050)] = Const Value(VALUE(0x1050)) CheckInterrupts - Return v27 + Return v28 "); } @@ -9921,10 +9942,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestFrozenNil@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:NilClass = Const Value(nil) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:NilClass = Const Value(nil) CheckInterrupts - Return v27 + Return v28 "); } @@ -9960,10 +9982,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestUnfrozen@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v26:BasicObject = LoadField v25, :@a@0x1049 + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:BasicObject = LoadField v20, :@a@0x104a CheckInterrupts - Return v26 + Return v27 "); } @@ -9999,10 +10022,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestAttrReader@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:Fixnum[42] = Const Value(42) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:Fixnum[42] = Const Value(42) CheckInterrupts - Return v27 + Return v28 "); } @@ -10038,10 +10062,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestFrozenSym@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:StaticSymbol[:hello] = Const Value(VALUE(0x1050)) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:StaticSymbol[:hello] = Const Value(VALUE(0x1050)) CheckInterrupts - Return v27 + Return v28 "); } @@ -10077,10 +10102,11 @@ mod hir_opt_tests { v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestFrozenBool@0x1010) - v25:HeapObject[VALUE(0x1008)] = GuardShape v20, 0x1048 - v27:TrueClass = Const Value(true) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v28:TrueClass = Const Value(true) CheckInterrupts - Return v27 + Return v28 "); } @@ -10114,10 +10140,11 @@ mod hir_opt_tests { PatchPoint MethodRedefined(TestDynamic@0x1000, val@0x1008, cme:0x1010) PatchPoint NoSingletonClass(TestDynamic@0x1000) v21:HeapObject[class_exact:TestDynamic] = GuardType v9, HeapObject[class_exact:TestDynamic] - v24:HeapObject[class_exact:TestDynamic] = GuardShape v21, 0x1038 - v25:BasicObject = LoadField v24, :@val@0x1039 + v24:CShape = LoadField v21, :_shape_id@0x1038 + v25:CShape[0x1039] = GuardBitEquals v24, CShape(0x1039) + v26:BasicObject = LoadField v21, :@val@0x103a CheckInterrupts - Return v25 + Return v26 "); } @@ -10154,20 +10181,22 @@ mod hir_opt_tests { v28:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) - v39:HeapObject[VALUE(0x1008)] = GuardShape v28, 0x1048 - v50:Fixnum[100] = Const Value(100) + v39:CShape = LoadField v28, :_shape_id@0x1048 + v40:CShape[0x1049] = GuardBitEquals v39, CShape(0x1049) + v52:Fixnum[100] = Const Value(100) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1050, NESTED_FROZEN) v34:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1058, cme:0x1060) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) - v42:HeapObject[VALUE(0x1008)] = GuardShape v34, 0x1048 - v51:Fixnum[200] = Const Value(200) + v43:CShape = LoadField v34, :_shape_id@0x1048 + v44:CShape[0x1049] = GuardBitEquals v43, CShape(0x1049) + v53:Fixnum[200] = Const Value(200) PatchPoint MethodRedefined(Integer@0x1088, +@0x1090, cme:0x1098) - v52:Fixnum[300] = Const Value(300) + v54:Fixnum[300] = Const Value(300) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v52 + Return v54 "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 68eeac456b38a2..01bd6e2f597ec8 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -190,6 +190,7 @@ make_counters! { exit_guard_bit_equals_failure, exit_guard_int_equals_failure, exit_guard_shape_failure, + exit_expandarray_failure, exit_guard_not_frozen_failure, exit_guard_not_shared_failure, exit_guard_less_failure, @@ -509,8 +510,8 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { BoxFixnumOverflow => exit_box_fixnum_overflow, GuardType(_) => exit_guard_type_failure, GuardTypeNot(_) => exit_guard_type_not_failure, - GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, + ExpandArray => exit_expandarray_failure, GuardNotFrozen => exit_guard_not_frozen_failure, GuardNotShared => exit_guard_not_shared_failure, GuardLess => exit_guard_less_failure, From c6f9a4d3936ff9a2f9db8a3548a7dbc8fadd18d9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 7 Jan 2026 19:56:49 -0500 Subject: [PATCH 2383/2435] Remove check for rb_obj_gen_fields_p in rb_hash_dup rb_copy_generic_ivar already checks for it, so we don't need to call rb_obj_gen_fields_p twice. --- hash.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hash.c b/hash.c index dc47eb6ab77736..28b729516474a7 100644 --- a/hash.c +++ b/hash.c @@ -1558,9 +1558,8 @@ rb_hash_dup(VALUE hash) const VALUE flags = RBASIC(hash)->flags; VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT); - if (rb_obj_gen_fields_p(hash)) { - rb_copy_generic_ivar(ret, hash); - } + rb_copy_generic_ivar(ret, hash); + return ret; } From 50b719115af8344888ef2735788ebc9f32c05a54 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 8 Jan 2026 23:07:50 +0000 Subject: [PATCH 2384/2435] Remove ruby-bench excludes (#15828) These benchmarks should be working fine now. --- .github/workflows/ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 7405ca997b8f56..3551ac8ef2adae 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -208,7 +208,7 @@ jobs: matrix: include: # Using the same setup as ZJIT jobs - - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' + - bench_opts: '--warmup=1 --bench=1' runs-on: ubuntu-24.04 diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 7f0f6ebd4d0e8d..6da45a3a42acf8 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index ba849d9bdf4e3d..d8b5460ed7f703 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,ruby-lsp' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 From b3216bc1e19c4b49a352793cc60ab06743ad58f2 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:46:29 +0100 Subject: [PATCH 2385/2435] Fix Ripper::Lexer::State#[] for to_s The instance variable is called `to_s`, not `event`. --- ext/ripper/lib/ripper/lexer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ripper/lib/ripper/lexer.rb b/ext/ripper/lib/ripper/lexer.rb index 870c79857bd670..9b849dfeae2487 100644 --- a/ext/ripper/lib/ripper/lexer.rb +++ b/ext/ripper/lib/ripper/lexer.rb @@ -68,7 +68,7 @@ def [](index) when 0, :to_int @to_int when 1, :to_s - @event + @to_s else nil end From aa7eb97d062e260f0d704b78e93c470ac8444129 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 8 Jan 2026 19:09:59 -0500 Subject: [PATCH 2386/2435] [ruby/mmtk] Add MMTK_ASSERT https://github.com/ruby/mmtk/commit/e34d5cf32f --- gc/mmtk/mmtk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 38e730a3761e4d..7b70ddff5e61f6 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -83,6 +83,12 @@ RB_THREAD_LOCAL_SPECIFIER VALUE marking_parent_object; # error We currently need language-supported TLS #endif +#ifdef MMTK_DEBUG +# define MMTK_ASSERT(expr, ...) RUBY_ASSERT_ALWAYS(expr, #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) +#else +# define MMTK_ASSERT(expr, ...) ((void)0) +#endif + #include static void From e89db8567c20692eabea70a4997a6ba325a347f8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 8 Jan 2026 19:10:08 -0500 Subject: [PATCH 2387/2435] [ruby/mmtk] Assert that objects are not T_NONE in the write barrier https://github.com/ruby/mmtk/commit/59d27203e2 --- gc/mmtk/mmtk.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 7b70ddff5e61f6..03210536131a09 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -981,6 +981,9 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) } #endif + MMTK_ASSERT(BUILTIN_TYPE(a) != T_NONE); + MMTK_ASSERT(BUILTIN_TYPE(b) != T_NONE); + mmtk_object_reference_write_post(cache->mutator, (MMTk_ObjectReference)a); } From b61e18d76b939d7e5e18f61a426b14a110f95b7c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Jan 2026 19:40:56 +0900 Subject: [PATCH 2388/2435] Win32: configure without an intermediate makefile This batch file used `nmake` on the old `command.com` to extract the parent directory name of this file and to get around the command line argument length limit. However, Windows 9X support as a build host ended over a decade ago, and this file now utilizes the functionality of `cmd.exe` already. --- win32/configure.bat | 32 +++++++++++++++----------------- win32/setup.mak | 8 +++++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/win32/configure.bat b/win32/configure.bat index 181813f4ad1581..9355caa4d852da 100755 --- a/win32/configure.bat +++ b/win32/configure.bat @@ -3,15 +3,24 @@ set PROMPT=$E[94m+$E[m$S set witharg= -for %%I in (%0) do if /%%~dpI/ == /%CD%\/ ( +if "%~dp0" == "%CD%\" ( echo don't run in win32 directory. exit /b 999 +) else if "%0" == "%~nx0" ( + set "WIN32DIR=%~$PATH:0" +) else if "%0" == "%~n0" ( + set "WIN32DIR=%~$PATH:0" +) else ( + set "WIN32DIR=%0" ) +set "WIN32DIR=%WIN32DIR:\=/%:/:" +call set "WIN32DIR=%%WIN32DIR:%~x0:/:=:/:%%" +call set "WIN32DIR=%%WIN32DIR:/%~n0:/:=:/:%%" +set "WIN32DIR=%WIN32DIR:~0,-3%" + set XINCFLAGS= set XLDFLAGS= - -set conf=%0 set pathlist= set config_make=confargs~%RANDOM%.mak set confargs=%config_make:.mak=.c% @@ -305,19 +314,8 @@ goto :exit ) >> %config_make% del %confargs% > nul -set setup_make=%config_make:confargs=setup% -( - echo #### -*- makefile -*- - echo conf = %conf% - echo $^(conf^): nul - echo @del %setup_make% - echo @$^(MAKE^) -l$^(MAKEFLAGS^) -f $^(@D^)/setup.mak \ - echo WIN32DIR=$^(@D:\=/^) config_make=%config_make% - echo -@move /y Makefile Makefile.old ^> nul 2^> nul - echo @ren Makefile.new Makefile -) > %setup_make% -nmake -alf %setup_make% MAKEFILE=Makefile.new - -exit /b %ERRORLEVEL% +nmake -al -f %WIN32DIR%/setup.mak "WIN32DIR=%WIN32DIR%" ^ + config_make=%config_make% ^ + MAKEFILE=Makefile.new MAKEFILE_BACK=Makefile.old MAKEFILE_NEW=Makefile :exit @endlocal diff --git a/win32/setup.mak b/win32/setup.mak index de8db870ba69eb..77b7d2f406a1dc 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -40,7 +40,7 @@ x64-mswin64: -prologue- -x64- -epilogue- -generic-: -osname- -basic-vars-: nul - @type << > $(MAKEFILE) + @rem <<$(MAKEFILE) ### Makefile for ruby $(TARGET_OS) ### MAKE = nmake srcdir = $(srcdir:\=/) @@ -146,8 +146,8 @@ main(void) << @( \ $(CC) -O2 $@.c && .\$@ || \ - set bug=%ERRORLEVEL% \ - echo This compiler has an optimization bug \ + (set bug=%ERRORLEVEL% & \ + echo This compiler has an optimization bug) \ ) & $(WIN32DIR:/=\)\rm.bat $@.* & exit /b %bug% -version-: nul verconf.mk @@ -272,4 +272,6 @@ AS = $(AS) -nologo $(BANG)include $$(srcdir)/win32/Makefile.sub << @$(COMSPEC) /C $(srcdir:/=\)\win32\rm.bat config.h config.status + -@move /y $(MAKEFILE_NEW) $(MAKEFILE_BACK) > nul 2> nul + @ren $(MAKEFILE) $(MAKEFILE_NEW) @echo type 'nmake' to make ruby. From 3185786874315ab4f1cfcc73c3d1b14613452905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rmungandrk?= Date: Fri, 9 Jan 2026 10:22:01 +0700 Subject: [PATCH 2389/2435] Fix integer overflow checks in enumerator --- enumerator.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/enumerator.c b/enumerator.c index 8580c605dffc16..292ddb0419fb8a 100644 --- a/enumerator.c +++ b/enumerator.c @@ -18,6 +18,7 @@ #include #endif +#include #include "id.h" #include "internal.h" #include "internal/class.h" @@ -4008,7 +4009,7 @@ arith_seq_take(VALUE self, VALUE num) ary = rb_ary_new_capa((n < len) ? n : len); while (n > 0 && i < end) { rb_ary_push(ary, LONG2FIX(i)); - if (i + unit < i) break; + if (i > LONG_MAX - unit) break; i += unit; --n; } @@ -4021,7 +4022,7 @@ arith_seq_take(VALUE self, VALUE num) ary = rb_ary_new_capa((n < len) ? n : len); while (n > 0 && i > end) { rb_ary_push(ary, LONG2FIX(i)); - if (i + unit > i) break; + if (i < LONG_MIN - unit) break; i += unit; --n; } From 364e25b1c3b7771c84b27e572796165c9b11b969 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 2 Jan 2026 09:37:59 +0900 Subject: [PATCH 2390/2435] Make `assert_separately` tolerant to core method redefinitions And split `TestRubyOptimization#test_objtostring` for each target class. --- test/ruby/test_optimization.rb | 8 +++++++- tool/lib/core_assertions.rb | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 089c5fbd1d0dc6..cfa90b8b8a0f25 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -1080,7 +1080,7 @@ def test_optimized_rescue class Objtostring end - def test_objtostring + def test_objtostring_immediate assert_raise(NoMethodError){"#{BasicObject.new}"} assert_redefine_method('Symbol', 'to_s', <<-'end') assert_match %r{\A#\z}, "#{:foo}" @@ -1094,11 +1094,17 @@ def test_objtostring assert_redefine_method('FalseClass', 'to_s', <<-'end') assert_match %r{\A#\z}, "#{false}" end + end + + def test_objtostring_fixnum assert_redefine_method('Integer', 'to_s', <<-'end') (-1..10).each { |i| assert_match %r{\A#\z}, "#{i}" } end + end + + def test_objtostring assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}" assert_match %r{\A#\z}, "#{Class.new}" assert_match %r{\A#\z}, "#{Module.new}" diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index ed38a34f225346..8aafd8882e6b98 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -303,9 +303,35 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) def separated_runner(token, out = nil) include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) + out = out ? IO.new(out, 'w') : STDOUT + + # avoid method redefinitions + out_write = out.method(:write) + integer_to_s = Integer.instance_method(:to_s) + array_pack = Array.instance_method(:pack) + marshal_dump = Marshal.method(:dump) + assertions_ivar_set = Test::Unit::Assertions.method(:instance_variable_set) + assertions_ivar_get = Test::Unit::Assertions.method(:instance_variable_get) + Test::Unit::Assertions.module_eval do + @_assertions = 0 + + undef _assertions= + define_method(:_assertions=, ->(n) {assertions_ivar_set.call(:@_assertions, n)}) + + undef _assertions + define_method(:_assertions, -> {assertions_ivar_get.call(:@_assertions)}) + end + # assume Method#call and UnboundMethod#bind_call need to work as the original + at_exit { - out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" + assertions = assertions_ivar_get.call(:@_assertions) + out_write.call <<~OUT + #{token} + #{array_pack.bind_call([marshal_dump.call($!)], 'm')} + #{token} + #{token}assertions=#{integer_to_s.bind_call(assertions)} + OUT } if defined?(Test::Unit::Runner) Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) @@ -360,7 +386,9 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o raise if $! abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) assert(!abort, FailDesc[status, nil, stderr]) - self._assertions += res[/^#{token_re}assertions=(\d+)/, 1].to_i + if (assertions = res[/^#{token_re}assertions=(\d+)/, 1].to_i) > 0 + self._assertions += assertions + end begin res = Marshal.load(res[/^#{token_re}\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) rescue => marshal_error From fc0c67deb2195c51661b6c35eb41cfb2cb92e3f8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 3 Jan 2026 12:20:35 +0900 Subject: [PATCH 2391/2435] Make `assert_separately` to count assertions in forked processes --- tool/lib/core_assertions.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 8aafd8882e6b98..419704448ba2fa 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -327,10 +327,9 @@ def separated_runner(token, out = nil) at_exit { assertions = assertions_ivar_get.call(:@_assertions) out_write.call <<~OUT - #{token} - #{array_pack.bind_call([marshal_dump.call($!)], 'm')} - #{token} - #{token}assertions=#{integer_to_s.bind_call(assertions)} + + #{array_pack.bind_call([marshal_dump.call($!)], 'm0')} + OUT } if defined?(Test::Unit::Runner) @@ -385,17 +384,17 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o end raise if $! abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) + assertions = 0 + marshal_error = nil assert(!abort, FailDesc[status, nil, stderr]) - if (assertions = res[/^#{token_re}assertions=(\d+)/, 1].to_i) > 0 - self._assertions += assertions - end - begin - res = Marshal.load(res[/^#{token_re}\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) + res.scan(/^\n(.*?)\n(?=<\/error id="#{token_re}">$)/m) do + assertions += $1.to_i + res = Marshal.load($2.unpack1("m")) or next rescue => marshal_error ignore_stderr = nil res = nil - end - if res and !(SystemExit === res) + else + next if SystemExit === res if bt = res.backtrace bt.each do |l| l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} @@ -407,7 +406,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o raise res end - # really is it succeed? + # really did it succeed? unless ignore_stderr # the body of assert_separately must not output anything to detect error assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) From e01e13c23999f0e43d8a0874003c73a1cdd68f1e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 2 Jan 2026 11:05:07 +0900 Subject: [PATCH 2392/2435] Use `assert_ruby_status` if no assertion --- test/-ext-/tracepoint/test_tracepoint.rb | 2 +- test/objspace/test_objspace.rb | 4 ++-- test/openssl/test_fips.rb | 2 +- test/ripper/assert_parse_files.rb | 1 + test/ruby/test_autoload.rb | 8 ++++++-- test/ruby/test_gc_compact.rb | 2 +- test/ruby/test_lambda.rb | 2 +- test/ruby/test_module.rb | 4 ++-- test/ruby/test_optimization.rb | 4 ++-- test/ruby/test_process.rb | 4 ++-- test/ruby/test_require.rb | 4 ++-- test/ruby/test_shapes.rb | 6 +++--- test/ruby/test_signal.rb | 2 +- test/ruby/test_syntax.rb | 4 ++-- test/ruby/test_thread.rb | 7 ++++--- test/ruby/test_weakmap.rb | 4 ++-- test/ruby/test_yield.rb | 2 +- test/ruby/test_yjit.rb | 2 +- 18 files changed, 35 insertions(+), 29 deletions(-) diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb index debddd83d043fe..603fd01fd5c7e6 100644 --- a/test/-ext-/tracepoint/test_tracepoint.rb +++ b/test/-ext-/tracepoint/test_tracepoint.rb @@ -83,7 +83,7 @@ def run(hook) end def test_teardown_with_active_GC_end_hook - assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start') + assert_ruby_status([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start') end end diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 3d5b2a4d2ae559..2c0c6195e6ba46 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -143,7 +143,7 @@ def test_reachable_objects_from def test_reachable_objects_during_iteration omit 'flaky on Visual Studio with: [BUG] Unnormalized Fixnum value' if /mswin/ =~ RUBY_PLATFORM opts = %w[--disable-gem --disable=frozen-string-literal -robjspace] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" + assert_ruby_status opts, "#{<<-"begin;"}\n#{<<-'end;'}" begin; ObjectSpace.each_object{|o| o.inspect @@ -179,7 +179,7 @@ def test_reachable_objects_size end def test_trace_object_allocations_stop_first - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; require "objspace" # Make sure stopping before the tracepoints are initialized doesn't raise. See [Bug #17020] diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb index efc2655e25bf54..683e0011e8d7fc 100644 --- a/test/openssl/test_fips.rb +++ b/test/openssl/test_fips.rb @@ -30,7 +30,7 @@ def test_fips_mode_get_is_false_on_fips_mode_disabled def test_fips_mode_is_reentrant return if aws_lc? # AWS-LC's FIPS mode is decided at compile time. - assert_separately(["-ropenssl"], <<~"end;") + assert_ruby_status(["-ropenssl"], <<~"end;") OpenSSL.fips_mode = false OpenSSL.fips_mode = false end; diff --git a/test/ripper/assert_parse_files.rb b/test/ripper/assert_parse_files.rb index 0d583a99e3e3cd..4f08589e41064b 100644 --- a/test/ripper/assert_parse_files.rb +++ b/test/ripper/assert_parse_files.rb @@ -40,6 +40,7 @@ class Parser < Ripper end } end + assert(true) if scripts.empty? end; end end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 50949274a362f8..82bf2d9d2c0992 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -574,7 +574,7 @@ def test_autoload_parallel_race autoload_path = File.join(tmpdir, "autoload_parallel_race.rb") File.write(autoload_path, 'module Foo; end; module Bar; end') - assert_separately([], <<-RUBY, timeout: 100) + assert_ruby_status([], <<-RUBY, timeout: 100) autoload_path = #{File.realpath(autoload_path).inspect} # This should work with no errors or failures. @@ -617,6 +617,10 @@ module SomeNamespace private def assert_separately(*args, **kwargs) - super(*args, **{ timeout: 60 }.merge(kwargs)) + super(*args, timeout: 60, **kwargs) + end + + def assert_ruby_status(*args, **kwargs) + super(*args, timeout: 60, **kwargs) end end diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index bc7692d644fba9..84828d498543aa 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -364,7 +364,7 @@ def add_ivars def test_compact_objects_of_varying_sizes omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 - assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; $objects = [] 160.times do |n| diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 6a0dd9915e32a6..c1858a36ddecdd 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -163,7 +163,7 @@ def self.b end def test_proc_inside_lambda_toplevel - assert_separately [], <<~RUBY + assert_ruby_status [], <<~RUBY lambda{ $g = proc{ return :pr } }.call diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3db60dec8f3b43..9ed6c1e321a670 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -583,7 +583,7 @@ def b; 2 end end def test_gc_prepend_chain - assert_separately([], <<-EOS) + assert_ruby_status([], <<-EOS) 10000.times { |i| m1 = Module.new do def foo; end @@ -3073,7 +3073,7 @@ def test_return_value_of_define_singleton_method end def test_prepend_gc - assert_separately [], %{ + assert_ruby_status [], %{ module Foo end class Object diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index cfa90b8b8a0f25..5d16984eeff883 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -946,14 +946,14 @@ def foo &b end def test_peephole_optimization_without_trace - assert_separately [], <<-END + assert_ruby_status [], <<-END RubyVM::InstructionSequence.compile_option = {trace_instruction: false} eval "def foo; 1.times{|(a), &b| nil && a}; end" END end def test_clear_unreachable_keyword_args - assert_separately [], <<-END, timeout: 60 + assert_ruby_status [], <<-END, timeout: 60 script = <<-EOS if true else diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 857ceab6762fda..b3a88b664cc09d 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -1996,7 +1996,7 @@ def test_popen_exit end def test_popen_reopen - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; io = File.open(IO::NULL) io2 = io.dup @@ -2387,7 +2387,7 @@ def test_clock_getres_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC end def test_deadlock_by_signal_at_forking - assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100) + assert_ruby_status(%W(- #{RUBY}), <<-INPUT, timeout: 100) ruby = ARGV.shift GC.start # reduce garbage GC.disable # avoid triggering CoW after forks diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index bbec9a5f954f93..0067a497006199 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -842,7 +842,7 @@ def test_require_with_loaded_features_pop } # [Bug #21567] - assert_separately(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}") + assert_ruby_status(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}") begin; class MyString def initialize(path) @@ -1029,7 +1029,7 @@ def test_resolve_feature_path_with_missing_feature def test_require_with_public_method_missing # [Bug #19793] - assert_separately(["-W0", "-rtempfile"], __FILE__, __LINE__, <<~RUBY, timeout: 60) + assert_ruby_status(["-W0", "-rtempfile"], <<~RUBY, timeout: 60) GC.stress = true class Object diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 453ca8f6a72dd0..67e2c543a3f1a3 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -228,7 +228,7 @@ class Hi; end end def test_run_out_of_shape_for_object - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; class A def initialize @@ -339,7 +339,7 @@ def test_evacuate_object_ivar_and_compaction end def test_gc_stress_during_evacuate_generic_ivar - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; [].instance_variable_set(:@a, 1) @@ -507,7 +507,7 @@ def initialize end def test_run_out_of_shape_rb_obj_copy_ivar - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; class A def initialize diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index 661ba031413ba8..091a66d5da9021 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -333,7 +333,7 @@ def test_self_stop def test_sigwait_fd_unused t = EnvUtil.apply_timeout_scale(0.1) - assert_separately([], <<-End) + assert_ruby_status([], <<-End) tgt = $$ trap(:TERM) { exit(0) } e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})" diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 585e691765c66a..b355128a7344a6 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2296,13 +2296,13 @@ def test_argument_forwarding_with_super_memory_leak end def test_class_module_Object_ancestors - assert_separately([], <<-RUBY) + assert_ruby_status([], <<-RUBY) m = Module.new m::Bug18832 = 1 include m class Bug18832; end RUBY - assert_separately([], <<-RUBY) + assert_ruby_status([], <<-RUBY) m = Module.new m::Bug18832 = 1 include m diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 8edebfb5c219da..2a61fc3450bbcc 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1594,7 +1594,8 @@ def frame_for_deadlock_test_2 # [Bug #21342] def test_unlock_locked_mutex_with_collected_fiber - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug21127 = '[ruby-core:120930] [Bug #21127]' + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; 5.times do m = Mutex.new @@ -1611,7 +1612,7 @@ def test_unlock_locked_mutex_with_collected_fiber end def test_unlock_locked_mutex_with_collected_fiber2 - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; MUTEXES = [] 5.times do @@ -1630,7 +1631,7 @@ def test_unlock_locked_mutex_with_collected_fiber2 end def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; mutexes = 1000.times.map do Mutex.new diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index 1050c74b3dc4cd..4f5823ecf4350c 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -203,7 +203,7 @@ def test_compaction @wm[i] = obj end - assert_separately([], <<-'end;') + assert_ruby_status([], <<-'end;') wm = ObjectSpace::WeakMap.new obj = Object.new 100.times do @@ -224,7 +224,7 @@ def test_compaction assert_equal(val, wm[key]) end; - assert_separately(["-W0"], <<-'end;') + assert_ruby_status(["-W0"], <<-'end;') wm = ObjectSpace::WeakMap.new ary = 10_000.times.map do diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb index 9b2b2f37e06e04..e7e65fce9e0f0f 100644 --- a/test/ruby/test_yield.rb +++ b/test/ruby/test_yield.rb @@ -401,7 +401,7 @@ def m.method_missing(*a) def test_block_cached_argc # [Bug #11451] - assert_separately([], <<-"end;") + assert_ruby_status([], <<-"end;") class Yielder def each yield :x, :y, :z diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 2096585451c324..6488661c2d3a6c 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -133,7 +133,7 @@ def compiled_counts = RubyVM::YJIT.runtime_stats&.dig(:compiled_iseq_count) end def test_yjit_enable_with_monkey_patch - assert_separately(%w[--yjit-disable], <<~RUBY) + assert_ruby_status(%w[--yjit-disable], <<~RUBY) # This lets rb_method_entry_at(rb_mKernel, ...) return NULL Kernel.prepend(Module.new) From c794a97940a36269cffcb6ad35ef7ff209fe2720 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Jan 2026 14:24:38 +0900 Subject: [PATCH 2393/2435] Rename `alloca_overflow` to `stack_overflow` `alloca` is an implementation detail to raise a stack overflow. --- ext/-test-/stack/stack.c | 4 ++-- test/-ext-/stack/test_stack_overflow.rb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/-test-/stack/stack.c b/ext/-test-/stack/stack.c index 8ff32f9737b218..f0e65e74b2e40e 100644 --- a/ext/-test-/stack/stack.c +++ b/ext/-test-/stack/stack.c @@ -2,7 +2,7 @@ #include "internal/string.h" static VALUE -stack_alloca_overflow(VALUE self) +stack_overflow(VALUE self) { size_t i = 0; @@ -30,6 +30,6 @@ asan_p(VALUE klass) void Init_stack(VALUE klass) { - rb_define_singleton_method(rb_cThread, "alloca_overflow", stack_alloca_overflow, 0); + rb_define_singleton_method(rb_cThread, "stack_overflow", stack_overflow, 0); rb_define_singleton_method(rb_cThread, "asan?", asan_p, 0); } diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb index 8eddec6ab5b089..3d7f00331dc711 100644 --- a/test/-ext-/stack/test_stack_overflow.rb +++ b/test/-ext-/stack/test_stack_overflow.rb @@ -17,7 +17,7 @@ def test_overflow require '-test-/stack' assert_raise(SystemStackError) do - Thread.alloca_overflow + Thread.stack_overflow end RUBY end @@ -29,7 +29,7 @@ def test_thread_stack_overflow thread = Thread.new do Thread.current.report_on_exception = false - Thread.alloca_overflow + Thread.stack_overflow end assert_raise(SystemStackError) do @@ -44,7 +44,7 @@ def test_fiber_stack_overflow GC.disable fiber = Fiber.new do - Thread.alloca_overflow + Thread.stack_overflow end assert_raise(SystemStackError) do From 7379b9ed780bc8fcf8c50ae2d33816523327608c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 24 Dec 2025 09:17:21 -0500 Subject: [PATCH 2394/2435] Optimize rb_mark_generic_ivar for T_DATA and T_STRUCT T_DATA and T_STRUCT could have ivars but might not use the generic_fields_tbl. This commit skips lookup in the generic_fields_tbl for those cases. --- depend | 36 ++++++++++++++++++++++++++++++++++++ ext/objspace/depend | 2 ++ ext/ripper/depend | 1 + gc.c | 4 ++-- shape.h | 20 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/depend b/depend index bf001f45ca8169..e7ad0b9f8ba508 100644 --- a/depend +++ b/depend @@ -300,6 +300,7 @@ ast.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h ast.$(OBJEXT): $(top_srcdir)/internal/serial.h ast.$(OBJEXT): $(top_srcdir)/internal/set_table.h ast.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +ast.$(OBJEXT): $(top_srcdir)/internal/struct.h ast.$(OBJEXT): $(top_srcdir)/internal/symbol.h ast.$(OBJEXT): $(top_srcdir)/internal/variable.h ast.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -539,6 +540,7 @@ bignum.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h bignum.$(OBJEXT): $(top_srcdir)/internal/serial.h bignum.$(OBJEXT): $(top_srcdir)/internal/set_table.h bignum.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +bignum.$(OBJEXT): $(top_srcdir)/internal/struct.h bignum.$(OBJEXT): $(top_srcdir)/internal/variable.h bignum.$(OBJEXT): $(top_srcdir)/internal/vm.h bignum.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -753,6 +755,7 @@ box.$(OBJEXT): $(top_srcdir)/internal/set_table.h box.$(OBJEXT): $(top_srcdir)/internal/st.h box.$(OBJEXT): $(top_srcdir)/internal/static_assert.h box.$(OBJEXT): $(top_srcdir)/internal/string.h +box.$(OBJEXT): $(top_srcdir)/internal/struct.h box.$(OBJEXT): $(top_srcdir)/internal/variable.h box.$(OBJEXT): $(top_srcdir)/internal/vm.h box.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -991,6 +994,7 @@ builtin.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h builtin.$(OBJEXT): $(top_srcdir)/internal/serial.h builtin.$(OBJEXT): $(top_srcdir)/internal/set_table.h builtin.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +builtin.$(OBJEXT): $(top_srcdir)/internal/struct.h builtin.$(OBJEXT): $(top_srcdir)/internal/variable.h builtin.$(OBJEXT): $(top_srcdir)/internal/vm.h builtin.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -1226,6 +1230,7 @@ class.$(OBJEXT): $(top_srcdir)/internal/serial.h class.$(OBJEXT): $(top_srcdir)/internal/set_table.h class.$(OBJEXT): $(top_srcdir)/internal/static_assert.h class.$(OBJEXT): $(top_srcdir)/internal/string.h +class.$(OBJEXT): $(top_srcdir)/internal/struct.h class.$(OBJEXT): $(top_srcdir)/internal/variable.h class.$(OBJEXT): $(top_srcdir)/internal/vm.h class.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -1636,6 +1641,7 @@ compile.$(OBJEXT): $(top_srcdir)/internal/serial.h compile.$(OBJEXT): $(top_srcdir)/internal/set_table.h compile.$(OBJEXT): $(top_srcdir)/internal/static_assert.h compile.$(OBJEXT): $(top_srcdir)/internal/string.h +compile.$(OBJEXT): $(top_srcdir)/internal/struct.h compile.$(OBJEXT): $(top_srcdir)/internal/symbol.h compile.$(OBJEXT): $(top_srcdir)/internal/thread.h compile.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -1895,6 +1901,7 @@ complex.$(OBJEXT): $(top_srcdir)/internal/serial.h complex.$(OBJEXT): $(top_srcdir)/internal/set_table.h complex.$(OBJEXT): $(top_srcdir)/internal/static_assert.h complex.$(OBJEXT): $(top_srcdir)/internal/string.h +complex.$(OBJEXT): $(top_srcdir)/internal/struct.h complex.$(OBJEXT): $(top_srcdir)/internal/variable.h complex.$(OBJEXT): $(top_srcdir)/internal/vm.h complex.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -2312,6 +2319,7 @@ cont.$(OBJEXT): $(top_srcdir)/internal/serial.h cont.$(OBJEXT): $(top_srcdir)/internal/set_table.h cont.$(OBJEXT): $(top_srcdir)/internal/static_assert.h cont.$(OBJEXT): $(top_srcdir)/internal/string.h +cont.$(OBJEXT): $(top_srcdir)/internal/struct.h cont.$(OBJEXT): $(top_srcdir)/internal/thread.h cont.$(OBJEXT): $(top_srcdir)/internal/variable.h cont.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -2552,6 +2560,7 @@ debug.$(OBJEXT): $(top_srcdir)/internal/serial.h debug.$(OBJEXT): $(top_srcdir)/internal/set_table.h debug.$(OBJEXT): $(top_srcdir)/internal/signal.h debug.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +debug.$(OBJEXT): $(top_srcdir)/internal/struct.h debug.$(OBJEXT): $(top_srcdir)/internal/variable.h debug.$(OBJEXT): $(top_srcdir)/internal/vm.h debug.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -5095,6 +5104,7 @@ error.$(OBJEXT): $(top_srcdir)/internal/serial.h error.$(OBJEXT): $(top_srcdir)/internal/set_table.h error.$(OBJEXT): $(top_srcdir)/internal/static_assert.h error.$(OBJEXT): $(top_srcdir)/internal/string.h +error.$(OBJEXT): $(top_srcdir)/internal/struct.h error.$(OBJEXT): $(top_srcdir)/internal/symbol.h error.$(OBJEXT): $(top_srcdir)/internal/thread.h error.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -5320,6 +5330,7 @@ eval.$(OBJEXT): $(top_srcdir)/internal/serial.h eval.$(OBJEXT): $(top_srcdir)/internal/set_table.h eval.$(OBJEXT): $(top_srcdir)/internal/static_assert.h eval.$(OBJEXT): $(top_srcdir)/internal/string.h +eval.$(OBJEXT): $(top_srcdir)/internal/struct.h eval.$(OBJEXT): $(top_srcdir)/internal/thread.h eval.$(OBJEXT): $(top_srcdir)/internal/variable.h eval.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -6563,6 +6574,7 @@ imemo.$(OBJEXT): $(top_srcdir)/internal/serial.h imemo.$(OBJEXT): $(top_srcdir)/internal/set_table.h imemo.$(OBJEXT): $(top_srcdir)/internal/st.h imemo.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +imemo.$(OBJEXT): $(top_srcdir)/internal/struct.h imemo.$(OBJEXT): $(top_srcdir)/internal/variable.h imemo.$(OBJEXT): $(top_srcdir)/internal/vm.h imemo.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -6948,6 +6960,7 @@ io.$(OBJEXT): $(top_srcdir)/internal/serial.h io.$(OBJEXT): $(top_srcdir)/internal/set_table.h io.$(OBJEXT): $(top_srcdir)/internal/static_assert.h io.$(OBJEXT): $(top_srcdir)/internal/string.h +io.$(OBJEXT): $(top_srcdir)/internal/struct.h io.$(OBJEXT): $(top_srcdir)/internal/thread.h io.$(OBJEXT): $(top_srcdir)/internal/transcode.h io.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -7396,6 +7409,7 @@ iseq.$(OBJEXT): $(top_srcdir)/internal/serial.h iseq.$(OBJEXT): $(top_srcdir)/internal/set_table.h iseq.$(OBJEXT): $(top_srcdir)/internal/static_assert.h iseq.$(OBJEXT): $(top_srcdir)/internal/string.h +iseq.$(OBJEXT): $(top_srcdir)/internal/struct.h iseq.$(OBJEXT): $(top_srcdir)/internal/symbol.h iseq.$(OBJEXT): $(top_srcdir)/internal/thread.h iseq.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -7645,6 +7659,7 @@ jit.$(OBJEXT): $(top_srcdir)/internal/serial.h jit.$(OBJEXT): $(top_srcdir)/internal/set_table.h jit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h jit.$(OBJEXT): $(top_srcdir)/internal/string.h +jit.$(OBJEXT): $(top_srcdir)/internal/struct.h jit.$(OBJEXT): $(top_srcdir)/internal/variable.h jit.$(OBJEXT): $(top_srcdir)/internal/vm.h jit.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -7897,6 +7912,7 @@ load.$(OBJEXT): $(top_srcdir)/internal/serial.h load.$(OBJEXT): $(top_srcdir)/internal/set_table.h load.$(OBJEXT): $(top_srcdir)/internal/static_assert.h load.$(OBJEXT): $(top_srcdir)/internal/string.h +load.$(OBJEXT): $(top_srcdir)/internal/struct.h load.$(OBJEXT): $(top_srcdir)/internal/thread.h load.$(OBJEXT): $(top_srcdir)/internal/variable.h load.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -9035,6 +9051,7 @@ memory_view.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h memory_view.$(OBJEXT): $(top_srcdir)/internal/serial.h memory_view.$(OBJEXT): $(top_srcdir)/internal/set_table.h memory_view.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/struct.h memory_view.$(OBJEXT): $(top_srcdir)/internal/variable.h memory_view.$(OBJEXT): $(top_srcdir)/internal/vm.h memory_view.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -9252,6 +9269,7 @@ miniinit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h miniinit.$(OBJEXT): $(top_srcdir)/internal/serial.h miniinit.$(OBJEXT): $(top_srcdir)/internal/set_table.h miniinit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/struct.h miniinit.$(OBJEXT): $(top_srcdir)/internal/variable.h miniinit.$(OBJEXT): $(top_srcdir)/internal/vm.h miniinit.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -9508,6 +9526,7 @@ node.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h node.$(OBJEXT): $(top_srcdir)/internal/serial.h node.$(OBJEXT): $(top_srcdir)/internal/set_table.h node.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +node.$(OBJEXT): $(top_srcdir)/internal/struct.h node.$(OBJEXT): $(top_srcdir)/internal/variable.h node.$(OBJEXT): $(top_srcdir)/internal/vm.h node.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -9722,6 +9741,7 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h node_dump.$(OBJEXT): $(top_srcdir)/internal/serial.h node_dump.$(OBJEXT): $(top_srcdir)/internal/set_table.h node_dump.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +node_dump.$(OBJEXT): $(top_srcdir)/internal/struct.h node_dump.$(OBJEXT): $(top_srcdir)/internal/variable.h node_dump.$(OBJEXT): $(top_srcdir)/internal/vm.h node_dump.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -9939,6 +9959,7 @@ numeric.$(OBJEXT): $(top_srcdir)/internal/serial.h numeric.$(OBJEXT): $(top_srcdir)/internal/set_table.h numeric.$(OBJEXT): $(top_srcdir)/internal/static_assert.h numeric.$(OBJEXT): $(top_srcdir)/internal/string.h +numeric.$(OBJEXT): $(top_srcdir)/internal/struct.h numeric.$(OBJEXT): $(top_srcdir)/internal/util.h numeric.$(OBJEXT): $(top_srcdir)/internal/variable.h numeric.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -10377,6 +10398,7 @@ pack.$(OBJEXT): $(top_srcdir)/internal/serial.h pack.$(OBJEXT): $(top_srcdir)/internal/set_table.h pack.$(OBJEXT): $(top_srcdir)/internal/static_assert.h pack.$(OBJEXT): $(top_srcdir)/internal/string.h +pack.$(OBJEXT): $(top_srcdir)/internal/struct.h pack.$(OBJEXT): $(top_srcdir)/internal/symbol.h pack.$(OBJEXT): $(top_srcdir)/internal/variable.h pack.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -10601,6 +10623,7 @@ parse.$(OBJEXT): $(top_srcdir)/internal/serial.h parse.$(OBJEXT): $(top_srcdir)/internal/set_table.h parse.$(OBJEXT): $(top_srcdir)/internal/static_assert.h parse.$(OBJEXT): $(top_srcdir)/internal/string.h +parse.$(OBJEXT): $(top_srcdir)/internal/struct.h parse.$(OBJEXT): $(top_srcdir)/internal/symbol.h parse.$(OBJEXT): $(top_srcdir)/internal/thread.h parse.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -12059,6 +12082,7 @@ proc.$(OBJEXT): $(top_srcdir)/internal/serial.h proc.$(OBJEXT): $(top_srcdir)/internal/set_table.h proc.$(OBJEXT): $(top_srcdir)/internal/static_assert.h proc.$(OBJEXT): $(top_srcdir)/internal/string.h +proc.$(OBJEXT): $(top_srcdir)/internal/struct.h proc.$(OBJEXT): $(top_srcdir)/internal/symbol.h proc.$(OBJEXT): $(top_srcdir)/internal/variable.h proc.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -12310,6 +12334,7 @@ process.$(OBJEXT): $(top_srcdir)/internal/serial.h process.$(OBJEXT): $(top_srcdir)/internal/set_table.h process.$(OBJEXT): $(top_srcdir)/internal/static_assert.h process.$(OBJEXT): $(top_srcdir)/internal/string.h +process.$(OBJEXT): $(top_srcdir)/internal/struct.h process.$(OBJEXT): $(top_srcdir)/internal/thread.h process.$(OBJEXT): $(top_srcdir)/internal/time.h process.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -12765,6 +12790,7 @@ random.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h random.$(OBJEXT): $(top_srcdir)/internal/serial.h random.$(OBJEXT): $(top_srcdir)/internal/set_table.h random.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +random.$(OBJEXT): $(top_srcdir)/internal/struct.h random.$(OBJEXT): $(top_srcdir)/internal/variable.h random.$(OBJEXT): $(top_srcdir)/internal/vm.h random.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -13390,6 +13416,7 @@ re.$(OBJEXT): $(top_srcdir)/internal/serial.h re.$(OBJEXT): $(top_srcdir)/internal/set_table.h re.$(OBJEXT): $(top_srcdir)/internal/static_assert.h re.$(OBJEXT): $(top_srcdir)/internal/string.h +re.$(OBJEXT): $(top_srcdir)/internal/struct.h re.$(OBJEXT): $(top_srcdir)/internal/time.h re.$(OBJEXT): $(top_srcdir)/internal/variable.h re.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -14617,6 +14644,7 @@ ruby.$(OBJEXT): $(top_srcdir)/internal/serial.h ruby.$(OBJEXT): $(top_srcdir)/internal/set_table.h ruby.$(OBJEXT): $(top_srcdir)/internal/static_assert.h ruby.$(OBJEXT): $(top_srcdir)/internal/string.h +ruby.$(OBJEXT): $(top_srcdir)/internal/struct.h ruby.$(OBJEXT): $(top_srcdir)/internal/thread.h ruby.$(OBJEXT): $(top_srcdir)/internal/variable.h ruby.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -15259,6 +15287,7 @@ set.$(OBJEXT): $(top_srcdir)/internal/serial.h set.$(OBJEXT): $(top_srcdir)/internal/set_table.h set.$(OBJEXT): $(top_srcdir)/internal/static_assert.h set.$(OBJEXT): $(top_srcdir)/internal/string.h +set.$(OBJEXT): $(top_srcdir)/internal/struct.h set.$(OBJEXT): $(top_srcdir)/internal/symbol.h set.$(OBJEXT): $(top_srcdir)/internal/variable.h set.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -17321,6 +17350,7 @@ thread.$(OBJEXT): $(top_srcdir)/internal/set_table.h thread.$(OBJEXT): $(top_srcdir)/internal/signal.h thread.$(OBJEXT): $(top_srcdir)/internal/static_assert.h thread.$(OBJEXT): $(top_srcdir)/internal/string.h +thread.$(OBJEXT): $(top_srcdir)/internal/struct.h thread.$(OBJEXT): $(top_srcdir)/internal/thread.h thread.$(OBJEXT): $(top_srcdir)/internal/time.h thread.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -17577,6 +17607,7 @@ time.$(OBJEXT): $(top_srcdir)/internal/serial.h time.$(OBJEXT): $(top_srcdir)/internal/set_table.h time.$(OBJEXT): $(top_srcdir)/internal/static_assert.h time.$(OBJEXT): $(top_srcdir)/internal/string.h +time.$(OBJEXT): $(top_srcdir)/internal/struct.h time.$(OBJEXT): $(top_srcdir)/internal/time.h time.$(OBJEXT): $(top_srcdir)/internal/variable.h time.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -18876,6 +18907,7 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/set_table.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/string.h +vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/struct.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -19109,6 +19141,7 @@ vm_dump.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/set_table.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +vm_dump.$(OBJEXT): $(top_srcdir)/internal/struct.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -19556,6 +19589,7 @@ vm_trace.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/set_table.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/struct.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/symbol.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/thread.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/variable.h @@ -20008,6 +20042,7 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/serial.h yjit.$(OBJEXT): $(top_srcdir)/internal/set_table.h yjit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h yjit.$(OBJEXT): $(top_srcdir)/internal/string.h +yjit.$(OBJEXT): $(top_srcdir)/internal/struct.h yjit.$(OBJEXT): $(top_srcdir)/internal/variable.h yjit.$(OBJEXT): $(top_srcdir)/internal/vm.h yjit.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -20262,6 +20297,7 @@ zjit.$(OBJEXT): $(top_srcdir)/internal/serial.h zjit.$(OBJEXT): $(top_srcdir)/internal/set_table.h zjit.$(OBJEXT): $(top_srcdir)/internal/static_assert.h zjit.$(OBJEXT): $(top_srcdir)/internal/string.h +zjit.$(OBJEXT): $(top_srcdir)/internal/struct.h zjit.$(OBJEXT): $(top_srcdir)/internal/variable.h zjit.$(OBJEXT): $(top_srcdir)/internal/vm.h zjit.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/ext/objspace/depend b/ext/objspace/depend index bc1565aa49c367..04b26eb6c26567 100644 --- a/ext/objspace/depend +++ b/ext/objspace/depend @@ -401,6 +401,7 @@ objspace.o: $(top_srcdir)/internal/sanitizers.h objspace.o: $(top_srcdir)/internal/serial.h objspace.o: $(top_srcdir)/internal/set_table.h objspace.o: $(top_srcdir)/internal/static_assert.h +objspace.o: $(top_srcdir)/internal/struct.h objspace.o: $(top_srcdir)/internal/variable.h objspace.o: $(top_srcdir)/internal/vm.h objspace.o: $(top_srcdir)/internal/warnings.h @@ -617,6 +618,7 @@ objspace_dump.o: $(top_srcdir)/internal/serial.h objspace_dump.o: $(top_srcdir)/internal/set_table.h objspace_dump.o: $(top_srcdir)/internal/static_assert.h objspace_dump.o: $(top_srcdir)/internal/string.h +objspace_dump.o: $(top_srcdir)/internal/struct.h objspace_dump.o: $(top_srcdir)/internal/variable.h objspace_dump.o: $(top_srcdir)/internal/vm.h objspace_dump.o: $(top_srcdir)/internal/warnings.h diff --git a/ext/ripper/depend b/ext/ripper/depend index 0086708d23b0a6..bd2de759065ba2 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -605,6 +605,7 @@ ripper.o: $(top_srcdir)/internal/serial.h ripper.o: $(top_srcdir)/internal/set_table.h ripper.o: $(top_srcdir)/internal/static_assert.h ripper.o: $(top_srcdir)/internal/string.h +ripper.o: $(top_srcdir)/internal/struct.h ripper.o: $(top_srcdir)/internal/symbol.h ripper.o: $(top_srcdir)/internal/thread.h ripper.o: $(top_srcdir)/internal/variable.h diff --git a/gc.c b/gc.c index 9ac68ccb40e09d..e81c004bd88bea 100644 --- a/gc.c +++ b/gc.c @@ -3171,7 +3171,7 @@ gc_mark_classext_iclass(rb_classext_t *ext, bool prime, VALUE box_value, void *a void rb_gc_move_obj_during_marking(VALUE from, VALUE to) { - if (rb_obj_gen_fields_p(to)) { + if (rb_obj_using_gen_fields_table_p(to)) { rb_mark_generic_ivar(from); } } @@ -3181,7 +3181,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) { struct gc_mark_classext_foreach_arg foreach_args; - if (rb_obj_gen_fields_p(obj)) { + if (rb_obj_using_gen_fields_table_p(obj)) { rb_mark_generic_ivar(obj); } diff --git a/shape.h b/shape.h index af6add9e08158a..96c78f2bc1a356 100644 --- a/shape.h +++ b/shape.h @@ -2,6 +2,7 @@ #define RUBY_SHAPE_H #include "internal/gc.h" +#include "internal/struct.h" typedef uint16_t attr_index_t; typedef uint32_t shape_id_t; @@ -452,6 +453,25 @@ rb_obj_gen_fields_p(VALUE obj) return rb_shape_obj_has_fields(obj); } +static inline bool +rb_obj_using_gen_fields_table_p(VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_DATA: + if (RTYPEDDATA_P(obj)) return false; + break; + + case T_STRUCT: + if (!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) return false; + break; + + default: + break; + } + + return rb_obj_gen_fields_p(obj); +} + // For ext/objspace RUBY_SYMBOL_EXPORT_BEGIN typedef void each_shape_callback(shape_id_t shape_id, void *data); From 51ab7b0405e39d6defe0b236e23f43b42aa6c1da Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 9 Jan 2026 09:21:45 +0100 Subject: [PATCH 2395/2435] YJIT: gen_struct_aset check for frozen status --- yjit/src/codegen.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 61de6a32dabe8a..0fc39322fb13b6 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -8947,6 +8947,12 @@ fn gen_struct_aset( return None; } + // If the comptime receiver is frozen, writing a struct member will raise an exception + // and we don't want to JIT code to deal with that situation. + if comptime_recv.is_frozen() { + return None; + } + if c_method_tracing_currently_enabled(jit) { // Struct accesses need fire c_call and c_return events, which we can't support // See :attr-tracing: From e08f316f28b1ce32b154582ce6ac1d2e1471a5b7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 9 Jan 2026 14:25:48 -0500 Subject: [PATCH 2396/2435] YJIT: Add frozen guard for struct aset (#15835) We used to just skip this check (oops), but we should not allow modifying frozen objects. --- test/ruby/test_yjit.rb | 20 ++++++++++++++++++++ yjit/src/codegen.rs | 11 +++++++++++ yjit/src/stats.rs | 1 + 3 files changed, 32 insertions(+) diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 6488661c2d3a6c..53b056d3359c85 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -657,6 +657,26 @@ def foo(obj) RUBY end + def test_struct_aset_guards_recv_is_not_frozen + assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 }) + def foo(obj) + obj.foo = 123 + end + + Foo = Struct.new(:foo) + obj = Foo.new(123) + 100.times do + foo(obj) + end + obj.freeze + begin + foo(obj) + rescue FrozenError + :ok + end + RUBY + end + def test_getblockparam assert_compiles(<<~'RUBY', insns: [:getblockparam]) def foo &blk diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 0fc39322fb13b6..1cf8247a2a905a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -8973,6 +8973,17 @@ fn gen_struct_aset( assert!(unsafe { RB_TYPE_P(comptime_recv, RUBY_T_STRUCT) }); assert!((off as i64) < unsafe { RSTRUCT_LEN(comptime_recv) }); + // Even if the comptime recv was not frozen, future recv may be. So we need to emit a guard + // that the recv is not frozen. + // We know all structs are heap objects, so we can check the flag directly. + let recv = asm.stack_opnd(1); + let recv = asm.load(recv); + let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS)); + asm.test(flags, (RUBY_FL_FREEZE as u64).into()); + asm.jnz(Target::side_exit(Counter::opt_aset_frozen)); + + // Not frozen, so we can proceed. + asm_comment!(asm, "struct aset"); let val = asm.stack_pop(1); diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 84549fa5d34963..105def2fff8577 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -489,6 +489,7 @@ make_counters! { opt_aset_not_array, opt_aset_not_fixnum, opt_aset_not_hash, + opt_aset_frozen, opt_case_dispatch_megamorphic, From 3d242a82a38f60664b5fab815a786901d57c11ca Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 9 Jan 2026 16:36:26 -0600 Subject: [PATCH 2397/2435] [DOC] Harmonize #> methods --- compar.c | 9 ++++++--- hash.c | 5 ++--- numeric.c | 6 ++++-- object.c | 23 ++++++++++++++++------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/compar.c b/compar.c index 0fb7e5f6584e73..142cb12a0cfe77 100644 --- a/compar.c +++ b/compar.c @@ -95,10 +95,13 @@ cmpint(VALUE x, VALUE y) /* * call-seq: - * obj > other -> true or false + * self > other -> true or false * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value greater than 0. + * Returns whether +self+ is "greater than" +other+; + * equivalent to (self <=> other) > 0: + * + * 'foo' > 'foo' # => false + * 'food' > 'foo' # => true */ static VALUE diff --git a/hash.c b/hash.c index 28b729516474a7..c1cc13383b701b 100644 --- a/hash.c +++ b/hash.c @@ -4971,10 +4971,9 @@ rb_hash_ge(VALUE hash, VALUE other) /* * call-seq: - * self > other_hash -> true or false + * self > other -> true or false * - * Returns +true+ if the entries of +self+ are a proper superset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a proper superset of the entries of +other+: * * h = {foo: 0, bar: 1, baz: 2} * h > {foo: 0, bar: 1} # => true # Proper superset. diff --git a/numeric.c b/numeric.c index ef44febe1b29cd..e8df2a6aa0568c 100644 --- a/numeric.c +++ b/numeric.c @@ -1638,7 +1638,8 @@ rb_float_cmp(VALUE x, VALUE y) * call-seq: * self > other -> true or false * - * Returns +true+ if +self+ is numerically greater than +other+: + * Returns whether the value of +self+ is greater than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 > 1 # => true * 2.0 > 1.0 # => true @@ -4958,7 +4959,8 @@ fix_gt(VALUE x, VALUE y) * call-seq: * self > other -> true or false * - * Returns +true+ if the value of +self+ is greater than that of +other+: + * Returns whether the value of +self+ is greater than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 > 0 # => true * 1 > 1 # => false diff --git a/object.c b/object.c index 2b3535158fe305..7dda2b6c0d49cd 100644 --- a/object.c +++ b/object.c @@ -2009,14 +2009,23 @@ rb_mod_ge(VALUE mod, VALUE arg) /* * call-seq: - * mod > other -> true, false, or nil + * self > other -> true, false, or nil * - * Returns true if mod is an ancestor of other. Returns - * false if mod is the same as other - * or mod is a descendant of other. - * Returns nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "B > A".) + * If +self+ is a class, returns +true+ if +self+ is a superclass of +other+, + * returns +false+ if +self+ is the same as +other+ or if +self+ is a subclass + * of +other+, and returns +nil+ if there are no relationship between the two: + * + * Numeric > Float # => true + * Float > Numeric # => false + * Float > Float # => false + * Float > Hash # => nil + * + * If +self+ is a module, returns +true+ if +other+ includes +self+, + * returns +false+ if +self+ is the same as +other+ or if +self+ includes + * +other+, and returns +nil+ if there are no relationship between the two: + * + * Enumerable > Array # => true + * Enumerable > String # => nil * */ From 77cad87dc176273a3b646330e0e9b50e3451a1a9 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 9 Jan 2026 21:06:57 +0000 Subject: [PATCH 2398/2435] [DOC] Doc for Enumerator class --- enumerator.c | 136 ++++++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/enumerator.c b/enumerator.c index 292ddb0419fb8a..d7440204eaed25 100644 --- a/enumerator.c +++ b/enumerator.c @@ -34,79 +34,93 @@ /* * Document-class: Enumerator * - * A class which allows both internal and external iteration. + * \Class \Enumerator supports: * - * An Enumerator can be created by the following methods. - * - Object#to_enum - * - Object#enum_for - * - Enumerator.new + * - {External iteration}[rdoc-ref:Enumerator@External+Iteration]. + * - {Internal iteration}[rdoc-ref:Enumerator@Internal+Iteration]. * - * Most methods have two forms: a block form where the contents - * are evaluated for each item in the enumeration, and a non-block form - * which returns a new Enumerator wrapping the iteration. + * An \Enumerator may be created by the following methods: * - * enumerator = %w(one two three).each - * puts enumerator.class # => Enumerator + * - Object#to_enum. + * - Object#enum_for. + * - Enumerator.new. * - * enumerator.each_with_object("foo") do |item, obj| - * puts "#{obj}: #{item}" - * end + * In addition, certain Ruby methods return \Enumerator objects: + * a Ruby iterator method that accepts a block + * may return an \Enumerator if no block is given. + * There are many such methods, for example, in classes Array and Hash. + * (In the documentation for those classes, search for `new_enumerator`.) * - * # foo: one - * # foo: two - * # foo: three + * == Internal Iteration * - * enum_with_obj = enumerator.each_with_object("foo") - * puts enum_with_obj.class # => Enumerator + * In _internal iteration_, an iterator method drives the iteration + * and the caller's block handles the processing; + * this example uses method #each_with_index: * - * enum_with_obj.each do |item, obj| - * puts "#{obj}: #{item}" - * end + * words = %w[foo bar baz] # => ["foo", "bar", "baz"] + * enumerator = words.each # => # + * enumerator.each_with_index {|word, i| puts "#{i}: #{word}" } + * 0: foo + * 1: bar + * 2: baz * - * # foo: one - * # foo: two - * # foo: three + * Iterator methods in class \Enumerator include: * - * This allows you to chain Enumerators together. For example, you - * can map a list's elements to strings containing the index - * and the element as a string via: + * - #each: + * passes each item to the block. + * - #each_with_index: + * passes each item and its index to the block. + * - #each_with_object (aliased as #with_object): + * passes each item and a given object to the block. + * - #with_index: + * like #each_with_index, but starting at a given offset (instead of zero). * - * puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } - * # => ["0:foo", "1:bar", "2:baz"] + * \Class \Enumerator includes module Enumerable, + * which provides many more iterator methods. * * == External Iteration * - * An Enumerator can also be used as an external iterator. - * For example, Enumerator#next returns the next value of the iterator - * or raises StopIteration if the Enumerator is at the end. - * - * e = [1,2,3].each # returns an enumerator object. - * puts e.next # => 1 - * puts e.next # => 2 - * puts e.next # => 3 - * puts e.next # raises StopIteration - * - * +next+, +next_values+, +peek+, and +peek_values+ are the only methods - * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+ internally). - * - * These methods do not affect other internal enumeration methods, - * unless the underlying iteration method itself has side-effect, e.g. IO#each_line. - * - * FrozenError will be raised if these methods are called against a frozen enumerator. - * Since +rewind+ and +feed+ also change state for external iteration, - * these methods may raise FrozenError too. - * - * External iteration differs *significantly* from internal iteration - * due to using a Fiber: - * - The Fiber adds some overhead compared to internal enumeration. - * - The stacktrace will only include the stack from the Enumerator, not above. - * - Fiber-local variables are *not* inherited inside the Enumerator Fiber, - * which instead starts with no Fiber-local variables. - * - Fiber storage variables *are* inherited and are designed - * to handle Enumerator Fibers. Assigning to a Fiber storage variable - * only affects the current Fiber, so if you want to change state - * in the caller Fiber of the Enumerator Fiber, you need to use an - * extra indirection (e.g., use some object in the Fiber storage + * In _external iteration_, the user's program both drives the iteration + * and handles the processing in stream-like fashion; + * this example uses method #next: + * + * words = %w[foo bar baz] + * enumerator = words.each + * enumerator.next # => "foo" + * enumerator.next # => "bar" + * enumerator.next # => "baz" + * enumerator.next # Raises StopIteration: iteration reached an end + * + * External iteration methods in class \Enumerator include: + * + * - #feed: + * sets the value that is next to be returned. + * - #next: + * returns the next value and increments the position. + * - #next_values: + * returns the next value in a 1-element array and increments the position. + * - #peek: + * returns the next value but does not increment the position. + * - #peek_values: + * returns the next value in a 1-element array but does not increment the position. + * - #rewind: + * sets the position to zero. + * + * Each of these methods raises FrozenError if called from a frozen \Enumerator. + * + * == External Iteration and \Fiber + * + * External iteration that uses Fiber differs *significantly* from internal iteration: + * + * - Using \Fiber adds some overhead compared to internal enumeration. + * - The stacktrace will only include the stack from the \Enumerator, not above. + * - \Fiber-local variables are *not* inherited inside the \Enumerator \Fiber, + * which instead starts with no \Fiber-local variables. + * - \Fiber storage variables *are* inherited and are designed + * to handle \Enumerator Fibers. Assigning to a \Fiber storage variable + * only affects the current \Fiber, so if you want to change state + * in the caller \Fiber of the \Enumerator \Fiber, you need to use an + * extra indirection (e.g., use some object in the \Fiber storage * variable and mutate some ivar of it). * * Concretely: @@ -126,7 +140,7 @@ * e.each { p _1 } * p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber) * - * == Convert External Iteration to Internal Iteration + * == Converting External Iteration to Internal Iteration * * You can use an external iterator to implement an internal iterator as follows: * From ef488bff987dcc1044bd1d98f2aa7013081f66d8 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 9 Jan 2026 22:47:13 +0000 Subject: [PATCH 2399/2435] [DOC] Doc for Enumerator.new --- enumerator.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/enumerator.c b/enumerator.c index d7440204eaed25..b60036a3dedc17 100644 --- a/enumerator.c +++ b/enumerator.c @@ -459,28 +459,31 @@ convert_to_feasible_size_value(VALUE obj) /* * call-seq: - * Enumerator.new(size = nil) { |yielder| ... } + * Enumerator.new(size = nil) {|yielder| ... } * - * Creates a new Enumerator object, which can be used as an - * Enumerable. + * Returns a new \Enumerator object that can be used for iteration. * - * Iteration is defined by the given block, in - * which a "yielder" object, given as block parameter, can be used to - * yield a value by calling the +yield+ method (aliased as <<): + * The given block defines the iteration; + * it is called with a "yielder" object that can yield an object + * via a call to method yielder.yield: * - * fib = Enumerator.new do |y| - * a = b = 1 - * loop do - * y << a - * a, b = b, a + b + * fib = Enumerator.new do |yielder| + * n = next_n = 1 + * while true do + * yielder.yield(n) + * n, next_n = next_n, n + next_n * end * end * * fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] * - * The optional parameter can be used to specify how to calculate the size - * in a lazy fashion (see Enumerator#size). It can either be a value or - * a callable object. + * Parameter +size+ specifies how the size is to be calculated (see #size); + * it can either be a value or a callable object: + * + * Enumerator.new{}.size # => nil + * Enumerator.new(42){}.size # => 42 + * Enumerator.new(-> {42}){}.size # => 42 + * */ static VALUE enumerator_initialize(int argc, VALUE *argv, VALUE obj) From e8c61f513928c411859df1d4fe0c201157d4f129 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 29 Dec 2025 10:19:19 -0500 Subject: [PATCH 2400/2435] Remove ObjectSpace.count_nodes ObjectSpace.count_nodes has been a no-op and returning an empty hash since Ruby 2.5 because parser nodes are not GC managed. --- ext/objspace/objspace.c | 30 ------------------------------ test/objspace/test_objspace.rb | 10 ---------- 2 files changed, 40 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 8b3045fda322c4..ba755b1ad60f71 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -336,35 +336,6 @@ count_symbols(int argc, VALUE *argv, VALUE os) return hash; } -/* - * call-seq: - * ObjectSpace.count_nodes([result_hash]) -> hash - * - * Counts nodes for each node type. - * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: - * - * {:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...} - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * Note: - * The contents of the returned hash is implementation defined. - * It may be changed in future. - * - * This method is only expected to work with C Ruby. - */ - -static VALUE -count_nodes(int argc, VALUE *argv, VALUE os) -{ - return setup_hash(argc, argv); -} - static void cto_i(VALUE v, void *data) { @@ -834,7 +805,6 @@ Init_objspace(void) rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1); rb_define_module_function(rb_mObjSpace, "count_symbols", count_symbols, -1); - rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1); rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1); rb_define_module_function(rb_mObjSpace, "count_imemo_objects", count_imemo_objects, -1); diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 2c0c6195e6ba46..78947a095bf44c 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -76,16 +76,6 @@ def test_count_objects_size_with_wrong_type assert_raise(TypeError) { ObjectSpace.count_objects_size(0) } end - def test_count_nodes - res = ObjectSpace.count_nodes - assert_not_empty(res) - arg = {} - ObjectSpace.count_nodes(arg) - assert_not_empty(arg) - bug8014 = '[ruby-core:53130] [Bug #8014]' - assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014) - end if false - def test_count_tdata_objects res = ObjectSpace.count_tdata_objects assert_not_empty(res) From c3f6fcc4c5d59aa12ce82148502b17b4bbe6877d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 8 Jan 2026 19:17:09 -0500 Subject: [PATCH 2401/2435] Skip ObjectSpaceTest#test_count_nodes for RBS --- tool/rbs_skip_tests | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index 6d9ecb40992e26..4bcb5707a51d9d 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -37,6 +37,9 @@ TestInstanceNetHTTPResponse depending on external resources test_TOPDIR(RbConfigSingletonTest) `TOPDIR` is `nil` during CI while RBS type is declared as `String` +# Failing because ObjectSpace.count_nodes has been removed +test_count_nodes(ObjectSpaceTest) + ## Unknown failures # NoMethodError: undefined method 'inspect' for an instance of RBS::UnitTest::Convertibles::ToInt From 0b69f7fa347e3aadb7998e3f33257e6c83787c8b Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Fri, 9 Jan 2026 16:51:04 +0000 Subject: [PATCH 2402/2435] [ruby/openssl] Fix test_cipher.rb in FIPS. https://github.com/ruby/openssl/commit/11bd2efb2a --- test/openssl/test_cipher.rb | 54 ++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 5b867671d312ad..6a405da0a9763f 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -32,28 +32,28 @@ def test_pkcs5_keyivgen salt = "\x01" * 8 num = 2048 pt = "data to be encrypted" - cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt - cipher.pkcs5_keyivgen(pass, salt, num, "MD5") + cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt + cipher.pkcs5_keyivgen(pass, salt, num, "SHA256") s1 = cipher.update(pt) << cipher.final - d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('MD5', out) } - d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('MD5', out) } - key = (d1 + d2)[0, 24] - iv = (d1 + d2)[24, 8] - cipher = new_encryptor("DES-EDE3-CBC", key: key, iv: iv) + d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) } + d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) } + key = (d1 + d2)[0, 32] + iv = (d1 + d2)[32, 16] + cipher = new_encryptor("AES-256-CBC", key: key, iv: iv) s2 = cipher.update(pt) << cipher.final assert_equal s1, s2 - cipher2 = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt - assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "MD5") } + cipher2 = OpenSSL::Cipher.new("AES-256-CBC").encrypt + assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "SHA256") } end def test_info - cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt - assert_equal "DES-EDE3-CBC", cipher.name - assert_equal 24, cipher.key_len - assert_equal 8, cipher.iv_len + cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt + assert_equal "AES-256-CBC", cipher.name + assert_equal 32, cipher.key_len + assert_equal 16, cipher.iv_len end def test_dup @@ -80,13 +80,13 @@ def test_reset end def test_key_iv_set - cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt - assert_raise(ArgumentError) { cipher.key = "\x01" * 23 } - assert_nothing_raised { cipher.key = "\x01" * 24 } - assert_raise(ArgumentError) { cipher.key = "\x01" * 25 } - assert_raise(ArgumentError) { cipher.iv = "\x01" * 7 } - assert_nothing_raised { cipher.iv = "\x01" * 8 } - assert_raise(ArgumentError) { cipher.iv = "\x01" * 9 } + cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt + assert_raise(ArgumentError) { cipher.key = "\x01" * 31 } + assert_nothing_raised { cipher.key = "\x01" * 32 } + assert_raise(ArgumentError) { cipher.key = "\x01" * 33 } + assert_raise(ArgumentError) { cipher.iv = "\x01" * 15 } + assert_nothing_raised { cipher.iv = "\x01" * 16 } + assert_raise(ArgumentError) { cipher.iv = "\x01" * 17 } end def test_random_key_iv @@ -109,8 +109,8 @@ def test_random_key_iv end def test_initialize - cipher = OpenSSL::Cipher.new("DES-EDE3-CBC") - assert_raise(RuntimeError) { cipher.__send__(:initialize, "DES-EDE3-CBC") } + cipher = OpenSSL::Cipher.new("AES-256-CBC") + assert_raise(RuntimeError) { cipher.__send__(:initialize, "AES-256-CBC") } assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final } assert_raise(OpenSSL::Cipher::CipherError) { OpenSSL::Cipher.new("no such algorithm") @@ -169,12 +169,12 @@ def test_AES %w(ecb cbc cfb ofb).each{|mode| c1 = OpenSSL::Cipher.new("aes-256-#{mode}") c1.encrypt - c1.pkcs5_keyivgen("passwd") + c1.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256") ct = c1.update(pt) + c1.final c2 = OpenSSL::Cipher.new("aes-256-#{mode}") c2.decrypt - c2.pkcs5_keyivgen("passwd") + c2.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256") assert_equal(pt, c2.update(ct) + c2.final) } end @@ -313,6 +313,9 @@ def test_aes_gcm_variable_iv_len end def test_aes_ocb_tag_len + # AES-128-OCB is not FIPS-approved. + omit_on_fips + # RFC 7253 Appendix A; the second sample key = ["000102030405060708090A0B0C0D0E0F"].pack("H*") iv = ["BBAA99887766554433221101"].pack("H*") @@ -347,6 +350,9 @@ def test_aes_ocb_tag_len end if has_cipher?("aes-128-ocb") def test_aes_gcm_siv + # AES-128-GCM-SIV is not FIPS-approved. + omit_on_fips + # RFC 8452 Appendix C.1., 8th example key = ["01000000000000000000000000000000"].pack("H*") iv = ["030000000000000000000000"].pack("H*") From 98c414a678c2497dc522abf3802b2f78bfc856c4 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 9 Jan 2026 17:48:27 -0500 Subject: [PATCH 2403/2435] [DOC] Improve docs for Module#> --- object.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/object.c b/object.c index 7dda2b6c0d49cd..c3128597762c12 100644 --- a/object.c +++ b/object.c @@ -2011,20 +2011,23 @@ rb_mod_ge(VALUE mod, VALUE arg) * call-seq: * self > other -> true, false, or nil * - * If +self+ is a class, returns +true+ if +self+ is a superclass of +other+, - * returns +false+ if +self+ is the same as +other+ or if +self+ is a subclass - * of +other+, and returns +nil+ if there are no relationship between the two: + * Returns +true+ if +self+ is an ancestor of +other+ + * (+self+ is a superclass of +other+ or +self+ is included in +other+): * - * Numeric > Float # => true - * Float > Numeric # => false - * Float > Float # => false - * Float > Hash # => nil + * Numeric > Float # => true + * Enumerable > Array # => true * - * If +self+ is a module, returns +true+ if +other+ includes +self+, - * returns +false+ if +self+ is the same as +other+ or if +self+ includes - * +other+, and returns +nil+ if there are no relationship between the two: + * Returns +false+ if +self+ is a descendant of +other+ + * (+self+ is a subclass of +other+ or +self+ includes +other+) or + * if +self+ is the same as +other+: * - * Enumerable > Array # => true + * Float > Numeric # => false + * Array > Enumerable # => false + * Float > Float # => false + * + * Returns +nil+ if there is no relationship between the two: + * + * Float > Hash # => nil * Enumerable > String # => nil * */ From 0b83346f1c1f6e54c344307c2ec5f9d0a6f80317 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 9 Jan 2026 17:54:22 -0500 Subject: [PATCH 2404/2435] [DOC] Improve docs for Module#< --- object.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/object.c b/object.c index c3128597762c12..8ec0dc50a6c25e 100644 --- a/object.c +++ b/object.c @@ -1967,13 +1967,24 @@ rb_class_inherited_p(VALUE mod, VALUE arg) * call-seq: * self < other -> true, false, or nil * - * Returns whether +self+ is a subclass of +other+, - * or +nil+ if there is no relationship between the two: + * Returns +true+ if +self+ is a descendant of +other+ + * (+self+ is a subclass of +other+ or +self+ includes +other+): * - * Float < Numeric # => true - * Numeric < Float # => false - * Float < Float # => false - * Float < Hash # => nil + * Float < Numeric # => true + * Array < Enumerable # => true + * + * Returns +false+ if +self+ is an ancestor of +other+ + * (+self+ is a superclass of +other+ or +self+ is included in +other+) or + * if +self+ is the same as +other+: + * + * Numeric < Float # => false + * Enumerable < Array # => false + * Float < Float # => false + * + * Returns +nil+ if there is no relationship between the two: + * + * Float < Hash # => nil + * Enumerable < String # => nil * */ From f0f4a683b4dddf870491607396a564dec55a4d6e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 9 Jan 2026 17:56:47 -0500 Subject: [PATCH 2405/2435] [DOC] Improve docs for Module#<= --- object.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/object.c b/object.c index 8ec0dc50a6c25e..0238fefbfeb678 100644 --- a/object.c +++ b/object.c @@ -1915,11 +1915,24 @@ rb_mod_eqq(VALUE mod, VALUE arg) * call-seq: * mod <= other -> true, false, or nil * - * Returns true if mod is a subclass of other or - * is the same as other. Returns - * nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "A < B".) + * Returns +true+ if +self+ is a descendant of +other+ + * (+self+ is a subclass of +other+ or +self+ includes +other+) or + * if +self+ is the same as +other+: + * + * Float <= Numeric # => true + * Array <= Enumerable # => true + * Float <= Float # => true + * + * Returns +false+ if +self+ is an ancestor of +other+ + * (+self+ is a superclass of +other+ or +self+ is included in +other+): + * + * Numeric <= Float # => false + * Enumerable <= Array # => false + * + * Returns +nil+ if there is no relationship between the two: + * + * Float <= Hash # => nil + * Enumerable <= String # => nil */ VALUE From 0d4538b57d5f835af27d8d29464c32dbdf4593f3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 9 Jan 2026 18:00:27 -0500 Subject: [PATCH 2406/2435] [DOC] Improve docs for Module#>= --- object.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/object.c b/object.c index 0238fefbfeb678..75186a30c66868 100644 --- a/object.c +++ b/object.c @@ -2013,11 +2013,24 @@ rb_mod_lt(VALUE mod, VALUE arg) * call-seq: * mod >= other -> true, false, or nil * - * Returns true if mod is an ancestor of other, or the - * two modules are the same. Returns - * nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "B > A".) + * Returns +true+ if +self+ is an ancestor of +other+ + * (+self+ is a superclass of +other+ or +self+ is included in +other+) or + * if +self+ is the same as +other+: + * + * Numeric >= Float # => true + * Enumerable >= Array # => true + * Float >= Float # => true + * + * Returns +false+ if +self+ is a descendant of +other+ + * (+self+ is a subclass of +other+ or +self+ includes +other+): + * + * Float >= Numeric # => false + * Array >= Enumerable # => false + * + * Returns +nil+ if there is no relationship between the two: + * + * Float >= Hash # => nil + * Enumerable >= String # => nil * */ From acd0c68a074cd47fba486d4d77895e2709be873a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Jan 2026 14:51:49 +0900 Subject: [PATCH 2407/2435] Relax the flaky test threshold on RHEL 10.0 x86_64 --- test/ruby/test_gc_compact.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index 84828d498543aa..7e0c499dd9c82c 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -324,7 +324,7 @@ def test_moving_arrays_up_heaps }.resume stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) - assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, ARY_COUNT - 15) + assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, (0.9995 * ARY_COUNT).to_i) refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) end; end From 463a806fb10e4631012d616a3133b4f8cce7938f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Jan 2026 15:12:58 +0900 Subject: [PATCH 2408/2435] Fix `VCS::GIT#branch_beginning` to search since the last relase The ChangeLog in ruby-4.0.0 did not contain entries for 3.5.0. --- tool/lib/vcs.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 4f6dc0043283e8..6ebfb9e93b81ad 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -448,9 +448,11 @@ def export(revision, url, dir, keep_temp = false) end def branch_beginning(url) - cmd_read(%W[ #{COMMAND} log -n1 --format=format:%H + files = %w[version.h include/ruby/version.h] + year = cmd_read(%W[ #{COMMAND} log -n1 --format=%cd --date=format:%Y #{url} --] + files).to_i + cmd_read(%W[ #{COMMAND} log --format=format:%H --reverse --since=#{year-1}-12-25 --author=matz --committer=matz --grep=started\\.$ - #{url.to_str} -- version.h include/ruby/version.h]) + #{url} --] + files)[/.*/] end def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, base_url: true) From 6a630d992ea6101bc2656f75954214f7e5babf7a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Jan 2026 17:09:40 +0900 Subject: [PATCH 2409/2435] Push the commits to the origin at `make matz` [ci skip] --- defs/gmake.mk | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index e6f553fa8c1b5d..2fc0ff8733af63 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -541,24 +541,25 @@ ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) # Just update the version in the title of NEWS.md. matz: up -matz: OLD := $(MAJOR).$(MINOR).0 +matz-commit: OLD := $(MAJOR).$(MINOR).0 ifdef NEW -matz: MAJOR := $(word 1,$(subst ., ,$(NEW))) -matz: MINOR := $(word 2,$(subst ., ,$(NEW))) -matz: $(DOT_WAIT) bump_news +matz-commit: MAJOR := $(word 1,$(subst ., ,$(NEW))) +matz-commit: MINOR := $(word 2,$(subst ., ,$(NEW))) +matz-commit: $(DOT_WAIT) bump_news bump_news$(DOT_WAIT): up bump_headers$(DOT_WAIT): bump_news else -matz: MINOR := $(shell expr $(MINOR) + 1) -matz: $(DOT_WAIT) reset_news +matz-commit: MINOR := $(shell expr $(MINOR) + 1) +matz-commit: $(DOT_WAIT) reset_news flush_news$(DOT_WAIT): up bump_headers$(DOT_WAIT): reset_news endif -matz: $(DOT_WAIT) bump_headers -matz: override NEW := $(MAJOR).$(MINOR).0 -matz: files := include/ruby/version.h include/ruby/internal/abi.h -matz: message := Development of $(NEW) started. +matz: $(DOT_WAIT) matz-commit +matz-commit: bump_headers +matz-commit: override NEW := $(MAJOR).$(MINOR).0 +matz-commit: files := include/ruby/version.h include/ruby/internal/abi.h +matz-commit: message := Development of $(NEW) started. flush_news: $(GIT_IN_SRC) mv -f NEWS.md doc/NEWS/NEWS-$(OLD).md @@ -589,10 +590,23 @@ bump_news: -e 'BEGIN {new = ARGV.shift; print gets("").sub(/Ruby \K[0-9.]+/, new)}' \ $(NEW) NEWS.md -matz: +matz: matz-commit matz-push + +matz-commit: $(GIT_IN_SRC) add NEWS.md $(files) $(GIT_IN_SRC) commit -m "$(message)" +GIT_REMOTE_ORIGIN = origin + +matz-push: matz-commit + $(eval origin_url := $(shell $(GIT_IN_SRC) remote get-url $(GIT_REMOTE_ORIGIN))) + $(if $(origin_url),,@false) + $(eval last_commit := $(shell $(GIT_IN_SRC) log -n1 --format=%H --author=matz HEAD~..HEAD)) + $(if $(last_commit),,$(ECHO) No matz commits 1>&2; false) + $(if $(filter 12-25 12-26,$(shell date +%m-%d)),,$(ECHO) Not the release date 1>&2; false) + $(ECHO) $$'\e[31m'Pushing to $$'\e[7m'$(GIT_REMOTE_ORIGIN)$$'\e[27m'" ($(origin_url))"$$'\e[m' + $(GIT_IN_SRC) push $(GIT_REMOTE_ORIGIN) + tags: $(MAKE) GIT="$(GIT)" -C "$(srcdir)" -f defs/tags.mk From 73be9992e93072be803ffd5173e29dcf597e04ef Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 11 Jan 2026 09:54:18 +0100 Subject: [PATCH 2410/2435] Disambiguate private and public RSTRUCT_ helpers RSTRUCT_LEN / RSTRUCT_GET / RSTRUCT_SET all existing in two versions, one public that does type and frozens checks and one private that doesn't. The problem is that this is error prone because the public version is always accessible, but the private one require to include `internal/struct.h`. So you may have some code that rely on the public version, and later on the private header is included and changes the behavior. This already led to introducing a bug in YJIT & ZJIT: https://github.com/ruby/ruby/pull/15835 --- gc.c | 5 ++- internal/range.h | 6 ++-- internal/struct.h | 38 +++++--------------- marshal.c | 4 +-- ractor.c | 4 +-- struct.c | 88 +++++++++++++++++++++++------------------------ vm_insnhelper.c | 4 +-- 7 files changed, 64 insertions(+), 85 deletions(-) diff --git a/gc.c b/gc.c index e81c004bd88bea..1fe3dbf0aebb4b 100644 --- a/gc.c +++ b/gc.c @@ -2461,9 +2461,8 @@ rb_obj_memsize_of(VALUE obj) break; case T_STRUCT: - if ((RBASIC(obj)->flags & RSTRUCT_EMBED_LEN_MASK) == 0 && - RSTRUCT(obj)->as.heap.ptr) { - size += sizeof(VALUE) * RSTRUCT_LEN(obj); + if (RSTRUCT_EMBED_LEN(obj) == 0) { + size += sizeof(VALUE) * RSTRUCT_LEN_RAW(obj); } break; diff --git a/internal/range.h b/internal/range.h index 2394937bf87461..80493ce13e4008 100644 --- a/internal/range.h +++ b/internal/range.h @@ -18,19 +18,19 @@ static inline VALUE RANGE_EXCL(VALUE r); static inline VALUE RANGE_BEG(VALUE r) { - return RSTRUCT(r)->as.ary[0]; + return RSTRUCT_GET_RAW(r, 0); } static inline VALUE RANGE_END(VALUE r) { - return RSTRUCT_GET(r, 1); + return RSTRUCT_GET_RAW(r, 1); } static inline VALUE RANGE_EXCL(VALUE r) { - return RSTRUCT_GET(r, 2); + return RSTRUCT_GET_RAW(r, 2); } VALUE diff --git a/internal/struct.h b/internal/struct.h index 337f96a336d796..d3c81573931cfa 100644 --- a/internal/struct.h +++ b/internal/struct.h @@ -49,36 +49,16 @@ struct RStruct { #define RSTRUCT(obj) ((struct RStruct *)(obj)) -#ifdef RSTRUCT_LEN -# undef RSTRUCT_LEN -#endif - -#ifdef RSTRUCT_PTR -# undef RSTRUCT_PTR -#endif - -#ifdef RSTRUCT_SET -# undef RSTRUCT_SET -#endif - -#ifdef RSTRUCT_GET -# undef RSTRUCT_GET -#endif - -#define RSTRUCT_LEN internal_RSTRUCT_LEN -#define RSTRUCT_SET internal_RSTRUCT_SET -#define RSTRUCT_GET internal_RSTRUCT_GET - /* struct.c */ VALUE rb_struct_init_copy(VALUE copy, VALUE s); VALUE rb_struct_lookup(VALUE s, VALUE idx); VALUE rb_struct_s_keyword_init(VALUE klass); static inline long RSTRUCT_EMBED_LEN(VALUE st); -static inline long RSTRUCT_LEN(VALUE st); +static inline long RSTRUCT_LEN_RAW(VALUE st); static inline int RSTRUCT_LENINT(VALUE st); static inline const VALUE *RSTRUCT_CONST_PTR(VALUE st); -static inline void RSTRUCT_SET(VALUE st, long k, VALUE v); -static inline VALUE RSTRUCT_GET(VALUE st, long k); +static inline void RSTRUCT_SET_RAW(VALUE st, long k, VALUE v); +static inline VALUE RSTRUCT_GET_RAW(VALUE st, long k); static inline long RSTRUCT_EMBED_LEN(VALUE st) @@ -89,7 +69,7 @@ RSTRUCT_EMBED_LEN(VALUE st) } static inline long -RSTRUCT_LEN(VALUE st) +RSTRUCT_LEN_RAW(VALUE st) { if (FL_TEST_RAW(st, RSTRUCT_EMBED_LEN_MASK)) { return RSTRUCT_EMBED_LEN(st); @@ -102,7 +82,7 @@ RSTRUCT_LEN(VALUE st) static inline int RSTRUCT_LENINT(VALUE st) { - return rb_long2int(RSTRUCT_LEN(st)); + return rb_long2int(RSTRUCT_LEN_RAW(st)); } static inline const VALUE * @@ -119,13 +99,13 @@ RSTRUCT_CONST_PTR(VALUE st) } static inline void -RSTRUCT_SET(VALUE st, long k, VALUE v) +RSTRUCT_SET_RAW(VALUE st, long k, VALUE v) { RB_OBJ_WRITE(st, &RSTRUCT_CONST_PTR(st)[k], v); } static inline VALUE -RSTRUCT_GET(VALUE st, long k) +RSTRUCT_GET_RAW(VALUE st, long k) { return RSTRUCT_CONST_PTR(st)[k]; } @@ -137,7 +117,7 @@ RSTRUCT_FIELDS_OBJ(VALUE st) VALUE fields_obj; if (embed_len) { RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS)); - fields_obj = RSTRUCT_GET(st, embed_len); + fields_obj = RSTRUCT_GET_RAW(st, embed_len); } else { fields_obj = RSTRUCT(st)->as.heap.fields_obj; @@ -151,7 +131,7 @@ RSTRUCT_SET_FIELDS_OBJ(VALUE st, VALUE fields_obj) const long embed_len = RSTRUCT_EMBED_LEN(st); if (embed_len) { RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS)); - RSTRUCT_SET(st, embed_len, fields_obj); + RSTRUCT_SET_RAW(st, embed_len, fields_obj); } else { RB_OBJ_WRITE(st, &RSTRUCT(st)->as.heap.fields_obj, fields_obj); diff --git a/marshal.c b/marshal.c index 8cd4dc6079a39a..9d9a83097ce267 100644 --- a/marshal.c +++ b/marshal.c @@ -1084,7 +1084,7 @@ w_object(VALUE obj, struct dump_arg *arg, int limit) case T_STRUCT: w_class(TYPE_STRUCT, obj, arg, TRUE); { - long len = RSTRUCT_LEN(obj); + long len = RSTRUCT_LEN_RAW(obj); VALUE mem; long i; @@ -1092,7 +1092,7 @@ w_object(VALUE obj, struct dump_arg *arg, int limit) mem = rb_struct_members(obj); for (i=0; iself); - RSTRUCT_SET(args->self, i, val); + RSTRUCT_SET_RAW(args->self, i, val); } return ST_CONTINUE; } @@ -782,7 +782,7 @@ rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self) rb_raise(rb_eArgError, "struct size differs"); } for (long i=0; i argc) { rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self)+argc, n-argc); @@ -917,8 +917,8 @@ rb_struct_each(VALUE s) long i; RETURN_SIZED_ENUMERATOR(s, 0, 0, struct_enum_size); - for (i=0; i"); @@ -1051,7 +1051,7 @@ rb_struct_inspect(VALUE s) static VALUE rb_struct_to_a(VALUE s) { - return rb_ary_new4(RSTRUCT_LEN(s), RSTRUCT_CONST_PTR(s)); + return rb_ary_new4(RSTRUCT_LEN_RAW(s), RSTRUCT_CONST_PTR(s)); } /* @@ -1080,13 +1080,13 @@ rb_struct_to_a(VALUE s) static VALUE rb_struct_to_h(VALUE s) { - VALUE h = rb_hash_new_with_size(RSTRUCT_LEN(s)); + VALUE h = rb_hash_new_with_size(RSTRUCT_LEN_RAW(s)); VALUE members = rb_struct_members(s); long i; int block_given = rb_block_given_p(); - for (i=0; icc)->def->body.optimized.type == OPTIMIZED_METHOD_TYPE_STRUCT_AREF); const unsigned int off = vm_cc_cme(calling->cc)->def->body.optimized.index; - return internal_RSTRUCT_GET(recv, off); + return RSTRUCT_GET_RAW(recv, off); } static VALUE @@ -4789,7 +4789,7 @@ vm_call_opt_struct_aset0(rb_execution_context_t *ec, struct rb_calling_info *cal rb_check_frozen(recv); const unsigned int off = vm_cc_cme(calling->cc)->def->body.optimized.index; - internal_RSTRUCT_SET(recv, off, val); + RSTRUCT_SET_RAW(recv, off, val); return val; } From 3363861a5af679e7027bdd744fc05d7a797ba33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heath=20Dutton=F0=9F=95=B4=EF=B8=8F?= Date: Sat, 10 Jan 2026 22:28:07 -0500 Subject: [PATCH 2411/2435] Remove RUBY_API_VERSION check in cxxanyargs.hpp The version check used undefined macros, causing -Wundef warnings. The conditional is no longer needed as Ruby is past version 3.1. --- include/ruby/backward/cxxanyargs.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/ruby/backward/cxxanyargs.hpp b/include/ruby/backward/cxxanyargs.hpp index d37495dd942790..0ca2745c206969 100644 --- a/include/ruby/backward/cxxanyargs.hpp +++ b/include/ruby/backward/cxxanyargs.hpp @@ -510,9 +510,7 @@ struct driver { * this writing the version is 2.8. Let's warn this later, some time * during 3.x. Hopefully codes in old (ANYARGS-ed) format should be * less than now. */ -#if (RUBY_API_VERSION_MAJOR * 100 + RUBY_API_VERSION_MINOR) >= 301 RUBY_CXX_DEPRECATED("use of ANYARGS is deprecated") -#endif /// @copydoc define(VALUE klass, T mid, U func) /// @deprecated Pass correctly typed function instead. static inline void From a071078e907af99db737799410d713d5ec953eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Hasi=C5=84ski?= Date: Sun, 11 Jan 2026 03:45:44 +0100 Subject: [PATCH 2412/2435] pack.c: remove wasted allocation in BER integer packing The 'w' format (BER compressed integer) was allocating an empty string with rb_str_new(0, 0) then immediately overwriting it with the correctly-sized allocation. Remove the wasted first allocation. ~50% improvement on BER pack benchmarks. --- pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pack.c b/pack.c index 87a5c6787134c3..2ef826b0e2a17f 100644 --- a/pack.c +++ b/pack.c @@ -736,7 +736,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) case 'w': /* BER compressed integer */ while (len-- > 0) { - VALUE buf = rb_str_new(0, 0); + VALUE buf; size_t numbytes; int sign; char *cp; From 8baaece6d9a64e8cc51d8d0c9839546e3d039eee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Jan 2026 22:46:06 +0900 Subject: [PATCH 2413/2435] [ruby/prism] [Bug #21831] Fix denominator of rational float literal Denominators can contain underscores in fraction part as well as other numeric literals. [Bug #21831]: https://bugs.ruby-lang.org/issues/21831 https://github.com/ruby/prism/commit/e247cb58c7 --- prism/prism.c | 8 ++++++-- test/prism/result/numeric_value_test.rb | 11 +++++++++++ test/ruby/test_literal.rb | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 1a8cdf7568fe98..b36a6da20493ba 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -3962,9 +3962,13 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { memcpy(digits + (point - start), point + 1, (unsigned long) (end - point - 1)); pm_integer_parse(&node->numerator, PM_INTEGER_BASE_DEFAULT, digits, digits + length - 1); + size_t fract_length = 0; + for (const uint8_t *fract = point; fract < end; ++fract) { + if (*fract != '_') ++fract_length; + } digits[0] = '1'; - if (end - point > 1) memset(digits + 1, '0', (size_t) (end - point - 1)); - pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + (end - point)); + if (fract_length > 1) memset(digits + 1, '0', fract_length - 1); + pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + fract_length); xfree(digits); pm_integers_reduce(&node->numerator, &node->denominator); diff --git a/test/prism/result/numeric_value_test.rb b/test/prism/result/numeric_value_test.rb index 5c89230a1fee41..0207fa6a864917 100644 --- a/test/prism/result/numeric_value_test.rb +++ b/test/prism/result/numeric_value_test.rb @@ -6,16 +6,27 @@ module Prism class NumericValueTest < TestCase def test_numeric_value assert_equal 123, Prism.parse_statement("123").value + assert_equal 123, Prism.parse_statement("1_23").value assert_equal 3.14, Prism.parse_statement("3.14").value + assert_equal 3.14, Prism.parse_statement("3.1_4").value assert_equal 42i, Prism.parse_statement("42i").value + assert_equal 42i, Prism.parse_statement("4_2i").value assert_equal 42.1ri, Prism.parse_statement("42.1ri").value + assert_equal 42.1ri, Prism.parse_statement("42.1_0ri").value assert_equal 3.14i, Prism.parse_statement("3.14i").value + assert_equal 3.14i, Prism.parse_statement("3.1_4i").value assert_equal 42r, Prism.parse_statement("42r").value + assert_equal 42r, Prism.parse_statement("4_2r").value assert_equal 0.5r, Prism.parse_statement("0.5r").value + assert_equal 0.5r, Prism.parse_statement("0.5_0r").value assert_equal 42ri, Prism.parse_statement("42ri").value + assert_equal 42ri, Prism.parse_statement("4_2ri").value assert_equal 0.5ri, Prism.parse_statement("0.5ri").value + assert_equal 0.5ri, Prism.parse_statement("0.5_0ri").value assert_equal 0xFFr, Prism.parse_statement("0xFFr").value + assert_equal 0xFFr, Prism.parse_statement("0xF_Fr").value assert_equal 0xFFri, Prism.parse_statement("0xFFri").value + assert_equal 0xFFri, Prism.parse_statement("0xF_Fri").value end end end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index dbff3c4734275d..cff888d4b3a5d4 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -682,6 +682,11 @@ def test_float $VERBOSE = verbose_bak end + def test_rational_float + assert_equal(12, 0.12r * 100) + assert_equal(12, 0.1_2r * 100) + end + def test_symbol_list assert_equal([:foo, :bar], %i[foo bar]) assert_equal([:"\"foo"], %i["foo]) From cf5c5abe1e7057532d1c945372b68d55cfaf163c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 6 Jan 2026 17:24:45 -0500 Subject: [PATCH 2414/2435] [DOC] Improve docs for ObjectSpace.count_tdata_objects --- ext/objspace/objspace.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index ba755b1ad60f71..1c265c722ab51c 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -365,32 +365,22 @@ cto_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_tdata_objects([result_hash]) -> hash + * ObjectSpace.count_tdata_objects(result_hash = nil) -> hash * - * Counts objects for each +T_DATA+ type. + * Returns a hash containing the number of objects for each +T_DATA+ type. + * The keys are Class objects when the +T_DATA+ object has an associated class, + * or Symbol objects of the name defined in the +rb_data_type_struct+ for internal + * +T_DATA+ objects. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: - * - * {RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6, - * :mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99, - * ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1, - * Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2} - * # T_DATA objects existing at startup on r32276. - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * The contents of the returned hash is implementation specific and may change - * in the future. + * ObjectSpace.count_tdata_objects + * # => {RBS::Location => 39255, marshal_compat_table: 1, Encoding => 103, mutex: 1, ... } * - * In this version, keys are Class object or Symbol object. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * If object is kind of normal (accessible) object, the key is Class object. - * If object is not a kind of normal (internal) object, the key is symbol - * name, registered by rb_data_type_struct. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ From 1267a3ab565523f5181cd33321897e47944e5771 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 6 Jan 2026 19:06:13 -0500 Subject: [PATCH 2415/2435] [DOC] Improve docs for ObjectSpace.count_imemo_objects --- ext/objspace/objspace.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 1c265c722ab51c..0f55c33a5f9b88 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -419,28 +419,22 @@ count_imemo_objects_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_imemo_objects([result_hash]) -> hash + * ObjectSpace.count_imemo_objects(result_hash = nil) -> hash * - * Counts objects for each +T_IMEMO+ type. + * Returns a hash containing the number of objects for each +T_IMEMO+ type. + * The keys are Symbol objects of the +T_IMEMO+ type name. + * +T_IMEMO+ objects are Ruby internal objects that are not visible to Ruby + * programs. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: + * ObjectSpace.count_imemo_objects + * # => {imemo_callcache: 5482, imemo_constcache: 1258, imemo_ment: 13906, ... } * - * {:imemo_ifunc=>8, - * :imemo_svar=>7, - * :imemo_cref=>509, - * :imemo_memo=>1, - * :imemo_throw_data=>1} - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * The contents of the returned hash is implementation specific and may change - * in the future. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * In this version, keys are symbol objects. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ From 61c372a1b7fe045adc9b67196503f29b79bff376 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 7 Jan 2026 18:10:10 -0500 Subject: [PATCH 2416/2435] [DOC] Improve docs for ObjectSpace.count_symbols --- ext/objspace/objspace.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 0f55c33a5f9b88..b12f5cb7c8c80e 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -295,28 +295,27 @@ size_t rb_sym_immortal_count(void); /* * call-seq: - * ObjectSpace.count_symbols([result_hash]) -> hash + * ObjectSpace.count_symbols(result_hash = nil) -> hash * - * Counts symbols for each Symbol type. + * Returns a hash containing the number of objects for each Symbol type. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. + * The types of Symbols are the following: * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. + * - +mortal_dynamic_symbol+: Symbols that are garbage collectable. + * - +immortal_dynamic_symbol+: Symbols that are objects allocated from the + * garbage collector, but are not garbage collectable. + * - +immortal_static_symbol+: Symbols that are not allocated from the + * garbage collector, and are thus not garbage collectable. + * - +immortal_symbol+: the sum of +immortal_dynamic_symbol+ and +immortal_static_symbol+. * - * Note: - * The contents of the returned hash is implementation defined. - * It may be changed in future. - * - * This method is only expected to work with C Ruby. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * On this version of MRI, they have 3 types of Symbols (and 1 total counts). + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * - * * mortal_dynamic_symbol: GC target symbols (collected by GC) - * * immortal_dynamic_symbol: Immortal symbols promoted from dynamic symbols (do not collected by GC) - * * immortal_static_symbol: Immortal symbols (do not collected by GC) - * * immortal_symbol: total immortal symbols (immortal_dynamic_symbol+immortal_static_symbol) + * This method is only expected to work with C Ruby. */ static VALUE From d57c3296a9f49dfa2c853194e4267edd794d405b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Jan 2026 11:21:35 +0900 Subject: [PATCH 2417/2435] Re-fix `VCS::GIT#branch_beginning` Count the last release date from the whole working directory, not only the version headers. --- tool/lib/vcs.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 6ebfb9e93b81ad..26c9763c13cc11 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -448,11 +448,10 @@ def export(revision, url, dir, keep_temp = false) end def branch_beginning(url) - files = %w[version.h include/ruby/version.h] - year = cmd_read(%W[ #{COMMAND} log -n1 --format=%cd --date=format:%Y #{url} --] + files).to_i + year = cmd_read(%W[ #{COMMAND} log -n1 --format=%cd --date=format:%Y #{url} --]).to_i cmd_read(%W[ #{COMMAND} log --format=format:%H --reverse --since=#{year-1}-12-25 --author=matz --committer=matz --grep=started\\.$ - #{url} --] + files)[/.*/] + #{url} -- version.h include/ruby/version.h])[/.*/] end def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, base_url: true) @@ -460,7 +459,7 @@ def export_changelog(url = '@', from = nil, to = nil, _path = nil, path: _path, rev or next rev unless rev.empty? end - unless (from && /./.match(from)) or ((from = branch_beginning(url)) && /./.match(from)) + unless from&.match?(/./) or (from = branch_beginning(url))&.match?(/./) warn "no starting commit found", uplevel: 1 from = nil end From 916c0a810552b4c2c9a7fce2a50dc8c6efd5bb2f Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 11 Jan 2026 10:29:32 +0100 Subject: [PATCH 2418/2435] ZJIT: remove unused rb_RSTRUCT_SET() --- zjit/bindgen/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 798a460c1980ac..4af4886384a1e9 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -414,7 +414,6 @@ fn main() { .allowlist_function("rb_RB_TYPE_P") .allowlist_function("rb_BASIC_OP_UNREDEFINED_P") .allowlist_function("rb_RSTRUCT_LEN") - .allowlist_function("rb_RSTRUCT_SET") .allowlist_function("rb_vm_ci_argc") .allowlist_function("rb_vm_ci_mid") .allowlist_function("rb_vm_ci_flag") From bf36ad9c12481461031a2dbde39e27d96e5a86e4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 11 Jan 2026 10:37:30 +0100 Subject: [PATCH 2419/2435] ZJIT: remove unused rb_RSTRUCT_LEN() --- zjit/bindgen/src/main.rs | 1 - zjit/src/cruby.rs | 1 - zjit/src/cruby_bindings.inc.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 4af4886384a1e9..794293d1d321c7 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -413,7 +413,6 @@ fn main() { .allowlist_function("rb_FL_TEST_RAW") .allowlist_function("rb_RB_TYPE_P") .allowlist_function("rb_BASIC_OP_UNREDEFINED_P") - .allowlist_function("rb_RSTRUCT_LEN") .allowlist_function("rb_vm_ci_argc") .allowlist_function("rb_vm_ci_mid") .allowlist_function("rb_vm_ci_flag") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 57a3bee7e01d8c..51faaab9c24658 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -198,7 +198,6 @@ pub use rb_FL_TEST as FL_TEST; pub use rb_FL_TEST_RAW as FL_TEST_RAW; pub use rb_RB_TYPE_P as RB_TYPE_P; pub use rb_BASIC_OP_UNREDEFINED_P as BASIC_OP_UNREDEFINED_P; -pub use rb_RSTRUCT_LEN as RSTRUCT_LEN; pub use rb_vm_ci_argc as vm_ci_argc; pub use rb_vm_ci_mid as vm_ci_mid; pub use rb_vm_ci_flag as vm_ci_flag; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index efb1559fb75512..5d4fed0c3ac18d 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2153,7 +2153,6 @@ unsafe extern "C" { pub fn rb_FL_TEST(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_FL_TEST_RAW(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_RB_TYPE_P(obj: VALUE, t: ruby_value_type) -> bool; - pub fn rb_RSTRUCT_LEN(st: VALUE) -> ::std::os::raw::c_long; pub fn rb_get_call_data_ci(cd: *const rb_call_data) -> *const rb_callinfo; pub fn rb_BASIC_OP_UNREDEFINED_P(bop: ruby_basic_operators, klass: u32) -> bool; pub fn rb_RCLASS_ORIGIN(c: VALUE) -> VALUE; From 6484a71a4fee654e73f377a01db331500222dd13 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Jan 2026 10:23:10 +0900 Subject: [PATCH 2420/2435] Add Onigmo to sync_default_gems.rb --- tool/sync_default_gems.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 6945c6cdce52a6..b37c75e704bb4a 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -66,6 +66,19 @@ def lib((upstream, branch), gemspec_in_subdir: false) "lib/unicode_normalize", # not to match with "lib/un" ] REPOSITORIES = { + Onigmo: repo("k-takata/Onigmo", [ + ["regcomp.c", "regcomp.c"], + ["regenc.c", "regenc.c"], + ["regenc.h", "regenc.h"], + ["regerror.c", "regerror.c"], + ["regexec.c", "regexec.c"], + ["regint.h", "regint.h"], + ["regparse.c", "regparse.c"], + ["regparse.h", "regparse.h"], + ["regsyntax.c", "regsyntax.c"], + ["onigmo.h", "include/ruby/onigmo.h"], + ["enc", "enc"], + ]), "io-console": repo("ruby/io-console", [ ["ext/io/console", "ext/io/console"], ["test/io/console", "test/io/console"], From fb7f344b09a8351544f9b4bbb593917b552b07ca Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 29 Jan 2019 18:59:50 +0900 Subject: [PATCH 2421/2435] [k-takata/Onigmo] Remove code for reg->int_map https://github.com/k-takata/Onigmo/commit/6c58de82d2 --- include/ruby/onigmo.h | 2 +- regcomp.c | 46 ++----------- regexec.c | 146 ++++++++++++------------------------------ 3 files changed, 45 insertions(+), 149 deletions(-) diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index db290cd47a644d..f949219308b2a3 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -789,7 +789,7 @@ typedef struct re_pattern_buffer { unsigned char *exact; unsigned char *exact_end; unsigned char map[ONIG_CHAR_TABLE_SIZE]; /* used as BM skip or char-map */ - int *int_map; /* BM skip for exact_len > 255 */ + int *reserved1; int *int_map_backward; /* BM skip for backward search */ OnigDistance dmin; /* min-distance of exact or map */ OnigDistance dmax; /* max-distance of exact or map */ diff --git a/regcomp.c b/regcomp.c index 0ecf162556b777..3445fcaccb0291 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4216,7 +4216,7 @@ setup_tree(Node* node, regex_t* reg, int state, ScanEnv* env) /* set skip map for Sunday's quick search */ static int set_bm_skip(UChar* s, UChar* end, regex_t* reg, - UChar skip[], int** int_skip, int ignore_case) + UChar skip[], int ignore_case) { OnigDistance i, len; int clen, flen, n, j, k; @@ -4280,36 +4280,7 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, /* This should not happen. */ return ONIGERR_TYPE_BUG; # else - if (IS_NULL(*int_skip)) { - *int_skip = (int* )xmalloc(sizeof(int) * ONIG_CHAR_TABLE_SIZE); - if (IS_NULL(*int_skip)) return ONIGERR_MEMORY; - } - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) (*int_skip)[i] = (int )(len + 1); - - n = 0; - for (i = 0; i < len; i += clen) { - p = s + i; - if (ignore_case) - n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, - p, end, items); - clen = enclen(enc, p, end); - if (p + clen > end) - clen = (int )(end - p); - - for (j = 0; j < n; j++) { - if ((items[j].code_len != 1) || (items[j].byte_len != clen)) - return 1; /* different length isn't supported. */ - flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf[j]); - if (flen != clen) - return 1; /* different length isn't supported. */ - } - for (j = 0; j < clen; j++) { - (*int_skip)[s[i + j]] = (int )(len - i - j); - for (k = 0; k < n; k++) { - (*int_skip)[buf[k][j]] = (int )(len - i - j); - } - } - } +# error OPT_EXACT_MAXLEN exceeds ONIG_CHAR_TABLE_SIZE. # endif } return (int)len; @@ -5299,7 +5270,7 @@ set_optimize_exact_info(regex_t* reg, OptExactInfo* e) if (e->ignore_case > 0) { if (e->len >= 3 || (e->len >= 2 && allow_reverse)) { e->len = set_bm_skip(reg->exact, reg->exact_end, reg, - reg->map, &(reg->int_map), 1); + reg->map, 1); reg->exact_end = reg->exact + e->len; if (e->len >= 3) { reg->optimize = (allow_reverse != 0 @@ -5318,7 +5289,7 @@ set_optimize_exact_info(regex_t* reg, OptExactInfo* e) else { if (e->len >= 3 || (e->len >= 2 && allow_reverse)) { set_bm_skip(reg->exact, reg->exact_end, reg, - reg->map, &(reg->int_map), 0); + reg->map, 0); reg->optimize = (allow_reverse != 0 ? ONIG_OPTIMIZE_EXACT_BM : ONIG_OPTIMIZE_EXACT_BM_NOT_REV); } @@ -5601,7 +5572,6 @@ onig_free_body(regex_t* reg) if (IS_NOT_NULL(reg)) { xfree(reg->p); xfree(reg->exact); - xfree(reg->int_map); xfree(reg->int_map_backward); xfree(reg->repeat_range); onig_free(reg->chain); @@ -5649,10 +5619,6 @@ onig_reg_copy(regex_t** nreg, regex_t* oreg) (reg)->exact_end = (reg)->exact + exact_size; } - if (IS_NOT_NULL(reg->int_map)) { - if (COPY_FAILED(int_map, sizeof(int) * ONIG_CHAR_TABLE_SIZE)) - goto err_int_map; - } if (IS_NOT_NULL(reg->int_map_backward)) { if (COPY_FAILED(int_map_backward, sizeof(int) * ONIG_CHAR_TABLE_SIZE)) goto err_int_map_backward; @@ -5685,8 +5651,6 @@ onig_reg_copy(regex_t** nreg, regex_t* oreg) err_p: xfree(reg->int_map_backward); err_int_map_backward: - xfree(reg->int_map); - err_int_map: xfree(reg->exact); err: xfree(reg); @@ -5703,7 +5667,6 @@ onig_memsize(const regex_t *reg) if (IS_NULL(reg)) return 0; if (IS_NOT_NULL(reg->p)) size += reg->alloc; if (IS_NOT_NULL(reg->exact)) size += reg->exact_end - reg->exact; - if (IS_NOT_NULL(reg->int_map)) size += sizeof(int) * ONIG_CHAR_TABLE_SIZE; if (IS_NOT_NULL(reg->int_map_backward)) size += sizeof(int) * ONIG_CHAR_TABLE_SIZE; if (IS_NOT_NULL(reg->repeat_range)) size += reg->repeat_range_alloc * sizeof(OnigRepeatRange); if (IS_NOT_NULL(reg->chain)) size += onig_memsize(reg->chain); @@ -5989,7 +5952,6 @@ onig_reg_init(regex_t* reg, OnigOptionType option, (reg)->syntax = syntax; (reg)->optimize = 0; (reg)->exact = (UChar* )NULL; - (reg)->int_map = (int* )NULL; (reg)->int_map_backward = (int* )NULL; (reg)->chain = (regex_t* )NULL; diff --git a/regexec.c b/regexec.c index eec3e236631805..31be00864ef55d 100644 --- a/regexec.c +++ b/regexec.c @@ -4401,39 +4401,19 @@ bm_search_notrev(regex_t* reg, const UChar* target, const UChar* target_end, s = text; - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = se = s + tlen1; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )s; - p--; t--; - } - if (s + 1 >= end) break; - skip = reg->map[se[1]]; - t = s; - do { - s += enclen(enc, s, end); - } while ((s - t) < skip && s < end); - } - } - else { -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = se = s + tlen1; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )s; - p--; t--; - } - if (s + 1 >= end) break; - skip = reg->int_map[se[1]]; - t = s; - do { - s += enclen(enc, s, end); - } while ((s - t) < skip && s < end); + while (s < end) { + p = se = s + tlen1; + t = tail; + while (*p == *t) { + if (t == target) return (UChar* )s; + p--; t--; } -# endif + if (s + 1 >= end) break; + skip = reg->map[se[1]]; + t = s; + do { + s += enclen(enc, s, end); + } while ((s - t) < skip && s < end); } return (UChar* )NULL; @@ -4460,32 +4440,17 @@ bm_search(regex_t* reg, const UChar* target, const UChar* target_end, end = text_end; s = text + tlen1; - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = s; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )p; - p--; t--; - } - if (s + 1 >= end) break; - s += reg->map[s[1]]; - } - } - else { /* see int_map[] */ -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = s; - t = tail; - while (*p == *t) { - if (t == target) return (UChar* )p; - p--; t--; - } - if (s + 1 >= end) break; - s += reg->int_map[s[1]]; + while (s < end) { + p = s; + t = tail; + while (*p == *t) { + if (t == target) return (UChar* )p; + p--; t--; } -# endif + if (s + 1 >= end) break; + s += reg->map[s[1]]; } + return (UChar* )NULL; } @@ -4514,35 +4479,17 @@ bm_search_notrev_ic(regex_t* reg, const UChar* target, const UChar* target_end, s = text; - if (IS_NULL(reg->int_map)) { - while (s < end) { - se = s + tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - s, se + 1)) - return (UChar* )s; - if (s + 1 >= end) break; - skip = reg->map[se[1]]; - t = s; - do { - s += enclen(enc, s, end); - } while ((s - t) < skip && s < end); - } - } - else { -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - se = s + tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - s, se + 1)) - return (UChar* )s; - if (s + 1 >= end) break; - skip = reg->int_map[se[1]]; - t = s; - do { - s += enclen(enc, s, end); - } while ((s - t) < skip && s < end); - } -# endif + while (s < end) { + se = s + tlen1; + if (str_lower_case_match(enc, case_fold_flag, target, target_end, + s, se + 1)) + return (UChar* )s; + if (s + 1 >= end) break; + skip = reg->map[se[1]]; + t = s; + do { + s += enclen(enc, s, end); + } while ((s - t) < skip && s < end); } return (UChar* )NULL; @@ -4571,28 +4518,15 @@ bm_search_ic(regex_t* reg, const UChar* target, const UChar* target_end, end = text_end; s = text + tlen1; - if (IS_NULL(reg->int_map)) { - while (s < end) { - p = s - tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - p, s + 1)) - return (UChar* )p; - if (s + 1 >= end) break; - s += reg->map[s[1]]; - } - } - else { /* see int_map[] */ -# if OPT_EXACT_MAXLEN >= ONIG_CHAR_TABLE_SIZE - while (s < end) { - p = s - tlen1; - if (str_lower_case_match(enc, case_fold_flag, target, target_end, - p, s + 1)) - return (UChar* )p; - if (s + 1 >= end) break; - s += reg->int_map[s[1]]; - } -# endif + while (s < end) { + p = s - tlen1; + if (str_lower_case_match(enc, case_fold_flag, target, target_end, + p, s + 1)) + return (UChar* )p; + if (s + 1 >= end) break; + s += reg->map[s[1]]; } + return (UChar* )NULL; } From bbf9bf3fc5c322ab8e622714b0178aca58e82191 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 29 Jan 2019 19:00:02 +0900 Subject: [PATCH 2422/2435] [k-takata/Onigmo] Remove code for backward BM search The code has not been used for long. (Oniguruma also removed this code.) https://github.com/k-takata/Onigmo/commit/8796781fdd --- include/ruby/onigmo.h | 2 +- regcomp.c | 9 ------ regexec.c | 66 ------------------------------------------- 3 files changed, 1 insertion(+), 76 deletions(-) diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index f949219308b2a3..b9a21206dea0bb 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -790,7 +790,7 @@ typedef struct re_pattern_buffer { unsigned char *exact_end; unsigned char map[ONIG_CHAR_TABLE_SIZE]; /* used as BM skip or char-map */ int *reserved1; - int *int_map_backward; /* BM skip for backward search */ + int *reserved2; OnigDistance dmin; /* min-distance of exact or map */ OnigDistance dmax; /* max-distance of exact or map */ diff --git a/regcomp.c b/regcomp.c index 3445fcaccb0291..6ad72bcbd0bdc3 100644 --- a/regcomp.c +++ b/regcomp.c @@ -5572,7 +5572,6 @@ onig_free_body(regex_t* reg) if (IS_NOT_NULL(reg)) { xfree(reg->p); xfree(reg->exact); - xfree(reg->int_map_backward); xfree(reg->repeat_range); onig_free(reg->chain); @@ -5619,10 +5618,6 @@ onig_reg_copy(regex_t** nreg, regex_t* oreg) (reg)->exact_end = (reg)->exact + exact_size; } - if (IS_NOT_NULL(reg->int_map_backward)) { - if (COPY_FAILED(int_map_backward, sizeof(int) * ONIG_CHAR_TABLE_SIZE)) - goto err_int_map_backward; - } if (IS_NOT_NULL(reg->p)) { if (COPY_FAILED(p, reg->alloc)) goto err_p; @@ -5649,8 +5644,6 @@ onig_reg_copy(regex_t** nreg, regex_t* oreg) err_repeat_range: xfree(reg->p); err_p: - xfree(reg->int_map_backward); - err_int_map_backward: xfree(reg->exact); err: xfree(reg); @@ -5667,7 +5660,6 @@ onig_memsize(const regex_t *reg) if (IS_NULL(reg)) return 0; if (IS_NOT_NULL(reg->p)) size += reg->alloc; if (IS_NOT_NULL(reg->exact)) size += reg->exact_end - reg->exact; - if (IS_NOT_NULL(reg->int_map_backward)) size += sizeof(int) * ONIG_CHAR_TABLE_SIZE; if (IS_NOT_NULL(reg->repeat_range)) size += reg->repeat_range_alloc * sizeof(OnigRepeatRange); if (IS_NOT_NULL(reg->chain)) size += onig_memsize(reg->chain); @@ -5952,7 +5944,6 @@ onig_reg_init(regex_t* reg, OnigOptionType option, (reg)->syntax = syntax; (reg)->optimize = 0; (reg)->exact = (UChar* )NULL; - (reg)->int_map_backward = (int* )NULL; (reg)->chain = (regex_t* )NULL; (reg)->p = (UChar* )NULL; diff --git a/regexec.c b/regexec.c index 31be00864ef55d..2e79623c719171 100644 --- a/regexec.c +++ b/regexec.c @@ -4530,58 +4530,6 @@ bm_search_ic(regex_t* reg, const UChar* target, const UChar* target_end, return (UChar* )NULL; } -#ifdef USE_INT_MAP_BACKWARD -static int -set_bm_backward_skip(UChar* s, UChar* end, OnigEncoding enc ARG_UNUSED, - int** skip) -{ - int i, len; - - if (IS_NULL(*skip)) { - *skip = (int* )xmalloc(sizeof(int) * ONIG_CHAR_TABLE_SIZE); - if (IS_NULL(*skip)) return ONIGERR_MEMORY; - } - - len = (int )(end - s); - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) - (*skip)[i] = len; - - for (i = len - 1; i > 0; i--) - (*skip)[s[i]] = i; - - return 0; -} - -static UChar* -bm_search_backward(regex_t* reg, const UChar* target, const UChar* target_end, - const UChar* text, const UChar* adjust_text, - const UChar* text_end, const UChar* text_start) -{ - const UChar *s, *t, *p; - - s = text_end - (target_end - target); - if (text_start < s) - s = text_start; - else - s = ONIGENC_LEFT_ADJUST_CHAR_HEAD(reg->enc, adjust_text, s, text_end); - - while (s >= text) { - p = s; - t = target; - while (t < target_end && *p == *t) { - p++; t++; - } - if (t == target_end) - return (UChar* )s; - - s -= reg->int_map_backward[*s]; - s = ONIGENC_LEFT_ADJUST_CHAR_HEAD(reg->enc, adjust_text, s, text_end); - } - - return (UChar* )NULL; -} -#endif - static UChar* map_search(OnigEncoding enc, UChar map[], const UChar* text, const UChar* text_range, const UChar* text_end) @@ -4828,21 +4776,7 @@ backward_search_range(regex_t* reg, const UChar* str, const UChar* end, case ONIG_OPTIMIZE_EXACT_BM: case ONIG_OPTIMIZE_EXACT_BM_NOT_REV: -#ifdef USE_INT_MAP_BACKWARD - if (IS_NULL(reg->int_map_backward)) { - int r; - if (s - range < BM_BACKWARD_SEARCH_LENGTH_THRESHOLD) - goto exact_method; - - r = set_bm_backward_skip(reg->exact, reg->exact_end, reg->enc, - &(reg->int_map_backward)); - if (r) return r; - } - p = bm_search_backward(reg, reg->exact, reg->exact_end, range, adjrange, - end, p); -#else goto exact_method; -#endif break; case ONIG_OPTIMIZE_MAP: From f9131412f874aa348df383266cee7dc2cc82a9ca Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 29 Jan 2019 19:00:12 +0900 Subject: [PATCH 2423/2435] [k-takata/Onigmo] Revise set_bm_skip() https://github.com/k-takata/Onigmo/commit/6875da50f7 --- regcomp.c | 89 ++++++++++++++++++++++++++----------------------------- regint.h | 2 +- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/regcomp.c b/regcomp.c index 6ad72bcbd0bdc3..41154b54855dd9 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4225,64 +4225,59 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, OnigEncoding enc = reg->enc; len = end - s; - if (len < ONIG_CHAR_TABLE_SIZE) { - if (ignore_case) { - for (i = 0; i < len; i += clen) { - p = s + i; - n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, - p, end, items); - clen = enclen(enc, p, end); - if (p + clen > end) - clen = (int )(end - p); - - for (j = 0; j < n; j++) { - if ((items[j].code_len != 1) || (items[j].byte_len != clen)) { - /* Different length isn't supported. Stop optimization at here. */ - end = p; - goto endcheck; - } - flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf); - if (flen != clen) { - /* Different length isn't supported. Stop optimization at here. */ - end = p; - goto endcheck; - } - } - } -endcheck: - ; - } + if (len >= ONIG_CHAR_TABLE_SIZE) { + /* This should not happen. */ + return ONIGERR_TYPE_BUG; + } - len = end - s; - for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) - skip[i] = (UChar )(len + 1); - n = 0; + if (ignore_case) { for (i = 0; i < len; i += clen) { p = s + i; - if (ignore_case) - n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, - p, end, items); + n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, + p, end, items); clen = enclen(enc, p, end); if (p + clen > end) clen = (int )(end - p); - for (j = 0; j < clen; j++) { - skip[s[i + j]] = (UChar )(len - i - j); - for (k = 0; k < n; k++) { - ONIGENC_CODE_TO_MBC(enc, items[k].code[0], buf); - skip[buf[j]] = (UChar )(len - i - j); - } + for (j = 0; j < n; j++) { + if ((items[j].code_len != 1) || (items[j].byte_len != clen)) { + /* Different length isn't supported. Stop optimization at here. */ + end = p; + goto endcheck; + } + flen = ONIGENC_CODE_TO_MBC(enc, items[j].code[0], buf); + if (flen != clen) { + /* Different length isn't supported. Stop optimization at here. */ + end = p; + goto endcheck; + } } } +endcheck: + len = end - s; } - else { -# if OPT_EXACT_MAXLEN < ONIG_CHAR_TABLE_SIZE - /* This should not happen. */ - return ONIGERR_TYPE_BUG; -# else -# error OPT_EXACT_MAXLEN exceeds ONIG_CHAR_TABLE_SIZE. -# endif + + for (i = 0; i < ONIG_CHAR_TABLE_SIZE; i++) + skip[i] = (UChar )(len + 1); + n = 0; + for (i = 0; i < len; i += clen) { + p = s + i; + if (ignore_case) + n = ONIGENC_GET_CASE_FOLD_CODES_BY_STR(enc, reg->case_fold_flag, + p, end, items); + clen = enclen(enc, p, end); + if (p + clen > end) + clen = (int )(end - p); + + for (j = 0; j < clen; j++) { + skip[s[i + j]] = (UChar )(len - i - j); + for (k = 0; k < n; k++) { + ONIGENC_CODE_TO_MBC(enc, items[k].code[0], buf); + skip[buf[j]] = (UChar )(len - i - j); + } + } } + return (int)len; } diff --git a/regint.h b/regint.h index 9d69e2d25e51a8..0593fa2cc1f057 100644 --- a/regint.h +++ b/regint.h @@ -91,7 +91,7 @@ #define DEFAULT_MATCH_STACK_LIMIT_SIZE 0 /* unlimited */ #define DEFAULT_PARSE_DEPTH_LIMIT 4096 -#define OPT_EXACT_MAXLEN 24 +#define OPT_EXACT_MAXLEN 24 /* This must be smaller than ONIG_CHAR_TABLE_SIZE. */ /* check config */ #if defined(USE_PERL_SUBEXP_CALL) || defined(USE_CAPITAL_P_NAMED_GROUP) From 85a7171b413305c4bfaca72ad958b8404353f723 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Wed, 30 Jan 2019 18:51:56 +0900 Subject: [PATCH 2424/2435] [k-takata/Onigmo] Add USE_CASE_MAP_API config The case_map API is mainly (only?) used in Ruby. Make it possible to disable the API. https://github.com/k-takata/Onigmo/commit/80e289d6bb --- enc/ascii.c | 4 ++++ enc/big5.c | 12 ++++++++++++ enc/cp949.c | 4 ++++ enc/emacs_mule.c | 4 ++++ enc/euc_jp.c | 4 ++++ enc/euc_kr.c | 8 ++++++++ enc/euc_tw.c | 4 ++++ enc/gb18030.c | 4 ++++ enc/gbk.c | 4 ++++ enc/iso_8859_1.c | 6 ++++++ enc/iso_8859_10.c | 6 ++++++ enc/iso_8859_11.c | 4 ++++ enc/iso_8859_13.c | 6 ++++++ enc/iso_8859_14.c | 6 ++++++ enc/iso_8859_15.c | 6 ++++++ enc/iso_8859_16.c | 6 ++++++ enc/iso_8859_2.c | 6 ++++++ enc/iso_8859_3.c | 6 ++++++ enc/iso_8859_4.c | 6 ++++++ enc/iso_8859_5.c | 6 ++++++ enc/iso_8859_6.c | 4 ++++ enc/iso_8859_7.c | 6 ++++++ enc/iso_8859_8.c | 4 ++++ enc/iso_8859_9.c | 6 ++++++ enc/koi8_r.c | 4 ++++ enc/koi8_u.c | 4 ++++ enc/shift_jis.c | 4 ++++ enc/unicode.c | 2 ++ enc/us_ascii.c | 4 ++++ enc/utf_16be.c | 4 ++++ enc/utf_16le.c | 4 ++++ enc/utf_32be.c | 4 ++++ enc/utf_32le.c | 4 ++++ enc/utf_8.c | 4 ++++ enc/windows_1250.c | 6 ++++++ enc/windows_1251.c | 6 ++++++ enc/windows_1252.c | 6 ++++++ enc/windows_1253.c | 6 ++++++ enc/windows_1254.c | 6 ++++++ enc/windows_1257.c | 6 ++++++ enc/windows_31j.c | 4 ++++ regenc.c | 2 ++ regenc.h | 2 ++ 43 files changed, 214 insertions(+) diff --git a/enc/ascii.c b/enc/ascii.c index ae7db97f25ed79..4ba93f4febdb21 100644 --- a/enc/ascii.c +++ b/enc/ascii.c @@ -54,7 +54,11 @@ OnigEncodingDefine(ascii, ASCII) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif ENCINDEX_ASCII_8BIT, ONIGENC_FLAG_NONE, }; diff --git a/enc/big5.c b/enc/big5.c index ab4fb69819b60e..e141ebdbe36988 100644 --- a/enc/big5.c +++ b/enc/big5.c @@ -300,7 +300,11 @@ OnigEncodingDefine(big5, BIG5) = { onigenc_not_support_get_ctype_code_range, big5_left_adjust_char_head, big5_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; @@ -335,7 +339,11 @@ OnigEncodingDefine(big5_hkscs, BIG5_HKSCS) = { onigenc_not_support_get_ctype_code_range, big5_left_adjust_char_head, big5_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; @@ -370,7 +378,11 @@ OnigEncodingDefine(big5_uao, BIG5_UAO) = { onigenc_not_support_get_ctype_code_range, big5_left_adjust_char_head, big5_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/cp949.c b/enc/cp949.c index 1600d0cd5bee29..77e961a7cdf3d0 100644 --- a/enc/cp949.c +++ b/enc/cp949.c @@ -211,7 +211,11 @@ OnigEncodingDefine(cp949, CP949) = { onigenc_not_support_get_ctype_code_range, cp949_left_adjust_char_head, cp949_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/emacs_mule.c b/enc/emacs_mule.c index f92eb183cf788d..abd986a1878e0b 100644 --- a/enc/emacs_mule.c +++ b/enc/emacs_mule.c @@ -334,7 +334,11 @@ OnigEncodingDefine(emacs_mule, Emacs_Mule) = { onigenc_not_support_get_ctype_code_range, left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/euc_jp.c b/enc/euc_jp.c index d283bf4ebb1208..678d0116682bee 100644 --- a/enc/euc_jp.c +++ b/enc/euc_jp.c @@ -576,7 +576,11 @@ OnigEncodingDefine(euc_jp, EUC_JP) = { get_ctype_code_range, left_adjust_char_head, is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/euc_kr.c b/enc/euc_kr.c index 21d6ab4e1c10b9..4079a0ece05b20 100644 --- a/enc/euc_kr.c +++ b/enc/euc_kr.c @@ -188,7 +188,11 @@ OnigEncodingDefine(euc_kr, EUC_KR) = { onigenc_not_support_get_ctype_code_range, euckr_left_adjust_char_head, euckr_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; @@ -213,7 +217,11 @@ OnigEncodingDefine(euc_cn, EUC_CN) = { onigenc_not_support_get_ctype_code_range, euckr_left_adjust_char_head, euckr_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/euc_tw.c b/enc/euc_tw.c index 1c5659cb1d0895..722e29a9dac70e 100644 --- a/enc/euc_tw.c +++ b/enc/euc_tw.c @@ -221,7 +221,11 @@ OnigEncodingDefine(euc_tw, EUC_TW) = { onigenc_not_support_get_ctype_code_range, euctw_left_adjust_char_head, euctw_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/gb18030.c b/enc/gb18030.c index 63d2e633ecb16e..316737db11463d 100644 --- a/enc/gb18030.c +++ b/enc/gb18030.c @@ -597,7 +597,11 @@ OnigEncodingDefine(gb18030, GB18030) = { onigenc_not_support_get_ctype_code_range, gb18030_left_adjust_char_head, gb18030_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/gbk.c b/enc/gbk.c index 31032553bf58ed..3df4e4b6d6a6a9 100644 --- a/enc/gbk.c +++ b/enc/gbk.c @@ -211,7 +211,11 @@ OnigEncodingDefine(gbk, GBK) = { onigenc_not_support_get_ctype_code_range, gbk_left_adjust_char_head, gbk_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_1.c b/enc/iso_8859_1.c index 7af0888c3edced..78ea1fba600582 100644 --- a/enc/iso_8859_1.c +++ b/enc/iso_8859_1.c @@ -255,6 +255,7 @@ is_code_ctype(OnigCodePoint code, unsigned int ctype, OnigEncoding enc ARG_UNUSE return FALSE; } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -297,6 +298,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_1, ISO_8859_1) = { onigenc_single_byte_mbc_enc_len, @@ -315,7 +317,11 @@ OnigEncodingDefine(iso_8859_1, ISO_8859_1) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_10.c b/enc/iso_8859_10.c index cae4be2db0367b..bf1c884cb23511 100644 --- a/enc/iso_8859_10.c +++ b/enc/iso_8859_10.c @@ -224,6 +224,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -269,6 +270,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_10, ISO_8859_10) = { onigenc_single_byte_mbc_enc_len, @@ -287,7 +289,11 @@ OnigEncodingDefine(iso_8859_10, ISO_8859_10) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_11.c b/enc/iso_8859_11.c index b9c6119fd9a02b..403ae6499e0cf8 100644 --- a/enc/iso_8859_11.c +++ b/enc/iso_8859_11.c @@ -93,7 +93,11 @@ OnigEncodingDefine(iso_8859_11, ISO_8859_11) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_13.c b/enc/iso_8859_13.c index fe1ddd7065e6ae..8c6e758b8066f3 100644 --- a/enc/iso_8859_13.c +++ b/enc/iso_8859_13.c @@ -217,6 +217,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -264,6 +265,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_13, ISO_8859_13) = { onigenc_single_byte_mbc_enc_len, @@ -282,7 +284,11 @@ OnigEncodingDefine(iso_8859_13, ISO_8859_13) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_14.c b/enc/iso_8859_14.c index 647514a01626c0..21dffea76f087b 100644 --- a/enc/iso_8859_14.c +++ b/enc/iso_8859_14.c @@ -226,6 +226,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -280,6 +281,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_14, ISO_8859_14) = { onigenc_single_byte_mbc_enc_len, @@ -298,7 +300,11 @@ OnigEncodingDefine(iso_8859_14, ISO_8859_14) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_15.c b/enc/iso_8859_15.c index 377a3afc7b15c5..dd6c29a6432455 100644 --- a/enc/iso_8859_15.c +++ b/enc/iso_8859_15.c @@ -220,6 +220,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -271,6 +272,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_15, ISO_8859_15) = { onigenc_single_byte_mbc_enc_len, @@ -289,7 +291,11 @@ OnigEncodingDefine(iso_8859_15, ISO_8859_15) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_16.c b/enc/iso_8859_16.c index 135630eb73df46..aa7ce99fbac467 100644 --- a/enc/iso_8859_16.c +++ b/enc/iso_8859_16.c @@ -222,6 +222,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -275,6 +276,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_16, ISO_8859_16) = { onigenc_single_byte_mbc_enc_len, @@ -293,7 +295,11 @@ OnigEncodingDefine(iso_8859_16, ISO_8859_16) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_2.c b/enc/iso_8859_2.c index 3a05c6320dbbeb..859073fd149cb4 100644 --- a/enc/iso_8859_2.c +++ b/enc/iso_8859_2.c @@ -220,6 +220,7 @@ is_code_ctype(OnigCodePoint code, unsigned int ctype, OnigEncoding enc ARG_UNUSE return FALSE; } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -266,6 +267,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_2, ISO_8859_2) = { onigenc_single_byte_mbc_enc_len, @@ -284,7 +286,11 @@ OnigEncodingDefine(iso_8859_2, ISO_8859_2) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_3.c b/enc/iso_8859_3.c index 2a343eac638482..d8199d5125b19c 100644 --- a/enc/iso_8859_3.c +++ b/enc/iso_8859_3.c @@ -220,6 +220,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API #define DOTLESS_i (0xB9) #define I_WITH_DOT_ABOVE (0xA9) static int @@ -276,6 +277,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_3, ISO_8859_3) = { onigenc_single_byte_mbc_enc_len, @@ -294,7 +296,11 @@ OnigEncodingDefine(iso_8859_3, ISO_8859_3) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_4.c b/enc/iso_8859_4.c index e2134e8c0b27a5..5f01f0157556dd 100644 --- a/enc/iso_8859_4.c +++ b/enc/iso_8859_4.c @@ -223,6 +223,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -272,6 +273,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_4, ISO_8859_4) = { onigenc_single_byte_mbc_enc_len, @@ -290,7 +292,11 @@ OnigEncodingDefine(iso_8859_4, ISO_8859_4) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_5.c b/enc/iso_8859_5.c index 6fafc358233eb9..8223fc0ec706c8 100644 --- a/enc/iso_8859_5.c +++ b/enc/iso_8859_5.c @@ -209,6 +209,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -240,6 +241,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_5, ISO_8859_5) = { onigenc_single_byte_mbc_enc_len, @@ -258,7 +260,11 @@ OnigEncodingDefine(iso_8859_5, ISO_8859_5) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_6.c b/enc/iso_8859_6.c index cdb74054d1e312..78543ea307d221 100644 --- a/enc/iso_8859_6.c +++ b/enc/iso_8859_6.c @@ -93,7 +93,11 @@ OnigEncodingDefine(iso_8859_6, ISO_8859_6) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_7.c b/enc/iso_8859_7.c index ac973f74ba51ef..e84f5c3460ad4e 100644 --- a/enc/iso_8859_7.c +++ b/enc/iso_8859_7.c @@ -205,6 +205,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -259,6 +260,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_7, ISO_8859_7) = { onigenc_single_byte_mbc_enc_len, @@ -277,7 +279,11 @@ OnigEncodingDefine(iso_8859_7, ISO_8859_7) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_8.c b/enc/iso_8859_8.c index e256855f2130a7..b757a283de15a0 100644 --- a/enc/iso_8859_8.c +++ b/enc/iso_8859_8.c @@ -93,7 +93,11 @@ OnigEncodingDefine(iso_8859_8, ISO_8859_8) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/iso_8859_9.c b/enc/iso_8859_9.c index 004eec310fcd52..f15953963bf4de 100644 --- a/enc/iso_8859_9.c +++ b/enc/iso_8859_9.c @@ -213,6 +213,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API #define DOTLESS_i (0xFD) #define I_WITH_DOT_ABOVE (0xDD) static int @@ -265,6 +266,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(iso_8859_9, ISO_8859_9) = { onigenc_single_byte_mbc_enc_len, @@ -283,7 +285,11 @@ OnigEncodingDefine(iso_8859_9, ISO_8859_9) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/koi8_r.c b/enc/koi8_r.c index a52097577416a1..39f24824651275 100644 --- a/enc/koi8_r.c +++ b/enc/koi8_r.c @@ -214,7 +214,11 @@ OnigEncodingDefine(koi8_r, KOI8_R) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/koi8_u.c b/enc/koi8_u.c index 50bb78bd04841d..8cd890dd16a041 100644 --- a/enc/koi8_u.c +++ b/enc/koi8_u.c @@ -218,7 +218,11 @@ OnigEncodingDefine(koi8_u, KOI8_U) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/shift_jis.c b/enc/shift_jis.c index f1355d2d95fcb4..48f648868af13d 100644 --- a/enc/shift_jis.c +++ b/enc/shift_jis.c @@ -47,7 +47,11 @@ OnigEncodingDefine(shift_jis, Shift_JIS) = { get_ctype_code_range, left_adjust_char_head, is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/unicode.c b/enc/unicode.c index 07497cdbe46731..5bc806863e8f55 100644 --- a/enc/unicode.c +++ b/enc/unicode.c @@ -655,6 +655,7 @@ onigenc_unicode_get_case_fold_codes_by_str(OnigEncoding enc, return n; } +#ifdef USE_CASE_MAP_API /* length in bytes for three characters in UTF-32; e.g. needed for ffi (U+FB03) */ #define CASE_MAPPING_SLACK 12 #define MODIFIED (flags |= ONIGENC_CASE_MODIFIED) @@ -798,6 +799,7 @@ onigenc_unicode_case_map(OnigCaseFoldType* flagP, *flagP = flags; return (int )(to - to_start); } +#endif const char onigenc_unicode_version_string[] = #ifdef ONIG_UNICODE_VERSION_STRING diff --git a/enc/us_ascii.c b/enc/us_ascii.c index 08f9072c435591..253ee695724159 100644 --- a/enc/us_ascii.c +++ b/enc/us_ascii.c @@ -32,7 +32,11 @@ OnigEncodingDefine(us_ascii, US_ASCII) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_single_byte_ascii_only_case_map, +#else + NULL, +#endif ENCINDEX_US_ASCII, ONIGENC_FLAG_NONE, }; diff --git a/enc/utf_16be.c b/enc/utf_16be.c index f9dd7119d65a0e..0086040b5d5bd9 100644 --- a/enc/utf_16be.c +++ b/enc/utf_16be.c @@ -249,7 +249,11 @@ OnigEncodingDefine(utf_16be, UTF_16BE) = { onigenc_utf16_32_get_ctype_code_range, utf16be_left_adjust_char_head, onigenc_always_false_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_unicode_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_UNICODE, }; diff --git a/enc/utf_16le.c b/enc/utf_16le.c index 2c8438d0be2554..ca0fce53872045 100644 --- a/enc/utf_16le.c +++ b/enc/utf_16le.c @@ -242,7 +242,11 @@ OnigEncodingDefine(utf_16le, UTF_16LE) = { onigenc_utf16_32_get_ctype_code_range, utf16le_left_adjust_char_head, onigenc_always_false_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_unicode_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_UNICODE, }; diff --git a/enc/utf_32be.c b/enc/utf_32be.c index 17841e52a4e82d..e05cfaf1b2fdf8 100644 --- a/enc/utf_32be.c +++ b/enc/utf_32be.c @@ -199,7 +199,11 @@ OnigEncodingDefine(utf_32be, UTF_32BE) = { onigenc_utf16_32_get_ctype_code_range, utf32be_left_adjust_char_head, onigenc_always_false_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_unicode_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_UNICODE, }; diff --git a/enc/utf_32le.c b/enc/utf_32le.c index 18b798f102c5d1..651efdcec57790 100644 --- a/enc/utf_32le.c +++ b/enc/utf_32le.c @@ -199,7 +199,11 @@ OnigEncodingDefine(utf_32le, UTF_32LE) = { onigenc_utf16_32_get_ctype_code_range, utf32le_left_adjust_char_head, onigenc_always_false_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_unicode_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_UNICODE, }; diff --git a/enc/utf_8.c b/enc/utf_8.c index cdf2510d84c829..ae7c98469d50bd 100644 --- a/enc/utf_8.c +++ b/enc/utf_8.c @@ -431,7 +431,11 @@ OnigEncodingDefine(utf_8, UTF_8) = { get_ctype_code_range, left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_unicode_case_map, +#else + NULL, +#endif ENCINDEX_UTF_8, ONIGENC_FLAG_UNICODE, }; diff --git a/enc/windows_1250.c b/enc/windows_1250.c index daf23e9d1e6e6a..d38d50a01d370d 100644 --- a/enc/windows_1250.c +++ b/enc/windows_1250.c @@ -190,6 +190,7 @@ cp1250_get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -239,6 +240,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1250, Windows_1250) = { onigenc_single_byte_mbc_enc_len, @@ -257,7 +259,11 @@ OnigEncodingDefine(windows_1250, Windows_1250) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_1251.c b/enc/windows_1251.c index 6c892c1b8ce39a..81641d0337f2b2 100644 --- a/enc/windows_1251.c +++ b/enc/windows_1251.c @@ -180,6 +180,7 @@ cp1251_get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -221,6 +222,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1251, Windows_1251) = { onigenc_single_byte_mbc_enc_len, @@ -239,7 +241,11 @@ OnigEncodingDefine(windows_1251, Windows_1251) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_1252.c b/enc/windows_1252.c index b685878d3fc5d7..6aece95c0ab2ae 100644 --- a/enc/windows_1252.c +++ b/enc/windows_1252.c @@ -181,6 +181,7 @@ cp1252_get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -228,6 +229,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1252, Windows_1252) = { onigenc_single_byte_mbc_enc_len, @@ -246,7 +248,11 @@ OnigEncodingDefine(windows_1252, Windows_1252) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_1253.c b/enc/windows_1253.c index b2a43581c39240..c95ea3f41ccd9f 100644 --- a/enc/windows_1253.c +++ b/enc/windows_1253.c @@ -213,6 +213,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API static int case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, @@ -272,6 +273,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1253, Windows_1253) = { onigenc_single_byte_mbc_enc_len, @@ -290,7 +292,11 @@ OnigEncodingDefine(windows_1253, Windows_1253) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_1254.c b/enc/windows_1254.c index 5e6d92d3d2680e..c8d5991686a0b3 100644 --- a/enc/windows_1254.c +++ b/enc/windows_1254.c @@ -221,6 +221,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API #define DOTLESS_i (0xFD) #define I_WITH_DOT_ABOVE (0xDD) static int @@ -277,6 +278,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1254, Windows_1254) = { onigenc_single_byte_mbc_enc_len, @@ -295,7 +297,11 @@ OnigEncodingDefine(windows_1254, Windows_1254) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_1257.c b/enc/windows_1257.c index ada03b72bf01cf..def13c8c49fed0 100644 --- a/enc/windows_1257.c +++ b/enc/windows_1257.c @@ -225,6 +225,7 @@ get_case_fold_codes_by_str(OnigCaseFoldType flag, flag, p, end, items); } +#ifdef USE_CASE_MAP_API #define DOTLESS_i (0xB9) #define I_WITH_DOT_ABOVE (0xA9) static int @@ -279,6 +280,7 @@ case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, *flagP = flags; return (int )(to - to_start); } +#endif OnigEncodingDefine(windows_1257, Windows_1257) = { onigenc_single_byte_mbc_enc_len, @@ -297,7 +299,11 @@ OnigEncodingDefine(windows_1257, Windows_1257) = { onigenc_not_support_get_ctype_code_range, onigenc_single_byte_left_adjust_char_head, onigenc_always_true_is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/enc/windows_31j.c b/enc/windows_31j.c index 1eb859596a3bc3..cd8bd83fddf693 100644 --- a/enc/windows_31j.c +++ b/enc/windows_31j.c @@ -48,7 +48,11 @@ OnigEncodingDefine(windows_31j, Windows_31J) = { get_ctype_code_range, left_adjust_char_head, is_allowed_reverse_match, +#ifdef USE_CASE_MAP_API onigenc_ascii_only_case_map, +#else + NULL, +#endif 0, ONIGENC_FLAG_NONE, }; diff --git a/regenc.c b/regenc.c index 823aacc28e615b..978d0cad1bce2d 100644 --- a/regenc.c +++ b/regenc.c @@ -966,6 +966,7 @@ onigenc_property_list_add_property(UChar* name, const OnigCodePoint* prop, } #endif +#ifdef USE_CASE_MAP_API extern int onigenc_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const OnigUChar* end, OnigUChar* to, OnigUChar* to_end, const struct OnigEncodingTypeST* enc) @@ -1027,3 +1028,4 @@ onigenc_single_byte_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar *flagP = flags; return (int )(to - to_start); } +#endif diff --git a/regenc.h b/regenc.h index 4fbe403b6301d8..2d96768763901b 100644 --- a/regenc.h +++ b/regenc.h @@ -134,11 +134,13 @@ typedef struct { #define roomof(x, y) (((x) + (y) - 1) / (y)) #define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) +/* config */ #define USE_CRNL_AS_LINE_TERMINATOR #define USE_UNICODE_PROPERTIES #define USE_UNICODE_AGE_PROPERTIES /* #define USE_UNICODE_CASE_FOLD_TURKISH_AZERI */ /* #define USE_UNICODE_ALL_LINE_TERMINATORS */ /* see Unicode.org UTS #18 */ +#define USE_CASE_MAP_API #define ONIG_ENCODING_INIT_DEFAULT ONIG_ENCODING_ASCII From 916fbf1063268e27e241363d81e68bf132e7602b Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Wed, 30 Jan 2019 10:31:10 +0900 Subject: [PATCH 2425/2435] [k-takata/Onigmo] Update copyright information * Update our copyright information. * Import the latest information from oniguruma. Related: #95 https://github.com/k-takata/Onigmo/commit/0d8662b500 --- include/ruby/onigmo.h | 4 ++-- regcomp.c | 4 ++-- regenc.c | 2 +- regenc.h | 2 +- regerror.c | 2 +- regexec.c | 4 ++-- regint.h | 2 +- regparse.c | 2 +- regparse.h | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index b9a21206dea0bb..74d012d79f3915 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -4,8 +4,8 @@ onigmo.h - Onigmo (Oniguruma-mod) (regular expression library) **********************************************************************/ /*- - * Copyright (c) 2002-2009 K.Kosako - * Copyright (c) 2011-2017 K.Takata + * Copyright (c) 2002-2016 K.Kosako + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regcomp.c b/regcomp.c index 41154b54855dd9..71e57c8e3923bc 100644 --- a/regcomp.c +++ b/regcomp.c @@ -2,8 +2,8 @@ regcomp.c - Onigmo (Oniguruma-mod) (regular expression library) **********************************************************************/ /*- - * Copyright (c) 2002-2013 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2002-2018 K.Kosako + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regenc.c b/regenc.c index 978d0cad1bce2d..ff2155aabc1e80 100644 --- a/regenc.c +++ b/regenc.c @@ -3,7 +3,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2007 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regenc.h b/regenc.h index 2d96768763901b..fe0440dd740e9b 100644 --- a/regenc.h +++ b/regenc.h @@ -5,7 +5,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2008 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regerror.c b/regerror.c index 8667084d41c931..0134fc29f5dffa 100644 --- a/regerror.c +++ b/regerror.c @@ -3,7 +3,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2007 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regexec.c b/regexec.c index 2e79623c719171..3210c7cc1b5603 100644 --- a/regexec.c +++ b/regexec.c @@ -2,8 +2,8 @@ regexec.c - Onigmo (Oniguruma-mod) (regular expression library) **********************************************************************/ /*- - * Copyright (c) 2002-2008 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2002-2018 K.Kosako + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regint.h b/regint.h index 0593fa2cc1f057..3f4aa919e5046f 100644 --- a/regint.h +++ b/regint.h @@ -5,7 +5,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2013 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regparse.c b/regparse.c index 418bd3814076d9..c61a8b28a2c118 100644 --- a/regparse.c +++ b/regparse.c @@ -3,7 +3,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2008 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/regparse.h b/regparse.h index dd35d485255bad..65da835a55aca1 100644 --- a/regparse.h +++ b/regparse.h @@ -5,7 +5,7 @@ **********************************************************************/ /*- * Copyright (c) 2002-2007 K.Kosako - * Copyright (c) 2011-2016 K.Takata + * Copyright (c) 2011-2019 K.Takata * All rights reserved. * * Redistribution and use in source and binary forms, with or without From a097878ed4fdecc347909f1cd62089bf6ab554c0 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Wed, 30 Jan 2019 19:02:41 +0900 Subject: [PATCH 2426/2435] [k-takata/Onigmo] Comment out unused errors https://github.com/k-takata/Onigmo/commit/5555ee4c81 --- regerror.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/regerror.c b/regerror.c index 0134fc29f5dffa..703d747de9f727 100644 --- a/regerror.c +++ b/regerror.c @@ -63,14 +63,18 @@ onig_error_code_to_format(OnigPosition code) p = "parse depth limit over"; break; case ONIGERR_DEFAULT_ENCODING_IS_NOT_SET: p = "default multibyte-encoding is not set"; break; +#if 0 case ONIGERR_SPECIFIED_ENCODING_CANT_CONVERT_TO_WIDE_CHAR: p = "can't convert to wide-char on specified multibyte-encoding"; break; +#endif case ONIGERR_INVALID_ARGUMENT: p = "invalid argument"; break; case ONIGERR_END_PATTERN_AT_LEFT_BRACE: p = "end pattern at left brace"; break; +#if 0 case ONIGERR_END_PATTERN_AT_LEFT_BRACKET: p = "end pattern at left bracket"; break; +#endif case ONIGERR_EMPTY_CHAR_CLASS: p = "empty char-class"; break; case ONIGERR_PREMATURE_END_OF_CHAR_CLASS: @@ -87,16 +91,20 @@ onig_error_code_to_format(OnigPosition code) p = "invalid control-code syntax"; break; case ONIGERR_CHAR_CLASS_VALUE_AT_END_OF_RANGE: p = "char-class value at end of range"; break; +#if 0 case ONIGERR_CHAR_CLASS_VALUE_AT_START_OF_RANGE: p = "char-class value at start of range"; break; +#endif case ONIGERR_UNMATCHED_RANGE_SPECIFIER_IN_CHAR_CLASS: p = "unmatched range specifier in char-class"; break; case ONIGERR_TARGET_OF_REPEAT_OPERATOR_NOT_SPECIFIED: p = "target of repeat operator is not specified"; break; case ONIGERR_TARGET_OF_REPEAT_OPERATOR_INVALID: p = "target of repeat operator is invalid"; break; +#if 0 case ONIGERR_NESTED_REPEAT_OPERATOR: p = "nested repeat operator"; break; +#endif case ONIGERR_UNMATCHED_CLOSE_PARENTHESIS: p = "unmatched close parenthesis"; break; case ONIGERR_END_PATTERN_WITH_UNMATCHED_PARENTHESIS: @@ -121,14 +129,18 @@ onig_error_code_to_format(OnigPosition code) p = "upper is smaller than lower in repeat range"; break; case ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS: p = "empty range in char class"; break; +#if 0 case ONIGERR_MISMATCH_CODE_LENGTH_IN_CLASS_RANGE: p = "mismatch multibyte code length in char-class range"; break; +#endif case ONIGERR_TOO_MANY_MULTI_BYTE_RANGES: p = "too many multibyte code ranges are specified"; break; case ONIGERR_TOO_SHORT_MULTI_BYTE_STRING: p = "too short multibyte code string"; break; +#if 0 case ONIGERR_TOO_BIG_BACKREF_NUMBER: p = "too big backref number"; break; +#endif case ONIGERR_INVALID_BACKREF: #ifdef USE_NAMED_GROUP p = "invalid backref number/name"; break; From ad150e90397ef85c1dbcf3ef54c0b1338fde4204 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Wed, 30 Jan 2019 19:47:54 +0900 Subject: [PATCH 2427/2435] [k-takata/Onigmo] Update version number (6.2.0) * Update the version number to 6.2.0 * Update LTVERSION to 6:5:0. https://github.com/k-takata/Onigmo/commit/9e0f7ceee0 --- include/ruby/onigmo.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index 74d012d79f3915..9dcddee829a86f 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -38,8 +38,8 @@ extern "C" { #endif #define ONIGMO_VERSION_MAJOR 6 -#define ONIGMO_VERSION_MINOR 1 -#define ONIGMO_VERSION_TEENY 3 +#define ONIGMO_VERSION_MINOR 2 +#define ONIGMO_VERSION_TEENY 0 #ifndef ONIG_EXTERN # ifdef RUBY_EXTERN From 496e74d0ccedd513eca9a156b207a36ed88e484f Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Thu, 31 Jan 2019 19:04:02 +0900 Subject: [PATCH 2428/2435] [k-takata/Onigmo] Fix that onig_new() may crash When onig_reg_init() returns an error, onig_free_body() which is called via onig_new() may crash because some members are not properly initialized. Fix it. https://github.com/k-takata/Onigmo/commit/d2a090a57e --- regcomp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/regcomp.c b/regcomp.c index 71e57c8e3923bc..9dfdb21e851e4d 100644 --- a/regcomp.c +++ b/regcomp.c @@ -5919,6 +5919,12 @@ onig_reg_init(regex_t* reg, OnigOptionType option, if (IS_NULL(reg)) return ONIGERR_INVALID_ARGUMENT; + (reg)->exact = (UChar* )NULL; + (reg)->chain = (regex_t* )NULL; + (reg)->p = (UChar* )NULL; + (reg)->name_table = (void* )NULL; + (reg)->repeat_range = (OnigRepeatRange* )NULL; + if (ONIGENC_IS_UNDEF(enc)) return ONIGERR_DEFAULT_ENCODING_IS_NOT_SET; @@ -5938,13 +5944,9 @@ onig_reg_init(regex_t* reg, OnigOptionType option, (reg)->options = option; (reg)->syntax = syntax; (reg)->optimize = 0; - (reg)->exact = (UChar* )NULL; - (reg)->chain = (regex_t* )NULL; - (reg)->p = (UChar* )NULL; (reg)->alloc = 0; (reg)->used = 0; - (reg)->name_table = (void* )NULL; (reg)->case_fold_flag = case_fold_flag; From f0b31a5898ae8101286dc47085139c56ba0bda54 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Mon, 29 Jul 2019 20:15:26 +0900 Subject: [PATCH 2429/2435] [k-takata/Onigmo] Fix SEGV in onig_error_code_to_str() (Fix https://github.com/k-takata/Onigmo/pull/132) When onig_new(ONIG_SYNTAX_PERL) fails with ONIGERR_INVALID_GROUP_NAME, onig_error_code_to_str() crashes. onig_scan_env_set_error_string() should have been used when returning ONIGERR_INVALID_GROUP_NAME. https://github.com/k-takata/Onigmo/commit/00cc7e28a3 --- regparse.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/regparse.c b/regparse.c index c61a8b28a2c118..1772196bcda027 100644 --- a/regparse.c +++ b/regparse.c @@ -4043,7 +4043,11 @@ fetch_token(OnigToken* tok, UChar** src, UChar* end, ScanEnv* env) if (c == 'R' || c == '0') { PINC; /* skip 'R' / '0' */ - if (!PPEEK_IS(')')) return ONIGERR_INVALID_GROUP_NAME; + if (!PPEEK_IS(')')) { + r = ONIGERR_INVALID_GROUP_NAME; + onig_scan_env_set_error_string(env, r, p - 1, p + 1); + return r; + } PINC; /* skip ')' */ name_end = name = p; gnum = 0; From ac379278e818eb92e87a8f82e6841d7ab59baeb2 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Mon, 29 Jul 2019 20:16:46 +0900 Subject: [PATCH 2430/2435] =?UTF-8?q?[k-takata/Onigmo]=20Fix=20stack=20ove?= =?UTF-8?q?rflow=20with=20X+++++++++++++++++++=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Imported the fix from: https://github.com/kkos/oniguruma/commit/4097828d7cc87589864fecf452f2cd46c5f37180 https://github.com/k-takata/Onigmo/commit/786b4849c1 --- regparse.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/regparse.c b/regparse.c index 1772196bcda027..123b3015a5a936 100644 --- a/regparse.c +++ b/regparse.c @@ -6313,11 +6313,14 @@ parse_exp(Node** np, OnigToken* tok, int term, int r, len, group = 0; Node* qn; Node** targetp; + unsigned int parse_depth; *np = NULL; if (tok->type == (enum TokenSyms )term) goto end_of_token; + parse_depth = env->parse_depth; + switch (tok->type) { case TK_ALT: case TK_EOT: @@ -6628,6 +6631,10 @@ parse_exp(Node** np, OnigToken* tok, int term, if (is_invalid_quantifier_target(*targetp)) return ONIGERR_TARGET_OF_REPEAT_OPERATOR_INVALID; + parse_depth++; + if (parse_depth > ParseDepthLimit) + return ONIGERR_PARSE_DEPTH_LIMIT_OVER; + qn = node_new_quantifier(tok->u.repeat.lower, tok->u.repeat.upper, (r == TK_INTERVAL ? 1 : 0)); CHECK_NULL_RETURN_MEMERR(qn); From 16086128ccb5fa9133ef57c0e16bd9eaa82d818c Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Tue, 30 Jul 2019 23:15:05 +0900 Subject: [PATCH 2431/2435] [k-takata/Onigmo] Suppress warning on 64-bit builds https://github.com/k-takata/Onigmo/commit/ced209d5e9 --- regcomp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regcomp.c b/regcomp.c index 9dfdb21e851e4d..18b2c97eb6381c 100644 --- a/regcomp.c +++ b/regcomp.c @@ -4278,7 +4278,7 @@ set_bm_skip(UChar* s, UChar* end, regex_t* reg, } } - return (int)len; + return (int )len; } typedef struct { From 81c13349049a3674819842e87b14cf35b8755392 Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Thu, 1 Aug 2019 21:27:51 +0900 Subject: [PATCH 2432/2435] [k-takata/Onigmo] Fix out-of-bounds read in parse_char_class() (Close https://github.com/k-takata/Onigmo/pull/139) /[\x{111111}]/ causes out-of-bounds read when encoding is a single byte encoding. \x{111111} is an invalid codepoint for a single byte encoding. Check if it is a valid codepoint. https://github.com/k-takata/Onigmo/commit/d4cf99d30b --- regenc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/regenc.c b/regenc.c index ff2155aabc1e80..c561cee29d210d 100644 --- a/regenc.c +++ b/regenc.c @@ -640,18 +640,23 @@ onigenc_single_byte_mbc_to_code(const UChar* p, const UChar* end ARG_UNUSED, } extern int -onigenc_single_byte_code_to_mbclen(OnigCodePoint code ARG_UNUSED, OnigEncoding enc ARG_UNUSED) +onigenc_single_byte_code_to_mbclen(OnigCodePoint code, OnigEncoding enc ARG_UNUSED) { + if (code > 0xff) + return ONIGERR_INVALID_CODE_POINT_VALUE; return 1; } extern int onigenc_single_byte_code_to_mbc(OnigCodePoint code, UChar *buf, OnigEncoding enc ARG_UNUSED) { + if (code > 0xff) { #ifdef RUBY - if (code > 0xff) rb_raise(rb_eRangeError, "%u out of char range", code); +#else + return ONIGERR_INVALID_CODE_POINT_VALUE; #endif + } *buf = (UChar )(code & 0xff); return 1; } From 76b1d4a4814eefa9bf50a1d3d0bf4e7094d5936b Mon Sep 17 00:00:00 2001 From: "K.Takata" Date: Fri, 2 Aug 2019 01:03:11 +0900 Subject: [PATCH 2433/2435] [k-takata/Onigmo] Disable error message for capture history when not needed Add `#ifdef USE_CAPTURE_HISTORY`. https://github.com/k-takata/Onigmo/commit/8217be2c3a --- regerror.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/regerror.c b/regerror.c index 703d747de9f727..e772feee81914a 100644 --- a/regerror.c +++ b/regerror.c @@ -173,8 +173,10 @@ onig_error_code_to_format(OnigPosition code) p = "multiplex definition name <%n> call"; break; case ONIGERR_NEVER_ENDING_RECURSION: p = "never ending recursion"; break; +#ifdef USE_CAPTURE_HISTORY case ONIGERR_GROUP_NUMBER_OVER_FOR_CAPTURE_HISTORY: p = "group number is too big for capture history"; break; +#endif case ONIGERR_INVALID_CHAR_PROPERTY_NAME: p = "invalid character property name {%n}"; break; case ONIGERR_TOO_MANY_CAPTURE_GROUPS: From aaf47cca03c4c7561fd931e0aa1a76adfdb23eba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Jan 2026 18:39:15 +0900 Subject: [PATCH 2434/2435] Now onigenc_single_byte_code_to_mbclen checks out-of-bound --- sprintf.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sprintf.c b/sprintf.c index cb266a98416e33..de88a9f4b35a20 100644 --- a/sprintf.c +++ b/sprintf.c @@ -441,7 +441,7 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) VALUE val = GETARG(); VALUE tmp; unsigned int c; - int n; + int n, encidx; tmp = rb_check_string_type(val); if (!NIL_P(tmp)) { @@ -451,11 +451,13 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) goto format_s1; } n = NUM2INT(val); - if (n >= 0) n = rb_enc_codelen((c = n), enc); + if (n >= 0) { + n = rb_enc_codelen((c = n), enc); + encidx = rb_ascii8bit_appendable_encoding_index(enc, c); + } if (n <= 0) { rb_raise(rb_eArgError, "invalid character"); } - int encidx = rb_ascii8bit_appendable_encoding_index(enc, c); if (encidx >= 0 && encidx != rb_enc_to_index(enc)) { /* special case */ rb_enc_associate_index(result, encidx); From f34297604f2b43bcec7f57b1f0ac1e2813ce58e3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Jan 2026 18:57:20 +0900 Subject: [PATCH 2435/2435] Remove a direct call of `rb_raise` in Onigmo --- regenc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/regenc.c b/regenc.c index c561cee29d210d..c595f44b29e36d 100644 --- a/regenc.c +++ b/regenc.c @@ -651,11 +651,7 @@ extern int onigenc_single_byte_code_to_mbc(OnigCodePoint code, UChar *buf, OnigEncoding enc ARG_UNUSED) { if (code > 0xff) { -#ifdef RUBY - rb_raise(rb_eRangeError, "%u out of char range", code); -#else return ONIGERR_INVALID_CODE_POINT_VALUE; -#endif } *buf = (UChar )(code & 0xff); return 1;